Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/json/terrain.json

Large diffs are not rendered by default.

21 changes: 14 additions & 7 deletions packages/core/src/sim/movement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ function laneNavGoal(
field,
entity.x,
entity.y,
isShortApproach ? DOCK_APPROACH_LOCAL_GOAL_CELLS : undefined,
isShortApproach ? dockApproachCells(mask.cellSizeX) : undefined,
);
if (step !== null) return step;
}
Expand All @@ -742,7 +742,7 @@ function laneNavGoal(
orderY,
entity.x,
entity.y,
isShortApproach ? DOCK_APPROACH_LOCAL_GOAL_CELLS : undefined,
isShortApproach ? dockApproachCells(ruleset.map.waterMask.cellSizeX) : undefined,
);
return step ?? { x: orderX, y: orderY };
}
Expand Down Expand Up @@ -780,11 +780,18 @@ function fieldToPoint(mask: WaterMask, x: number, y: number): NavField | null {
return field;
}

/** Hand-off distance (in cells) for a SHIP rounding land onto a dock: follow the
* field gradient to within ONE cell of the goal's water-access cell before the
* straight-line final step, so the ship rounds the coastal spit instead of
* beelining into it. Open hauls keep navStepToward's default (6). */
const DOCK_APPROACH_LOCAL_GOAL_CELLS = 1;
/** Hand-off distance for a SHIP rounding land onto a dock, in WORLD UNITS
* (~one 128u legacy cell): follow the field gradient to within this range of
* the goal's water-access cell before the straight-line final step, so the
* ship rounds the coastal spit instead of beelining into it. Converted to
* grid hops per-mask so behavior is resolution-independent (the emitted
* grid is 64u cells as of the full-res lane deduction). Open hauls keep
* navStepToward's unit-derived default. */
const DOCK_APPROACH_LOCAL_GOAL_UNITS = 128;

function dockApproachCells(cellSize: number): number {
return Math.max(1, Math.round(DOCK_APPROACH_LOCAL_GOAL_UNITS / cellSize));
}

/** True if the straight segment (x0,y0)->(x1,y1) passes over any LAND cell
* (sampled at half-cell resolution incl. the endpoint), i.e. a naive
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/sim/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1773,17 +1773,22 @@ export function navStepToward(
field: NavField,
x: number,
y: number,
localGoalDistCells = 6,
localGoalDistCells?: number,
): { x: number; y: number } | null {
const { bounds, cols, rows, cellSizeX, cellSizeY, dist } = field;
if (dist.length === 0) return null; // stub field (no real mask) -> straight line
// Default local-goal basin: ~768 WORLD UNITS (six 128u legacy cells),
// derived from the field's cell size so the hand-off distance is
// resolution-independent (the emitted grid is 64u cells as of the
// full-res lane deduction).
const localGoal = localGoalDistCells ?? Math.max(1, Math.round(768 / cellSizeX));
if (x < bounds.minX || x >= bounds.maxX || y <= bounds.minY || y > bounds.maxY) return null;
const col = Math.floor((x - bounds.minX) / cellSizeX);
const row = Math.floor((bounds.maxY - y) / cellSizeY);
if (col < 0 || col >= cols || row < 0 || row >= rows) return null;
const here = dist[row * cols + col] ?? NAV_UNREACHABLE;
if (here === NAV_UNREACHABLE) return null; // on land / unreachable -> let slide handle it
if (here <= localGoalDistCells) return null; // near the goal -> straight line in
if (here <= localGoal) return null; // near the goal -> straight line in

// Lowest-distance navigable 8-neighbour (deterministic neighbour order).
const NEIGHBOURS: readonly [number, number][] = [
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/fog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function findPair(blocked: boolean, minD: number, maxD: number): { a: { x: numbe
for (let c = 2; c < cols - 2; c += 2) {
if (cells[r * cols + c] !== 1) continue;
const a = world(c, r);
const reach = Math.ceil(maxD / 128) + 1;
const reach = Math.ceil(maxD / cellSizeX) + 1;
for (let dr = -reach; dr <= reach; dr += 2) {
for (let dc = -reach; dc <= reach; dc += 2) {
const nc = c + dc;
Expand Down
11 changes: 6 additions & 5 deletions packages/core/test/terrain-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ describe('terrain integration (real water mask)', () => {
expect(ruleset.map.waterMask.cells.length).toBeGreaterThan(0);
// The mask is SAILABILITY from the map's own PATHING MAP (war3map.wpm bit
// 0x40 = no-water — the engine's enforced truth; see terrain.py), cropped
// to the 81x113 PLAYABLE tilepoint grid (the unplayable border removed;
// to the 162x226 PLAYABLE grid (64u cells — 2x the tilepoint spacing;
// lane-topology equivalence vs the raw 32u wpm is gate G6) (the unplayable border removed;
// the WEST bound extended 3 cells west of the camera bounds so the Goblin
// Potion Dealer shop sits off the grid edge — see docs/TERRAIN.md
// WEST-BOUND EXTENSION), PLUS only MINIMAL 1-cell connectivity necks (so
Expand All @@ -91,10 +92,10 @@ describe('terrain integration (real water mask)', () => {
// real map separates.
const water = ruleset.map.waterMask.cells.reduce((n, c) => n + c, 0);
const total = ruleset.map.waterMask.cells.length;
expect(total).toBe(81 * 113);
// ~0.553: the wpm sailable fraction over the playable crop (+ necks +
// moats). Same band as the extractor's fail-loud gate [0.50, 0.62]:
// well above = colour-key-style over-watering, well below = over-dry.
expect(total).toBe(162 * 226); // 64u cells (full-res lane deduction)
// ~0.513: the wpm sailable fraction at 64u cells with LAND-BIASED ties
// (straddling walls are kept, conservatively thickening land). Same band
// as the extractor's fail-loud gate [0.50, 0.62].
expect(water / total).toBeGreaterThan(0.5);
expect(water / total).toBeLessThan(0.62);
// Nav fields are populated (a real flood from each base goal).
Expand Down
7 changes: 6 additions & 1 deletion packages/server/test/ai-match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,12 @@ describe('bot-vs-bot match (real brain, both teams AI)', () => {
// touched. The HQ-damage milestone is therefore replaced by the funnel's real
// signal: enemy towers keep taking chip from held creeps across the run.
it('over a long match the funnel keeps engaging the enemy towers, without idling in retreat', async () => {
const LONG_CAP = 6000;
// 9000 ticks: on the TRUE 64u map (full-res lane deduction) the lanes are
// tighter than the old blobby 128u mask — opposing waves clash in narrow
// chokes and the first tower-chipping leaker lands around tick ~5000 in a
// 10-captain probe (later with only these 2 captains). Same window as the
// core terrain-integration grind test.
const LONG_CAP = 9000;
const invPeak = new Map<number, number>();
const retreatThinks = new Map<number, number>();
const totalThinks = new Map<number, number>();
Expand Down
Loading
Loading