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()