feedback7: merge fb7-fields — PR #337 boss-field portals teleport (route-end portals: hidden 3 s, out of [5,10], leak at the side's door), PR #347 碎铳之簧 chases the full 5 s, #370 耀骑士临光's redeploy reduction floored at 0, #375 the water-map patrol takes the official upper road
- Grok review: MERGE AFTER FIXES — the end-portal wait (0 s → 3 s, the data's modal crossing) fixed on the branch
(1205336a). solo-NORMAL-1 still fails the Hidden Core (explained in the branch commits).
- Golden union OK; broad node --test 5517 (5516 pass, 0 fail, 1 skipped); lint 0 errors.
This commit is contained in:
commit
31fee55c6c
18 files changed
+819
-336
No files matched your search
+45
-6
@@ -28,10 +28,11 @@
|
||||
// (PRTS 状态机: an enemy's COMBAT state ends with its attack, not with its blocker); the candidates pass the
|
||||
// enemy's own rule (`e.profile.canTarget`) and are ordered
|
||||
// blocker → taunt → latest deployed (targeting.js sortAllyTargets). An enemy's damage type is its data's unless content
|
||||
// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the
|
||||
// enemy runs between random checkpoints away from the fear's source (fear.js moveFeared; a self-inflicted fear
|
||||
// flutters inside its own tile); an `attract` (诱导) status walks it to the status point instead (moveAttracted);
|
||||
// both re-plan the route when released (恐惧 outranks 诱导).
|
||||
// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak — on a
|
||||
// portal entrance it is a teleport to the far exit and a walk to that side's blue door instead (portalPickup). A `fear`
|
||||
// (恐惧) status suspends the route: the enemy runs between random checkpoints away from the fear's source (fear.js
|
||||
// moveFeared; a self-inflicted fear flutters inside its own tile); an `attract` (诱导) status walks it to the status
|
||||
// point instead (moveAttracted); both re-plan the route when released (恐惧 outranks 诱导).
|
||||
|
||||
import { ATTACK_PAUSE, ALLY_COLLIDER_RADIUS, MOVE_SCALE, PROJECTILE_SPEEDS, PROJECTILE_SPEED, BOOMERANG_RETURN_SPEED, COLS, CHAIN_RADIUS } from './constants.js';
|
||||
import { sortEnemyTargets, sortAllyTargets, canTargetEnemy, canTargetAlly, tileKeyOf } from './targeting.js';
|
||||
@@ -634,7 +635,10 @@ function advanceRoute(b, e, dt, R, standing = false) {
|
||||
let guard = 16;
|
||||
while (budget > 1e-9 && guard-- > 0 && e.alive) {
|
||||
const leg = R.legs[R.legIdx];
|
||||
if (!leg) { b.leak(e); return; }
|
||||
if (!leg) {
|
||||
if (!portalPickup(b, e)) { b.leak(e); return; }
|
||||
continue;
|
||||
}
|
||||
if (leg.t === 'wait') {
|
||||
if (R.waitLeft == null) R.waitLeft = leg.time;
|
||||
const use = Math.min(budget, R.waitLeft);
|
||||
@@ -684,13 +688,48 @@ function advanceRoute(b, e, dt, R, standing = false) {
|
||||
}
|
||||
budget = dist / speed;
|
||||
if (R.pts && R.ptIdx >= R.pts.length) {
|
||||
if (leg.final) { b.leak(e); return; }
|
||||
if (leg.final) {
|
||||
if (!portalPickup(b, e)) { b.leak(e); return; }
|
||||
}
|
||||
R.legIdx++;
|
||||
R.pts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boss-field portal pickup (tile_telin / tile_telout; GitHub #336, PR #337 by @2321Robin, both with a recording of the
|
||||
* official game): a route that ENDS on a portal entrance does not leak there — the enemy is teleported out of the far
|
||||
* exit and walks on to the blue door on the entrance's side, where it leaks. The boss / Hidden Core circuits end on
|
||||
* an entrance ([1,3] / [1,17]; 137 routes of data/waves.json); their mid-route crossings are the routes' own DISAPPEAR /
|
||||
* WAIT / APPEAR steps, every MOVE onto an entrance being followed by a DISAPPEAR, so only the route's end needs this.
|
||||
* Appends vanish → wait → reappear → walk-to-door legs to the live route and returns true (false: not on an entrance,
|
||||
* or no exit in the field). The exit is the telout farthest from the entrance, the door the end tile nearest to it:
|
||||
* every explicit pair of the data (95, routes and extra routes) reads [1,3] / [1,17] → [5,10] — the farther of the two
|
||||
* telouts [5,10] / [2,10] of the battle stages — and the doors [2,2] / [2,18] stand beside the entrances. Inside the
|
||||
* portal the enemy stays hidden PORTAL_WAIT s, the wait of the explicit crossings of the same tiles (WAIT 3 s in 85 of
|
||||
* the 95; 5 s in 9, 1 s in 1). [ASSUMED] the pairing (no tile carries a link) and the 3 s (no source times this one).
|
||||
*/
|
||||
const PORTAL_WAIT = 3;
|
||||
function portalPickup(b, e) {
|
||||
const r = Math.round(e.y), c = Math.round(e.x);
|
||||
if (!b.grid.inBounds(r, c) || b.grid.tile(r, c).special !== 'telin') return false;
|
||||
const outs = b.grid.specialTiles('telout');
|
||||
if (!outs.length) return false;
|
||||
const dist = (p) => hypot(p[0] - r, p[1] - c);
|
||||
const out = outs.reduce((a, x) => (dist(x) > dist(a) ? x : a));
|
||||
const R = e.route;
|
||||
R.legs.splice(R.legIdx + 1, 0, { t: 'disappear' }, { t: 'wait', time: PORTAL_WAIT }, { t: 'appear', r: out[0], c: out[1] });
|
||||
const ends = b.grid.specialTiles('end');
|
||||
if (ends.length) {
|
||||
const door = ends.reduce((a, x) => (dist(x) < dist(a) ? x : a));
|
||||
R.legs.push({ t: 'move', r: door[0], c: door[1], final: true });
|
||||
}
|
||||
R.pts = null;
|
||||
R.tailVersion = -1; // the appended tail invalidates the cached remaining-distance suffixes
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long an enemy stands for one attack: an unblocked ranged enemy for its whole clip (GitHub #58 — the owner's
|
||||
* decision of 2026-10-04 from first-hand memory of the official game: a ranged enemy stops for each attack's animation
|
||||
|
||||
Reference in new issue
Block a user