fix(pathfinding): stop opening every .swb twice at boot

MovementPath.Configure() explicitly called PathCacheCommands.Configure()
and CacheEvictionTimer.Configure(). Both are types with a public static
parameterless Configure(), so AssemblyHandler.Invoke("Configure") already
discovered and called them once each. PathCacheCommands.Configure() ran
twice, and with it AutoLoadAtStartup(), so every map's .swb was opened,
indexed and logged twice. PathCacheCommands.Configure() called
PathfindRecorder.Configure() the same way.

Consolidate the cache lifecycle into Initialize:

- Configure() keeps only settings and command registration.
- Initialize() opens the readers once, then prebakes only maps that still
  lack one.
- The post-bake reopen is per-map instead of a blanket AutoLoadAtStartup(),
  which on a partial bake would close and reopen the readers already open.

Initialize is the correct phase: Configure runs before LoadTileMatrix() and
World.Load(), so opening a .swb there forced the lazy Map.Tiles property and
built every TileMatrix ahead of the loader that owns it, possibly before
TileMatrix.Configure() settled Pre6000ClientSupport. Both sit at the default
call priority and the phase sort is unstable. Moving pathfinding out leaves
nothing in Configure that touches Map.Tiles.

Also demote the per-map "opened ... chunks indexed" line to Debug. Opening
is the expected case; BakeMap already logs a rebuild at Information.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-07-25 12:58:17 -07:00
parent c39454137e
commit 313fe4bdb0
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
4 changed files with 28 additions and 21 deletions

View file

@ -139,13 +139,17 @@ several-minutes cost. Wiring:
after assemblies load (so content can register prompts) but **before Serilog starts**, so the
console prompt is not interleaved with the async console sink. Any class can participate by
defining `public static void ConfigurePrompts()` and self-gating on first-boot state.
- The bake runs in the later `Invoke("Initialize")` phase (after the tile matrix + world load,
which the bake walks).
- Both the reader open and the bake run in `Invoke("Initialize")`, after the tile matrix and world
load. Neither belongs in `Configure`, which runs *before* both: the fingerprint hashes the map
files, so opening a `.swb` there would force the lazy `Map.Tiles` property and build every
`TileMatrix` ahead of `TileMatrixLoader` — possibly before `TileMatrix.Configure()` settles
`Pre6000ClientSupport`, since both sit at the default call priority and the phase sort is
unstable. `PathCacheCommands.Configure` is limited to settings and command registration.
- Staleness is decided by the `.swb` fingerprint, which `StepCacheFile.OpenForLazy` validates at
open time (hash of `tiledata.mul` + the per-map `.mul`/`.uop` files — never the in-memory
`TileData` tables, which the server patches at runtime). `Configure` opens a reader for every
up-to-date file; the bake in `Initialize` then skips any map where `StepCache.HasLazyReader` is
already true, so the fingerprint is computed once per boot, not twice.
`TileData` tables, which the server patches at runtime). `Initialize` opens a reader for every
up-to-date file, then skips any map where `StepCache.HasLazyReader` is already true, so the
fingerprint is computed once per boot. Each newly baked map reopens only itself.
## Configuration levers
@ -154,7 +158,7 @@ several-minutes cost. Wiring:
| `pathfinding.enable` | `PathFollower.Configure` | `true` | Master switch for `PathFollower` pathfinding. Off → greedy/auto-turn only, no A* at all. |
| `bitmap_pathfinding_cache` feature flag (`ContentFeatureFlags.BitmapPathfindingCache`, `Server.Systems.FeatureFlags`) | `FeatureFlagManager` | `true` | Off → `BitmapAStar` routes straight to the slow path with **no cache probe and no warming memory**. ≈ old FastAStar at ~1×. |
| `pathfinding.maxResidentChunks` | `PathCacheCommands.Configure` | 8192 (~40 MB) | LRU cap on resident chunks = the warming-memory ceiling. Lower it (e.g. 5121024 ≈ 2.55 MB) on small shards. |
| `pathfinding.maxSearchNodes` | `PathCacheCommands.Configure` → `BitmapAStarAlgorithm.MaxSearchNodes` | 1000 | A* per-Find node-expansion budget. See limits above; ~1000 is the sweet spot. |
| `pathfinding.maxSearchNodes` | `BitmapAStarAlgorithm.Configure` → `BitmapAStarAlgorithm.MaxSearchNodes` | 1000 | A* per-Find node-expansion budget. See limits above; ~1000 is the sweet spot. |
| `pathfinding.prebakeMaps` | `PathCacheCommands` (first-boot prompt + `Initialize`) | `false` | When set, bakes any missing/stale `.swb` for the selected maps at startup (fingerprint-gated, so a fresh cache is a no-op). Set interactively by the first-boot prompt. |
| `PathFollower` `RepathDelay` | `PathFollower.cs` (const) | 2 s | Throttle: a moving goal re-`Find`s at most ~once per 2 s; a stationary reachable goal is pathed once and reused until arrival. Not a setting (compile-time). |