DramaticShapeVoxelMod archival mirror · read-only
da0fc491768f1300d3b93eb68b9419fb6127c0b9

level the cliff mounds: pin the east slope chain, region-adopt doorways

DramaticShape · 2026-07-27 · parent 5a94dfe85a

level the cliff mounds: pin the east slope chain, region-adopt doorways

The cliff drawing's dark east slope ($02/$24/$34) broke the repeat scan
at its corners and rose to 32px towers; a folded doorway column read
its own drawn extent and towered a block over the 16px mound around
Diglett's Cave. The slope chain is pinned to one 16px course, and a
doorway column now takes height AND top from its region: roofed among
drawn facades (houses), flat among flat repeats (the mound). Adds the
voxel_mound_probe driver that prints per-column detector decisions.

Changed files

CHANGELOG.md                | 19 +++++++++++++++++++
 data/voxel_heights.lua      |  9 +++++++++
 lib/Structures.lua          | 41 ++++++++++++++++++++++++++++++++++++-----
 tests/voxel_mound_probe.lua | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 129 insertions(+), 5 deletions(-)

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 242e5ff..1df9b24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -138,6 +138,25 @@
   construction: they now carry an `own` flag the edge keep-rules never
   touch.
 
+- **Diglett's Cave mounds (and cliffs everywhere) stop sprouting
+  towers.** Two detector misreadings stacked up on the cave-entrance
+  mound. The dark east slope of the cliff drawing ($02/$24/$34) is one
+  texture repeated over the mound's whole height, but its corner tiles
+  break the repeat scan, so those columns rose to 32px -- the rock
+  pillar beside the entrance. And a folded doorway column reads its own
+  drawn extent (the door plus everything above it), which is a house's
+  real height when the door is a house's, but a 32px tower over a 16px
+  plateau when the door is a cave mouth -- the entrance jumped a block
+  above the mound around it. The slope chain is now profile-pinned to
+  one 16px course, and a doorway column answers to its REGION entirely:
+  height from the region's dominant column, top flat when those columns
+  are flat repeats (the mound) and roofed when they are drawn facades
+  (a house). Both cave entrances -- and every cliff built from the same
+  slope tiles -- now read as one level mesa with the cave mouth at
+  ground level. A new `voxel_mound_probe` driver prints the detector's
+  per-column class and height over any rectangle, which is how this was
+  diagnosed.
+
 ## 1.0.3
 
 ### Added
diff --git a/data/voxel_heights.lua b/data/voxel_heights.lua
index 5fe4b67..a0a3e39 100644
--- a/data/voxel_heights.lua
+++ b/data/voxel_heights.lua
@@ -129,6 +129,15 @@ return {
       -- fence-textured tower. `post` extracts each cell alone, so these
       -- render as the same thin posts, marching north
       post = { 14, 85 },
+      -- the cliff-mound's dark east slope and its corners ($02 the NE
+      -- corner, $24 the slope column, $34 the slope-to-rim seat). Their
+      -- drawn runs span the whole mound drawing, so the detector raised
+      -- them to 32px towers -- the rock pillar beside Diglett's Cave --
+      -- and the doorway column, which adopts its REGION's height,
+      -- inherited the same 32 and put the cave entrance a block above
+      -- the mound around it. Pinned to one 16px course they match the
+      -- plateau body, and the doorway drops with them.
+      wall = { 2, 36, 52 },
       -- the cuttable bush ($2D/$2E/$3D/$3E, the four tiles Cut deletes
       -- -- across the whole tileset they appear only in the five
       -- cut-tree blocks): a standing per-pixel cutout 5 voxels deep,
diff --git a/lib/Structures.lua b/lib/Structures.lua
index 127d3c0..d55ee24 100644
--- a/lib/Structures.lua
+++ b/lib/Structures.lua
@@ -192,7 +192,7 @@ function Structures.forMap(map)
   -- still overdraws a walker's feet even though characters stamp over
   -- terrain.)
   S = { shapeAt = shapeAt, tileAt = tileAt, outdoor = Map.isOutdoor(def),
-        runs = {}, skip = {}, ground = {}, objectQuads = {},
+        runs = {}, skip = {}, ground = {}, doorFold = {}, objectQuads = {},
         grassQuads = {}, flowerQuads = {}, roundStamps = {} }
   Buildings.build(S, map, pixels(tileset), perRow)
 
@@ -213,7 +213,12 @@ function Structures.forMap(map)
         if ns and ns.art == "upright" then
           for dy = 0, 1 do
             for dx = 0, 1 do
-              shapeAt[keyOf(cx * 2 + dx, cy * 2 + dy)] = shapes.classes.wall
+              local dk = keyOf(cx * 2 + dx, cy * 2 + dy)
+              shapeAt[dk] = shapes.classes.wall
+              -- remembered for buildVolume: a folded doorway column
+              -- answers to its REGION for height and top, not to its
+              -- own drawn extent (see the door adoption there)
+              S.doorFold[dk] = true
             end
           end
         end
@@ -1314,6 +1319,7 @@ function Structures.buildVolume(S, map, tiles)
 
   local runs = {}
   local heightVotes = {}
+  local repeatVotes = {}
   for tx, ys in pairs(cols) do
     -- visit each contiguous vertical run in this column
     local sorted = {}
@@ -1345,11 +1351,19 @@ function Structures.buildVolume(S, map, tiles)
           end
         end
       end
+      local isDoor = false
+      for ty = north, front do
+        if S.doorFold[keyOf(tx, ty)] then
+          isDoor = true
+          break
+        end
+      end
       local run = { front = front, north = north, extent = extent,
-                    unit = unit, fromRepeat = repeatRead }
+                    unit = unit, fromRepeat = repeatRead, door = isDoor }
       runs[#runs + 1] = { tx = tx, run = run }
       local h = unit * 8
       heightVotes[h] = (heightVotes[h] or 0) + 1
+      if repeatRead then repeatVotes[h] = (repeatVotes[h] or 0) + 1 end
     end
   end
 
@@ -1361,11 +1375,27 @@ function Structures.buildVolume(S, map, tiles)
   for h, n in pairs(heightVotes) do
     if n > modeN or (n == modeN and h > modeH) then modeH, modeN = h, n end
   end
+  -- whether the region's dominant columns are flat repeats (a cliff
+  -- mound's plateau) rather than drawn facades (a house's front)
+  local modeRepeat = (repeatVotes[modeH] or 0) * 2 > modeN
   for _, r in ipairs(runs) do
     local run = r.run
     local h = run.unit * 8
     local adopted = false
-    if run.fromRepeat and modeH > h then
+    local flatDoor = false
+    if run.door then
+      -- A folded doorway column answers to its region ENTIRELY. Its own
+      -- reading spans the door plus everything drawn above it -- a
+      -- house's full height when the door is a house's, but a 32px
+      -- tower over a 16px plateau when the door is a cave mouth cut
+      -- into a cliff mound (Diglett's Cave: the entrance jumped a block
+      -- above the mound around it). Height and top both come from the
+      -- region: the mode height, roofed like a facade when the mode
+      -- columns are drawn facades, flat when they are flat repeats.
+      h = modeH
+      adopted = not modeRepeat
+      flatDoor = modeRepeat
+    elseif run.fromRepeat and modeH > h then
       h = modeH
       adopted = true
     end
@@ -1383,7 +1413,8 @@ function Structures.buildVolume(S, map, tiles)
     -- whole roof area -- and a rooftop tilted into a 48px ramp reads
     -- wrong instantly. Distinct top rows -> slope; repeated -> level top.
     local roofRows = 0
-    if S.outdoor and (not run.fromRepeat or adopted) and h >= 16 then
+    if S.outdoor and (not run.fromRepeat or adopted) and h >= 16
+       and not flatDoor then
       roofRows = math.min(2, math.floor(h / 8) - 1)
       if roofRows > 0 and map:tileAt(r.tx, run.north)
                          == map:tileAt(r.tx, run.north + 1) then
diff --git a/tests/voxel_mound_probe.lua b/tests/voxel_mound_probe.lua
new file mode 100644
index 0000000..3bbcb2a
--- /dev/null
+++ b/tests/voxel_mound_probe.lua
@@ -0,0 +1,65 @@
+-- Driver: what did the detector decide over a rectangle of tiles?
+--
+-- Prints, for every tile of MOUND_RECT on MOUND_MAP, the resolved shape
+-- class and the volume run height Structures gave it -- the ground truth
+-- for diagnosing cliff/mound columns that extrude to the wrong height
+-- (Diglett's Cave entrance ridge, bug of 2026-07-27). Then a shot.
+--
+--   POKEPORT_DRIVER=mods/DRAMATIC_SHAPE/tests/voxel_mound_probe.lua lovec .
+--
+-- knobs (env):
+--   MOUND_MAP    map id                       (default ROUTE_2)
+--   MOUND_RECT   "tx0,ty0,tx1,ty1" in tiles   (default 16,12,35,23)
+--   MOUND_SPOT   "x,y[,facing]" player cell   (default 12,12,up)
+--   SHOT_DIR     output directory, must exist (default "shots")
+return function(game)
+  local U = dofile("tests/drivers/util.lua")
+  local Pipelines = require("src.render.Pipelines")
+
+  local DIR = os.getenv("SHOT_DIR") or "shots"
+  local mapId = os.getenv("MOUND_MAP") or "ROUTE_2"
+  local rx0, ry0, rx1, ry1 = (os.getenv("MOUND_RECT") or "16,12,35,23")
+    :match("^(%-?%d+),(%-?%d+),(%-?%d+),(%-?%d+)$")
+  rx0, ry0, rx1, ry1 = tonumber(rx0), tonumber(ry0), tonumber(rx1), tonumber(ry1)
+  local sx, sy, facing = (os.getenv("MOUND_SPOT") or "12,12,up")
+    :match("^%s*(%d+)%s*,%s*(%d+)%s*,?%s*(%a*)")
+  facing = (facing ~= "" and facing) or "up"
+
+  local Zoom = require("src.render.Zoom")
+  Zoom.reset()
+  Pipelines.setLevel("tiltshift", 0)
+  U.teleport(game, mapId, tonumber(sx), tonumber(sy), facing)
+  U.wait(20)
+  Pipelines.setLevel("voxel", 3)
+  U.wait(30)
+
+  local V = game.mods.exports["DRAMATIC_SHAPE"]
+  V = V and V.lib
+  local Structures = V and V.require("Structures")
+  local ow = game.overworld
+  if Structures and ow and ow.map then
+    local S = Structures.forMap(ow.map)
+    local function keyOf(tx, ty) return (ty + 64) * 4096 + (tx + 64) end
+    print(("[mound] %s tiles (%d,%d)-(%d,%d): tile/class/h per column")
+      :format(mapId, rx0, ry0, rx1, ry1))
+    for ty = ry0, ry1 do
+      local row = {}
+      for tx = rx0, rx1 do
+        local k = keyOf(tx, ty)
+        local s = S.shapeAt[k]
+        local run = S.runs[k]
+        local h = run and run.h or (s and s.h) or 0
+        if S.skip[k] then h = 0 end
+        row[#row + 1] = ("%02X%s%02d"):format(S.tileAt[k] or 0xFF,
+          s and s.class:sub(1, 1) or "?", h)
+      end
+      print("[mound] " .. table.concat(row, " "))
+    end
+  end
+
+  game.capturePath = ("%s/mound_%s.png"):format(DIR, mapId)
+  U.wait(5)
+  Pipelines.setLevel("voxel", 0)
+  U.wait(5)
+  print("[mound] done")
+end