From a87cc1e9fd7bab322a59994fb93f43a24285c414 Mon Sep 17 00:00:00 2001 From: 2321Robin <2321Robin@users.noreply.github.com> Date: Thu, 8 Oct 2026 19:00:50 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feedback7=20R22-FIELDS:=20boss-field=20port?= =?UTF-8?q?als=20teleport=20=E2=80=94=20a=20route=20ending=20on=20a=20port?= =?UTF-8?q?al=20entrance=20comes=20out=20of=20the=20far=20exit=20and=20lea?= =?UTF-8?q?ks=20at=20that=20side's=20blue=20door,=20the=20four=20appear-le?= =?UTF-8?q?ss=20crossings=20reappear=20at=20the=20exit=20(GitHub=20#336,?= =?UTF-8?q?=20PR=20#337=20by=20@2321Robin);=20golden:=208=20fields,=206=20?= =?UTF-8?q?matches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #337 by @2321Robin (GitHub #336, both with a recording of the official game), applied with small changes: the exit distance uses detmath hypot (PR #373 bans Math.hypot in the sim), comments cite the data counts as measured here (137 routes end on [1,3] / [1,17]; 95 explicit DISAPPEAR / APPEAR pairs, all → [5,10]), the end-teleport's zero wait is marked [ASSUMED] beside the pairing, and the test reads the test enemy's own leaks (the templates' escorts leak too) and checks the entrance itself is no leak. - simdata.js portalAppear: act1autochess_h07_02 #4, act1autochess_h08_02 #5 / #9 and act2autochess_h07_05 #6 write DISAPPEAR → WAIT → MOVE[5,10] with no APPEAR (the level files too); the exit APPEAR is inserted before that move. Before, the enemy walked the rest of its route hidden and leaked unseen. - ai.js portalPickup: a route that ends on tile_telin (the boss / Hidden Core circuits) vanishes the enemy, brings it out of the farthest telout ([5,10]) and walks it to the nearest blue door ([2,2] / [2,18]), where it leaks. Before, stepping into the corner portal was the leak. Mid-route crossings are unchanged (every MOVE onto an entrance in the data is followed by an explicit DISAPPEAR). Golden — only boss / Hidden Core fields and the matches' final rounds move (no round row of any match moves): - fields boss-boss_2-solo (铳): the 2 lrrook escorts still leak, now at the door after the portal, and take 120652 damage instead of 66943; leader left 2761255 → 2763024. - fields boss-boss_4-solo (昆图斯): the 2 dspred leaks now happen at the door; one more operator death (6 → 7); leader left 3759559 → 3759921. - fields boss-boss_6-pair (阿利斯泰尔): the 2 hllevi_2 that leaked at the portals are killed on the way back: killed 4 → 6, both players perfect (1 leak each → 0); leader left 7017881 → 6972383. - fields boss-boss_6-solo: killed 5 → 8, leaks 5 → 2 (the 3 msfshu that leaked are killed; the 2 hllevi_2 leak at the door); leader damage 105194 → 75209. - fields hidden-boss_8-pair (胄): p2's one vtsk leak is gone — that vtsk is still walking to the door at the 200 s cap; p2 perfect. - fields hidden-boss_8-solo: killed 3 → 8, total 13 → 19 (more sfshu die, so more msfshu split off: 3 → 9), leaks 10 → 10; leader damage 402606 → 191576 (the escorts that come back out of [5,10] draw the fire). - fields hidden-boss_9-pair (铳): from portalAppear alone — h08_02's routes #5 / #9 no longer count the portal hop as 8 tiles of walking, so the targeting order of those escorts changes; leader left 3570058 → 3511007. - fields hidden-boss_10-solo (管): the yokai_3 escort crosses back (taken 68252 → 101682), 4 more operator deaths; leader left 7323878 → 7323370. - matches solo-FUNNY-2 (boss_5 Final Assault, lost at 8 rounds): pool left 94860 → 94412, kills 143 → 144. - matches solo-NORMAL-1 (m03, hidden boss_8 胄, act1autochess_h08_01_s): the Hidden Core goes from cleared to failed (hiddenCleared true → false, team LP 26 → 0, roundsPassed 15 → 14, pool left 0 → 99894; victory stays true — the Final Assault was won). The fight is identical until 105 s, when the third vtsk escort reaches the corner portal [1,3]: before, it leaked there (27 → 26 LP) and the leader died at 214 s. Now it comes out of [5,10], walks through the team's line to the blue door [2,2] and leaks at 170 s; meanwhile only 0–2 of the 8 operators stand (2–4 before), the leader stalls near 120000 HP, the fight passes the 150 real-second mark (300 game s) and the overtime drain (1 LP per real second) plus two drone leaks at the door take the last 25 LP at 348 s with 99894 HP left. - matches solo-HARD-1 (m03, hidden boss_8): still failed at 0 LP after 14 rounds; pool left 3046117 → 2839368, kills 419 → 422. - matches solo-HARD-9-boosted (hidden boss_10 管): still cleared; team LP 383 → 361 — the 管 echoes (acdums) leaked 7 times at the portal before and once at the door now, but the longer fight pays 37 overtime LP instead of 9. - matches coop2-ABYSS-10-boosted (hidden boss_9 铳): from portalAppear alone (the remaining-distance change above); still cleared at 800 LP, the two seats' leader damage shifts (6609998 / 7282442 → 7523075 / 6369365) and their titles swap (坚若磐石 / 卫戍之星 → 卫戍之星 / 不朽盟约). - matches coop2-NORMAL-14-standins (boss_5 Final Assault, lost at 13 rounds): pool left 107915 → 97588. Tests: test/sim/portal-appear.test.js (new, 9; 7 fail on the parent: the APPEAR insertion, the four real routes, the two route-end portals). --- docs/SIM.md | 9 +- server/sim/ai.js | 50 +++- server/sim/simdata.js | 21 ++ test/golden/fields.json | 417 +++++++++++++++++---------------- test/golden/matches.json | 36 +-- test/sim/portal-appear.test.js | 114 +++++++++ 6 files changed, 414 insertions(+), 233 deletions(-) create mode 100644 test/sim/portal-appear.test.js diff --git a/docs/SIM.md b/docs/SIM.md index 08a53a3f..c4658234 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -187,7 +187,14 @@ bounty?:{coins, ownerPlayerId}, tag?:'boss'|'part'|'escort'|'bounty', ownerPlaye `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 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) and walks on to the blue door on the entrance's side, where it leaks (ai.js portalPickup; [ASSUMED] the +pairing and no wait inside this portal). 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 diff --git a/server/sim/ai.js b/server/sim/ai.js index c9a9f0eb..db73ed83 100644 --- a/server/sim/ai.js +++ b/server/sim/ai.js @@ -28,10 +28,11 @@ // (PRTS 状态机: an enemy's COMBAT state ends with its attack, not with its blocker); the candidates pass the // enemy's own rule (`e.profile.canTarget`) and are ordered // blocker → taunt → latest deployed (targeting.js sortAllyTargets). An enemy's damage type is its data's unless content -// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the -// enemy runs between random checkpoints away from the fear's source (fear.js moveFeared; a self-inflicted fear -// flutters inside its own tile); an `attract` (诱导) status walks it to the status point instead (moveAttracted); -// both re-plan the route when released (恐惧 outranks 诱导). +// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak — on a +// portal entrance it is a teleport to the far exit and a walk to that side's blue door instead (portalPickup). A `fear` +// (恐惧) status suspends the route: the enemy runs between random checkpoints away from the fear's source (fear.js +// moveFeared; a self-inflicted fear flutters inside its own tile); an `attract` (诱导) status walks it to the status +// point instead (moveAttracted); both re-plan the route when released (恐惧 outranks 诱导). import { ATTACK_PAUSE, ALLY_COLLIDER_RADIUS, MOVE_SCALE, PROJECTILE_SPEEDS, PROJECTILE_SPEED, BOOMERANG_RETURN_SPEED, COLS, CHAIN_RADIUS } from './constants.js'; import { sortEnemyTargets, sortAllyTargets, canTargetEnemy, canTargetAlly, tileKeyOf } from './targeting.js'; @@ -633,7 +634,10 @@ function advanceRoute(b, e, dt, R, standing = false) { let guard = 16; while (budget > 1e-9 && guard-- > 0 && e.alive) { const leg = R.legs[R.legIdx]; - if (!leg) { b.leak(e); return; } + if (!leg) { + if (!portalPickup(b, e)) { b.leak(e); return; } + continue; + } if (leg.t === 'wait') { if (R.waitLeft == null) R.waitLeft = leg.time; const use = Math.min(budget, R.waitLeft); @@ -682,13 +686,47 @@ function advanceRoute(b, e, dt, R, standing = false) { } budget = dist / speed; if (R.pts && R.ptIdx >= R.pts.length) { - if (leg.final) { b.leak(e); return; } + if (leg.final) { + if (!portalPickup(b, e)) { b.leak(e); return; } + } R.legIdx++; R.pts = null; } } } +/** + * Boss-field portal pickup (tile_telin / tile_telout; GitHub #336, PR #337 by @2321Robin, both with a recording of the + * official game): a route that ENDS on a portal entrance does not leak there — the enemy is teleported out of the far + * exit and walks on to the blue door on the entrance's side, where it leaks. The boss / Hidden Core circuits end on + * an entrance ([1,3] / [1,17]; 137 routes of data/waves.json); their mid-route crossings are the routes' own DISAPPEAR / + * WAIT / APPEAR steps, every MOVE onto an entrance being followed by a DISAPPEAR, so only the route's end needs this. + * Appends vanish → 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. + * [ASSUMED] the pairing (no tile carries a link) and no wait inside this portal (the explicit crossings write their + * own: 3 s in 85 of the 95, 5 s in 9, 1 s in 1). + */ +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: 'appear', r: out[0], c: out[1] }); + const ends = b.grid.specialTiles('end'); + if (ends.length) { + const door = ends.reduce((a, x) => (dist(x) < dist(a) ? x : a)); + R.legs.push({ t: 'move', r: door[0], c: door[1], final: true }); + } + R.pts = null; + R.tailVersion = -1; // the appended tail invalidates the cached remaining-distance suffixes + return true; +} + /** * How long an enemy stands for one attack: an unblocked ranged enemy for its whole clip (GitHub #58 — the owner's * decision of 2026-10-04 from first-hand memory of the official game: a ranged enemy stops for each attack's animation diff --git a/server/sim/simdata.js b/server/sim/simdata.js index 70921635..60eed174 100644 --- a/server/sim/simdata.js +++ b/server/sim/simdata.js @@ -394,6 +394,7 @@ export function normalizeRoute(r) { 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) }); } + portalAppear(cps); return { motion, start, end, checkpoints: cps }; } 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) }); } } + portalAppear(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) { const s = String(t ?? 'MOVE').toUpperCase(); if (s.includes('DISAPPEAR')) return 'DISAPPEAR'; diff --git a/test/golden/fields.json b/test/golden/fields.json index 89f0b465..fa0f436c 100644 --- a/test/golden/fields.json +++ b/test/golden/fields.json @@ -227,27 +227,27 @@ "killed": 5, "total": 7, "unspawned": 0, - "bossHpLeft": 2761255, + "bossHpLeft": 2763024, "rngDraws": 127, "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":453723,"bossDmg":230580,"heal":40581,"deaths":1,"layerGains":{}}}, "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,5388,19443,15985,82,0], ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,86351,0,0,79,4], ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,6,0,77745,6115,3711,91,4], ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,7,1,23391,7108,1936,80,7], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2569,1,8,4,131322,8759,0,69,2], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,9,0,16118,0,0,11,0], + ["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,23153,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",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,75476,0,1129,89,6], ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",695.86,1,3,0,0,1448,1057,0,3], ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,0,16195,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]}, - "hooks": [1,19,6000,580,580,1299,1898,5497,6,8,30,29,0,13846,99,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}, - "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}, - "snaps": "a42f06d2 4a40e022 e95a4901 abb693b9 1c238b4b afb89449 d6c6b3df c846d6b3 fa04e7ce c4d85f2e 4a6790c9 effed23a 72e8e203 8146bf98 e8dfb61c 69d1b960 6d949949 0310d38f a1429900 b8610bee" + "enemies": {"enemy_1249_lysdb_2":[5,5,0,8138,107495],"enemy_1430_lrrook":[2,0,2,0,120652],"enemy_9017_achunt":[1,0,0,23716,236976]}, + "hooks": [1,19,6000,591,591,1390,1989,5497,6,8,33,32,0,15126,113,5,8,2,1,8,0,6,2], + "events": {"atk":642,"deploy":11,"die":6,"dmg":1979,"engage":7,"fx":290,"heal":37,"leak":2,"skill":68,"spawn":18,"status":509}, + "fx": {"appear":2,"bandage":3,"block":9,"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,"sandChainsCharged":1,"shieldBreak":15,"slash":38,"spGift":5,"thorns":21,"truesilver":3}, + "snaps": "a42f06d2 4a40e022 e95a4901 abb693b9 1c238b4b afb89449 d6c6b3df c846d6b3 f0cff710 d31f361e 6222a056 6c7abdc9 c7c74721 0757ffc7 91acf075 8e9b73d0 f30d9021 dae03804 f0c86afa 248b5f7a" }, "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", @@ -516,29 +516,29 @@ "killed": 5, "total": 7, "unspawned": 0, - "bossHpLeft": 3759559, - "rngDraws": 84, + "bossHpLeft": 3759921, + "rngDraws": 90, "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":732206,"bossDmg":440079,"heal":93600,"deaths":7,"layerGains":{}}}, "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",2710.93,1,23,2,19548,12098,13170,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",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,21,3,202794,41516,12031,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",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2541.58,1,10,0,162116,8875,0,135,3], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,24,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",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,25,0,52353,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",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,4444,25271,28,4], + ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,21,3,202794,41516,12403,43,1], + ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2350.14,1,9,0,2748,11517,0,5,1], + ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2061.88,1,24,0,175575,6029,0,120,3], + ["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",1545.34,1,11,0,0,4444,19915,29,2], + ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",1748.63,1,26,0,54944,7314,487,76,5], + ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1394.71,1,3,0,0,6735,7448,0,3], + ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,4444,25541,27,5], ["-","~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] ], - "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]}, - "hooks": [1,25,6000,565,565,958,1133,7303,11,13,28,28,0,13157,125,16,8,2,1,16,0,29,1], - "events": {"atk":770,"deploy":15,"die":11,"dmg":1115,"engage":6,"fx":335,"heal":105,"leak":2,"skill":59,"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}, - "snaps": "b3cecd10 d3d68301 c10d8554 c91066bb 2ac0bbdd cb65c8b1 e357c87a 1cfd4db1 1a8bd8d8 e639cb94 69f98f4a 92de5c02 51bb18c2 a8b02c45 54b09843 f60538cb 94407072 8c567dae 4c15135e a957f119" + "enemies": {"enemy_1010_demon_2":[2,2,0,5940,107495],"enemy_1438_dspred":[5,3,2,36058,184632],"enemy_1521_dslily":[1,0,0,69595,440079]}, + "hooks": [1,26,6000,558,558,958,1130,8107,12,14,30,30,0,13499,128,20,8,2,1,17,0,28,3], + "events": {"atk":764,"deploy":16,"die":12,"dmg":1112,"engage":7,"fx":339,"heal":93,"leak":2,"skill":63,"spawn":20,"status":483}, + "fx": {"appear":2,"bandage":5,"block":15,"burst":28,"camouflage":1,"disappear":2,"dodge":3,"featherArrow":5,"flame":1,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":61,"redistribute":45,"reweave":3,"riposte":2,"rockfall":18,"rockslide":5,"sandChains":5,"shieldBreak":18,"slash":48,"spGift":4,"tentacle":6,"thorns":48,"tide":5,"truesilver":3,"undying":1}, + "snaps": "b3cecd10 d3d68301 c10d8554 c91066bb 2ac0bbdd cb65c8b1 e357c87a 1cfd4db1 fbb2d966 235fafe1 061ad690 dabccf6c b8e37a8a 8618ace2 44fc44ec 4fbab63c 309205ea 3215de21 1ce4f962 d787882b" }, "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", @@ -703,35 +703,35 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 4, + "killed": 6, "total": 6, "unspawned": 0, - "bossHpLeft": 7017881, - "rngDraws": 570, + "bossHpLeft": 6972383, + "rngDraws": 697, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":4,"total":6,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":869760,"bossDmg":436648,"heal":2876,"deaths":5,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":1033990,"bossDmg":505471,"heal":580,"deaths":19,"layerGains":{}} + "p1": {"killed":6,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1061526,"bossDmg":492176,"heal":2876,"deaths":5,"layerGains":{}}, + "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1196635,"bossDmg":495441,"heal":580,"deaths":19,"layerGains":{}} }, "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",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",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,303339,0,551,64,13], - ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,179149,0,0,76,6], - ["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,66,0,19506,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",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,65,0,2158,3220,2325,3,1], - ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,0,52833,0,0,38,2], + ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,373439,0,551,84,16], + ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,197565,0,0,73,6], + ["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,140802,3222,0,46,15], + ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,65,0,4316,3220,2325,3,1], + ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,1,65490,0,0,48,3], ["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",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,61,0,58463,13682,40,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",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,0,493990,0,0,140,2], + ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,61,0,60730,13682,40,128,7], + ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,200340,0,0,114,6], + ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,1,624669,0,0,180,3], ["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,48350,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",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], @@ -739,50 +739,50 @@ ], "enemies": { "enemy_10028_vtswd": [3,3,0,0,0], - "enemy_10029_vtshld": [5,5,0,0,0], - "enemy_10030_vtwand": [5,5,0,0,0], - "enemy_10085_hllevi_2": [2,0,2,0,761019], + "enemy_10029_vtshld": [6,6,0,0,0], + "enemy_10030_vtwand": [4,4,0,0,0], + "enemy_10085_hllevi_2": [2,2,0,0,1069932], "enemy_1010_demon": [4,4,0,12402,200612], - "enemy_9032_aclionk": [1,0,0,47314,942119] + "enemy_9032_aclionk": [1,0,0,47314,987617] }, - "hooks": [1,65,6000,1074,1088,2031,2044,359,41,45,91,88,0,30474,660,8,20,2,1,29,0,2,0], - "events": {"atk":1139,"deploy":45,"die":43,"dmg":1988,"engage":15,"fx":618,"heal":11,"leak":2,"skill":179,"spawn":42,"status":942}, + "hooks": [1,65,6000,1150,1164,2276,2290,359,43,45,104,99,0,32169,744,8,20,0,1,31,0,2,0], + "events": {"atk":1233,"deploy":45,"die":45,"dmg":2222,"engage":15,"fx":730,"heal":11,"skill":203,"spawn":42,"status":1113}, "fx": { - "aoe": 30, - "appear": 4, + "aoe": 44, + "appear": 6, "blink": 2, "burst": 2, - "crit": 81, + "crit": 108, "dilemma": 6, - "disappear": 4, + "disappear": 6, "displace": 19, - "equip": 18, + "equip": 17, "explosion": 4, "frostNova": 9, "garrisonGrant": 1, "iceSpike": 33, "overclock": 1, "pull": 4, - "quadShot": 2, + "quadShot": 3, "revive": 2, - "ripple": 18, + "ripple": 25, "sentry": 4, - "shadowWeave": 4, + "shadowWeave": 5, "shield": 1, "soul": 2, - "sp": 13, + "sp": 16, "stealth": 6, "sword": 1, - "swordRain": 25, + "swordRain": 26, "swordStorm": 7, - "telegraph": 19, + "telegraph": 21, "tornado": 5, "tornadoPulse": 20, - "vest": 5, + "vest": 6, "weightless": 6, - "zone": 260 + "zone": 312 }, - "snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 f03b4c62 86a1deba 85583d64 215fecf5 ccd39d46 4a08d997 af901ca4 08ece609 76458391 47fbc660 009a9bbe 48624b1a 42116d05 63ab6257 f103fa00" + "snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 f03b4c62 86a1deba 85583d64 215fecf5 ccd39d46 4a08d997 af901ca4 08ece609 e3643313 6f0df0ee 63a32534 86450a7b fdbbbdc8 f239ab01 bac8ff90" }, "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", @@ -796,24 +796,24 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 5, + "killed": 8, "total": 10, "unspawned": 0, - "bossHpLeft": 3874806, - "rngDraws": 45, + "bossHpLeft": 3904791, + "rngDraws": 52, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":5,"total":10,"perfect":false,"leaks":5,"leaksUncounted":0,"coins":0,"dmg":152625,"bossDmg":105194,"heal":1783,"deaths":16,"layerGains":{}}}, + "players": {"p1":{"killed":8,"total":10,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":195265,"bossDmg":75209,"heal":6651,"deaths":17,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,10,2,8,0,0,8,0], - ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,11,0,6,0,0,6,1], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,12,1,8984,35,35,15,2], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,48,0,1033,8472,0,40,3], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,46,0,52228,18906,1436,38,4], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,2,88481,0,0,50,7], + ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,10,3,9,0,0,9,0], + ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,11,0,9,0,0,9,1], + ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,12,2,12627,35,847,19,2], + ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,48,0,465,10590,0,34,3], + ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,49,0,70149,22768,931,30,5], + ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,2,58468,0,0,52,7], ["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",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,44,0,0,6995,311,0,0], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,9,0,0,0,0,0,0], + ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",0,0,45,1,53539,9209,114,41,3], + ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,46,0,0,10071,1886,0,1], + ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,9,0,0,0,2872,4,1], ["-","~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], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,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] ], "enemies": { - "enemy_10028_vtswd": [4,4,0,0,0], - "enemy_10029_vtshld": [3,3,0,0,0], - "enemy_10030_vtwand": [1,1,0,0,0], - "enemy_10085_hllevi_2": [2,0,2,0,29468], + "enemy_10028_vtswd": [3,3,0,0,0], + "enemy_10029_vtshld": [5,5,0,0,0], + "enemy_10030_vtwand": [2,2,0,0,0], + "enemy_10085_hllevi_2": [2,0,2,0,102074], "enemy_1197_sfshu": [2,2,0,17,17916], - "enemy_1198_msfshu": [6,3,3,0,47], - "enemy_9032_aclionk": [1,0,0,43085,105194] + "enemy_1198_msfshu": [6,6,0,0,66], + "enemy_9032_aclionk": [1,0,0,52638,75209] }, - "hooks": [1,49,6000,179,179,275,472,1334,29,34,20,20,0,6463,12,1,19,5,1,21,0,0,3], - "events": {"atk":205,"deploy":24,"die":29,"dmg":201,"engage":7,"fx":91,"leak":5,"skill":42,"spawn":35,"status":73}, - "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}, - "snaps": "3e08ed3d 2a259699 77c4f1c2 a547c8e8 80cdbe94 5387ac7c ac8acb26 e405b502 b8cb4679 62dee17d 6a983199 49974232 ee10619f d218a36a 29eef739 77b2f124 b45c9755 95f6faee a7fcd01a edbf901f" + "hooks": [1,50,6000,211,211,362,559,1264,35,37,23,23,0,6727,100,1,21,2,1,25,0,0,3], + "events": {"atk":298,"deploy":23,"die":35,"dmg":273,"engage":7,"fx":104,"heal":5,"leak":2,"skill":48,"spawn":37,"status":274}, + "fx": {"appear":4,"bandage":1,"disappear":4,"dodge":3,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":10,"redistribute":4,"reweave":1,"rockslide":1,"sandChains":3,"slash":19,"summon":2,"sword":3,"telegraph":14,"truesilver":5,"vest":5}, + "snaps": "3e08ed3d 2a259699 77c4f1c2 f8300f5c 0da728a3 74bd5834 472e12a4 7db18cf8 403525d1 cc9436e7 8b639112 609ef73d 54e889e7 f8c0662d 8e3f01be 58e300b6 a655b3e3 6e779450 0f89e048 cb188754" }, "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", @@ -964,7 +964,7 @@ "errors": {"count":0,"kinds":[]}, "players": { "p1": {"killed":16,"total":18,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2677042,"bossDmg":1550396,"heal":69312,"deaths":17,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":560253,"bossDmg":262771,"heal":9942,"deaths":25,"layerGains":{}} + "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":560253,"bossDmg":262771,"heal":9942,"deaths":25,"layerGains":{}} }, "units": [ ["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] ], "enemies": { - "enemy_10027_vtsk": [6,4,1,138057,1019681], + "enemy_10027_vtsk": [6,4,0,138057,1019681], "enemy_1005_yokai": [4,4,0,0,10699], "enemy_1229_darmy_2": [8,8,0,6104,294231], "enemy_9013_acstmk_2": [1,0,0,63941,1814230], @@ -999,11 +999,11 @@ "enemy_9015_acstmb": [3,2,0,9688,83569], "enemy_9016_acstmr": [8,7,0,0,150] }, - "hooks": [1,92,6000,904,910,2960,3146,7480,72,75,107,101,6,25061,1027,17,36,1,1,61,0,5,9], - "events": {"atk":1083,"deploy":56,"die":74,"dmg":3143,"engage":18,"fx":651,"heal":136,"leak":1,"skill":208,"spawn":58,"status":990}, + "hooks": [1,92,6000,904,910,2960,3146,7480,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":653,"heal":136,"skill":208,"spawn":58,"status":990}, "fx": { "aoe": 42, - "appear": 7, + "appear": 8, "beam": 48, "blink": 16, "burst": 5, @@ -1011,7 +1011,7 @@ "counter": 6, "crit": 59, "dilemma": 3, - "disappear": 7, + "disappear": 8, "displace": 1, "dodge": 9, "explode": 10, @@ -1044,7 +1044,7 @@ "weightless": 3, "zone": 180 }, - "snaps": "cac31b2c d1e28667 87626189 11a56b43 640cf8cf 9a92be69 12784d9a 195ea951 99de9374 65451b7e 90846f89 9bcf64aa 0cf3dfb9 5737ca93 68f962d8 98c8303b e5af0a0e 3c947908 ce14aa80 61dc2e23" + "snaps": "cac31b2c d1e28667 87626189 11a56b43 640cf8cf 9a92be69 12784d9a 195ea951 99de9374 65451b7e 90846f89 9bcf64aa 0cf3dfb9 5737ca93 68f962d8 98c8303b e5af0a0e 3c947908 ce14aa80 4fc6091d" }, "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", @@ -1058,43 +1058,43 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 3, - "total": 13, + "killed": 8, + "total": 19, "unspawned": 0, - "bossHpLeft": 6797394, - "rngDraws": 227, + "bossHpLeft": 7008424, + "rngDraws": 122, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":3,"total":13,"perfect":false,"leaks":10,"leaksUncounted":0,"coins":0,"dmg":450453,"bossDmg":402606,"heal":28293,"deaths":23,"layerGains":{}}}, + "players": {"p1":{"killed":8,"total":19,"perfect":false,"leaks":10,"leaksUncounted":0,"coins":0,"dmg":237408,"bossDmg":191576,"heal":29339,"deaths":24,"layerGains":{}}}, "units": [ ["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",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,7,0,0,2811,0,0,0], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,53,0,0,10739,0,0,0], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2569,1,11,2,397515,0,140,154,3], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,29,0,5558,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",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,35,3,12364,18678,6470,13,5], + ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,7,1,20,2811,0,12,1], + ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,59,1,99,11440,0,15,1], + ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,11,3,189596,3399,140,65,1], + ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,29,0,1903,2838,0,6,0], + ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",0,0,60,0,0,8993,12523,39,6], ["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",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,50,0,0,5960,6111,15,1], + ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,57,0,0,11646,3157,0,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], ["-","~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], ["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null] ], "enemies": { - "enemy_10027_vtsk": [3,0,3,29903,18989], - "enemy_1005_yokai": [4,2,2,0,2867], - "enemy_1197_sfshu": [3,1,2,27,17625], - "enemy_1198_msfshu": [3,0,3,0,7], - "enemy_9013_acstmk_2": [1,0,0,37240,402606], - "enemy_9014_acstma": [3,2,0,0,7972], - "enemy_9015_acstmb": [4,3,0,6587,340], - "enemy_9016_acstmr": [7,6,0,0,47] + "enemy_10027_vtsk": [3,0,3,35787,17457], + "enemy_1005_yokai": [4,1,2,0,1433], + "enemy_1197_sfshu": [3,3,0,856,26874], + "enemy_1198_msfshu": [9,4,5,0,57], + "enemy_9013_acstmk_2": [1,0,0,34921,191576], + "enemy_9014_acstma": [4,3,0,0,0], + "enemy_9015_acstmb": [4,3,0,5808,0], + "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], - "events": {"atk":360,"deploy":24,"die":37,"dmg":569,"engage":5,"fx":188,"heal":76,"leak":10,"skill":35,"spawn":42,"status":149}, - "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}, - "snaps": "cf292ec5 7036981d ce112fc3 25f0eb93 7e29d0f3 a4667973 30a2224e 8827f472 5ed6aeed 09c8a1e1 a4c3c74f 484ec38d cf5610d2 28371e20 bee6b488 d24d7a86 3e0c2516 47741f73 949b25ac 7b412345" + "hooks": [1,63,6000,254,254,409,404,904,44,54,17,18,0,3859,54,7,35,10,1,39,0,0,0], + "events": {"atk":306,"deploy":24,"die":44,"dmg":397,"engage":7,"fx":173,"heal":80,"leak":10,"skill":36,"spawn":49,"status":162}, + "fx": {"appear":11,"bandage":1,"beam":31,"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": "cf292ec5 7036981d ce112fc3 25f0eb93 70c3bccc e26c17f4 00a4d474 544cd08f aa7be5f8 c5be8afe f91d2b31 046a0906 0349b50e e6ee0388 5018349f 33803746 6f6b44c6 61b9c230 1fc2a162 0c040988" }, "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", @@ -1111,106 +1111,107 @@ "killed": 8, "total": 8, "unspawned": 0, - "bossHpLeft": 3570058, - "rngDraws": 1579, + "bossHpLeft": 3511007, + "rngDraws": 1705, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1989162,"bossDmg":803960,"heal":23383,"deaths":4,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3240431,"bossDmg":1176195,"heal":126751,"deaths":10,"layerGains":{}} + "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1689940,"bossDmg":698535,"heal":15297,"deaths":5,"layerGains":{}}, + "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3810677,"bossDmg":1348791,"heal":109383,"deaths":7,"layerGains":{}} }, "units": [ - ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",5343.08,1,15,0,1906,10482,12401,28,3], - ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2910.14,1,11,0,19692,205,0,21,1], - ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1761.93,1,17,0,50071,1601,0,31,2], - ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",0,0,7,2,1548110,3561,0,55,8], - ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1074.65,1,9,0,33675,10123,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",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,1,90440,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",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,144190,13392,0,29,29], - ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,63,1,24647,9390,349,66,2], + ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",6348.28,1,67,0,9,7512,6537,1,3], + ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2695.89,1,11,0,8762,1179,0,9,1], + ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1406.57,1,17,0,35012,3869,0,15,2], + ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1165.15,1,7,2,1312507,1285,0,58,8], + ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",0,0,9,0,19135,12418,0,23,1], + ["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1104.58,1,55,0,35830,11463,444,0,4], + ["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,0,67894,0,0,0,10], + ["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,30095,0,7111,86,3], + ["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,150260,13231,0,28,28], + ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,60,0,22306,8566,349,73,3], ["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], - ["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",0,0,62,1,459553,38345,0,197,3], - ["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,60,0,497307,16632,0,66,5], - ["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,66,0,13781,7737,2820,42,2], - ["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",1302.59,1,61,0,512511,25088,0,68,3], - ["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",765.29,1,8,0,477111,13038,0,78,3], - ["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2215,1,14,0,969378,5435,3408,291,6], - ["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,24080,78,1], - ["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,10,0,98857,0,0,131,4], - ["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1695.1,1,59,1,183585,13999,0,2,5], - ["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1592.1,1,25,0,0,14000,96443,80,2], + ["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",0,0,66,4,730725,36055,0,338,3], + ["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",203.08,1,58,0,450324,11985,0,58,5], + ["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,65,0,41071,8230,3308,52,2], + ["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3430.93,1,59,1,771816,24776,0,77,3], + ["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2886.8,1,8,0,378435,1696,0,78,3], + ["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2215,1,14,0,1014033,6744,2745,288,6], + ["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,7939,78,1], + ["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,10,0,105424,0,0,132,4], + ["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1690.1,1,41,0,234825,7508,0,60,4], + ["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1592.1,1,25,0,0,14000,95391,82,2], ["-","~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], ["-","~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], ["-","~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], - ["p2","~1","token","token_10019_nearl2_sword",null,null,2280.4,0,47,0,6744,6000,0,5,null], - ["p1","~1","token","token_10030_mlyss_wtrman",null,null,1322,1,48,0,1766,2370,1514,30,1], - ["p1","~2","token","token_10030_mlyss_wtrman",null,null,1102.65,0,49,0,595,675,0,11,null], - ["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,51,0,661,1322,0,10,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,54,0,4750,270,0,10,null], + ["p2","~1","token","token_10019_nearl2_sword",null,null,4710.39,0,47,0,6744,3570,0,5,null], + ["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,48,0,1670,1538,0,22,1], + ["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,220,3142,0,9,null], + ["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,51,0,463,1322,0,7,null], + ["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,53,0,4750,270,0,10,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,0,0,57,0,21605,6600,0,12,null], - ["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,3926.59,0,65,0,0,97,0,0,null], - ["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,67,0,0,0,0,0,null] + ["p2","~2","token","token_10019_nearl2_sword",null,null,0,0,57,0,68925,6922,0,11,null], + ["p1","~4","token","token_10030_mlyss_wtrman",null,null,1172,1,61,0,1025,710,538,7,1], + ["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,62,0,0,0,0,0,null], + ["p1","~2","token","token_10040_siege2_vlion",null,null,3942.24,0,63,0,0,76,0,0,null], + ["p2","~3","token","token_10019_nearl2_sword",null,null,3290.4,0,64,0,8355,3008,0,21,null], + ["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,68,0,0,0,0,0,null] ], "enemies": { - "enemy_1267_nhpbr_2": [6,6,0,5992,141894], - "enemy_9007_acelem": [2,2,0,22960,309587], - "enemy_9017_achunt_2": [2,0,0,57558,2029942], - "enemy_9018_actrpa": [1,0,0,42972,765851], - "enemy_9019_actrpb": [1,0,0,26761,756394], - "enemy_9020_actrpc": [1,0,0,18223,1328263] + "enemy_1267_nhpbr_2": [6,6,0,1800,141894], + "enemy_9007_acelem": [2,2,0,15725,309587], + "enemy_9017_achunt_2": [2,0,0,54791,2088993], + "enemy_9018_actrpa": [1,0,0,30160,855522], + "enemy_9019_actrpb": [1,0,0,29590,798033], + "enemy_9020_actrpc": [1,0,0,20449,1384683] }, - "hooks": [1,65,6000,1653,1653,7414,13693,3721,26,37,100,94,326,46845,2814,18,13,0,1,27,0,18,0], - "events": {"atk":2008,"deploy":46,"die":37,"dmg":13203,"engage":24,"fx":2168,"heal":883,"skill":194,"spawn":52,"status":833}, + "hooks": [1,66,6000,1799,1799,6944,13382,2834,25,37,99,90,326,47765,2836,14,13,0,1,26,0,13,0], + "events": {"atk":2246,"deploy":47,"die":37,"dmg":12926,"engage":25,"fx":2188,"heal":930,"skill":189,"spawn":53,"status":849}, "fx": { - "airstrike": 117, + "airstrike": 123, "beam": 31, "blessing": 1, - "blink": 4, - "bombard": 68, - "bombardShell": 68, - "burst": 18, + "blink": 5, + "bombard": 72, + "bombardShell": 72, + "burst": 13, "camouflage": 1, - "copy": 2, + "copy": 3, "dash": 24, - "displace": 24, - "dp": 2, - "drone": 275, - "droneLock": 15, - "drones": 5, - "extraAttack": 12, + "displace": 1, + "dp": 3, + "drone": 320, + "droneLock": 14, + "drones": 4, + "extraAttack": 16, "finale": 5, "firewall": 3, "garrisonGrant": 1, - "lightning": 119, + "lightning": 116, "link": 1141, "lock": 72, - "lure": 2, - "phase": 11, - "radiantSword": 2, + "lure": 1, + "phase": 12, + "radiantSword": 3, "revive": 3, - "scorchBurst": 3, - "shieldBreak": 9, + "scorchBurst": 1, + "shieldBreak": 10, "slash": 55, - "snow": 25, - "split": 3, + "snow": 15, + "split": 2, "steal": 6, - "storm": 11, - "summon": 4, + "storm": 10, + "summon": 5, "sunBurst": 3, - "talent": 7, + "talent": 5, "tide": 4, "wanted": 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 264faece f8920de3 cef81b6f 47f4a450 cc0a8289 6b7ac4fb 81b93e22 d10e889f 97a33a13 a796916b 202ae19b 16aa629d c7afee38 08c75624 11b64823 c1f874f3 b92e528f" }, "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", @@ -1377,33 +1378,33 @@ "killed": 11, "total": 13, "unspawned": 0, - "bossHpLeft": 7323878, - "rngDraws": 102, + "bossHpLeft": 7323370, + "rngDraws": 166, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":11,"total":13,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":430741,"bossDmg":276122,"heal":148595,"deaths":10,"layerGains":{}}}, + "players": {"p1":{"killed":11,"total":13,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":464694,"bossDmg":276630,"heal":131305,"deaths":14,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3123.26,1,29,5,45,3903,0,19,0], - ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",1092.31,1,24,0,36,24428,13312,31,6], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,28,0,140999,24118,1514,143,3], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",3600.6,1,31,0,13,10454,0,12,0], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,22,3,176153,57886,6009,118,2], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,30,1,87204,1419,0,56,6], - ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1584,1,25,0,0,20837,92999,101,2], - ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,2,2,26292,3463,0,38,4], - ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,23,0,0,33782,4919,0,1], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,3,0,0,2358,29841,56,9] + ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,29,5,86,7026,0,29,0], + ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,25,1,24,20856,8648,22,3], + ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",3972.12,1,36,1,135461,22114,1894,138,3], + ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2794.81,1,30,1,24,19328,0,20,2], + ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",3905.8,1,35,1,190741,38172,6349,92,3], + ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,31,0,91309,2838,0,56,6], + ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",2920.8,1,24,0,0,22500,76891,97,2], + ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",1857.76,1,2,2,47049,4356,390,63,5], + ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,23,0,0,30194,6136,0,2], + ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,34,0,0,6414,30997,45,11] ], "enemies": { - "enemy_1005_yokai_3": [1,0,1,11629,68252], + "enemy_1005_yokai_3": [1,0,1,18035,101682], "enemy_9010_acpupp": [2,2,0,19404,86026], - "enemy_9021_acduml_2": [1,0,0,6962,276122], - "enemy_9022_acdumm": [1,0,0,3379,0], - "enemy_9023_acdums": [10,9,0,138776,342] + "enemy_9021_acduml_2": [1,0,0,5561,276630], + "enemy_9022_acdumm": [1,0,0,3192,0], + "enemy_9023_acdums": [10,9,0,126299,357] }, - "hooks": [1,33,6000,650,650,2402,2852,7246,21,22,33,33,0,9568,105,16,15,1,1,65,0,27,2], - "events": {"atk":973,"deploy":18,"die":21,"dmg":2838,"engage":7,"fx":844,"heal":298,"leak":1,"skill":69,"spawn":25,"status":303}, - "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}, - "snaps": "65ab7720 4d0f9bc6 b175a7ad decfc6d9 b1fdbc1b 7b25f64c 67b47291 502d1b71 8861598b 7112c963 3170b708 8e72387d c384da93 0d0d33a6 19ea6be5 8daa9858 108f2f2f 471283be f8dc4d37 8c04e516" + "hooks": [1,36,6000,638,638,2467,2898,4848,25,26,37,36,0,9821,145,20,15,1,1,67,0,28,9], + "events": {"atk":954,"deploy":21,"die":25,"dmg":2888,"engage":7,"fx":947,"heal":263,"leak":1,"skill":75,"spawn":25,"status":338}, + "fx": {"appear":2,"bandage":11,"beam":21,"block":35,"burst":28,"disappear":2,"dodge":9,"ember":2,"explode":516,"featherArrow":6,"flame":2,"garrisonGrant":1,"healField":2,"jungleSoul":3,"mote":47,"phase":59,"redistribute":29,"reweave":2,"riposte":4,"rockslide":3,"sandChains":1,"sandChainsCharged":4,"shieldBreak":18,"slash":52,"spGift":1,"summon":8,"telegraph":2,"thorns":73,"truesilver":3,"undying":1}, + "snaps": "65ab7720 4d0f9bc6 b175a7ad decfc6d9 b1fdbc1b 7b25f64c ed1f9251 983f7608 cca2598e 0fd58d49 527eb26a dd8fcf46 aba72471 d9ca8e66 de4bc95b 4aff4604 6433c156 9f47302e d9d4b49d f50d18d6" }, "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", diff --git a/test/golden/matches.json b/test/golden/matches.json index a8c1f602..befd37aa 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -59,7 +59,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":null,"factions":["ELEMENT","DOT","REFLECTION"]}, - "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":94860,"virtualMs":68100}, + "end": {"victory":false,"roundsPassed":8,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":94412,"virtualMs":68100}, "rng": {"setup":16,"shop":161,"waves":0,"draft":0,"bots":5205,"meta":43}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -83,7 +83,7 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 0, "reward": 100, - "stats": {"activatedLayers":128,"bossDamage":105140,"dmgDealt":663269,"gold":78,"itemsEquipped":16,"kills":143,"leaks":0,"lpLost":0,"merges":0,"perfectRounds":8,"refreshes":18}, + "stats": {"activatedLayers":128,"bossDamage":105588,"dmgDealt":664772,"gold":78,"itemsEquipped":16,"kills":144,"leaks":0,"lpLost":0,"merges":0,"perfectRounds":8,"refreshes":18}, "lineup": ["3_20_a@12,3[4_07_e_a+3_04_e_b]","2_14_a@12,4","2_05_a@12,6","3_10_a@11,3","4_14_a@11,8[4_02_e_a+4_08_e_a]","1_06_a@10,3[5_02_e_a+2_03_e_a]","1_02_a@10,4[3_08_e_a+2_01_e_b]","4_11_a@10,8[4_04_e_a+1_01_e_a]"], "bonds": ["deputShip 2/40","emptyShip 1/12","kjeragShip 1/28","steadShip 0/26","swiftShip 1/18","victoriaShip 1/30"] } @@ -99,7 +99,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":26,"bossPoolLeft":0,"virtualMs":112800}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":99894,"virtualMs":112800}, "rng": {"setup":25,"shop":233,"waves":0,"draft":10,"bots":8919,"meta":6}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -148,14 +148,14 @@ "ai_0": { "alive": true, "victory": true, - "roundsPassed": 15, + "roundsPassed": 14, "eliminatedRound": null, - "lp": 26, + "lp": 0, "bandId": "band_ermengard", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 400, - "stats": {"activatedLayers":398,"bossDamage":1327500,"dmgDealt":3388979,"gold":144,"itemsEquipped":16,"kills":388,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":18}, + "stats": {"activatedLayers":398,"bossDamage":1227606,"dmgDealt":3258313,"gold":144,"itemsEquipped":16,"kills":388,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":18}, "lineup": ["6_08_a@12,8","1_17_b@11,7","4_14_a@11,8[5_03_e_a]","6_18_a@10,4[3_02_e_a+3_05_e_a]","6_02_a@10,5","4_10_a@10,6[3_02_e_b+3_08_e_b]","1_19_b@10,8[4_05_e_a]","1_08_a@9,9[1_01_e_a+1_02_e_b]"], "bonds": ["arcaneShip 2/34","kazimierzShip 0/10","kjeragShip 0/2","miraShip 1/3","preciShip 2/79","sargonShip 0/8","siracusaShip 1/224","soloShip 1/3","steadShip 0/2","suntShip 1/0","swiftShip 1/55","yanShip 0/6"] } @@ -246,7 +246,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":3046117,"virtualMs":113700}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":2839368,"virtualMs":113700}, "rng": {"setup":25,"shop":254,"waves":0,"draft":42,"bots":8253,"meta":13}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -302,7 +302,7 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":512,"bossDamage":1333883,"dmgDealt":3325553,"gold":151,"itemsEquipped":18,"kills":419,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":21}, + "stats": {"activatedLayers":512,"bossDamage":1540632,"dmgDealt":3524500,"gold":151,"itemsEquipped":18,"kills":422,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":21}, "lineup": ["4_14_b@12,8[5_01_e_a+2_01_e_b]","3_19_a@11,7[3_02_e_b]","4_20_b@11,8[3_11_e_b+3_06_e_a]","4_10_a@10,6","6_17_a@10,8[4_10_e_b+4_05_e_a]","1_08_a@9,5[1_01_e_a+1_02_e_a]","5_07_a@9,6","1_19_b@9,9[3_08_e_a+3_01_e_a]"], "bonds": ["arcaneShip 1/18","kazimierzShip 1/104","kjeragShip 0/2","miraShip 1/3","preciShip 1/80","raidShip 1/134","sargonShip 0/8","siracusaShip 1/119","soloShip 1/1","steadShip 0/10","suntShip 1/0","swiftShip 1/53"] } @@ -1320,7 +1320,7 @@ "combat": "client", "boost": {"lp":400,"layers":300}, "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":383,"bossPoolLeft":0,"virtualMs":113700}, + "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":361,"bossPoolLeft":0,"virtualMs":113700}, "rng": {"setup":25,"shop":352,"waves":0,"draft":42,"bots":10368,"meta":2}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1383,12 +1383,12 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 383, + "lp": 361, "bandId": "band_paganini", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":2528,"bossDamage":4580000,"dmgDealt":6435237,"gold":174,"itemsEquipped":21,"kills":384,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":34}, + "stats": {"activatedLayers":2528,"bossDamage":4580000,"dmgDealt":6435323,"gold":174,"itemsEquipped":21,"kills":391,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":34}, "lineup": ["2_01_a@12,6","4_02_b@12,7[5_02_e_a+1_02_e_b]","6_11_a@12,8","1_17_b@11,5[3_08_e_b+2_01_e_b]","1_19_b@11,8[1_05_e_b]","4_14_b@10,4[4_03_e_b+2_01_e_b]","2_18_b@10,5[1_02_e_a+3_04_e_a]","4_07_b@9,9[6_09_e_a+1_02_e_b]"], "bonds": ["arcaneShip 1/314","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 1/308","kjeragShip 0/310","lateranoShip 1/300","maniShip 1/300","miraShip 0/300","preciShip 2/338","raidShip 0/300","sargonShip 0/300","siracusaShip 0/306","skillfulShip 0/316","soloShip 0/307","steadShip 1/313","suntShip 2/300","swiftShip 1/355","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] } @@ -1482,10 +1482,10 @@ "eliminatedRound": null, "lp": 400, "bandId": "band_sarkazb", - "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, + "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2267,"bossDamage":6609998,"dmgDealt":23342655,"gold":149,"itemsEquipped":23,"kills":290,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, + "stats": {"activatedLayers":2267,"bossDamage":7523075,"dmgDealt":26462402,"gold":149,"itemsEquipped":23,"kills":290,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, "lineup": ["3_16_b@12,8[3_01_e_a+1_01_e_b]","4_09_b@11,5","3_11_b@11,8[3_02_e_a+6_06_e_a]","1_02_b@10,4[1_02_e_a+4_02_e_a]","5_14_a@10,8[3_03_e_a+1_05_e_a]","6_20_a@9,6","3_20_a@9,8[3_07_e_a+3_08_e_a]","6_05_a@9,9[2_04_e_a+5_02_e_b]"], "bonds": ["arcaneShip 0/316","deputShip 1/312","egirShip 0/300","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 1/343","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/343","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/319","soloShip 1/310","steadShip 1/341","suntShip 1/310","swiftShip 1/308","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] }, @@ -1496,10 +1496,10 @@ "eliminatedRound": null, "lp": 400, "bandId": "band_doberm", - "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, + "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2301,"bossDamage":7282442,"dmgDealt":25059114,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, + "stats": {"activatedLayers":2301,"bossDamage":6369365,"dmgDealt":22481097,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, "lineup": ["2_10_a@11,5[2_03_e_b+3_09_e_a]","5_03_a@11,8[1_02_e_a+5_02_e_a]","1_06_a@10,3[1_05_e_a+3_02_e_a]","3_16_a@10,4[3_03_e_b+4_04_e_b]","5_06_a@10,8[3_09_e_b+3_09_e_b]","4_21_b@9,6[4_06_e_a+3_10_e_b]","5_02_a@9,8[4_09_e_a+3_06_e_b]","1_17_b@9,9[3_08_e_a+1_01_e_a]"], "bonds": ["arcaneShip 1/327","deputShip 1/318","egirShip 0/300","emptyShip 0/300","indomShip 1/308","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/404","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/308","soloShip 0/310","steadShip 0/323","suntShip 1/304","swiftShip 0/300","victoriaShip 1/332","visiShip 0/300","yanShip 0/305"] } @@ -1515,7 +1515,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":"boss_10","factions":["DOT","INVISIBLE","REFLECTION"]}, - "end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":107915,"virtualMs":119900}, + "end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":97588,"virtualMs":119900}, "rng": {"setup":25,"shop":511,"waves":0,"draft":34,"bots":18074,"meta":23}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1596,7 +1596,7 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 3, "reward": 320, - "stats": {"activatedLayers":219,"bossDamage":248468,"dmgDealt":2618336,"gold":145,"itemsEquipped":18,"kills":245,"leaks":69,"lpLost":10,"merges":6,"perfectRounds":9,"refreshes":24}, + "stats": {"activatedLayers":219,"bossDamage":249655,"dmgDealt":2602313,"gold":145,"itemsEquipped":18,"kills":245,"leaks":69,"lpLost":10,"merges":6,"perfectRounds":9,"refreshes":24}, "lineup": ["3_13_b@12,3","5_10_a@12,7[1_02_e_b]","5_15_a@11,3[3_11_e_a]","5_02_a@11,8","3_19_b@10,3[6_01_e_b]","1_08_b@10,7[4_11_e_b+4_02_e_a]","4_16_a@10,8[4_03_e_b+3_07_e_a]","6_05_a@9,8[3_01_e_a+1_01_e_a]"], "bonds": ["arcaneShip 0/11","kazimierzShip 0/16","kjeragShip 0/8","miraShip 1/30","preciShip 1/52","sargonShip 1/24","siracusaShip 1/44","soloShip 1/0","suntShip 1/0","swiftShip 1/69","yanShip 0/13"] }, @@ -1610,7 +1610,7 @@ "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "trophies": 3, "reward": 320, - "stats": {"activatedLayers":163,"bossDamage":423617,"dmgDealt":3743301,"gold":141,"itemsEquipped":21,"kills":274,"leaks":47,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":19}, + "stats": {"activatedLayers":163,"bossDamage":432757,"dmgDealt":3761884,"gold":141,"itemsEquipped":21,"kills":274,"leaks":47,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":19}, "lineup": ["6_12_a@12,3","5_10_a@12,7","3_21_b@11,3[1_05_e_b+4_08_e_b]","5_20_a@11,8[3_08_e_b+3_07_e_b]","3_01_b@10,3[5_09_e_a+6_05_e_a]","4_16_a@10,7[4_11_e_a+4_02_e_a]","6_06_a@10,8","6_01_a@9,8"], "bonds": ["arcaneShip 0/2","deputShip 0/32","emptyShip 1/0","kazimierzShip 0/8","lateranoShip 1/79","miraShip 1/6","preciShip 1/2","raidShip 0/2","sargonShip 0/2","siracusaShip 1/62","skillfulShip 0/68","suntShip 1/14","swiftShip 0/55","visiShip 1/0"] } diff --git a/test/sim/portal-appear.test.js b/test/sim/portal-appear.test.js new file mode 100644 index 00000000..e318c48e --- /dev/null +++ b/test/sim/portal-appear.test.js @@ -0,0 +1,114 @@ +// 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'); + +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. +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 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'); +}); + +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'], + }); + 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'); +}); From f2aec39e9387a987eba95feb1c03d90a4d0887ef Mon Sep 17 00:00:00 2001 From: CXUtk Date: Thu, 8 Oct 2026 19:13:11 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feedback7=20R22-FIELDS:=20the=20Hidden=20Co?= =?UTF-8?q?re=20=E9=93=B3's=20=E7=A2=8E=E9=93=B3=E4=B9=8B=E7=B0=A7=20chase?= =?UTF-8?q?=20the=20casting=20gun=20for=20the=20whole=205=20s=20of=20?= =?UTF-8?q?=E6=9C=AB=E6=97=A5=E5=B8=83=E9=81=93=20instead=20of=20stopping?= =?UTF-8?q?=20on=20arrival;=20each=20gun=20keeps=20its=20data=20timer,=20t?= =?UTF-8?q?he=20later=20call=20takes=20the=20springs=20(PR=20#347=20by=20@?= =?UTF-8?q?CXUtk,=20its=20pair=20stagger=20and=20lock=20not=20taken);=20go?= =?UTF-8?q?lden:=20hidden-boss=5F9-pair,=20hidden-boss=5F9-solo,=20coop2-A?= =?UTF-8?q?BYSS-10-boosted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #347 by @CXUtk, applied with changes. Kept: 追逐模式 lasts the gain's whole 5 s — PRTS 假想敌:铳 末日布道 "令全场的 “碎铳之簧”获得5秒增益…增益期间切换为追逐模式", “碎铳之簧” 追逐模式 "持续追踪令其进入该形态的场上的假想敌:铳移动" (no arrival clause, unlike the 铳's own 冲锋模式, which ends "到达目标所在位置后"); data skill 2: cooldown 45, initCooldown 20, dog_duration 5, move_speed 8. kitSpring no longer ends the chase when it reaches the gun or comes within 1 tile; with no gun left on the field the spring holds still until the gain ends (until 0.2.1 that ended the chase at once). Not taken (the maintainer's decision on the Grok triage): the second gun's half-cooldown first-call delay and the cond that refused a call while any spring was chasing — neither is in the data, and the lock contradicts the handbook's 「持续召唤场上所有“碎铳之簧”向自身移动」 (a call takes every spring to its caster). Both guns keep their data timers, so on a pair field the mirrored gun's call, a tick after the first, retargets the springs for a new 5 s. Measured (notes repro-347.mjs): a spring 0.2 tile from the gun chased 0.03 s, one 5 tiles away 2.97 s; now 5.03 s both. Tests: test/content/hidden-gun-pursuit.test.js (new, 5; the arena harness and two tests are the PR's): the full 5 s next to a moving gun and with no gun left fail on the parent; the pair timing (20 s after each gun's spawn, then every 45 s, the two calls a tick apart), a mid-chase call retargeting the springs for a new 5 s and the solo timing guard against the stagger / lock. test/sim/feedback1e-healers.test.js E2 audit: the fields whose leaders / parts a healer must block run 200 s instead of 120 — on the solo 铳 field the springs now first meet a healer at 145 s (at 21 s until now). Golden (on top of the portal commit): - fields hidden-boss_9-pair: the springs spend 5 s invulnerable per call, so they take less (actrpa 855522 → 722451, actrpb 798033 → 660498, actrpc 1384683 → 1168947) and pass less on; leader left 3511007 → 3747749; p2's deaths 7 → 12 (longer tramples); still 8 / 8 kills, no leak. - fields hidden-boss_9-solo: leader left 1932856 → 2011732; the springs take less; 隐德来希's candles (5601_entlec, an enemy-side summon) 6 → 8. - matches coop2-ABYSS-10-boosted (hidden boss_9): still cleared at 800 LP; the seats' leader damage shifts (7523075 / 6369365 → 6992346 / 6900614). --- docs/SIM.md | 6 +- server/sim/content/bosses.js | 16 ++- test/content/hidden-gun-pursuit.test.js | 156 +++++++++++++++++++++ test/golden/fields.json | 177 ++++++++++++------------ test/golden/matches.json | 4 +- test/sim/feedback1e-healers.test.js | 4 +- 6 files changed, 265 insertions(+), 98 deletions(-) create mode 100644 test/content/hidden-gun-pursuit.test.js diff --git a/docs/SIM.md b/docs/SIM.md index c4658234..6b8470b0 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -205,7 +205,11 @@ 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 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 -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 destination, crates cost 1000, then Bresenham line-of-sight smoothing — research 08 §3.4). The official route stays diff --git a/server/sim/content/bosses.js b/server/sim/content/bosses.js index eb3cd4be..61b1eb3b 100644 --- a/server/sim/content/bosses.js +++ b/server/sim/content/bosses.js @@ -639,8 +639,11 @@ function kitGun(ab, e, b) { }); if (s2) list.push({ 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 - // PRTS “碎铳之簧” 追逐模式 "不进行普通攻击": `disarm` for the dash (until 0.1.3 it kept shooting while it ran) + 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). + // 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) { 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() }; @@ -708,11 +711,16 @@ function kitSpring(ab, e) { if (!P.up && P.downAt != null && b.time - P.downAt >= regen) raise(b); const d = e2.mem.ab.dash; 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 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) 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 diff --git a/test/content/hidden-gun-pursuit.test.js b/test/content/hidden-gun-pursuit.test.js new file mode 100644 index 00000000..09c8b06a --- /dev/null +++ b/test/content/hidden-gun-pursuit.test.js @@ -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); +}); diff --git a/test/golden/fields.json b/test/golden/fields.json index fa0f436c..b7a41ef2 100644 --- a/test/golden/fields.json +++ b/test/golden/fields.json @@ -1111,107 +1111,106 @@ "killed": 8, "total": 8, "unspawned": 0, - "bossHpLeft": 3511007, - "rngDraws": 1705, + "bossHpLeft": 3747749, + "rngDraws": 1619, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1689940,"bossDmg":698535,"heal":15297,"deaths":5,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3810677,"bossDmg":1348791,"heal":109383,"deaths":7,"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":3117516,"bossDmg":1090251,"heal":121649,"deaths":12,"layerGains":{}} }, "units": [ - ["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",6348.28,1,67,0,9,7512,6537,1,3], - ["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2695.89,1,11,0,8762,1179,0,9,1], - ["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1406.57,1,17,0,35012,3869,0,15,2], - ["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1165.15,1,7,2,1312507,1285,0,58,8], - ["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",0,0,9,0,19135,12418,0,23,1], - ["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1104.58,1,55,0,35830,11463,444,0,4], - ["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,0,67894,0,0,0,10], - ["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,30095,0,7111,86,3], - ["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,150260,13231,0,28,28], - ["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,60,0,22306,8566,349,73,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",2893.32,1,11,0,20047,281,0,22,1], + ["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",430.19,1,7,1,1229734,2143,0,58,8], + ["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",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,0,69036,0,0,0,11], + ["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,174620,13925,0,30,30], + ["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",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,66,4,730725,36055,0,338,3], - ["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",203.08,1,58,0,450324,11985,0,58,5], - ["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,65,0,41071,8230,3308,52,2], - ["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3430.93,1,59,1,771816,24776,0,77,3], - ["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2886.8,1,8,0,378435,1696,0,78,3], - ["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2215,1,14,0,1014033,6744,2745,288,6], - ["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,7939,78,1], - ["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1439,1,10,0,105424,0,0,132,4], - ["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1690.1,1,41,0,234825,7508,0,60,4], - ["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1592.1,1,25,0,0,14000,95391,82,2], + ["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,59,0,341242,18065,0,56,4], + ["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",0,0,55,1,514814,17168,0,62,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",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,15762,80,1], + ["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",0,0,62,0,132239,19665,0,24,3], + ["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], ["-","~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], ["-","~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], ["-","~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,4710.39,0,47,0,6744,3570,0,5,null], - ["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,48,0,1670,1538,0,22,1], - ["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,220,3142,0,9,null], - ["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,51,0,463,1322,0,7,null], - ["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,53,0,4750,270,0,10,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,0,0,57,0,68925,6922,0,11,null], - ["p1","~4","token","token_10030_mlyss_wtrman",null,null,1172,1,61,0,1025,710,538,7,1], - ["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,62,0,0,0,0,0,null], - ["p1","~2","token","token_10040_siege2_vlion",null,null,3942.24,0,63,0,0,76,0,0,null], - ["p2","~3","token","token_10019_nearl2_sword",null,null,3290.4,0,64,0,8355,3008,0,21,null], - ["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,68,0,0,0,0,0,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,0,0,48,1,1801,1538,0,22,1], + ["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,50,0,463,1322,0,7,null], + ["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,52,0,4750,270,0,10,null], + ["p1","~1","token","token_10017_skadi2_dedant",null,null,138,0,57,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], + ["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,61,0,0,385,293,0,1], + ["p1","~2","token","token_10040_siege2_vlion",null,null,3897.9,0,63,0,0,137,0,0,null], + ["p2","~3","token","token_10019_nearl2_sword",null,null,133.02,0,64,0,16707,6165,0,9,null], + ["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,67,0,0,0,0,0,null] ], "enemies": { - "enemy_1267_nhpbr_2": [6,6,0,1800,141894], - "enemy_9007_acelem": [2,2,0,15725,309587], - "enemy_9017_achunt_2": [2,0,0,54791,2088993], - "enemy_9018_actrpa": [1,0,0,30160,855522], - "enemy_9019_actrpb": [1,0,0,29590,798033], - "enemy_9020_actrpc": [1,0,0,20449,1384683] + "enemy_1267_nhpbr_2": [6,6,0,1400,141894], + "enemy_9007_acelem": [2,2,0,17143,309587], + "enemy_9017_achunt_2": [2,0,0,58691,1852251], + "enemy_9018_actrpa": [1,0,0,44364,722451], + "enemy_9019_actrpb": [1,0,0,28255,660498], + "enemy_9020_actrpc": [1,0,0,23012,1168947] }, - "hooks": [1,66,6000,1799,1799,6944,13382,2834,25,37,99,90,326,47765,2836,14,13,0,1,26,0,13,0], - "events": {"atk":2246,"deploy":47,"die":37,"dmg":12926,"engage":25,"fx":2188,"heal":930,"skill":189,"spawn":53,"status":849}, + "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":2097,"deploy":47,"die":42,"dmg":12154,"engage":23,"fx":2024,"heal":885,"skill":186,"spawn":52,"status":769}, "fx": { - "airstrike": 123, - "beam": 31, + "airstrike": 88, + "beam": 27, "blessing": 1, "blink": 5, "bombard": 72, "bombardShell": 72, - "burst": 13, + "burst": 16, "camouflage": 1, "copy": 3, "dash": 24, - "displace": 1, - "dp": 3, - "drone": 320, - "droneLock": 14, - "drones": 4, - "extraAttack": 16, - "finale": 5, + "displace": 20, + "dp": 2, + "drone": 190, + "droneLock": 9, + "drones": 3, + "extraAttack": 7, + "finale": 4, "firewall": 3, "garrisonGrant": 1, - "lightning": 116, + "lightning": 117, "link": 1141, "lock": 72, - "lure": 1, - "phase": 12, + "lure": 2, + "phase": 11, "radiantSword": 3, "revive": 3, "scorchBurst": 1, - "shieldBreak": 10, - "slash": 55, - "snow": 15, + "shieldBreak": 9, + "slash": 44, + "snow": 26, "split": 2, - "steal": 6, - "storm": 10, + "steal": 5, + "storm": 11, "summon": 5, "sunBurst": 3, - "talent": 5, - "tide": 4, + "talent": 6, + "tide": 3, "wanted": 6, "zone": 6 }, - "snaps": "2a921421 57801ce9 151fd5ae 264faece f8920de3 cef81b6f 47f4a450 cc0a8289 6b7ac4fb 81b93e22 d10e889f 97a33a13 a796916b 202ae19b 16aa629d c7afee38 08c75624 11b64823 c1f874f3 b92e528f" + "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": { "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", @@ -1228,37 +1227,35 @@ "killed": 3, "total": 3, "unspawned": 0, - "bossHpLeft": 1932856, - "rngDraws": 128, + "bossHpLeft": 2011732, + "rngDraws": 207, "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": [ - ["p1",1,"op","chess_char_5_08_b",2,"uniequip_002_horn",2775,1,1,1,368359,8434,0,127,4], - ["p1",2,"op","chess_char_5_11_b",1,"uniequip_002_demkni",4804.8,1,3,0,1265,3442,23540,46,35], - ["p1",3,"op","chess_char_5_05_b",2,"uniequip_002_ulpia",11263.65,1,33,0,413261,29216,17173,39,5], - ["p1",4,"op","chess_char_5_06_b",2,"uniequip_002_etlchi",3747,1,7,1,1380629,16819,16926,201,5], - ["p1",5,"op","chess_char_5_13_b",1,"uniequip_002_ghost2",3448.92,1,9,0,128392,14229,3714,76,3], - ["p1",6,"op","chess_char_5_07_b",2,"uniequip_002_surtr",7680,1,35,0,42625,19991,4759,13,3], - ["p1",7,"op","chess_char_5_02_b",2,"uniequip_002_titi",1439,1,5,0,0,0,0,1,1], - ["p1",8,"op","chess_char_5_03_b",2,"uniequip_002_blaze2",1148.18,1,6,0,207074,1471,1147,186,6], - ["p1",9,"op","chess_char_5_10_b",2,"uniequip_002_lisa",1480,1,32,0,14018,13299,5899,28,6], - ["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1787,1,2,1,198179,0,0,53,41], - ["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] + ["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",4784.8,1,3,0,2559,11172,34206,59,38], + ["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",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,19,1,146526,21512,5080,102,4], + ["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,0,0], + ["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,28,0,0,8783,2863,1,5], + ["p1",10,"op","chess_char_5_12_b",0,"uniequip_002_dusk",1787,1,2,1,206669,0,0,57,43] ], "enemies": { "enemy_10067_ftsjc": [1,1,0,1161,21565], - "enemy_5601_entlec": [6,5,0,0,160038], - "enemy_9007_acelem": [2,2,0,632,119439], - "enemy_9017_achunt_2": [1,0,0,32010,867144], - "enemy_9018_actrpa": [1,0,0,14160,440951], - "enemy_9019_actrpb": [1,0,0,19148,445768], - "enemy_9020_actrpc": [1,0,0,13881,698896] + "enemy_5601_entlec": [8,7,0,0,221752], + "enemy_9007_acelem": [2,2,0,1897,119439], + "enemy_9017_achunt_2": [1,0,0,61863,788268], + "enemy_9018_actrpa": [1,0,0,22333,431138], + "enemy_9019_actrpb": [1,0,0,16840,403557], + "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], - "events": {"atk":1327,"deploy":23,"die":17,"dmg":7399,"engage":10,"fx":851,"heal":385,"skill":213,"spawn":25,"status":317}, - "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}, - "snaps": "9f379cb4 794e9645 d2f1f9f3 6f045130 3ba3cbda c2dffcd7 002617f9 8eb229f1 44fa10f5 02a4f054 024d05f4 4f2b850e 264e7810 e232b25c fb23ac72 ffe30a9a 966d17a8 f21eca66 c335c5c3 dadd437f" + "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":1320,"deploy":16,"die":16,"dmg":7575,"engage":9,"fx":765,"heal":611,"skill":209,"spawn":25,"status":228}, + "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 1eab82b0 9bd21a64 94bcd685 ea068a42 11f6e172 f1d86469 8dae333a a7b0bf9e 854de036 d7f516e9 946585b3 06953c95 f04d0312 832df1bd 4f3fdc1c eec59b6e 4c10a52c 9c930ebb" }, "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", diff --git a/test/golden/matches.json b/test/golden/matches.json index befd37aa..634754e0 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -1485,7 +1485,7 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2267,"bossDamage":7523075,"dmgDealt":26462402,"gold":149,"itemsEquipped":23,"kills":290,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, + "stats": {"activatedLayers":2267,"bossDamage":6992346,"dmgDealt":24545248,"gold":149,"itemsEquipped":23,"kills":290,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, "lineup": ["3_16_b@12,8[3_01_e_a+1_01_e_b]","4_09_b@11,5","3_11_b@11,8[3_02_e_a+6_06_e_a]","1_02_b@10,4[1_02_e_a+4_02_e_a]","5_14_a@10,8[3_03_e_a+1_05_e_a]","6_20_a@9,6","3_20_a@9,8[3_07_e_a+3_08_e_a]","6_05_a@9,9[2_04_e_a+5_02_e_b]"], "bonds": ["arcaneShip 0/316","deputShip 1/312","egirShip 0/300","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 1/343","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/343","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/319","soloShip 1/310","steadShip 1/341","suntShip 1/310","swiftShip 1/308","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] }, @@ -1499,7 +1499,7 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2301,"bossDamage":6369365,"dmgDealt":22481097,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, + "stats": {"activatedLayers":2301,"bossDamage":6900614,"dmgDealt":23861530,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, "lineup": ["2_10_a@11,5[2_03_e_b+3_09_e_a]","5_03_a@11,8[1_02_e_a+5_02_e_a]","1_06_a@10,3[1_05_e_a+3_02_e_a]","3_16_a@10,4[3_03_e_b+4_04_e_b]","5_06_a@10,8[3_09_e_b+3_09_e_b]","4_21_b@9,6[4_06_e_a+3_10_e_b]","5_02_a@9,8[4_09_e_a+3_06_e_b]","1_17_b@9,9[3_08_e_a+1_01_e_a]"], "bonds": ["arcaneShip 1/327","deputShip 1/318","egirShip 0/300","emptyShip 0/300","indomShip 1/308","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/404","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/308","soloShip 0/310","steadShip 0/323","suntShip 1/304","swiftShip 0/300","victoriaShip 1/332","visiShip 0/300","yanShip 0/305"] } diff --git a/test/sim/feedback1e-healers.test.js b/test/sim/feedback1e-healers.test.js index d4d5eddc..4733801c 100644 --- a/test/sim/feedback1e-healers.test.js +++ b/test/sim/feedback1e-healers.test.js @@ -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); let healsOnLead = 0; 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(); // operators break the 碎铳之簧 shields (unblockable while shielded): a hit of the kind each shield yields to for (const e of h.b.enemies) { From f14b3a062f12773919e2526d59f134f72983e598 Mon Sep 17 00:00:00 2001 From: sganggs Date: Thu, 8 Oct 2026 19:56:44 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feedback7=20R22-FIELDS:=20=E8=80=80?= =?UTF-8?q?=E9=AA=91=E5=A3=AB=E4=B8=B4=E5=85=89's=20=E5=8D=A1=E8=A5=BF?= =?UTF-8?q?=E7=B1=B3=E5=B0=94=20redeploy=20reduction=20(garrison=20144)=20?= =?UTF-8?q?has=20no=205=20%=20floor=20=E2=80=94=20the=20multiplier=20clamp?= =?UTF-8?q?s=20at=200=20like=20every=20other=20redeploy=20multiplier,=20so?= =?UTF-8?q?=20a=20knock-out=20timer=20can=20reach=200=20s=20(GitHub=20#370?= =?UTF-8?q?);=20golden:=20unchanged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub #370 (FrogThai): 「耀骑士临光特性赋予身前干员减再部署有上限,最多减至5秒」. Cause: content/garrisons/battle.js applyReader, act1autochess_gar_eff_respawnTimeByBond, wrote redeployMul = max(0.05, 1 + respawn_time × steps), so the timer stopped at 5 % of the base time — from 192 卡西米尔 layers for 144_a (raw 0.04) and from 96 for 144_b. Measured on a 100 s base (notes repro-370.mjs): 144_a 192 / 300 layers → 5.00 s / 5.00 s, 144_b 96 / 192 / 300 → 5.00 s each. Official: data/garrisons.json garrison_144_a bb { divide_num 3, respawn_time −0.015 } 「每叠加3层,本干员再部署时间-1.5%」, 144_b −0.03 「-3%」 — no minimum key; PRTS 耀骑士临光 has no 卫戍 note on a minimum. Now max(0, …) as items/battle.js, bands, the 协防 bond and svrash; deploy.js already clamps the timer at 0: 144_a 192 / 300 layers → 4.00 s / 0 s, 144_b 96 / 192 / 300 → 4.00 s / 0 s / 0 s. A 0 s timer brings the operator back on the next tick, no error (notes probe-370-redeploy.mjs). The ASPD part (garrison 159 / 160) is untouched. Patch from the Grok triage (scratch-i1b/370.patch), the test extended with 96 layers and the 0 s return. Tests: test/content/garrisons_battle.test.js 26/26 — the new #370 test (144_a / 144_b at 9, 96, 192, 300 layers: the multiplier, the knock-out timer, the 0 s return) fails on the parent. --- server/sim/content/garrisons/battle.js | 5 ++++- test/content/garrisons_battle.test.js | 27 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/server/sim/content/garrisons/battle.js b/server/sim/content/garrisons/battle.js index 122205b8..214f7029 100644 --- a/server/sim/content/garrisons/battle.js +++ b/server/sim/content/garrisons/battle.js @@ -400,7 +400,10 @@ function applyReader(battle, it) { else battle.removeBuff(u, key); break; 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); break; case 'act2autochess_gar_eff_attrByBond_add_onstart': { diff --git a/test/content/garrisons_battle.test.js b/test/content/garrisons_battle.test.js index a4f5f83c..fbb8ec49 100644 --- a/test/content/garrisons_battle.test.js +++ b/test/content/garrisons_battle.test.js @@ -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); + } +}); + // --------------------------------------------------------------------------------------------------------------------- // 魔王 From 8d896e40ea2363dfc0df30369d2a6e90df205916 Mon Sep 17 00:00:00 2001 From: sganggs Date: Thu, 8 Oct 2026 20:14:37 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feedback7=20R22-FIELDS:=20the=20path=20tie-?= =?UTF-8?q?break=20never=20wades=20into=20more=20=E6=B7=B1=E6=B0=B4?= =?UTF-8?q?=E5=8C=BA=20than=20the=20official=20route=20=E2=80=94=20on=20?= =?UTF-8?q?=E6=88=98=E5=9C=BA#08(=E4=B8=8B=E5=8D=8A)=20the=20patrolling=20?= =?UTF-8?q?=E9=93=B3=20/=20=E5=8D=A2=E8=A5=BF=E6=81=A9=20take=20the=20offi?= =?UTF-8?q?cial=20upper=20road=20from=20(3,9)=20to=20(2,3)=20again=20(GitH?= =?UTF-8?q?ub=20#375);=20golden:=20boss-boss=5F2-pair,=20coop2-ABYSS-10-bo?= =?UTF-8?q?osted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub #375 (FrogThai): 「水图boss会从下面这条路往蓝门走,实际应该从上路走」. The walking leaders of 战场#08(下半) 涨潮控制 (act2autochess_m04) are 铳 (level_act1autochess_h07_02 route 6; the Hidden Core 铳 on act1autochess_h08_02's patrol routes) and 卢西恩 (act2autochess_h07_05 route 8): PATROL_MOVE (3,9) → (2,3) → (5,9). Measured (notes repro-375.mjs): ours 「3,9 2,9 2,6 4,6 4,4 2,4 2,3」 — the lower road through the 深水区 (3,6) / (4,6) — against the official SPFA 「3,9 5,9 5,4 2,4 2,3」 along row 5 with one 深水区 (5,6); the mirrored (3,11) → (2,17) split the same way. Cause: grid.js _buildField takes the blockable-ground preference pointer (user playtest #2) when its route crosses strictly fewer non-blockable tiles; 深水区 is non-blockable itself (no deployment), so the lower road counted 2 against the upper road's 3 (two floor tiles and one water tile) and won. Official pathfinding is the plain 4-neighbour SPFA (research 08 §3.1). Now the preference pointer is taken only when its route also crosses no more 深水区 than the official pointer's (a per-tile `deep` count beside `cost`, start excluded). Rule from the Grok triage (scratch-i1b/375.patch), re-implemented with a precomputed Grid.deepWater mask (terrain 'deepsea': tile_deepsea and tile_deepwater). Scope (notes dump-fields.mjs / check-official.mjs, every stage × normal / 联防 / boss rect × every walkable destination): 48 fields change, only on the two water maps (act2 m04 boss field; the inactive act1 m05), 149 pointers, every one back to the official pointer. The 21 accepted DEVIATIONS (战场#01 / #02 floor, #07, the inactive #05's 7 water-avoiding routes) are unchanged, as are all 154 audited gate routes and data/stages.json's groundPaths (tools/build-data.mjs --offline into a scratch dir: every data file identical). Other boss-field preference routes of m04 that cross fewer 深水区 than the official one stay. Tests: test/sim/pathing-official.test.js — two new #375 tests (the four patrol legs equal the pure official waypoints; in a real battle the 铳 climbs to row 5 and steps on the 深水区 (5,6) only), both failing on the parent; test/sim/pathing-crosscheck.test.js — the independent reference port gets the same 深水区 rule (it flagged both the patrol leg and a random-crate normal-field route of act2 m04 until then); test/sim/pathing-blockable.test.js — the WALK-leg audit compares with the equal-length routes that cross no more 深水区 than ours (the 14 accepted arena exceptions unchanged). Grid-related suites 777/777. Golden: - fields boss-boss_2-pair (铳 pair on act2autochess_m04, act1autochess_h07_02): the two 铳 patrol the upper roads; leader left 5311560 → 5301010, p1 damage up / p2 down, deaths 0 / 2 → 1 / 3; still 8 / 8 kills. - matches coop2-ABYSS-10-boosted (act2 m04; Final Assault 卢西恩, Hidden Core 铳): still cleared, team LP 800 → 615. The bots' operators stand on the lower rows (the same tiles as before); with the leaders on the upper road the Final Assault lasts 335 game s instead of 247 and the Hidden Core 638 instead of 190, both past the 150 real-second mark: the overtime drain takes 17 + 168 LP (none before), no leak. --- docs/DATA.md | 2 +- docs/SIM.md | 4 +- docs/design/network.md | 2 +- docs/research/08-waves-official.md | 5 +- server/sim/grid.js | 33 ++++++---- test/golden/fields.json | 96 ++++++++++++++--------------- test/golden/matches.json | 14 ++--- test/sim/pathing-blockable.test.js | 43 +++++++++---- test/sim/pathing-crosscheck.test.js | 14 ++++- test/sim/pathing-official.test.js | 40 +++++++++++- 10 files changed, 167 insertions(+), 86 deletions(-) diff --git a/docs/DATA.md b/docs/DATA.md index abcc2b76..f81165f8 100644 --- a/docs/DATA.md +++ b/docs/DATA.md @@ -442,7 +442,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 | | `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) | -| `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…}` | | Glyph legend (`rows`): diff --git a/docs/SIM.md b/docs/SIM.md index 6b8470b0..c5651787 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -215,7 +215,9 @@ WALK legs pathfind on the stage grid inside the rect with the official flow fiel 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 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 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 diff --git a/docs/design/network.md b/docs/design/network.md index 384b3c9c..32e81533 100644 --- a/docs/design/network.md +++ b/docs/design/network.md @@ -95,7 +95,7 @@ Piece = { uid, kind: 'chess'|'item'|'token', id, golden: bool, tier, items: [ { **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. -- 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. - 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. diff --git a/docs/research/08-waves-official.md b/docs/research/08-waves-official.md index 3b87b93a..b84dcad7 100644 --- a/docs/research/08-waves-official.md +++ b/docs/research/08-waves-official.md @@ -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 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) -crosses strictly fewer non-blockable tiles; 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 +crosses strictly fewer non-blockable tiles and no more 深水区 (GitHub #375, 0.2.2: the 深水区 is non-blockable too, and +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) → (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): diff --git a/server/sim/grid.js b/server/sim/grid.js index 8eb6404d..41bbe5dd 100644 --- a/server/sim/grid.js +++ b/server/sim/grid.js @@ -26,8 +26,7 @@ // 0.1.0: on 战场#04 活性源石 the lower-gate enemies must cross diagonally from row 9 into row 10 as officially, not // walk two tiles left of the gate and then straight up). A tile is non-blockable when it is walkable but not LOW // ground buildable for melee (floor / gate / goal / teleport / 深水区 tiles — `blockable()` false; no gate route of 战场#08 -// changed when its 深水区 became non-buildable, a few off-route smoothed steps of its boss field, rows 4–5, go around the -// water instead of cutting across it). Each field holds two candidate +// changed when its 深水区 became non-buildable). Each field holds two candidate // pointers per tile, both with the official `dist` (so the crate cost 1000 and every grid route length stay official): // * official: the plain algorithm above; // * preference (0.1.0's): the SPFA relaxes on (dist, pen) lexicographically, `pen` = non-blockable tiles on the chain @@ -36,7 +35,10 @@ // only on the tile's own raw chain between its two ends, so it never slips past a road tile beside floor its grid // route does not walk. // `next[tile]` is the official pointer unless the preference pointer leads to a route CROSSING strictly fewer -// non-blockable tiles (`cost`, counted to the destination in increasing `dist` order). A segment crosses the tiles +// non-blockable tiles (`cost`, counted to the destination in increasing `dist` order) and no more 深水区 (`deep`): 深水区 +// is non-blockable itself, so without that test the preference waded into the pool to dodge floor (GitHub #375: on +// 战场#08(下半) the patrolling 铳 / 卢西恩 took the lower road (3,9) → (2,3) through two water tiles, one floor tile fewer +// than the official upper road with one). A segment crosses the tiles // whose interior it passes through (`crossTiles`, exact geometry); one it only touches at a corner point is not crossed // — the first version counted the corner tiles there too and so bent official diagonals into L shapes (D5). On equal // counts the official pointer stays, except where the preference one merely skips it (the official waypoint lies on @@ -166,9 +168,12 @@ export class Grid { } /** 1 = walkable terrain an operator cannot block on (floor, gate, goal, teleport…): the path tie-break penalty */ this.unblockable = new Uint8Array(ROWS * COLS); + /** 1 = walkable 深水区 (terrain 'deepsea': tile_deepsea / tile_deepwater): the tie-break never walks into more of it */ + this.deepWater = new Uint8Array(ROWS * COLS); for (let k = 0; k < ROWS * COLS; k++) { const t = this.tiles[k]; this.unblockable[k] = t.pass === 'ALL' && !isBlockableTile(t) ? 1 : 0; + this.deepWater[k] = t.pass === 'ALL' && t.terrain === 'deepsea' ? 1 : 0; } /** obstacle bits per tile (OB_BLOCK | OB_CRATE) */ this.obstacle = new Uint8Array(ROWS * COLS); @@ -272,10 +277,10 @@ export class Grid { _buildField(er, ec, allowDiagonal, ignore) { const N = ROWS * COLS; - const f = { dest: -1, dist: null, parent: null, pen: null, next: null, official: null, cost: null, len: null, version: this.version, allowDiagonal, ignore }; + const f = { dest: -1, dist: null, parent: null, pen: null, next: null, official: null, cost: null, deep: null, len: null, version: this.version, allowDiagonal, ignore }; if (!this.inBounds(er, ec)) { const none = new Int32Array(N).fill(-1); - return Object.assign(f, { dist: none, parent: none, next: none, official: none, pen: new Int32Array(N), cost: new Int32Array(N) }); + return Object.assign(f, { dist: none, parent: none, next: none, official: none, pen: new Int32Array(N), cost: new Int32Array(N), deep: new Int32Array(N) }); } const dest = er * COLS + ec; f.dest = dest; @@ -300,25 +305,30 @@ export class Grid { (n) => { for (let x = n, g = N; x >= 0 && g-- > 0; x = pref.parent[x]) onChain[x] = n; }); // per tile, in increasing distance: the official pointer unless the preference one crosses strictly fewer // non-blockable tiles on the way to the destination — or as few while only skipping the official waypoint o, which - // then lies on the straight line to it and leads there (the same walk, one waypoint fewer) + // then lies on the straight line to it and leads there (the same walk, one waypoint fewer) — and no more 深水区 + // than the official one (GitHub #375) const order = []; for (let k = 0; k < N; k++) if (dist[k] >= 0) order.push(k); order.sort((a, b) => dist[a] - dist[b] || a - b); const through = (a, b) => { let n = -unb[a]; crossTiles(a, b, (r, c) => { n += unb[r * COLS + c]; }); return n; }; + const deepW = this.deepWater; + const wet = (a, b) => { let n = -deepW[a]; crossTiles(a, b, (r, c) => { n += deepW[r * COLS + c]; }); return n; }; const next = new Int32Array(N).fill(-1); const cost = new Int32Array(N); + const deep = new Int32Array(N); for (const k of order) { const o = nextO[k], p = nextP[k]; if (o < 0) continue; // the destination - let use = o, best = through(k, o) + cost[o]; + let use = o, best = through(k, o) + cost[o], bestWet = wet(k, o) + deep[o]; if (p >= 0 && p !== o) { - const cp = through(k, p) + cost[p]; - if (cp < best || (cp === best && next[o] === p && onSegment(k, o, p))) { use = p; best = cp; } + const cp = through(k, p) + cost[p], wp = wet(k, p) + deep[p]; + if (wp <= bestWet && (cp < best || (cp === best && next[o] === p && onSegment(k, o, p)))) { use = p; best = cp; bestWet = wp; } } next[k] = use; cost[k] = best; + deep[k] = bestWet; } - return Object.assign(f, { dist, parent: pref.parent, pen: pref.pen, next, official: nextO, cost }); + return Object.assign(f, { dist, parent: pref.parent, pen: pref.pen, next, official: nextO, cost, deep }); } /** @@ -434,8 +444,9 @@ export class Grid { * @property {Int32Array} pen non-blockable tiles on that raw chain (tile included, destination excluded) * @property {Int32Array} official smoothed parent of the pure official algorithm * @property {Int32Array} next the tile to walk straight toward (official, or the preference pointer when it crosses fewer - * non-blockable tiles — see the module header) + * non-blockable tiles and no more 深水区 — see the module header) * @property {Int32Array} cost non-blockable tiles the route of `next` crosses from the tile (excluded) to the destination + * @property {Int32Array} deep 深水区 tiles the route of `next` crosses from the tile (excluded) to the destination * @property {Float64Array|null} len memoised smoothed length (fieldLength) */ diff --git a/test/golden/fields.json b/test/golden/fields.json index b7a41ef2..f9dd682a 100644 --- a/test/golden/fields.json +++ b/test/golden/fields.json @@ -142,75 +142,75 @@ "killed": 8, "total": 8, "unspawned": 0, - "bossHpLeft": 5311560, - "rngDraws": 839, + "bossHpLeft": 5301010, + "rngDraws": 783, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":612448,"bossDmg":313008,"heal":16689,"deaths":0,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":937710,"bossDmg":372608,"heal":6945,"deaths":2,"layerGains":{}} + "p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":741043,"bossDmg":366117,"heal":19816,"deaths":1,"layerGains":{}}, + "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":819707,"bossDmg":330056,"heal":4781,"deaths":3,"layerGains":{}} }, "units": [ - ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,183438,1132,0,53,3], - ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,3903,11319,305,35,1], - ["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,7,0,30509,953,294,58,17], - ["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,13,0,10026,0,0,66,1], - ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,1,0,79315,0,168,32,8], - ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",2418.5,1,3,0,78933,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",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,19,2,168011,1748,1748,55,18], - ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,14119,21,3], - ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,15,0,32280,1383,55,35,2], - ["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",836.62,1,12,0,0,3919,1500,0,2], - ["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",3545.85,1,51,2,253580,496,458,287,8], - ["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",2099.31,1,8,0,4500,1149,865,24,8], - ["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",2671.35,1,52,1,112288,6746,1602,147,13], - ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,10,0,62072,0,0,54,3], - ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,2,1,303642,0,0,181,3], + ["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",3907,1,9,0,235262,2487,117,74,3], + ["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",5482.35,1,11,0,12166,13761,318,33,1], + ["p1",3,"op","chess_char_5_14_b",1,"none",2174,1,7,0,24813,953,148,16,12], + ["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,123408,0,5557,77,10], + ["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,2,56882,1768,780,100,3], + ["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",737.6,1,43,1,153989,3270,350,46,16], + ["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,5,0,0,0,12546,19,3], + ["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",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,259014,0,0,279,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,49,1,97836,2233,1258,153,10], + ["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,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",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1418.27,1,18,1,69028,3331,2517,58,3], - ["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1989,1,40,0,44456,1991,2,63,18], - ["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1626,1,6,0,88144,0,0,57,14], + ["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,62819,1991,2,81,22], + ["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",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]}, - "hooks": [1,50,6000,1312,1325,2789,2834,4850,11,26,131,125,21,35636,978,0,10,0,1,11,0,13,1], - "events": {"atk":1342,"deploy":40,"die":26,"dmg":2826,"engage":17,"fx":602,"heal":43,"skill":256,"spawn":32,"status":489}, + "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,1371,1409,2907,2944,3285,13,26,127,123,21,38164,1047,0,10,0,1,13,0,12,1], + "events": {"atk":1410,"deploy":40,"die":26,"dmg":2934,"engage":17,"fx":696,"heal":80,"skill":250,"spawn":32,"status":549}, "fx": { - "aoe": 38, + "aoe": 33, "appear": 2, - "blink": 16, - "burst": 13, + "blink": 14, + "burst": 12, "catShield": 1, - "charge": 8, + "charge": 7, "counter": 21, - "crit": 88, - "dilemma": 3, + "crit": 89, + "dilemma": 4, "disappear": 2, "dodge": 1, - "explosion": 14, - "frostNova": 18, + "explosion": 13, + "frostNova": 22, "garrisonGrant": 1, - "iceSpike": 39, - "overclock": 2, - "pull": 3, + "iceSpike": 89, + "overclock": 1, + "pull": 4, "quadShot": 2, - "ripple": 13, + "ripple": 20, "sentry": 6, "sermon": 1, - "shadowWeave": 11, - "shield": 6, - "soul": 5, - "sp": 11, + "shadowWeave": 10, + "shield": 5, + "soul": 4, + "sp": 7, "stealth": 8, - "swordRain": 96, - "swordStorm": 13, - "tornado": 3, - "tornadoPulse": 15, + "swordRain": 82, + "swordStorm": 10, + "tornado": 4, + "tornadoPulse": 20, "weightless": 4, - "zone": 138 + "zone": 197 }, - "snaps": "7917cfe4 ec53e8ac 09d8b201 823345e1 5d4f2a95 9efdd7ee 56e0addd 78c7726c 4ee3695b 20aee5b5 ba3311c0 d4d127c9 0c500cac d842272b 4cbf4a87 2525db60 e8039c1e 209c57b0 af85310c 92ca4257" + "snaps": "7917cfe4 719c3d1e 5fce9836 dcc6d1db c6372862 d28f9510 24fc36dd 51354537 24003a40 0c6277ff 0e828fa2 41a80b07 a7179da1 e40e2870 d6867511 cc2e7940 487d9e8e b4112909 c46a7eff 812e6963" }, "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", diff --git a/test/golden/matches.json b/test/golden/matches.json index 634754e0..54abcd9f 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -1404,7 +1404,7 @@ "combat": "client", "boost": {"lp":400,"layers":300}, "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":121650}, + "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":615,"bossPoolLeft":0,"virtualMs":121650}, "rng": {"setup":25,"shop":590,"waves":0,"draft":61,"bots":22075,"meta":141}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1480,12 +1480,12 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 400, + "lp": 308, "bandId": "band_sarkazb", - "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, + "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2267,"bossDamage":6992346,"dmgDealt":24545248,"gold":149,"itemsEquipped":23,"kills":290,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, + "stats": {"activatedLayers":2267,"bossDamage":5899848,"dmgDealt":17549821,"gold":149,"itemsEquipped":23,"kills":301,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":16}, "lineup": ["3_16_b@12,8[3_01_e_a+1_01_e_b]","4_09_b@11,5","3_11_b@11,8[3_02_e_a+6_06_e_a]","1_02_b@10,4[1_02_e_a+4_02_e_a]","5_14_a@10,8[3_03_e_a+1_05_e_a]","6_20_a@9,6","3_20_a@9,8[3_07_e_a+3_08_e_a]","6_05_a@9,9[2_04_e_a+5_02_e_b]"], "bonds": ["arcaneShip 0/316","deputShip 1/312","egirShip 0/300","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 1/343","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/343","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/319","soloShip 1/310","steadShip 1/341","suntShip 1/310","swiftShip 1/308","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] }, @@ -1494,12 +1494,12 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 400, + "lp": 307, "bandId": "band_doberm", - "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, + "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 8, "reward": 425, - "stats": {"activatedLayers":2301,"bossDamage":6900614,"dmgDealt":23861530,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, + "stats": {"activatedLayers":2301,"bossDamage":7980752,"dmgDealt":27393341,"gold":165,"itemsEquipped":31,"kills":276,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":38}, "lineup": ["2_10_a@11,5[2_03_e_b+3_09_e_a]","5_03_a@11,8[1_02_e_a+5_02_e_a]","1_06_a@10,3[1_05_e_a+3_02_e_a]","3_16_a@10,4[3_03_e_b+4_04_e_b]","5_06_a@10,8[3_09_e_b+3_09_e_b]","4_21_b@9,6[4_06_e_a+3_10_e_b]","5_02_a@9,8[4_09_e_a+3_06_e_b]","1_17_b@9,9[3_08_e_a+1_01_e_a]"], "bonds": ["arcaneShip 1/327","deputShip 1/318","egirShip 0/300","emptyShip 0/300","indomShip 1/308","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/404","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/308","soloShip 0/310","steadShip 0/323","suntShip 1/304","swiftShip 0/300","victoriaShip 1/332","visiShip 0/300","yanShip 0/305"] } diff --git a/test/sim/pathing-blockable.test.js b/test/sim/pathing-blockable.test.js index 90cb6f52..3b814e2c 100644 --- a/test/sim/pathing-blockable.test.js +++ b/test/sim/pathing-blockable.test.js @@ -1,7 +1,8 @@ // 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 // 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 // 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 @@ -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 - * 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 best = f.dist[s[0] * COLS + s[1]]; if (best < 0 || best >= 1000) return null; const ek = e[0] * COLS + e[1]; 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; 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(); - for (const [k, p] of cur) { + for (const [k, byWet] of cur) { if (k === ek) continue; const r = (k / COLS) | 0, c = k % COLS; for (const [dr, dc] of [[1, 0], [0, 1], [-1, 0], [0, -1]]) { const nr = r + dr, nc = c + dc; if (!g.walkable(nr, nc) || g.isCrate(nr, nc)) continue; - const nk = nr * COLS + nc, np = p + cost(nk); - if (!nx.has(nk) || nx.get(nk) > np) nx.set(nk, np); + const nk = nr * COLS + nc; + 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; @@ -97,6 +107,14 @@ function fewestNb(g, s, e, slack = 2) { 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. */ function walk(h) { 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)); const g = grids.get(key); if (!g.walkable(from[0], from[1]) || !g.walkable(to[0], to[1])) continue; - const eq = fewestNb(g, from, to, 0); - if (!eq) continue; + if (!fewestNb(g, from, to, 0)) continue; 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(' ')}`; 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) { assert.ok(rect === GEO.BOSS_RECT && got.every(arena), `${what}: a route of length ≤ ${near.best}+2 crosses only ${near.min}`); accepted.push(what); diff --git a/test/sim/pathing-crosscheck.test.js b/test/sim/pathing-crosscheck.test.js index 7325ca59..520f1c44 100644 --- a/test/sim/pathing-crosscheck.test.js +++ b/test/sim/pathing-crosscheck.test.js @@ -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 // 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 -// (on the straight line to it, leading there); "crosses" = positive-length intersection with the tile, found here by -// clipping the segment against each tile, independently of grid.js crossTiles): +// (on the straight line to it, leading there), and no more 深水区 than the official route (GitHub #375); "crosses" = +// 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, // 联防 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; @@ -56,6 +57,8 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec if (t.tileKey === 'tile_deepsea') return true; 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 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; @@ -140,12 +143,15 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec const off = spfa(false); 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 segWet = (a, b) => through(a, b).filter(([y, x]) => !(y === a[0] && x === a[1]) && deep(y, x)).length; let nxt = smooth(off, false); if (prefer) { // 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) + // and no more 深水区 than the official pointer's route const nxtP = smooth(spfa(true), true); const cost = new Map([[D, 0]]); + const wet = new Map([[D, 0]]); const chosen = new Map(); const skips = (a, o, p) => { const t = through(a, p); @@ -155,9 +161,11 @@ function official(sid, { crates: extraCrates = [], blocks: extraBlocks = [], rec if (k === D) continue; 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 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); cost.set(k, useP ? cp : co); + wet.set(k, useP ? wp : wo); } nxt = chosen; } diff --git a/test/sim/pathing-official.test.js b/test/sim/pathing-official.test.js index 9bf0c881..aefc032d 100644 --- a/test/sim/pathing-official.test.js +++ b/test/sim/pathing-official.test.js @@ -1,7 +1,8 @@ // 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 // 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 // official algorithm; the routes that still differ are listed, each avoiding floor the official route walks over. 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); }); +// 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, () => { 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 }); From 1205336a2794d6916c5d5ef69833a1c20a1eed20 Mon Sep 17 00:00:00 2001 From: sganggs Date: Thu, 8 Oct 2026 21:05:18 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feedback7=20R22-FIELDS:=20an=20enemy=20whos?= =?UTF-8?q?e=20route=20ends=20on=20a=20corner=20portal=20stays=20hidden=20?= =?UTF-8?q?3=20s=20inside=20it=20before=20it=20comes=20out=20of=20[5,10],?= =?UTF-8?q?=20like=20the=20explicit=20crossings=20of=20the=20same=20tiles?= =?UTF-8?q?=20(review=20of=20the=20#337=20commit);=20golden:=207=20fields,?= =?UTF-8?q?=204=20matches=20=E2=80=94=20solo-NORMAL-1's=20Hidden=20Core=20?= =?UTF-8?q?still=20fails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Grok review of fb7-fields (MEDIUM): portalPickup spliced DISAPPEAR and APPEAR with nothing between, so the enemy vanished on [1,3] / [1,17] and stood on [5,10] in the same tick, targetable at once, while the data's explicit crossings of the same tiles hide it: WAIT 3 s in 85 of the 95, 5 s in 9, 1 s in 1. portalPickup now splices a 3 s WAIT (PORTAL_WAIT, the modal explicit wait) between them; [ASSUMED] the 3 s, no source times this crossing. Docs: the SIM.md sentence and the portalPickup comment. Tests: test/sim/portal-appear.test.js — the two route-end tests record the test enemy's hidden spans: one crossing, entrance → [5,10], hidden 3 s (± a tick); both fail on the parent (0.000 s). 9/9. Golden (against the 0 s version; the rounds of every match unchanged): - matches solo-NORMAL-1 (Hidden Core boss_8 胄, act1autochess_h08_01_s): still failed — hiddenCleared false, team LP 0, 14 rounds — with the leader left at 63897 instead of 99894 (cleared with 26 LP in 0.2.1). The same course as with 0 s: the third vtsk, which leaked at the portal at 105 s in 0.2.1, comes out of [5,10] 3 s later, walks through the team's line and leaks at the door at 185.5 s (170.2 s before); 0–2 of the 8 operators stand between 100 and 320 s, the leader stalls near 108000 from 280 s, the overtime drain from 300 game s takes the 25 LP left by 350 s while the redeployed operators bring the leader to 63897 (notes trace-hidden*.mjs). - matches solo-HARD-1 (Hidden Core failed as before): pool left 2839368 → 2982555; solo-HARD-9-boosted (cleared): team LP 361 → 376 (383 in 0.2.1); coop2-NORMAL-14-standins (Final Assault lost as before): pool left 97588 → 108473. - fields boss-boss_2-solo (leader left 2763024 → 2757148), boss-boss_4-solo (3759921 → 3757752), boss-boss_6-pair (killed 6 → 5, one hllevi_2 still walking at the 200 s cap, both players still perfect; leader 6972383 → 6932482), boss-boss_6-solo (3904791 → 3907393), hidden-boss_8-pair (the vtsk that comes back near the cap is still inside the portal at 200 s: one appear fewer, the last snapshot hash), hidden-boss_8-solo (killed 8 → 6, leaks 10 → 12), hidden-boss_10-solo (killed 11 → 10, leader left 7323370 → 7333760). - unchanged by the wait: solo-FUNNY-2, coop2-ABYSS-10-boosted, the other fields, and the roster / bonds / standins / diy families. --- docs/SIM.md | 4 +- server/sim/ai.js | 15 ++- test/golden/fields.json | 236 ++++++++++++++++----------------- test/golden/matches.json | 20 +-- test/sim/portal-appear.test.js | 23 ++++ 5 files changed, 161 insertions(+), 137 deletions(-) diff --git a/docs/SIM.md b/docs/SIM.md index c5651787..d326279f 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -193,8 +193,8 @@ twins spell the `appear` out), so normalizeRoute re-inserts the exit `appear` ah 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) and walks on to the blue door on the entrance's side, where it leaks (ai.js portalPickup; [ASSUMED] the -pairing and no wait inside this portal). +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 `Battle._bountyPayee`: the player of the operator or summon that dealt the blow, if that player is in the battle; any diff --git a/server/sim/ai.js b/server/sim/ai.js index db73ed83..a078eb9c 100644 --- a/server/sim/ai.js +++ b/server/sim/ai.js @@ -701,13 +701,14 @@ function advanceRoute(b, e, dt, R, standing = false) { * 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 → 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. - * [ASSUMED] the pairing (no tile carries a link) and no wait inside this portal (the explicit crossings write their - * own: 3 s in 85 of the 95, 5 s in 9, 1 s in 1). + * 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; @@ -716,7 +717,7 @@ function portalPickup(b, e) { 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: 'appear', r: out[0], c: out[1] }); + 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)); diff --git a/test/golden/fields.json b/test/golden/fields.json index f9dd682a..836b19a7 100644 --- a/test/golden/fields.json +++ b/test/golden/fields.json @@ -227,27 +227,27 @@ "killed": 5, "total": 7, "unspawned": 0, - "bossHpLeft": 2763024, - "rngDraws": 127, + "bossHpLeft": 2757148, + "rngDraws": 130, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":453723,"bossDmg":230580,"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": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2383.55,1,18,0,5388,19443,15985,82,0], - ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,4,0,86351,0,0,79,4], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,6,0,77745,6115,3711,91,4], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2118,1,7,1,23391,7108,1936,80,7], + ["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,89239,0,0,82,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,23474,7108,1915,82,7], ["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,23153,0,0,12,2], + ["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",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",2178,1,1,0,75476,0,1129,89,6], - ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",695.86,1,3,0,0,1448,1057,0,3], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,0,16195,34,3] + ["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",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,16224,34,3] ], - "enemies": {"enemy_1249_lysdb_2":[5,5,0,8138,107495],"enemy_1430_lrrook":[2,0,2,0,120652],"enemy_9017_achunt":[1,0,0,23716,236976]}, - "hooks": [1,19,6000,591,591,1390,1989,5497,6,8,33,32,0,15126,113,5,8,2,1,8,0,6,2], - "events": {"atk":642,"deploy":11,"die":6,"dmg":1979,"engage":7,"fx":290,"heal":37,"leak":2,"skill":68,"spawn":18,"status":509}, - "fx": {"appear":2,"bandage":3,"block":9,"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,"sandChainsCharged":1,"shieldBreak":15,"slash":38,"spGift":5,"thorns":21,"truesilver":3}, - "snaps": "a42f06d2 4a40e022 e95a4901 abb693b9 1c238b4b afb89449 d6c6b3df c846d6b3 f0cff710 d31f361e 6222a056 6c7abdc9 c7c74721 0757ffc7 91acf075 8e9b73d0 f30d9021 dae03804 f0c86afa 248b5f7a" + "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,595,595,1368,1966,5211,6,8,32,32,0,15401,91,5,8,2,1,8,0,6,2], + "events": {"atk":638,"deploy":11,"die":6,"dmg":1956,"engage":7,"fx":289,"heal":37,"leak":2,"skill":67,"spawn":18,"status":465}, + "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 7c93c566 120f2baa 5eba2341 475bd0e1 a82180cf 2ec0f97d b5acfe2a 942750af b2587ba1 f8bb84fe 0dee9c0d c305f7f7" }, "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", @@ -516,29 +516,29 @@ "killed": 5, "total": 7, "unspawned": 0, - "bossHpLeft": 3759921, - "rngDraws": 90, + "bossHpLeft": 3757752, + "rngDraws": 87, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":5,"total":7,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":732206,"bossDmg":440079,"heal":93600,"deaths":7,"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": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",2710.93,1,23,2,19548,12098,13170,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",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,21,3,202794,41516,12403,43,1], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2350.14,1,9,0,2748,11517,0,5,1], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",2061.88,1,24,0,175575,6029,0,120,3], + ["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",2350.14,1,9,0,785,9979,0,1,1], + ["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,25,0,234028,2838,0,104,5], - ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",1545.34,1,11,0,0,4444,19915,29,2], - ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",1748.63,1,26,0,54944,7314,487,76,5], - ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1394.71,1,3,0,0,6735,7448,0,3], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,5,0,0,4444,25541,27,5], + ["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",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,6735,7186,0,3], + ["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], ["-","~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,36058,184632],"enemy_1521_dslily":[1,0,0,69595,440079]}, - "hooks": [1,26,6000,558,558,958,1130,8107,12,14,30,30,0,13499,128,20,8,2,1,17,0,28,3], - "events": {"atk":764,"deploy":16,"die":12,"dmg":1112,"engage":7,"fx":339,"heal":93,"leak":2,"skill":63,"spawn":20,"status":483}, - "fx": {"appear":2,"bandage":5,"block":15,"burst":28,"camouflage":1,"disappear":2,"dodge":3,"featherArrow":5,"flame":1,"garrisonGrant":1,"grow":1,"healField":2,"jungleSoul":1,"mote":61,"redistribute":45,"reweave":3,"riposte":2,"rockfall":18,"rockslide":5,"sandChains":5,"shieldBreak":18,"slash":48,"spGift":4,"tentacle":6,"thorns":48,"tide":5,"truesilver":3,"undying":1}, - "snaps": "b3cecd10 d3d68301 c10d8554 c91066bb 2ac0bbdd cb65c8b1 e357c87a 1cfd4db1 fbb2d966 235fafe1 061ad690 dabccf6c b8e37a8a 8618ace2 44fc44ec 4fbab63c 309205ea 3215de21 1ce4f962 d787882b" + "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,26,6000,551,551,949,1120,8051,12,14,30,30,0,13414,126,18,8,2,1,17,0,28,2], + "events": {"atk":754,"deploy":16,"die":12,"dmg":1102,"engage":7,"fx":339,"heal":91,"leak":2,"skill":63,"spawn":20,"status":479}, + "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 842efc1f 73ccb5e1 7cbe283e 83895aff b84dfc8e 65315bfe 89187e2c 11947687 dc8072f3 31f16379 3c0d78cb cf38d801" }, "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", @@ -703,35 +703,35 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 6, + "killed": 5, "total": 6, "unspawned": 0, - "bossHpLeft": 6972383, - "rngDraws": 697, + "bossHpLeft": 6932482, + "rngDraws": 708, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":6,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1061526,"bossDmg":492176,"heal":2876,"deaths":5,"layerGains":{}}, - "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1196635,"bossDmg":495441,"heal":580,"deaths":19,"layerGains":{}} + "p1": {"killed":5,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1096623,"bossDmg":528737,"heal":2876,"deaths":5,"layerGains":{}}, + "p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1199975,"bossDmg":498782,"heal":580,"deaths":19,"layerGains":{}} }, "units": [ ["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",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",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,373439,0,551,84,16], - ["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,13,0,197565,0,0,73,6], + ["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",1170,1,7,1,407448,0,551,83,18], + ["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,24193,1629,0,43,2], ["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,4316,3220,2325,3,1], - ["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,9,1,65490,0,0,48,3], + ["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",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",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,61,0,60730,13682,40,128,7], - ["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2777.6,1,14,0,200340,0,0,114,6], - ["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",1561,1,8,1,624669,0,0,180,3], + ["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,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",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",2232,1,47,0,48350,2232,0,106,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",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], @@ -739,24 +739,24 @@ ], "enemies": { "enemy_10028_vtswd": [3,3,0,0,0], - "enemy_10029_vtshld": [6,6,0,0,0], - "enemy_10030_vtwand": [4,4,0,0,0], - "enemy_10085_hllevi_2": [2,2,0,0,1069932], + "enemy_10029_vtshld": [5,5,0,0,0], + "enemy_10030_vtwand": [5,5,0,0,0], + "enemy_10085_hllevi_2": [2,1,0,0,1068467], "enemy_1010_demon": [4,4,0,12402,200612], - "enemy_9032_aclionk": [1,0,0,47314,987617] + "enemy_9032_aclionk": [1,0,0,47314,1027518] }, - "hooks": [1,65,6000,1150,1164,2276,2290,359,43,45,104,99,0,32169,744,8,20,0,1,31,0,2,0], - "events": {"atk":1233,"deploy":45,"die":45,"dmg":2222,"engage":15,"fx":730,"heal":11,"skill":203,"spawn":42,"status":1113}, + "hooks": [1,65,6000,1150,1164,2296,2310,359,42,44,106,101,0,32028,744,8,20,0,1,30,0,2,0], + "events": {"atk":1232,"deploy":45,"die":44,"dmg":2254,"engage":15,"fx":761,"heal":11,"skill":207,"spawn":42,"status":1102}, "fx": { "aoe": 44, "appear": 6, "blink": 2, "burst": 2, - "crit": 108, + "crit": 109, "dilemma": 6, "disappear": 6, "displace": 19, - "equip": 17, + "equip": 18, "explosion": 4, "frostNova": 9, "garrisonGrant": 1, @@ -770,7 +770,7 @@ "shadowWeave": 5, "shield": 1, "soul": 2, - "sp": 16, + "sp": 19, "stealth": 6, "sword": 1, "swordRain": 26, @@ -778,11 +778,11 @@ "telegraph": 21, "tornado": 5, "tornadoPulse": 20, - "vest": 6, + "vest": 5, "weightless": 6, - "zone": 312 + "zone": 339 }, - "snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 f03b4c62 86a1deba 85583d64 215fecf5 ccd39d46 4a08d997 af901ca4 08ece609 e3643313 6f0df0ee 63a32534 86450a7b fdbbbdc8 f239ab01 bac8ff90" + "snaps": "8ef2441b ee06d28e 0f318fc5 8e4156fd 466da928 f03b4c62 86a1deba 85583d64 215fecf5 ccd39d46 4a08d997 af901ca4 08ece609 c016740f f58ba6a3 18374633 a5ab4ed2 c4cfd501 44869d7f c8302bc8" }, "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", @@ -799,21 +799,21 @@ "killed": 8, "total": 10, "unspawned": 0, - "bossHpLeft": 3904791, - "rngDraws": 52, + "bossHpLeft": 3907393, + "rngDraws": 54, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":8,"total":10,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":195265,"bossDmg":75209,"heal":6651,"deaths":17,"layerGains":{}}}, + "players": {"p1":{"killed":8,"total":10,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":164010,"bossDmg":72607,"heal":1088,"deaths":18,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",3513,1,10,3,9,0,0,9,0], - ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",6104,1,11,0,9,0,0,9,1], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",2811,1,12,2,12627,35,847,19,2], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,48,0,465,10590,0,34,3], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,49,0,70149,22768,931,30,5], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",1419,1,7,2,58468,0,0,52,7], + ["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,1,8,0,0,8,1], + ["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",0,0,48,1,790,10590,0,30,2], + ["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,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",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",0,0,45,1,53539,9209,114,41,3], - ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,46,0,0,10071,1886,0,1], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,9,0,0,0,2872,4,1], + ["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,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], ["-","~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], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null], @@ -823,17 +823,17 @@ ], "enemies": { "enemy_10028_vtswd": [3,3,0,0,0], - "enemy_10029_vtshld": [5,5,0,0,0], + "enemy_10029_vtshld": [4,4,0,0,0], "enemy_10030_vtwand": [2,2,0,0,0], - "enemy_10085_hllevi_2": [2,0,2,0,102074], - "enemy_1197_sfshu": [2,2,0,17,17916], + "enemy_10085_hllevi_2": [2,0,2,0,73421], + "enemy_1197_sfshu": [2,2,0,383,17916], "enemy_1198_msfshu": [6,6,0,0,66], - "enemy_9032_aclionk": [1,0,0,52638,75209] + "enemy_9032_aclionk": [1,0,0,45814,72607] }, - "hooks": [1,50,6000,211,211,362,559,1264,35,37,23,23,0,6727,100,1,21,2,1,25,0,0,3], - "events": {"atk":298,"deploy":23,"die":35,"dmg":273,"engage":7,"fx":104,"heal":5,"leak":2,"skill":48,"spawn":37,"status":274}, - "fx": {"appear":4,"bandage":1,"disappear":4,"dodge":3,"equip":12,"featherArrow":7,"flame":3,"garrisonGrant":1,"jungleSoul":2,"mote":10,"redistribute":4,"reweave":1,"rockslide":1,"sandChains":3,"slash":19,"summon":2,"sword":3,"telegraph":14,"truesilver":5,"vest":5}, - "snaps": "3e08ed3d 2a259699 77c4f1c2 f8300f5c 0da728a3 74bd5834 472e12a4 7db18cf8 403525d1 cc9436e7 8b639112 609ef73d 54e889e7 f8c0662d 8e3f01be 58e300b6 a655b3e3 6e779450 0f89e048 cb188754" + "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":212,"deploy":24,"die":35,"dmg":238,"engage":7,"fx":94,"leak":2,"skill":38,"spawn":36,"status":77}, + "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 2a259699 77c4f1c2 4c892736 5fd1d086 3f7ba93d 93347cb4 ca27755e 08b50f0d 6ce4db08 7c1cd809 d606de7e 2d3ad538 1b4509ed 6b654423 70fbad66 91d8190b 63b4149b 78cf40b8 0e5a361a" }, "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", @@ -1000,10 +1000,10 @@ "enemy_9016_acstmr": [8,7,0,0,150] }, "hooks": [1,92,6000,904,910,2960,3146,7480,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":653,"heal":136,"skill":208,"spawn":58,"status":990}, + "events": {"atk":1083,"deploy":56,"die":74,"dmg":3143,"engage":18,"fx":652,"heal":136,"skill":208,"spawn":58,"status":990}, "fx": { "aoe": 42, - "appear": 8, + "appear": 7, "beam": 48, "blink": 16, "burst": 5, @@ -1044,7 +1044,7 @@ "weightless": 3, "zone": 180 }, - "snaps": "cac31b2c d1e28667 87626189 11a56b43 640cf8cf 9a92be69 12784d9a 195ea951 99de9374 65451b7e 90846f89 9bcf64aa 0cf3dfb9 5737ca93 68f962d8 98c8303b e5af0a0e 3c947908 ce14aa80 4fc6091d" + "snaps": "cac31b2c d1e28667 87626189 11a56b43 640cf8cf 9a92be69 12784d9a 195ea951 99de9374 65451b7e 90846f89 9bcf64aa 0cf3dfb9 5737ca93 68f962d8 98c8303b e5af0a0e 3c947908 ce14aa80 a7bc36d6" }, "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", @@ -1058,23 +1058,23 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 8, + "killed": 6, "total": 19, "unspawned": 0, - "bossHpLeft": 7008424, + "bossHpLeft": 7007993, "rngDraws": 122, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":8,"total":19,"perfect":false,"leaks":10,"leaksUncounted":0,"coins":0,"dmg":237408,"bossDmg":191576,"heal":29339,"deaths":24,"layerGains":{}}}, + "players": {"p1":{"killed":6,"total":19,"perfect":false,"leaks":12,"leaksUncounted":0,"coins":0,"dmg":236889,"bossDmg":192007,"heal":29302,"deaths":24,"layerGains":{}}}, "units": [ ["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,35,3,12364,18678,6470,13,5], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",0,0,7,1,20,2811,0,12,1], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",0,0,59,1,99,11440,0,15,1], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",0,0,11,3,189596,3399,140,65,1], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,29,0,1903,2838,0,6,0], - ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",0,0,60,0,0,8993,12523,39,6], + ["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,10,2811,0,8,1], + ["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",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,3718,2838,0,6,0], + ["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",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",0,0,57,0,0,11646,3157,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,55,0,0,5960,6111,15,1], ["-","~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], @@ -1082,19 +1082,19 @@ ["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null] ], "enemies": { - "enemy_10027_vtsk": [3,0,3,35787,17457], + "enemy_10027_vtsk": [3,0,3,34423,16521], "enemy_1005_yokai": [4,1,2,0,1433], - "enemy_1197_sfshu": [3,3,0,856,26874], - "enemy_1198_msfshu": [9,4,5,0,57], - "enemy_9013_acstmk_2": [1,0,0,34921,191576], + "enemy_1197_sfshu": [3,3,0,649,26874], + "enemy_1198_msfshu": [9,2,7,0,43], + "enemy_9013_acstmk_2": [1,0,0,36456,192007], "enemy_9014_acstma": [4,3,0,0,0], "enemy_9015_acstmb": [4,3,0,5808,0], "enemy_9016_acstmr": [7,6,0,0,11] }, - "hooks": [1,63,6000,254,254,409,404,904,44,54,17,18,0,3859,54,7,35,10,1,39,0,0,0], - "events": {"atk":306,"deploy":24,"die":44,"dmg":397,"engage":7,"fx":173,"heal":80,"leak":10,"skill":36,"spawn":49,"status":162}, - "fx": {"appear":11,"bandage":1,"beam":31,"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": "cf292ec5 7036981d ce112fc3 25f0eb93 70c3bccc e26c17f4 00a4d474 544cd08f aa7be5f8 c5be8afe f91d2b31 046a0906 0349b50e e6ee0388 5018349f 33803746 6f6b44c6 61b9c230 1fc2a162 0c040988" + "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":287,"deploy":24,"die":42,"dmg":379,"engage":7,"fx":174,"heal":78,"leak":12,"skill":36,"spawn":49,"status":159}, + "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": "cf292ec5 7036981d ce112fc3 25f0eb93 4b92b2c1 1f4271d1 92c37ba3 871b7c96 b1b343a4 34726b3b 63978219 c0f49c1c 4446319f 390d9c72 1bba6c43 dcd75026 5d623cac 07a7e407 a4443c8e a1aa7067" }, "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", @@ -1372,36 +1372,36 @@ "time": 200, "ticks": 6000, "reason": "forced", - "killed": 11, + "killed": 10, "total": 13, "unspawned": 0, - "bossHpLeft": 7323370, - "rngDraws": 166, + "bossHpLeft": 7333760, + "rngDraws": 139, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":11,"total":13,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":464694,"bossDmg":276630,"heal":131305,"deaths":14,"layerGains":{}}}, + "players": {"p1":{"killed":10,"total":13,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":448196,"bossDmg":266240,"heal":121244,"deaths":16,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_4_17_b",1,"uniequip_002_hsguma",0,0,29,5,86,7026,0,29,0], - ["p1",2,"op","chess_char_4_18_b",1,"uniequip_002_mudrok",0,0,25,1,24,20856,8648,22,3], - ["p1",3,"op","chess_char_4_23_b",2,"uniequip_002_gvial2",3972.12,1,36,1,135461,22114,1894,138,3], - ["p1",4,"op","chess_char_4_19_b",2,"uniequip_002_flamtl",2794.81,1,30,1,24,19328,0,20,2], - ["p1",5,"op","chess_char_4_22_b",2,"uniequip_002_svrash",3905.8,1,35,1,190741,38172,6349,92,3], - ["p1",6,"op","chess_char_4_20_b",2,"uniequip_002_fartth",0,0,31,0,91309,2838,0,56,6], - ["p1",7,"op","chess_char_4_21_b",1,"uniequip_002_plosis",2920.8,1,24,0,0,22500,76891,97,2], - ["p1",8,"op","chess_char_4_24_b",1,"uniequip_002_billro",1857.76,1,2,2,47049,4356,390,63,5], - ["p1",9,"op","chess_char_4_25_b",2,"uniequip_002_cetsyr",1671,1,23,0,0,30194,6136,0,2], - ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",1522,1,34,0,0,6414,30997,45,11] + ["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",0,0,25,0,30,21486,9278,25,5], + ["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",0,0,34,1,24,20162,0,21,1], + ["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",0,0,30,0,76234,2838,0,53,6], + ["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",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,26222,3713,0,2], + ["p1",10,"op","chess_char_4_26_b",0,"uniequip_002_bldsk",0,0,3,0,0,7174,29294,42,11] ], "enemies": { - "enemy_1005_yokai_3": [1,0,1,18035,101682], + "enemy_1005_yokai_3": [1,0,1,18962,95601], "enemy_9010_acpupp": [2,2,0,19404,86026], - "enemy_9021_acduml_2": [1,0,0,5561,276630], - "enemy_9022_acdumm": [1,0,0,3192,0], - "enemy_9023_acdums": [10,9,0,126299,357] + "enemy_9021_acduml_2": [1,0,0,4772,266240], + "enemy_9022_acdumm": [1,0,0,2812,0], + "enemy_9023_acdums": [10,8,0,122384,328] }, - "hooks": [1,36,6000,638,638,2467,2898,4848,25,26,37,36,0,9821,145,20,15,1,1,67,0,28,9], - "events": {"atk":954,"deploy":21,"die":25,"dmg":2888,"engage":7,"fx":947,"heal":263,"leak":1,"skill":75,"spawn":25,"status":338}, - "fx": {"appear":2,"bandage":11,"beam":21,"block":35,"burst":28,"disappear":2,"dodge":9,"ember":2,"explode":516,"featherArrow":6,"flame":2,"garrisonGrant":1,"healField":2,"jungleSoul":3,"mote":47,"phase":59,"redistribute":29,"reweave":2,"riposte":4,"rockslide":3,"sandChains":1,"sandChainsCharged":4,"shieldBreak":18,"slash":52,"spGift":1,"summon":8,"telegraph":2,"thorns":73,"truesilver":3,"undying":1}, - "snaps": "65ab7720 4d0f9bc6 b175a7ad decfc6d9 b1fdbc1b 7b25f64c ed1f9251 983f7608 cca2598e 0fd58d49 527eb26a dd8fcf46 aba72471 d9ca8e66 de4bc95b 4aff4604 6433c156 9f47302e d9d4b49d f50d18d6" + "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":938,"deploy":20,"die":26,"dmg":2659,"engage":7,"fx":895,"heal":257,"leak":1,"skill":75,"spawn":25,"status":361}, + "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 b1fdbc1b 7b25f64c 74154bc4 6da4a2dc 96949f94 e0447098 98bf97c3 a16b4f8c 3e2f8172 69c051ba b1a16f74 1174eaca e5178290 04548081 b5216a94 9870af27" }, "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", diff --git a/test/golden/matches.json b/test/golden/matches.json index 54abcd9f..4d93c7a7 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -99,7 +99,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":99894,"virtualMs":112800}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":63897,"virtualMs":112800}, "rng": {"setup":25,"shop":233,"waves":0,"draft":10,"bots":8919,"meta":6}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -155,7 +155,7 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 400, - "stats": {"activatedLayers":398,"bossDamage":1227606,"dmgDealt":3258313,"gold":144,"itemsEquipped":16,"kills":388,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":18}, + "stats": {"activatedLayers":398,"bossDamage":1263603,"dmgDealt":3322495,"gold":144,"itemsEquipped":16,"kills":389,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":18}, "lineup": ["6_08_a@12,8","1_17_b@11,7","4_14_a@11,8[5_03_e_a]","6_18_a@10,4[3_02_e_a+3_05_e_a]","6_02_a@10,5","4_10_a@10,6[3_02_e_b+3_08_e_b]","1_19_b@10,8[4_05_e_a]","1_08_a@9,9[1_01_e_a+1_02_e_b]"], "bonds": ["arcaneShip 2/34","kazimierzShip 0/10","kjeragShip 0/2","miraShip 1/3","preciShip 2/79","sargonShip 0/8","siracusaShip 1/224","soloShip 1/3","steadShip 0/2","suntShip 1/0","swiftShip 1/55","yanShip 0/6"] } @@ -246,7 +246,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":2839368,"virtualMs":113700}, + "end": {"victory":true,"roundsPassed":14,"hiddenReached":true,"hiddenCleared":false,"reason":"victory","teamLp":0,"bossPoolLeft":2982555,"virtualMs":113700}, "rng": {"setup":25,"shop":254,"waves":0,"draft":42,"bots":8253,"meta":13}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -302,7 +302,7 @@ "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":512,"bossDamage":1540632,"dmgDealt":3524500,"gold":151,"itemsEquipped":18,"kills":422,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":21}, + "stats": {"activatedLayers":512,"bossDamage":1397445,"dmgDealt":3385108,"gold":151,"itemsEquipped":18,"kills":420,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":21}, "lineup": ["4_14_b@12,8[5_01_e_a+2_01_e_b]","3_19_a@11,7[3_02_e_b]","4_20_b@11,8[3_11_e_b+3_06_e_a]","4_10_a@10,6","6_17_a@10,8[4_10_e_b+4_05_e_a]","1_08_a@9,5[1_01_e_a+1_02_e_a]","5_07_a@9,6","1_19_b@9,9[3_08_e_a+3_01_e_a]"], "bonds": ["arcaneShip 1/18","kazimierzShip 1/104","kjeragShip 0/2","miraShip 1/3","preciShip 1/80","raidShip 1/134","sargonShip 0/8","siracusaShip 1/119","soloShip 1/1","steadShip 0/10","suntShip 1/0","swiftShip 1/53"] } @@ -1320,7 +1320,7 @@ "combat": "client", "boost": {"lp":400,"layers":300}, "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":361,"bossPoolLeft":0,"virtualMs":113700}, + "end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":376,"bossPoolLeft":0,"virtualMs":113700}, "rng": {"setup":25,"shop":352,"waves":0,"draft":42,"bots":10368,"meta":2}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1383,12 +1383,12 @@ "victory": true, "roundsPassed": 15, "eliminatedRound": null, - "lp": 361, + "lp": 376, "bandId": "band_paganini", "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, "trophies": 0, "reward": 425, - "stats": {"activatedLayers":2528,"bossDamage":4580000,"dmgDealt":6435323,"gold":174,"itemsEquipped":21,"kills":391,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":34}, + "stats": {"activatedLayers":2528,"bossDamage":4580000,"dmgDealt":6435314,"gold":174,"itemsEquipped":21,"kills":390,"leaks":1,"lpLost":1,"merges":6,"perfectRounds":12,"refreshes":34}, "lineup": ["2_01_a@12,6","4_02_b@12,7[5_02_e_a+1_02_e_b]","6_11_a@12,8","1_17_b@11,5[3_08_e_b+2_01_e_b]","1_19_b@11,8[1_05_e_b]","4_14_b@10,4[4_03_e_b+2_01_e_b]","2_18_b@10,5[1_02_e_a+3_04_e_a]","4_07_b@9,9[6_09_e_a+1_02_e_b]"], "bonds": ["arcaneShip 1/314","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 1/308","kjeragShip 0/310","lateranoShip 1/300","maniShip 1/300","miraShip 0/300","preciShip 2/338","raidShip 0/300","sargonShip 0/300","siracusaShip 0/306","skillfulShip 0/316","soloShip 0/307","steadShip 1/313","suntShip 2/300","swiftShip 1/355","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"] } @@ -1515,7 +1515,7 @@ "combat": "client", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_5","hiddenBossId":"boss_10","factions":["DOT","INVISIBLE","REFLECTION"]}, - "end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":97588,"virtualMs":119900}, + "end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":108473,"virtualMs":119900}, "rng": {"setup":25,"shop":511,"waves":0,"draft":34,"bots":18074,"meta":23}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ @@ -1596,7 +1596,7 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 3, "reward": 320, - "stats": {"activatedLayers":219,"bossDamage":249655,"dmgDealt":2602313,"gold":145,"itemsEquipped":18,"kills":245,"leaks":69,"lpLost":10,"merges":6,"perfectRounds":9,"refreshes":24}, + "stats": {"activatedLayers":219,"bossDamage":244606,"dmgDealt":2624755,"gold":145,"itemsEquipped":18,"kills":247,"leaks":69,"lpLost":10,"merges":6,"perfectRounds":9,"refreshes":24}, "lineup": ["3_13_b@12,3","5_10_a@12,7[1_02_e_b]","5_15_a@11,3[3_11_e_a]","5_02_a@11,8","3_19_b@10,3[6_01_e_b]","1_08_b@10,7[4_11_e_b+4_02_e_a]","4_16_a@10,8[4_03_e_b+3_07_e_a]","6_05_a@9,8[3_01_e_a+1_01_e_a]"], "bonds": ["arcaneShip 0/11","kazimierzShip 0/16","kjeragShip 0/8","miraShip 1/30","preciShip 1/52","sargonShip 1/24","siracusaShip 1/44","soloShip 1/0","suntShip 1/0","swiftShip 1/69","yanShip 0/13"] }, @@ -1610,7 +1610,7 @@ "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, "trophies": 3, "reward": 320, - "stats": {"activatedLayers":163,"bossDamage":432757,"dmgDealt":3761884,"gold":141,"itemsEquipped":21,"kills":274,"leaks":47,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":19}, + "stats": {"activatedLayers":163,"bossDamage":426921,"dmgDealt":3756514,"gold":141,"itemsEquipped":21,"kills":274,"leaks":47,"lpLost":10,"merges":6,"perfectRounds":12,"refreshes":19}, "lineup": ["6_12_a@12,3","5_10_a@12,7","3_21_b@11,3[1_05_e_b+4_08_e_b]","5_20_a@11,8[3_08_e_b+3_07_e_b]","3_01_b@10,3[5_09_e_a+6_05_e_a]","4_16_a@10,7[4_11_e_a+4_02_e_a]","6_06_a@10,8","6_01_a@9,8"], "bonds": ["arcaneShip 0/2","deputShip 0/32","emptyShip 1/0","kazimierzShip 0/8","lateranoShip 1/79","miraShip 1/6","preciShip 1/2","raidShip 0/2","sargonShip 0/2","siracusaShip 1/62","skillfulShip 0/68","suntShip 1/14","swiftShip 0/55","visiShip 1/0"] } diff --git a/test/sim/portal-appear.test.js b/test/sim/portal-appear.test.js index e318c48e..ed62b8ee 100644 --- a/test/sim/portal-appear.test.js +++ b/test/sim/portal-appear.test.js @@ -16,6 +16,21 @@ const REAL = { skip: !hasGeneratedData() && 'no generated data' }; 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({ @@ -81,6 +96,7 @@ for (const [tplId, routeIdx, motion] of CASES) { // ([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 }, @@ -89,6 +105,7 @@ test('solo boss: a route ending on the left portal entrance leaks at the left bl 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]'); @@ -97,6 +114,9 @@ test('solo boss: a route ending on the left portal entrance leaks at the left bl 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, () => { @@ -106,9 +126,12 @@ test('a route ending on the right portal entrance mirrors: leak at the right blu 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)`); });