From 0b8730fbf6a90b98f592a8ef55a40a1c051c719d Mon Sep 17 00:00:00 2001 From: DramaticShape Date: Mon, 27 Jul 2026 04:39:27 -0400 Subject: [PATCH] rejoin the Pokemon Tower across the LAVENDER_TOWN / ROUTE_10 seam The tower's drawing straddles the map boundary -- roof band and top window courses in Route 10's last rows, the rest in Lavender -- so each map folded its half upright alone: two half-buildings, Lavender's under a fabricated yellow cap. Buildings templates gain `topRows`: drawing rows composited ABOVE the matched grid, for a drawing no single map holds. The model is built from the complete 16-row artwork (96px facade under the real 32px purple band) while placement still matches only the on-map rows; depth now derives from the matched footprint rather than sprite height, identical for every whole-drawing template. A `claimOnly` twin template claims Route 10's roof rows and stamps nothing, so the top half no longer also stands as its own building. Both templates verified to place exactly once, world-wide, at the intended coordinates. --- data/voxel_heights.lua | 105 +++++++++++++++++++++-------------------- lib/Buildings.lua | 40 ++++++++++++++-- 2 files changed, 89 insertions(+), 56 deletions(-) diff --git a/data/voxel_heights.lua b/data/voxel_heights.lua index e171f27..55bec61 100644 --- a/data/voxel_heights.lua +++ b/data/voxel_heights.lua @@ -2731,59 +2731,45 @@ return { roofRows = 64, roofBack = 8, roofFront = 8, roofCycle = { 8, 31 }, slab = 4, frontEave = 4, ledge = nil, seal = "s", }, - -- assets/docs/buildings/B30: the POKEMON TOWER, and the one drawing - -- in the catalogue the map edge cuts off. It stands at - -- LAVENDER_TOWN (12,0), tile row 0, so the four rows of roof band - -- every other block of this family wears are simply not on the map: - -- rows 0..55 are three window courses over brick, 56..62 the base - -- course, and that is the whole drawing. + -- assets/docs/buildings/B30: the POKEMON TOWER. The one drawing + -- in the catalogue that STRADDLES A MAP BOUNDARY: its purple roof + -- band and top two window courses stand in ROUTE_10's last eight + -- tile rows, and only the lower eight rows -- six more courses and + -- the base with the door -- are on LAVENDER_TOWN, where the + -- building begins at tile row 0. Modelled per map it came out as + -- two half-buildings: Lavender's half wearing a fabricated flat + -- cap (art row 0 stretched into a yellow slab), Route 10's half a + -- low volume of roof drawing folded at ground level behind it. -- - -- REMAINING.md files it unvoxelizable on two counts. The first is - -- real and `seal` answers it; the second is a misreading of it. + -- `topRows` is the fix, built for this template: the eight + -- ROUTE_10 rows are composited ABOVE the matched grid, so the + -- model is the COMPLETE 16-row drawing -- 96px of facade under a + -- real 32px roof band -- while placement still matches only the + -- Lavender rows. Its twin `pokemon_tower_top` below claims the + -- Route 10 rows and stamps nothing, so the roof half no longer + -- also stands as its own building behind the tower. -- - -- Unbounded silhouette. Every sibling is closed on the north by - -- the black outline along the top of its roof band. Here the top - -- row is the 1px light margin over the first window course, so the - -- flood seeds the whole of y=0, drops down the 2px light channel - -- between the side outline (x=5, x=90) and the window band (x=8), - -- and from the first brick course eats the wall out: 37% fill in - -- 126 pieces. North is the ONLY way in -- the base course seals - -- the south, the side outlines seal east and west -- so `seal = - -- "n"` alone restores it: 88% fill, ONE piece, largest 100%, in - -- the shipped band. The 728px left outside are exactly the 5px - -- grass margin down each flank plus the terrain row under the base - -- course, the same margins every sibling keeps. (REMAINING.md - -- tests "s" and "nswe" and concludes the box cannot be sealed - -- because a quarter of it is ground -- but that quarter is the - -- unsealed flood's own damage measured back: its 1552px is - -- precisely what the flood ate. North alone is never tried.) - -- - -- No roof drawn. True, and not fixable: `tiles` is the matcher as - -- well as the art, so the roof band cannot be borrowed from B14 - -- without asking the map for tiles at ty=-4. One row of band is - -- the least the pipeline can be given, and roofRows = 1 spends it - -- on that light top margin alone -- no window course is eaten, the - -- whole 63px of drawn facade stays standing, and the 4px slab caps - -- it flat. 67 voxels: nearly twice the Center and the Mart beside - -- it, and one voxel under its own twin B14 -- which is right, - -- because B14's facade IS this drawing. - -- - -- The last row carries a THIRTEENTH tile id. `matches` walks each - -- row to its own length while `read` composites only the first - -- #tiles[1] columns, so that id constrains where the template may - -- land without entering the drawing -- and it must not enter it, - -- because the only tile beside the tower is the $11 filler, whose - -- hatch carries isolated dark pixels the flood cannot reach: drawn, - -- each becomes a 64-deep rod of speckle hanging beside the tower. - -- The constraint is needed because these 8 rows ARE rows 5..12 of - -- flat_block_6x6: the bare grid matches four times, the tower plus - -- the lower two thirds of B14 at CELADON_CITY (14,4), ROUTE_16 - -- (18,6) and ROUTE_18 (34,4), and nothing stops a second model being - -- stamped inside the first. $11 sits east of the tower's south-east - -- corner; the three B14 plots carry grass, path and a plant there - -- ($23, $30, $55), so with the extra id the grid resolves once. + -- The old silhouette problem is gone with the roof back: the band + -- closes the drawing's north outline the way it does on every + -- sibling, so no `seal` is needed. The 13-id last row survives + -- from the first attempt: the bare 12-wide grid is B14's lower + -- two-thirds tile for tile, and the extra id keeps this template + -- from stamping inside flat_block_6x6's three placements + -- (`matches` walks a row to its own length; `read` composites + -- only #tiles[1] columns, so the 13th id constrains placement + -- without entering the drawing). { id = "pokemon_tower", + topRows = { + { 21, 56, 18, 18, 18, 18, 18, 18, 18, 18, 56, 25 }, + { 21, 56, 18, 18, 18, 18, 18, 18, 18, 18, 56, 25 }, + { 21, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 25 }, + { 37, 38, 34, 34, 34, 34, 34, 34, 34, 34, 40, 41 }, + { 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 31 }, + { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, + { 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 31 }, + { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, + }, tiles = { { 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 31 }, { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, @@ -2794,8 +2780,25 @@ return { { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, { 78, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 79, 17 }, }, - roofRows = 1, roofBack = 1, roofFront = 0, roofCycle = { 0, 0 }, - slab = 4, frontEave = 4, ledge = nil, seal = "n", + roofRows = 32, roofBack = 7, roofFront = 8, roofCycle = { 5, 12 }, + slab = 4, frontEave = 4, ledge = nil, + }, + -- the tower's roof half, where it actually stands on ROUTE_10: + -- claimed flat so the drawing does not ALSO fold up as a second + -- building behind the modelled tower (see topRows above) + { + id = "pokemon_tower_top", + claimOnly = true, + tiles = { + { 21, 56, 18, 18, 18, 18, 18, 18, 18, 18, 56, 25 }, + { 21, 56, 18, 18, 18, 18, 18, 18, 18, 18, 56, 25 }, + { 21, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 25 }, + { 37, 38, 34, 34, 34, 34, 34, 34, 34, 34, 40, 41 }, + { 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 31 }, + { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, + { 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 31 }, + { 15, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 31 }, + }, }, }, diff --git a/lib/Buildings.lua b/lib/Buildings.lua index 050a312..244943a 100644 --- a/lib/Buildings.lua +++ b/lib/Buildings.lua @@ -98,9 +98,24 @@ local models = {} -- ":" -> prebuilt local quads -- Composite the template out of the atlas and flood the silhouette in from -- the border. Returns flat arrays indexed y * W + x. +-- +-- `topRows`, when a template carries it, is extra drawing rows composited +-- ABOVE the matched grid: rows of the same drawing that are not on the +-- map this template places on. The Pokemon Tower is the case that needs +-- it -- the drawing straddles the LAVENDER_TOWN / ROUTE_10 boundary, its +-- roof band and top window courses standing in the route's last rows, so +-- no single map's grid holds the whole building. The matcher never sees +-- topRows (placement is still by `tiles` alone); they exist so the MODEL +-- is built from the complete drawing and the tower rises to its real +-- height instead of folding as two half-buildings. local function read(t, data, perRow) local tiles = t.tiles - local bh, bw = #tiles, #tiles[1] + if t.topRows then + tiles = {} + for _, row in ipairs(t.topRows) do tiles[#tiles + 1] = row end + for _, row in ipairs(t.tiles) do tiles[#tiles + 1] = row end + end + local bh, bw = #tiles, #t.tiles[1] local W, H = bw * 8, bh * 8 local col, ax, ay = {}, {}, {} for sy = 0, H - 1 do @@ -258,7 +273,13 @@ local function measure(sp, t) shadeTexel[s] = shadeTexel[s] or shadeTexel[BLACK] or 0 end - return { top = top, ytop = ytop, D = H, + -- Depth is the MATCHED footprint, not the sprite height. The two are + -- the same number for every whole-drawing template (the sprite is + -- built from `tiles` alone), but a template with `topRows` has a + -- sprite taller than its footprint -- the tower's 16-row drawing + -- stands on the 8 rows of it that are actually on the map, and D = H + -- would have pushed its body 64px south into the town plaza. + return { top = top, ytop = ytop, D = #t.tiles * 8, recess = recess, interior = interior, shadeTexel = shadeTexel } end @@ -610,9 +631,18 @@ function Buildings.build(S, map, data, perRow) if not built then local key = tileset.id .. ":" .. index if not models[key] then - local sp = read(t, data, perRow) - local pr = measure(sp, t) - models[key] = emit(model(sp, pr, t), sp, atlasW, atlasH) + if t.claimOnly then + -- claim the cells, stamp nothing: the drawing here is + -- the off-map half of a building another map models in + -- full (the tower's roof rows on ROUTE_10 -- Lavender's + -- placement composites them via topRows). Left to the + -- detector they stood as a second half-building. + models[key] = {} + else + local sp = read(t, data, perRow) + local pr = measure(sp, t) + models[key] = emit(model(sp, pr, t), sp, atlasW, atlasH) + end end built = models[key] end