update readme, mod card
DramaticShape · 2026-07-26 ·
parent 7046ead648
update readme, mod card
Changed files
.modkitignore | 30 +++
CHANGELOG.md | 830 ++++++++++++++++++++++++++++++++++++++----------------------------------------
README.md | 155 ++-------------
mod.card | 8 +-
4 files changed, 447 insertions(+), 576 deletions(-)
Diff
diff --git a/.modkitignore b/.modkitignore
new file mode 100644
index 0000000..c40bb83
--- /dev/null
+++ b/.modkitignore
@@ -0,0 +1,30 @@
+# Files that stay in the repo and out of the distributed package.
+# Exact relative paths, one per line -- modkit matches literally, no globs.
+
+# The SDK suite and the probes it grew out of. A shipped test that requires
+# an engine module reads as a private require against the archive
+# (CONTRIBUTING-mods.md "What the PR must contain", 2).
+tests/dramatic_shape_test.lua
+tests/voxel_anim_probe.lua
+tests/voxel_door_probe.lua
+tests/voxel_mem_probe.lua
+tests/voxel_perf_probe.lua
+tests/voxel_pipeline_probe.lua
+tests/voxel_seam_shots.lua
+tests/voxel_shadow_probe.lua
+tests/voxel_survey.lua
+tests/voxel_void_probe.lua
+
+# Authoring tools. These run by hand on a machine that has imported its own
+# ROM, read the local cache, and emit assets/voxels/*.lua; the carved models
+# are what a player installs, the carving is not.
+tools/build_voxels.py
+tools/building_images.py
+tools/building_voxels.py
+tools/voxel-survey.md
+assets/docs/buidling_to_voxel/sprite_extract.py
+assets/docs/buidling_to_voxel/voxel_build_verify.py
+
+# assets/docs/**.md stays in: data/voxel_heights.lua points a modder at the
+# building catalogue, and the methodology is where the geometry's provenance
+# is written down.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2b074e..186eede 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,22 @@
# Changelog
-## Unreleased
+## 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
@@ -8,6 +24,136 @@ 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.
@@ -30,12 +176,12 @@ long as the thing throwing them is tall.
- The mod's four controls sit on adjacent keys, and the two that never had
one now have a hotkey at all:
- | key | control | was |
- | --- | --- | --- |
- | 3 | VOXEL, the camera ladder | 6 |
- | 5 | V-GRID, the wireframe | menu only |
- | 6 | T-SHIFT, the blur ladder | 9 |
- | 7 | V-CURVE, the horizon bend | menu only |
+ | 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,
@@ -160,63 +306,258 @@ long as the thing throwing them is tall.
whatever the player left in `options.lua`, and the world view size (which
the light frustum is fitted to) swings 3x across that range.
-### Fixed
-
-- 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.
+- 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.
- 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.
+- `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).
-- Toggling palettes in voxel mode flashed the flat 2D world for a moment on
- every switch.
+### Changed
- `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.
+- 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.
- 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
+- 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.
@@ -297,373 +638,6 @@ long as the thing throwing them is tall.
Nothing showed the fault until the voxel wireframe drew the grid those
pixels were supposed to be sitting on.
-### Changed
-
-- `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.
-
-### Added
-
-- 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).
-
-## 1.3.0
-
-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.
-
-### Changed
-
-- 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.
-
-### Fixed
-
-- 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).
-
-## 1.2.0
-
-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/`.
-
-### Added
-
-- `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.
-
-### Changed
-
-- 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.
-
-### 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.
-
-## 1.1.0
-
-Interior furniture gets the shapes it depicts
-(mods/DRAMATIC_SHAPE/tools/voxel-survey.md is the procedure that found and
-verified these).
-
-### Added
-
-- 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.
-
-### 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.
-
-## 1.0.0
-
-First release, ported from the engine-internal voxel branch onto the
-`render_pipelines` mod API.
-
-### 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 `6` (OFF / 15 / 35 / 50).
-- `tiltshift` render pipeline: a `worldPresent` post-process giving the
- miniature-photo look. T-SHIFT options row and hotkey `9` (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.
-
### Changed from the pre-mod version
- The level is no longer the mod's to keep. The engine owns the ladder, the
@@ -671,7 +645,7 @@ First release, ported from the engine-internal voxel branch onto the
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 `6`/`9`: the fork already uses `4` for
+- 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
diff --git a/README.md b/README.md
index 596adea..de48a41 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
# Dramatic Shape Voxel Mod
+A mod for the [Pokémon Gen 1 Recompilation
+Project](https://github.com/bryanthaboi/pokemon-gen1-recomp-project).
+
The overworld as a 3D diorama. Terrain is extruded into real geometry,
occlusion comes from a depth buffer rather than a y-sort, characters stand
as leaning sprite slabs, a shadow map throws real cast shadows across
@@ -11,7 +14,10 @@ or scripts — it changes what the world *looks* like and nothing about what
it *is*. Battles, menus and cutscenes are untouched; only the free-roam
overworld draws differently.
-## Using it
+## Controls
+
+Every key is free-roam only, and each one is also a row on the OPTIONS
+menu.
| control | does |
| --- | --- |
@@ -20,146 +26,7 @@ overworld draws differently.
| `6`, or the **T-SHIFT** options row | OFF → 1 → 2 → 3 → OFF (miniature blur) |
| `7`, or the **V-CURVE** options row | OFF → 1 → 2 → 3 — bend the world over the horizon |
-The four keys sit together, and two of them are taken off the engine: `3`
-was **TILT** and `5` was **GBC FX**. Neither is reachable by key while this
-mod is enabled, and both are still on the OPTIONS menu. Every key is
-free-roam only, and a screen that handles its own keys keeps them, so
-nothing here fires while a menu or a text box has focus.
-
-**`3` also turns TILT and GBC FX off**, on every press — including the one
-that cycles the ladder back round to OFF. Both fight the diorama: TILT is
-the flat fake of what this mode does for real, and GBC FX lays a
-full-screen present pass over the top of it. Since `3` is now the only key
-that reaches either, it is also the way back from having left one on.
-
-75 degrees is a rung the engine's flat **TILT** could not have: it projects
-the world canvas as one rigid plane, which degenerates into a horizon line
-down there, where real geometry only gets more of itself to show.
-
-That rung is also the one that gets a **sky**. Pitched that far over the
-horizon comes into frame, so the void behind the diorama is filled rather
-than left as the black plate it reads as at every rung below — enough to
-stand the world under something without a skybox to draw. Outdoors 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, not open air. The colour follows the display
-mode like everything else here — blue in the colour modes, grey under
-GRAY, green under CLASSIC — and fades in with the camera tween rather than
-snapping on at the keypress.
-
-V-CURVE is the *curved world* — every vertex pushed down by the square of
-its horizontal distance from the camera's focus, so the ground under you
-stays flat and the map bends away over a near horizon. It is not a fisheye:
-a fisheye is a lens, a screen-space warp that bends straight lines
-everywhere and resamples every pixel to do it. This bends the geometry, so
-lines near the camera stay straight and the art stays crisp.
-
-Both pipeline levels persist in `save.options.pipelines`, and V-GRID and
-V-CURVE in `save.options.modOptions.DRAMATIC_SHAPE` — the same place the mod
-manager's own settings page for this mod reads, so each row is one setting
-in two places. VOXEL and the engine's **TILT** are two answers to the same
-question, so switching one on switches the other off.
-
-Without shader or depth-canvas support the rows still cycle but the world
-stays 2D — the mode reports itself unavailable and the engine keeps the
-vanilla path.
-
-## How it is built
-
-This is a *rendering pipeline* mod (see `docs/modding.md`). `main.lua`
-registers two records in the engine's `render_pipelines` registry and
-writes almost nothing else — the ladder, the options rows, persistence and
-the free-roam gate are all engine plumbing driven from those records:
-
-- **`voxel`** — a `drawWorld` pipeline. Replaces the world pass with a
- rendered 3D scene and returns it as one window-resolution canvas.
-- **`tiltshift`** — a `worldPresent` pipeline. Post-processes the finished
- world *before* the UI composites, so the diorama blurs and the text boxes
- in front of it stay crisp.
-
-Everything else lives in `lib/`, loaded through the mod's own namespace
-(`V.require`) rather than `package.path`, which a mod directory is not on:
-
-| module | role |
-| --- | --- |
-| `Voxel3D` | the 3D pass: shader, depth buffer, camera, projection |
-| `ShadowMap` | the sun's own pass: what it cannot see is in shadow |
-| `WorldCurve` | the curved world: the Animal Crossing horizon |
-| `VoxelGrid` | the wireframe toggle |
-| `ModSetting` | a setting of this mod's own: ladder, store, options row |
-| `Mat4` | the 4x4 matrix math the camera and model transforms need |
-| `VoxelState` | the camera angle and its ease; the engine owns the level |
-| `VoxelScene` | assembles one frame: shadows, terrain, characters, grass |
-| `ChunkMesher` | a map's block layer → a terrain mesh, cached per map |
-| `Structures` | flood-fills drawn things and decides how tall each is |
-| `Buildings` | a profiled building sprite → a voxel model, band by band |
-| `TileShape` | resolves every tile to an extrusion class and art mode |
-| `TerrainAtlas` | the texture geometry samples: SGB palette bakes, and the animated water/flower tiles |
-| `SpriteBillboards` | a sprite frame → a leaning slab with side relief |
-| `VoxelModels` | the carved character models in `assets/voxels/` |
-| `TiltShift` | the two-pass separable blur and saturation lift |
-
-`data/voxel_heights.lua` is the hand-authored shape profile: listing a tile
-there pins its height and art mode over the automatic detector, and its
-`buildings` section names whole building sprites to voxelize band by band
-(`assets/docs/buidling_to_voxel/` is the pipeline; `assets/docs/buildings/` catalogues the
-drawings). Another mod can pin its own tiles by shipping the same file, and
-can replace a character's model with `overrides/voxels/<name>.lua` — models
-resolve through the engine's asset search path before falling back to ours.
-
-`tools/build_voxels.py` is what carved `assets/voxels/` out of the
-overworld sprite sheets. `tools/building_voxels.py` is the reference
-implementation of the building voxelizer — it builds the same models
-offline, asserts their geometry and renders isometric previews, and its
-voxel and shell counts are what `lib/Buildings.lua` is checked against.
-
-## Engine seams it relies on
-
-The mod reaches into a few engine internals (hence `"permissions":
-["engine_internals"]`). All but the last are read-only:
-
-- `Game:keypressed` — **wrapped, not read.** The only thing here that
- changes engine behaviour, and the one seam worth arguing about. The
- engine answers its own display keys and returns before a mod's pipeline
- is offered the key, expressly so a pipeline cannot shadow one — so `3`
- (TILT) and `5` (GBC FX) cannot be claimed the documented way, and V-GRID
- and V-CURVE own no render pass and have no registry to claim from at all.
- Only `6` arrives by the supported route. Polling the keyboard instead
- would fire *alongside* the engine's handler rather than instead of it, so
- `3` would cycle both this mode and TILT on one press. The wrapper
- delegates everything it can: `Pipelines.hotkey` still applies its own
- gate and ladder, the settings borrow the voxel pipeline's free-roam gate,
- unclaimed keys pass straight through, and a screen with its own handler
- keeps the keyboard.
-- `Player:pose()` / `NPC:pose()` — the sheet, position, facing and step
- phase a frame renders to, so 2D and 3D can never disagree about a pose.
-- `SpriteRenderer:resolveImage()` and `SpriteRenderer.STAND/WALK` — the
- live sprite image and frame tables, so palette modes and sprite-replacing
- mods apply to the geometry with no second copy of the logic.
-- `TileRenderer.recolorSample` — the exact shade cutoffs the 2D palette
- shader uses, so baked terrain lands on the same colours as flat tiles.
-- `TileRenderer.animFrame()` / `.atlasImageData()` / `.defaultAnimatedTiles()`
- — the tile-animation clock, the pixels behind the atlas texture, and the
- vanilla water/flower spec. A static mesh cannot overdraw animated cells
- the way the tile layer does, so it animates the texture instead, and
- these are what let it do that on the engine's own clock and colours.
-
- Only `defaultAnimatedTiles` is required. The other two are OPTIONAL and
- read guarded, because an engine build is free not to carry them — this
- one does not carry either. Missing `animFrame` freezes the tile clock at
- step 0 (terrain stops animating in voxel mode; everything else is
- unaffected). Missing `atlasImageData` costs nothing where the mod baked
- the atlas itself, falls back to the tileset art where the engine is
- drawing that art unmodified, and skips animating a RED++ per-map bake,
- whose pixels are unreachable without the seam. None of the three may ever
- be called unguarded: a throw inside `drawWorld` costs the player the whole
- pipeline for the session, not one frame of moving water.
-
-## Tests
-
-```sh
-luajit mods/DRAMATIC_SHAPE/tests/dramatic_shape_test.lua
-```
-
-Runs headless: the mod loads, both pipelines land in the registry with the
-shape the engine dispatches on, the ladders and persistence round-trip, and
-the whole thing stays inert on a machine with no 3D support.
+Two of those keys are taken off the engine: `3` was **TILT** and `5` was
+**GBC FX**. Neither is reachable by key while this mod is enabled, and both
+are still on the OPTIONS menu. Pressing `3` also switches both off — they
+fight the diorama, and it is the way back from having left one on.
\ No newline at end of file
diff --git a/mod.card b/mod.card
index 9e5f37c..e53f6e2 100644
--- a/mod.card
+++ b/mod.card
@@ -2,8 +2,8 @@
-- and the manager detail pane; never by the loader's merge.
return {
summary = "The overworld as a 3D diorama: extruded terrain, real occlusion, voxel characters, tilt-shift miniature blur.",
- author = "Pokemon Gen 1 Recompilation Project",
- contact = "https://github.com/bryanthaboi/pokemon-gen1-recomp-project",
+ author = "DramaticShape",
+ contact = "https://github.com/DramaticShape/DRAMATIC_SHAPE",
tags = { "graphics", "3d", "voxel", "render-pipeline", "presentation" },
differences = {
changed = {
@@ -14,7 +14,7 @@ return {
"the VOXEL key (3) turns TILT and GBC FX off on every press -- both fight the diorama, and 3 is now the only key that reaches either",
},
added = {
- "VOXEL options row and hotkey 3 (OFF / 15 / 35 / 50 degrees)",
+ "VOXEL options row and hotkey 3 (OFF / 15 / 35 / 50 / 75 degrees)",
"T-SHIFT options row and hotkey 6 (OFF / 1 / 2 / 3), the miniature blur",
"V-GRID on hotkey 5 and V-CURVE on hotkey 7",
"a sky behind the diorama at the 75-degree rung, outdoor maps only, coloured by the active palette mode",
@@ -30,5 +30,5 @@ return {
credits = {
{ who = "pret/pokered", for_ = "the tile and sprite data the geometry is derived from" },
},
- compat = { engine = ">=0.1.0 <2.0.0", modApi = 2 },
+ compat = { engine = ">=0.1.28 <2.0.0", modApi = 2 },
}