mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 09:40:50 +02:00
1207 lines
70 KiB
Markdown
1207 lines
70 KiB
Markdown
# Changelog
|
|
|
|
## 1.1.0
|
|
|
|
### Added
|
|
|
|
- **Battles happen on the map you were standing on.** The battle screen's
|
|
white field is replaced by the world: the mod finds the nearest patch of
|
|
open ground, points a placed over-the-shoulder camera at it, and draws the
|
|
fight over that. New **3D-BTL** row and hotkey `8`, on by default.
|
|
|
|
The arena is a 3x6 clearing of cells the player could walk on, with the
|
|
two mons three cells apart down the middle column and a one-cell apron all
|
|
round so the camera looks across floor rather than into a wall. Where no
|
|
map has room for that -- a corridor, a cave, a shop -- the search relaxes
|
|
to a 1x4 corridor with the apron given up, and where even that will not
|
|
fit the battle draws exactly as it always did.
|
|
|
|
Everything else in the frame is the engine's own. The mon pics, HUDs, HP
|
|
bars, move animations, faint slides and text box are drawn by BattleState,
|
|
in its order, at its coordinates -- the GB's own layout, with the player's
|
|
mon low and left and the enemy's high and right, which is why the camera
|
|
is placed east of the arena axis rather than the layout being moved to
|
|
suit the camera. What changes is what is behind them.
|
|
|
|
Three things carry the shot. The overworld's cast is culled before the
|
|
wipe, so it plays over an empty map and no bystander is standing in the
|
|
arena. The camera drifts on a slow orbit about a point between the two
|
|
mons, which moves the near ground and the far ground by different amounts
|
|
-- parallax, not a sliding backdrop. And a depth-of-field pass holds the
|
|
band of frame the two mons stand in sharp and softens the middle distance
|
|
and the foreground; both mons are in focus by construction, because they
|
|
are drawn as the battle screen's own pics after the pass has run.
|
|
|
|
**Nobody moves.** The arena is where the CAMERA goes. Nothing here writes
|
|
a cell, a facing, a flag or a warp, so a trainer's post-battle dialogue is
|
|
still talking to someone standing in front of them, and the blackout path,
|
|
sight lines and every script find the player exactly where they left them.
|
|
|
|
The two HUD blocks gain the backing the white field used to be. Gen 1
|
|
draws them as black glyphs straight onto the background with no box round
|
|
them, and black-on-grass is not readable; the backing is painted inside
|
|
`drawHUDs`, so it lands in the same target and takes the same zone colour
|
|
as the HUD it sits under, in both the colorized and flat pipelines.
|
|
|
|
Declines cleanly at every step it cannot take: no depth support, no open
|
|
ground, the row switched off, or a terrain mesh still building all end at
|
|
the battle screen the engine has always drawn.
|
|
|
|
### Changed
|
|
|
|
- **Characters are flat sprite billboards, and nothing about a sprite is
|
|
voxelized any more.** Every figure -- the player, NPCs, the ghosts
|
|
standing on a neighbour map -- is now its current 2D frame on a single
|
|
flat quad, with the shader's alpha discard cutting the exact silhouette
|
|
out of it. It still faces south and leans back by the camera's pitch,
|
|
so it reads face-on at every tilt exactly as before.
|
|
|
|
Two things went away with that. The contoured slab, which gave each row
|
|
a thickness measured from the sheet's own side view; and the carved
|
|
visual-hull models (`lib/VoxelModels.lua`, `tools/build_voxels.py`, and
|
|
~70 generated files under `assets/voxels/`, 2 MB), which reconstructed
|
|
a figure from its three drawn views.
|
|
|
|
A sprite is a DRAWING, not an object seen from one side. Gen 1's
|
|
overworld figures are 16x16 icons with a fixed front-on reading, and
|
|
turning one into a solid invents a body the artist never drew and the
|
|
game never implied. The shipped models were also the one place this mod
|
|
carried a description of the ROM art -- a carve is a faithful record of
|
|
a sprite's silhouette, pixel for pixel -- which sat badly against a mod
|
|
that otherwise ships no game data at all.
|
|
|
|
The flat card is cheaper on every axis: no pixel access (only the
|
|
sheet's dimensions), one quad instead of hundreds of faces, and one
|
|
mesh shared by the solid draw, the sun pass and the player's occlusion
|
|
silhouette. That sharing is load-bearing rather than tidy -- the
|
|
silhouette draws with the depth test inverted, and any self-overlap in
|
|
the mesh would read as "behind something" and repaint the figure on
|
|
open ground.
|
|
|
|
A mod can still ship `overrides/voxels/<name>.lua`; that path is
|
|
unchanged and still wins where it exists.
|
|
|
|
## 1.0.6
|
|
|
|
### Added
|
|
|
|
- **Conditional pins.** A profile entry may now carry `when_above`:
|
|
tile id -> rules keyed on the tile drawn directly north of it,
|
|
resolved per POSITION in `TileShape.at`. A pin is per tile id and one
|
|
graphic can mean two things -- the route gates' `$32` is both the
|
|
wall's dark base course and every service counter's front, and it is
|
|
the bottom row of its cell either way. Pinned `wall` the counters
|
|
stood a full 16px; pinned `counter` the deep wall banks corrugated
|
|
16/8 for sixteen rows and the room read as crates. What separates the
|
|
two uses is what sits on top, so that is what the rule reads. The
|
|
gates now have half-height counters AND level walls.
|
|
|
|
- **The Pokemon Tower has an exterior.** It is the one catalogued
|
|
building drawing the map edge cuts off (no roof band is on the map at
|
|
all), and it had no `buildings` entry, so it fell to the volume path
|
|
-- which tops a run by repeating its first two rows, laying window
|
|
courses flat across the plateau. Sealing the silhouette on the north
|
|
alone closes it (88% fill, one piece, against 37% and 126 pieces
|
|
unsealed), and one row of roof band spent on the drawing's top margin
|
|
costs no window course. It stands as a real tower, panes recessed,
|
|
door on the ground.
|
|
|
|
- **The Indigo Plateau statues stand up**, built exactly like the gym
|
|
statues: plinth a solid 16px block, figure a per-pixel cutout riding
|
|
it. On the avenue the statues stack with no gap, so the flood joined
|
|
six of them into one 24-row region and the volume builder raised
|
|
ridges of boxes with the statue art folded on the front. The same
|
|
bird is drawn at the foot of every badge-check pillar, so those are
|
|
crowned too.
|
|
|
|
### Fixed
|
|
|
|
- **Tall grass: one clump per tile.** Each 8x8 tile is a whole clump,
|
|
but the template split every tile AGAIN into its top and bottom four
|
|
art rows and stood those at two different depths -- so any blade
|
|
running down a tile was cut in half, into two 4px stubs 4px apart.
|
|
One tile is now one full-height standing slab at its own depth; a
|
|
cell's 2x2 tiles still stand independently, so the player walks
|
|
between the north and south rows.
|
|
|
|
- **Ledge lines are continuous.** `$34` is the cliff slope's foot and
|
|
also the pillar between hop-down segments; pinned `wall` with the
|
|
rest of the slope chain (the Diglett's Cave fix) it stood those
|
|
pillars 16px beside a 6px lip. At ledge height the run reads as one
|
|
lip, and the mound is unchanged -- its foot row reads as the talus it
|
|
is drawn as.
|
|
|
|
- **A prop only stands on furniture when its own cell is blocked.**
|
|
"Is something drawn above me" is not "am I standing on it": a chair
|
|
drawn against the north side of a table is above the table's trim row
|
|
too, and was being lifted onto the tabletop, with its claimed cells
|
|
re-tiled as tabletop so the table marched two rows north. Three
|
|
chairs in Cinnabar's trade room and Fuchsia's meeting room, and the
|
|
Celadon diner's stools. The world already knows the difference: a
|
|
thing that sits ON furniture occupies a blocked cell, a seat you walk
|
|
up to is in a walkable one.
|
|
|
|
- **Caves: nothing below sea level, and the water is water.** Two tiles
|
|
were identified backwards in the first pass. `$14` -- the tile the
|
|
engine animates, that `Map.WATER_TILES` names and that Surf runs on
|
|
-- was pinned `wall`, so Cerulean Cave's lake and the Seafoam sea
|
|
stood up as rock slabs. And the pale dithered rock fill was pinned
|
|
`water`, cutting 612 tiles of two-cell-wide trench through four maps.
|
|
Both corrected; a sweep of all 19 cave maps now reports zero tiles
|
|
below the datum. The elevation scheme is documented in the entry and
|
|
derived from the game's own `tilePairs`: dark floor, water and drop
|
|
holes at 0, the lit shelf a 6px step above, rock at 16.
|
|
|
|
- **Cave ladders climb.** Which ladder graphic goes up and which goes
|
|
down is unanimous in the warp table -- 37 cells of one always warp
|
|
down, 40 of the other always up -- so they are real stepped flights
|
|
now, not painted plates.
|
|
|
|
- **Poke Mart's register stands on the counter.** The pin was on the
|
|
wrong tile: `$08` is the counter's own top band, not the register, so
|
|
the standee flood ate everything but two black lines. The register is
|
|
the keypad-and-receipt drawing one row up.
|
|
|
|
- **Celadon's televisions**, which were solid 16px boxes wearing the TV
|
|
art on one face, and the **Pokemon Tower reception desk** and the
|
|
**gate counters**, which stood at wall height, are all their drawn
|
|
heights now. The **Fan Club and Silph boardroom statues** were read
|
|
as seated chairmen and painted onto the tabletop; they are cutouts
|
|
standing on their pedestals, and the tables are cut to a true
|
|
octagonal footprint.
|
|
|
|
## 1.0.5
|
|
|
|
### Added
|
|
|
|
- **Every remaining interior is furnished.** The profile covered ten
|
|
tilesets; it now covers twenty-three -- 1,190 pinned tiles across
|
|
`GATE`, `FOREST_GATE`, `LOBBY`, `MUSEUM`, `LAB`, `MANSION`,
|
|
`INTERIOR`, `CLUB`, `SHIP`, `SHIP_PORT`, `FACILITY`, `CEMETERY` and
|
|
`UNDERGROUND`, plus full entries for `CAVERN` and `GYM` which had only
|
|
stubs. Roughly 130 maps, surveyed against the standard the finished
|
|
interiors already set: one 16px wall band carrying whatever is drawn
|
|
built into it, half-cell counters so the drawn front folds up and the
|
|
top stays on top, `bookcase` collapse for free-standing shelves,
|
|
thin-pool standees for plants, and small objects riding the furniture
|
|
they are drawn above.
|
|
|
|
What the detector was doing before, by way of what changed:
|
|
|
|
- **Rooms with no walls.** Three tile ids (`$14`, `$32`, `$48`) are
|
|
claimed by the engine's water set in EVERY tileset, and collision is
|
|
per CELL, so one of them in a cell's bottom-left corner sank the
|
|
whole cell. `$32` draws both the route gates' wall base course and
|
|
every counter front, so all 25 gate maps were a checkered floor in a
|
|
moat; the same trap put ponds through two thirds of Seafoam B4F,
|
|
under every museum vitrine, along the S.S. Anne's wall corners and
|
|
across Silph Co 1F's lobby island. The set is wider than three ids
|
|
in practice -- `LAB`, `MANSION` and `INTERIOR` each hit six to nine
|
|
-- because the test is per cell, so an innocent tile sharing a cell
|
|
with a trapped one sinks with it and has to be pinned too.
|
|
- **Towers and fused monoliths.** Counters raised to 48px dragging
|
|
their wall band with them, merchandise racks fused sideways into
|
|
32px blocks four tiles deep, cave shelf edges standing as 48px fins
|
|
beside a 16px band, the Vermilion liner folded upright into a lumpy
|
|
48px slab.
|
|
- **Furniture that was not there at all.** Anything whose cell is
|
|
walkable resolved to flat ground: 59 department-store stools, every
|
|
gate lounge table and pair of binoculars, both museum staircases,
|
|
the gym-lounge chairs, and -- via the void rule -- the black
|
|
partition walls every gym is divided by, which left their white rim
|
|
columns standing as hollow 48px fins.
|
|
- **314 gravestones** in Pokemon Tower were 8px stubs, because the
|
|
volume path measured only their bottom row and dropped the arch.
|
|
|
|
Notable readings: the S.S. Anne's hull is `roof` (a drawing seen from
|
|
above, so the art belongs on the top face); the Warden's specimens and
|
|
Celadon Gym's shrubs are `cylinder` voxel balls, the first indoor use
|
|
of that class; the Fan Club's octagonal boardroom table is `counter`
|
|
rather than `table`, because only a counter rides its upper rows onto
|
|
the top face in drawn order -- which is what draws the seated chairman
|
|
exactly once, the Pokemon Center couch case verbatim.
|
|
|
|
Fuchsia Gym's invisible maze is deliberately left flat. Raising it
|
|
would read better as a room, and would also hand the player the
|
|
solution; a shape is purely presentational, so the drawn answer wins
|
|
and the gym plays as the flat game does.
|
|
|
|
### Fixed
|
|
|
|
- **A profile pin now outranks the door fold.** `Structures.forMap`
|
|
folds a door cell into its facade so the doorway does not punch a hole
|
|
in the wall -- but it overwrote the resolved shape unconditionally,
|
|
including for AUTHORED tiles, which contradicts rule 1 of the
|
|
documented resolution order. Any pin on a tile its tileset also lists
|
|
in `doorTiles` was dead on arrival: all four Celadon Mansion
|
|
staircases and Pokemon Mansion 3F's descent are door tiles, so
|
|
`stair_*` pins there silently did nothing and the flights stayed
|
|
painted flat on the floor. The fold now skips authored tiles.
|
|
|
|
## 1.0.4
|
|
|
|
### Added
|
|
|
|
- **Viridian Forest grows real trees.** Nearly everything drawn in the
|
|
forest is ROUND, and the detector was boxing all of it: the big trees
|
|
came out as ragged mixed-height volumes (their sparse canopy-rim
|
|
tiles read 0px against 32px bodies, leaving gap-toothed hedge walls),
|
|
the stump rows merged into 16px crate walls wearing folded stump art,
|
|
and the trail signs were broken piles -- their $32 tile is the
|
|
water-fallback trap and recessed into a pond lip in the middle of the
|
|
woods. All of it is now profile-pinned to the treatments the rest of
|
|
the world already uses: every tile of the tree drawing (ball, rim
|
|
wisps, feet) and the stumps take the per-cell voxel HULL the overworld
|
|
border forest wears -- a tree spans 2x2 cells, so its four
|
|
quarter-hulls tile into one big lumpy canopy, and each stump becomes a
|
|
round bollard; the signs take the standing thin-slab `signpost`
|
|
treatment every town sign gets; and the white sparkle filler inside
|
|
the tree masses is flat ground instead of an invisible zero-height
|
|
box. The whole map now resolves to hulls, signs, ledges and ground --
|
|
a detector sweep finds no stray boxed column anywhere.
|
|
|
|
Two refinements over the first cut, both new hull-builder abilities.
|
|
A tree's drawing spans 2x2 CELLS, and per-cell hulls unfolded it onto
|
|
the ground -- the ball's top half sat one cell north of its bottom
|
|
half at the same elevation, reading as a tree cut in half. The new
|
|
`canopy` class pins the drawing's corner tile as a group anchor and
|
|
the whole 2x2-cell drawing carves as ONE 32px hull, so every tree is
|
|
a single tall round canopy (the carver is now parametric over its
|
|
canvas size, and hull stamps carry their footprint radius). And the
|
|
stumps' drawn tops are a CUT FACE -- an ellipse of growth rings seen
|
|
at an angle, not body: the new `stump` class builds the hull from the
|
|
bark rows alone and projects the ellipse across the round flat top,
|
|
near arc to the south (`stump_cap` names the ellipse's drawn height),
|
|
so the rings ride the round part in perspective.
|
|
|
|
- **Flowers stand up, and keep swaying.** The animated meadow tile
|
|
($03, the one tile the overworld animates by frame rewrite) now
|
|
renders as a billboard one voxel deep: the drawing's darkest tones
|
|
plus everything they enclose are cut out per pixel, and the ground
|
|
beneath is synthesized from the commonest flat neighbour, exactly
|
|
like the ground under a detected prop.
|
|
|
|
The interesting part is that the cutout still animates. A mesh is
|
|
static, so the geometry spans the UNION of the mask over the base art
|
|
and all three animation frames, and the animation lives entirely in
|
|
the texture: TerrainAtlas already rewrites the flower's slot in the
|
|
private animated atlas each step, and for this tile it now writes
|
|
only the current frame's mask opaque with everything else keyed to
|
|
alpha 0 -- which the voxel shader discards, and the shadow pass with
|
|
it. The standing silhouette trims itself frame by frame in texture
|
|
space, off the same engine clock as the flat path, without a vertex
|
|
moving. The class is derived, not authored: any frames-animated tile
|
|
resolves to the new `flower` class with no profile entry, the same
|
|
way tall grass derives from `grassTile` (hand-authoring still wins).
|
|
|
|
- **The cuttable bush is a standing cutout.** The four tiles Cut
|
|
deletes ($2D/$2E/$3D/$3E -- across the whole tileset they appear only
|
|
in the five cut-tree blocks) are pinned to the thin `prop` pool: a
|
|
per-pixel standee 5 voxels deep, black-outline segmented with its
|
|
enclosed pixels kept, the drawn grass dither flooding away. It
|
|
stands on plain grass ($2C) -- the very tile Cut leaves behind per
|
|
field.cutTreeSwaps -- via the profile's new `prop_ground` key, which
|
|
names the tile painted under a pinned prop instead of whatever flat
|
|
tile its neighbours vote in.
|
|
|
|
- **Gym statues: a solid plinth, a standing bird.** The statue pair
|
|
flanking every badge gym's aisle (and Bruno's room) is one cell of
|
|
figure over one cell of plinth. The plinth ($22/$23/$32/$33) is
|
|
pinned `wall`: a solid 16px block. The figure ($02/$38/$12/$13) is
|
|
pinned `prop`: a 5-voxel cutout that stands ON the plinth through the
|
|
authored-box support rule, its checkered background flooded away and
|
|
the pixels its outline encloses kept.
|
|
|
|
The whole statue keeps ONE cell of footprint. The support rule used
|
|
to extend the box under the claimed cell (the monitor-on-desk path),
|
|
which marched the plinth a second block backwards; a figure whose
|
|
support is a FULL-HEIGHT block now collapses instead -- the drawn
|
|
figure cell becomes synthesized floor, since the block below already
|
|
carries the whole base. Furniture supports keep the extension: their
|
|
drawn cell is the furniture's own upper rows, and floor there would
|
|
amputate the desk. The round boulder drawn beside some statues is
|
|
deliberately NOT pinned -- it also tiles wall-to-wall as Pewter's
|
|
rock rows, which are scenery for the detector.
|
|
|
|
- **Lt. Surge's trash cans stand up.** The can ($0B/$0C/$1B/$1C, the
|
|
lone graphic of blocks 38/39) takes the same treatment as the
|
|
cuttable bush: a 5-voxel `prop` cutout, black-outline segmented with
|
|
its enclosed pixels kept, standing on the gyms' main floor tile
|
|
($11) via `prop_ground`.
|
|
|
|
- **The Poke Marts furnished to the Center's standard.** The MART
|
|
tileset shares the Center's atlas image but is its own id, so none
|
|
of the Center's pins applied, and every mart was raw detector
|
|
output: a 32px double-height display band for a back wall, the two
|
|
shelf racks fused into one four-tile-deep monolith, and the clerk's
|
|
booth towered into a 48px slab wearing the juice poster. Pinned the
|
|
way the finished interiors are -- the back wall's SALE cases and
|
|
drink fridges one 16px face like the Center's healing consoles, the
|
|
racks collapsed to one-cell-deep shelves at drawn height like Red's
|
|
bookcases, the counter half a cell with the poster riding its top
|
|
like the nurse's tray, and the cash register standing ON the counter
|
|
through the authored-box support rule. One 4x4 layout serves every
|
|
city, so this covers all eight marts.
|
|
|
|
### Fixed
|
|
|
|
- **Cut trees now vanish in voxel mode -- and grow back.** The
|
|
engine's Cut path swapped the block with a raw `setBlock` + renderer
|
|
rebuild, never emitting `world.block_replaced` -- so this mod's
|
|
listener (which rebuilds the map's mesh exactly for this) never
|
|
heard about it, and the diorama kept showing the tree. The engine
|
|
now routes Cut through `replaceBlock`
|
|
(src/world/OverworldController.lua), whose whole purpose -- per its
|
|
own comment, "Victory Road barriers, Cut trees" -- is that same swap
|
|
plus the event. The regrowth path had the same hole one door away:
|
|
cut trees are restored block by block when the map is re-entered,
|
|
and the card-key doors are stamped closed on floor load, both
|
|
through the same silent `setBlock` -- with the mesh cache staying
|
|
warm across a round trip (that is what prevLive is for), the world
|
|
kept showing the stump you left. Both paths now announce each block.
|
|
|
|
- **A block edit no longer blinks the world down to 2D.** The
|
|
listener used to drop the edited map's mesh outright, and mesh
|
|
builds are asynchronous -- so cutting a tree (or stepping out of a
|
|
door onto a map whose trees just regrew) flashed the flat 2D world
|
|
for the frames the rebuild took. `ChunkMesher.refresh` rebuilds in
|
|
place instead: the stale mesh keeps drawing, the replacement cooks
|
|
in the background, and each slot swaps as its build lands -- the
|
|
tree pops out (or back in) with the scene never leaving 3D.
|
|
|
|
- **Flowers cull the player correctly from every angle.** The flower
|
|
billboards were baked into the terrain mesh, which draws without
|
|
the characters' camera-ward pull -- so a walker standing among
|
|
flowers won the depth test against ALL of them, including the
|
|
flower south of their feet that should overdraw them. The flower
|
|
quads now ride their own mesh, drawn after the characters with
|
|
exactly the characters' pull (the tall-grass trick): the flower in
|
|
front of a walker occludes their feet, the one behind them hides,
|
|
at every camera angle. Unlike grass the flower mesh still casts
|
|
shadows -- it is a handful of cutouts per meadow, not thousands of
|
|
tufts.
|
|
|
|
- The `voxel_anim_probe` driver crashed on engine builds without the
|
|
optional `TileRenderer.animFrame` seam, and again on tilesets whose
|
|
animation list carries a "toggle" entry (spinner rooms), which claims
|
|
a tile LIST rather than one slot. It now reads the clock through the
|
|
mod's own fallback chain and skips toggle entries in the placement
|
|
census.
|
|
|
|
- **The shoreline no longer opens into the sky beside buildings and
|
|
signs.** Water recesses 2px below the ground, and the ground tile beside
|
|
it closes the step with a small below-ground side band -- but a tile
|
|
CLAIMED by a standing object (a building footprint, a sign standee, the
|
|
bushes ringing Fuchsia's ponds) only painted its synthesized flat ground
|
|
and never emitted sides. Along every stretch where such a tile met
|
|
water, the two-pixel step was an open slit straight through to the sky
|
|
behind the mesh. The skip branch now emits the same below-ground bands
|
|
ordinary ground does, cut from the synthesized ground's own art, so the
|
|
shoreline lip is continuous whatever stands on the bank.
|
|
|
|
- **Edge-row buildings keep their facades.** The south wall of Saffron's
|
|
row houses -- profiled buildings whose front row is the map's last tile
|
|
row -- lies exactly on the boundary plane shared with Route 6, and the
|
|
prebuilt-quad keep rules dropped it: the strict body test excludes the
|
|
plane, and the closed neighbour mask (which exists to kill ring scraps
|
|
whose rects sit exactly on that line) swallowed what was left, so from
|
|
Route 6 the houses stood hollow. The two cases are geometrically
|
|
identical degenerate rects, but they FACE opposite ways: a face pointing
|
|
away from the body is this map's own facade and nothing in the
|
|
neighbour will ever draw that plane, while a face pointing into the
|
|
body is the scrap the mask is for. The mesher now reads the winding and
|
|
keeps outward faces on the body's boundary planes, on all four edges.
|
|
|
|
The roof RIM had the same problem one step further out: an edge-row
|
|
house's eave overhangs `frontEave` voxels PAST the boundary plane into
|
|
the neighbour's airspace, and those quads are neither on the plane
|
|
(the winding rescue) nor over the body -- the neighbour-body mask ate
|
|
them as ring scraps, so from across the seam the roof edge was open
|
|
sky at low camera angles. Building placements only ever scan the map
|
|
BODY, so every building quad is this map's own structure by
|
|
construction: they now carry an `own` flag the edge keep-rules never
|
|
touch.
|
|
|
|
- **Diglett's Cave mounds (and cliffs everywhere) stop sprouting
|
|
towers.** Two detector misreadings stacked up on the cave-entrance
|
|
mound. The dark east slope of the cliff drawing ($02/$24/$34) is one
|
|
texture repeated over the mound's whole height, but its corner tiles
|
|
break the repeat scan, so those columns rose to 32px -- the rock
|
|
pillar beside the entrance. And a folded doorway column reads its own
|
|
drawn extent (the door plus everything above it), which is a house's
|
|
real height when the door is a house's, but a 32px tower over a 16px
|
|
plateau when the door is a cave mouth -- the entrance jumped a block
|
|
above the mound around it. The slope chain is now profile-pinned to
|
|
one 16px course, and a doorway column answers to its REGION entirely:
|
|
height from the region's dominant column, top flat when those columns
|
|
are flat repeats (the mound) and roofed when they are drawn facades
|
|
(a house). Both cave entrances -- and every cliff built from the same
|
|
slope tiles -- now read as one level mesa with the cave mouth at
|
|
ground level. A new `voxel_mound_probe` driver prints the detector's
|
|
per-column class and height over any rectangle, which is how this was
|
|
diagnosed.
|
|
|
|
Routes 3 and 4 had a third variant of the same misreading: the repeat
|
|
scan anchors at a column's FRONT tile, and a plateau column that ends
|
|
in a one-off rounded corner tile ($13/$35) never matched -- it read
|
|
its whole capped extent and shot up as a 48px fin (several together
|
|
made a tent). When the two rows directly above the front are
|
|
identical, the column is now read as that repeat wearing a trim foot:
|
|
its unit is one course plus the trim. Doorway columns still answer to
|
|
their region first, so houses are untouched.
|
|
|
|
## 1.0.3
|
|
|
|
### Added
|
|
|
|
- **The player shows through whatever hides them.** Occlusion in this mode is
|
|
the real thing -- walk north of Red's house and the roof is genuinely in
|
|
front of you -- but a player who cannot see their own character has lost
|
|
track of where they are standing, which the flat game never allowed. The
|
|
figure now draws a second time as a translucent silhouette wherever the
|
|
world is in front of it.
|
|
|
|
No code anywhere asks whether the player is occluded: the depth buffer
|
|
already knows, and the test is the question. The silhouette is drawn with
|
|
the depth compare INVERTED -- `greater` where the scene uses `lequal` --
|
|
so it appears exactly where the ordinary draw would have lost, and nothing
|
|
at all is drawn when nothing is in the way. LOVE hands the compare straight
|
|
to `glDepthFunc`, so the two are true complements with no seam between
|
|
them.
|
|
|
|
It goes down BEFORE the characters, so the only thing it can meet in the
|
|
depth buffer is the world -- terrain, buildings, trees. Drawn after the
|
|
solid pass it would meet the player's own card instead, and every fragment
|
|
of a figure sits behind the one that just wrote it, so it would paint over
|
|
the player permanently. Characters then draw on top as usual.
|
|
|
|
It uses the FLAT card (`SpriteBillboards.shadowQuad`), not the relief slab
|
|
the solid pass draws. The slab carries front and back faces and the mode
|
|
culls neither, so with the test inverted its own back faces -- a few voxels
|
|
deeper than the front ones that just won -- read as "behind something", and
|
|
the figure repaints itself on open ground whether or not anything is in
|
|
front of it. One quad has no self-overlap, which is exactly why the shadow
|
|
pass already uses this mesh, and it cannot double-blend into a mottled
|
|
patch either. A silhouette is an outline, so the outline is the right mesh.
|
|
|
|
Depth writes are off: the pass is behind the scenery by definition, and
|
|
writing would file the hidden figure in front of the building hiding it,
|
|
which the grass pass at the end of the frame reads. The card carries the
|
|
same transform and the same camera-ward pull as the solid draw (both now
|
|
come from one shared `billboardMatrix`/`billboardPull`, so they cannot
|
|
drift), which is what keeps the leaning-over-a-near-wall case out of it:
|
|
pull already won that fight for the solid draw, so a character merely
|
|
standing close to a wall does not shimmer a silhouette over it.
|
|
|
|
It is drawn as ONE flat translucent grey, not as a dimmed copy of the
|
|
sprite. Tinting through the vertex colour could only MULTIPLY the sprite's
|
|
own pixels, which darkens each one by its own amount and keeps all the
|
|
character's internal detail -- a murky picture of Red rather than a shape.
|
|
So the fragment shader carries a `ghost` / `ghostColor` pair and replaces
|
|
the colour outright, last in the chain so neither the sun nor a voxel seam
|
|
can mottle it. Staying translucent is what keeps it reading as "behind
|
|
that wall" rather than as a hole punched through it.
|
|
|
|
`Voxel3D.GHOST_COLOR` and `GHOST_ALPHA` (0.5) are the knobs. Only the
|
|
player gets this -- NPCs and the ghosts standing on a neighbouring map are
|
|
left to honest occlusion, because it is only your own character you cannot
|
|
afford to lose behind a roof.
|
|
|
|
## 1.0.2
|
|
|
|
### Fixed
|
|
|
|
- On Android the diorama drew into the top-left corner at a fraction of the
|
|
screen -- about a third of the width and height on a 420dpi panel -- with
|
|
the field effects (dust, emotes, the cut-tree shudder) correspondingly
|
|
oversized against the world they sat on. Desktop was unaffected.
|
|
|
|
The pipeline ctx hands over `width`/`height` measured in LOVE UNITS
|
|
(`love.graphics.getDimensions`), but the engine composites a pipeline's
|
|
returned canvas with `draw(canvas, 0, 0, 0, 1/dpiX, 1/dpiY)` -- a scale
|
|
that only covers the window if the canvas is at PIXEL resolution. Sizing
|
|
the scene canvas from the ctx therefore paid the DPI scale twice: the
|
|
canvas came out that much smaller, and was then drawn that much smaller
|
|
again. On desktop the two units are the same number and nothing shows;
|
|
Android's DPI scale is the display density (2.625 at 420dpi), so that is
|
|
where it surfaced.
|
|
|
|
The scene canvas is now sized from `love.graphics.getPixelDimensions`
|
|
directly rather than from the ctx. That is the number a fixed engine would
|
|
hand over, so this does not double-correct if the ctx is ever changed to
|
|
agree with the compositor. It also squares the FX pass for free:
|
|
`ctx.scale` was ALREADY in pixels per world pixel (`Zoom.scale` over
|
|
`Renderer:fitScale`, which measures the drawable), so the closures were
|
|
being scaled for a canvas 2.6x bigger than the one they were drawing into
|
|
-- one wrong number, not two.
|
|
|
|
## 1.0.1
|
|
|
|
### Fixed
|
|
|
|
- The RED++ texture-readback fallback in `TerrainAtlas` never worked on real
|
|
drivers: LOVE refuses `Canvas:newImageData` while that canvas is currently
|
|
active, and `readback()` read the pixels back before restoring the previous
|
|
render target, so the call threw on every driver rather than only on
|
|
stubborn ones. The previous target is now put back BEFORE the read. The
|
|
headless suite could not see this -- its stub canvas does not enforce the
|
|
rule -- so it survived until a live probe ran the chain unguarded.
|
|
|
|
Harmless for vanilla tilesets, where the CPU rebuild (`gbcPixels`) answers
|
|
first and the fallback is never consulted; it was the last-resort route for
|
|
a map the palette pack does not know, which until now had no working route
|
|
at all.
|
|
|
|
## 1.0.0
|
|
|
|
First release, ported from the engine-internal voxel branch onto the
|
|
`render_pipelines` mod API.
|
|
|
|
Interior furniture gets the shapes it depicts
|
|
(mods/DRAMATIC_SHAPE/tools/voxel-survey.md is the procedure that found and
|
|
verified these).
|
|
|
|
Buildings stop being boxes wearing their own elevation. A profiled
|
|
building is voxelized from its own sprite, band by band -- the pipeline
|
|
written up in `assets/docs/buidling_to_voxel/`.
|
|
|
|
Terrain meshing goes asynchronous, instanced and bounded. The first voxel
|
|
frame used to build every neighbourhood map synchronously (a ~2.4s
|
|
freeze), retain every map's analysis forever (gigabytes over a
|
|
cross-region trek), and string stray pixels along map seams.
|
|
|
|
Real shadows. The sun moves to the southeast and drops to 45 degrees, so
|
|
shadows fall northwest -- up and to the left on screen -- and run about as
|
|
long as the thing throwing them is tall.
|
|
|
|
### Added
|
|
|
|
- `voxel` render pipeline: 3D diorama overworld with extruded terrain,
|
|
depth-buffered occlusion, leaning sprite billboards, drop shadows and
|
|
contact AO. VOXEL options row and hotkey `3` (OFF / 15 / 35 / 50 / 75).
|
|
- `tiltshift` render pipeline: a `worldPresent` post-process giving the
|
|
miniature-photo look. T-SHIFT options row and hotkey `6` (OFF / 1 / 2 / 3).
|
|
- Carved voxel models for 67 overworld sprites, plus the
|
|
`tools/build_voxels.py` that produced them.
|
|
- `data/voxel_heights.lua`, the hand-authored tile shape profile.
|
|
|
|
- Furniture shape classes in the profile: `bed` (a low slab wearing its
|
|
top-down art), `table` and `desk` (boxes at their drawn height whose
|
|
faces fold the artwork up), `relief` (a prop drawn from above -- a
|
|
game console -- lying flat and extruding a few voxels inside its
|
|
outline), the standee pools `billboard` (10px) / `prop` (5px) /
|
|
`stool` (5px, and characters standing on its walkable cell sit at
|
|
seat height) / `cutout` (one voxel: pure profile, for the vase on the
|
|
table), and the stair archetypes `stair_e`/`stair_w` (a rising flight
|
|
of real steps) and `stair_down_e`/`stair_down_w` (a sunken stairwell
|
|
descending below the floor -- stairs that lead down). Separate pools
|
|
cluster separately, so touching drawings never merge into one cutout.
|
|
- Standee clusters split into per-pixel connected components, each
|
|
standing on its own feet in the depth band of the row it is drawn in:
|
|
two stools stacked in adjacent cells become two stools, and no
|
|
fragment of a drawing ever floats at its bounding-box height. A
|
|
`cutout` keeps only its largest component -- a cast shadow's drawn
|
|
edge is background, not a floating scrap.
|
|
- `bookcase` class: free-standing shelf drawings collapse in ranks onto
|
|
one-cell-deep boxes at their full drawn height, back rows becoming
|
|
hidden floor; the trim row above a rank -- undetected structure or a
|
|
row pinned `table`, since the same trim tiles cap other furniture --
|
|
is adopted as its cap. Pinned for Oak's Lab (`DOJO`).
|
|
- Oak's Lab tables pinned `table`: the starter-ball display and the
|
|
north tables stand at real table height, the display frame's black
|
|
corner brackets no longer auto-extract into standing prisms, and the
|
|
Poke Ball / Pokedex sprites ride the authored height onto the
|
|
tabletops.
|
|
- Pinned props drawn directly above a pinned box stand ON it: the PC
|
|
monitor on its desk, the flower pot on the dining table.
|
|
- Profile pins for `REDS_HOUSE_1` / `REDS_HOUSE_2` (Red's house and the
|
|
Copycat's, both floors): bed, stools, tables, PC desk with its
|
|
standing monitor, bookcases, TV standing on the floor behind the game
|
|
console's relief, potted plant, flower pot, both staircases, and the
|
|
wall/window band.
|
|
- `mods/DRAMATIC_SHAPE/tests/voxel_survey.lua`: screenshot-survey driver
|
|
behind the repeatable inspection procedure (SURVEY_MAP / SURVEY_SPOTS /
|
|
SURVEY_LEVELS / SHOT_DIR), documented in
|
|
mods/DRAMATIC_SHAPE/tools/voxel-survey.md.
|
|
|
|
- `lib/Buildings.lua`: a building archetype. Where the volume path folds a
|
|
whole drawing upright (roof, facade and sloped ends alike) into one box,
|
|
this classifies each BAND of the drawing by the 3D surface it depicts and
|
|
applies the matching operation: top-facing rows lay flat over the
|
|
footprint, the facade extrudes straight back, an awning band juts past
|
|
the walls, and the drawn taper at the ends becomes a stepped slope in
|
|
elevation. Every visible voxel carries a real texel of the drawing, so
|
|
the model recolours with the atlas.
|
|
- A `buildings` section in `data/voxel_heights.lua`. A building is matched
|
|
by its exact tile grid (the drawings are catalogued in `assets/docs/buildings/`),
|
|
so one entry covers every map that places the same art -- Red's house,
|
|
Blue's house, Bill's, the Copycat's and the two Fuchsia houses are one
|
|
seven-placement entry, and Oak's lab is a second. Only the band table is
|
|
authored; the silhouette, the taper rate, the eave height and every
|
|
window and doorway are measured off the pixels.
|
|
- The flat-roofed civic block and its sixteen relatives -- every Pokemon
|
|
Center and every Poke Mart, Fuchsia Gym, the museum, the Game Corner,
|
|
Celadon Mansion and its department store, the Power Plant, the Route 5
|
|
and Route 22 gates, Silph Co and five anonymous scenery blocks. They are
|
|
one architecture drawn at eleven different footprints, from 4x4 cells up
|
|
to Silph Co's 8x12, and they share one band table: their lattice is drawn
|
|
from straight above, so the measured taper comes out flat and the whole
|
|
band is depth under one level roof. No new roof mode was needed -- the
|
|
drawn profile was always the shape, and a drawing with no taper simply
|
|
yields a level one.
|
|
- Pewter's museum hall (`assets/docs/buildings/B24`). It is the one
|
|
sloped-roof building in this pass, and the only one so far whose roof
|
|
texture is not a plain repeat: the drawing states its own period by
|
|
repeating the whole lattice-and-course motif, rows 8..31 again at 32..55,
|
|
so the cycle is 24 rows and the roof carries its drawn courses across the
|
|
depth instead of a bare lattice. The band below them is the roof's
|
|
fascia, wider than the wall it covers, so it belongs to the roof and
|
|
lands on the south rim. Note the museum is TWO drawings: this hall and
|
|
the east entrance beside it, which is B18 and shares its drawing with the
|
|
Route 2 gate.
|
|
- The rest of the 2:1 sloped-roof buildings: both gym drawings (the
|
|
standard one at Cinnabar, Pewter, Vermilion and Viridian plus the
|
|
Fighting Dojo, and the wider Celadon / Cerulean / Saffron one), the 4x2
|
|
cottage that houses Mr Fuji, the Cubone house, Bill's grandpa, the Name
|
|
Rater and the Viridian school, Cerulean's three wide houses, the day
|
|
care, and three scenery blocks -- among them B01, which at 19 placements
|
|
is the commonest drawing in the game. Each one's roof band is pixel for
|
|
pixel one of the three already authored, so they take that sibling's band
|
|
table unchanged: 16 rows for Red's house's, 32 for Oak's lab's, 64 for
|
|
the museum's.
|
|
- The Safari Zone rest houses and the Victory Road entrance, the first
|
|
buildings outside the `OVERWORLD` tileset -- `buildings` is keyed by
|
|
tileset and until now only had the one section. The rest house's
|
|
corrugated roof repeats every 5 rows rather than the overworld lattice's
|
|
8, which is the point of `roofCycle` being authored per building.
|
|
- Route 10's scenery block, via a new `seal` field. Its drawing has no
|
|
black base course -- it ends on a row of light brick -- so the silhouette
|
|
flood climbed in from the south border through the mortar and hollowed
|
|
the wall out, leaving 72% of the sprite in 65 pieces. `seal` names the
|
|
sides a drawing runs off rather than closing, and the flood does not seed
|
|
there: sealed, it is 95% in one piece, and the model is its twin the
|
|
museum's. No other building sets it, and none changes by a voxel.
|
|
- Together these take the mod to 31 of the catalogue's 34 drawings and 144
|
|
of its 147 placements. The last three, and why each resists, are written
|
|
up in `assets/docs/buildings/REMAINING.md`.
|
|
- A roof no longer runs past the drawing's own silhouette. These sprites
|
|
are inset from their boxes, and the columns outside the inset carry no
|
|
roof: they now get none, and the fascia belongs to the outermost columns
|
|
the drawing actually paints. Before, they raised a four-voxel slab of
|
|
black kerb the full depth of the building, flanking its walls at ground
|
|
level. Nothing shipped hit this -- Red's house and Oak's lab are drawn
|
|
edge to edge -- so no existing model changes by a single voxel.
|
|
- Windows and doorways sink a voxel behind their frames, found rather than
|
|
listed: a pane is a non-black region the drawing seals off behind its own
|
|
black outline. A nested frame (the door's own little window) layers for
|
|
free.
|
|
- `tools/building_voxels.py`: the reference implementation of the same
|
|
algorithm, with the geometric asserts and isometric previews Stage 5 of
|
|
the methodology calls for. It and the runtime agree exactly on voxel and
|
|
shell counts for all 19 templates -- 96,617 / 12,866 for Red's house up
|
|
to 3,715,963 / 146,762 for Silph Co, which ship as 3,410 and 13,994
|
|
quads. Its slope asserts read the drawn columns only and stand down for a
|
|
roof with no taper, where the check is that the roof is level instead;
|
|
its previews fit the projection to the model rather than to a fixed
|
|
camera, so a building taller or deeper than the first two still lands on
|
|
the canvas.
|
|
|
|
- A sky at the 75-degree rung. Pitched that far over, the horizon comes
|
|
into frame and a good part of the picture is void, so the void gets
|
|
filled instead of reading as the black plate it does at every rung below.
|
|
No skybox and no geometry -- it is the colour the scene canvas clears to.
|
|
|
|
**Outdoor maps only.** A house, a cave or a gym is a room with a ceiling,
|
|
and the void past its walls is the outside of a box rather than open air.
|
|
The test is `Map.isOutdoor`, the same one the engine uses for door SFX
|
|
and the town map, and the same one `Structures` already asks to decide
|
|
whether a map rings with trees.
|
|
|
|
The colour is a four-shade ramp shaped like a world palette and run
|
|
through `PaletteFX.effectiveColors`, so it answers to the display mode
|
|
exactly as the baked terrain does: blue in the colour modes, grey under
|
|
GRAY, green under CLASSIC, dark under GBC INV. A hardcoded blue would sit
|
|
wrong in every mode that is not a colour mode. It fades across the
|
|
approach to the top rung rather than switching at the keypress, so it
|
|
arrives with the camera tween.
|
|
|
|
- The mod's four controls sit on adjacent keys, and the two that never had
|
|
one now have a hotkey at all:
|
|
|
|
| key | control |
|
|
| --- | --- |
|
|
| 3 | VOXEL, the camera ladder |
|
|
| 5 | V-GRID, the wireframe |
|
|
| 6 | T-SHIFT, the blur ladder |
|
|
| 7 | V-CURVE, the horizon bend |
|
|
|
|
Only 6 arrives by the documented route. `Game:keypressed` answers the
|
|
engine's own display keys first and returns -- 2 COLORS, 3 TILT, 4 ZOOM,
|
|
5 GBC FX -- and only then offers the key to `Pipelines.hotkey`, expressly
|
|
so that "a pipeline can never shadow one". Two of the four wanted keys are
|
|
in that set, and V-GRID and V-CURVE own no render pass, so they have no
|
|
registry to claim a key from in the first place. The mod wraps
|
|
`Game:keypressed` to take the four. Polling the keyboard in `update`
|
|
would not do: it fires alongside the engine's handler rather than instead
|
|
of it, so 3 would cycle this mode AND the engine's TILT on one press.
|
|
|
|
**TILT (3) and GBC FX (5) are no longer reachable by key while this mod
|
|
is enabled.** Both are still on the OPTIONS menu. Key 9 is now free.
|
|
|
|
So the VOXEL key turns both off itself, on every press. Both fight the
|
|
diorama -- TILT is the flat fake of what this mode does for real, GBC FX
|
|
a full-screen present pass laid over the top -- and with 3 the only key
|
|
that now reaches either, it also has to be the way back from having left
|
|
one on. Every press, not just the one that switches the mode on: the
|
|
registry's own tilt exclusion covers switching ON, but the press that
|
|
cycles the ladder round to OFF would otherwise leave both running with
|
|
no key left to clear them.
|
|
|
|
The wrapper delegates rather than reimplements: `Pipelines.hotkey` still
|
|
applies its own gate and ladder, the settings borrow the same free-roam
|
|
gate the voxel pipeline uses, and a screen with its own key handler keeps
|
|
the keyboard -- so typing a nickname cannot cycle a render mode behind
|
|
the text box.
|
|
|
|
- `lib/ShadowMap.lua`: the scene rendered once from the sun into an
|
|
orthographic depth map, which the main pass then samples per fragment.
|
|
What the sun cannot see is in shadow, whatever surface it is, so a
|
|
shadow climbs a wall, drapes over a roof and slides across a passing
|
|
NPC with no case in the code -- and every caster is simply whatever the
|
|
pass draws. The terrain mesh goes in, which means buildings, trees,
|
|
ledges, signs and every prop cast, where before only characters did.
|
|
- Depth is packed into two 8-bit channels of an ordinary color canvas
|
|
(~16 bits over a ~700px frustum, a hundredth of a world pixel).
|
|
Readable depth textures are the least portable corner of the graphics
|
|
API, and the mod's contract is that an unsupported driver falls back
|
|
rather than errors: `available()` reports, and VoxelScene keeps the old
|
|
flat decals when it says no.
|
|
- The map resolution is picked per frame from a 1024/1536/2048 ladder
|
|
against a 0.45 world-pixels-per-texel target, because the light frustum
|
|
is fitted to the world view and that swings 3x between the closest zoom
|
|
and a maximised window at the widest. The frustum is snapped to whole
|
|
texels, without which every shadow edge in the world crawls as you walk.
|
|
- Ambient occlusion, the genuine article: each vertex counts the
|
|
neighbours crowding it and steps down once per neighbour, on top faces
|
|
(four corners, three neighbours each) and now on upright faces too --
|
|
the crease a wall rises out of, and the inside corners where flanking
|
|
columns box it in. It is the complement of the shadow pass rather than
|
|
a duplicate: the map draws the long directional shadow, this draws the
|
|
dark seam in every corner the sky cannot see into, at scales finer than
|
|
a shadow map texel.
|
|
- Plus a ground-contact term for the prebuilt prop quads -- per-pixel
|
|
plants, signs and lone trees, and the round-tree stamps. Those arrive
|
|
from Structures already finished, so the neighbour counting has no
|
|
columns to count; what it can still say is that the floor blocks half
|
|
the sky, so a voxel's first 6px of rise ramps back to full light. It is
|
|
what stops a prop reading as pasted over the ground rather than
|
|
standing on it, and outdoors it is most of what AO does at all, since
|
|
trees and posts are nearly all prop geometry.
|
|
- One knob for the lot: `AO_STRENGTH` in `ChunkMesher` scales every term
|
|
(they are written as darkening amounts, not multipliers), against a
|
|
floor that keeps a crank from punching holes of pure black.
|
|
- The **V-CURVE** row in OPTIONS: the curved world, the Animal Crossing
|
|
horizon. Every vertex is pushed down by the square of its horizontal
|
|
distance from the camera's focus, and that is the whole effect -- a
|
|
quadratic is nearly zero near its vertex, so the ground being played on
|
|
stays flat, and the falloff accelerates, so the far edge rolls away over
|
|
a near horizon and the town reads as sitting on a small sphere.
|
|
- It is deliberately NOT a fisheye. A fisheye is a LENS -- a screen-space
|
|
warp -- which bends straight lines everywhere including right in front of
|
|
the player, resamples every pixel to do it, and would leave this mode's
|
|
art blurred and crawling. Bending the WORLD is one line in the vertex
|
|
shader: lines near the camera stay straight and not a pixel is resampled.
|
|
- Displacing along Y only is what keeps it readable rather than
|
|
nauseating: the drop depends on where a column stands, not how tall it
|
|
is, so the world tips away and the buildings on it stay upright.
|
|
- Shadows and the wireframe ride along for free. Both are already worked
|
|
out before the bend -- the shadow map in flat world space, the grid in
|
|
model space -- so the bend carries them exactly as if they had been
|
|
painted on, and neither the light frustum nor the grid needs to know the
|
|
curve exists. `Voxel3D.project` applies the same drop on the CPU, which
|
|
is what keeps the overworld's 2D field FX on their ground points.
|
|
- The strength scales with the view height, so a rung reads the same at
|
|
every zoom, and the ladder is calibrated against the far edge of the
|
|
visible ground rather than against nothing.
|
|
- `lib/ModSetting.lua`: the ladder/store/rows a setting of this mod's own
|
|
needs, now that there are two of them. V-GRID's copy of it moved here.
|
|
- A **75 degree** rung on the VOXEL ladder, below the 15/35/50 it shared
|
|
with the engine's TILT: low enough to read as a diorama shot from table
|
|
height. Tilt could not have it -- its flat plane degenerates into a
|
|
horizon line down there -- but geometry only gets more of itself to show.
|
|
- The **V-GRID** row in OPTIONS: a one-display-pixel wireframe along every
|
|
voxel edge, 3D Dot Game Heroes style. Every mesh here is built one unit
|
|
per voxel in its OWN model space, so the seams are that space's integer
|
|
planes, and reading them in model space rather than world space is what
|
|
keeps them glued to a thing however it is posed -- a character's slab
|
|
leans back by the camera's pitch and its seams lean with it.
|
|
- The seams fade out where a voxel shrinks under about 3 display pixels.
|
|
Survey zoom draws a world pixel at roughly a display pixel, and a wall
|
|
seen nearly edge-on squashes one to nothing at any zoom; drawn anyway,
|
|
the lines land closer together than they are wide and the wireframe
|
|
stops being a wireframe and becomes a flat 45% dimming of the scene.
|
|
- `lib/VoxelGrid.lua` owns the toggle. It is NOT a pipeline: it owns no
|
|
pass of the frame, it parameterises the voxel one, so it has nothing to
|
|
put in `drawWorld` or `present` and the registry rightly rejects it. A
|
|
plain mod setting instead -- `options:define` for the store and the mod
|
|
manager's page, `ui.options.rows` for the row in OPTIONS next to VOXEL
|
|
and T-SHIFT. Both rows read and write the one stored value.
|
|
- The wireframe is a SECOND COMPILATION of the scene shader rather than a
|
|
branch inside it, because it needs shader derivatives (`fwidth`) -- the
|
|
one part of the mode a driver can refuse. A refusal costs the grid and
|
|
nothing else.
|
|
- `mods/DRAMATIC_SHAPE/tests/voxel_shadow_probe.lua`: reports the fitted
|
|
frustum and the resolution rung, dumps the map itself, and shoots a
|
|
stand point at every pitch. `SHADOW_SUN="kx,kz"` retunes the bearing for
|
|
one run, `SHADOW_GRID=1` forces the wireframe on, and `SHADOW_ZOOM` pins
|
|
the zoom, without which two runs are not comparable -- a driver inherits
|
|
whatever the player left in `options.lua`, and the world view size (which
|
|
the light frustum is fitted to) swings 3x across that range.
|
|
|
|
- A `counter` class (8px, upright): half-cell furniture. One 8px band,
|
|
so exactly the drawing's bottom row stands up as the front and every
|
|
row above it rides the top face in drawn order. `table`'s 12px could
|
|
not be retuned for it; the houses share that class.
|
|
|
|
- `tilesets.POKECENTER` in `data/voxel_heights.lua`. Before it, the
|
|
detector merged the wall-touching counters and healing machines into
|
|
the wall band and towered them 3-6 blocks, flattened the machines'
|
|
near-black screens to void, read the pillar bases, plant pots and
|
|
machine bodies as ponds (the $14/$32/$48 stale-cache water fallback),
|
|
boxed each plant pair into one hedge cube, and extruded the lounge
|
|
seat -- a PERSON is drawn into its tile art -- into a monolith wearing
|
|
his face.
|
|
- The pins, by shape: the wall band, windows, poster, pillars and the
|
|
16px machine bodies are `wall`; the counters (with the nurse's tray)
|
|
are `counter` and the PC's desk is `table`; the machine screens and
|
|
the PC are `billboard`, standing on the pinned boxes below them; the
|
|
potted plants are `prop` standees like every other interior plant.
|
|
- The lounge couch with the man sitting on it is a `counter` box: its
|
|
bottom row stands up as the couch's front and the cushion and the man
|
|
ride the top face, each drawn exactly once.
|
|
He cannot be stood upright, and the reason is structural rather than a
|
|
tuning question. His skin pixels span two tile rows and stop dead at
|
|
the row 9/10 seam; folding two rows upright requires both to share a
|
|
class, which makes the box two tiles deep, and a fully folded box
|
|
repeats its north row across its whole top face. So every upright
|
|
arrangement puts his head on screen two or three times -- as a 16px
|
|
seat-back, on the front and twice more on the top; as a 32px bookcase,
|
|
a cabinet taller than the room's own walls. Dropping the seat in front
|
|
of him to floor level only changes which copy you see. Nor can he be a
|
|
standee: the drawing has no floor margin, so all three non-black
|
|
shades touch the cluster rim and the mask drains 307 of its 420
|
|
interior pixels -- 46% of him even segmented alone, because his skin
|
|
is the same light shade as the couch behind him.
|
|
- Survey evidence: full before/after passes of VIRIDIAN_POKECENTER at
|
|
15/35/50 degrees, plus spot-checks of CELADON_POKECENTER and
|
|
CELADON_HOTEL (shared tileset, both inherit correctly) and of
|
|
REDS_HOUSE_1F, OAKS_LAB and VIRIDIAN_CITY (unchanged -- the new class
|
|
is additive and no other tileset lists it).
|
|
|
|
### Changed
|
|
|
|
- Pinned props are segmented the way the art is authored: objects wear a
|
|
black outline, so background is the shades touching the cluster's edge
|
|
(white floor around a TV, grey tabletop around a vase) flooded in from
|
|
the aprons; the outline, its interior, paint whites and anything they
|
|
enclose survive -- pixel-perfect cutouts on any surface.
|
|
- Indoor structure analysis floods background from all four aprons and
|
|
accepts ground contact on any side (outdoors keeps the south-only rule
|
|
that protects roofs), so face-on furniture drawings voxelize per pixel
|
|
instead of rising as wall-height volumes.
|
|
- Profile-pinned standees are 10px deep (detected props stay 6px), so a
|
|
deliberate object like a TV keeps a body at shallow camera angles.
|
|
- Authored upright boxes fold their artwork up every face (flanks and
|
|
back wear the front stack darkened) and top faces keep the drawn
|
|
tabletop: face-on rows wear the row above the fold instead of
|
|
repeating their front art lying flat, and a run that folded entirely
|
|
tops with the furniture row drawn above it (a bookcase's shelf trim).
|
|
- Characters no longer ride a pinned stair tile's class height: stairs
|
|
are walked through at floor level, fixing the step-up onto thin air in
|
|
front of stairwells.
|
|
|
|
- A voxelized building is as tall as its facade plus its roof slab rather
|
|
than as tall as its drawing: the roof rows are DEPTH now, not height, so
|
|
Red's house is 36px over a 4x3-cell plot instead of a 48px cube.
|
|
- Round trees (the `cylinder` pin: lone canopies and the border tree
|
|
wall) stop being lathes -- the sprite wrapped around a 12-segment
|
|
column read as exactly that, art smeared on a barrel. Each cell is now
|
|
a real voxel hull: the canopy is segmented out of its cell as the
|
|
darkest-pixel outline plus everything it encloses (which also drops
|
|
the background grass that used to inflate every row to full width, and
|
|
the cast shadow under the ball), and each mask row runs its own span's
|
|
circular chord in depth -- the front view is the sprite pixel for
|
|
pixel, the plan view is the sprite's width profile turned in depth.
|
|
Dithered art with no closed outline (the tree wall) falls back to
|
|
light-shades-only flooding, per the methodology doc's boundary rule.
|
|
Sides de-outline like building extrusions so flanks read as canopy
|
|
rather than solid black, and dome caps keep their outline on the rim
|
|
while the interior samples the canopy a couple of rows deeper.
|
|
- A `post` standee pool, pinned for the overworld's vertical fence-post
|
|
cell (tiles 14/85 -- across every map the pair appears only as this
|
|
cell). The detector already turns HORIZONTAL fence runs (tile 57) into
|
|
per-post standees, but a vertical run of repeated cells trips its
|
|
scenery-repetition guard and fell to the volume path as a
|
|
fence-textured tower (Viridian's west line, Route 25).
|
|
`post` extracts every CELL as its own cluster -- pooled clustering
|
|
would stand the whole line up as one drawing-tall slab at one depth --
|
|
and classifies pixels the way the detector does (non-white is body)
|
|
rather than by the pinned-prop outline rule, which would strip the
|
|
posts to black skeletons; at the detector's own 6px depth, pinned and
|
|
detected fences look alike.
|
|
- Town signs move from the `billboard` pool to a new `signpost` pool: the
|
|
same per-pixel standing slab, but 2 voxels thin instead of 10. A sign
|
|
is a plate on a stick, and the standee body that keeps a TV from
|
|
vanishing at shallow angles read as a solid block of furniture here.
|
|
- The ground under a round tree matches the tree's own drawn background
|
|
instead of the map's commonest ground tile. The hull's segmentation
|
|
already knows which pixels are NOT the tree; those pixels are scored
|
|
against every flat ground tile the map places and the closest art
|
|
wins, per template -- so border trees drawn over checker grass stand
|
|
on checker grass even on a map that is mostly pale path (the old
|
|
fallback painted path under every mid-forest tree, which has no flat
|
|
neighbour to vote with). The drawn cast shadow stays out of the score:
|
|
no ground tile carries a shadow, and its darks would drag every match.
|
|
|
|
- Mesh builds stream in the background. `ChunkMesher` queues per-map
|
|
build jobs and `pump()` -- driven from the pipeline's update -- runs
|
|
them inside a few-millisecond frame budget (`lib/BuildBudget.lua`
|
|
suspends the build coroutine mid-loop when the slice is spent). The
|
|
camera tween holds at flat until the current map's terrain exists, so
|
|
toggling voxel mode shows a handful of flat frames instead of a frozen
|
|
one; neighbours pop in as they finish. Warp fades prefetch the
|
|
destination (the pipeline update ticks while the Transition covers the
|
|
screen, with a wider pump slice), so a door exit lands on terrain that
|
|
is already built.
|
|
- Vertex packing goes through FFI into one native buffer
|
|
(`Mesh:setVertices(ByteData)`) instead of a Lua table per vertex --
|
|
the headless table path remains for the pure `geometry()` API and its
|
|
suite.
|
|
- Round-tree hulls are carved once per (tileset, art, ground set) and
|
|
kept as stamps -- template plus cell offset, expanded during vertex
|
|
packing -- instead of materialized per-cell quad tables. A route's
|
|
border forest was ~500 quads x hundreds of cells of retained heap.
|
|
- Mesh and analysis caches evict down to the live neighbourhood (current
|
|
map + rendered neighbours, plus one set of history so a house
|
|
round-trip keeps the town warm). Evicted meshes are released
|
|
explicitly. Memory over Pallet -> Mt Moon: was ~2.9GB and monotonic,
|
|
now oscillates between ~90 and 200MB.
|
|
|
|
- `Voxel3D.SHADOW_KX/KZ` are -0.85 / -0.55, from +0.30 / +0.45: the sun
|
|
crosses to the southeast and drops from 62 degrees to 45. The bearing
|
|
leans WEST of northwest on purpose -- a character is drawn as a slab
|
|
leaning away from the camera, which covers the ground due north of its
|
|
feet, so a shadow thrown straight up-screen lands entirely underneath
|
|
the figure casting it and is never seen.
|
|
- `Voxel3D.SHADOW_ALPHA` 0.32 -> 0.40, a quarter darker.
|
|
- `FACE_SHADE` east 0.78 -> 0.84 and west 0.78 -> 0.72. The two were equal
|
|
because the old sun sat due northwest and they were symmetric about it;
|
|
under a southeastern sun east is a lit flank and west a shaded one.
|
|
- A character's shadow lookup runs off the UPRIGHT card the sun saw, not
|
|
the leaning slab the camera sees (`Voxel3D.draw`'s `sunModel`). Casting
|
|
the leaning slab instead would shrink every shadow to nothing as the
|
|
camera flattened toward top-down; looking up with the leaned position
|
|
put each sprite's own card across its front.
|
|
- The contact-shadow term in `ChunkMesher` was a one-directional stripe
|
|
keyed to a northwestern sun -- two neighbours, one corner, top faces
|
|
only. It is now the ambient occlusion above.
|
|
- The light frustum is fitted to the ground the CAMERA CAN SEE rather than
|
|
to a view-sized box around the focus, and both of its margins are now
|
|
asymmetric -- for opposite reasons. The camera sits south of its focus
|
|
and looks north, so the ground it sees runs far north and barely south;
|
|
the sun sits southeast, so the casters for that ground stand south and
|
|
east of it. Paying for a view-sized box plus caster margin on all four
|
|
sides covered about a third of what was on screen at 75 degrees, and
|
|
overpaid at 15.
|
|
- Shadows ease off at the frustum's rim instead of ending on it. Past the
|
|
low rungs the horizon is further out than any box worth paying for, and
|
|
a covered region that simply stops draws a hard line across the middle
|
|
distance where every shadow ends at once.
|
|
|
|
The Pokemon Center interiors. One `POKECENTER` group in
|
|
`data/voxel_heights.lua` plus one new class, and because
|
|
VIRIDIAN_POKECENTER places every tile the tileset's other maps use, the
|
|
one pin set covers all eleven Centers and the Celadon Hotel.
|
|
|
|
### Fixed
|
|
|
|
- The generic town-house tileset (`HOUSE` -- Blue's house, Daisy at her
|
|
table, and eighteen more homes, the schoolhouse and the trashed house
|
|
among them) is now pinned in `data/voxel_heights.lua` the way Red's
|
|
rooms already were: the dining table stops towering as a wall-height
|
|
volume and sits at table height with its front folded upright, stools
|
|
become seat-high boxes that characters sit on, the corner potted
|
|
plants become per-pixel standees instead of texture-smeared box
|
|
stacks, the bookcases get clean capped tops, and the wall band (with
|
|
its window, picture and the schoolhouse blackboard) stays one 16px
|
|
face. The schoolhouse's open book stands on the pinned tabletop as a
|
|
cutout, and the trashed house's ransacked table corner keeps table
|
|
height.
|
|
- Interior door mats lie flat again in Red's and the generic houses.
|
|
Their collision tile is $14, which the engine's stale-cache fallback
|
|
counts as water in every tileset, so the rug recessed into a pond lip;
|
|
a `ground` pin now overrides the water read.
|
|
|
|
- Stray pixels along map seams: ring props (border-tree hulls) whose
|
|
quad CENTER sat exactly on a neighbour body's edge line escaped the
|
|
strict point-in-rect mask and survived as fragments of otherwise
|
|
dropped trees. Object quads now keep/drop by their full extent,
|
|
boundary inclusive; props straddling the body edge also stay whole
|
|
instead of shedding their outer half.
|
|
- The one-step "ledge hop" when crossing a connection into a tree-ringed
|
|
map: the seam step stands the player one cell off the new map, where
|
|
`Map:cellTile` border-extends into the borderBlock -- a raised tile on
|
|
maps ringed with trees. Off-map ground now reads as height 0 (the
|
|
departed neighbour's flat walkway, which is what is actually rendered
|
|
there).
|
|
|
|
- Cycling palette modes with voxel mode on eventually killed the pipeline
|
|
outright: `attempt to call field 'atlasImageData' (a nil value) --
|
|
disabled for this session`. Nothing brought it back short of a restart.
|
|
|
|
`TerrainAtlas` reads three engine seams to animate water and flowers in
|
|
the terrain texture, and this build ships only one of them
|
|
(`defaultAnimatedTiles`). The tile clock, `animFrame`, was already read
|
|
guarded and simply degrades. `atlasImageData` was called straight -- but
|
|
only down the branch where the mod had NOT baked the atlas itself, which
|
|
is why it looked stable until a palette changed. Every mode with no world
|
|
palette for the map (`PaletteFX.pal` answering nil), plus RED++ and any
|
|
trueColor tileset, takes that branch, so the first map with animated
|
|
tiles entered under one of them threw out of `drawWorld` and the engine
|
|
disabled the pass for the session, exactly as it should.
|
|
|
|
The seam is now read guarded like its sibling, and when it is absent the
|
|
pixels are recovered rather than given up on. An atlas neither we nor
|
|
RED++ replaced is the tileset art itself, so animation carries on from
|
|
the art on disk. RED++'s per-map bake exists only as a texture --
|
|
`getGbcAtlas` throws its `ImageData` away -- so that one comes back off
|
|
the GPU: the atlas is drawn 1:1 into a canvas and read back, once per map,
|
|
with the pass's own render target captured and restored around it (the
|
|
usual `setCanvas()` would drop the rest of the frame). A driver that
|
|
refuses the readback declines to animate and keeps the static atlas.
|
|
Worst case now costs one animation, never the pipeline.
|
|
|
|
- Water and flowers did not animate in voxel mode at all, and had not since
|
|
the mode shipped -- a silent one, since the terrain was otherwise correct.
|
|
|
|
The tile clock is the third seam, and this build does not export it
|
|
either. Being read guarded, it answered 0 forever instead of throwing,
|
|
which pinned every animated tile at step 0. `animFrame` is a plain local
|
|
in `TileRenderer`, but an upvalue of the exported `tick()`, so the mod now
|
|
reads the real counter through it. That it is the ENGINE's counter is the
|
|
point: the flat tile layer draws from the same number, so toggling voxel
|
|
mode mid-cycle continues the animation rather than restarting it. A build
|
|
that exports `animFrame()` outright is preferred; a build that hides the
|
|
local falls back to wall time in 60Hz steps, which free-runs against the
|
|
2D path but still moves the water.
|
|
|
|
- Toggling palettes in voxel mode flashed the flat 2D world for a moment on
|
|
every switch.
|
|
|
|
`PaletteFX.setMode` reloads the live map to rebuild its atlas, and this
|
|
mod dropped that map's terrain mesh on any `map.reloaded` at all. Mesh
|
|
builds are asynchronous, so the frames between the drop and the first
|
|
rebuilt mesh had no terrain to draw -- and a voxel `drawWorld` with no
|
|
terrain returns nil, which is exactly how the pipeline asks for the 2D
|
|
fallback. The flash was the mod correctly reporting that it had nothing
|
|
to show.
|
|
|
|
The geometry was never stale: the mesher reads block layout and tile ids
|
|
and never reads colour, and the palette lives entirely in the texture
|
|
`TerrainAtlas` hands back per frame, keyed by palette and so already
|
|
rebuilt by the next frame. A reload whose reason is `colors` now keeps
|
|
the mesh, and the new palette lands on the diorama already on screen in
|
|
one frame. Every other reload -- warps re-entering a map, hot reload, a
|
|
replaced block -- still drops it.
|
|
|
|
- Every non-colour palette mode rendered as SGB in voxel mode: GRAY and
|
|
both INVERTED modes came through as the map's blue.
|
|
|
|
`paletteFor` hands a pipeline the map's RAW SGB zone palette. The flat
|
|
path runs that through `PaletteFX.effectiveColors` on its way to the
|
|
shade-remap shader, and that call is where the non-colour modes actually
|
|
happen -- OG and OG INV swap in the DMG greys (reversed for the latter),
|
|
CLASSIC swaps in the green set, GBC INV permutes the zone's own shades,
|
|
and only GBC and RED++ pass through. This pass bakes colour into the
|
|
atlas and the sprite sheets ahead of the draw rather than shading at blit
|
|
time, so it never reached that call and painted the raw zone palette in
|
|
every mode.
|
|
|
|
Both bakes now run the same transform the shader would have. Terrain and
|
|
characters go through one resolve, so they cannot disagree about what
|
|
mode is on.
|
|
|
|
- VOID FILL did nothing in voxel mode, in two separate ways.
|
|
|
|
**BLACK crashed the build.** The mode is not a block at all --
|
|
`TileRenderer.borderBlockFor` answers `false` for it -- and `Structures`
|
|
added 1 to that `false`. The arithmetic threw, which failed the mesh
|
|
build for every map in the neighbourhood, which left the mode with no
|
|
terrain and dropped it to the flat 2D path entirely. It now builds no
|
|
ring: `tileLookup` answers nil past the body and those keys are never
|
|
written, which the rest of the file already copes with -- every
|
|
neighbour query in it reaches one step outside the analysed range and
|
|
reads nil for its trouble, so an absent cell is the shape "nothing" has
|
|
always had here.
|
|
|
|
**WATER changed nothing on screen.** The ring is BAKED INTO THE MESH in
|
|
this mode rather than drawn each frame, and nothing dropped the cache
|
|
when the option moved, so the old ring simply stayed until the meshes
|
|
were invalidated for some other reason. The pipeline's update hook now
|
|
polls `TileRenderer.voidFill` and invalidates on a change -- polled
|
|
rather than hooked because the engine changes it from three places (the
|
|
options row, `applyOptions` on load, `setVoidFill`) and none of them
|
|
announces it, and checked ahead of the active() gate so switching it
|
|
while voxel mode is off still drops what is cached.
|
|
|
|
`mods/DRAMATIC_SHAPE/tests/voxel_void_probe.lua` walks the three modes
|
|
and reports the border block, whether the mesh built and whether the
|
|
scene took the 3D path. It deliberately does NOT invalidate the cache
|
|
itself, since doing so would hide the second half of this.
|
|
|
|
- Water and flowers did not animate. The 2D path animates them by
|
|
OVERDRAWING the animated cells on top of the static tile layer each
|
|
frame, which a single static mesh has no equivalent of -- the geometry
|
|
samples one texture and that is that. So `TerrainAtlas` animates the
|
|
texture instead: a private copy of the atlas whose animated tile slots
|
|
are rewritten when the step advances, which moves every instance of that
|
|
tile across the whole mesh at once. Which is what the Game Boy does in
|
|
the first place (`home/vcopy.asm` rewrites the tile's VRAM bytes); the
|
|
overdraw is the port's workaround for a tile layer, not the original.
|
|
~130 pixels of work three times a second, on the same
|
|
`TileRenderer.animFrame` clock the 2D path uses, so the two can never
|
|
disagree about which frame they are on.
|
|
- The frame files (`flower1..3.png`) are raw grayscale and have to land on
|
|
the colours of the tile they replace, but the two recolour paths do not
|
|
share a rule -- SGB bakes one world palette over everything, RED++ picks
|
|
a palette group per tile graphic. So the shade mapping is LEARNED from
|
|
the atlas: read the static tile's slot in the raw art and in the finished
|
|
atlas side by side and ask what each shade became. Right under both
|
|
without this file knowing which one ran.
|
|
- Terrain art was off the pixel grid by up to half a pixel, with one art
|
|
pixel per tile sampled twice and another never at all. A tile is 8
|
|
texels across 8 world pixels -- one texel per pixel exactly -- and
|
|
`ChunkMesher`'s uv inset squeezed that art into a 7-texel sample range
|
|
while the quad still covered 8 world pixels, so it advanced 7/8 of a
|
|
texel per pixel and drifted. The inset exists to stop the rasteriser
|
|
reaching a neighbouring tile along a shared edge, but half a texel was
|
|
fifty times more than that needs: 0.02 is as safe (interpolation error
|
|
is nowhere near it) and drifts 0.25% of a pixel across a whole tile.
|
|
Nothing showed the fault until the voxel wireframe drew the grid those
|
|
pixels were supposed to be sitting on.
|
|
|
|
### Changed from the pre-mod version
|
|
|
|
- The level is no longer the mod's to keep. The engine owns the ladder, the
|
|
options rows, the hotkeys, persistence and the TILT exclusion; the mod
|
|
keeps only the camera-angle tween.
|
|
- Persistence moved from `save.options.voxel` / `save.options.tiltshift` to
|
|
`save.options.pipelines.voxel` / `.tiltshift`.
|
|
- Hotkeys moved from `4`/`9` to `3`/`6`: the fork already uses `4` for
|
|
survey zoom.
|
|
- The tilt-shift pass is a declared `worldPresent` stage rather than a call
|
|
spliced into the world draw, so it composes with any world pipeline
|
|
instead of only this one.
|
|
- The cut-tree animation now draws in voxel mode; the pre-mod version
|
|
omitted it from the 3D field-effect list.
|