- Added `microGameMap` to support a new resolution level for compact games, allowing for more efficient map loading and pathfinding. - Updated `createGame` and `GameImpl` to incorporate `microGameMap`, ensuring proper handling of different map resolutions. - Modified `loadTerrainMap` to always expose the 16x map for coarse heuristics, improving navigation capabilities.
3.3 KiB
Coarse-to-fine pathfinding (boats) — notes
Why
Full-res water BFS is optimal and simple, but the “ocean case” can still expand a lot of tiles. Coarse-to-fine is the next lever: do a cheap solve on a low-res map to guide / bound the expensive solve.
Do we already have low-res maps?
Yes. The terrain loader already ships multiple resolutions per map:
manifest.map+map.bin(full res)manifest.map4x+map4x.bin(coarser)manifest.map16x+map16x.bin(even coarser)
At runtime we load:
gameMap: full res for normal games (ormap4xfor compact games)miniGameMap: lower res (map4xfor normal games, ormap16xfor compact games)microGameMap: alwaysmap16x(in compact games this is the same instance asminiGameMap)
So we can prototype coarse-to-fine without extending mapgen first.
Core idea (don’t overthink it)
Stage 1 (coarse):
- Run the same multi-source/any-target search on
miniGameMap(BFS, water-only, king-moves if desired). - Result is a coarse path (or just a coarse distance field).
Stage 2 (refine):
- Run full-res BFS on
gameMap, but restricted by what stage 1 learned (a “corridor”) or guided by a coarse heuristic.
Important: the coarse map is an approximation. It must never be allowed to make the final path invalid. If the refine stage fails inside the corridor, fall back to full-res BFS.
Option A: Coarse corridor (usually the biggest win)
- Map fine tiles → coarse cells by integer scaling:
scaleX = gameMap.width / miniGameMap.widthscaleY = gameMap.height / miniGameMap.height
- Solve on coarse, get a coarse cell path.
- Inflate that path into a corridor:
- include all coarse cells within radius
rof the coarse path (e.g.r = 1..3)(Manhattan or Chebyshev radius depending on move rules)
- include all coarse cells within radius
- Refine on full-res with a fast mask:
passableFine(tile) = gm.isWater(tile) && corridorMask[coarseOf(tile)]
- If no path found, retry without the corridor (or inflate
rand retry once).
Notes:
- If the low-res generation is “optimistic” (water if any child tile is water), the coarse path can cut across land. Inflation + fallback is what keeps this safe.
Option B: Coarse heuristic for A*
If we ever move from BFS → A* on full-res, a cheap heuristic is:
- Precompute
coarseDist[coarseCell]by BFS onminiGameMapseeded from coarse targets. - Use
h(tile) = coarseDist[coarseOf(tile)] * min(scaleX, scaleY)
If the coarse map is “more passable” than the fine map (typical for minimaps), coarseDist tends to underestimate,
which is admissible (safe) but not always very tight.
Where component IDs fit
Water-component IDs are still a free early reject:
WaterComponents.tsalready precomputes IDs perGameMapinstance.- Do the same check on
miniGameMapif useful, but full-res component filtering already prevents the worst “wrong ocean” searches.
Practical next steps (incremental)
- Add a coarse route helper that mirrors the existing API but runs on
miniGameMap. - Implement corridor masking + refine fallback as a generic helper (so transport/trade/warship can all share it).
- Measure: expansions + ms, before/after, on worst-case oceans.
- Only then decide if mapgen needs a better “navmap” (e.g. conservative water, coastline preservation, etc.).