feedback7 R22-FIELDS: the path tie-break never wades into more 深水区 than the official route — on 战场#08(下半) the patrolling 铳 / 卢西恩 take the official upper road from (3,9) to (2,3) again (GitHub #375); golden: boss-boss_2-pair, coop2-ABYSS-10-boosted
GitHub #375 (FrogThai): 「水图boss会从下面这条路往蓝门走,实际应该从上路走」. The walking leaders of 战场#08(下半) 涨潮控制 (act2autochess_m04) are 铳 (level_act1autochess_h07_02 route 6; the Hidden Core 铳 on act1autochess_h08_02's patrol routes) and 卢西恩 (act2autochess_h07_05 route 8): PATROL_MOVE (3,9) → (2,3) → (5,9). Measured (notes repro-375.mjs): ours 「3,9 2,9 2,6 4,6 4,4 2,4 2,3」 — the lower road through the 深水区 (3,6) / (4,6) — against the official SPFA 「3,9 5,9 5,4 2,4 2,3」 along row 5 with one 深水区 (5,6); the mirrored (3,11) → (2,17) split the same way. Cause: grid.js _buildField takes the blockable-ground preference pointer (user playtest #2) when its route crosses strictly fewer non-blockable tiles; 深水区 is non-blockable itself (no deployment), so the lower road counted 2 against the upper road's 3 (two floor tiles and one water tile) and won. Official pathfinding is the plain 4-neighbour SPFA (research 08 §3.1). Now the preference pointer is taken only when its route also crosses no more 深水区 than the official pointer's (a per-tile `deep` count beside `cost`, start excluded). Rule from the Grok triage (scratch-i1b/375.patch), re-implemented with a precomputed Grid.deepWater mask (terrain 'deepsea': tile_deepsea and tile_deepwater). Scope (notes dump-fields.mjs / check-official.mjs, every stage × normal / 联防 / boss rect × every walkable destination): 48 fields change, only on the two water maps (act2 m04 boss field; the inactive act1 m05), 149 pointers, every one back to the official pointer. The 21 accepted DEVIATIONS (战场#01 / #02 floor, #07, the inactive #05's 7 water-avoiding routes) are unchanged, as are all 154 audited gate routes and data/stages.json's groundPaths (tools/build-data.mjs --offline into a scratch dir: every data file identical). Other boss-field preference routes of m04 that cross fewer 深水区 than the official one stay. Tests: test/sim/pathing-official.test.js — two new #375 tests (the four patrol legs equal the pure official waypoints; in a real battle the 铳 climbs to row 5 and steps on the 深水区 (5,6) only), both failing on the parent; test/sim/pathing-crosscheck.test.js — the independent reference port gets the same 深水区 rule (it flagged both the patrol leg and a random-crate normal-field route of act2 m04 until then); test/sim/pathing-blockable.test.js — the WALK-leg audit compares with the equal-length routes that cross no more 深水区 than ours (the 14 accepted arena exceptions unchanged). Grid-related suites 777/777. Golden: - fields boss-boss_2-pair (铳 pair on act2autochess_m04, act1autochess_h07_02): the two 铳 patrol the upper roads; leader left 5311560 → 5301010, p1 damage up / p2 down, deaths 0 / 2 → 1 / 3; still 8 / 8 kills. - matches coop2-ABYSS-10-boosted (act2 m04; Final Assault 卢西恩, Hidden Core 铳): still cleared, team LP 800 → 615. The bots' operators stand on the lower rows (the same tiles as before); with the leaders on the upper road the Final Assault lasts 335 game s instead of 247 and the Hidden Core 638 instead of 190, both past the 150 real-second mark: the overtime drain takes 17 + 168 LP (none before), no leak.
This commit is contained in:
1 parent
f14b3a062f
commit
8d896e40ea
10 files changed
+167
-86
No files matched your search
+22
-11
@@ -26,8 +26,7 @@
|
||||
// 0.1.0: on 战场#04 活性源石 the lower-gate enemies must cross diagonally from row 9 into row 10 as officially, not
|
||||
// walk two tiles left of the gate and then straight up). A tile is non-blockable when it is walkable but not LOW
|
||||
// ground buildable for melee (floor / gate / goal / teleport / 深水区 tiles — `blockable()` false; no gate route of 战场#08
|
||||
// changed when its 深水区 became non-buildable, a few off-route smoothed steps of its boss field, rows 4–5, go around the
|
||||
// water instead of cutting across it). Each field holds two candidate
|
||||
// changed when its 深水区 became non-buildable). Each field holds two candidate
|
||||
// pointers per tile, both with the official `dist` (so the crate cost 1000 and every grid route length stay official):
|
||||
// * official: the plain algorithm above;
|
||||
// * preference (0.1.0's): the SPFA relaxes on (dist, pen) lexicographically, `pen` = non-blockable tiles on the chain
|
||||
@@ -36,7 +35,10 @@
|
||||
// only on the tile's own raw chain between its two ends, so it never slips past a road tile beside floor its grid
|
||||
// route does not walk.
|
||||
// `next[tile]` is the official pointer unless the preference pointer leads to a route CROSSING strictly fewer
|
||||
// non-blockable tiles (`cost`, counted to the destination in increasing `dist` order). A segment crosses the tiles
|
||||
// non-blockable tiles (`cost`, counted to the destination in increasing `dist` order) and no more 深水区 (`deep`): 深水区
|
||||
// is non-blockable itself, so without that test the preference waded into the pool to dodge floor (GitHub #375: on
|
||||
// 战场#08(下半) the patrolling 铳 / 卢西恩 took the lower road (3,9) → (2,3) through two water tiles, one floor tile fewer
|
||||
// than the official upper road with one). A segment crosses the tiles
|
||||
// whose interior it passes through (`crossTiles`, exact geometry); one it only touches at a corner point is not crossed
|
||||
// — the first version counted the corner tiles there too and so bent official diagonals into L shapes (D5). On equal
|
||||
// counts the official pointer stays, except where the preference one merely skips it (the official waypoint lies on
|
||||
@@ -166,9 +168,12 @@ export class Grid {
|
||||
}
|
||||
/** 1 = walkable terrain an operator cannot block on (floor, gate, goal, teleport…): the path tie-break penalty */
|
||||
this.unblockable = new Uint8Array(ROWS * COLS);
|
||||
/** 1 = walkable 深水区 (terrain 'deepsea': tile_deepsea / tile_deepwater): the tie-break never walks into more of it */
|
||||
this.deepWater = new Uint8Array(ROWS * COLS);
|
||||
for (let k = 0; k < ROWS * COLS; k++) {
|
||||
const t = this.tiles[k];
|
||||
this.unblockable[k] = t.pass === 'ALL' && !isBlockableTile(t) ? 1 : 0;
|
||||
this.deepWater[k] = t.pass === 'ALL' && t.terrain === 'deepsea' ? 1 : 0;
|
||||
}
|
||||
/** obstacle bits per tile (OB_BLOCK | OB_CRATE) */
|
||||
this.obstacle = new Uint8Array(ROWS * COLS);
|
||||
@@ -272,10 +277,10 @@ export class Grid {
|
||||
|
||||
_buildField(er, ec, allowDiagonal, ignore) {
|
||||
const N = ROWS * COLS;
|
||||
const f = { dest: -1, dist: null, parent: null, pen: null, next: null, official: null, cost: null, len: null, version: this.version, allowDiagonal, ignore };
|
||||
const f = { dest: -1, dist: null, parent: null, pen: null, next: null, official: null, cost: null, deep: null, len: null, version: this.version, allowDiagonal, ignore };
|
||||
if (!this.inBounds(er, ec)) {
|
||||
const none = new Int32Array(N).fill(-1);
|
||||
return Object.assign(f, { dist: none, parent: none, next: none, official: none, pen: new Int32Array(N), cost: new Int32Array(N) });
|
||||
return Object.assign(f, { dist: none, parent: none, next: none, official: none, pen: new Int32Array(N), cost: new Int32Array(N), deep: new Int32Array(N) });
|
||||
}
|
||||
const dest = er * COLS + ec;
|
||||
f.dest = dest;
|
||||
@@ -300,25 +305,30 @@ export class Grid {
|
||||
(n) => { for (let x = n, g = N; x >= 0 && g-- > 0; x = pref.parent[x]) onChain[x] = n; });
|
||||
// per tile, in increasing distance: the official pointer unless the preference one crosses strictly fewer
|
||||
// non-blockable tiles on the way to the destination — or as few while only skipping the official waypoint o, which
|
||||
// then lies on the straight line to it and leads there (the same walk, one waypoint fewer)
|
||||
// then lies on the straight line to it and leads there (the same walk, one waypoint fewer) — and no more 深水区
|
||||
// than the official one (GitHub #375)
|
||||
const order = [];
|
||||
for (let k = 0; k < N; k++) if (dist[k] >= 0) order.push(k);
|
||||
order.sort((a, b) => dist[a] - dist[b] || a - b);
|
||||
const through = (a, b) => { let n = -unb[a]; crossTiles(a, b, (r, c) => { n += unb[r * COLS + c]; }); return n; };
|
||||
const deepW = this.deepWater;
|
||||
const wet = (a, b) => { let n = -deepW[a]; crossTiles(a, b, (r, c) => { n += deepW[r * COLS + c]; }); return n; };
|
||||
const next = new Int32Array(N).fill(-1);
|
||||
const cost = new Int32Array(N);
|
||||
const deep = new Int32Array(N);
|
||||
for (const k of order) {
|
||||
const o = nextO[k], p = nextP[k];
|
||||
if (o < 0) continue; // the destination
|
||||
let use = o, best = through(k, o) + cost[o];
|
||||
let use = o, best = through(k, o) + cost[o], bestWet = wet(k, o) + deep[o];
|
||||
if (p >= 0 && p !== o) {
|
||||
const cp = through(k, p) + cost[p];
|
||||
if (cp < best || (cp === best && next[o] === p && onSegment(k, o, p))) { use = p; best = cp; }
|
||||
const cp = through(k, p) + cost[p], wp = wet(k, p) + deep[p];
|
||||
if (wp <= bestWet && (cp < best || (cp === best && next[o] === p && onSegment(k, o, p)))) { use = p; best = cp; bestWet = wp; }
|
||||
}
|
||||
next[k] = use;
|
||||
cost[k] = best;
|
||||
deep[k] = bestWet;
|
||||
}
|
||||
return Object.assign(f, { dist, parent: pref.parent, pen: pref.pen, next, official: nextO, cost });
|
||||
return Object.assign(f, { dist, parent: pref.parent, pen: pref.pen, next, official: nextO, cost, deep });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,8 +444,9 @@ export class Grid {
|
||||
* @property {Int32Array} pen non-blockable tiles on that raw chain (tile included, destination excluded)
|
||||
* @property {Int32Array} official smoothed parent of the pure official algorithm
|
||||
* @property {Int32Array} next the tile to walk straight toward (official, or the preference pointer when it crosses fewer
|
||||
* non-blockable tiles — see the module header)
|
||||
* non-blockable tiles and no more 深水区 — see the module header)
|
||||
* @property {Int32Array} cost non-blockable tiles the route of `next` crosses from the tile (excluded) to the destination
|
||||
* @property {Int32Array} deep 深水区 tiles the route of `next` crosses from the tile (excluded) to the destination
|
||||
* @property {Float64Array|null} len memoised smoothed length (fieldLength)
|
||||
*/
|
||||
|
||||
|
||||
Reference in new issue
Block a user