Big Water-Nukes Performance Improvements 💧 (#3668)

## Description:

### 1. Water Magnitude Calculation Optimization (WaterManager.ts)
* Boxed BFS Approach: Refactored the water magnitude recomputation to
use "Dirty" and "Seed" boxes. Instead of a global update, the system now
only recalculates magnitudes within a specific radius of the affected
area, significantly reducing CPU load after water-nuke-explosions
* Shoreline Bit Optimization: Narrowed the scope for updating shoreline
bits to a 2-ring neighborhood around converted tiles, avoiding
unnecessary checks across the entire map.

Performance test on the world map:
- AtomBomb (r=30): 24ms (was 344ms with global BFS), 2,993 changed tiles
(was 630k)
- Massive (r=200): 178ms (was 378ms), 130k changed tiles (was 654k)

### 2. Pathfinding Rebuild Staggering (PathFinder.ts,
TradeShipExecution.ts, TransportShipExecution.ts)
* Distributed Rebuilds: Introduced a staggering mechanism in
WaterPathFinder. Ship pathfinders now wait a randomized/distributed
number of ticks (0 - 5 seconds) before rebuilding after a water graph
change.
* CPU Spike Mitigation: By spreading out these expensive A* rebuilds
over time, we prevent lag when hundreds of ships attempt to re-path
simultaneously
* Like Mole said it: "Pretty realistic I;d say the capitan needs a
second to realize the big nuke on the left opened a new path"

From a performance test on the big new Luna map:
Graph rebuild: 256.4ms
Pathfinder-Rebuild of 329 ships (Including other Executions): 1564.4ms
(No longer noticeable, spread over 5s)

###  3. Performance Refinements
* Simplified deep ocean magnitude logic within the optimized BFS flow.
   * Improved memory efficiency by utilizing clipped BFS wavefronts.

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
This commit is contained in:
FloPinguin
2026-04-15 01:17:15 +02:00
committed by GitHub
parent e42c99bae0
commit 35a64fa0d9
4 changed files with 122 additions and 71 deletions
+34 -5
View File
@@ -98,11 +98,28 @@ export class WaterPathFinder implements SteppingPathFinder<TileRef> {
private _waterGraphVersion: number;
private _rebuilt = false;
constructor(private game: Game) {
// Stagger support: spread pathfinder rebuilds over multiple ticks so all
// ships don't re-run A* simultaneously after a water-nuke.
private _staggerCountdown: number;
private _pendingVersion: number = -1;
/**
* @param stagger - How many ticks to wait before rebuilding when the water
* graph changes. 0 = immediate (default). Pass a value spread across
* [0, STAGGER_SPREAD) to distribute rebuilds over time.
*/
constructor(
private game: Game,
private _stagger: number = 0,
) {
this.inner = PathFinding.Water(game);
this._waterGraphVersion = game.waterGraphVersion();
this._staggerCountdown = 0;
}
/** Spread to use when auto-staggering ship pathfinders */
static readonly STAGGER_SPREAD = 50;
/** True if the pathfinder was rebuilt since the last call to `rebuilt`. Resets on read. */
get rebuilt(): boolean {
this.ensureFresh();
@@ -113,11 +130,23 @@ export class WaterPathFinder implements SteppingPathFinder<TileRef> {
private ensureFresh(): void {
const v = this.game.waterGraphVersion();
if (v !== this._waterGraphVersion) {
this._waterGraphVersion = v;
this.inner = PathFinding.Water(this.game);
this._rebuilt = true;
if (v === this._waterGraphVersion) return;
// New graph version detected — start or continue the stagger countdown.
if (this._pendingVersion !== v) {
this._pendingVersion = v;
this._staggerCountdown = this._stagger;
}
if (this._staggerCountdown > 0) {
this._staggerCountdown--;
return; // Keep using old pathfinder for now
}
// Countdown complete — rebuild.
this._waterGraphVersion = v;
this.inner = PathFinding.Water(this.game);
this._rebuilt = true;
}
next(from: TileRef, to: TileRef, dist?: number): PathResult<TileRef> {