1.0.6: conditional pins, Pokemon Tower, plateau statues, cave elevation

Adds `when_above` conditional pins, resolved per position in
TileShape.at: one graphic can mean two things (the gates' $32 is both
wall base course and counter front), and a flat pin has to pick one.
Gates get half-height counters and level walls.

Pokemon Tower gains a buildings entry -- it is the drawing the map edge
cuts off, sealed on the north -- and the Indigo Plateau statues are
built like the gym statues.

Fixes: grass stands one full-height clump per tile instead of two
half-cut stubs at different depths; ledge pillars drop to ledge height;
a prop only rides furniture when its own cell is blocked (chairs were
being lifted onto tables); and the caves had water and rock pinned
backwards, which stood the Seafoam sea up as rock and cut trenches
through Mt Moon. No cave tile is below the datum now.
This commit is contained in:
DramaticShape
2026-07-27 03:54:33 -04:00
parent 7f41f323f5
commit 89b6c8513f
6 changed files with 849 additions and 217 deletions
+89
View File
@@ -1,5 +1,94 @@
# Changelog
## 1.0.6
### Added
- **Conditional pins.** A profile entry may now carry `when_above`:
tile id -> rules keyed on the tile drawn directly north of it,
resolved per POSITION in `TileShape.at`. A pin is per tile id and one
graphic can mean two things -- the route gates' `$32` is both the
wall's dark base course and every service counter's front, and it is
the bottom row of its cell either way. Pinned `wall` the counters
stood a full 16px; pinned `counter` the deep wall banks corrugated
16/8 for sixteen rows and the room read as crates. What separates the
two uses is what sits on top, so that is what the rule reads. The
gates now have half-height counters AND level walls.
- **The Pokemon Tower has an exterior.** It is the one catalogued
building drawing the map edge cuts off (no roof band is on the map at
all), and it had no `buildings` entry, so it fell to the volume path
-- which tops a run by repeating its first two rows, laying window
courses flat across the plateau. Sealing the silhouette on the north
alone closes it (88% fill, one piece, against 37% and 126 pieces
unsealed), and one row of roof band spent on the drawing's top margin
costs no window course. It stands as a real tower, panes recessed,
door on the ground.
- **The Indigo Plateau statues stand up**, built exactly like the gym
statues: plinth a solid 16px block, figure a per-pixel cutout riding
it. On the avenue the statues stack with no gap, so the flood joined
six of them into one 24-row region and the volume builder raised
ridges of boxes with the statue art folded on the front. The same
bird is drawn at the foot of every badge-check pillar, so those are
crowned too.
### Fixed
- **Tall grass: one clump per tile.** Each 8x8 tile is a whole clump,
but the template split every tile AGAIN into its top and bottom four
art rows and stood those at two different depths -- so any blade
running down a tile was cut in half, into two 4px stubs 4px apart.
One tile is now one full-height standing slab at its own depth; a
cell's 2x2 tiles still stand independently, so the player walks
between the north and south rows.
- **Ledge lines are continuous.** `$34` is the cliff slope's foot and
also the pillar between hop-down segments; pinned `wall` with the
rest of the slope chain (the Diglett's Cave fix) it stood those
pillars 16px beside a 6px lip. At ledge height the run reads as one
lip, and the mound is unchanged -- its foot row reads as the talus it
is drawn as.
- **A prop only stands on furniture when its own cell is blocked.**
"Is something drawn above me" is not "am I standing on it": a chair
drawn against the north side of a table is above the table's trim row
too, and was being lifted onto the tabletop, with its claimed cells
re-tiled as tabletop so the table marched two rows north. Three
chairs in Cinnabar's trade room and Fuchsia's meeting room, and the
Celadon diner's stools. The world already knows the difference: a
thing that sits ON furniture occupies a blocked cell, a seat you walk
up to is in a walkable one.
- **Caves: nothing below sea level, and the water is water.** Two tiles
were identified backwards in the first pass. `$14` -- the tile the
engine animates, that `Map.WATER_TILES` names and that Surf runs on
-- was pinned `wall`, so Cerulean Cave's lake and the Seafoam sea
stood up as rock slabs. And the pale dithered rock fill was pinned
`water`, cutting 612 tiles of two-cell-wide trench through four maps.
Both corrected; a sweep of all 19 cave maps now reports zero tiles
below the datum. The elevation scheme is documented in the entry and
derived from the game's own `tilePairs`: dark floor, water and drop
holes at 0, the lit shelf a 6px step above, rock at 16.
- **Cave ladders climb.** Which ladder graphic goes up and which goes
down is unanimous in the warp table -- 37 cells of one always warp
down, 40 of the other always up -- so they are real stepped flights
now, not painted plates.
- **Poke Mart's register stands on the counter.** The pin was on the
wrong tile: `$08` is the counter's own top band, not the register, so
the standee flood ate everything but two black lines. The register is
the keypad-and-receipt drawing one row up.
- **Celadon's televisions**, which were solid 16px boxes wearing the TV
art on one face, and the **Pokemon Tower reception desk** and the
**gate counters**, which stood at wall height, are all their drawn
heights now. The **Fan Club and Silph boardroom statues** were read
as seated chairmen and painted onto the tabletop; they are cutouts
standing on their pedestals, and the tables are cut to a true
octagonal footprint.
## 1.0.5
### Added
+687 -213
View File
File diff suppressed because it is too large Load Diff
+15 -1
View File
@@ -1919,10 +1919,24 @@ function Structures.buildObject(S, map, region, cluster,
-- the box's top with its feet on the box's north row, and the claimed
-- tiles keep rendering as that box (wearing its plain art) instead of
-- punching a floor-level hole through it.
--
-- Only when the prop's OWN CELL IS BLOCKED, though. "Is something
-- drawn above me?" is not the same question as "am I standing on it":
-- a chair drawn against the north side of a table is above the table's
-- trim row too, and it was being lifted onto the tabletop -- three
-- chairs standing on the furniture in Cinnabar's trade room and
-- Fuchsia's meeting room, with the claimed cells re-tiled as tabletop
-- so the table marched two rows north with them. The world already
-- knows which is which: a thing that sits ON furniture occupies a
-- blocked cell (you cannot walk through the gym statue, Red's plant,
-- the PC), while a seat you walk up to is in a walkable one.
local baseY, support = 0, nil
if force then
local bs = S.shapeAt[keyOf(cluster.minX, cluster.maxY + 1)]
if bs and bs.authored and bs.art == "upright" and (bs.h or 0) > 0 then
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
baseY, support = bs.h, bs
end
end
+56 -1
View File
@@ -202,6 +202,48 @@ local function authoredGroups(tilesetId, heights)
return out
end
-- Conditional pins: tile id -> list of { above = {tile ids}, class }.
--
-- A pin is per TILE ID, and one graphic can mean two things. The route
-- gates' $32/$33 is the case that forced this: the artist reuses it for
-- the wall's dark base course AND for every service counter's front, and
-- it is the bottom row of its cell either way. Pinned `wall` the counter
-- stands a full 16px; pinned `counter` the wall bank corrugates 16/8 for
-- sixteen rows. Neither is right, and no per-tile pin can be, because
-- forMap resolves an id to ONE shape.
--
-- What separates the two uses is what is drawn ABOVE: the wall's upper
-- course over a wall base, the counter's top over a counter front. So a
-- profile entry may carry `when_above = { [tile] = { { above = {...},
-- 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.
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
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 }
end
end
if #list > 0 then
out[tile] = list
any = true
end
end
end
return any and out or nil
end
local function shapeFor(class, heights, authored)
return { class = class, h = heights[class] or 0,
art = ART[class] or "upright",
@@ -251,7 +293,7 @@ function TileShape.forMap(map)
end
end
local shapes = { classes = {} }
local shapes = { classes = {}, cond = authoredConditions(id, heights) }
for class in pairs(FALLBACK_HEIGHTS) do
shapes.classes[class] = shapeFor(class, heights)
end
@@ -284,6 +326,19 @@ end
-- map:tileAt(tx, ty), passed in because every caller already has it.
function TileShape.at(map, shapes, tile, tx, ty)
local s = shapes[tile]
-- conditional pins first: they are authored answers that need the
-- POSITION to resolve, so they outrank both the flat pin on the same
-- 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
shapes.classes[rule.class].authored = true
return shapes.classes[rule.class]
end
end
end
if not s or s.authored then return s end
local cx = math.floor(tx / 2)
local cy = math.floor(ty / 2)
+1 -1
View File
@@ -376,7 +376,7 @@ mod.events:on("map.reloaded", function(payload)
if mapId then ChunkMesher.invalidate(mapId) end
end)
mod.exports.version = "1.0.5"
mod.exports.version = "1.0.6"
-- exposed so a companion mod can pin its own tiles' shapes or read the
-- camera without reaching into this mod's file layout
mod.exports.lib = V
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "DRAMATIC_SHAPE",
"name": "Dramatic Shape Voxel Mod",
"version": "1.0.5",
"version": "1.0.6",
"api": 2,
"entry": "main.lua",
"profile": "content",