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:
sganggs committed 2026-10-08 21:20:37 +08:00
commit 31fee55c6c
18 files changed
+819 -336

No files matched your search

+1 -1
View File
@@ -445,7 +445,7 @@ as official level data, and sim tests use them as a plain two-halves road.
| `special` | `{"mire":{"intervalSec":1,"aspdPerStack":-0.05,"moveMulPerStack":-0.05,"maxStacks":10,"heavyWeight":3,…}}`, `{"infection":{"bb":{"damage":70,"atk":0.2,"attack_speed":20,"duration":300}}}`, `{"deepsea":{"skillId":"sktok_tidectrl_3","bb":{…}}}`, `{"smog":{"rule":"operatorsNotTargetableByEnemyRanged"}}`, `{"blower":{…}}` | special-terrain parameters | | `special` | `{"mire":{"intervalSec":1,"aspdPerStack":-0.05,"moveMulPerStack":-0.05,"maxStacks":10,"heavyWeight":3,…}}`, `{"infection":{"bb":{"damage":70,"atk":0.2,"attack_speed":20,"duration":300}}}`, `{"deepsea":{"skillId":"sktok_tidectrl_3","bb":{…}}}`, `{"smog":{"rule":"operatorsNotTargetableByEnemyRanged"}}`, `{"blower":{…}}` | special-terrain parameters |
| `runes[]`, `globalBuffs[]` | `{"key":"map_tile_blackb_assign","bb":{…},"bbStr":{…}}` | level runes (act2 m03 reed rune has no reed tiles — leftover) | | `runes[]`, `globalBuffs[]` | `{"key":"map_tile_blackb_assign","bb":{…},"bbStr":{…}}` | level runes (act2 m03 reed rune has no reed tiles — leftover) |
| `deployTiles` | `{"normal":{"melee":[[9,3],…],"rangedOnly":[[10,4],…],"changedByDevices":[…]},"bossLeft":{…},"bossRight":{…}}` | deployable tiles at match start: `melee` = LOW (effective) buildable ALL/MELEE (ranged may use them too) — and a 深水区 under an active 特制水上平台 (`waterPlatform`, "在水上建立可以部署任意单位的平台"; act1 m05 only — the prep's deploy map only: the sim does not model the canoes, so its Grid keeps those tiles NONE, automatic placements avoid them and the path tie-break counts them as non-blockable [ASSUMED, weight 0 this season]) —, `rangedOnly` = high ground / active platforms; tiles under active crates/mounds and the 深水区 excluded. `bossLeft` / `bossRight` (field coordinates, boss rows 1–5) are what a player may deploy on in a boss round's prep: board (r, c) = boss (r − 7, c) / the right half mirrored (r − 7, 20 − c) (server/match/board.js `field`, user playtest #5 item 7) | | `deployTiles` | `{"normal":{"melee":[[9,3],…],"rangedOnly":[[10,4],…],"changedByDevices":[…]},"bossLeft":{…},"bossRight":{…}}` | deployable tiles at match start: `melee` = LOW (effective) buildable ALL/MELEE (ranged may use them too) — and a 深水区 under an active 特制水上平台 (`waterPlatform`, "在水上建立可以部署任意单位的平台"; act1 m05 only — the prep's deploy map only: the sim does not model the canoes, so its Grid keeps those tiles NONE, automatic placements avoid them and the path tie-break counts them as non-blockable [ASSUMED, weight 0 this season]) —, `rangedOnly` = high ground / active platforms; tiles under active crates/mounds and the 深水区 excluded. `bossLeft` / `bossRight` (field coordinates, boss rows 1–5) are what a player may deploy on in a boss round's prep: board (r, c) = boss (r − 7, c) / the right half mirrored (r − 7, 20 − c) (server/match/board.js `field`, user playtest #5 item 7) |
| `groundPaths` / `groundPathsWithDevices` | `{"12,10->9,2":[[12,10],[12,9],…,[12,3],[11,3],[10,3],[9,3],[9,2]]}` | the ground route of the sim's own `server/sim/grid.js` flow field (research 08 §3.4: 4-direction SPFA from the goal, crates cost 1000, Bresenham line-of-sight smoothing — official route lengths — kept unless the remake's (0.1.0's) road-over-floor preference route crosses strictly fewer non-blockable floor / gate / 深水区 tiles — the fewest among equal-length chains, smoothing that never cuts across floor its grid route does not walk; a corner touch does not count as a crossing — e.g. 战场#01's lower gate takes the col-8 road, not the col-9 floor lane, while 战场#04's keeps the official diagonal from row 9 into row 10; DESIGN §14 corrections, test/sim/pathing-official.test.js) as the tiles an enemy crosses, over the whole map, without / with the match-start devices (crates cost 1000, platforms / mounds blocking [ASSUMED]); the battle re-reads its flow fields live | | `groundPaths` / `groundPathsWithDevices` | `{"12,10->9,2":[[12,10],[12,9],…,[12,3],[11,3],[10,3],[9,3],[9,2]]}` | the ground route of the sim's own `server/sim/grid.js` flow field (research 08 §3.4: 4-direction SPFA from the goal, crates cost 1000, Bresenham line-of-sight smoothing — official route lengths — kept unless the remake's (0.1.0's) road-over-floor preference route crosses strictly fewer non-blockable floor / gate / 深水区 tiles and no more 深水区 (GitHub #375) — the fewest among equal-length chains, smoothing that never cuts across floor its grid route does not walk; a corner touch does not count as a crossing — e.g. 战场#01's lower gate takes the col-8 road, not the col-9 floor lane, while 战场#04's keeps the official diagonal from row 9 into row 10; DESIGN §14 corrections, test/sim/pathing-official.test.js) as the tiles an enemy crosses, over the whole map, without / with the match-start devices (crates cost 1000, platforms / mounds blocking [ASSUMED]); the battle re-reads its flow fields live |
| `options`, `config` | `{"characterLimit":8,"moveMultiplier":0.5}`, `{…configBlackBoard strings…}` | | | `options`, `config` | `{"characterLimit":8,"moveMultiplier":0.5}`, `{…configBlackBoard strings…}` | |
Glyph legend (`rows`): Glyph legend (`rows`):
+16 -3
View File
@@ -190,7 +190,14 @@ a part) is not, even when it counts for LP (`counted`).
`RouteSpec` accepts data/waves.json routes (`{motion, start, end, checkpoints:[[r,c]…], steps:[{t:'move',p},{t:'wait',s},{t:'disappear'},{t:'appear',p}]}`) `RouteSpec` accepts data/waves.json routes (`{motion, start, end, checkpoints:[[r,c]…], steps:[{t:'move',p},{t:'wait',s},{t:'disappear'},{t:'appear',p}]}`)
and research routes (`{m, s, e, cp:[['MOVE',r,c]…]}`). `spawnsFromTemplate(waveEntry, {mods})` (simdata.js) converts a and research routes (`{m, s, e, cp:[['MOVE',r,c]…]}`). `spawnsFromTemplate(waveEntry, {mods})` (simdata.js) converts a
template into `{ routes, spawns, maxPlayTime, overrides, extraRoutes }` (non-spawn `action` entries are skipped; `unharmful` template into `{ routes, spawns, maxPlayTime, overrides, extraRoutes }` (non-spawn `action` entries are skipped; `unharmful`
and `tag:'part'` spawns don't count in `total`). and `tag:'part'` spawns don't count in `total`). The boss templates' 传送门 reads as "reappear on the far side" (GitHub
#336, PR #337): four official routes write the crossing as `disappear → wait → move` to the exit with no `appear` (their
twins spell the `appear` out), so normalizeRoute re-inserts the exit `appear` ahead of that move — otherwise the enemy
walks the rest of its route hidden and leaks unseen (test/sim/portal-appear.test.js). A route that ENDS on an entrance
(`tile_telin`; the boss / Hidden Core circuits end on [1,3] / [1,17]) does not leak there: the enemy vanishes, comes
out of the far exit (`tile_telout`; both entrances feed [5,10], the pairing of every explicit `disappear`/`appear` pair
of the data) after 3 s hidden inside — the wait of the explicit crossings (85 of 95) — and walks on to the blue door
on the entrance's side, where it leaks (ai.js portalPickup; [ASSUMED] the pairing and the 3 s).
A `bounty` pays `coins` once, when the enemy really dies (not a knock-out it survives; a leak pays nothing), to A `bounty` pays `coins` once, when the enemy really dies (not a knock-out it survives; a leak pays nothing), to
`Battle._bountyPayee`: the player of the operator or summon that dealt the blow, if that player is in the battle; any `Battle._bountyPayee`: the player of the operator or summon that dealt the blow, if that player is in the battle; any
@@ -201,13 +208,19 @@ Leader parts (`tag:'part'`) pass damage to their leader with `loseHp(leader, sha
(无来源, credited to the attacker's `bossDamage`): `PART_TRANSFER` 1 for 斩胄之剑 / 破胄之锤 (`BLADE_TRANSFER`, the same (无来源, credited to the attacker's `bossDamage`): `PART_TRANSFER` 1 for 斩胄之剑 / 破胄之锤 (`BLADE_TRANSFER`, the same
constant) and 碎铳之簧 (PRTS "受到伤害时令假想敌:胄/铳受到等量的无来源生命流失"; DESIGN §20.10, §20.13). Content may replace a part mid-battle: every 剑/锤 sortie (content/bosses.js `kitBlade`) constant) and 碎铳之簧 (PRTS "受到伤害时令假想敌:胄/铳受到等量的无来源生命流失"; DESIGN §20.10, §20.13). Content may replace a part mid-battle: every 剑/锤 sortie (content/bosses.js `kitBlade`)
ends by spawning a new 初始模式 copy on its level branch route (`left_hand_origin` / `right_hand_origin`) with the old ends by spawning a new 初始模式 copy on its level branch route (`left_hand_origin` / `right_hand_origin`) with the old
HP, then `kill(old, null)` — uncounted, no bounty; the client sees a `die` and a `spawn`. HP, then `kill(old, null)` — uncounted, no bounty; the client sees a `die` and a `spawn`. The Hidden Core 铳's 【末日布道】
puts every 碎铳之簧 into 追逐模式 for its whole 5 s gain (`dog_duration`; invulnerable, no ordinary attacks): the spring
keeps following the casting gun after reaching it (PR #347; until 0.2.1 arrival or 1 tile ended the chase), and each
gun casts on its own data timer (20 s after it spawns, then every 45 s), so on a pair field the later call retargets
every spring to that gun for a new 5 s (PRTS: 「持续召唤场上所有“碎铳之簧”向自身移动」).
WALK legs pathfind on the stage grid inside the rect with the official flow field (grid.js: 4-direction SPFA from the WALK legs pathfind on the stage grid inside the rect with the official flow field (grid.js: 4-direction SPFA from the
destination, crates cost 1000, then Bresenham line-of-sight smoothing — research 08 §3.4). The official route stays destination, crates cost 1000, then Bresenham line-of-sight smoothing — research 08 §3.4). The official route stays
unless 0.1.0's road-over-floor preference route (the fewest non-blockable tiles among equal-length chains, a line of unless 0.1.0's road-over-floor preference route (the fewest non-blockable tiles among equal-length chains, a line of
sight that never covers floor — diagonal-step corners included — its grid route does not walk; user playtest #2 item 2) sight that never covers floor — diagonal-step corners included — its grid route does not walk; user playtest #2 item 2)
crosses strictly fewer non-blockable tiles (floor / gate lanes); "crosses" = passes through the tile's interior, a crosses strictly fewer non-blockable tiles (floor / gate lanes) and no more 深水区 (GitHub #375: on 战场#08(下半) the
patrolling 铳 / 卢西恩 waded through two water tiles to dodge one floor tile; the official upper road crosses one);
"crosses" = passes through the tile's interior, a
corner touch does not count (`grid.js` `segmentTiles`; community report D5 after 0.1.0: 战场#04's lower-gate enemies cut corner touch does not count (`grid.js` `segmentTiles`; community report D5 after 0.1.0: 战场#04's lower-gate enemies cut
diagonally from row 9 into row 10 as officially), and on equal counts the official route stays. diagonally from row 9 into row 10 as officially), and on equal counts the official route stays.
test/sim/pathing-official.test.js lists the 21 of 154 stage routes that still differ from the official ones (战场#01's test/sim/pathing-official.test.js lists the 21 of 154 stage routes that still differ from the official ones (战场#01's
+1 -1
View File
@@ -110,7 +110,7 @@ Piece = { uid, kind: 'chess'|'item'|'token', id, golden: bool, tier, items: [ {
**Corrections from research 08/09 (normative):** **Corrections from research 08/09 (normative):**
- Waves: official generator (research 08 §2): 3 of 6 types, each owns exactly 3 of the 15 round slots, SPECIAL the rest (shuffled per match); per round one type + one weighted special entry (half by round); each template SPAWN **action** becomes `clamp(roundHalfEven(n·P(t)/P(new)), 1, 5)` units over the same window; placeholder actions of the other movement class are skipped. Stage per match from the mode's list (标准 = 战场#01 only, §6.5). No custom balance multipliers (`data/tuning.json` keeps only titles); co-op boss pool = `bloodPoint[difficulty]` unscaled. - Waves: official generator (research 08 §2): 3 of 6 types, each owns exactly 3 of the 15 round slots, SPECIAL the rest (shuffled per match); per round one type + one weighted special entry (half by round); each template SPAWN **action** becomes `clamp(roundHalfEven(n·P(t)/P(new)), 1, 5)` units over the same window; placeholder actions of the other movement class are skipped. Stage per match from the mode's list (标准 = 战场#01 only, §6.5). No custom balance multipliers (`data/tuning.json` keeps only titles); co-op boss pool = `bloodPoint[difficulty]` unscaled.
- Ground pathing: official SPFA-style 4-direction search from the goal with crates as cost-1000 tiles, then straight-line smoothing (research 08 §3.4). act1 m02 crates #001–#004 inactive in 下半. Remake refinement (user playtest #2, narrowed by §21.15): route lengths and routes stay the official ones unless 0.1.0's road-over-floor preference route crosses strictly fewer non-blockable (floor / gate / 深水区) tiles — a corner touch is not a crossing; ties keep the official route. E.g. 战场#01's lower-gate enemies take the col-8 road, while 战场#04's keep the official diagonal from row 9 into row 10 (server/sim/grid.js header; test/sim/pathing-official.test.js, pathing-blockable.test.js). - Ground pathing: official SPFA-style 4-direction search from the goal with crates as cost-1000 tiles, then straight-line smoothing (research 08 §3.4). act1 m02 crates #001–#004 inactive in 下半. Remake refinement (user playtest #2, narrowed by §21.15): route lengths and routes stay the official ones unless 0.1.0's road-over-floor preference route crosses strictly fewer non-blockable (floor / gate / 深水区) tiles and no more 深水区 (GitHub #375) — a corner touch is not a crossing; ties keep the official route. E.g. 战场#01's lower-gate enemies take the col-8 road, while 战场#04's keep the official diagonal from row 9 into row 10 (server/sim/grid.js header; test/sim/pathing-official.test.js, pathing-blockable.test.js).
- Observing: no watching while your own normal battle runs; after it ends (and in prep) tap a teammate → 前往查看 (local replica, fast-forwarded). ‹ › only inside 联防/FA fields; FA: own pair only; eliminated: free. - Observing: no watching while your own normal battle runs; after it ends (and in prep) tap a teammate → 前往查看 (local replica, fast-forwarded). ‹ › only inside 联防/FA fields; FA: own pair only; eliminated: free.
- Prep interactions: tap a unit → underframe with 出售 +1 (and 撤退 on board); items/Arts → 销毁; shop and upgrade are two-tap (select → confirm; the remake's 机变 cards too, §18.2); strategy draft forbids a strategy already taken by a teammate (队友已选). - Prep interactions: tap a unit → underframe with 出售 +1 (and 撤退 on board); items/Arts → 销毁; shop and upgrade are two-tap (select → confirm; the remake's 机变 cards too, §18.2); strategy draft forbids a strategy already taken by a teammate (队友已选).
- Emotes: 6 themes × 6 battle emotes (research 09 §4.2), image-only bubbles beside the sender avatar, 1 s cooldown, 3 s bubble; `g.emote {id}` with the official emoji id. - Emotes: 6 themes × 6 battle emotes (research 09 §4.2), image-only bubbles beside the sender avatar, 1 s cooldown, 3 s bubble; `g.emote {id}` with the official emoji id.
+3 -2
View File
@@ -352,8 +352,9 @@ reported the 战场#01 lower-gate enemies walking up the col-9 floor lane, where
`server/sim/grid.js` keeps every official route LENGTH and the official route itself unless the 2026-09-29 `server/sim/grid.js` keeps every official route LENGTH and the official route itself unless the 2026-09-29
road-over-floor preference route (equal-length ties to the chain with the fewest non-blockable floor / gate tiles, road-over-floor preference route (equal-length ties to the chain with the fewest non-blockable floor / gate tiles,
smoothing that never cuts across floor its grid route does not walk, the corner tiles of a diagonal step included) smoothing that never cuts across floor its grid route does not walk, the corner tiles of a diagonal step included)
crosses strictly fewer non-blockable tiles; in that comparison a segment that only touches a floor tile's corner does crosses strictly fewer non-blockable tiles and no more 深水区 (GitHub #375, 0.2.2: the 深水区 is non-blockable too, and
not cross it. Used alone (0.1.0) the preference also bent official diagonals into L shapes — the D5 report after on 战场#08(下半) the patrolling leaders had waded through two water tiles to dodge one floor tile); in that comparison a
segment that only touches a floor tile's corner does not cross it. Used alone (0.1.0) the preference also bent official diagonals into L shapes — the D5 report after
0.1.0: on 战场#04 the lower-gate enemies walked (9,10) → (9,8) → (10,8) instead of the official diagonal (9,10) → 0.1.0: on 战场#04 the lower-gate enemies walked (9,10) → (9,8) → (10,8) instead of the official diagonal (9,10) →
(10,7), which only brushes the corner of the floor (10,9). Lanes that still differ from the table above (21 of the 154 (10,7), which only brushes the corner of the floor (10,9). Lanes that still differ from the table above (21 of the 154
stage × gate × field routes, test/sim/pathing-official.test.js): stage × gate × field routes, test/sim/pathing-official.test.js):
+45 -6
View File
@@ -28,10 +28,11 @@
// (PRTS 状态机: an enemy's COMBAT state ends with its attack, not with its blocker); the candidates pass the // (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 // 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 // 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 // arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak — on a
// enemy runs between random checkpoints away from the fear's source (fear.js moveFeared; a self-inflicted fear // portal entrance it is a teleport to the far exit and a walk to that side's blue door instead (portalPickup). A `fear`
// flutters inside its own tile); an `attract` (诱导) status walks it to the status point instead (moveAttracted); // (恐惧) status suspends the route: the enemy runs between random checkpoints away from the fear's source (fear.js
// both re-plan the route when released (恐惧 outranks 诱导). // 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 { 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'; import { sortEnemyTargets, sortAllyTargets, canTargetEnemy, canTargetAlly, tileKeyOf } from './targeting.js';
@@ -634,7 +635,10 @@ function advanceRoute(b, e, dt, R, standing = false) {
let guard = 16; let guard = 16;
while (budget > 1e-9 && guard-- > 0 && e.alive) { while (budget > 1e-9 && guard-- > 0 && e.alive) {
const leg = R.legs[R.legIdx]; 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 (leg.t === 'wait') {
if (R.waitLeft == null) R.waitLeft = leg.time; if (R.waitLeft == null) R.waitLeft = leg.time;
const use = Math.min(budget, R.waitLeft); const use = Math.min(budget, R.waitLeft);
@@ -684,13 +688,48 @@ function advanceRoute(b, e, dt, R, standing = false) {
} }
budget = dist / speed; budget = dist / speed;
if (R.pts && R.ptIdx >= R.pts.length) { 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.legIdx++;
R.pts = null; 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 * 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 * decision of 2026-10-04 from first-hand memory of the official game: a ranged enemy stops for each attack's animation
+12 -4
View File
@@ -640,8 +640,11 @@ function kitGun(ab, e, b) {
}); });
if (s2) list.push({ if (s2) list.push({
cd: s2.cd, icd: s2.icd, cond: (b2) => b2.enemies.some((o) => o.alive && isSpring(o)), cd: s2.cd, icd: s2.icd, cond: (b2) => b2.enemies.some((o) => o.alive && isSpring(o)),
fire(b2, e2) { // 【末日布道】 springs dash to 铳, invulnerable, trampling operators fire(b2, e2) { // 【末日布道】 springs chase 铳 for the whole 5 s gain, invulnerable, trampling operators (kitSpring)
// PRTS “碎铳之簧” 追逐模式 "不进行普通攻击": `disarm` for the dash (until 0.1.3 it kept shooting while it ran) // PRTS “碎铳之簧” 追逐模式 "不进行普通攻击": `disarm` for the dash (until 0.1.3 it kept shooting while it ran).
// Each gun casts on its own data timer, and a cast takes every spring — on a pair field the later of the two
// calls retargets them all to its gun (handbook 「持续召唤场上所有“碎铳之簧”向自身移动」; no stagger, no lock while
// a chase runs: PR #347's pair coordination is not taken, the data has neither)
for (const sp of b2.enemies) { for (const sp of b2.enemies) {
if (!sp.alive || !isSpring(sp) || !sp.mem.ab) continue; if (!sp.alive || !isSpring(sp) || !sp.mem.ab) continue;
sp.mem.ab.dash = { until: b2.time + (s2.bb.dog_duration ?? 0), mul: 1 + (s2.bb.move_speed ?? 0), gun: e2, hit: new Set() }; sp.mem.ab.dash = { until: b2.time + (s2.bb.dog_duration ?? 0), mul: 1 + (s2.bb.move_speed ?? 0), gun: e2, hit: new Set() };
@@ -709,11 +712,16 @@ function kitSpring(ab, e) {
if (!P.up && P.downAt != null && b.time - P.downAt >= regen) raise(b); if (!P.up && P.downAt != null && b.time - P.downAt >= regen) raise(b);
const d = e2.mem.ab.dash; const d = e2.mem.ab.dash;
if (!d) return; if (!d) return;
// 追逐模式 lasts the whole gain (PRTS 末日布道 "令全场的“碎铳之簧”获得5秒增益…增益期间切换为追逐模式"; “碎铳之簧”
// 追逐模式 "持续追踪令其进入该形态的场上的假想敌:铳移动" — no arrival clause, unlike the 铳's own 冲锋模式): reaching
// the gun does not end it, the spring keeps following the gun until the 5 s are over (PR #347 by @CXUtk; until
// 0.2.1 it ended on arrival or within 1 tile). The casting gun dead: the nearest other gun; none left on the
// field: it holds still, the gain running on.
const g = d.gun && d.gun.alive ? d.gun : gun(b); const g = d.gun && d.gun.alive ? d.gun : gun(b);
const arrived = !g || stepToward(e2, g.x, g.y, e2.s.moveSpeed * d.mul * MOVE_SCALE * dt) || hypot(g.x - e2.x, g.y - e2.y) < 1; if (g) stepToward(e2, g.x, g.y, e2.s.moveSpeed * d.mul * MOVE_SCALE * dt);
// 追逐模式 "对进入自身0.35半径范围内的我方单位(包括飞行单位)造成一次攻击力100%的物理普通伤害" (until 0.1.3: radius 0.5) // 追逐模式 "对进入自身0.35半径范围内的我方单位(包括飞行单位)造成一次攻击力100%的物理普通伤害" (until 0.1.3: radius 0.5)
for (const u of areaAllies(b, e2, e2.x, e2.y, CHARGE_RADIUS)) if (!d.hit.has(u)) { d.hit.add(u); hurt(b, e2, u, e2.s.atk, 'phys'); } for (const u of areaAllies(b, e2, e2.x, e2.y, CHARGE_RADIUS)) if (!d.hit.has(u)) { d.hit.add(u); hurt(b, e2, u, e2.s.atk, 'phys'); }
if (arrived || b.time >= d.until) { e2.mem.ab.dash = null; b.removeBuff(e2, 'boss:dash'); if (e2.route) e2.route.pts = null; } if (b.time >= d.until) { e2.mem.ab.dash = null; b.removeBuff(e2, 'boss:dash'); if (e2.route) e2.route.pts = null; }
}, },
}, },
// while shielded, every (spCost+1)-th attack (enemy SP +1 per attack, as 粉碎攻坚手's official text) adds the shield's // while shielded, every (spCost+1)-th attack (enemy SP +1 per attack, as 粉碎攻坚手's official text) adds the shield's
+4 -1
View File
@@ -400,7 +400,10 @@ function applyReader(battle, it) {
else battle.removeBuff(u, key); else battle.removeBuff(u, key);
break; break;
case 'act1autochess_gar_eff_respawnTimeByBond': case 'act1autochess_gar_eff_respawnTimeByBond':
if (k > 0) S.passiveBuff(battle, u, key, { redeployMul: Math.max(0.05, 1 + S.num(it.bb.respawn_time, 0) * k) }); // 耀骑士临光's 144: −1.5 % / −3 % per 3 卡西米尔 layers with no minimum in the blackboard (divide_num, respawn_time
// only) — clamped at 0 like every other redeploy multiplier, so a knock-out timer can reach 0 s (GitHub #370;
// until 0.2.1 a 0.05 floor kept it at 5 % of the base time from 192 layers, 96 for the elite)
if (k > 0) S.passiveBuff(battle, u, key, { redeployMul: Math.max(0, 1 + S.num(it.bb.respawn_time, 0) * k) });
else battle.removeBuff(u, key); else battle.removeBuff(u, key);
break; break;
case 'act2autochess_gar_eff_attrByBond_add_onstart': { case 'act2autochess_gar_eff_attrByBond_add_onstart': {
+22 -11
View File
@@ -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 // 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 // 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 // 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 // changed when its 深水区 became non-buildable). Each field holds two candidate
// water instead of cutting across it). 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): // 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; // * official: the plain algorithm above;
// * preference (0.1.0's): the SPFA relaxes on (dist, pen) lexicographically, `pen` = non-blockable tiles on the chain // * 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 // 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. // route does not walk.
// `next[tile]` is the official pointer unless the preference pointer leads to a route CROSSING strictly fewer // `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 // 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 // — 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 // 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 */ /** 1 = walkable terrain an operator cannot block on (floor, gate, goal, teleport…): the path tie-break penalty */
this.unblockable = new Uint8Array(ROWS * COLS); 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++) { for (let k = 0; k < ROWS * COLS; k++) {
const t = this.tiles[k]; const t = this.tiles[k];
this.unblockable[k] = t.pass === 'ALL' && !isBlockableTile(t) ? 1 : 0; 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) */ /** obstacle bits per tile (OB_BLOCK | OB_CRATE) */
this.obstacle = new Uint8Array(ROWS * COLS); this.obstacle = new Uint8Array(ROWS * COLS);
@@ -272,10 +277,10 @@ export class Grid {
_buildField(er, ec, allowDiagonal, ignore) { _buildField(er, ec, allowDiagonal, ignore) {
const N = ROWS * COLS; 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)) { if (!this.inBounds(er, ec)) {
const none = new Int32Array(N).fill(-1); 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; const dest = er * COLS + ec;
f.dest = dest; 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; }); (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 // 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 // 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 = []; const order = [];
for (let k = 0; k < N; k++) if (dist[k] >= 0) order.push(k); for (let k = 0; k < N; k++) if (dist[k] >= 0) order.push(k);
order.sort((a, b) => dist[a] - dist[b] || a - b); 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 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 next = new Int32Array(N).fill(-1);
const cost = new Int32Array(N); const cost = new Int32Array(N);
const deep = new Int32Array(N);
for (const k of order) { for (const k of order) {
const o = nextO[k], p = nextP[k]; const o = nextO[k], p = nextP[k];
if (o < 0) continue; // the destination 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) { if (p >= 0 && p !== o) {
const cp = through(k, p) + cost[p]; const cp = through(k, p) + cost[p], wp = wet(k, p) + deep[p];
if (cp < best || (cp === best && next[o] === p && onSegment(k, o, p))) { use = p; best = cp; } if (wp <= bestWet && (cp < best || (cp === best && next[o] === p && onSegment(k, o, p)))) { use = p; best = cp; bestWet = wp; }
} }
next[k] = use; next[k] = use;
cost[k] = best; 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} pen non-blockable tiles on that raw chain (tile included, destination excluded)
* @property {Int32Array} official smoothed parent of the pure official algorithm * @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 * @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} 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) * @property {Float64Array|null} len memoised smoothed length (fieldLength)
*/ */
+21
View File
@@ -394,6 +394,7 @@ export function normalizeRoute(r) {
const t = String(st.t ?? st.type ?? 'move').toUpperCase(); const t = String(st.t ?? st.type ?? 'move').toUpperCase();
cps.push({ type: normCpType(t), pos: pairOf(st.p ?? st.pos) ?? [0, 0], time: num(st.s ?? st.time, 0) }); cps.push({ type: normCpType(t), pos: pairOf(st.p ?? st.pos) ?? [0, 0], time: num(st.s ?? st.time, 0) });
} }
portalAppear(cps);
return { motion, start, end, checkpoints: cps }; return { motion, start, end, checkpoints: cps };
} }
for (const cp of r.checkpoints ?? r.cp ?? []) { for (const cp of r.checkpoints ?? r.cp ?? []) {
@@ -406,9 +407,29 @@ export function normalizeRoute(r) {
cps.push({ type: normCpType(cp.type), pos: pairOf(cp.pos ?? cp.position ?? [cp.row, cp.col]) ?? [0, 0], time: num(cp.time ?? cp.t, 0) }); cps.push({ type: normCpType(cp.type), pos: pairOf(cp.pos ?? cp.position ?? [cp.row, cp.col]) ?? [0, 0], time: num(cp.time ?? cp.t, 0) });
} }
} }
portalAppear(cps);
return { motion, start, end, checkpoints: cps }; return { motion, start, end, checkpoints: cps };
} }
/**
* The boss / Hidden Core templates' 传送门 (GitHub #336, PR #337 by @2321Robin): the twin routes spell a crossing as
* DISAPPEAR → WAIT → APPEAR[exit], but four official routes leave the APPEAR out and go straight to MOVE-to-the-exit
* (act1autochess_h07_02 #4, act1autochess_h08_02 #5 / #9, act2autochess_h07_05 #6; the level files themselves have no
* APPEAR_AT_POS there). Taken literally, such a route walks the enemy's whole remaining run hidden (untargetable,
* unblockable) and it leaks unseen — the crossing must read "reappear on the far side". Re-insert the exit APPEAR
* ahead of that MOVE, at the MOVE's own target, where the twins appear ([5,10]; the leftover MOVE becomes a
* zero-length hop, and the hop no longer counts as walking distance for the targeting order). A DISAPPEAR followed by
* an APPEAR, or by no MOVE, stays as it is; idempotent.
*/
function portalAppear(cps) {
for (let i = 0; i < cps.length; i++) {
if (cps[i].type !== 'DISAPPEAR') continue;
let j = i + 1;
while (j < cps.length && cps[j].type === 'WAIT') j++;
if (cps[j] && cps[j].type === 'MOVE') cps.splice(j, 0, { type: 'APPEAR', pos: cps[j].pos.slice(), time: 0 });
}
}
function normCpType(t) { function normCpType(t) {
const s = String(t ?? 'MOVE').toUpperCase(); const s = String(t ?? 'MOVE').toUpperCase();
if (s.includes('DISAPPEAR')) return 'DISAPPEAR'; if (s.includes('DISAPPEAR')) return 'DISAPPEAR';
+27
View File
@@ -465,6 +465,33 @@ test('ADD_BOND 耀骑士临光 145 / 160 (real chess): self + front get 144 (red
} }
}); });
test('respawnTimeByBond 144 has no 5% floor (#370): redeployMul is max(0, 1 + respawn_time * steps)', () => {
for (const [gid, rt] of [['garrison_144_a', -0.015], ['garrison_144_b', -0.03]]) {
assert.equal(GR(gid).bb.respawn_time, rt);
assert.deepEqual(Object.keys(GR(gid).bb).sort(), ['divide_num', 'respawn_time']);
// 96 / 192 layers: the old 0.05 floor bound the elite / the normal form (raw 0.04); 300: below zero
for (const layers of [9, 96, 192, 300]) {
const k = Math.floor(layers / 3);
const want = Math.max(0, 1 + rt * k);
const h = battle({
units: [{ id: 'op', g: [gid], row: 10, col: 4, o: { stats: { respawnTime: 100 } } }],
bonds: { kazimierzShip: B(layers) },
});
const u = h.unit('op');
approx(u.findBuff(`gar:${gid}`).mods.redeployMul, want, `${gid} @ ${layers}`);
h.b.dealDamage(null, u, { amount: 1e9, type: 'true' });
assert.equal(u.alive, false);
approx(u.respawnAt - u.deathAt, 100 * want, `${gid} timer @ ${layers}`);
if (want === 0) {
h.step(2);
assert.ok(u.alive && u.deployed, `${gid} @ ${layers}: a 0 s timer brings it straight back`);
assert.equal(h.b.errorCount, 0);
}
}
cover(gid);
}
});
// --------------------------------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------------------------------
// 魔王 // 魔王
+156
View File
@@ -0,0 +1,156 @@
// 隐秘核心铳's 【末日布道】 (PR #347 by @CXUtk): every 碎铳之簧 chases the casting gun for the gain's whole 5 s — PRTS 末日布道
// "令全场的“碎铳之簧”获得5秒增益:移动速度+800%,无敌,增益期间切换为追逐模式", “碎铳之簧” 追逐模式 "持续追踪令其进入该形态的场上的
// 假想敌:铳移动" (no arrival clause). Until 0.2.1 reaching the gun (or 1 tile from it) ended the chase. Each gun casts on its
// own data timer (initCooldown 20, cooldown 45 — no stagger between the two guns of a pair field, no lock while a chase
// runs: the data has neither, and the handbook's 「向自身」 means the later call takes the springs).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
import * as enemies from '../../server/sim/content/enemies.js';
import * as bosses from '../../server/sim/content/bosses.js';
const W = JSON.parse(fs.readFileSync(new URL('../../data/waves.json', import.meta.url), 'utf8'));
const E = JSON.parse(fs.readFileSync(new URL('../../data/enemies.json', import.meta.url), 'utf8'));
const GUN = 'enemy_9017_achunt_2';
const SPRING = 'enemy_9020_actrpc';
const wall = chessRec({ id: 'pursuit_wall', profession: 'TANK', stats: { maxHp: 1e9, atk: 0, def: 1000, blockCnt: 3 }, rangeGrid: [[0, 0]], skill: null });
/** The real h08_02(_s) template (leader + parts only, or no spawns with `manual`), one non-attacking wall per side. */
function arena({ solo = false, manual = false, cooldown = null } = {}) {
const tpl = structuredClone(W[solo ? 'act1autochess_h08_02_s' : 'act1autochess_h08_02']);
tpl.spawns = manual ? [] : tpl.spawns.filter((s) => s.tag === 'boss' || s.tag === 'part');
if (cooldown != null) {
tpl.overrides[GUN] ??= { skills: structuredClone(E[GUN].skills) };
const skill = tpl.overrides[GUN].skills.find((s) => s.prefabKey === '2');
skill.initCooldown = 0;
skill.cooldown = cooldown;
}
return makeBattle({
kind: 'hidden', waveTemplate: tpl, content: 'generic', extraContent: [enemies, bosses], seed: 7,
autoFinish: false, timeLimit: 300, recordEvents: false, hooks: [],
defs: { chess: { pursuit_wall: wall } }, kits: { pursuit_wall: () => ({ trait: { noAttack: true } }) },
sharedBoss: { hp: 1e12, maxHp: 1e12, damage(pid, n) { this.hp -= n; } },
players: [
{ playerId: 'p1', seat: 0, side: 'L', colOffset: 0, units: [{ uid: 1, chessId: 'pursuit_wall', row: 3, col: 5, abs: true }] },
...solo ? [] : [{ playerId: 'p2', seat: 1, side: 'R', colOffset: 8, units: [{ uid: 2, chessId: 'pursuit_wall', row: 3, col: 15, abs: true }] }],
],
setup(b) { b.enemyOverrides = tpl.overrides; b.opts.waveTemplate = tpl; b.opts.bossMirror = !manual; },
});
}
/** Every 【末日布道】 call (a new chase object on the spring): { time, gun }. */
function recordCalls(h) {
const calls = [];
let last = null;
h.b.on('tick', () => {
const d = h.enemies().find((e) => e.defId === SPRING)?.mem.ab.dash;
if (!d || d === last) return;
last = d;
calls.push({ time: h.b.time, gun: d.gun });
});
return calls;
}
test('隐秘核心铳 pair: each gun calls on its own data timer (20 s after it spawns, then every 45 s); the later call takes the springs', () => {
const h = arena();
const spawned = new Map();
h.b.on('enemySpawn', (c) => { if (c.enemy.defId === GUN) spawned.set(c.enemy, h.b.time); });
const calls = recordCalls(h);
h.run(150);
assert.equal(spawned.size, 2, 'the pair field has two guns (the mirrored one)');
const skill = W.act1autochess_h08_02.overrides[GUN].skills.find((s) => s.prefabKey === '2');
for (const [gun, t0] of spawned) {
const own = calls.filter((c) => c.gun === gun).map((c) => c.time);
assert.equal(own.length, 3, 'three calls each in 150 s');
assert.ok(Math.abs(own[0] - t0 - skill.initCooldown) < h.TICK * 2, `first call ${skill.initCooldown} s after its spawn`);
for (let i = 1; i < own.length; i++) assert.ok(Math.abs(own[i] - own[i - 1] - skill.cooldown) < h.TICK * 2, `then every ${skill.cooldown} s`);
}
// the two mirrored guns call within a tick or two of each other (no half-cooldown stagger), the later one wins
for (let i = 1; i < calls.length; i += 2) assert.ok(calls[i].time - calls[i - 1].time < h.TICK * 3, 'the two calls come together');
assert.equal(h.b.errorCount, 0);
checkInvariants(h.b);
});
test('隐秘核心铳: reaching a moving gun keeps pursuit, invulnerability and disarm for the full 5 s', () => {
const h = arena({ solo: true, manual: true, cooldown: 45 });
h.step();
const gun = h.spawn(GUN, { pos: [3, 5], tag: 'boss', mods: { speedMul: 0 } });
const spring = h.spawn(SPRING, { pos: [3, 4.8], tag: 'part' });
h.step(2);
const d = spring.mem.ab.dash;
assert.ok(d, 'arriving next to the gun does not end the chase');
h.run(1);
gun.x = 6;
const oldX = spring.x;
h.run(1);
assert.ok(spring.x > oldX, 'it keeps following the gun when the gun moves');
assert.equal(spring.mem.ab.dash, d);
assert.ok(spring.s.flags.invulnerable && spring.s.flags.disarm);
h.run(d.until - h.b.time - h.TICK * 2);
assert.equal(spring.mem.ab.dash, d, 'still chasing just before the 5 s are over');
assert.ok(spring.s.flags.invulnerable && spring.s.flags.disarm);
h.run(h.TICK * 4);
assert.equal(spring.mem.ab.dash, null, 'over once the gain ends');
assert.ok(!spring.s.flags.invulnerable && !spring.s.flags.disarm);
assert.equal(h.b.errorCount, 0);
});
test('隐秘核心铳: the other gun\'s call during a chase retargets every spring to that gun for a new 5 s', () => {
const h = arena({ manual: true, cooldown: 45 });
h.step();
const left = h.spawn(GUN, { pos: [3, 5], tag: 'boss', mods: { speedMul: 0 } });
const right = h.spawn(GUN, { pos: [3, 15], tag: 'boss', mods: { speedMul: 0 } });
const call = right.mem.ab.list.find((a) => a.cd === 45 && a.fire);
call.left = 999; // the right gun's call is not ready yet
const spring = h.spawn(SPRING, { pos: [3, 10], tag: 'part' });
h.step(2);
const first = spring.mem.ab.dash;
assert.ok(first && first.gun === left, 'the first call takes the spring to the left gun');
h.run(1);
assert.equal(spring.mem.ab.dash, first, 'chasing the left gun');
call.left = 0; // the right gun's call comes ready mid-chase
h.step(2);
const second = spring.mem.ab.dash;
assert.ok(second && second !== first && second.gun === right, 'the later call takes the spring at once');
assert.ok(Math.abs(second.until - h.b.time - 5) < h.TICK * 3, 'for a new 5 s');
const oldX = spring.x;
h.run(1);
assert.ok(spring.x > oldX, 'it now runs to the right gun');
assert.ok(spring.s.flags.invulnerable && spring.s.flags.disarm);
assert.equal(h.b.errorCount, 0);
});
test('隐秘核心铳: with no gun left on the field the spring holds still, invulnerable and disarmed, until the gain ends', () => {
const h = arena({ solo: true, manual: true, cooldown: 45 });
h.step();
const gun = h.spawn(GUN, { pos: [3, 5], tag: 'boss', mods: { speedMul: 0 } });
const spring = h.spawn(SPRING, { pos: [3, 1], tag: 'part' });
h.step(2);
const d = spring.mem.ab.dash;
assert.ok(d, 'chasing');
h.b.kill(gun, null);
h.step();
const at = [spring.x, spring.y];
h.run(1);
assert.deepEqual([spring.x, spring.y], at, 'no gun to follow: it stands');
assert.equal(spring.mem.ab.dash, d, 'the chase is not over');
assert.ok(spring.s.flags.invulnerable && spring.s.flags.disarm);
h.run(d.until - h.b.time + h.TICK * 2);
assert.equal(spring.mem.ab.dash, null);
assert.ok(!spring.s.flags.invulnerable);
assert.equal(h.b.errorCount, 0);
});
test('隐秘核心铳: solo keeps the template initial cooldown and repeated call timing', () => {
const h = arena({ solo: true });
const calls = recordCalls(h);
h.run(100);
assert.equal(calls.length, 2);
const tpl = W.act1autochess_h08_02_s;
const spawnTime = tpl.spawns.find((s) => s.tag === 'boss').time;
const skill = (tpl.overrides[GUN]?.skills ?? E[GUN].skills).find((s) => s.prefabKey === '2');
assert.ok(Math.abs(calls[0].time - spawnTime - skill.initCooldown) < h.TICK * 2);
assert.ok(Math.abs(calls[1].time - calls[0].time - skill.cooldown) < h.TICK * 2);
assert.equal(h.b.errorCount, 0);
});
+281 -283
View File
@@ -142,75 +142,75 @@
"killed": 8, "killed": 8,
"total": 8, "total": 8,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 5311560, "bossHpLeft": 5301010,
"rngDraws": 839, "rngDraws": 783,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":612448,"bossDmg":313008,"heal":16689,"deaths":0,"layerGains":{}}, "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":741043,"bossDmg":366117,"heal":19827,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":937710,"bossDmg":372608,"heal":6945,"deaths":2,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":819707,"bossDmg":330056,"heal":4781,"deaths":3,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,183438,1132,168,53,3], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,235262,2487,437,74,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,3903,11319,311,35,1], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,12166,13761,3921,33,1],
["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,7,0,30509,953,294,58,17], ["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,7,0,24813,953,294,16,12],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,10026,0,0,66,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,5871,0,0,20,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,79315,0,0,32,8], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,123408,0,0,77,10],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,78933,0,0,44,4], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,81805,0,0,44,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,1,26033,0,0,43,2], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",2769.3,1,17,2,56882,1768,1768,100,3],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,19,2,168011,1748,1748,55,18], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",748.12,1,43,1,153989,3270,796,46,16],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,14114,21,3], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,12611,20,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,32280,1383,55,35,2], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,1,46847,0,0,34,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",836.62,1,12,0,0,3919,1500,0,2], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2172.64,1,50,0,0,5191,1078,0,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,51,2,253580,496,458,287,8], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,51,2,259014,0,0,279,8],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2099.31,1,8,0,4500,1149,865,24,8], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,52,0,3678,2726,343,18,7],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,52,1,112288,6746,1602,147,13], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,49,1,97836,2233,1258,153,10],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,10,0,62072,0,0,54,3], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,10,0,82406,0,0,73,4],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,1,303642,0,0,181,3], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,0,179138,0,0,231,3],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,0,0,0,0,0], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,4,0,0,0,0,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1418.27,1,18,1,69028,3331,2517,58,3], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1003.26,1,18,0,72377,3328,2099,61,4],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,40,0,44456,1991,2,63,18], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,40,0,62819,1991,2,81,22],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,88144,0,0,57,14], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,1,62440,0,0,47,13],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null] ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
], ],
"enemies": {"enemy_1005_yokai_2":[6,6,0,3179,62190],"enemy_1430_lrrook":[2,2,0,0,802449],"enemy_9017_achunt":[2,0,0,30987,688440]}, "enemies": {"enemy_1005_yokai_2":[6,6,0,3956,62190],"enemy_1430_lrrook":[2,2,0,0,802449],"enemy_9017_achunt":[2,0,0,33752,698990]},
"hooks": [1,50,6000,1312,1325,2789,2834,4861,11,26,131,125,21,35636,978,0,10,0,1,11,0,13,1], "hooks": [1,50,6000,1372,1410,2907,2944,3787,13,26,127,123,21,38164,1047,0,10,0,1,13,0,12,1],
"events": {"atk":1342,"deploy":40,"die":26,"dmg":2826,"engage":17,"fx":602,"heal":41,"skill":256,"spawn":32,"status":489}, "events": {"atk":1411,"deploy":40,"die":26,"dmg":2934,"engage":17,"fx":696,"heal":41,"skill":250,"spawn":32,"status":549},
"fx": { "fx": {
"aoe": 38, "aoe": 33,
"appear": 2, "appear": 2,
"blink": 16, "blink": 14,
"burst": 13, "burst": 12,
"catShield": 1, "catShield": 1,
"charge": 8, "charge": 7,
"counter": 21, "counter": 21,
"crit": 88, "crit": 89,
"dilemma": 3, "dilemma": 4,
"disappear": 2, "disappear": 2,
"dodge": 1, "dodge": 1,
"explosion": 14, "explosion": 13,
"frostNova": 18, "frostNova": 22,
"garrisonGrant": 1, "garrisonGrant": 1,
"iceSpike": 39, "iceSpike": 89,
"overclock": 2, "overclock": 1,
"pull": 3, "pull": 4,
"quadShot": 2, "quadShot": 2,
"ripple": 13, "ripple": 20,
"sentry": 6, "sentry": 6,
"sermon": 1, "sermon": 1,
"shadowWeave": 11, "shadowWeave": 10,
"shield": 6, "shield": 5,
"soul": 5, "soul": 4,
"sp": 11, "sp": 7,
"stealth": 8, "stealth": 8,
"swordRain": 96, "swordRain": 82,
"swordStorm": 13, "swordStorm": 10,
"tornado": 3, "tornado": 4,
"tornadoPulse": 15, "tornadoPulse": 20,
"weightless": 4, "weightless": 4,
"zone": 138 "zone": 197
}, },
"snaps": "7917cfe4 ec53e8ac 09d8b201 823345e1 5d4f2a95 9efdd7ee 56e0addd 78c7726c 4ee3695b 20aee5b5 ba3311c0 d4d127c9 0c500cac d842272b 4cbf4a87 2525db60 3291ce13 ecb45e94 c2e5c2d0 a4ac7943" "snaps": "7917cfe4 719c3d1e 5fce9836 dcc6d1db c6372862 d28f9510 24fc36dd 451a8b51 a84bc802 17264489 87aca75c 241abfe9 0bdd4e23 94a753a7 f66cf2d6 27f37155 8c831d53 112e1ac2 a0065d70 7e66122d"
}, },
"boss-boss_2-solo": { "boss-boss_2-solo": {
"about": "假想敌:铳 solo pool 3000000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "假想敌:铳 solo pool 3000000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -227,27 +227,27 @@
"killed": 5, "killed": 5,
"total": 7, "total": 7,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 2761255, "bossHpLeft": 2757148,
"rngDraws": 127, "rngDraws": 130,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":401784,"bossDmg":232315,"heal":40581,"deaths":1,"layerGains":{}}}, "players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":451604,"bossDmg":236418,"heal":39582,"deaths":1,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2383.55,1,18,0,4821,19443,15985,82,0], ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2383.55,1,18,0,5154,18917,15557,82,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,86351,0,0,79,4], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,89239,0,0,82,4],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,6,0,77745,6115,3711,91,4], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,6,0,89910,6115,3639,104,4],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,7,1,23391,7108,1936,80,7], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,7,1,23474,7108,1915,82,7],
["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2569,1,8,4,131322,8759,0,69,2], ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2569,1,8,4,162219,8759,0,75,3],
["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,9,0,16118,0,0,11,0], ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,9,0,23155,0,0,12,2],
["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,10,0,0,1263,566,2,1], ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,10,0,0,1263,566,2,1],
["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,1,0,62036,0,1129,85,6], ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,1,0,58453,0,1107,75,5],
["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",695.86,1,3,0,0,1448,1057,0,3], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,3,0,0,0,574,0,3],
["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,0,16195,34,3] ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,0,16224,34,3]
], ],
"enemies": {"enemy_1249_lysdb_2":[5,5,0,8138,107495],"enemy_1430_lrrook":[2,0,2,0,66943],"enemy_9017_achunt":[1,0,0,23716,238745]}, "enemies": {"enemy_1249_lysdb_2":[5,5,0,8138,107495],"enemy_1430_lrrook":[2,0,2,0,112690],"enemy_9017_achunt":[1,0,0,21742,242852]},
"hooks": [1,19,6000,580,580,1299,1898,5497,6,8,30,29,0,13846,99,5,8,2,1,8,0,6,2], "hooks": [1,19,6000,595,595,1368,1966,5211,6,8,32,32,0,15401,91,5,8,2,1,8,0,6,2],
"events": {"atk":615,"deploy":11,"die":6,"dmg":1888,"engage":7,"fx":277,"heal":37,"leak":2,"skill":62,"spawn":18,"status":481}, "events": {"atk":638,"deploy":11,"die":6,"dmg":1956,"engage":7,"fx":289,"heal":37,"leak":2,"skill":67,"spawn":18,"status":465},
"fx": {"bandage":3,"block":9,"burst":6,"charge":7,"dodge":2,"flame":7,"garrisonGrant":1,"healField":1,"jungleSoul":4,"mote":102,"redistribute":45,"reweave":3,"riposte":2,"rockslide":4,"sandChains":5,"sandChainsCharged":1,"shieldBreak":15,"slash":32,"spGift":5,"thorns":21,"truesilver":2}, "fx": {"appear":2,"bandage":3,"block":10,"burst":6,"charge":7,"disappear":2,"dodge":2,"featherArrow":2,"flame":7,"garrisonGrant":1,"healField":1,"jungleSoul":4,"mote":102,"redistribute":45,"reweave":3,"riposte":2,"rockslide":4,"sandChains":5,"shieldBreak":15,"slash":38,"spGift":5,"thorns":20,"truesilver":3},
"snaps": "a42f06d2 4a40e022 e95a4901 abb693b9 1c238b4b afb89449 d6c6b3df c846d6b3 fa04e7ce c4d85f2e 4a6790c9 effed23a 72e8e203 8146bf98 e8dfb61c 69d1b960 6d949949 0310d38f a1429900 b8610bee" "snaps": "a42f06d2 4a40e022 e95a4901 abb693b9 1c238b4b afb89449 d6c6b3df c846d6b3 7c93c566 120f2baa 5eba2341 475bd0e1 a82180cf 2ec0f97d b5acfe2a 942750af b2587ba1 f8bb84fe 0dee9c0d c305f7f7"
}, },
"boss-boss_3-pair": { "boss-boss_3-pair": {
"about": "假想敌:管 pair pool 4000000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b", "about": "假想敌:管 pair pool 4000000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b",
@@ -516,29 +516,29 @@
"killed": 5, "killed": 5,
"total": 7, "total": 7,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 3759559, "bossHpLeft": 3757752,
"rngDraws": 84, "rngDraws": 87,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":713408,"bossDmg":440441,"heal":95170,"deaths":6,"layerGains":{}}}, "players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":731117,"bossDmg":442248,"heal":91813,"deaths":7,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2497.84,1,23,2,19548,11293,12704,21,0], ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2762.76,1,23,2,19548,11760,12983,21,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",5981.62,1,22,0,42568,20827,14636,11,5], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",5981.62,1,22,0,42568,20827,14636,11,5],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,21,3,202794,41516,12031,43,1], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,21,3,202794,41516,12121,43,1],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",3350.14,1,9,0,0,7920,0,0,0], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2350.14,1,9,0,785,9979,0,1,1],
["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2541.58,1,10,0,162116,8875,0,135,3], ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2061.88,1,24,0,177744,6029,0,120,3],
["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,24,0,234028,2838,0,104,5], ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,25,0,234028,2838,0,104,5],
["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,11,0,0,4444,24160,29,2], ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1545.34,1,11,0,0,4444,18376,28,2],
["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,25,0,52353,7314,487,76,5], ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",1748.63,1,26,0,53649,7314,487,76,5],
["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1394.71,1,3,0,0,5589,5880,0,3], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1394.71,1,3,0,0,6735,7186,0,3],
["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,4444,25271,28,4], ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,4444,26024,27,5],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null] ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
], ],
"enemies": {"enemy_1010_demon_2":[2,2,0,5940,107495],"enemy_1438_dspred":[5,3,2,31950,165472],"enemy_1521_dslily":[1,0,0,71001,440441]}, "enemies": {"enemy_1010_demon_2":[2,2,0,5940,107495],"enemy_1438_dspred":[5,3,2,34519,181373],"enemy_1521_dslily":[1,0,0,69256,442248]},
"hooks": [1,25,6000,565,565,958,1133,7303,11,13,28,28,0,13157,125,16,8,2,1,16,0,29,1], "hooks": [1,26,6000,551,551,949,1120,8051,12,14,30,30,0,13414,126,18,8,2,1,17,0,28,2],
"events": {"atk":770,"deploy":15,"die":11,"dmg":1115,"engage":6,"fx":335,"heal":105,"leak":2,"skill":59,"spawn":20,"status":479}, "events": {"atk":754,"deploy":16,"die":12,"dmg":1102,"engage":7,"fx":339,"heal":91,"leak":2,"skill":63,"spawn":20,"status":479},
"fx": {"bandage":4,"block":15,"burst":29,"camouflage":1,"dodge":1,"featherArrow":5,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":61,"redistribute":45,"reweave":3,"rockfall":18,"rockslide":5,"sandChains":5,"shieldBreak":18,"slash":53,"spGift":4,"tentacle":6,"thorns":48,"tide":5,"truesilver":3,"undying":1}, "fx": {"appear":2,"bandage":5,"block":15,"burst":28,"camouflage":1,"disappear":2,"dodge":2,"featherArrow":5,"flame":1,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":61,"redistribute":45,"reweave":3,"riposte":1,"rockfall":18,"rockslide":5,"sandChains":5,"shieldBreak":18,"slash":50,"spGift":4,"tentacle":6,"thorns":48,"tide":5,"truesilver":3,"undying":1},
"snaps": "b3cecd10 d3d68301 c10d8554 c91066bb 2ac0bbdd cb65c8b1 e357c87a 1cfd4db1 1a8bd8d8 e639cb94 69f98f4a 92de5c02 51bb18c2 a8b02c45 54b09843 f60538cb 94407072 8c567dae 4c15135e a957f119" "snaps": "b3cecd10 d3d68301 c10d8554 c91066bb 2ac0bbdd cb65c8b1 e357c87a 1cfd4db1 842efc1f 73ccb5e1 7cbe283e 83895aff b84dfc8e 65315bfe 89187e2c 11947687 dc8072f3 31f16379 3c0d78cb cf38d801"
}, },
"boss-boss_5-pair": { "boss-boss_5-pair": {
"about": "卢西恩,“猩红血钻” pair pool 1560000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b", "about": "卢西恩,“猩红血钻” pair pool 1560000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b",
@@ -703,35 +703,35 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 4, "killed": 5,
"total": 6, "total": 6,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 7017881, "bossHpLeft": 6932482,
"rngDraws": 570, "rngDraws": 708,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":869760,"bossDmg":436648,"heal":2146,"deaths":5,"layerGains":{}}, "p1": {"killed":5,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1096623,"bossDmg":528737,"heal":2146,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1033990,"bossDmg":505471,"heal":1246,"deaths":19,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1199975,"bossDmg":498782,"heal":1246,"deaths":19,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,1,147578,0,0,30,2], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,3,1,211527,0,0,46,3],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,1,0,0,0,0,0,0], ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3537,1,1,0,0,0,0,0,0],
["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,5,1,25423,0,0,0,10], ["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,5,1,39624,0,0,0,16],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,1,4568,0,0,6,1], ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",4654.6,1,11,1,4568,0,0,6,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,303339,0,0,64,13], ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,407448,0,0,83,18],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,179149,0,0,76,6], ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,198029,0,0,73,6],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,66,0,19506,1629,0,43,2], ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,66,0,24193,1629,0,43,2],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,67,0,135206,3222,0,46,14], ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,67,0,140802,3222,0,46,15],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,65,0,2158,3220,2146,3,1], ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,65,0,4316,3220,2146,3,1],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,52833,0,0,38,2], ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,66114,0,0,47,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,49,0,4798,9646,260,10,1], ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2644,1,49,0,4798,9646,260,10,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",190.91,1,63,0,118842,15240,280,209,6], ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",190.91,1,63,0,134122,15240,280,209,6],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,6,0,0,0,0,0,0], ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2383,1,6,0,0,0,0,0,0],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,61,0,58463,13618,706,128,7], ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,61,0,60730,13618,706,128,7],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,205534,0,0,117,6], ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,201953,0,0,114,6],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,493990,0,0,140,2], ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,1,634150,0,0,182,3],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,0,0,0,0,0], ["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1664,1,12,0,0,0,0,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,47,0,28737,2232,0,110,5], ["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,47,0,40597,2232,0,106,5],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,58,0,47100,5967,0,39,9], ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,58,0,47100,5967,0,39,9],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,59,0,76526,4878,0,22,4], ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",0,0,59,0,76526,4878,0,22,4],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null], ["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
@@ -741,20 +741,20 @@
"enemy_10028_vtswd": [3,3,0,0,0], "enemy_10028_vtswd": [3,3,0,0,0],
"enemy_10029_vtshld": [5,5,0,0,0], "enemy_10029_vtshld": [5,5,0,0,0],
"enemy_10030_vtwand": [5,5,0,0,0], "enemy_10030_vtwand": [5,5,0,0,0],
"enemy_10085_hllevi_2": [2,0,2,0,761019], "enemy_10085_hllevi_2": [2,1,0,0,1068467],
"enemy_1010_demon": [4,4,0,12402,200612], "enemy_1010_demon": [4,4,0,12402,200612],
"enemy_9032_aclionk": [1,0,0,47250,942119] "enemy_9032_aclionk": [1,0,0,47250,1027518]
}, },
"hooks": [1,65,6000,1074,1088,2031,2044,431,41,45,91,88,0,30474,660,8,20,2,1,29,0,2,0], "hooks": [1,65,6000,1150,1164,2296,2310,431,42,44,106,101,0,32028,744,8,20,0,1,30,0,2,0],
"events": {"atk":1139,"deploy":45,"die":43,"dmg":1988,"engage":15,"fx":618,"heal":7,"leak":2,"skill":179,"spawn":42,"status":942}, "events": {"atk":1232,"deploy":45,"die":44,"dmg":2254,"engage":15,"fx":761,"heal":7,"skill":207,"spawn":42,"status":1102},
"fx": { "fx": {
"aoe": 30, "aoe": 44,
"appear": 4, "appear": 6,
"blink": 2, "blink": 2,
"burst": 2, "burst": 2,
"crit": 81, "crit": 109,
"dilemma": 6, "dilemma": 6,
"disappear": 4, "disappear": 6,
"displace": 19, "displace": 19,
"equip": 18, "equip": 18,
"explosion": 4, "explosion": 4,
@@ -763,26 +763,26 @@
"iceSpike": 33, "iceSpike": 33,
"overclock": 1, "overclock": 1,
"pull": 4, "pull": 4,
"quadShot": 2, "quadShot": 3,
"revive": 2, "revive": 2,
"ripple": 18, "ripple": 25,
"sentry": 4, "sentry": 4,
"shadowWeave": 4, "shadowWeave": 5,
"shield": 1, "shield": 1,
"soul": 2, "soul": 2,
"sp": 13, "sp": 19,
"stealth": 6, "stealth": 6,
"sword": 1, "sword": 1,
"swordRain": 25, "swordRain": 26,
"swordStorm": 7, "swordStorm": 7,
"telegraph": 19, "telegraph": 21,
"tornado": 5, "tornado": 5,
"tornadoPulse": 20, "tornadoPulse": 20,
"vest": 5, "vest": 5,
"weightless": 6, "weightless": 6,
"zone": 260 "zone": 339
}, },
"snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 e81f42d7 7687b463 1723e66d 7f419938 8e9ddd0f 9f00d62e 2aa9f2dd bca0bec4 7ba9a9bc a548f671 7a235ae3 7fc35fab 2bf5becc eb0dbe2a ee2661d9" "snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 e81f42d7 7687b463 1723e66d 7f419938 8e9ddd0f 9f00d62e 2aa9f2dd bca0bec4 90e786de 4f33ccda b4b8971e b9cab80b b4845fa4 1fdd7096 42eef665"
}, },
"boss-boss_6-solo": { "boss-boss_6-solo": {
"about": "阿利斯泰尔,帝国余晖 solo pool 3980000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "阿利斯泰尔,帝国余晖 solo pool 3980000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -796,23 +796,23 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 5, "killed": 8,
"total": 4, "total": 4,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 3874806, "bossHpLeft": 3907393,
"rngDraws": 45, "rngDraws": 54,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":5,"total":4,"perfect":false,"leaks":5,"leaksUncounted":0,"coins":0,"dmg":152625,"bossDmg":105194,"heal":1783,"deaths":16,"layerGains":{}}}, "players": {"p1":{"killed":8,"total":4,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":164010,"bossDmg":72607,"heal":1088,"deaths":18,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,10,2,8,0,0,8,0], ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,10,2,10,0,0,10,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,11,0,6,0,0,6,1], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,11,1,8,0,0,8,1],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,12,1,8984,35,35,15,2], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,12,2,8987,35,35,17,2],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,48,0,1033,8472,0,40,3], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,48,1,790,10590,0,30,2],
["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,46,0,52228,18906,1436,38,4], ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,46,1,87664,18412,943,31,4],
["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,2,88481,0,0,50,7], ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,1,59955,0,0,53,7],
["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",2772,1,8,0,0,0,0,0,0], ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",2772,1,8,0,0,0,0,0,0],
["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,49,0,1884,8712,0,10,3], ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",0,0,43,0,6596,8712,0,7,2],
["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,44,0,0,6995,311,0,0], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,49,0,0,8465,110,0,0],
["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,9,0,0,0,0,0,0], ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,9,0,0,0,0,0,0],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
@@ -822,18 +822,18 @@
["-","~6","device","trap_1105_accrate",null,null,100,1,6,0,0,0,0,0,null] ["-","~6","device","trap_1105_accrate",null,null,100,1,6,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_10028_vtswd": [4,4,0,0,0], "enemy_10028_vtswd": [3,3,0,0,0],
"enemy_10029_vtshld": [3,3,0,0,0], "enemy_10029_vtshld": [4,4,0,0,0],
"enemy_10030_vtwand": [1,1,0,0,0], "enemy_10030_vtwand": [2,2,0,0,0],
"enemy_10085_hllevi_2": [2,0,2,0,29468], "enemy_10085_hllevi_2": [2,0,2,0,73421],
"enemy_1197_sfshu": [2,2,0,17,17916], "enemy_1197_sfshu": [2,2,0,383,17916],
"enemy_1198_msfshu": [6,3,3,0,47], "enemy_1198_msfshu": [6,6,0,0,66],
"enemy_9032_aclionk": [1,0,0,43085,105194] "enemy_9032_aclionk": [1,0,0,45814,72607]
}, },
"hooks": [1,49,6000,179,179,275,472,1334,29,34,20,20,0,6463,12,1,19,5,1,21,0,0,3], "hooks": [1,50,6000,169,169,290,488,922,35,37,18,18,0,5924,10,2,20,2,1,27,0,0,2],
"events": {"atk":205,"deploy":24,"die":29,"dmg":201,"engage":7,"fx":91,"leak":5,"skill":42,"spawn":35,"status":73}, "events": {"atk":212,"deploy":24,"die":35,"dmg":238,"engage":7,"fx":94,"leak":2,"skill":38,"spawn":36,"status":77},
"fx": {"dodge":3,"equip":9,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":8,"riposte":3,"rockslide":1,"sandChains":3,"slash":29,"summon":2,"sword":3,"telegraph":10,"truesilver":4,"vest":3}, "fx": {"appear":5,"disappear":5,"dodge":2,"equip":11,"featherArrow":7,"flame":2,"garrisonGrant":1,"jungleSoul":2,"mote":6,"riposte":1,"rockslide":1,"sandChains":2,"slash":22,"spGift":1,"summon":2,"sword":3,"telegraph":13,"truesilver":4,"vest":4},
"snaps": "3e08ed3d b0f5c6bf 12616b1d 9cce9eed afcfc076 fb56bae7 617f9500 b046bb87 398d9618 80f1089f 99802228 823c06f9 a478439e 71b3d152 452c5be2 683a7bbf 1f49e639 2d0e0575 72ea261c 8fc27088" "snaps": "3e08ed3d b0f5c6bf 12616b1d 26cd4bb1 3ac9a7a9 dd6d6849 99b9989f 8d641dd4 9437b015 26f59cb1 cd9f4a7b d484bda0 a0fbf26e bb4ffa9d 8bd4554c d61db91c 9810e989 2fda8eab d003c4fc bad20758"
}, },
"boss-boss_7-pair": { "boss-boss_7-pair": {
"about": "“萨米的意志” pair pool 4000000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b", "about": "“萨米的意志” pair pool 4000000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b",
@@ -964,7 +964,7 @@
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":16,"total":14,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2677042,"bossDmg":1550396,"heal":58431,"deaths":17,"layerGains":{}}, "p1": {"killed":16,"total":14,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2677042,"bossDmg":1550396,"heal":58431,"deaths":17,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":560253,"bossDmg":262771,"heal":20725,"deaths":25,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":560253,"bossDmg":262771,"heal":20725,"deaths":25,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",7814,1,90,1,335665,24097,655,20,3], ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",7814,1,90,1,335665,24097,655,20,3],
@@ -991,7 +991,7 @@
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null] ["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_10027_vtsk": [6,4,1,137764,1019681], "enemy_10027_vtsk": [6,4,0,137764,1019681],
"enemy_1005_yokai": [4,4,0,0,10699], "enemy_1005_yokai": [4,4,0,0,10699],
"enemy_1229_darmy_2": [8,8,0,6104,294231], "enemy_1229_darmy_2": [8,8,0,6104,294231],
"enemy_9013_acstmk_2": [1,0,0,63941,1814230], "enemy_9013_acstmk_2": [1,0,0,63941,1814230],
@@ -999,8 +999,8 @@
"enemy_9015_acstmb": [3,2,0,9835,83569], "enemy_9015_acstmb": [3,2,0,9835,83569],
"enemy_9016_acstmr": [8,7,0,0,150] "enemy_9016_acstmr": [8,7,0,0,150]
}, },
"hooks": [1,92,6000,904,910,2960,3146,8374,72,75,107,101,6,25061,1027,17,36,1,1,61,0,5,9], "hooks": [1,92,6000,904,910,2960,3146,8374,72,74,107,101,6,25061,1027,17,36,0,1,61,0,5,9],
"events": {"atk":1083,"deploy":56,"die":74,"dmg":3143,"engage":18,"fx":651,"heal":62,"leak":1,"skill":208,"spawn":58,"status":990}, "events": {"atk":1083,"deploy":56,"die":74,"dmg":3143,"engage":18,"fx":652,"heal":62,"skill":208,"spawn":58,"status":990},
"fx": { "fx": {
"aoe": 42, "aoe": 42,
"appear": 7, "appear": 7,
@@ -1011,7 +1011,7 @@
"counter": 6, "counter": 6,
"crit": 59, "crit": 59,
"dilemma": 3, "dilemma": 3,
"disappear": 7, "disappear": 8,
"displace": 1, "displace": 1,
"dodge": 9, "dodge": 9,
"explode": 10, "explode": 10,
@@ -1044,7 +1044,7 @@
"weightless": 3, "weightless": 3,
"zone": 180 "zone": 180
}, },
"snaps": "0499777c 2a8a8958 27548bdc 4d50b14c 2d952e29 e8bac176 f62bddd6 c54603c8 eec4acbb 0e35cf5c f32cd036 b1a89b99 601ad31e 90860daf 260abb25 982d94cf 1e000364 bf1ae288 42ae578f 90a8c025" "snaps": "0499777c 2a8a8958 27548bdc 4d50b14c 2d952e29 e8bac176 f62bddd6 c54603c8 eec4acbb 0e35cf5c f32cd036 b1a89b99 601ad31e 90860daf 260abb25 982d94cf 1e000364 bf1ae288 42ae578f 9af424ea"
}, },
"hidden-boss_8-solo": { "hidden-boss_8-solo": {
"about": "假想敌:胄 solo pool 7200000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b", "about": "假想敌:胄 solo pool 7200000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -1058,43 +1058,43 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 3, "killed": 6,
"total": 6, "total": 6,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 6797394, "bossHpLeft": 7007993,
"rngDraws": 227, "rngDraws": 122,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":6,"perfect":false,"leaks":10,"leaksUncounted":0,"coins":0,"dmg":450453,"bossDmg":402606,"heal":28293,"deaths":23,"layerGains":{}}}, "players": {"p1":{"killed":6,"total":6,"perfect":false,"leaks":12,"leaksUncounted":0,"coins":0,"dmg":236889,"bossDmg":192007,"heal":29302,"deaths":24,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,6,0,264,7875,0,4,0], ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,6,0,264,7875,0,4,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,32,1,13954,18678,6470,10,5], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,35,1,12200,18678,6470,12,5],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,7,0,0,2811,0,0,0], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,7,0,10,2811,0,8,1],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,53,0,0,10739,0,0,0], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,59,2,97,11660,0,13,1],
["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2569,1,11,2,397515,0,140,154,3], ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,11,3,187439,3191,140,63,1],
["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,29,0,5558,2838,0,6,0], ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,29,0,3718,2838,0,6,0],
["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",0,0,55,0,0,8993,10992,37,6], ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",0,0,60,0,0,8933,12476,37,6],
["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",0,0,13,0,33161,6074,939,21,1], ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",0,0,13,0,33161,6074,939,21,1],
["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,52,0,0,12130,3641,0,1], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,57,0,0,11657,3168,0,1],
["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,50,0,0,5960,6111,15,1], ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,55,0,0,5960,6111,15,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null] ["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_10027_vtsk": [3,0,3,29903,18989], "enemy_10027_vtsk": [3,0,3,34423,16521],
"enemy_1005_yokai": [4,2,2,0,2867], "enemy_1005_yokai": [4,1,2,0,1433],
"enemy_1197_sfshu": [3,1,2,27,17625], "enemy_1197_sfshu": [3,3,0,649,26874],
"enemy_1198_msfshu": [3,0,3,0,7], "enemy_1198_msfshu": [9,2,7,0,43],
"enemy_9013_acstmk_2": [1,0,0,37240,402606], "enemy_9013_acstmk_2": [1,0,0,36456,192007],
"enemy_9014_acstma": [3,2,0,0,7972], "enemy_9014_acstma": [4,3,0,0,0],
"enemy_9015_acstmb": [4,3,0,6587,340], "enemy_9015_acstmb": [4,3,0,5808,0],
"enemy_9016_acstmr": [7,6,0,0,47] "enemy_9016_acstmr": [7,6,0,0,11]
}, },
"hooks": [1,56,6000,303,303,564,576,1204,37,47,17,17,0,5895,50,3,28,10,1,29,0,0,0], "hooks": [1,63,6000,241,241,391,386,909,42,54,17,18,0,3782,53,7,35,12,1,36,0,0,0],
"events": {"atk":360,"deploy":24,"die":37,"dmg":569,"engage":5,"fx":188,"heal":76,"leak":10,"skill":35,"spawn":42,"status":149}, "events": {"atk":287,"deploy":24,"die":42,"dmg":379,"engage":7,"fx":174,"heal":78,"leak":12,"skill":36,"spawn":49,"status":159},
"fx": {"bandage":1,"beam":34,"blink":11,"block":6,"charge":6,"explode":11,"garrisonGrant":1,"healField":6,"mote":4,"redistribute":5,"reweave":1,"rockslide":5,"sandChains":1,"shell":7,"shieldBreak":7,"slash":58,"spGift":2,"summon":5,"telegraph":3,"thorns":11,"truesilver":3}, "fx": {"appear":11,"bandage":1,"beam":32,"blink":12,"block":6,"charge":6,"disappear":11,"explode":12,"flame":1,"garrisonGrant":1,"healField":6,"jungleSoul":1,"mote":4,"redistribute":5,"reweave":1,"rockslide":5,"sandChains":1,"shell":7,"shieldBreak":7,"slash":20,"spGift":2,"summon":7,"telegraph":3,"thorns":11,"truesilver":1},
"snaps": "9b5e58cf c4e06fea a40166a3 0e30f917 42214d72 ebc20f66 7258ad62 49f2a70f 0b93ecfb 8a8ebc4e 4bf3c623 15ee0c07 41d58e70 7b48444f 6d3425f8 2d1f93c0 a755c2ad 76e23872 1016f3ce e5423b09" "snaps": "9b5e58cf c4e06fea a40166a3 0e30f917 34fdd11e 47c00c11 fcd7b05e 1cc31311 8cab7ffd b87cca35 51d24d5e 732c5c43 e37369d0 c87fb776 672f8ced c7d655a3 119d0939 7599310d 196c7089 f76ddd4f"
}, },
"hidden-boss_9-pair": { "hidden-boss_9-pair": {
"about": "假想敌:铳 pair pool 5600000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b", "about": "假想敌:铳 pair pool 5600000: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b token_10017_skadi2_dedant token_10030_mlyss_wtrman | chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_6_17_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b",
@@ -1111,106 +1111,106 @@
"killed": 8, "killed": 8,
"total": 8, "total": 8,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 3570058, "bossHpLeft": 3747749,
"rngDraws": 1579, "rngDraws": 1619,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": { "players": {
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1989162,"bossDmg":803960,"heal":23383,"deaths":4,"layerGains":{}}, "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1664353,"bossDmg":722232,"heal":20932,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3240431,"bossDmg":1176195,"heal":126751,"deaths":10,"layerGains":{}} "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3117516,"bossDmg":1090251,"heal":121649,"deaths":12,"layerGains":{}}
}, },
"units": [ "units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",5343.08,1,15,0,1906,10482,12401,28,3], ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",5902.25,1,15,0,2317,10287,12324,32,3],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2910.14,1,11,0,19692,205,0,21,1], ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2893.32,1,11,0,20047,281,0,22,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1761.93,1,17,0,50071,1601,0,31,2], ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1834.75,1,17,0,42326,1612,0,24,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",0,0,7,2,1548110,3561,0,55,8], ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",430.19,1,7,1,1229734,2143,0,58,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1074.65,1,9,0,33675,10123,0,46,2], ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1329.31,1,9,0,34472,14154,0,46,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",463.12,1,55,0,25244,10860,954,0,4], ["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",0,0,66,0,35830,9923,233,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,1,90440,0,0,0,11], ["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,0,69036,0,0,0,11],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,1,42621,0,7847,77,2], ["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,26444,0,7415,77,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,144190,13392,0,29,29], ["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,174620,13925,0,30,30],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,63,1,24647,9390,349,66,2], ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,60,0,22306,9359,349,73,2],
["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,28,0,0,0,0,0,null], ["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,28,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,24,0,0,1640,318,4,1], ["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,24,0,0,1640,318,4,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",0,0,62,1,459553,38345,0,197,3], ["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",2052.07,1,68,4,561677,30058,0,292,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,60,0,497307,16632,0,66,5], ["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,59,0,341242,18065,0,56,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,66,0,13781,7737,2820,42,2], ["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,65,0,3198,6795,1515,24,1],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",1302.59,1,61,0,512511,25088,0,68,3], ["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",0,0,55,1,514814,17168,0,62,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",765.29,1,8,0,477111,13038,0,78,3], ["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",942.17,1,8,0,674333,8720,0,73,3],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2215,1,14,0,969378,5435,3408,291,6], ["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",0,0,14,0,769354,8634,2644,263,5],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,24080,78,1], ["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,15762,80,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,10,0,98857,0,0,131,4], ["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",0,0,10,0,97174,1439,0,119,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1695.1,1,59,1,183585,13999,0,2,5], ["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",0,0,62,0,132239,19665,0,24,3],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1592.1,1,25,0,0,14000,96443,80,2], ["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1183.78,1,25,0,0,15377,101728,82,2],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null], ["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null],
["-","~5","device","trap_1105_accrate",null,null,100,1,5,0,0,0,0,0,null], ["-","~5","device","trap_1105_accrate",null,null,100,1,5,0,0,0,0,0,null],
["-","~6","device","trap_1105_accrate",null,null,100,1,6,0,0,0,0,0,null], ["-","~6","device","trap_1105_accrate",null,null,100,1,6,0,0,0,0,0,null],
["p2","~1","token","token_10019_nearl2_sword",null,null,2280.4,0,47,0,6744,6000,0,5,null], ["p2","~1","token","token_10019_nearl2_sword",null,null,2916.93,0,47,0,6778,5363,0,21,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,1322,1,48,0,1766,2370,1514,30,1], ["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,48,1,1801,1538,0,22,1],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1102.65,0,49,0,595,675,0,11,null], ["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,207,3142,0,9,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,51,0,661,1322,0,10,null], ["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,50,0,463,1322,0,7,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,53,0,794,1322,0,12,null], ["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,52,0,4750,270,0,10,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,54,0,4750,270,0,10,null], ["p1","~1","token","token_10017_skadi2_dedant",null,null,138,0,57,0,0,0,0,0,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,138,0,56,0,0,0,0,0,null], ["p2","~2","token","token_10019_nearl2_sword",null,null,6950.53,0,58,0,0,963,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,0,0,57,0,21605,6600,0,12,null], ["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,61,0,0,385,293,0,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,64,0,0,0,0,0,null], ["p1","~2","token","token_10040_siege2_vlion",null,null,3897.9,0,63,0,0,137,0,0,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,3926.59,0,65,0,0,97,0,0,null], ["p2","~3","token","token_10019_nearl2_sword",null,null,133.02,0,64,0,16707,6165,0,9,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,67,0,0,0,0,0,null] ["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,67,0,0,0,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_1267_nhpbr_2": [6,6,0,5992,141894], "enemy_1267_nhpbr_2": [6,6,0,1400,141894],
"enemy_9007_acelem": [2,2,0,22960,309587], "enemy_9007_acelem": [2,2,0,17143,309587],
"enemy_9017_achunt_2": [2,0,0,57558,2029942], "enemy_9017_achunt_2": [2,0,0,58691,1852251],
"enemy_9018_actrpa": [1,0,0,42972,765851], "enemy_9018_actrpa": [1,0,0,44364,722451],
"enemy_9019_actrpb": [1,0,0,26761,756394], "enemy_9019_actrpb": [1,0,0,28255,660498],
"enemy_9020_actrpc": [1,0,0,18223,1328263] "enemy_9020_actrpc": [1,0,0,23012,1168947]
}, },
"hooks": [1,65,6000,1653,1653,7414,13693,3721,26,37,100,94,326,46845,2814,18,13,0,1,27,0,18,0], "hooks": [1,66,6000,1682,1682,6586,12669,2825,29,42,95,91,286,44763,2551,19,13,0,1,30,0,16,0],
"events": {"atk":2008,"deploy":46,"die":37,"dmg":13203,"engage":24,"fx":2168,"heal":883,"skill":194,"spawn":52,"status":833}, "events": {"atk":2097,"deploy":47,"die":42,"dmg":12154,"engage":23,"fx":2024,"heal":885,"skill":186,"spawn":52,"status":769},
"fx": { "fx": {
"airstrike": 117, "airstrike": 88,
"beam": 31, "beam": 27,
"blessing": 1, "blessing": 1,
"blink": 4, "blink": 5,
"bombard": 68, "bombard": 72,
"bombardShell": 68, "bombardShell": 72,
"burst": 18, "burst": 16,
"camouflage": 1, "camouflage": 1,
"copy": 2, "copy": 3,
"dash": 24, "dash": 24,
"displace": 24, "displace": 20,
"dp": 2, "dp": 2,
"drone": 275, "drone": 190,
"droneLock": 15, "droneLock": 9,
"drones": 5, "drones": 3,
"extraAttack": 12, "extraAttack": 7,
"finale": 5, "finale": 4,
"firewall": 3, "firewall": 3,
"garrisonGrant": 1, "garrisonGrant": 1,
"lightning": 119, "lightning": 117,
"link": 1141, "link": 1141,
"lock": 72, "lock": 72,
"lure": 2, "lure": 2,
"phase": 11, "phase": 11,
"radiantSword": 2, "radiantSword": 3,
"revive": 3, "revive": 3,
"scorchBurst": 3, "scorchBurst": 1,
"shieldBreak": 9, "shieldBreak": 9,
"slash": 55, "slash": 44,
"snow": 25, "snow": 26,
"split": 3, "split": 2,
"steal": 6, "steal": 5,
"storm": 11, "storm": 11,
"summon": 4, "summon": 5,
"sunBurst": 3, "sunBurst": 3,
"talent": 7, "talent": 6,
"tide": 4, "tide": 3,
"wanted": 6, "wanted": 6,
"zone": 6 "zone": 6
}, },
"snaps": "2a921421 d160972c 4e5b9396 7b13edd2 689bae2b f9a15be0 67150ecd 3a4286df 13119f08 b1b42c22 b5c0b449 f4bddab7 22e104fd fd2f838a b7964c3e 83e4b794 62994daa 16b9850d 3f3a5054 498cda94" "snaps": "2a921421 57801ce9 151fd5ae 991b7440 79722413 1faab651 85db54ec 88d6f82e 6d938e5f fc881a30 9c294f55 4373e468 af475c5f df3e9b70 b0474d4c 538602b8 4e66de79 e70dcab9 481e5d84 e8e351b1"
}, },
"hidden-boss_9-solo": { "hidden-boss_9-solo": {
"about": "假想敌:铳 solo pool 2800000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b", "about": "假想敌:铳 solo pool 2800000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -1227,37 +1227,35 @@
"killed": 3, "killed": 3,
"total": 3, "total": 3,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 1932856, "bossHpLeft": 2011732,
"rngDraws": 128, "rngDraws": 207,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":3,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2753803,"bossDmg":867144,"heal":73158,"deaths":5,"layerGains":{}}}, "players": {"p1":{"killed":3,"total":3,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2574046,"bossDmg":788268,"heal":101441,"deaths":5,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_5_08_b",2,"uniequip_002_horn",2775,1,1,1,368359,8434,0,127,4], ["p1",1,"op","chess_char_5_08_b",2,"uniequip_002_horn",2757.97,1,1,1,328544,10883,0,121,4],
["p1",2,"op","chess_char_5_11_b",1,"uniequip_002_demkni",4804.8,1,3,0,1265,3442,23540,46,35], ["p1",2,"op","chess_char_5_11_b",1,"uniequip_002_demkni",4784.8,1,3,0,2559,11172,34206,59,38],
["p1",3,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",11263.65,1,33,0,413261,29216,17173,39,5], ["p1",3,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",10680.88,1,4,0,391098,51057,37953,64,5],
["p1",4,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",3747,1,7,1,1380629,16819,16926,201,5], ["p1",4,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",1801.92,1,7,0,1462149,23079,21225,197,5],
["p1",5,"op","chess_char_5_13_b",1,"uniequip_002_ghost2",3448.92,1,9,0,128392,14229,3714,76,3], ["p1",5,"op","chess_char_5_13_b",1,"uniequip_002_ghost2",3448.92,1,19,1,146526,21512,5080,102,4],
["p1",6,"op","chess_char_5_07_b",2,"uniequip_002_surtr",7680,1,35,0,42625,19991,4759,13,3], ["p1",6,"op","chess_char_5_07_b",2,"uniequip_002_surtr",2680,1,23,0,4444,7780,113,7,1],
["p1",7,"op","chess_char_5_02_b",2,"uniequip_002_titi",1439,1,5,0,0,0,0,1,1], ["p1",7,"op","chess_char_5_02_b",2,"uniequip_002_titi",1439,1,5,0,0,0,0,0,0],
["p1",8,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",1148.18,1,6,0,207074,1471,1147,186,6], ["p1",8,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",1207.04,1,6,0,32056,265,0,25,1],
["p1",9,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,32,0,14018,13299,5899,28,6], ["p1",9,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,28,0,0,8783,2863,1,5],
["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1787,1,2,1,198179,0,0,53,41], ["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1787,1,2,1,206669,0,0,57,43]
["p1","~1","token","token_10039_ulpia_block",null,null,3290,0,20,0,0,210,0,0,null],
["p1","~2","token","token_10039_ulpia_block",null,null,3335,0,28,0,0,165,0,0,null]
], ],
"enemies": { "enemies": {
"enemy_10067_ftsjc": [1,1,0,1161,21565], "enemy_10067_ftsjc": [1,1,0,1161,21565],
"enemy_5601_entlec": [6,5,0,0,160038], "enemy_5601_entlec": [8,7,0,0,221752],
"enemy_9007_acelem": [2,2,0,632,119439], "enemy_9007_acelem": [2,2,0,1897,119439],
"enemy_9017_achunt_2": [1,0,0,32010,867144], "enemy_9017_achunt_2": [1,0,0,61863,788268],
"enemy_9018_actrpa": [1,0,0,14160,440951], "enemy_9018_actrpa": [1,0,0,22333,431138],
"enemy_9019_actrpb": [1,0,0,19148,445768], "enemy_9019_actrpb": [1,0,0,16840,403557],
"enemy_9020_actrpc": [1,0,0,13881,698896] "enemy_9020_actrpc": [1,0,0,15204,588326]
}, },
"hooks": [1,36,6000,876,876,3337,7978,7359,13,17,109,104,118,25510,1863,10,13,0,1,120,0,2,0], "hooks": [1,31,6000,758,758,3651,7974,3912,15,16,106,103,21,22194,1289,14,15,0,1,70,0,8,0],
"events": {"atk":1327,"deploy":23,"die":17,"dmg":7399,"engage":10,"fx":851,"heal":385,"skill":213,"spawn":25,"status":317}, "events": {"atk":1320,"deploy":16,"die":16,"dmg":7575,"engage":9,"fx":765,"heal":611,"skill":209,"spawn":25,"status":228},
"fx": {"anchor":5,"aoe":3,"beam":22,"burst":2,"candle":6,"charge":7,"dash":12,"downed":1,"ember":2,"explode":1,"healAoe":35,"link":597,"overload":4,"palsy":3,"phase":12,"reborn":1,"shieldBreak":10,"splash":118,"substitute":3,"swap":3,"teleport":4}, "fx": {"anchor":5,"aoe":1,"beam":30,"burst":8,"candle":8,"charge":7,"dash":12,"dollEnd":1,"ember":1,"explode":1,"healAoe":38,"link":597,"overload":4,"palsy":3,"phase":11,"reborn":1,"revive":1,"shieldBreak":9,"splash":21,"substitute":4,"swap":2},
"snaps": "9f379cb4 794e9645 d2f1f9f3 6f045130 3ba3cbda c2dffcd7 002617f9 8eb229f1 44fa10f5 02a4f054 024d05f4 4f2b850e 264e7810 e232b25c fb23ac72 ffe30a9a 966d17a8 f21eca66 c335c5c3 dadd437f" "snaps": "9f379cb4 794e9645 1eab82b0 9bd21a64 94bcd685 ea068a42 11f6e172 f1d86469 8dae333a a7b0bf9e 854de036 d7f516e9 946585b3 06953c95 f04d0312 832df1bd 4f3fdc1c eec59b6e 4c10a52c 9c930ebb"
}, },
"hidden-boss_10-pair": { "hidden-boss_10-pair": {
"about": "假想敌:管 pair pool 15200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld", "about": "假想敌:管 pair pool 15200000: chess_char_5_19_b chess_char_4_01_b chess_char_5_14_b chess_char_5_17_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b chess_char_5_22_b chess_char_5_23_b chess_char_4_02_b | chess_char_4_11_b chess_char_4_04_b chess_char_4_07_b chess_char_4_16_b chess_char_4_09_b chess_char_4_06_b chess_char_4_10_b chess_char_4_12_b chess_char_4_13_b chess_char_4_14_b token_10041_cathy_catsld token_10041_cathy_catsld",
@@ -1372,36 +1370,36 @@
"time": 200, "time": 200,
"ticks": 6000, "ticks": 6000,
"reason": "forced", "reason": "forced",
"killed": 11, "killed": 10,
"total": 5, "total": 5,
"unspawned": 0, "unspawned": 0,
"bossHpLeft": 7323878, "bossHpLeft": 7333760,
"rngDraws": 102, "rngDraws": 139,
"errors": {"count":0,"kinds":[]}, "errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":11,"total":5,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":430741,"bossDmg":276122,"heal":148595,"deaths":10,"layerGains":{}}}, "players": {"p1":{"killed":10,"total":5,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":448196,"bossDmg":266240,"heal":121244,"deaths":16,"layerGains":{}}},
"units": [ "units": [
["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3123.26,1,29,5,45,3903,0,19,0], ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,29,6,69,7026,0,24,0],
["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",1092.31,1,24,0,36,24428,13312,31,6], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,25,0,30,21486,9278,25,5],
["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,28,0,140999,24118,1514,143,3], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,26,1,135024,23127,1337,137,3],
["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",3600.6,1,31,0,13,10454,0,12,0], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,34,1,24,20162,0,21,1],
["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,22,3,176153,57886,6009,118,2], ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",3905.8,1,35,1,190029,35166,2983,92,3],
["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,30,1,87204,1419,0,56,6], ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,30,0,76234,2838,0,53,6],
["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,25,0,0,20837,92999,101,2], ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",2920.8,1,24,0,0,20473,74171,95,2],
["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,2,2,26292,3463,0,38,4], ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",343.79,1,31,1,46787,6392,468,62,4],
["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,23,0,0,33782,4919,0,1], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,23,0,0,26222,3713,0,2],
["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,3,0,0,2358,29841,56,9] ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,3,0,0,7174,29294,42,11]
], ],
"enemies": { "enemies": {
"enemy_1005_yokai_3": [1,0,1,11629,68252], "enemy_1005_yokai_3": [1,0,1,18962,95601],
"enemy_9010_acpupp": [2,2,0,19404,86026], "enemy_9010_acpupp": [2,2,0,19404,86026],
"enemy_9021_acduml_2": [1,0,0,6962,276122], "enemy_9021_acduml_2": [1,0,0,4772,266240],
"enemy_9022_acdumm": [1,0,0,3379,0], "enemy_9022_acdumm": [1,0,0,2812,0],
"enemy_9023_acdums": [10,9,0,138776,342] "enemy_9023_acdums": [10,8,0,122384,328]
}, },
"hooks": [1,33,6000,650,650,2402,2852,7246,21,22,33,33,0,9568,105,16,15,1,1,65,0,27,2], "hooks": [1,35,6000,629,629,2222,2669,4675,26,27,37,36,0,8979,144,19,15,1,1,63,0,28,5],
"events": {"atk":973,"deploy":18,"die":21,"dmg":2838,"engage":7,"fx":844,"heal":298,"leak":1,"skill":69,"spawn":25,"status":303}, "events": {"atk":938,"deploy":20,"die":26,"dmg":2659,"engage":7,"fx":895,"heal":257,"leak":1,"skill":75,"spawn":25,"status":361},
"fx": {"bandage":9,"beam":21,"block":14,"burst":27,"dodge":2,"ember":2,"explode":500,"featherArrow":6,"garrisonGrant":1,"healField":2,"jungleSoul":3,"mote":52,"phase":55,"redistribute":15,"reweave":1,"riposte":1,"rockslide":6,"sandChains":1,"sandChainsCharged":3,"shieldBreak":21,"slash":40,"spGift":2,"summon":8,"telegraph":2,"thorns":47,"truesilver":2,"undying":1}, "fx": {"appear":2,"bandage":11,"beam":21,"block":26,"burst":28,"disappear":2,"dodge":5,"ember":2,"explode":497,"featherArrow":6,"flame":1,"garrisonGrant":1,"healField":2,"jungleSoul":3,"mote":48,"phase":56,"redistribute":29,"reweave":2,"riposte":3,"rockslide":5,"sandChains":1,"sandChainsCharged":3,"shieldBreak":17,"slash":51,"spGift":1,"summon":8,"telegraph":2,"thorns":58,"truesilver":3,"undying":1},
"snaps": "65ab7720 4d0f9bc6 b175a7ad decfc6d9 d1ad8895 b6add8db 8b187de4 1339864b 84136f5a 963ae966 244b6531 f0d249a1 55dc7a49 f44509fa 5ba1fdbd c65a6ccf 00d4edb3 c6669080 45cab92a 414911cc" "snaps": "65ab7720 4d0f9bc6 b175a7ad decfc6d9 d1ad8895 b6add8db d001bf54 dc66077c e9917391 7dcdab2e 165041d1 65a94aa3 35d72c2e 6ee3e9c7 a7174d70 93c52780 189aaca7 d0965cc7 aedfc5b2 1b3f4a01"
}, },
"unite-1": { "unite-1": {
"about": "联防 1 helper(s), 11 leaked: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b token_10017_skadi2_dedant", "about": "联防 1 helper(s), 11 leaked: chess_char_6_03_b chess_char_6_06_b chess_char_6_07_b chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b chess_char_6_08_b token_10017_skadi2_dedant",
+8 -8
View File
@@ -1314,7 +1314,7 @@
"combat": "client", "combat": "client",
"boost": {"lp":400,"layers":300}, "boost": {"lp":400,"layers":300},
"setup": {"stageId":"act1autochess_m02","bossId":"boss_5","hiddenBossId":"boss_10","factions":["TIMES","ELEMENT","DOT"]}, "setup": {"stageId":"act1autochess_m02","bossId":"boss_5","hiddenBossId":"boss_10","factions":["TIMES","ELEMENT","DOT"]},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":395,"bossPoolLeft":0,"virtualMs":113700}, "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":397,"bossPoolLeft":0,"virtualMs":113700},
"rng": {"setup":25,"shop":346,"waves":0,"draft":42,"bots":10350,"meta":3}, "rng": {"setup":25,"shop":346,"waves":0,"draft":42,"bots":10350,"meta":3},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [ "rounds": [
@@ -1377,12 +1377,12 @@
"victory": true, "victory": true,
"roundsPassed": 15, "roundsPassed": 15,
"eliminatedRound": null, "eliminatedRound": null,
"lp": 395, "lp": 397,
"bandId": "band_paganini", "bandId": "band_paganini",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0, "trophies": 0,
"reward": 425, "reward": 425,
"stats": {"activatedLayers":2281,"bossDamage":4580000,"dmgDealt":6374743,"gold":175,"itemsEquipped":25,"kills":376,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":34}, "stats": {"activatedLayers":2281,"bossDamage":4580000,"dmgDealt":6374751,"gold":175,"itemsEquipped":25,"kills":376,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":34},
"lineup": ["4_02_b@12,7[3_08_e_a+1_02_e_b]","6_01_a@12,8[3_03_e_a+3_02_e_a]","6_16_a@11,5","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_a@10,4[1_05_e_b]","6_05_a@10,5","4_01_b@10,6[4_06_e_a+4_05_e_a]","1_08_b@9,9[4_10_e_a]"], "lineup": ["4_02_b@12,7[3_08_e_a+1_02_e_b]","6_01_a@12,8[3_03_e_a+3_02_e_a]","6_16_a@11,5","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_a@10,4[1_05_e_b]","6_05_a@10,5","4_01_b@10,6[4_06_e_a+4_05_e_a]","1_08_b@9,9[4_10_e_a]"],
"bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/322","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] "bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/322","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
} }
@@ -1398,7 +1398,7 @@
"combat": "client", "combat": "client",
"boost": {"lp":400,"layers":300}, "boost": {"lp":400,"layers":300},
"setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]}, "setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":800,"bossPoolLeft":0,"virtualMs":124650}, "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":698,"bossPoolLeft":0,"virtualMs":124650},
"rng": {"setup":25,"shop":750,"waves":0,"draft":61,"bots":22165,"meta":198}, "rng": {"setup":25,"shop":750,"waves":0,"draft":61,"bots":22165,"meta":198},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [ "rounds": [
@@ -1474,12 +1474,12 @@
"victory": true, "victory": true,
"roundsPassed": 15, "roundsPassed": 15,
"eliminatedRound": null, "eliminatedRound": null,
"lp": 400, "lp": 349,
"bandId": "band_sarkazb", "bandId": "band_sarkazb",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 8, "trophies": 8,
"reward": 425, "reward": 425,
"stats": {"activatedLayers":3346,"bossDamage":7735817,"dmgDealt":25492475,"gold":154,"itemsEquipped":21,"kills":285,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":21}, "stats": {"activatedLayers":3346,"bossDamage":8584552,"dmgDealt":21757593,"gold":154,"itemsEquipped":21,"kills":287,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":21},
"lineup": ["4_09_b@11,5[3_11_e_a+3_11_e_b]","6_11_a@11,8","3_13_b@10,3","3_05_b@10,8[1_01_e_a+6_09_e_a]","6_16_a@9,6","6_05_a@9,7[3_02_e_a]","4_14_b@9,8[2_01_e_a+1_02_e_b]","5_07_a@9,9[4_10_e_b]"], "lineup": ["4_09_b@11,5[3_11_e_a+3_11_e_b]","6_11_a@11,8","3_13_b@10,3","3_05_b@10,8[1_01_e_a+6_09_e_a]","6_16_a@9,6","6_05_a@9,7[3_02_e_a]","4_14_b@9,8[2_01_e_a+1_02_e_b]","5_07_a@9,9[4_10_e_b]"],
"bonds": ["arcaneShip 1/365","deputShip 0/304","egirShip 1/364","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 0/308","lateranoShip 0/300","maniShip 1/300","miraShip 0/300","preciShip 1/345","raidShip 1/405","sargonShip 1/300","siracusaShip 0/300","skillfulShip 1/338","soloShip 1/307","steadShip 0/338","suntShip 1/300","swiftShip 1/322","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] "bonds": ["arcaneShip 1/365","deputShip 0/304","egirShip 1/364","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 0/308","lateranoShip 0/300","maniShip 1/300","miraShip 0/300","preciShip 1/345","raidShip 1/405","sargonShip 1/300","siracusaShip 0/300","skillfulShip 1/338","soloShip 1/307","steadShip 0/338","suntShip 1/300","swiftShip 1/322","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
}, },
@@ -1488,12 +1488,12 @@
"victory": true, "victory": true,
"roundsPassed": 15, "roundsPassed": 15,
"eliminatedRound": null, "eliminatedRound": null,
"lp": 400, "lp": 349,
"bandId": "band_doberm", "bandId": "band_doberm",
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 8, "trophies": 8,
"reward": 425, "reward": 425,
"stats": {"activatedLayers":2801,"bossDamage":6160343,"dmgDealt":21332968,"gold":191,"itemsEquipped":32,"kills":287,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":55}, "stats": {"activatedLayers":2801,"bossDamage":5304736,"dmgDealt":21479720,"gold":191,"itemsEquipped":32,"kills":287,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":55},
"lineup": ["4_10_a@11,5[3_05_e_a+4_03_e_a]","6_05_a@11,8[4_08_e_a+6_09_e_a]","6_16_a@10,3[3_11_e_a+1_05_e_b]","2_13_a@10,7[4_04_e_a+4_05_e_a]","1_08_b@10,8[4_07_e_b+5_07_e_a]","6_08_a@9,6[2_01_e_b+3_01_e_b]","5_20_a@9,7[1_05_e_a+3_04_e_a]","6_08_a@9,8[3_03_e_a+1_01_e_a]","3_19_b@9,9[2_04_e_a+3_07_e_b]"], "lineup": ["4_10_a@11,5[3_05_e_a+4_03_e_a]","6_05_a@11,8[4_08_e_a+6_09_e_a]","6_16_a@10,3[3_11_e_a+1_05_e_b]","2_13_a@10,7[4_04_e_a+4_05_e_a]","1_08_b@10,8[4_07_e_b+5_07_e_a]","6_08_a@9,6[2_01_e_b+3_01_e_b]","5_20_a@9,7[1_05_e_a+3_04_e_a]","6_08_a@9,8[3_03_e_a+1_01_e_a]","3_19_b@9,9[2_04_e_a+3_07_e_b]"],
"bonds": ["arcaneShip 1/371","deputShip 0/332","egirShip 0/300","emptyShip 0/308","indomShip 0/300","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 1/334","preciShip 1/311","raidShip 0/300","sargonShip 0/300","siracusaShip 1/465","skillfulShip 1/356","soloShip 1/322","steadShip 0/312","suntShip 1/308","swiftShip 1/334","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] "bonds": ["arcaneShip 1/371","deputShip 0/332","egirShip 0/300","emptyShip 0/308","indomShip 0/300","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 1/334","preciShip 1/311","raidShip 0/300","sargonShip 0/300","siracusaShip 1/465","skillfulShip 1/356","soloShip 1/322","steadShip 0/312","suntShip 1/308","swiftShip 1/334","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
} }
+3 -1
View File
@@ -247,7 +247,9 @@ test('E2 audit: healers on the lane tiles of every boss / hidden field deal no d
const lead = (e) => e.isBoss || e.tag === 'boss' || e.tag === 'part' || LUCIEN.test(e.defId); const lead = (e) => e.isBoss || e.tag === 'boss' || e.tag === 'part' || LUCIEN.test(e.defId);
let healsOnLead = 0; let healsOnLead = 0;
h.b.on('heal', (c) => { if (healers.includes(c.source) && c.amount > 0 && c.source.blocking.some(lead)) healsOnLead++; }); h.b.on('heal', (c) => { if (healers.includes(c.source) && c.amount > 0 && c.source.blocking.some(lead)) healsOnLead++; });
for (let i = 0; i < 120 * 30; i++) { // 200 s on the fields whose leaders / parts a healer must block (the solo 铳 field's springs first meet a healer at
// 145 s since their 末日布道 chase lasts its whole 5 s — PR #347), 120 s elsewhere
for (let i = 0; i < (EXPECT[key] ? 200 : 120) * 30; i++) {
h.step(); h.step();
// operators break the 碎铳之簧 shields (unblockable while shielded): a hit of the kind each shield yields to // operators break the 碎铳之簧 shields (unblockable while shielded): a hit of the kind each shield yields to
for (const e of h.b.enemies) { for (const e of h.b.enemies) {
+32 -11
View File
@@ -1,7 +1,8 @@
// Blockable-ground preference of the ground pathing (grid.js header; user playtest #2: on 战场#01 the lower-gate enemies // Blockable-ground preference of the ground pathing (grid.js header; user playtest #2: on 战场#01 the lower-gate enemies
// walked up the col-9 floor lane, where no operator can stand, instead of the col-8 road). The flow field keeps the // walked up the col-9 floor lane, where no operator can stand, instead of the col-8 road). The flow field keeps the
// official route unless the preference route — the fewest non-blockable tiles among equal-length chains, smoothing // official route unless the preference route — the fewest non-blockable tiles among equal-length chains, smoothing
// that never cuts across floor its grid route does not walk — crosses fewer non-blockable tiles. "Crosses" = the // that never cuts across floor its grid route does not walk — crosses fewer non-blockable tiles and no more 深水区
// (GitHub #375). "Crosses" = the
// segment passes through the tile's interior (grid.js segmentTiles; brushing a corner is not crossing — the D5 report // segment passes through the tile's interior (grid.js segmentTiles; brushing a corner is not crossing — the D5 report
// after 0.1.0, test/sim/pathing-official.test.js). Audit: every active stage × gate × field (normal, 联防 both halves, // after 0.1.0, test/sim/pathing-official.test.js). Audit: every active stage × gate × field (normal, 联防 both halves,
// boss both halves incl. the solo `_s` templates) — the non-blockable tiles an enemy still crosses are listed and // boss both halves incl. the solo `_s` templates) — the non-blockable tiles an enemy still crosses are listed and
@@ -69,27 +70,36 @@ function nbCrossed(g, s, e) {
/** /**
* Fewest non-blockable tiles (start and goal excluded) over all 4-connected crate-free routes s → e of length ≤ the * Fewest non-blockable tiles (start and goal excluded) over all 4-connected crate-free routes s → e of length ≤ the
* shortest + `slack`; { best, min } (min = the minimum over those lengths). * shortest + `slack` that cross at most `maxWet` 深水区 tiles (grid.js: the tie-break never walks into more 深水区 than
* the official route — GitHub #375); { best, min } (min = the minimum over those lengths).
*/ */
function fewestNb(g, s, e, slack = 2) { function fewestNb(g, s, e, slack = 2, maxWet = Infinity) {
const f = g.flowField(e[0], e[1]); const f = g.flowField(e[0], e[1]);
const best = f.dist[s[0] * COLS + s[1]]; const best = f.dist[s[0] * COLS + s[1]];
if (best < 0 || best >= 1000) return null; if (best < 0 || best >= 1000) return null;
const ek = e[0] * COLS + e[1]; const ek = e[0] * COLS + e[1];
const cost = (k) => (k === ek ? 0 : g.unblockable[k]); const cost = (k) => (k === ek ? 0 : g.unblockable[k]);
let cur = new Map([[s[0] * COLS + s[1], 0]]); const wetOf = Number.isFinite(maxWet) ? (k) => (k === ek ? 0 : g.deepWater[k]) : () => 0;
// tile → (深水区 so far → fewest non-blockable so far)
let cur = new Map([[s[0] * COLS + s[1], new Map([[0, 0]])]]);
let min = Infinity; let min = Infinity;
for (let L = 0; L <= best + slack; L++) { for (let L = 0; L <= best + slack; L++) {
if (cur.has(ek)) min = Math.min(min, cur.get(ek)); if (cur.has(ek)) for (const p of cur.get(ek).values()) min = Math.min(min, p);
const nx = new Map(); const nx = new Map();
for (const [k, p] of cur) { for (const [k, byWet] of cur) {
if (k === ek) continue; if (k === ek) continue;
const r = (k / COLS) | 0, c = k % COLS; const r = (k / COLS) | 0, c = k % COLS;
for (const [dr, dc] of [[1, 0], [0, 1], [-1, 0], [0, -1]]) { for (const [dr, dc] of [[1, 0], [0, 1], [-1, 0], [0, -1]]) {
const nr = r + dr, nc = c + dc; const nr = r + dr, nc = c + dc;
if (!g.walkable(nr, nc) || g.isCrate(nr, nc)) continue; if (!g.walkable(nr, nc) || g.isCrate(nr, nc)) continue;
const nk = nr * COLS + nc, np = p + cost(nk); const nk = nr * COLS + nc;
if (!nx.has(nk) || nx.get(nk) > np) nx.set(nk, np); let m = nx.get(nk);
for (const [w, p] of byWet) {
const nw = w + wetOf(nk), np = p + cost(nk);
if (nw > maxWet) continue;
if (!m) nx.set(nk, m = new Map());
if (!m.has(nw) || m.get(nw) > np) m.set(nw, np);
}
} }
} }
cur = nx; cur = nx;
@@ -97,6 +107,14 @@ function fewestNb(g, s, e, slack = 2) {
return { best, min }; return { best, min };
} }
/** 深水区 tiles crossed from s to e (start and goal excluded). */
function wetCrossed(g, s, e) {
const wp = g.waypoints(s[0], s[1], e[0], e[1]);
const seen = new Set();
for (const [r, c] of crossed(wp)) if (!(r === s[0] && c === s[1]) && !(r === e[0] && c === e[1]) && g.deepWater[r * COLS + c]) seen.add(`${r},${c}`);
return seen.size;
}
/** Tiles an enemy of `route` visits (rounded position per tick) and whether `u` blocked it. */ /** Tiles an enemy of `route` visits (rounded position per tick) and whether `u` blocked it. */
function walk(h) { function walk(h) {
const seen = []; const seen = [];
@@ -293,12 +311,15 @@ test('audit: every WALK leg of every wave template (normal / 联防 / boss / hid
if (!grids.has(key)) grids.set(key, stageGrid(sid, rect)); if (!grids.has(key)) grids.set(key, stageGrid(sid, rect));
const g = grids.get(key); const g = grids.get(key);
if (!g.walkable(from[0], from[1]) || !g.walkable(to[0], to[1])) continue; if (!g.walkable(from[0], from[1]) || !g.walkable(to[0], to[1])) continue;
const eq = fewestNb(g, from, to, 0); if (!fewestNb(g, from, to, 0)) continue;
if (!eq) continue;
const got = nbCrossed(g, from, to); const got = nbCrossed(g, from, to);
// compared with the routes that wade through no more 深水区 than ours (#375: 战场#08(下半)'s patrol legs to (2,3) /
// (2,17) keep the official upper road, not the lower one through two water tiles that crosses one floor tile less)
const wet = wetCrossed(g, from, to);
const eq = fewestNb(g, from, to, 0, wet);
const what = `${sid} ${id} (${from}) → (${to}) crosses ${got.join(' ')}`; const what = `${sid} ${id} (${from}) → (${to}) crosses ${got.join(' ')}`;
assert.ok(got.length <= eq.min, `${what}, an equal-length route only ${eq.min}`); assert.ok(got.length <= eq.min, `${what}, an equal-length route only ${eq.min}`);
const near = fewestNb(g, from, to, 2); const near = fewestNb(g, from, to, 2, wet);
if (near.min < got.length) { if (near.min < got.length) {
assert.ok(rect === GEO.BOSS_RECT && got.every(arena), `${what}: a route of length ≤ ${near.best}+2 crosses only ${near.min}`); assert.ok(rect === GEO.BOSS_RECT && got.every(arena), `${what}: a route of length ≤ ${near.best}+2 crosses only ${near.min}`);
accepted.push(what); accepted.push(what);
+11 -3
View File
@@ -5,8 +5,9 @@
// preference field = 0.1.0's — equal-length ties to the chain with the fewest non-blockable tiles, a line of sight that // preference field = 0.1.0's — equal-length ties to the chain with the fewest non-blockable tiles, a line of sight that
// covers non-blockable tiles, diagonal-step corners included, only on the own raw chain — whose pointer replaces the // covers non-blockable tiles, diagonal-step corners included, only on the own raw chain — whose pointer replaces the
// official one where its route crosses fewer non-blockable tiles, or as few while only skipping the official waypoint // official one where its route crosses fewer non-blockable tiles, or as few while only skipping the official waypoint
// (on the straight line to it, leading there); "crosses" = positive-length intersection with the tile, found here by // (on the straight line to it, leading there), and no more 深水区 than the official route (GitHub #375); "crosses" =
// clipping the segment against each tile, independently of grid.js crossTiles): // positive-length intersection with the tile, found here by clipping the segment against each tile, independently of
// grid.js crossTiles):
// * the 8 active stages with their match-start devices: the smoothed chain from EVERY walkable tile of the normal, // * the 8 active stages with their match-start devices: the smoothed chain from EVERY walkable tile of the normal,
// 联防 and boss rects to every goal equals the full-map reference one (the sim's rect limit changes nothing); // 联防 and boss rects to every goal equals the full-map reference one (the sim's rect limit changes nothing);
// * 400 random crate / block layouts: identical chains to the same algorithm limited to the rect; // * 400 random crate / block layouts: identical chains to the same algorithm limited to the rect;
@@ -56,6 +57,8 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec
if (t.tileKey === 'tile_deepsea') return true; if (t.tileKey === 'tile_deepsea') return true;
return !(t.heightType === 'LOWLAND' && (t.buildableType === 'ALL' || t.buildableType === 'MELEE')); return !(t.heightType === 'LOWLAND' && (t.buildableType === 'ALL' || t.buildableType === 'MELEE'));
}; };
/** 深水区 — the preference may not cross more of it than the official route (#375) */
const deep = (r, c) => ['tile_deepsea', 'tile_deepwater'].includes(tile(r, c).tileKey);
const bres = ([r0, c0], [r1, c1], clear) => { const bres = ([r0, c0], [r1, c1], clear) => {
const dr = Math.abs(r1 - r0), dc = Math.abs(c1 - c0), sr = r1 > r0 ? 1 : -1, sc = c1 > c0 ? 1 : -1; const dr = Math.abs(r1 - r0), dc = Math.abs(c1 - c0), sr = r1 > r0 ? 1 : -1, sc = c1 > c0 ? 1 : -1;
let err = dc - dr, r = r0, c = c0; let err = dc - dr, r = r0, c = c0;
@@ -140,12 +143,15 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec
const off = spfa(false); const off = spfa(false);
const dist = off.dist; const dist = off.dist;
const segNb = (a, b) => through(a, b).filter(([y, x]) => !(y === a[0] && x === a[1]) && nb(y, x)).length; const segNb = (a, b) => through(a, b).filter(([y, x]) => !(y === a[0] && x === a[1]) && nb(y, x)).length;
const segWet = (a, b) => through(a, b).filter(([y, x]) => !(y === a[0] && x === a[1]) && deep(y, x)).length;
let nxt = smooth(off, false); let nxt = smooth(off, false);
if (prefer) { if (prefer) {
// per tile in increasing distance: the preference pointer when its route crosses fewer non-blockable tiles (or as // per tile in increasing distance: the preference pointer when its route crosses fewer non-blockable tiles (or as
// few while it only skips the official waypoint: o strictly inside the segment a → p, and o's chosen pointer is p) // few while it only skips the official waypoint: o strictly inside the segment a → p, and o's chosen pointer is p)
// and no more 深水区 than the official pointer's route
const nxtP = smooth(spfa(true), true); const nxtP = smooth(spfa(true), true);
const cost = new Map([[D, 0]]); const cost = new Map([[D, 0]]);
const wet = new Map([[D, 0]]);
const chosen = new Map(); const chosen = new Map();
const skips = (a, o, p) => { const skips = (a, o, p) => {
const t = through(a, p); const t = through(a, p);
@@ -155,9 +161,11 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec
if (k === D) continue; if (k === D) continue;
const a = unkey(k), o = nxt.get(k), p = nxtP.get(k); const a = unkey(k), o = nxt.get(k), p = nxtP.get(k);
const co = segNb(a, o) + cost.get(key(o)), cp = segNb(a, p) + cost.get(key(p)); const co = segNb(a, o) + cost.get(key(o)), cp = segNb(a, p) + cost.get(key(p));
const useP = cp < co || (cp === co && key(o) !== key(p) && key(chosen.get(key(o)) ?? []) === key(p) && skips(a, o, p)); const wo = segWet(a, o) + wet.get(key(o)), wp = segWet(a, p) + wet.get(key(p));
const useP = wp <= wo && (cp < co || (cp === co && key(o) !== key(p) && key(chosen.get(key(o)) ?? []) === key(p) && skips(a, o, p)));
chosen.set(k, useP ? p : o); chosen.set(k, useP ? p : o);
cost.set(k, useP ? cp : co); cost.set(k, useP ? cp : co);
wet.set(k, useP ? wp : wo);
} }
nxt = chosen; nxt = chosen;
} }
+39 -1
View File
@@ -1,7 +1,8 @@
// Official route shapes (community report after 0.1.0, D5: on 战场#04 活性源石 the lower-gate enemies walked two tiles // Official route shapes (community report after 0.1.0, D5: on 战场#04 活性源石 the lower-gate enemies walked two tiles
// straight left of the red gate and then straight up, instead of crossing diagonally from the 4th row to the 3rd as in // straight left of the red gate and then straight up, instead of crossing diagonally from the 4th row to the 3rd as in
// the official game). grid.js keeps the official flow field (research 08 §3.1) unless the blockable-ground preference // the official game). grid.js keeps the official flow field (research 08 §3.1) unless the blockable-ground preference
// route of user playtest #2 (0.1.0's) crosses strictly fewer non-blockable tiles (floor, gates) — a segment that only // route of user playtest #2 (0.1.0's) crosses strictly fewer non-blockable tiles (floor, gates) and no more 深水区
// (GitHub #375) — a segment that only
// brushes a floor tile's corner does not cross it. Audit: every stage (active or not) × gate × field against the pure // brushes a floor tile's corner does not cross it. Audit: every stage (active or not) × gate × field against the pure
// official algorithm; the routes that still differ are listed, each avoiding floor the official route walks over. // official algorithm; the routes that still differ are listed, each avoiding floor the official route walks over.
import { test } from 'node:test'; import { test } from 'node:test';
@@ -127,6 +128,43 @@ test('audit: every route of every stage (11, active or not) × gate × field is
assert.equal(official, stages.length * legs.length - seen.size); assert.equal(official, stages.length * legs.length - seen.size);
}); });
// GitHub #375 (FrogThai: 「水图boss会从下面这条路往蓝门走,实际应该从上路走」): on 战场#08(下半) 涨潮控制 the patrolling leaders
// (铳 — level_act1autochess_h07_02 route 6 —, 卢西恩 — act2autochess_h07_05 route 8: PATROL_MOVE (3,9) → (2,3) → (5,9))
// took the lower road (3,9) → (2,3) through the two 深水区 tiles (3,6) / (4,6) — one floor tile fewer than the official
// upper road along row 5, which crosses one water tile. The tie-break may not wade into more 深水区 than the official
// route (grid.js), so these legs are official again.
test('#375 战场#08(下半): the (3,9) → (2,3) patrol leg stays on the official upper road', REAL, () => {
const sid = 'act2autochess_m04';
const g = stageGrid(sid, GEO.BOSS_RECT), pure = stageGrid(sid, GEO.BOSS_RECT, true);
for (const [s, e, wp] of [
[[3, 9], [2, 3], '3,9 5,9 5,4 2,4 2,3'],
[[3, 11], [2, 17], '3,11 5,11 5,16 2,16 2,17'],
[[3, 10], [2, 3], '3,10 5,9 5,4 2,4 2,3'],
[[4, 10], [3, 9], '4,10 3,9'],
]) {
assert.deepEqual(g.waypoints(s[0], s[1], e[0], e[1]), pure.waypoints(s[0], s[1], e[0], e[1]), `(${s}) → (${e})`);
assert.equal(str(g.waypoints(s[0], s[1], e[0], e[1])), wp);
}
});
test('#375 real battle (act2 m04, 铳 on act1autochess_h07_02): the patrol walks row 5 to (2,3) and steps on one 深水区 only', REAL, () => {
const h = makeBattle({ kind: 'boss', stageId: 'act2autochess_m04', waveTemplate: 'act1autochess_h07_02', units: [], autoFinish: false, timeLimit: 120,
sharedBoss: { hp: 1e12, maxHp: 1e12, damage(pid, n) { this.hp -= n; } } });
const rows = getDefaultSource().getStage('act2autochess_m04').raw.rows;
const tiles = [];
h.b.on('tick', () => {
const e = h.b.enemies.find((u) => u.alive && u.defId === 'enemy_9017_achunt');
if (!e) return;
const k = `${Math.round(e.y)},${Math.round(e.x)}`;
if (tiles[tiles.length - 1] !== k) tiles.push(k);
});
assert.ok(h.runUntil(() => tiles.includes('2,4'), 60), `reaches (2,4): ${tiles.join(' ')}`);
const upTo = tiles.slice(0, tiles.indexOf('2,4') + 1);
assert.deepEqual(upTo.slice(0, 4), ['4,10', '3,9', '4,9', '5,9'], `up to row 5 first: ${upTo.join(' ')}`);
const water = upTo.filter((k) => { const [r, c] = k.split(',').map(Number); return rows[r][c] === 'd'; });
assert.deepEqual(water, ['5,6'], `深水区 stepped on: ${water.join(' ')} (route ${upTo.join(' ')})`);
});
test('D5: an operator on (9,9), (9,8) or (10,8) blocks the lower-gate enemy on the official diagonal', REAL, () => { test('D5: an operator on (9,9), (9,8) or (10,8) blocks the lower-gate enemy on the official diagonal', REAL, () => {
const guard = chessRec({ id: 't_guard', profession: 'WARRIOR', stats: { atk: 0, blockCnt: 3, maxHp: 1e6 }, skill: null }); const guard = chessRec({ id: 't_guard', profession: 'WARRIOR', stats: { atk: 0, blockCnt: 3, maxHp: 1e6 }, skill: null });
const walker = enemyRec({ key: 'enemy_walker', hp: 1e6, speed: 1 }); const walker = enemyRec({ key: 'enemy_walker', hp: 1e6, speed: 1 });
+137
View File
@@ -0,0 +1,137 @@
// The 传送门 (portal) of the boss / hidden-core templates (GitHub #336, PR #337 by @2321Robin): an enemy flies or walks
// into the portal and reappears on the far side. Four official routes spell the crossing as DISAPPEAR → WAIT → MOVE-to-the-exit with no APPEAR step
// (act1autochess_h07_02 route #4, act1autochess_h08_02 routes #5/#9, act2autochess_h07_05 route #6), while their twin
// routes in the same templates write it out as DISAPPEAR → WAIT → APPEAR[exit]. Taken literally, an appear-less route
// made the enemy walk its whole remaining route invisible and then leak unseen — the blue door takes the leak and the
// team never sees who or how (user report: 打 boss 时小怪进传送门后被判定进蓝门,全程隐身). normalizeRoute re-inserts
// the exit APPEAR ahead of that MOVE — at the MOVE's own target, which is exactly where the twins appear.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { normalizeRoute, hasGeneratedData } from '../../server/sim/simdata.js';
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
const REAL = { skip: !hasGeneratedData() && 'no generated data' };
/** Leg signature of a normalized route: `MOVE[r,c]` / `APPEAR[r,c]` / `DISAPPEAR` / `WAIT`. */
const sig = (r) => r.checkpoints.map((c) => c.type + (c.type === 'MOVE' || c.type === 'APPEAR' ? `[${c.pos[0]},${c.pos[1]}]` : ''));
/** The test enemy's leaks (the templates' own spawns leak too). */
const leaksOf = (h) => h.hooksOf('enemyLeak').filter((l) => l.enemy.defId === 'enemy_test_portal');
/** The test enemy's hidden spans: [{ from: [r,c], to: [r,c], dt }] (vanish tile, reappear tile, seconds hidden). */
function hiddenSpans(h) {
const spans = [];
let open = null;
const set = h.b._setHidden.bind(h.b);
h.b._setHidden = (e, on) => {
if (e.defId === 'enemy_test_portal' && on !== !!e.hidden) {
const at = [Math.round(e.y), Math.round(e.x)];
if (on) open = { at, t: h.b.time };
else if (open) { spans.push({ from: open.at, to: at, dt: h.b.time - open.t }); open = null; }
}
return set(e, on);
};
return spans;
}
test('normalizeRoute: DISAPPEAR → WAIT → MOVE gains the twin APPEAR at the move target', () => {
const r = normalizeRoute({
motion: 'FLY', start: [2, 10], end: [1, 3],
steps: [
{ t: 'move', p: [2, 11] }, { t: 'move', p: [1, 17] }, { t: 'disappear' }, { t: 'wait', s: 3 },
{ t: 'move', p: [5, 10] }, { t: 'move', p: [5, 9] },
],
});
assert.deepEqual(sig(r), ['MOVE[2,11]', 'MOVE[1,17]', 'DISAPPEAR', 'WAIT', 'APPEAR[5,10]', 'MOVE[5,10]', 'MOVE[5,9]']);
});
test('normalizeRoute: a DISAPPEAR the data does spell out with an APPEAR is kept as the only one', () => {
const r = normalizeRoute({
motion: 'FLY', start: [2, 10], end: [1, 17],
steps: [
{ t: 'move', p: [1, 3] }, { t: 'disappear' }, { t: 'wait', s: 3 }, { t: 'appear', p: [5, 10] }, { t: 'move', p: [5, 13] },
],
});
assert.equal(r.checkpoints.filter((c) => c.type === 'APPEAR').length, 1);
assert.deepEqual(sig(r), ['MOVE[1,3]', 'DISAPPEAR', 'WAIT', 'APPEAR[5,10]', 'MOVE[5,13]']);
});
test('normalizeRoute: idempotent — a second pass over a fixed route changes nothing', () => {
const once = normalizeRoute({
motion: 'FLY', start: [2, 10], end: [1, 3],
steps: [{ t: 'move', p: [1, 17] }, { t: 'disappear' }, { t: 'wait', s: 3 }, { t: 'move', p: [5, 10] }],
});
assert.deepEqual(normalizeRoute(once), once);
});
// The four official appear-less routes on their real templates: after the portal the enemy must be back in the open
// at the exit [5,10] (where the twins appear) and the route's leak at the blue door must happen VISIBLE.
const PORTAL_EXIT = [5, 10];
const CASES = [
['act1autochess_h07_02', 4, 'FLY'],
['act1autochess_h08_02', 5, 'WALK'],
['act1autochess_h08_02', 9, 'WALK'],
['act2autochess_h07_05', 6, 'FLY'],
];
for (const [tplId, routeIdx, motion] of CASES) {
test(`${tplId} route #${routeIdx}: portal crossing reappears at [${PORTAL_EXIT}] and leaks visible`, REAL, () => {
const h = makeBattle({
kind: 'boss', waveTemplate: tplId, content: 'none',
defs: { enemies: { enemy_test_portal: enemyRec({ key: 'enemy_test_portal', hp: 1e6, speed: 3, motion }) } },
enemies: [{ key: 'enemy_test_portal', route: routeIdx }],
autoFinish: true, timeLimit: 400, hooks: ['enemyLeak'],
});
const e = () => h.enemy('enemy_test_portal');
assert.ok(h.runUntil(() => e() && e().hidden, 400), 'reaches the portal (disappears)');
assert.ok(h.runUntil(() => e() && !e().hidden, 400), 'reappears after the portal');
assert.deepEqual([Math.round(e().y), Math.round(e().x)], PORTAL_EXIT, 'reappears at the portal exit');
h.runToEnd(400);
const [leak, ...more] = leaksOf(h);
assert.ok(leak, 'the route still ends in its leak');
assert.equal(more.length, 0, 'one leak');
assert.equal(leak.enemy.hidden, false, 'the leak is visible');
});
}
// A route that ENDS on a portal entrance does not leak there: the entrance teleports the enemy out of the far exit
// ([5,10] — the same pairing every explicit DISAPPEAR / APPEAR pair in the data reads) and it walks on to the blue
// door on that side of the field, where the leak happens (GitHub #336: 打 boss 小怪第一次进传送门就被判定进蓝门,
// 官方是传送后走向蓝门). The solo boss routes are exactly this shape: straight from the centre into the corner portal.
// Inside the portal it stays hidden for 3 s, as the explicit crossings of the same tiles (WAIT 3 in 85 of the 95).
test('solo boss: a route ending on the left portal entrance leaks at the left blue door, not the portal', REAL, () => {
const h = makeBattle({
kind: 'boss', stageId: 'act1autochess_m01', rect: { r0: 0, r1: 5, c0: 0, c1: 20 },
waveTemplate: 'act1autochess_h07_02_s', content: 'none',
defs: { enemies: { enemy_test_portal: enemyRec({ key: 'enemy_test_portal', hp: 1e6, speed: 3 }) } },
enemies: [{ key: 'enemy_test_portal', route: 0 }],
autoFinish: true, timeLimit: 300, hooks: ['enemyLeak'],
});
const spans = hiddenSpans(h);
const e = () => h.enemy('enemy_test_portal');
assert.ok(h.runUntil(() => e() && Math.round(e().y) === 1 && Math.round(e().x) === 3, 300), 'reaches the portal entrance [1,3]');
assert.ok(h.runUntil(() => e() && Math.round(e().y) === 5 && Math.round(e().x) === 10, 300), 'teleports out of the exit [5,10]');
h.runToEnd(400);
const [leak, ...more] = leaksOf(h);
assert.ok(leak, 'leaks eventually');
assert.deepEqual([Math.round(leak.enemy.y), Math.round(leak.enemy.x)], [2, 2], 'the leak is at the left blue door [2,2]');
assert.equal(more.length, 0, 'stepping into the portal entrance is no leak of its own');
assert.equal(spans.length, 1, 'one portal crossing');
assert.deepEqual([spans[0].from, spans[0].to], [[1, 3], [5, 10]], 'hidden from the entrance to the exit');
assert.ok(Math.abs(spans[0].dt - 3) < h.TICK * 1.5, `hidden 3 s inside the portal (${spans[0].dt.toFixed(3)} s)`);
});
test('a route ending on the right portal entrance mirrors: leak at the right blue door', REAL, () => {
const h = makeBattle({
kind: 'boss', stageId: 'act1autochess_m01', rect: { r0: 0, r1: 5, c0: 0, c1: 20 }, content: 'none',
defs: { enemies: { enemy_test_portal: enemyRec({ key: 'enemy_test_portal', hp: 1e6, speed: 3 }) } },
enemies: [{ key: 'enemy_test_portal', route: { motion: 'WALK', start: [2, 10], end: [1, 17], checkpoints: [] } }],
autoFinish: true, timeLimit: 300, hooks: ['enemyLeak'],
});
const spans = hiddenSpans(h);
h.runToEnd(400);
const [leak, ...more] = leaksOf(h);
assert.ok(leak, 'leaks eventually');
assert.deepEqual([Math.round(leak.enemy.y), Math.round(leak.enemy.x)], [2, 18], 'the leak is at the right blue door [2,18]');
assert.equal(more.length, 0, 'stepping into the portal entrance is no leak of its own');
assert.deepEqual(spans.map((x) => [x.from, x.to]), [[[1, 17], [5, 10]]], 'one crossing, entrance to exit');
assert.ok(Math.abs(spans[0].dt - 3) < h.TICK * 1.5, `hidden 3 s inside the portal (${spans[0].dt.toFixed(3)} s)`);
});