DramaticShapeVoxelMod archival mirror · read-only
41a86b5fb222e1e3acae2931c94e4ff4a0ee4ff2

second pass at indigo plateau

DramaticShape · 2026-07-29 · parent 9a34ba9c77

second pass at indigo plateau

Changed files

data/voxel_heights.lua | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 lib/Structures.lua     | 30 +++++++++++++++++++++++++----
 lib/TileShape.lua      | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
 3 files changed, 148 insertions(+), 24 deletions(-)

Diff

diff --git a/data/voxel_heights.lua b/data/voxel_heights.lua
index 1c372d4..4079a53 100644
--- a/data/voxel_heights.lua
+++ b/data/voxel_heights.lua
@@ -2344,15 +2344,81 @@ return {
       -- facade, the walls flanking the avenue and every badge-check gate
       -- down ROUTE_23), and $2E/$2F the white pillar shaft with $20/$21 its
       -- cap.  These four groups are the enclosure.
-      cliff = { 3,
-                13, 14, 15,
+      cliff = { 3, 13,
                 32, 33, 46, 47 },
+      -- THE GATE WALLS, stacked rather than laid out in depth.  $0D/$0F/$0E
+      -- (13/15/14 -- top band, face, base) draw the League's facade, the
+      -- walls flanking the avenue and every badge-check gate down ROUTE_23,
+      -- as FOUR tile rows: 13 / 15 / 15 / 14.  That is 32px of artwork
+      -- depicting one wall, and at one class per row it built four boxes
+      -- marching north, so the wall was 32px DEEP -- you saw the southmost
+      -- row's face and the rest hid behind it.
+      --
+      -- `bookcase` collapses the run onto a single one-cell-deep box at its
+      -- full drawn height: bands from the south are base, face, face, top
+      -- band, and the two rows behind are vacated.  Same mechanism the
+      -- Mart's back wall uses.
+      -- ...and the PILASTER stacks with it.  $05/$06 is its shaft, and it
+      -- is drawn only ever inside a pilaster, so it needs no rule.  Without
+      -- this the pale columns stood 16px against a 32px brown wall and
+      -- their top vertices sat a whole course below the wall's crown.
+      bookcase = { 14, 15, 5, 6 },
+      -- Tile 13 is DUAL-USE and needs resolving per position.  It is the
+      -- gate wall's TOP BAND (block $28's first row) and it is also the
+      -- BASE COURSE under a column of rock face (blocks $18/$1A/$1B, last
+      -- row).  Pinned `bookcase` outright the second use became a one-row
+      -- rank -- an 8px stub under the rock, 352 of them over both maps.
+      --
+      -- ABOVE cannot tell them apart, which is what `when_below` is for.
+      -- Scanned over both maps through the engine's own tileAt, the tile
+      -- above a 13 is the rock face $03 for 140 base courses AND for 64
+      -- gate bands -- so a rule on `above` misfires on those 64 (it did:
+      -- their runs came out 2 and 3 bands instead of 4).  BELOW splits it
+      -- exactly: the wall's own face $0F sits under the top band and under
+      -- nothing else, 336 against 352.  So 13 defaults to `cliff` and is
+      -- promoted where the face is drawn beneath it.
+      --
+      -- 14 and 15 need no rule: 14 only ever sits under 15, 15 only ever
+      -- under 13 or 15.
+      when_below = {
+        [13] = { { below = { 15 }, class = "bookcase" } },
+        -- The pilaster's CAP ($15/$16) is the same stone as the statue's
+        -- plinth top -- the artist drew it twice -- so it too resolves per
+        -- position.  Scanned over both maps: a cap with the shaft $05/$06
+        -- beneath it is a pilaster (76 of them) and one with the plaque
+        -- base $30/$31 beneath it is a statue's plinth (47).  Default
+        -- `wall`, promoted here, so the plinth keeps its single course and
+        -- the statue standing on it still totals the 32px the wall is.
+        [21] = { { below = { 5 }, class = "bookcase" } },
+        [22] = { { below = { 6 }, class = "bookcase" } },
+      },
+      -- The other two ends of the same pair of drawings, keyed the other
+      -- way round.  A pilaster is capped at BOTH ends, so its lower cap has
+      -- the shaft ABOVE it (8 placements); and the plaque base $30/$31 is a
+      -- pilaster's foot when the shaft is above it (68) but a statue's
+      -- plinth bottom when the cap $15/$16 is (47).
+      when_above = {
+        [21] = { { above = { 5 }, class = "bookcase" } },
+        [22] = { { above = { 6 }, class = "bookcase" } },
+        [48] = { { above = { 5 }, class = "bookcase" } },
+        [49] = { { above = { 6 }, class = "bookcase" } },
+      },
+      -- The vacated rows behind a collapsed wall take the cell ABOVE the
+      -- run rather than the default hidden floor: here that is more rock
+      -- face on ROUTE_23 and the plateau's paving or grass on INDIGO_
+      -- PLATEAU, so the wall reads as set INTO the terrace instead of
+      -- standing in front of a trench of synthesized ground.
+      bookcase_backfill = "above",
       -- ONE course, and it must stay one: $15/$16 + $05/$06 + $30/$31 is
       -- the pilaster -- cap, shaft, plaque base -- and $15/$16 over $30/$31
       -- IS the statue's plinth, the artist having drawn the same stone
       -- twice.  Raising it would carry every statue standee up to 48px and
       -- break the very match the cliff height was chosen for.
-      wall = { 21, 22, 48, 49, 5, 6 },
+      -- $05/$06 is NOT here: it moved to `bookcase` above, and a tile
+      -- listed in two groups resolves by whichever `pairs` order wins --
+      -- half the pilasters kept the shaft as `wall`, which broke the run
+      -- and left their caps as isolated 8px stubs.  One group per tile.
+      wall = { 21, 22, 48, 49 },
       -- The statues, and the same bird at the pillar feet.  Black-outline
       -- segmented: the outline and everything it encloses stay, the sky
       -- and the paving around them flood away.
diff --git a/lib/Structures.lua b/lib/Structures.lua
index c4ab192..d27b364 100644
--- a/lib/Structures.lua
+++ b/lib/Structures.lua
@@ -1222,6 +1222,9 @@ local function bookcaseRank(S, map, tx, northTy, frontTy, capTile)
 end
 
 function Structures.buildBookcases(S, map, x0, x1, y0, y1)
+  -- What to do with the rows a rank VACATES (see TileShape.bookcaseBackfill).
+  -- Read once: it is a property of the tileset, not of the column.
+  local backfill = TileShape.bookcaseBackfill(map.tileset.id)
   for tx = x0, x1 do
     local ty = y1
     while ty >= y0 do
@@ -1249,10 +1252,24 @@ function Structures.buildBookcases(S, map, x0, x1, y0, y1)
               capTile = S.tileAt[ck]
             end
           end
+          -- The box is one cell deep, so it covers only the run's southmost
+          -- rows; everything north of that is vacated.  By default a vacated
+          -- row is skipped and painted with synthesized ground -- right for a
+          -- shelf standing in a room.  `bookcase_backfill = "above"` hands it
+          -- the cell above the run instead, shape and art, so a wall cut into
+          -- a terrace has more terrace behind it rather than a trench.
+          local covered = math.min(2, front - top + 1)
+          local srcK = keyOf(tx, top - 1)
+          local src = backfill == "above" and S.shapeAt[srcK] or nil
           for cy = top, front do
             local tk = keyOf(tx, cy)
-            S.skip[tk] = true
-            S.ground[tk] = false
+            if src and cy <= front - covered then
+              S.shapeAt[tk] = src
+              S.tileAt[tk] = S.tileAt[srcK]
+            else
+              S.skip[tk] = true
+              S.ground[tk] = false
+            end
           end
           bookcaseRank(S, map, tx, top, front, capTile)
           front = top - 1
@@ -1977,8 +1994,13 @@ function Structures.buildObject(S, map, region, cluster,
     local bs = S.shapeAt[keyOf(cluster.minX, cluster.maxY + 1)]
     local blocked = not map:isWalkableCell(math.floor(cluster.minX / 2),
                                            math.floor(cluster.maxY / 2))
-    if blocked and bs and bs.authored and bs.art == "upright"
-       and (bs.h or 0) > 0 then
+    -- `bookcase` supports as well as `upright`.  A prop drawn above an
+    -- authored box stands ON it whatever art the box renders with, and a
+    -- stacked box is still a box: the Plateau's gate pilasters carry a
+    -- statue on 48 of their tops, and collapsing the pilaster to a stacked
+    -- run made every one of them fail this test and drop to ground level.
+    if blocked and bs and bs.authored and (bs.h or 0) > 0
+       and (bs.art == "upright" or bs.art == "bookcase") then
       baseY, support = bs.h, bs
     end
   end
diff --git a/lib/TileShape.lua b/lib/TileShape.lua
index 31ac92b..6cb65ce 100644
--- a/lib/TileShape.lua
+++ b/lib/TileShape.lua
@@ -233,29 +233,42 @@ end
 -- class = "..." } } }`, evaluated per POSITION in TileShape.at, where
 -- the map and coordinates are in hand. First match wins; no match keeps
 -- the tile's ordinary pin.
+-- `when_below` is the mirror, and it exists because ABOVE is not always the
+-- side that tells the two uses apart.  The Plateau's $0D is the case: it is
+-- the gate wall's top band AND the base course under a column of rock face,
+-- and scanned over both maps the tile above is $03 for 64 of the first and
+-- 140 of the second -- no rule on `above` can split them.  What is BELOW
+-- does, exactly: the wall's own face $0F sits under the top band and under
+-- nothing else (336 vs 352, clean).
 local function authoredConditions(tilesetId, heights)
   local s = load()
   local entry = s and s.tilesets and s.tilesets[tilesetId]
-  local spec = entry and entry.when_above
-  if type(spec) ~= "table" then return nil end
+  if type(entry) ~= "table" then return nil end
   local out, any = {}, false
-  for tile, rules in pairs(spec) do
-    if type(tile) == "number" and type(rules) == "table" then
-      local list = {}
-      for _, rule in ipairs(rules) do
-        if type(rule) == "table" and heights[rule.class]
-           and type(rule.above) == "table" then
-          local set = {}
-          for _, t in ipairs(rule.above) do set[t] = true end
-          list[#list + 1] = { above = set, class = rule.class }
+
+  local function collect(spec, side)
+    if type(spec) ~= "table" then return end
+    for tile, rules in pairs(spec) do
+      if type(tile) == "number" and type(rules) == "table" then
+        local list = out[tile] or {}
+        for _, rule in ipairs(rules) do
+          if type(rule) == "table" and heights[rule.class]
+             and type(rule[side]) == "table" then
+            local set = {}
+            for _, t in ipairs(rule[side]) do set[t] = true end
+            list[#list + 1] = { side = side, set = set, class = rule.class }
+          end
+        end
+        if #list > 0 then
+          out[tile] = list
+          any = true
         end
-      end
-      if #list > 0 then
-        out[tile] = list
-        any = true
       end
     end
   end
+
+  collect(entry.when_above, "above")
+  collect(entry.when_below, "below")
   return any and out or nil
 end
 
@@ -357,9 +370,12 @@ function TileShape.at(map, shapes, tile, tx, ty)
   -- tile and the cell rules below (see authoredConditions)
   local rules = shapes.cond and shapes.cond[tile]
   if rules then
-    local above = map:tileAt(tx, ty - 1)
     for _, rule in ipairs(rules) do
-      if above and rule.above[above] then
+      -- NOTE map:tileAt border-EXTENDS: one row off an edge answers the
+      -- map's borderBlock, never nil.  A rule listing whatever that block
+      -- draws will fire along that whole edge (it did, on the Marts).
+      local n = map:tileAt(tx, rule.side == "above" and ty - 1 or ty + 1)
+      if n and rule.set[n] then
         -- shapes.condShape, NOT shapes.classes: the canonical class
         -- shapes are SHARED, and `wall` in particular is the very object
         -- rule 4 hands every unauthored solid tile. Marking that one
@@ -503,6 +519,26 @@ function TileShape.propBg(tilesetId)
   return bgCache[tilesetId] or nil
 end
 
+-- What a bookcase rank does with the rows it VACATES -- the ones behind the
+-- one-cell-deep box it collapses onto (a tileset entry's
+-- bookcase_backfill).  Returns the mode name, or nil for the default.
+--
+--   "above"   hand them the cell immediately above the run: its shape and
+--             its art.  A wall set INTO a terrace wants this -- the ground
+--             behind it is more terrace, not a trench.
+--   nil       skip them and paint the map's commonest ground underneath,
+--             which is right for a free-standing shelf against a wall.
+--
+-- Per tileset because it is a statement about what the drawing depicts, and
+-- the answer differs: the Mart's racks and Red's shelves stand in a room,
+-- the Plateau's gate walls are cut into a hillside.
+function TileShape.bookcaseBackfill(tilesetId)
+  local s = load()
+  local entry = s and s.tilesets and s.tilesets[tilesetId]
+  local mode = entry and entry.bookcase_backfill
+  return mode == "above" and mode or nil
+end
+
 -- Drop the cache: a mod that shadows data/voxel_heights.lua or a tileset
 -- record needs the next lookup to re-resolve (hot reload, mod toggle).
 function TileShape.invalidate()