a few fixes

This commit is contained in:
DramaticShape
2026-08-01 14:51:55 -04:00
parent c9d7e26858
commit 1e08f09c27
2 changed files with 51 additions and 11 deletions
+34 -11
View File
@@ -14,17 +14,40 @@
sea-level datum at 0, and the terrain tops out 22px up. 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 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 walked around through a gap, so a plateau flood fill would leak through
across its own ledge line and contradict. Instead a least-squares field it, meet itself across its own ledge line and conclude an area sits a
is solved over the whole connected overworld at once (lib/Elevation.lua): tier above itself. So lib/Elevation.lua closes the lines before it
every ledge is a hard one-tier step with the direction the game's own fills anything. Ledge cells group into runs, and each run is extended
hop table gives it, every body of water is hard-tied to one level, and along its own axis from either end -- across the gap the player detours
everywhere else adjacent cells prefer to be level -- so the missing tier through -- until it reaches something that closes it: unwalkable
through a bypass gap is paid off as a gentle ramp of 2px terrace treads, ground, another ledge, or the edge of the world. A run that finds
and far from any ledge the rounding snaps the world flat. The solve is nothing within reach is not a cliff anybody walks around and stays
global and anchored at Pallet, so two connected maps never disagree open. The cells that extension crosses become STEPS, and they are the
about a seam; it runs once, inside the build budget, in ~0.15s for all one piece of sloped ground in the world: their tiles grade into 4px and
43k cells of Kanto, with zero contradictions in the shipped ledge data. 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 Everything that stands on the ground rides it: characters and NPCs
(including ghosts on neighbour maps), grass tufts, flowers, props, (including ghosts on neighbour maps), grass tufts, flowers, props,
+17
View File
@@ -700,6 +700,23 @@ mod.events:on("map.reloaded", function(payload)
if mapId then ChunkMesher.invalidate(mapId) end if mapId then ChunkMesher.invalidate(mapId) end
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 -- ------- rows come and go, so the menu has to notice
-- --
-- OptionsMenu builds its row list ONCE, when it is opened, and then reads -- OptionsMenu builds its row list ONCE, when it is opened, and then reads