DramaticShapeVoxelMod archival mirror · read-only
a7c9541ac49fc8d5567a23fe29ee70c0b8846d98

pokecenter machines, water occlusion

DramaticShape · 2026-08-02 · parent 91cc2d6f51

pokecenter machines, water occlusion

Changed files

data/voxel_heights.lua        | 337 +++++++++++++++++++++++++++++++++++++++++++++++---------------
 lib/Buildings.lua             |  86 +++++++++++++---
 lib/VoxelScene.lua            |  45 +++++++--
 lib/Water.lua                 |   9 +-
 tests/dramatic_shape_test.lua |  13 +++
 tools/building_voxels.py      | 107 +++++++++++++-------
 6 files changed, 454 insertions(+), 143 deletions(-)

Diff

diff --git a/data/voxel_heights.lua b/data/voxel_heights.lua
index 2c5ae61..a98c059 100644
--- a/data/voxel_heights.lua
+++ b/data/voxel_heights.lua
@@ -842,12 +842,12 @@ return {
       -- console face (76/77) and button panel (6/22) are ALSO wall:
       -- drawn 16px tall against the band, so wall height is their drawn
       -- height and they read as equipment jutting from it.  (The
-      -- `center_heal_machine` template below claims those four tiles at
-      -- every placement and models the console properly; these pins are
-      -- the degradation path when the profile is absent.)  The
-      -- near-black screen tiles must be pinned or the void rule flattens
-      -- them.  What is NOT wall is the machines' two flanks -- see
-      -- `prop` below.
+      -- `center_heal_machine_w`/`_e` templates below claim the full
+      -- 4x4 machine grids at every placement and model the console
+      -- properly; these pins are the degradation path when the profile
+      -- is absent.)  The near-black screen tiles must be pinned or the
+      -- void rule flattens them.  What is NOT wall is the machines'
+      -- two flanks -- see `prop` below.
       wall = { 2, 3, 4, 5, 6, 16, 18, 19, 20, 21, 22, 40, 41,
                76, 77, 92, 93, 94, 95 },
       -- the counters, half a cell high: top band (8) with the nurse's
@@ -899,7 +899,10 @@ return {
       -- game -- 12 maps, the same two cells in each -- so this pin cannot
       -- reach anything but the machines.  72 is still the $48
       -- water-fallback trap and still has to carry SOME pin; it just
-      -- wanted the thin one, not the wall.
+      -- wanted the thin one, not the wall.  (The machine templates now
+      -- claim the flank tiles too and model the hoses and keyboard as
+      -- attached 3D equipment; these standee pins are the degradation
+      -- path when the profile is absent.)
       prop = { 7, 13, 72, 73,
                -- ...and the potted plants -- see THE POTTED PLANTS below
                32, 33, 34, 35, 48, 49, 50, 51 },
@@ -1968,13 +1971,24 @@ return {
       -- on (see `buildings` below).  The pin is only the degradation
       -- path here -- the template claims both placements -- but the two
       -- must not disagree about where the desk's top is.
-      heights = { table = 8 },
+      --
+      -- `stool` is 5 for the same reason: that is the drawn elevation of
+      -- BOTH seats in this tileset -- rows 11-15, the seat's front edge
+      -- over its legs -- and the height the `club_stool` template stands
+      -- them.  Bill's chair and the Fan Club's are different drawings
+      -- above the seat but share tiles 59/60 below it, so they are drawn
+      -- at the same height pixel for pixel; the class default of 8 was
+      -- three voxels over both.  Every one of these cells is WALKABLE,
+      -- so this is also where a character sitting on one ends up.
+      heights = { table = 8, stool = 5 },
       -- the seats: Bill's desk stool (43/44 over 59/60) and the Fan
       -- Club's four members' chairs (61/62 over 59/60), a whole cell
       -- each.  All of them sit in WALKABLE cells, so they were flat
-      -- floor until pinned; the 8px standee pool puts them at seat
-      -- height, which is also where a character standing on the cell
-      -- ends up.
+      -- floor until pinned; the standee pool puts them at seat height,
+      -- which is also where a character standing on the cell ends up.
+      -- The Fan Club's four are modelled in full by `club_stool` below
+      -- and these pins are their degradation path; Bill's chair stays a
+      -- standee, stood up as a part of `bills_desk`.
       stool = { 43, 44, 59, 60, 61, 62 },
       -- flat, and pinned flat on purpose.  31 is the rooms' floor tile
       -- and needs no pin for its 980 walkable placements -- but the
@@ -3737,82 +3751,154 @@ return {
             z = 13 },                                       -- keyboard
         },
       },
-      -- F05: the healing machine behind the counter -- the pair in every
-      -- Center (cells (1,0):(2,1) and (6,0):(7,1)), 22 placements, plus
-      -- the Indigo Plateau lobby's pair under MART below (scan
-      -- "58,59;74,75;76,77;6,22").
+      -- F05: the healing machine behind the counter -- two 4x4-tile
+      -- variants, 24 placements between them (a pair in every Center,
+      -- cells (1,0):(2,1) and (6,0):(7,1), plus the Indigo Plateau
+      -- lobby's pair under MART below).  The variants differ only in
+      -- the east flank: the west machine has the control keyboard
+      -- there (7/13, scan "40,58,59,40;40,74,75,40;72,76,77,7;
+      -- 72,6,22,13" -- 12 placements), the east machine mirrored
+      -- hoses (73, scan "...;72,76,77,73;72,6,22,73" -- 12).
       --
-      -- The drawing is 4x4 tiles but only its middle COLUMN is this
-      -- template's.  The corners are the striped wall band (40), and
-      -- the outer tile columns are the flanking EQUIPMENT -- the hoses
-      -- (72 west, 73 east) and the control keyboard (7/13) -- which are
-      -- separate thin objects, not part of this console: they are
-      -- pinned into the THIN standee pool above and stay out of this
-      -- grid.  What no class can split is the middle -- a wall-height
+      -- The FULL drawing is claimed now: the corners are the striped
+      -- wall band (40), the flanks the machine's own equipment --
+      -- hoses and a keyboard ATTACHED to the cabinet, not furniture
+      -- standing free (their standee pins above stay as the
+      -- degradation path).  What no class can split is a wall-height
       -- cabinet with a monitor perched on its front top edge, drawn
       -- across two map rows because it towers over the 16px band
       -- behind it, which the volume path can only read as more wall.
       --
-      -- Read off the pixels (local x, the whole drawing's x8..x23):
-      --   rows 15-31  the CABINET's front face, 16 wide, 17 tall:
-      --               black front-top edge with vent notches (15),
-      --               white top rail (16), black panel in a white
-      --               frame (17-28), white rail / light plinth / black
-      --               ground line (29-31).
-      --   rows 6-14   the cabinet's TOP FACE seen from above -- not a
-      --               second-story facade: a white expanse with a lit
-      --               west strip (x1 white) and a shaded east strip
-      --               (x14 #555) wrapping the monitor, full-width in
-      --               front of it (13-14), strips beside it (7-12).
-      --               Its 9 rows plus the front edge row 15 ARE the
-      --               cabinet's depth: 10.  `desk.top` lays the band
-      --               flat as the lid; where the monitor's drawing
-      --               occludes it, the lid continues the nearest strip
-      --               (the drawing's own pixels).
-      --   rows 1-13   the MONITOR, x2..x13: black back rim (1) and
-      --               white cap (2-3) seen from above -- 3 rows plus
-      --               the front edge, depth 4 -- then bezel, screen
-      --               and control strip (4-13) face-on, 10 tall,
-      --               standing on the cabinet top at its front: the
-      --               drawn base edge (13) is one band row up from the
-      --               front strip (14), so its front plane is one
-      --               voxel back from the cabinet's -- z 20..23.  The
-      --               screen interior (x5..x10, rows 6-8) is sealed
-      --               behind its own black frame and sinks one voxel
+      -- Read off the pixels (absolute grid x0..x31):
+      --   rows 15-31, x8..x23  the CABINET's front face, 16 wide, 17
+      --               tall: black front-top edge with vent notches
+      --               (15), white top rail (16), black panel in a
+      --               white frame (17-28), white rail / light plinth /
+      --               black ground line (29-31).  `desk.x` bounds the
+      --               desk to the middle columns.
+      --   rows 6-14, x8..x23   the cabinet's TOP FACE seen from above
+      --               -- not a second-story facade: a white expanse
+      --               with a lit west strip (x9) and a shaded east
+      --               strip (x22) wrapping the monitor.  Its 9 rows
+      --               plus the front edge row 15 ARE the cabinet's
+      --               depth: 10, z 16..25 against the wall.
+      --               `desk.top` lays the band flat as the lid; where
+      --               the monitor's drawing occludes it, the lid
+      --               continues the nearest strip (the drawing's own
+      --               pixels).
+      --   rows 1-13, x10..x21  the MONITOR: black back rim (1) and
+      --               white cap (2-3) seen from above -- depth 4 --
+      --               then bezel, screen and control strip (4-13)
+      --               face-on, 10 tall, at the cabinet top's front
+      --               (the drawn base edge 13 is one band row up from
+      --               the front strip 14): z 20..23.  The screen
+      --               interior (x13..x18, rows 6-8) sinks one voxel
       --               via `inset` -- the pane rule applied by hand,
       --               because `panes = false` blocks the global pass.
-      --   rows 0-5    at the flanks: wall stripes and the machine's
-      --               cast shadow on the wall east of it -- BACKGROUND,
-      --               the same standing as the potted plants' floor.
-      --               The `wall` element keeps the band solid over the
-      --               back map row: a 16-tall, 16-deep block cycling
-      --               the drawing's own stripe unit (x0, rows 0-3),
-      --               what the neighbouring cells' wall pins render.
+      --   rows 16-31, x0..x7   the HOSES (tile 72 stacked): one 8-row
+      --               motif per map row, and the two stacked motifs
+      --               are two hoses in DEPTH -- the drawn row band is
+      --               the depth band, and each motif's own rows are
+      --               its elevation WITHIN that band, exactly the
+      --               potted plants' stacked-cell rule.  So both
+      --               hoses hug the floor: a horizontal run off the
+      --               cabinet's side (rows 24-26, x3..x7 -> heights
+      --               5..7) over a shaded drop landing ON the floor
+      --               (rows 27-31, x2..x7 -> heights 0..4) -- all
+      --               measured, the drawn extent maps 1:1 with no
+      --               continuation.  `box` parts at z 18 and z 24, 3
+      --               deep; both wear the LOWER motif's rows (the one
+      --               drawn at its true elevation; the upper motif is
+      --               the same atlas tile, pixel-identical).
+      --   rows 16-31, x24..x31 west variant: the KEYBOARD (7/13), a
+      --               key grid with its cable at the north end and a
+      --               bar down the east edge -- TOP-VIEW art, 16 rows
+      --               = 16 depth rows.  A `flat` part mounted on the
+      --               cabinet's side at COUNTER level (`at` 8, the
+      --               counters' own 8px band -- the one number
+      --               top-view art cannot state, pinned to the room's
+      --               work surface), 3 thick, top face wearing the
+      --               drawn art.  The dark dashes east of it (x30-31)
+      --               are its cast shadow on the floor: background.
+      --               East variant: mirrored hoses (runs x24..x28,
+      --               drops x24..x29), same rows and z slots.
+      --   rows 0-15 elsewhere  wall stripes and the machine's cast
+      --               shadow -- BACKGROUND, the same standing as the
+      --               potted plants' floor.  The `wall` element keeps
+      --               the band solid over the back map row, full grid
+      --               width: a 16-tall, 16-deep block cycling the
+      --               drawing's own stripe unit (x0, rows 0-3), what
+      --               the neighbouring cells' wall pins render.
       --
       -- `depth = 4` is BOTH map rows: the back row is the wall's plot,
-      -- the front row the machine's own, and the cabinet leans against
-      -- the wall at z 16..25.  `panes = false`: the front panel seals
-      -- DARK behind LIGHT (a black panel in a white frame), the
-      -- opposite polarity to the pane rule, so the global recess pass
-      -- would sink the frame and leave the panel proud.
+      -- the front row the machine's own.  `panes = false`: the front
+      -- panel seals DARK behind LIGHT (a black panel in a white
+      -- frame), the opposite polarity to the pane rule, so the global
+      -- recess pass would sink the frame and leave the panel proud.
       {
-        id = "center_heal_machine",
+        id = "center_heal_machine_w",
         tiles = {
-          { 58, 59 },
-          { 74, 75 },
-          { 76, 77 },
-          {  6, 22 },
+          { 40, 58, 59, 40 },
+          { 40, 74, 75, 40 },
+          { 72, 76, 77,  7 },
+          { 72,  6, 22, 13 },
         },
         roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
         slab = 0, frontEave = 0, ledge = nil, depth = 4,
         panes = false,
         wall = { h = 16, depthPx = 16, x = 0, cycle = { 0, 3 } },
-        desk = { fascia = { 15, 16 }, base = { 17, 31 }, z = 16,
-                 depthPx = 10, top = { 6, 14 } },
+        desk = { x = { 8, 23 }, fascia = { 15, 16 }, base = { 17, 31 },
+                 z = 16, depthPx = 10, top = { 6, 14 } },
         parts = {
-          { kind = "upright", x = { 2, 13 }, top = { 1, 3 },
+          { kind = "upright", x = { 10, 21 }, top = { 1, 3 },
             facade = { 4, 13 }, z = 20, depth = 4,
-            inset = { x = { 5, 10 }, rows = { 6, 8 } } },    -- the monitor
+            inset = { x = { 13, 18 }, rows = { 6, 8 } } },   -- the monitor
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- N hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- N hose drop
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- S hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- S hose drop
+          { kind = "flat", x = { 24, 29 }, rows = { 16, 31 },
+            z = 16, at = 8, thick = 3 },                     -- keyboard shelf
+        },
+      },
+      {
+        id = "center_heal_machine_e",
+        tiles = {
+          { 40, 58, 59, 40 },
+          { 40, 74, 75, 40 },
+          { 72, 76, 77, 73 },
+          { 72,  6, 22, 73 },
+        },
+        roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
+        slab = 0, frontEave = 0, ledge = nil, depth = 4,
+        panes = false,
+        wall = { h = 16, depthPx = 16, x = 0, cycle = { 0, 3 } },
+        desk = { x = { 8, 23 }, fascia = { 15, 16 }, base = { 17, 31 },
+                 z = 16, depthPx = 10, top = { 6, 14 } },
+        parts = {
+          { kind = "upright", x = { 10, 21 }, top = { 1, 3 },
+            facade = { 4, 13 }, z = 20, depth = 4,
+            inset = { x = { 13, 18 }, rows = { 6, 8 } } },   -- the monitor
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- NW hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- NW hose drop
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- SW hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- SW hose drop
+          { kind = "box", x = { 24, 28 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- NE hose run
+          { kind = "box", x = { 24, 29 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- NE hose drop
+          { kind = "box", x = { 24, 28 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- SE hose run
+          { kind = "box", x = { 24, 29 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- SE hose drop
         },
       },
     },
@@ -3840,28 +3926,75 @@ return {
         },
       },
       -- F05 again: the Indigo Plateau lobby has the Centers' healing
-      -- machine pair too (cells (5,4):(6,5) and (10,4):(11,5)), and its
-      -- MART tileset shares the POKECENTER atlas, so this is the same
-      -- drawing tile for tile. Same part table as the POKECENTER entry
-      -- above.
+      -- machine pair too -- the west-variant grid at (5,4):(6,5) and
+      -- the east at (10,4):(11,5), verified by region dump -- and its
+      -- MART tileset shares the POKECENTER atlas, so these are the
+      -- same drawings tile for tile. Same part tables as the
+      -- POKECENTER entries above.
       {
-        id = "center_heal_machine",
+        id = "center_heal_machine_w",
         tiles = {
-          { 58, 59 },
-          { 74, 75 },
-          { 76, 77 },
-          {  6, 22 },
+          { 40, 58, 59, 40 },
+          { 40, 74, 75, 40 },
+          { 72, 76, 77,  7 },
+          { 72,  6, 22, 13 },
         },
         roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
         slab = 0, frontEave = 0, ledge = nil, depth = 4,
         panes = false,
         wall = { h = 16, depthPx = 16, x = 0, cycle = { 0, 3 } },
-        desk = { fascia = { 15, 16 }, base = { 17, 31 }, z = 16,
-                 depthPx = 10, top = { 6, 14 } },
+        desk = { x = { 8, 23 }, fascia = { 15, 16 }, base = { 17, 31 },
+                 z = 16, depthPx = 10, top = { 6, 14 } },
         parts = {
-          { kind = "upright", x = { 2, 13 }, top = { 1, 3 },
+          { kind = "upright", x = { 10, 21 }, top = { 1, 3 },
             facade = { 4, 13 }, z = 20, depth = 4,
-            inset = { x = { 5, 10 }, rows = { 6, 8 } } },    -- the monitor
+            inset = { x = { 13, 18 }, rows = { 6, 8 } } },   -- the monitor
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- N hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- N hose drop
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- S hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- S hose drop
+          { kind = "flat", x = { 24, 29 }, rows = { 16, 31 },
+            z = 16, at = 8, thick = 3 },                     -- keyboard shelf
+        },
+      },
+      {
+        id = "center_heal_machine_e",
+        tiles = {
+          { 40, 58, 59, 40 },
+          { 40, 74, 75, 40 },
+          { 72, 76, 77, 73 },
+          { 72,  6, 22, 73 },
+        },
+        roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
+        slab = 0, frontEave = 0, ledge = nil, depth = 4,
+        panes = false,
+        wall = { h = 16, depthPx = 16, x = 0, cycle = { 0, 3 } },
+        desk = { x = { 8, 23 }, fascia = { 15, 16 }, base = { 17, 31 },
+                 z = 16, depthPx = 10, top = { 6, 14 } },
+        parts = {
+          { kind = "upright", x = { 10, 21 }, top = { 1, 3 },
+            facade = { 4, 13 }, z = 20, depth = 4,
+            inset = { x = { 13, 18 }, rows = { 6, 8 } } },   -- the monitor
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- NW hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- NW hose drop
+          { kind = "box", x = { 3, 7 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- SW hose run
+          { kind = "box", x = { 2, 7 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- SW hose drop
+          { kind = "box", x = { 24, 28 }, rows = { 24, 26 },
+            z = 18, depth = 3 },                             -- NE hose run
+          { kind = "box", x = { 24, 29 }, rows = { 27, 31 },
+            z = 18, depth = 3 },                             -- NE hose drop
+          { kind = "box", x = { 24, 28 }, rows = { 24, 26 },
+            z = 24, depth = 3 },                             -- SE hose run
+          { kind = "box", x = { 24, 29 }, rows = { 27, 31 },
+            z = 24, depth = 3 },                             -- SE hose drop
         },
       },
     },
@@ -4236,6 +4369,46 @@ return {
             facade = { 22, 31 }, rise = -8, z = 22, depth = 10 },
         },
       },
+      -- F09 once more: the Pokemon Fan Club's four members' chairs,
+      -- drawn round the boardroom table -- POKEMON_FAN_CLUB cells (1,3),
+      -- (1,4), (6,3) and (6,4), and nowhere else in the game.
+      --
+      -- A DIFFERENT drawing from the house stool -- its seat field
+      -- carries a one-pixel white margin where the house's carries two
+      -- -- but the same object band for band: rows 5-10 the seat seen
+      -- from above (its lid), row 11 the seat's own front edge, 12-15
+      -- the legs with the floor showing between them.  Its elevation
+      -- rows are PIXEL-IDENTICAL to the house drawing's, so it takes
+      -- that part table unchanged, growth and all: z 3..13, `stretch`
+      -- mapping the seat band over the deeper lid, `panes = false` so
+      -- the pane rule does not sink the legs' lit faces.
+      --
+      -- Rows 11-15 come from tiles 59/60, which this drawing SHARES
+      -- with Bill's chair (43/44 over 59/60, modelled as a part of
+      -- `bills_desk` above).  That is why the tileset's `stool = 5`
+      -- height override is right for both seats rather than a special
+      -- case for this one: they are drawn at the same height because
+      -- they are drawn with the same pixels.
+      --
+      -- Listed after `bills_desk` although neither grid is a subgrid of
+      -- the other (that grid has 43/44 over 59/60, this one 61/62), so
+      -- the order is free and the bigger, more specific grid keeps
+      -- first claim.
+      {
+        id = "club_stool",
+        tiles = {
+          { 61, 62 },
+          { 59, 60 },
+        },
+        roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
+        slab = 0, frontEave = 0, ledge = nil,
+        panes = false,
+        parts = {
+          { kind = "upright", x = { 2, 13 }, top = { 5, 10 },
+            facade = { 11, 15 }, z = 3, depth = 11,
+            stretch = true },                             -- the stool
+        },
+      },
     },
   },
 }
diff --git a/lib/Buildings.lua b/lib/Buildings.lua
index 04a55df..def6c81 100644
--- a/lib/Buildings.lua
+++ b/lib/Buildings.lua
@@ -440,14 +440,68 @@ local function deskSetModel(sp, pr, t)
       if p.kind == "flat" then
         -- drawn row = depth row by default; `z` renames the origin when
         -- the flat sits below the desk's own drawn top span (the Center
-        -- PC's keyboard)
+        -- PC's keyboard). `at` names the sheet's own height when it does
+        -- not lie on the desk plane (the healing machine's keyboard is a
+        -- shelf mounted on the cabinet's side); `thick` gives it a body
+        -- -- layers below the sheet repeating each column's own texel,
+        -- the same continuation rule every synthesized surface follows.
         local r0 = p.rows[1]
         local z0 = p.z or r0
+        local atY = p.at or plane
+        local thick = p.thick or 1
+        if atY > ytop then ytop = atY end
         for sy = r0, p.rows[2] do
           local z = z0 + (sy - r0)
           if z >= 0 and z < D then
             for sx = x0, x1 do
-              if inside[sy * W + sx] then put(sx, plane, z, sy * W + sx) end
+              if inside[sy * W + sx] then
+                for y = math.max(0, atY - thick + 1), atY do
+                  put(sx, y, z, sy * W + sx)
+                end
+              end
+            end
+          end
+        end
+      elseif p.kind == "box" then
+        -- A BOX part is a drawn rect standing at its own drawn
+        -- elevation -- equipment attached to the machine rather than an
+        -- object on the desk plane. The rows are face-on art: the top
+        -- row's drawn height IS the box's top (ground - 1 - r0,
+        -- measured), and the box runs down to `base` (default the drawn
+        -- extent; 0 continues it to the floor, the legs-continue rule).
+        -- Height beyond the drawn rows fills the way a roof band does:
+        -- rows before `cycle` map 1:1 from the top, rows after it 1:1
+        -- from the bottom -- the healing machine hoses' foot lands ON
+        -- the floor -- and the cycle window repeats between.
+        local r0, r1 = p.rows[1], p.rows[2]
+        local c0 = p.cycle and p.cycle[1] or r1
+        local c1 = p.cycle and p.cycle[2] or r1
+        local pz = p.z or 0
+        local pd = p.depth
+        local top = pr.ground - 1 - r0
+        local bot = p.base or (pr.ground - 1 - r1)
+        local nTop, nBot = c0 - r0, r1 - c1
+        if top > ytop then ytop = top end
+        for y = bot, top do
+          local k, j = top - y, y - bot
+          local sy
+          if k < nTop then
+            sy = r0 + k
+          elseif j < nBot then
+            sy = r1 - j
+          else
+            sy = c0 + (k - nTop) % (c1 - c0 + 1)
+          end
+          for sx = x0, x1 do
+            local i = sy * W + sx
+            if inside[i] then
+              local ix = interiorAt(sx, sy, x0, x1)
+              for z = pz, pz + pd - 1 do
+                if z >= 0 and z < D then
+                  local px = (z == pz or z == pz + pd - 1) and sx or ix
+                  put(sx, y, z, sy * W + px)
+                end
+              end
             end
           end
         end
@@ -700,6 +754,11 @@ local function deskSetModel(sp, pr, t)
   local dz0 = t.desk.z or 0
   local dz1 = dz0 + deskD - 1
   local deskG = b1 + 1
+  -- ...and the desk's COLUMNS (`x`), when the grid is wider than the
+  -- desk: the healing machine's grid carries its flanking hoses and
+  -- keyboard, and the cabinet is only the middle 16 columns.
+  local dx0 = t.desk.x and t.desk.x[1] or 0
+  local dx1 = t.desk.x and t.desk.x[2] or W - 1
 
   -- The WALL element: the band the machine backs onto, whose tiles this
   -- grid claims. The drawing shows it only as the stripe background
@@ -727,9 +786,9 @@ local function deskSetModel(sp, pr, t)
   for sy = b0, b1 do
     Budget.tick()
     local y = deskG - 1 - sy
-    for sx = 0, W - 1 do
+    for sx = dx0, dx1 do
       if inside[sy * W + sx] then
-        local ix = interiorAt(sx, sy, 0, W - 1)
+        local ix = interiorAt(sx, sy, dx0, dx1)
         for z = dz0, dz1 do
           local px = (z == dz0 or z == dz1) and sx or ix
           put(sx, y, z, sy * W + px)
@@ -739,8 +798,9 @@ local function deskSetModel(sp, pr, t)
   end
   for i in pairs(pr.recess) do
     local sy = math.floor(i / W)
-    if sy >= b0 and sy <= b1 then
-      vox[key(i % W, deskG - 1 - sy, dz1)] = nil
+    local sx = i % W
+    if sy >= b0 and sy <= b1 and sx >= dx0 and sx <= dx1 then
+      vox[key(sx, deskG - 1 - sy, dz1)] = nil
     end
   end
 
@@ -748,7 +808,7 @@ local function deskSetModel(sp, pr, t)
   for sy = f0, f1 do
     Budget.tick()
     local y = plane - 1 - (sy - f0)
-    for sx = 0, W - 1 do
+    for sx = dx0, dx1 do
       for z = dz0, dz1 do put(sx, y, z, sy * W + sx) end
     end
   end
@@ -767,19 +827,19 @@ local function deskSetModel(sp, pr, t)
     for z = dz0, dz1 do
       Budget.tick()
       local sy = z == dz1 and f0 or math.min(tr0 + (z - dz0), tr1)
-      for sx = 0, W - 1 do
+      for sx = dx0, dx1 do
         local px = sx
         for _, p in ipairs(t.parts) do
           local px0, px1 = p.x[1], p.x[2]
           local r0, r1
-          if p.kind == "flat" or p.kind == "iso" then
+          if p.kind == "flat" or p.kind == "iso" or p.kind == "box" then
             r0, r1 = p.rows[1], p.rows[2]
           else
             r0, r1 = p.top[1], p.facade[2]
           end
           if sx >= px0 and sx <= px1 and sy >= r0 and sy <= r1 then
             px = (sx - px0 < px1 - sx) and (px0 - 1) or (px1 + 1)
-            px = math.max(0, math.min(W - 1, px))
+            px = math.max(dx0, math.min(dx1, px))
             break
           end
         end
@@ -790,12 +850,12 @@ local function deskSetModel(sp, pr, t)
     -- the lid continues the sibling tables' top -- black rim, white
     -- highlight courses along the north and west, grey field
     local field = t.desk.lid == "white" and WHITE or GREY
-    for sx = 0, W - 1 do
+    for sx = dx0, dx1 do
       for z = dz0, dz1 do
         local shade = field
-        if sx == 0 or sx == W - 1 or z == dz0 or z == dz1 then
+        if sx == dx0 or sx == dx1 or z == dz0 or z == dz1 then
           shade = BLACK
-        elseif sx == 1 or z == dz0 + 1 then
+        elseif sx == dx0 + 1 or z == dz0 + 1 then
           shade = WHITE
         end
         put(sx, plane - 1, z, pr.shadeTexel[shade])
diff --git a/lib/VoxelScene.lua b/lib/VoxelScene.lua
index daa4131..e06036e 100644
--- a/lib/VoxelScene.lua
+++ b/lib/VoxelScene.lua
@@ -583,8 +583,40 @@ end
 -- The overworld's alone: the staged battle draws its water plain, always --
 -- its placed camera reads this pass wrong, and a stage set wants painted
 -- water anyway (see BattleScene, where the choice is argued).
+-- ------- and why the flat draw happens FIRST, every time
+--
+-- The reflective pass writes no depth -- it cannot, the depth canvas is
+-- detached for the length of it so the shader can READ it -- and it does its
+-- own depth test against that texture instead. That test asks whether
+-- something opaque is in front, and it answers correctly for every case but
+-- one: WATER IN FRONT OF WATER. Nothing puts water in the depth buffer, so
+-- no lake can hide another, and the pass simply paints them in mesh order.
+--
+-- On flat water that never mattered, because every surface lies in the one
+-- plane at its own recessed height and a farther sheet always lands farther
+-- down the screen. THE WORLD CURVE ENDS THAT. The bend drops the world by
+-- the square of its distance, so the far side of the map swings down and
+-- back up into the near field of view -- and a sheet of sea a hundred and
+-- fifty tiles away, drawn later in the same mesh, paints straight over the
+-- pond at the player's feet. That is the tall grass and the water "from the
+-- other side of the map": not a reflection of them, THEM, rasterised on top
+-- of the water in front of you.
+--
+-- So the meshes go down flat first, through the ordinary scene shader with
+-- depth writes on, and the reflective pass draws over the top of what
+-- survived. Three things fall out of it and all three are wanted: the depth
+-- buffer now holds the water surface, so the pass's own test throws the far
+-- sheet away; the reflection COPY holds it too, so a ray grazing another
+-- part of the lake reads water rather than the void behind it; and the flat
+-- draw is the same fallback this function already ended with, so a frame
+-- that cannot run the reflective pass is unchanged.
+--
+-- It costs one more rasterisation of the water meshes. Water is a small
+-- share of a frame's triangles and this is the cheapest shader in the mode.
 function VoxelScene.drawWater(draws, cast)
-  local plain = true
+  for _, d in ipairs(draws) do
+    Voxel3D.draw(d[1], d[2], d[3])
+  end
   if Water.enabled() and Voxel3D.depthReadable() then
     local mirror, depth = Voxel3D.beginWater(cast)
     local w, h = Voxel3D.size()
@@ -602,20 +634,15 @@ function VoxelScene.drawWater(draws, cast)
         Water.draw(d[1], d[2], d[3])
       end
       Water.finish()
-      plain = false
     end
     -- Unconditionally, and OUTSIDE the success branch: beginWater unbinds
     -- the shader and the depth mode BEFORE it can discover it cannot go on,
     -- so a frame that bails halfway through has to be put back together
-    -- exactly like one that succeeded -- otherwise the plain draw below (and
-    -- every pass after it) runs with no shader and no depth test.
+    -- exactly like one that succeeded -- otherwise every pass after it runs
+    -- with no shader and no depth test. (What a bail leaves on screen is the
+    -- flat draw above, which is where this function used to end anyway.)
     Voxel3D.endWater()
   end
-  if plain then
-    for _, d in ipairs(draws) do
-      Voxel3D.draw(d[1], d[2], d[3])
-    end
-  end
 end
 
 -- A stamp of everything the sun pass depends on. Nothing in it moving
diff --git a/lib/Water.lua b/lib/Water.lua
index 4ab4022..3bfcf4e 100644
--- a/lib/Water.lua
+++ b/lib/Water.lua
@@ -956,8 +956,15 @@ vec4 effect(mediump vec4 color, Image tex, mediump vec2 tc, mediump vec2 sc) {
   // the bound canvas's own pixel size, the same units sc is measured in, on
   // every display. (`screen` stays in units: skyPos reads it against cell
   // and skyEdge, which are unit-measured with it.)
+  //
+  // The buffer now holds THIS SURFACE too (VoxelScene draws the water flat
+  // before the pass that reflects it), which is what makes one lake able to
+  // hide another -- and it means every fragment here is testing against its
+  // own depth. The two draws reach it through different shader programs, so
+  // ask for a hair of slack rather than bit-equality: far less than the gap
+  // to anything genuinely in front, far more than two compilers disagree by.
   vec2 uv = sc / love_ScreenSize.xy;
-  if (gl_FragCoord.z > Texel(depthTex, uv).r) discard;
+  if (gl_FragCoord.z > Texel(depthTex, uv).r + 1e-5) discard;
 
   // THE COLUMN THIS FRAGMENT IS LOOKING AT. Every water pixel is a bar of
   // its own standing a whole number of pixels tall, and the ray decides
diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua
index 2fb9094..a832e15 100644
--- a/tests/dramatic_shape_test.lua
+++ b/tests/dramatic_shape_test.lua
@@ -1915,6 +1915,19 @@ T.check(plain:find("sc / love_ScreenSize.xy", 1, true) ~= nil,
   .. "size -- `screen` counts canvas UNITS, and on a highdpi phone the two "
   .. "differ by the density, which clamped the lookup and cut the water "
   .. "into blocks")
+-- ...and it tests against a buffer that now holds the water surface too,
+-- because VoxelScene lays the meshes down flat before the reflective pass
+-- runs over them. Nothing else can make one lake hide another: the pass
+-- writes no depth (the buffer is detached so it can be READ), so before this
+-- the sheets were simply painted in mesh order. Flat water never showed it --
+-- one plane, and a farther sheet always lands farther down the screen -- but
+-- the world curve drops the far side of the map into the near field of view,
+-- and a sea a hundred and fifty tiles away came out rasterised on top of the
+-- pond at the player's feet, tall grass and all.
+T.check(plain:find("Texel(depthTex, uv).r + 1e-5", 1, true) ~= nil,
+  "with a hair of slack, because every water fragment is now testing "
+  .. "against its own depth through a second shader program")
+T.check(VoxelScene.drawWater ~= nil, "and the flat draw that puts it there")
 
 -- ------- the lift itself
 --
diff --git a/tools/building_voxels.py b/tools/building_voxels.py
index a73fa9d..bcc9078 100644
--- a/tools/building_voxels.py
+++ b/tools/building_voxels.py
@@ -765,6 +765,35 @@ TEMPLATES = {
                  z=3, depth=11, stretch=True),                # the stool
         ],
     ),
+    # F09 once more, on the interior atlas: the Pokemon Fan Club's four
+    # members' chairs, drawn round the boardroom table -- 4 placements
+    # (cells (1,3), (1,4), (6,3), (6,4)) and nowhere else in the game.
+    # A DIFFERENT drawing from the house stool -- its seat field carries
+    # a one-pixel white margin where the house's carries two -- but the
+    # same object band for band, and its elevation rows (11-15: the
+    # seat's front edge, then the legs) are pixel-identical to the house
+    # drawing's. So it takes the same part table unchanged, including
+    # the two-voxel growth north and south.
+    #
+    # Those elevation rows come from tiles 59/60, which this drawing
+    # SHARES with Bill's chair (43/44 over 59/60, modelled as a part of
+    # `bills_desk`) -- which is why one `stool = 5` height override in
+    # the tileset entry is right for both seats rather than a special
+    # case: they are drawn at the same height because they are drawn
+    # with the same pixels.
+    "club_stool": dict(
+        tiles=[
+            [61, 62],
+            [59, 60],
+        ],
+        roof_rows=0, roof_back=0, roof_front=0, roof_cycle=(0, 0),
+        slab=0, front_eave=0, ledge=None, tileset="interior",
+        panes=False,
+        parts=[
+            dict(kind="upright", x=(2, 13), top=(5, 10), facade=(11, 15),
+                 z=3, depth=11, stretch=True),                # the stool
+        ],
+    ),
     # F04: the Pokemon Center's PC -- every Center's northeast corner
     # (11 placements) plus the Indigo Plateau lobby, whose MART tileset
     # shares this atlas. The lab desk-set read again: a
@@ -829,29 +858,31 @@ TEMPLATES = {
     #               voxel via `inset` -- the pane rule by hand, since
     #               `panes=False` blocks the global pass.
     #   rows 16-31, x0..x7    the HOSES (tile 72 twice): one 8-row
-    #               motif per map row -- a horizontal run high on the
-    #               cabinet's side (16-18, x3..x7 -> heights 13..15,
-    #               measured off the drawn rows) and a shaded drop
-    #               (19-23, x2..x7) that bends down INTO THE FLOOR.
-    #               The two stacked motifs are two hoses in DEPTH (the
-    #               drawn row band is the depth band): `box` parts at
-    #               z 18 and z 24, 3 deep, the drop cycling its two
-    #               shading rows (19-20) down to the floor with the
-    #               drawn foot rows (21-23) landing at the bottom --
-    #               the roof-rim rule turned vertical. The second
-    #               hose re-wears the first motif's rows: the atlas
-    #               tile is the same, so the pixels are identical.
+    #               motif per map row, and the two stacked motifs are
+    #               two hoses in DEPTH -- the drawn row band is the
+    #               depth band, and each motif's own rows are its
+    #               elevation WITHIN that band, exactly the potted
+    #               plants' stacked-cell rule. So both hoses hug the
+    #               floor: a horizontal run off the cabinet's side
+    #               (rows 24-26, x3..x7 -> heights 5..7) over a shaded
+    #               drop landing ON the floor (rows 27-31, x2..x7 ->
+    #               heights 0..4) -- all measured, the drawn extent
+    #               maps 1:1 with no continuation. `box` parts at z 18
+    #               and z 24, 3 deep; both wear the LOWER motif's rows
+    #               (the one drawn at its true elevation; the upper
+    #               motif is the same atlas tile, pixel-identical).
     #   rows 16-31, x24..x31  west variant: the KEYBOARD (7/13), a
     #               key grid with its cable at the north end and a bar
     #               down the east edge -- TOP-VIEW art, 16 rows = 16
     #               depth rows. A `flat` part mounted on the cabinet's
-    #               side at working height (`at` 11, authored -- the
-    #               one number top-view art cannot state), 3 thick,
-    #               top face wearing the drawn art. The stray dark
-    #               dashes east of it (x30-31) are its cast shadow on
-    #               the floor: background. East variant: mirrored
-    #               hoses (runs 16-18 x24..x28, drops 19-23 x24..x29),
-    #               same z slots.
+    #               side at COUNTER level (`at` 8, the counters' own
+    #               8px band -- the one number top-view art cannot
+    #               state, pinned to the room's work surface), 3
+    #               thick, top face wearing the drawn art. The stray
+    #               dark dashes east of it (x30-31) are its cast
+    #               shadow on the floor: background. East variant:
+    #               mirrored hoses (runs x24..x28, drops x24..x29),
+    #               same rows and z slots.
     #   rows 0-15 elsewhere   wall stripes and cast shadow: BACKGROUND.
     #               The `wall` element keeps the band solid over the
     #               back map row, full grid width -- a 16-tall,
@@ -878,16 +909,16 @@ TEMPLATES = {
             dict(kind="upright", x=(10, 21), top=(1, 3), facade=(4, 13),
                  z=20, depth=4,
                  inset=dict(x=(13, 18), rows=(6, 8))),       # the monitor
-            dict(kind="box", x=(3, 7), rows=(16, 18),
+            dict(kind="box", x=(3, 7), rows=(24, 26),
                  z=18, depth=3),                             # north hose run
-            dict(kind="box", x=(2, 7), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=18, depth=3),                     # north hose drop
-            dict(kind="box", x=(3, 7), rows=(16, 18),
+            dict(kind="box", x=(2, 7), rows=(27, 31),
+                 z=18, depth=3),                             # north hose drop
+            dict(kind="box", x=(3, 7), rows=(24, 26),
                  z=24, depth=3),                             # south hose run
-            dict(kind="box", x=(2, 7), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=24, depth=3),                     # south hose drop
+            dict(kind="box", x=(2, 7), rows=(27, 31),
+                 z=24, depth=3),                             # south hose drop
             dict(kind="flat", x=(24, 29), rows=(16, 31),
-                 z=16, at=11, thick=3),                      # keyboard shelf
+                 z=16, at=8, thick=3),                       # keyboard shelf
         ],
     ),
     "center_heal_machine_e": dict(
@@ -907,22 +938,22 @@ TEMPLATES = {
             dict(kind="upright", x=(10, 21), top=(1, 3), facade=(4, 13),
                  z=20, depth=4,
                  inset=dict(x=(13, 18), rows=(6, 8))),       # the monitor
-            dict(kind="box", x=(3, 7), rows=(16, 18),
+            dict(kind="box", x=(3, 7), rows=(24, 26),
                  z=18, depth=3),                             # NW hose run
-            dict(kind="box", x=(2, 7), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=18, depth=3),                     # NW hose drop
-            dict(kind="box", x=(3, 7), rows=(16, 18),
+            dict(kind="box", x=(2, 7), rows=(27, 31),
+                 z=18, depth=3),                             # NW hose drop
+            dict(kind="box", x=(3, 7), rows=(24, 26),
                  z=24, depth=3),                             # SW hose run
-            dict(kind="box", x=(2, 7), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=24, depth=3),                     # SW hose drop
-            dict(kind="box", x=(24, 28), rows=(16, 18),
+            dict(kind="box", x=(2, 7), rows=(27, 31),
+                 z=24, depth=3),                             # SW hose drop
+            dict(kind="box", x=(24, 28), rows=(24, 26),
                  z=18, depth=3),                             # NE hose run
-            dict(kind="box", x=(24, 29), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=18, depth=3),                     # NE hose drop
-            dict(kind="box", x=(24, 28), rows=(16, 18),
+            dict(kind="box", x=(24, 29), rows=(27, 31),
+                 z=18, depth=3),                             # NE hose drop
+            dict(kind="box", x=(24, 28), rows=(24, 26),
                  z=24, depth=3),                             # SE hose run
-            dict(kind="box", x=(24, 29), rows=(19, 23), cycle=(19, 20),
-                 base=0, z=24, depth=3),                     # SE hose drop
+            dict(kind="box", x=(24, 29), rows=(27, 31),
+                 z=24, depth=3),                             # SE hose drop
         ],
     ),
     # F06: the Bike Shop's toolbox -- 2 placements, cells (6,6) and (7,7)