Skip to content

Generate per-cell elevation for outdoor maps (an attempt at #41) - #50

Open
Wiggen94 wants to merge 3 commits into
artyrambles:mainfrom
Wiggen94:elevation-for-issue-41
Open

Wiggen94 wants to merge 3 commits into
artyrambles:mainfrom
Wiggen94:elevation-for-issue-41

Conversation

@Wiggen94

@Wiggen94 Wiggen94 commented Aug 24, 2026

Copy link
Copy Markdown

An attempt at issue #41.

lib/MapGrids.lua previously had hand-authored elevation data for Pallet Town and Route 1. Instead of finishing the rest by hand, this generates elevation data for every outdoor map using the same ledge tile-pair data the walker already uses in data/generated/field.lua.

The approach uses a weighted union-find over each map's walkable cells, treating known ledge hop-down overrides as authoritative and assuming normal neighbouring cells are at the same elevation by default. Pallet Town and Route 1 now use the same generation process too.

A few extra passes were added while testing, mostly in Pewter City: handling conflicting connections, matching the engine's bottom-left-tile collision rule, fixing leaked corridors and pass-through gaps, bridging cells between ledges, and filling isolated scenery from neighbouring cells. These are documented in MapGrids.lua, but they're based mostly on what looked right in Pewter rather than a known reference, so they're definitely open to challenge.

There was no existing way to actually use the generated data, so I also had a go at wiring it into the renderer. That part is probably the roughest part of this change and could likely use some improvement:

  • New lib/MapElevation.lua converts map elevation data into world-pixel heights.
  • Buildings, walls, and objects now follow the resolved ground beneath them instead of sitting on a flat pad.
  • Ledges use the elevation of their edge so they read as a lip rather than blending into the slope.
  • Water sits slightly lower relative to its banks.

Since this is all new and somewhat experimental, it's behind a setting toggle. Turning it off restores the old behaviour, including water, and switching it takes effect immediately without a reload.

Open questions

  • Is the union-find approach actually a sensible way to derive elevation data?
  • Are the current ledge and slope heights good defaults, or just values that happened to look right in Pewter City?
  • The rendering implementation was mainly a first attempt at making the generated data usable and could probably be improved.
  • Only a few maps have been checked in-game, so there are probably maps where this produces incorrect results.

…e flat draft

MapGrids.lua carried a hand-authored elevation draft for Pallet Town and
Route 1 only, and the changelog explicitly asked for help finishing it
("I first need to gather info about what the actual height of the
terrain is"). This regenerates it from the same tile-pair ledge data the
walker already uses (data/generated/field.lua), for every outdoor map,
via a weighted union-find over the walkable cell graph seeded by real
ledge hop overrides. Route 1 and Pallet Town's old draft entries are
regenerated the same way for full, uniform coverage rather than kept as
a special case.

Rendering changes to go with it (MapElevation.lua, ChunkMesher.lua,
VoxelScene.lua):
- Buildings/walls rise with their own footprint's resolved ground
  instead of forcing a flat pad underneath.
- Grass tufts, flowers, signs and other object geometry follow the
  slope they actually sit on instead of a fixed height baked in before
  MapGrids existed.
- A ledge tile itself now renders one tile above the slope value it's
  the edge of (MapElevation.LEDGE_LIP), reading as a lip standing over
  its terrace rather than blending flush into the midpoint slope.
- Water recesses a full tile below its banks (voxel_heights.lua,
  TileShape.lua) instead of the old couple-pixel dip, which read as
  flush with the shoreline at normal render scale.

Data pipeline notes carried in MapGrids.lua's header comment: drops
cells whose only path to the map's main body crossed a conflicting
edge, judges walkability by the cell's bottom-left tile only (matching
the engine's own collision rule), trims unsupported leaked corridors,
resolves row connectivity before column connectivity so pass-through
gaps in a ledge row inherit their real neighbours, bridges a decorated
pass-through cell wedged between two real ledges to their midpoint,
and fills lone scenery from its resolved neighbours (a signpost takes
the higher side rather than the midpoint).
@Wiggen94

Copy link
Copy Markdown
Author

Closing for now -- opened before final review/confirmation. Will reopen once ready.

@Wiggen94 Wiggen94 closed this Aug 24, 2026
Gjermund Høsøien Wiggen added 2 commits August 25, 2026 00:44
Adds MapElevation.setting (a ModSetting, ON/OFF, defaults ON) gating
MapElevation.worldHeight -- OFF returns 0 everywhere, which folds every
consumer (ledges, buildings, grass/flowers/signs) back to the exact flat
behaviour they had before this branch, with no changes needed at those
call sites. Wired into the OPTIONS menu / mod manager the same way the
other toggles in this file are.

Flipping it needs a full chunk-mesh rebuild, since elevation is baked
into vertex positions at build time rather than read per frame. Follows
the existing void-fill precedent in this file (same category of
problem) rather than the mod.options_changed event, since that event
turned out not to fire reliably from the in-game OPTIONS row.

Also reverted the CHANGELOG.md edit from the previous commit -- not
this branch's call to make.
Water's recessed depth (TileShape.heights()'s `water` entry) wasn't
gated by MapElevation.setting at all, so turning elevation OFF left
water at the deeper -8 recess meant to read against banks that had
just gone flat again. Added WATER_H_FLAT (-2, the original pre-
elevation depth) as what TileShape.heights() substitutes back in while
the setting is off.

TileShape caches resolved shapes per tileset, same as ChunkMesher
caches chunk meshes, so the elevation-change poller in main.lua now
drops that cache too (TileShape.invalidate(), already existed but had
no caller) alongside ChunkMesher.invalidate().
@Wiggen94 Wiggen94 reopened this Aug 24, 2026
@Wiggen94 Wiggen94 changed the title Auto-derive per-cell elevation for outdoor maps (closes #41) Generate per-cell elevation for outdoor maps (an attempt at #41) Aug 24, 2026
@artyrambles

Copy link
Copy Markdown
Owner

This is looking pretty good so far, what I noticed though is that on elevations above 0, the enemy sprite in battles is zoomed in. There's also some glitches with how the elevation is applied. Directly behind the slopes for example, the ground dips again.

I'm guessing this is still a wip?

@Wiggen94

Copy link
Copy Markdown
Author

I'm guessing this is still a wip?

Very much so, it's at least a proof of concept for now. I will keep improving it when I get the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants