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