diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index 92689275b..292e3c47c 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -111,10 +111,16 @@ export class GameMapImpl implements GameMap { private readonly width_: number; private readonly height_: number; - // Lookup tables (LUTs) contain pre-computed values to avoid performing division at runtime - private readonly refToX: number[]; - private readonly refToY: number[]; - private readonly yToRef: number[]; + // Lookup tables (LUTs) contain pre-computed values to avoid performing division at runtime. + // Typed arrays are used instead of plain JS Array to keep memory tight on large maps: + // Uint16Array uses 2 bytes/element vs ~8 bytes for a boxed number, saving ~53 MB on + // the Indian Subcontinent map (2000×2220 = 4.44 M tiles). + // Coordinates never exceed 65535 for any map in the game, so Uint16 is safe for x/y. + // yToRef stores tile refs (up to width*height-1) which can exceed 65535 for large maps, + // so it uses Int32Array. + private readonly refToX: Uint16Array; + private readonly refToY: Uint16Array; + private readonly yToRef: Int32Array; // Terrain bits (Uint8Array) private static readonly IS_LAND_BIT = 7; @@ -148,11 +154,11 @@ export class GameMapImpl implements GameMap { this.height_ = height; this.terrain = terrainData; this.state = new Uint16Array(width * height); - // Precompute the LUTs + // Precompute the LUTs using typed arrays (see field declarations for rationale). let ref = 0; - this.refToX = new Array(width * height); - this.refToY = new Array(width * height); - this.yToRef = new Array(height); + this.refToX = new Uint16Array(width * height); + this.refToY = new Uint16Array(width * height); + this.yToRef = new Int32Array(height); for (let y = 0; y < height; y++) { this.yToRef[y] = ref; for (let x = 0; x < width; x++) { diff --git a/src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts b/src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts index 2958de79a..933804fbb 100644 --- a/src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts +++ b/src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts @@ -5,7 +5,6 @@ import { AbstractGraphAStar } from "./AStar.AbstractGraph"; import { AStarWaterBounded } from "./AStar.WaterBounded"; import { AbstractGraph, AbstractNode } from "./AbstractGraph"; import { BFSGrid } from "./BFS.Grid"; -import { LAND_MARKER } from "./ConnectedComponents"; export class AStarWaterHierarchical implements PathFinder { private tileBFS: BFSGrid; @@ -314,7 +313,7 @@ export class AStarWaterHierarchical implements PathFinder { this.map.height(), tile, maxDistance, - (t: TileRef) => this.graph.getComponentId(t) !== LAND_MARKER, + (t: TileRef) => this.map.isWater(t), (t: TileRef, _dist: number) => { const tileX = this.map.x(t); const tileY = this.map.y(t); diff --git a/tests/core/pathfinding/PathFinding.Water.test.ts b/tests/core/pathfinding/PathFinding.Water.test.ts index 48d06b20f..fceef9764 100644 --- a/tests/core/pathfinding/PathFinding.Water.test.ts +++ b/tests/core/pathfinding/PathFinding.Water.test.ts @@ -12,6 +12,7 @@ import { createGame, L, W } from "./_fixtures"; describe("PathFinding.Water", () => { let game: Game; let worldGame: Game; + let giantWorldGame: Game; function createPathFinder(g: Game = game): SteppingPathFinder { return PathFinding.Water(g); @@ -20,6 +21,7 @@ describe("PathFinding.Water", () => { beforeAll(async () => { game = await setup("ocean_and_land"); worldGame = await setup("world", { disableNavMesh: false }); + giantWorldGame = await setup("giantworldmap", { disableNavMesh: false }); }); describe("findPath", () => { @@ -274,4 +276,23 @@ describe("PathFinding.Water", () => { expect(path).not.toBeNull(); }); }); + + describe("Giant World Map routes", () => { + it("routes correctly in promoted components", () => { + const pathFinder = createPathFinder(giantWorldGame); + const map = giantWorldGame.map(); + + // Coordinates inside Component 459 in map4x (promoted) + const from = map.ref(2616, 452); + const to = map.ref(2676, 474); + + expect(map.isWater(from)).toBe(true); + expect(map.isWater(to)).toBe(true); + + const path = pathFinder.findPath(from, to); + expect(path).not.toBeNull(); + expect(path![0]).toBe(from); + expect(path![path!.length - 1]).toBe(to); + }); + }); });