From 1e08f09c271ec371f15091581f7a1242c38a00d9 Mon Sep 17 00:00:00 2001 From: DramaticShape Date: Sat, 1 Aug 2026 14:51:55 -0400 Subject: [PATCH] a few fixes --- CHANGELOG.md | 45 ++++++++++++++++++++++++++++++++++----------- main.lua | 17 +++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9674882..f584463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,17 +14,40 @@ sea-level datum at 0, and the terrain tops out 22px up. The subtlety is that ledges do not enclose anything -- every one can be - walked around through a gap, so a plateau flood fill would meet itself - across its own ledge line and contradict. Instead a least-squares field - is solved over the whole connected overworld at once (lib/Elevation.lua): - every ledge is a hard one-tier step with the direction the game's own - hop table gives it, every body of water is hard-tied to one level, and - everywhere else adjacent cells prefer to be level -- so the missing tier - through a bypass gap is paid off as a gentle ramp of 2px terrace treads, - and far from any ledge the rounding snaps the world flat. The solve is - global and anchored at Pallet, so two connected maps never disagree - about a seam; it runs once, inside the build budget, in ~0.15s for all - 43k cells of Kanto, with zero contradictions in the shipped ledge data. + walked around through a gap, so a plateau flood fill would leak through + it, meet itself across its own ledge line and conclude an area sits a + tier above itself. So lib/Elevation.lua closes the lines before it + fills anything. Ledge cells group into runs, and each run is extended + along its own axis from either end -- across the gap the player detours + through -- until it reaches something that closes it: unwalkable + ground, another ledge, or the edge of the world. A run that finds + nothing within reach is not a cliff anybody walks around and stays + open. The cells that extension crosses become STEPS, and they are the + one piece of sloped ground in the world: their tiles grade into 4px and + 2px treads, so walking a sealed gap climbs three crisp 2px risers where + the cliff line crosses it. + + With every line closed, an ordinary flood over the walkable ground cuts + the world into areas -- and now they really are the areas enclosed by + ledges. Those areas are then levelled against each other in whole + tiers, by least squares over the area graph rather than by propagation: + each lip votes "the area behind me is one tier over the area in front", + areas meeting across ordinary trees or water cast a small + same-ground vote, and a stray lip is outvoted by the run it disagrees + with instead of tipping half a route. Because each area is a single + variable the result is exactly FLAT -- these are plateaus, not a + smoothed field. The solve is global and anchored on Pallet's own + ground, so two connected maps never disagree about a seam; it runs once + inside the build budget, in well under a second for all 43k cells of + Kanto, and cuts them into 199 areas rising six tiers from Pallet at 0 to + the Route 22/23 highlands at 42px. + + Roughly three quarters of the ledges with standable ground on both + sides come out as an exact one-tier drop. The rest -- and the ones + whose high side is impassable mountain rock, where there is no plateau + to stand on at all -- take the level of the ground around them, which + reads as terrain rather than as a mistake, but they are the cases still + worth an eye in-game. Everything that stands on the ground rides it: characters and NPCs (including ghosts on neighbour maps), grass tufts, flowers, props, diff --git a/main.lua b/main.lua index 95ca933..1b61226 100644 --- a/main.lua +++ b/main.lua @@ -700,6 +700,23 @@ mod.events:on("map.reloaded", function(payload) if mapId then ChunkMesher.invalidate(mapId) end end) +-- ------- the terrain solver needs the whole map registry +-- +-- lib/Elevation.lua cuts the connected overworld into plateaus, which +-- takes every map's blocks and connections at once -- not just the one +-- being walked. The engine keeps that registry in main.lua's `Game`, +-- which is a LOCAL there and reachable from no mod, so it arrives here +-- instead: `mods.loaded` carries the merged dataset, and it is the only +-- moment the whole of it is handed over. Without this the solver found +-- no data, answered "no field" for every map, and the world stayed as +-- flat as it ever was -- silently, which is the part that cost a while. +mod.events:on("mods.loaded", function(payload) + local data = payload and payload.data + if data and data.maps then + V.require("Elevation").install(data) + end +end) + -- ------- rows come and go, so the menu has to notice -- -- OptionsMenu builds its row list ONCE, when it is opened, and then reads