diff --git a/CHANGELOG.md b/CHANGELOG.md index 511d62b5..35c9ada2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - 阿戈尔吞噬获得的基础攻击力改为最终加算,不再被技能攻击力百分比放大(基础攻击力 1000、技能 +100%、吞噬 2000 时为 4000,而不是 6000)(GitHub #165、PR #176 第 2 点,感谢 @SpiritedAwayCN)。 - 开场吞噬中先倒下的阿戈尔,它付出的标记仍照常结算,击杀记在付与者名下;只取消被击倒目标身上尚未触发的标记(GitHub #165、PR #176 第 3 点,感谢 @SpiritedAwayCN)。 - 缇缇二技能每次让周围敌人陷入沉睡都算一次进入沉睡,特质很快叠到每场上限(24 层,精锐 48 层);攻击范围外的沉睡不计入(GitHub #162)。 +- 沉睡的敌人不可阻挡、不占阻挡数:被阻挡的敌人陷入沉睡时立即脱离阻挡,阻挡位让给下一个敌人;醒来时只有阻挡数未满才会被重新阻挡,否则继续前进(如瑕光二技能;GitHub #140,感谢 @Hucxious 的实测)。 ### 战斗 diff --git a/docs/DESIGN.md b/docs/DESIGN.md index f13fa96b..1eb1c044 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -268,7 +268,7 @@ Engine helpers (content must use these, never touch internals): `battle.dealDama - **Initial deployment** at t=0, free: operators by column — the left column first, top to bottom within a column (right-side boss player: from its own left, the highest field column) — then the summons (PRTS 卫戍协议/帮助 "按从上到下>从左到右的顺序部署。优先部署干员,随后为召唤物", read as a scan: down each column, the columns left to right — the order PRTS gives the 阿戈尔 devour, "更靠左和靠上"; row-major until 0.1.3, §23.23; `aggroSeq` ranks every start-of-battle summon after all operators) — a skill's placed summon (赫默 医疗探机, 巫恋 诅咒娃娃) included, once and free ("所有手动部署的召唤物,无视所属干员的持有状态…作战开始时立即部署一次"; afterwards it re-appears on its tile each time the owner's skill gives one, §20.1); on a shared field (联防, boss) the players' i-th operators deploy together [ASSUMED]. Deploy effects fire (`deploy` with `initial: true`). Then operators carried into 联防 knocked out (`carryState.down`) are forced out (`FORCED_EXIT`, HP 0, §19.3). Then `battleStart`, after which the forced-out operators' timers are re-read (redeploy-time effects that start with the battle, e.g. 机变 征召, cover them too). - **DP**: per player, start 10, +1/s, cap 99; used only for auto-redeploy (dead op: after `respawnTime`, if the tile it lies on is free and DP ≥ cost ⇒ redeploy there with full HP, SP = initSp). A knocked-out operator (and, since 0.1.2, one forced out by its own effects, §22.15) stays **down** on the tile it fell on — its own home when it fell on another board piece's home (that piece on the field or not) and its home is free — until it redeploys there, and no ally deploys or moves onto that tile meanwhile (PRTS 卫戍协议/帮助 §作战阶段 单位部署; `Battle.downOn` / `restTile` / `_layBody`, §21.24; b.snap `down`, §18.3: counting / waiting for DP / its tile taken, a safeguard) — also one that entered 联防 knocked out (`FORCED_EXIT`, its full redeploy timer — confirmed by the user — with the battle-start redeploy effects, §19.3); summons, devices and enemies leave at once. -- **Blocking** (contact radius, PRTS 游戏数据基础 §阻挡半径 / 作战机制 "中点判定…案例:阻挡"; `constants.js BLOCK_RADIUS`, `Battle._checkBlock`, §19.2): an operator with free capacity blocks an unblocked, blockable enemy whose position lies within its block radius of its centre — ground 0.7071 (squared 0.49999037), 0.8944 for blockFly units vs flyers (起飞), devices 0.4472 (阻隔工事 / 障碍物); air units (FLY, 近地悬浮, 浮空) only by blockFly units. A ground enemy is never blocked by a unit standing on a tile ground units cannot pass (围墙 / 围栏: PRTS 地形机制 "部署在其中的单位,若当前阻挡类型为'地面阻挡'则无法阻挡敌人" — an enemy pushed or pulled against the fence walks on; air blocking there stays [ASSUMED], §21.23) nor by an airborne (起飞) unit, which blocks flyers only (§21.22). The nearest such operator wins (ties: scan order [ASSUMED]). It is re-checked every tick, moving or not, so another operator in contact with room takes the enemy over when its blocker dies, is withdrawn or is stunned, and an operator whose capacity frees up grabs an overlapping enemy; one that finds no room walks on. A head-on enemy stops at contact (≈ 0.71 tile), outside the blocker's tile. Enemy uses `blockCnt` capacity (default 1). Blocked enemies stop moving and attack their blocker if melee (or anyone in range if ranged, blocker first). **Every blocker** — melee units (重装要塞 / 领主 / 哨戒铁卫 included), summons and a ranged operator on a melee tile alike — whose attack hits enemies may always target the enemies it blocks, in range or not, whatever its facing, and targets them first ("可以选择且优先选择阻挡单位", PRTS 选择器; the user's rule after playtest #6, "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front; `Battle.blockedTargets`, `targeting.js sortEnemyTargets`; §20.3 — it replaces the playtest #5 QA's melee-only restriction of §19.2). A healer (dmgType `heal`, or a skill attack turned into a heal) selects injured allies only and keeps healing while it blocks (PRTS 卫戍协议/帮助 "对于医疗干员(咒愈师分支除外),攻击目标为需要治疗的单位"; §21.17); 咒愈师 follow the blocker rule. The huge leaders are 自缚 + unblockable (§7). +- **Blocking** (contact radius, PRTS 游戏数据基础 §阻挡半径 / 作战机制 "中点判定…案例:阻挡"; `constants.js BLOCK_RADIUS`, `Battle._checkBlock`, §19.2): an operator with free capacity blocks an unblocked, blockable enemy whose position lies within its block radius of its centre — ground 0.7071 (squared 0.49999037), 0.8944 for blockFly units vs flyers (起飞), devices 0.4472 (阻隔工事 / 障碍物); air units (FLY, 近地悬浮, 浮空) only by blockFly units. A ground enemy is never blocked by a unit standing on a tile ground units cannot pass (围墙 / 围栏: PRTS 地形机制 "部署在其中的单位,若当前阻挡类型为'地面阻挡'则无法阻挡敌人" — an enemy pushed or pulled against the fence walks on; air blocking there stays [ASSUMED], §21.23) nor by an airborne (起飞) unit, which blocks flyers only (§21.22). An enemy holding 不可阻挡 (PRTS 异常效果 BLOCK_FREE 「无法阻挡/被阻挡,自动解除阻挡」) — `unblockable` (恐惧, 诱导, enemy abilities), 浮空, and 沉睡 (SLEEPING = 无法行动+无敌+不可阻挡) — is never blocked and is released at once: a sleeping enemy frees its blocker's slot, stays where it is and, once awake, is blocked again only by a blocker with room (§24.9). The nearest such operator wins (ties: scan order [ASSUMED]). It is re-checked every tick, moving or not, so another operator in contact with room takes the enemy over when its blocker dies, is withdrawn or is stunned, and an operator whose capacity frees up grabs an overlapping enemy; one that finds no room walks on. A head-on enemy stops at contact (≈ 0.71 tile), outside the blocker's tile. Enemy uses `blockCnt` capacity (default 1). Blocked enemies stop moving and attack their blocker if melee (or anyone in range if ranged, blocker first). **Every blocker** — melee units (重装要塞 / 领主 / 哨戒铁卫 included), summons and a ranged operator on a melee tile alike — whose attack hits enemies may always target the enemies it blocks, in range or not, whatever its facing, and targets them first ("可以选择且优先选择阻挡单位", PRTS 选择器; the user's rule after playtest #6, "阻挡了就一定要能打到": officially the collision pushes a blocked enemy to its blocker's front; `Battle.blockedTargets`, `targeting.js sortEnemyTargets`; §20.3 — it replaces the playtest #5 QA's melee-only restriction of §19.2). A healer (dmgType `heal`, or a skill attack turned into a heal) selects injured allies only and keeps healing while it blocks (PRTS 卫戍协议/帮助 "对于医疗干员(咒愈师分支除外),攻击目标为需要治疗的单位"; §21.17); 咒愈师 follow the blocker rule. The huge leaders are 自缚 + unblockable (§7). - **Operator attacks**: every `interval` s if a valid target exists; targets = up to `maxTargets` (default 1) from `unitsInGrid(rangeGrid)` (a `rangeAoe` profile — 阵法术师, 轰击术师 — takes every selectable enemy in range, §21.18), priority: (1) the enemies it blocks (every blocker, in range or not, §20.3), (2) profession/trait priorities (e.g. fly-first snipers, `trait` text parsed by build-data into `targetPriority`), (3) enemy with the **least remaining path distance** to its goal, ties → earliest spawned. Melee (no ranged trait) and ground-only attacks cannot hit air units (`Unit.isFlying`: FLY, or a 近地悬浮 / 浮空 enemy — PRTS "算作空中单位", §19.1). Stealthed enemies can't be targeted unless blocked or revealed — or within 3 s of a block's end (§22.8). Ranged ops fire projectiles (speed ~12 tiles/s, instant for melee/arts-beam classes as data indicates); damage applies on impact; a 锁定攻击范围 AoE (`rangeAoe`) strikes every selectable enemy on its range at once with instant hits (§21.18). - **Healers** (`dmgType heal`): target the ally in range with the lowest HP% below 100%, never a 禁疗 / 孤立 unit (tokens.json `abnormal`, `noHeal`; §20.3; 史尔特尔 during 余烬, §22.7), and never the enemies they block — also while blocking (§21.17); do nothing otherwise (SP still regenerates). - **Enemy AI**: follow the route (checkpoints; WALK paths recomputed on the grid when devices change); when blocked stop & fight (`dmgType 'none'` enemies never attack unless content arms them through `e.profile` — 转译基底's armed forms, §21.4); ranged enemies (`rangeRadius > 0`) attack ops whose 0.25 collider touches their radius (centre distance ≤ `rangeRadius` + 0.25, §3, §20.4): the enemy's own target rule filters the candidates first (`profile.canTarget`: 只攻击地面单位 …), then priority blocker → special priority → highest taunt → **latest deployed** (`aggroSeq`) — PRTS 索敌 "阻挡→特殊优先级→仇恨值→最早出现"; a stealthed ally is attacked only by the enemy it blocks (§19.1), and an enemy's area effects — splash, blasts, area skills, zones, jumps — and buff auras skip it unless it blocks that enemy, while 迷彩 does not protect from splash and the other area effects (`targeting.js areaSelectable` / `auraSelectable`, §22.12); an airborne (起飞) ally is never a ground enemy's selection — picks, area damage, zones and statuses skip it, while 无视无法选择 abilities, direct picks, debuff ticks and flying units still reach it (§21.22); unblocked, they stand for each attack's clip (enemies.json `attackAnim`: its wind-up before the strike and the rest of the clip after it, shortened when the attacks come quicker than the clip; `ATTACK_PAUSE = 0.35 s` after the strike when no clip is known; a 「不停止移动」 attacker never stops; §22.14). FLY enemies ignore ground obstacles (only blockFly units block them) and follow their checkpoint route. A feared enemy runs between random 恐惧可达地块 away from the source (PRTS 诱发移动; `fear.js`); 恐惧 outranks 诱导; the 恐惧 / 诱导 walks re-plan from where the enemy stands after a push or an obstacle change, and the route re-plans when they end (§20.4). @@ -1586,7 +1586,7 @@ Withdrawn in 0.1.3 (§23.35): only elite 歌蕾蒂娅 carrying HOK-Y may use a - **Seen**: §21.4 left "隐匿 returns at once after a block (PRTS: 3 s)" [ASSUMED]; v0.1.1 hid an enemy again one tick after its block ended (a 潜伏者 whose blocker falls: untargetable 0.03 s later). Players reported (via the owner) that the 深池逐火 "stealth monster revives forever and can't be killed": a warrior knocked out while blocked releases its block through the 重生's 无法阻挡, its blocker takes the next warrior at once, and when the 重生 ends the ember is not blocked again — 隐匿, out of reach of ranged operators and operator splash — walks on and stands up with full HP 10 s later. - **Official** (checked on PRTS 2026-10-03): 作战机制 §隐匿 "对于绝大部分可隐匿的敌人而言,在被我方单位阻挡后会解除隐匿,不被阻挡的3秒后重新进入隐匿。这是敌方单位的隐匿所拥有的特殊机制,我方干员并不会因为阻挡而解除隐匿" and "少部分的隐匿表现上与我方干员相同…可能不具有这样的特殊机制"; §隐匿与Buff的关系 "在被阻挡时开关会被关掉从而失去隐匿,阻挡状态解除后3s开关重新被开启而恢复隐匿"; 异常效果 隐匿 "与'阻挡时解除'没有直接关系,该机制通常由其他效果实现". The enemy pages' 天赋 "隐匿(解除阻挡N秒后恢复)" (PRTS text search): 0 s on 21 enemies — here 业余竞演者, 节日爵士乐手, 假想敌:骨刺, 流泪小子 (both), 访问团强攻冠军, and the veil of 清明 / 堂皇 ("获得隐匿(解除阻挡0秒后恢复)"); 1 s on 家族灭迹人 / 家族暗影灭迹人; no page names another time. Every other stealthy enemy here has a plain 隐匿: 潜伏者, 隐形弩手 (+组长), 隐形术师, 山海众头目 / 秘使, 重弩突袭者, 架桥船工 / 扶桥老手, 深池伙友影刃精英, 潜水员 (in water), 锏's second form and the 深池逐火 embers (PRTS 深池逐火战士 / 精锐战士 / 护卫 "获得隐匿、缴械"). 山海众头目 / 秘使 天赋 "该隐匿每次生效后自身获得强击标记(不可叠加)", 技能 破隐一击 "仅持有强击标记且被阻挡时可触发…技能开始时消耗强击标记". - **Cause**: `targeting.js canTargetEnemy`, `snapshot.js flagsOf` and `Battle.foesInRadius` read `blockedBy` live, so the 隐匿 came back with the block's end. -- **Now**: every block release — `Battle._unblock`, `releaseBlocked`, `ai.js enforceBlockCapacity` (a blocker's death, retreat or stun, a block-count drop, a push, 无法阻挡 / 浮空 / 恐惧, a teleport) — calls `Battle._stealthSwitch(e)`: each 隐匿 source buff the enemy holds gets a `stealthOff:` buff for its restore time, `constants.js STEALTH_RESTORE` (3 s) or the source's `data.stealthRestore` (`content/enemies.js STEALTH_RESTORE_BY_KEY`: the 0 s / 1 s enemies above; the veil 0 s). `targeting.js enemyStealthed(e)` — 隐匿 flag, not revealed (反隐), not blocked, and some source not switched off — is the one test for ally targeting (`canTargetEnemy`), operator area damage (`foesInRadius`), the b.snap stealth bit (drawn solid meanwhile), 节日爵士乐手 (attacks only without its 隐匿), 假想敌:骨刺 (3 targets only in 隐匿) and 山海众头目 (the mark comes at the spawn and whenever its 隐匿 turns on again, the next blocked attack spends it: a new block inside the 3 s gives no second ×2 hit — until 0.1.2 every block did). A new block inside the window lifts the 隐匿 again and its end restarts the window; a 反隐 that ends outside a window hides it at once, as before; a veiled 潜伏者 hides as soon as its block ends (the veil) and, if the veil runs out inside its own 3 s, is revealed until they end. Our operators' 隐匿 / 迷彩 are untouched (allies get no switch). +- **Now**: every block release — `Battle._unblock`, `releaseBlocked`, `ai.js enforceBlockCapacity` (a blocker's death, retreat or stun, a block-count drop, a push, 无法阻挡 / 浮空 / 恐惧 / 沉睡 (since §24.9), a teleport) — calls `Battle._stealthSwitch(e)`: each 隐匿 source buff the enemy holds gets a `stealthOff:` buff for its restore time, `constants.js STEALTH_RESTORE` (3 s) or the source's `data.stealthRestore` (`content/enemies.js STEALTH_RESTORE_BY_KEY`: the 0 s / 1 s enemies above; the veil 0 s). `targeting.js enemyStealthed(e)` — 隐匿 flag, not revealed (反隐), not blocked, and some source not switched off — is the one test for ally targeting (`canTargetEnemy`), operator area damage (`foesInRadius`), the b.snap stealth bit (drawn solid meanwhile), 节日爵士乐手 (attacks only without its 隐匿), 假想敌:骨刺 (3 targets only in 隐匿) and 山海众头目 (the mark comes at the spawn and whenever its 隐匿 turns on again, the next blocked attack spends it: a new block inside the 3 s gives no second ×2 hit — until 0.1.2 every block did). A new block inside the window lifts the 隐匿 again and its end restarts the window; a 反隐 that ends outside a window hides it at once, as before; a veiled 潜伏者 hides as soon as its block ends (the veil) and, if the veil runs out inside its own 3 s, is revealed until they end. Our operators' 隐匿 / 迷彩 are untouched (allies get no switch). - **The 深池逐火 embers** (all three, `husk()` with `stealthy`): the knock-out releases the warrior's block at once (`_unblock` right after the 重生's 无法阻挡, as `applyStatus` does for a status) once the ember's 隐匿 is on, so the ember is revealed until 3 s after the knock-out — the 1 s 重生 stays 无敌 and untargetable, then about 2 s in which ranged operators and operator splash can hit it even though its blocker took the next warrior. An ember knocked out while not blocked is 隐匿 as soon as its 重生 ends (the switch only follows a block). - **Measured** (before = the v0.1.1 sim, after = this change; the flat stage with its row-9 lane, and real rounds on 战场#01 built like the match — an official wave with the TIMES pick, full content): the integrator's seven lineups (艾丝黛尔 / 角峰 / 隐现 / 幽灵鲨 against 1–4 战士 or 护卫): identical — 艾丝黛尔 alone against four 战士 2 s apart stays 0 / 4 because only one warrior is ever knocked out, and no lineup there knocks out a 护卫. 28 runs of a blocker + 2–3 ranged / splash operators against 4–6 战士 / 精锐战士 0.5–3 s apart: 81 → 84 of 126 killed, stand-ups 12 → 7 (23 runs unchanged, 4 one more kill, 1 one fewer — its blocker fell in the changed fight). Real rounds (R3 / R5 精锐战士 pick, R9 / R11 护卫 pick, three lineups up to the tier-4 four of §21.4's test): unchanged (R3 7 / 7, R5 5 / 16, R9 0 / 18, R11 0 / 28 for the tier-4 lineup; 0–1 for the weak ones). Why so little: in real rounds most knock-outs happen while the warrior is NOT blocked — ranged operators finish it on the way after it passed a full blocker (R5, tier-4 lineup: 10 of its 18 knock-outs, and 10 of the 13 embers that stood up again) — so its ember is 隐匿 at once, which is official; an ember freed by its own knock-out gets about 2 s after the 重生 and still needs 5 separate hits (火灰 10). What beats embers, officially: a free block slot where they walk (a second blocker behind the first), 反隐 (银灰 鹰眼视觉, 伊内丝 影哨, the SPT-X modules, 信仰搅拌机's module), or many fast hits. - [ASSUMED]: at the knock-out the 重生's cleanse comes first, then the ember's 隐匿 with the block-end switch, which outlasts the 重生 (PRTS documents neither the order nor whether the switch survives a 重生); 深池伙友影刃精英 and 锏's second form (PRTS writes their 隐匿 as the plain 异常效果 with no restore note) follow the general 3 s, not the "表现上与我方干员相同" kind; one switch per 隐匿 source — with several sources the enemy hides as soon as any is back on; a push or a teleport (DISAPPEAR) ends a block like any other release; a warrior released by its blocker less than 3 s before its knock-out leaves an ember that is 隐匿 straight after its 重生 (the window is opened by the knock-out's own release only — 4 of 55 knock-outs in the measured rounds, no outcome changed when prototyped). @@ -2142,7 +2142,7 @@ Decisions of the owner, 2026-10-04: a 联防 devour counts members who enter alr ## 24. Community reports after 0.1.3 -Reports after the 0.1.3 release. Each was checked against the official data and PRTS; anything with no source is marked [ASSUMED]. Where each is handled: a skill clip with no Begin and no own Idle plays once (德克萨斯 S2 剑雨) → §24.1; 高台 for every melee chess whose trait reads 「可以放置于远程位」 (GitHub #153, PR #69) → §24.2; the 5-阿戈尔 revives go to the first 3 members knocked out (GitHub #105, #140) → §24.3; 联防 阿戈尔 devours the operator in front whoever owns it (GitHub #140) → §24.4; a special terrain tile explains itself on a tap (GitHub #184, PR #185) → §24.5; 活性源石 was two different materials on the two boards (GitHub #184, PR #185) → §24.6; 阿戈尔's devoured base ATK is a 最终加算 and a fallen marker's marks still resolve (GitHub #165, PR #176) → §24.7; 缇缇 S2's sleep pulses count for her trait (GitHub #162) → §24.8. Merged pull requests keep their own notes where they changed the rules: deploy-timed skills as duration skills (PR #109, §5 skill contract), the teammate's hand / temp / equipment in prep scouting (PR #129), 灵巧's knocked-out aura and the hidden-layer bonds (PR #66, research 02). +Reports after the 0.1.3 release. Each was checked against the official data and PRTS; anything with no source is marked [ASSUMED]. Where each is handled: a skill clip with no Begin and no own Idle plays once (德克萨斯 S2 剑雨) → §24.1; 高台 for every melee chess whose trait reads 「可以放置于远程位」 (GitHub #153, PR #69) → §24.2; the 5-阿戈尔 revives go to the first 3 members knocked out (GitHub #105, #140) → §24.3; 联防 阿戈尔 devours the operator in front whoever owns it (GitHub #140) → §24.4; a special terrain tile explains itself on a tap (GitHub #184, PR #185) → §24.5; 活性源石 was two different materials on the two boards (GitHub #184, PR #185) → §24.6; 阿戈尔's devoured base ATK is a 最终加算 and a fallen marker's marks still resolve (GitHub #165, PR #176) → §24.7; 缇缇 S2's sleep pulses count for her trait (GitHub #162) → §24.8; a sleeping enemy cannot be blocked and takes no block slot (GitHub #140) → §24.9. Merged pull requests keep their own notes where they changed the rules: deploy-timed skills as duration skills (PR #109, §5 skill contract), the teammate's hand / temp / equipment in prep scouting (PR #129), 灵巧's knocked-out aura and the hidden-layer bonds (PR #66, research 02). ### 24.1 A skill clip with no Begin and no own Idle plays once — 德克萨斯 S2 剑雨 (player report) — `render/spine.js SpineActor.setSkill / update` (`SKILL_CLIP_MIN`, `skillClipOnce`, `skillEndPending`) @@ -2410,3 +2410,64 @@ Reports after the 0.1.3 release. Each was checked against the official data and elite, S2 cast: an enemy at her back sleeps through 8 s of pulses and adds nothing beyond the ward's two operators; an enemy in range beside both wards — one entry per pulse — takes the trait to 24 / 48 within 6.5 s; fails on 0.1.3's code). The 125 test (a stun refresh is not an entry) and `kits_alt_t5`'s S2 test pass unchanged. + +### 24.9 A sleeping enemy cannot be blocked: 沉睡 frees the block slot (GitHub #140; the owner's decision of 2026-10-06) — `sim/Battle.js _checkBlock / applyStatus`, `sim/ai.js updateEnemy`, `sim/buffs.js` (`sleep`), `kits/tier3.js` (瑕光 S2) + +- **Seen**: GitHub #140 — a player tested 瑕光's S2 in the official mode (the 爱国者 stage): she put 4 enemies to sleep, + the hounds that came next were still blocked, and after the skill no more were — 「被沉睡的敌人是不占用阻挡数的」. In the + remake an enemy that fell asleep while blocked stayed blocked and kept its blocker's slot, so 瑕光 S2's sleepers filled + her 3 block for the whole skill and the next enemies walked past her (the owner's first reply on #140 had kept that + reading). +- **Official**: PRTS 异常效果 — the 异常组合 SLEEPING 沉睡 = 「无法行动+无敌+不可阻挡」, and the 异常效果 BLOCK_FREE 不可阻挡 = + 「无法阻挡/被阻挡,自动解除阻挡」. 小睡 (DOZE) and 闭锁 (SHELTERING, 泥岩 S3) hold 不可阻挡 too — on operators, outside this + change. +- **Decision**: the owner's decision of 2026-10-06, as PRTS and the report say: a sleeping enemy cannot be blocked and + takes no block slot. +- **Cause**: 沉睡 was in neither block check. `Battle._checkBlock` refused `unblockable` / `levitate` / `fear` only, the + release in `Battle.applyStatus` fired for those three flags, and `ai.js updateEnemy` returns early for an enemy that + cannot act (a sleeper is `stun` too: `units.js`) before its own release check. The sleep template's `noBlock` only + stops a sleeping operator from blocking. +- **Now**: 沉睡 is one of the 不可阻挡 states. `applyStatus(enemy, 'sleep')` releases the enemy at once (`_unblock`: the + blocker's slot frees for the next enemy; a 隐匿 enemy's block-end window starts, §22.8); `_checkBlock` never blocks a + sleeper; a sleep added as a plain buff (`flags.sleep`) is let go on the enemy's next update (the cannot-act branch of + `updateEnemy`). The sleeper stays where it fell asleep — overlapping its former blocker and whatever that blocker takes + next — and when it wakes it is checked like any unblocked enemy: blocked again where it stands by a blocker in contact + with a free slot (usually the same one), else it walks on through the full blocker. 瑕光 S2 (the ground enemies on her + tile and the enemies she blocks): the sleepers free her 3 slots, she blocks the next 3 during the skill, and when S2 + ends the sleepers wake beside her and, her slots full, walk on — the report. A stun, a freeze or a refused sleep (23 + enemies are 沉睡免疫) keeps the block as before; sleeping operators are unchanged (they already released what they block). +- **Other readers of the block** (checked): `Battle.blockedTargets` — blocked-first targeting, the DEFAULT trigger, the + 强攻手 / 重剑手 / 推击手 attack on every blocked enemy — no longer lists a sleeper. Only `hitSleep` attackers could target + one, and of those only 瑕光 blocks: her 仁慈 (sleepers in her range first, ×atk_scale) reads her range, so a sleeper she + let go in front of her is still her first target and one behind her is out of reach. A 要塞 that blocked nothing but a + sleeper stalled (its melee branch found no target); it now fires its ranged splash. The "while blocking" effects + (`unit.blocking`) do not count a sleeper; “庞贝”'s self-blast clock restarts when a sleep ends its block (as a 浮空 + already did); the snapshot's blocked bit (`UF.BLOCKED`, the client's block marker) follows. 荒芜拉普兰德 S1's 「非移动敌人」 + reads the sleep itself. +- [ASSUMED]: a woken sleeper is re-blocked in the enemy update order (an enemy that reaches the blocker earlier in the + same tick takes the slot first; PRTS gives no order) and where it stands, never pulled back to the blocker's front; + “庞贝”'s clock restarts although its talent lists 沉睡 among the pauses — the 不可阻挡 ends the block, and + 「解除阻挡时重置计时」. +- Golden: every moved scenario fields one of the three kits that sleep enemies (小满 S2, 瑕光 S2, 缇缇) and sleeps enemies + that were blocked (counted with a probe): `roster-003` (小满 S2, 6 such sleeps: 82 kills instead of 83, 3 leaks instead + of 2 at the 110 s timeout); `roster-005` (elite 小满 S2, 3: cleared at 136.73 s with 83 kills and 1 leak instead of the + 140 s timeout with 82 and 2); `roster-013` (缇缇 S3, 13: the same fight — only the `blocked` count, 23 → 34 with the + woken sleepers held again, and the snapshot hashes, the blocked bit, move); `roster-032` (缇缇 S1, 4: 7 knock-outs + instead of 5, 缇缇 among them, and 24 萨尔贡 layers instead of 34; same kills and leaks); `roster-038` (elite 缇缇 S2, 2: + ends at 197.43 s instead of 197.27 s, 4 knock-outs instead of 3); `roster-039` (瑕光 S2, normal and elite, 32 + 22: the + enemies they hold and put to sleep walk on when they wake with both blocks full — 41 of 52 killed instead of 47, 11 + leaks instead of 5 at the 170 s timeout); `roster-042` (缇缇 S2, 4: ends at 161.17 s instead of §24.8's 166.97 s); + `bond-maniShip-high` (小满 S2 among the members, 1: only the `blocked` count, 29 → 30, and the snapshot hashes); + `coop4-FUNNY-6` (ai_0's 缇缇: from round 9 her trait's 萨尔贡 / 精准 layers and ai_0 / ai_2's boss damage move); + `coop4-ABYSS-7` (ai_2's 小满: rounds 11–12, one more kill and one fewer leak in round 11, four more kills in round 12); + `coop2-NORMAL-8-serverrun` (ai_1's 缇缇: from round 10 its battles move — one more leak in round 10 — and the team is + eliminated in round 13 instead of winning after round 14). `fields` did not move. +- Tests: `test/sim/feedback4-sleep-unblock.test.js` (new; every case fails without this change) — a block-1 operator + holding A: A falls asleep → released at once, the slot frees and B is blocked, A stays put; A wakes while B holds the + slot → not blocked, it walks on; B knocked out while A sleeps → A stays unblocked beside the free operator and is + blocked again the moment it wakes, never having moved; a plain-buff sleep is let go on the next update, a stun or a + refused sleep keeps the block; `blockedTargets` drops the sleeper and takes B; the real 瑕光 S2 — her 3 blocked enemies + sleep and are let go, she blocks the next 3 during S2, and when it ends the sleepers wake with no room and walk on. + `playtest5_blocking` (its 瑕光 S2 test included), `combat`, `engine-requests`, `kits_t1t2` / `kits_alt_t2` (小满), + `kits_t3` / `kits_alt_t3` (瑕光), `kits_t5` / `kits_alt_t5` / `garrisons_battle` (缇缇) and the other sleep and block + suites pass unchanged. diff --git a/docs/SIM.md b/docs/SIM.md index 33e0ed45..40bcaf58 100644 --- a/docs/SIM.md +++ b/docs/SIM.md @@ -218,8 +218,11 @@ ground enemy (PRTS 围墙 / 围栏 地形机制 "部署在其中的单位,若 unit; it walks on from there (found while checking community report F4 after 0.1.0, 深巡 on a fenced tile: 薄绿 S2 held the enemies she dragged against the fence; test/sim/feedback1f-fence.test.js). Air blocking (blockFly against flyers) stays [ASSUMED: PRTS restricts the rule to 地面阻挡], and a unit on a fenced tile still attacks whatever stands on its -range tiles. It is checked every tick for every unblocked enemy, moving or -not: an enemy that overlaps an operator when its blocker dies / is withdrawn / is stunned, or when the operator's +range tiles. Never blocked: an enemy holding 不可阻挡 (PRTS 异常效果 BLOCK_FREE 「无法阻挡/被阻挡,自动解除阻挡」) — +the `unblockable` flag (恐惧, 诱导 and many enemy abilities carry it), 浮空, and 沉睡 (SLEEPING = 无法行动+无敌+不可阻挡: an +enemy falling asleep is released at once, its slot freeing for the next enemy, and stays where it is; once awake it is +blocked again only by a blocker with room, else it walks on — DESIGN §24.9). It is checked every tick for every +unblocked enemy, moving or not: an enemy that overlaps an operator when its blocker dies / is withdrawn / is stunned, or when the operator's blocked enemy dies, is taken over at once; an enemy that finds no room walks on (pass-through). Several blockers in contact → the nearest [ASSUMED]. A head-on enemy therefore stops at contact, ~0.71 tile from the blocker's centre, on the tile in front of it (PRTS 作战机制: a blocked enemy's collider does not enter the blocker's tile; the official few @@ -424,7 +427,7 @@ Guarantees content can rely on (pinned by `test/sim/robustness.test.js`): - **Runaway content.** At most `MAX_ALIVE_ENEMIES` (600) living enemies per field (`spawnEnemy` returns **null** beyond — content must handle it); a SpawnSpec `count` is capped the same way and `time: Infinity` spawns are never scheduled. - **Movement.** Route legs outside the rect are clamped onto it (h07_01's extra fly route runs along row 6, outside the - boss rect: without the clamp the flyer hovered at the border forever). An enemy that gains `unblockable`/`levitate` + boss rect: without the clamp the flyer hovered at the border forever). An enemy that gains `unblockable`/`levitate`/`sleep` through a plain buff is released by its blocker on its next update. - **Listener hygiene.** When a unit is removed for good (enemy killed/leaked, token expired or dead, device destroyed, permanent retreat) its hooks and periodic `every` timers registered with `{ owner: unit }` are dropped at the end of @@ -571,7 +574,7 @@ of coverage per 3 s), kept because the current wording no longer says so (feedba | `stun` | cannot act / move; **a stunned operator blocks nothing** (its blocked enemies are released: taken over by another operator in contact with room, else they walk on — §1.2 Blocking) | – | | `freeze` | stun; **enemies** also RES −15 | – | | `cold` | ASPD −30; a 2nd cold while cold ⇒ `freeze` for max(remaining cold, the incoming cold after 抵抗) — PRTS 术语释义 寒冷 「持续时间取双方之中最高」 (`COLD_FREEZE_DURATION` 3 s only when neither side has a duration; unless frozen-immune). [ASSUMED] the one catalogue cold uses that 友方 sentence for an enemy-applied cold too | – | -| `sleep` | 无敌且无法行动: inactive, untargetable, **takes no damage** (unless the attacker profile has `hitSleep` or the damage `ignoreSleep`), blocks nothing | – | +| `sleep` | 无敌且无法行动: inactive, untargetable, **takes no damage** (unless the attacker profile has `hitSleep` or the damage `ignoreSleep`), blocks nothing; PRTS 异常效果 SLEEPING = 无法行动+无敌+**不可阻挡**: an enemy asleep **cannot be blocked and takes no block slot** — its blocker lets go at once (the slot frees for the next enemy), it stays where it is, and when it wakes it is blocked again only by a blocker with room, else it walks on (DESIGN §24.9) | – | | `slow` | moveMul 1 − value (*strongest*) | default 0.5 | | `sluggish` (停顿) | moveMul 0.2 | – | | `bind` (束缚) | cannot move | – | @@ -593,7 +596,8 @@ of coverage per 3 s), kept because the current wording no longer says so (feedba | `defDown` / `resDown` | defMul 1 − value / RES −value (*strongest*) | 0.3 / 20 | Unknown keys become a flag buff `{ [key]: true }`. Flags `noBlock` (blocks nothing) and `tremble` exist for custom buffs; -a custom buff with `flags.sleep` also blocks nothing and is untargetable/invulnerable like the status. +a custom buff with `flags.sleep` also blocks nothing, is untargetable/invulnerable and, on an enemy, cannot be blocked +(its blocker lets go on the enemy's next update: `ai.js updateEnemy`) like the status. **Element gauges** (`unit.elem = {burn, neural, apoptosis, erosion, necrosis}`; capacity `unit.gaugeMax` = 1000, enemy leaders (rank BOSS / boss units) 2000): deal `{ type:'element', element, amount }` (fires `elementHit` first; the gauge diff --git a/server/sim/Battle.js b/server/sim/Battle.js index 67550290..6130376c 100644 --- a/server/sim/Battle.js +++ b/server/sim/Battle.js @@ -1117,12 +1117,14 @@ export class Battle { * a fenced tile blocks no ground enemy, _blockerFor). Checked every tick for every * unblocked enemy, moving or not, so an enemy overlapping an operator is taken over as soon as its blocker is gone or * the operator's capacity frees up (user playtest #5 item 4). Several blockers in contact → the nearest [ASSUMED], - * ties → the first in row-then-column scan order. + * ties → the first in row-then-column scan order. Never blocked: an enemy holding 不可阻挡 (PRTS 异常效果 BLOCK_FREE + * 「无法阻挡/被阻挡,自动解除阻挡」) — the flag itself (恐惧 / 诱导 carry it), 浮空, and 沉睡 (SLEEPING = 无法行动+无敌+不可阻挡: + * a sleeper takes no block slot, DESIGN §24.9); once it wakes it is blocked again only by a blocker with room. */ _checkBlock(e) { if (e.blockedBy || e.hidden || !e.alive) return !!e.blockedBy; const f = e.s.flags; - if (f.unblockable || f.levitate || f.fear) return false; + if (f.unblockable || f.levitate || f.fear || f.sleep) return false; const r0 = Math.round(e.y), c0 = Math.round(e.x); const w = e.blockWeight ?? 1; let u = null, bd = Infinity; @@ -1355,7 +1357,8 @@ export class Battle { * then resumes); other statuses refresh to the longer duration. 诱导 (`attract`) walks the enemy to `point` * ([r, c] or {x, y}; default the source's tile — a new application moves the point); 恐惧 (`fear`) stamps where it * was applied and from where (fear.js stampFear: the fan of 恐惧可达地块 its movement uses). A stunned/sleeping operator - * releases the enemies it blocks; a feared/levitated/unblockable/attracted enemy is released by its blocker. + * releases the enemies it blocks; a feared/levitated/unblockable/attracted/sleeping enemy is released by its blocker + * (沉睡 = 无法行动+无敌+不可阻挡, PRTS 异常效果: the slot frees for the next enemy, the sleeper stays put — DESIGN §24.9). * `statusApplied` reports the final duration and `entered` (the target did not carry the status before) — or, with * `reenter`, entered anyway: a pulse whose own short status the caller re-applies as a fresh one each time (缇缇 S2's * sleep ward, DESIGN §24.8); the buff itself is refreshed as usual. @@ -1424,7 +1427,7 @@ export class Battle { if (key === 'fear' && b && target.side === 'enemy') stampFear(this, target, b, source); } const f = tpl.flags; - if (f && target.side === 'enemy' && (f.levitate || f.unblockable || f.fear)) this._unblock(target); + if (f && target.side === 'enemy' && (f.levitate || f.unblockable || f.fear || f.sleep)) this._unblock(target); if (f && target.side === 'ally' && f.noBlock) this.releaseBlocked(target); if (this._hooks.statusApplied) this.emit('statusApplied', { source, target, status: key, duration, value, entered }); return true; diff --git a/server/sim/ai.js b/server/sim/ai.js index 95069282..36041e4a 100644 --- a/server/sim/ai.js +++ b/server/sim/ai.js @@ -11,7 +11,9 @@ // path always follows `motion` — a hovering (近地悬浮) enemy is an air unit for targeting and blocking (Unit.isFlying) // but walks the ground. An unblocked enemy touching an ally with free block capacity — within its block radius (0.7071 // ground, 0.8944 air, devices 0.4472; Battle._checkBlock) — is blocked, moving or not, so an enemy overlapping an -// operator is taken over once its blocker is gone. Blocked enemies fight their blocker (ranged ones may pick anyone in +// operator is taken over once its blocker is gone; never one holding 不可阻挡 (恐惧, 诱导, 浮空, 沉睡): an enemy falling +// asleep is let go by its blocker, whose slot frees, and stays where it is until it wakes (DESIGN §24.9). Blocked +// enemies fight their blocker (ranged ones may pick anyone in // range, blocker first); every blocker whose attack hits enemies — a ranged operator on a melee tile included — may // always target the enemies it blocks, in range or not, whatever its facing, and targets them first (acquireTargets, // Battle.blockedTargets; user playtest #6: "阻挡了就一定要能打到"); a heal attack keeps selecting injured allies while @@ -459,8 +461,14 @@ export function updateEnemy(b, e, dt) { // true: an unblocked ranged enemy in the wind-up of its next attack with a target in range (it stands) const winding = !e.hidden && !stunned && enemyAttack(b, e); if (!e.alive) return; - // a stun / freeze / sleep cuts the attack clip short: no stand left once it ends [ASSUMED] - if (stunned && !e.hidden) { e.atkStandUntil = -Infinity; return; } + // a stun / freeze / sleep cuts the attack clip short: no stand left once it ends [ASSUMED]. 沉睡 also holds 不可阻挡 + // (PRTS 异常效果 SLEEPING = 无法行动+无敌+不可阻挡): a sleeper's blocker lets go — Battle.applyStatus releases it at once, + // this catches a sleep added as a plain buff — and it stays where it is until it wakes (DESIGN §24.9) + if (stunned && !e.hidden) { + e.atkStandUntil = -Infinity; + if (e.blockedBy && e.s.flags.sleep) b._unblock(e); + return; + } if (e.blockedBy) { const bl = e.blockedBy; // (unblockable/levitate/fear may also arrive through a plain addBuff, which does not unblock by itself; a diff --git a/server/sim/buffs.js b/server/sim/buffs.js index 14d4c4fd..422fe866 100644 --- a/server/sim/buffs.js +++ b/server/sim/buffs.js @@ -58,7 +58,9 @@ export const STATUS = Object.freeze({ // 寒冷: ASPD −30. A second cold while this one remains applies 冻结 for max(remaining, incoming) // (Battle.applyStatus; PRTS 术语释义 寒冷 「持续时间取双方之中最高」). cold: { flags: { cold: true }, mods: { aspd: COLD_ASPD } }, - // 沉睡: 无敌且无法行动 (untargetable, takes no damage — except from `hitSleep` attackers — and blocks nothing) + // 沉睡: 无敌且无法行动 (untargetable, takes no damage — except from `hitSleep` attackers — and blocks nothing); PRTS 异常效果 + // SLEEPING = 无法行动+无敌+不可阻挡: an enemy asleep is not blocked either — its blocker lets go and the slot frees, it + // stays put until it wakes (the `sleep` flag: Battle._checkBlock / applyStatus, ai.js updateEnemy; DESIGN §24.9) sleep: { flags: { sleep: true, noBlock: true }, immune: 'sleep' }, // value = fraction of speed removed (0.8 ⇒ moveMul 0.2). Default 0.5. slow: { mods: (v) => ({ moveMul: 1 - clamp01(v ?? 0.5) }), valued: 0.5 }, diff --git a/server/sim/content/kits/tier3.js b/server/sim/content/kits/tier3.js index 2cd5c3d3..07d42561 100644 --- a/server/sim/content/kits/tier3.js +++ b/server/sim/content/kits/tier3.js @@ -1101,7 +1101,9 @@ const KITS = { const dur = skill.timeLeft > 0 ? skill.timeLeft : Math.max(0.1, num(s.duration, 10)); let n = 0; // PRTS 备注: "技能生效对象实际为“自身这格内的所有地面敌人及自身阻挡的敌人”,即使阻挡的是飞行敌人" — a blocked - // enemy stands at the block radius, outside her tile (Battle._checkBlock) + // enemy stands at the block radius, outside her tile (Battle._checkBlock). Asleep they are no longer blocked + // (沉睡 = 不可阻挡, DESIGN §24.9; GitHub #140): her slots free for the next enemies, and a sleeper that wakes + // is held again only while she has room for (const e of battle.enemies) { if (!e.alive || e.hidden) continue; if (e.blockedBy !== unit && (e.isFlying || !bodyOnTile(e, unit.tileR, unit.tileC))) continue; diff --git a/test/golden/bonds.json b/test/golden/bonds.json index db0d2e6a..199d1ec4 100644 --- a/test/golden/bonds.json +++ b/test/golden/bonds.json @@ -775,10 +775,10 @@ ["p1","~3","token","token_10030_mlyss_wtrman",null,null,921.55,1,39,0,203,245,125,3,null] ], "enemies": {"enemy_1288_duskls":[11,11,0,210,63415],"enemy_1288_duskls_2":[7,7,0,0,48419],"enemy_1292_duskld":[5,5,0,1224,57650]}, - "hooks": [1,38,2506,288,330,531,531,411,23,25,19,16,0,11219,246,29,23,0,1,53,0,0,0], + "hooks": [1,38,2506,288,330,531,531,411,23,25,19,16,0,11219,246,30,23,0,1,53,0,0,0], "events": {"atk":346,"deploy":12,"die":25,"dmg":531,"engage":12,"fx":200,"skill":35,"spawn":38,"status":422}, "fx": {"aoe":2,"copy":1,"crit":6,"dp":2,"ember":23,"iceSpike":66,"palsy":3,"sleep":3,"split":3,"weightless":2,"zone":89}, - "snaps": "936990ce cac0c6e5 0c5910e5 94cfd566 931c176f 4042d89c fadbeae3 2767137e 57fe0fc3" + "snaps": "936990ce cac0c6e5 0c5910e5 94cfd566 931c176f 4042d89c 233a4f1f 65d2bb0a 91f58e9f" }, "bond-miraShip-low": { "about": "奇迹 low: tier 1 layers 1; chess_char_4_01_a chess_char_2_11_a chess_char_3_18_a chess_char_3_01_a chess_char_3_17_a chess_char_3_20_a chess_char_3_21_a chess_char_4_02_a", diff --git a/test/golden/matches.json b/test/golden/matches.json index a59eb04b..c967a44f 100644 --- a/test/golden/matches.json +++ b/test/golden/matches.json @@ -927,35 +927,35 @@ }, { "round": 9, - "ai_0": [1,26,12,0,0,4,8,"fc454676",169,"deputShip 1/47, kjeragShip 0/2, preciShip 2/40, sargonShip 1/44, steadShip 1/38",30,30,0,74], + "ai_0": [1,26,12,0,0,4,8,"fc454676",166,"deputShip 1/47, kjeragShip 0/2, preciShip 2/39, sargonShip 1/42, steadShip 1/38",30,30,0,74], "ai_1": [1,45,12,0,0,4,8,"13fccde5",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",30,30,0,70], "ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72], "ai_3": [1,31,13,0,0,4,8,"5f784848",153,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/30, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76] }, { "round": 10, - "ai_0": [1,26,12,0,0,5,8,"2b6a123a",243,"deputShip 1/53, kjeragShip 0/2, preciShip 2/72, sargonShip 1/70, steadShip 1/48",29,29,0,86], + "ai_0": [1,26,12,0,0,5,8,"2b6a123a",252,"deputShip 1/53, kjeragShip 0/2, preciShip 2/77, sargonShip 1/74, steadShip 1/48",29,29,0,86], "ai_1": [1,45,12,0,0,5,8,"549957f7",105,"deputShip 2/58, emptyShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/39, steadShip 0/4",28,29,1,82], "ai_2": [1,28,12,0,0,5,8,"fe139ef2",72,"sargonShip 0/8, siracusaShip 2/60, swiftShip 1/12, visiShip 1/0, yanShip 0/1",29,29,0,83], "ai_3": [1,31,12,0,0,5,8,"9ce217c6",161,"deputShip 1/18, emptyShip 0/12, kjeragShip 2/32, preciShip 1/81, siracusaShip 0/3, swiftShip 1/30, yanShip 0/1",29,29,0,88] }, { "round": 11, - "ai_0": [1,26,12,0,0,5,8,"5c1d4d08",291,"deputShip 1/55, kjeragShip 0/2, preciShip 1/88, sargonShip 1/90, steadShip 1/58",28,28,0,99], + "ai_0": [1,26,12,0,0,5,8,"5c1d4d08",296,"deputShip 1/55, kjeragShip 0/2, preciShip 1/91, sargonShip 1/92, steadShip 1/58",28,28,0,99], "ai_1": [1,45,12,0,0,5,8,"28ba083d",133,"deputShip 2/68, emptyShip 1/15, preciShip 0/4, sargonShip 0/2, siracusaShip 1/50, steadShip 0/4",13,28,15,94], "ai_2": [1,28,12,0,0,5,8,"8321f307",130,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/106, swiftShip 1/24, yanShip 0/1",28,28,0,95], "ai_3": [1,31,12,0,0,5,7,"5e2bfa81",195,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/68, preciShip 1/92, siracusaShip 0/3, swiftShip 1/35, visiShip 1/0, yanShip 0/1",28,28,0,98] }, { "round": 12, - "ai_0": [1,26,12,0,0,6,8,"934bb983",409,"deputShip 1/57, emptyShip 1/2, kjeragShip 0/2, preciShip 1/138, sargonShip 1/144, steadShip 1/68",46,46,0,111], + "ai_0": [1,26,12,0,0,6,8,"934bb983",414,"deputShip 1/57, emptyShip 1/2, kjeragShip 0/2, preciShip 1/141, sargonShip 1/146, steadShip 1/68",46,46,0,111], "ai_1": [1,45,12,0,0,6,8,"f1340001",155,"deputShip 2/78, emptyShip 1/19, preciShip 0/4, sargonShip 0/2, siracusaShip 1/58, steadShip 0/4",20,46,26,104], "ai_2": [1,28,12,0,0,6,8,"696f5ca3",184,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/160, swiftShip 1/24, yanShip 0/1",46,46,0,106], "ai_3": [1,31,12,0,0,6,8,"2e3cd509",247,"deputShip 0/18, emptyShip 0/12, kjeragShip 2/108, preciShip 2/99, siracusaShip 0/3, swiftShip 1/40, visiShip 1/0, yanShip 0/1",46,46,0,110] }, { "round": 13, - "ai_0": [1,26,12,0,0,6,8,"2df19066",463,"deputShip 1/59, emptyShip 1/4, kjeragShip 0/7, preciShip 2/156, sargonShip 1/166, steadShip 1/78",32,32,0,126], + "ai_0": [1,26,12,0,0,6,8,"2df19066",468,"deputShip 1/59, emptyShip 1/4, kjeragShip 0/7, preciShip 2/159, sargonShip 1/168, steadShip 1/78",32,32,0,126], "ai_1": [1,45,12,0,2,6,8,"9346fb5a",177,"deputShip 1/86, emptyShip 1/21, preciShip 1/6, sargonShip 0/2, siracusaShip 1/64, steadShip 0/4",32,32,0,115], "ai_2": [1,28,12,0,0,6,8,"0236b2a3",260,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/236, swiftShip 1/24, yanShip 0/1",32,32,0,117], "ai_3": [1,31,12,0,0,6,7,"ceba29b5",265,"deputShip 0/18, emptyShip 0/12, kjeragShip 1/116, preciShip 2/104, siracusaShip 0/3, swiftShip 1/45, yanShip 0/1",32,32,0,124] @@ -972,9 +972,9 @@ "title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"}, "trophies": 3, "reward": 250, - "stats": {"activatedLayers":485,"bossDamage":46721,"dmgDealt":1696544,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21}, + "stats": {"activatedLayers":490,"bossDamage":47348,"dmgDealt":1712814,"gold":140,"itemsEquipped":25,"kills":325,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":21}, "lineup": ["6_08_a@11,6[3_03_e_a+3_02_e_a]","6_12_a@11,7[4_08_e_a+6_07_e_b]","2_14_b@10,3","2_17_b@10,4[4_02_e_b+5_01_e_a]","2_08_a@9,3[4_04_e_a+1_01_e_b]","6_06_a@9,4[3_08_e_a+3_01_e_b]","1_06_b@9,8","5_02_b@9,9[5_02_e_a+3_06_e_b]"], - "bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/158","sargonShip 1/172","steadShip 1/88"] + "bonds": ["deputShip 1/61","emptyShip 1/6","kjeragShip 0/7","preciShip 2/161","sargonShip 1/174","steadShip 1/88"] }, "ai_1": { "alive": true, @@ -1000,7 +1000,7 @@ "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, "trophies": 3, "reward": 250, - "stats": {"activatedLayers":282,"bossDamage":72915,"dmgDealt":1633817,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16}, + "stats": {"activatedLayers":282,"bossDamage":72288,"dmgDealt":1647921,"gold":127,"itemsEquipped":20,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":16}, "lineup": ["4_25_a@12,3","1_07_b@11,7[1_01_e_a+1_05_e_a]","5_20_a@11,8[3_02_e_a+2_01_e_b]","6_18_a@10,3[2_01_e_b+4_03_e_b]","4_10_a@10,7[4_03_e_b]","3_18_b@9,4[3_07_e_b+3_11_e_a]","6_05_a@9,8","2_16_b@9,9[5_01_e_a+4_02_e_a]"], "bonds": ["sargonShip 0/8","siracusaShip 2/258","swiftShip 1/24","yanShip 0/1"] }, @@ -1108,14 +1108,14 @@ "round": 11, "ai_0": [1,8,12,0,1,5,8,"88de4e65",216,"arcaneShip 1/11, indomShip 0/4, kazimierzShip 1/68, kjeragShip 0/5, preciShip 2/74, raidShip 1/12, suntShip 1/0, swiftShip 1/51",13,25,12,100], "ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62], - "ai_2": [1,5,12,0,0,5,8,"052cd291",171,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/16, lateranoShip 0/4, preciShip 0/4, raidShip 0/6, skillfulShip 1/12, soloShip 1/14, steadShip 0/62, suntShip 1/0, swiftShip 1/75, victoriaShip 1/37, yanShip 1/33",4,25,21,100], + "ai_2": [1,5,12,0,0,5,8,"81827b1b",173,"deputShip 0/24, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/14, lateranoShip 0/4, preciShip 0/4, raidShip 0/6, skillfulShip 1/14, soloShip 1/14, steadShip 0/62, suntShip 1/0, swiftShip 1/75, victoriaShip 1/35, yanShip 1/35",5,25,20,100], "ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68] }, { "round": 12, "ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"arcaneShip 0/11, indomShip 0/4, kazimierzShip 0/84, kjeragShip 0/5, preciShip 0/83, raidShip 0/24, swiftShip 0/56",14,47,33,114], "ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, kazimierzShip 0/25, kjeragShip 0/7, preciShip 0/2, sargonShip 0/8, siracusaShip 0/12, skillfulShip 0/5, steadShip 0/2, swiftShip 0/19",null,null,null,62], - "ai_2": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/26, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/18, lateranoShip 0/4, preciShip 0/6, raidShip 0/6, skillfulShip 0/14, soloShip 0/14, steadShip 0/62, swiftShip 0/89, victoriaShip 0/45, yanShip 0/35",10,47,37,113], + "ai_2": [0,0,12,0,0,6,0,"811c9dc5",0,"deputShip 0/26, egirShip 0/6, kazimierzShip 0/4, kjeragShip 0/14, lateranoShip 0/4, preciShip 0/6, raidShip 0/6, skillfulShip 0/18, soloShip 0/14, steadShip 0/62, swiftShip 0/89, victoriaShip 0/41, yanShip 0/39",14,47,33,113], "ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/6, miraShip 0/4, preciShip 0/4, sargonShip 0/2, siracusaShip 0/31, skillfulShip 0/2, soloShip 0/10",null,null,null,68] } ], @@ -1158,9 +1158,9 @@ "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, "trophies": 2, "reward": 255, - "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4195040,"gold":113,"itemsEquipped":11,"kills":170,"leaks":75,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":9}, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":4195622,"gold":113,"itemsEquipped":11,"kills":175,"leaks":70,"lpLost":33,"merges":3,"perfectRounds":5,"refreshes":9}, "lineup": [], - "bonds": ["deputShip 0/26","egirShip 0/6","kazimierzShip 0/4","kjeragShip 0/18","lateranoShip 0/4","preciShip 0/6","raidShip 0/6","skillfulShip 0/14","soloShip 0/14","steadShip 0/62","swiftShip 0/89","victoriaShip 0/45","yanShip 0/35"] + "bonds": ["deputShip 0/26","egirShip 0/6","kazimierzShip 0/4","kjeragShip 0/14","lateranoShip 0/4","preciShip 0/6","raidShip 0/6","skillfulShip 0/18","soloShip 0/14","steadShip 0/62","swiftShip 0/89","victoriaShip 0/41","yanShip 0/39"] }, "ai_3": { "alive": false, @@ -1188,8 +1188,8 @@ "combat": "server", "boost": null, "setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]}, - "end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":6,"bossPoolLeft":0,"virtualMs":121400}, - "rng": {"setup":25,"shop":448,"waves":0,"draft":34,"bots":18063,"meta":84}, + "end": {"victory":false,"roundsPassed":12,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":112150}, + "rng": {"setup":25,"shop":396,"waves":0,"draft":34,"bots":17567,"meta":81}, "errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0}, "rounds": [ { @@ -1240,52 +1240,52 @@ { "round": 10, "ai_0": [1,18,13,0,0,5,8,"e24edc84",82,"arcaneShip 2/14, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/44, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",23,24,1,89], - "ai_1": [1,17,12,0,0,5,8,"f2b57087",100,"arcaneShip 1/16, indomShip 1/12, kjeragShip 0/8, miraShip 0/3, preciShip 1/35, siracusaShip 1/20, soloShip 1/0, steadShip 1/17, suntShip 1/0, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",23,23,0,86] + "ai_1": [1,16,12,0,0,5,8,"a0b76493",105,"arcaneShip 1/16, indomShip 1/12, kjeragShip 0/8, miraShip 0/3, preciShip 1/41, siracusaShip 1/20, soloShip 1/0, steadShip 1/16, suntShip 1/0, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",22,23,1,86] }, { "round": 11, "ai_0": [1,17,12,0,0,5,8,"65c25a4f",94,"arcaneShip 2/20, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/7, siracusaShip 1/50, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",22,23,1,101], - "ai_1": [1,13,12,0,0,5,8,"b5188f04",124,"arcaneShip 2/26, indomShip 1/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/54, siracusaShip 1/30, soloShip 1/0, steadShip 0/17, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",19,23,4,99] + "ai_1": [1,10,12,0,0,5,8,"22d9d05f",115,"arcaneShip 2/26, indomShip 1/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/45, siracusaShip 1/30, soloShip 1/0, steadShip 0/16, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",17,23,6,99] }, { "round": 12, "ai_0": [1,7,12,0,0,5,8,"b6fcbbbb",106,"arcaneShip 2/26, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/9, siracusaShip 1/54, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",23,46,23,114], - "ai_1": [1,3,12,0,0,6,8,"f5a033f5",153,"arcaneShip 2/26, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/78, siracusaShip 1/30, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",27,46,19,111] + "ai_1": [1,2,12,0,0,6,8,"9a8aedd9",125,"arcaneShip 2/26, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/69, siracusaShip 1/30, soloShip 1/0, steadShip 0/16, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",38,46,8,111] }, { "round": 13, - "ai_0": [1,7,12,0,0,6,8,"3d99f43b",110,"arcaneShip 2/28, emptyShip 1/0, indomShip 0/8, kjeragShip 0/7, preciShip 1/11, siracusaShip 1/54, soloShip 1/11, steadShip 0/2, suntShip 1/0, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",14,39,25,124], - "ai_1": [1,3,12,0,0,6,8,"e9f74be3",189,"arcaneShip 2/34, indomShip 0/14, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 1/92, siracusaShip 1/44, soloShip 1/0, steadShip 1/19, suntShip 1/0, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",39,39,0,124] + "ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/28, indomShip 0/8, kjeragShip 0/7, preciShip 0/11, siracusaShip 0/54, soloShip 0/11, steadShip 0/2, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",14,39,25,124], + "ai_1": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/32, indomShip 0/41, kazimierzShip 0/8, kjeragShip 0/16, miraShip 0/3, preciShip 0/93, siracusaShip 0/42, steadShip 0/16, swiftShip 0/8, victoriaShip 0/8, visiShip 0/8, yanShip 0/6",28,39,11,122] } ], "players": { "ai_0": { - "alive": true, - "victory": true, - "roundsPassed": 14, - "eliminatedRound": null, - "lp": 4, + "alive": false, + "victory": false, + "roundsPassed": 12, + "eliminatedRound": 13, + "lp": 0, "bandId": "band_doberm", - "title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"}, - "trophies": 4, - "reward": 400, - "stats": {"activatedLayers":122,"bossDamage":108916,"dmgDealt":2898036,"gold":136,"itemsEquipped":15,"kills":232,"leaks":65,"lpLost":23,"merges":4,"perfectRounds":7,"refreshes":18}, - "lineup": ["5_10_a@11,3[5_02_e_a+1_02_e_b]","3_10_b@11,6","6_12_a@11,7[3_06_e_a+1_05_e_b]","1_17_a@10,3","4_10_a@10,7[1_02_e_b+1_05_e_b]","1_08_b@10,8[3_11_e_a+4_04_e_b]","4_02_a@9,8","5_14_a@9,9[4_10_e_a+4_05_e_a]"], - "bonds": ["arcaneShip 2/34","emptyShip 1/0","indomShip 0/8","kjeragShip 0/7","miraShip 1/0","preciShip 1/11","siracusaShip 1/60","soloShip 1/11","steadShip 0/2","suntShip 1/0","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"] + "title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"}, + "trophies": 3, + "reward": 280, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2637285,"gold":124,"itemsEquipped":13,"kills":214,"leaks":65,"lpLost":33,"merges":4,"perfectRounds":7,"refreshes":15}, + "lineup": [], + "bonds": ["arcaneShip 0/28","indomShip 0/8","kjeragShip 0/7","preciShip 0/11","siracusaShip 0/54","soloShip 0/11","steadShip 0/2","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"] }, "ai_1": { - "alive": true, - "victory": true, - "roundsPassed": 14, - "eliminatedRound": null, - "lp": 2, + "alive": false, + "victory": false, + "roundsPassed": 12, + "eliminatedRound": 13, + "lp": 0, "bandId": "band_malkie", - "title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"}, - "trophies": 4, - "reward": 400, - "stats": {"activatedLayers":200,"bossDamage":566084,"dmgDealt":4442196,"gold":137,"itemsEquipped":22,"kills":254,"leaks":39,"lpLost":25,"merges":4,"perfectRounds":9,"refreshes":19}, - "lineup": ["5_23_a@11,3","1_17_a@11,6[5_07_e_a+1_02_e_b]","5_12_a@11,7[3_01_e_a+5_09_e_a]","5_02_a@10,3[3_06_e_a+4_08_e_b]","4_10_a@10,7[1_01_e_a+1_05_e_b]","2_16_b@10,8[3_11_e_a+3_08_e_b]","6_03_a@9,8[5_03_e_b]","1_08_b@9,9"], - "bonds": ["arcaneShip 2/42","indomShip 0/14","kazimierzShip 0/8","kjeragShip 0/16","miraShip 0/3","preciShip 1/92","siracusaShip 1/54","soloShip 1/0","steadShip 0/19","suntShip 1/0","swiftShip 0/8","victoriaShip 0/8","visiShip 0/8","yanShip 1/12"] + "title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"}, + "trophies": 3, + "reward": 280, + "stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2743644,"gold":122,"itemsEquipped":19,"kills":226,"leaks":42,"lpLost":36,"merges":3,"perfectRounds":7,"refreshes":17}, + "lineup": [], + "bonds": ["arcaneShip 0/32","indomShip 0/41","kazimierzShip 0/8","kjeragShip 0/16","miraShip 0/3","preciShip 0/93","siracusaShip 0/42","steadShip 0/16","swiftShip 0/8","victoriaShip 0/8","visiShip 0/8","yanShip 0/6"] } } }, diff --git a/test/golden/roster.json b/test/golden/roster.json index d93d5cd0..77c3d912 100644 --- a/test/golden/roster.json +++ b/test/golden/roster.json @@ -135,56 +135,56 @@ "time": 110, "ticks": 3300, "reason": "timeout", - "killed": 83, + "killed": 82, "total": 85, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 69, + "rngDraws": 66, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":83,"total":85,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":14,"dmg":119489,"bossDmg":0,"heal":20553,"deaths":4,"layerGains":{"preciShip":3}} + "p1": {"killed":82,"total":85,"perfect":false,"leaks":3,"leaksUncounted":0,"coins":12,"dmg":122194,"bossDmg":0,"heal":20245,"deaths":4,"layerGains":{"preciShip":3}} }, "units": [ - ["p1",1,"op","chess_char_1_10_b",0,"uniequip_002_sunbr",2493,1,5,1,1,0,818,7,6], - ["p1",2,"op","chess_char_1_12_b",1,"uniequip_002_estell",2215.6,1,7,2,2737,991,0,38,2], - ["p1",3,"op","chess_char_1_20_b",1,"uniequip_002_liskam",1682.2,1,10,16,22525,1763,0,73,1], - ["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",0,0,11,6,23407,10446,7092,102,1], - ["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,14,7,42035,8350,0,76,2], + ["p1",1,"op","chess_char_1_10_b",0,"uniequip_002_sunbr",2493,1,5,0,0,0,833,5,5], + ["p1",2,"op","chess_char_1_12_b",1,"uniequip_002_estell",1703.55,1,7,7,4452,1518,0,30,2], + ["p1",3,"op","chess_char_1_20_b",1,"uniequip_002_liskam",1038.85,1,10,13,20532,2776,0,72,1], + ["p1",4,"op","chess_char_1_18_b",1,"uniequip_002_utage",0,0,11,11,23891,9794,6440,92,1], + ["p1",5,"op","chess_char_1_19_b",1,"uniequip_002_wildmn",0,0,14,8,43663,8645,0,78,2], ["p1",6,"op","chess_char_1_13_b",1,"uniequip_002_podego",2446.5,1,8,3,3,0,0,2,2], - ["p1",7,"op","chess_char_1_14_b",1,"uniequip_002_greyy",1017,1,12,9,874,2616,0,18,1], - ["p1",8,"op","chess_char_1_17_b",1,"uniequip_002_indigo",0,0,15,8,12385,1613,0,14,1], - ["p1",9,"op","chess_char_2_01_a",1,null,1694,1,4,9,5557,0,0,32,1], - ["p1",10,"op","chess_char_2_02_a",1,null,1400,1,6,0,0,0,7247,40,3], - ["p1",11,"op","chess_char_2_04_a",1,null,977,1,9,22,9964,0,0,59,3], - ["p1",12,"op","chess_char_2_05_a",1,null,0,0,13,0,0,4736,3091,21,1], - ["p1",13,"token","token_10000_silent_healrb",null,null,1000,0,97,0,0,0,2304,41,null], + ["p1",7,"op","chess_char_1_14_b",1,"uniequip_002_greyy",1424.9,1,12,9,890,1570,0,18,1], + ["p1",8,"op","chess_char_1_17_b",1,"uniequip_002_indigo",0,0,15,8,12369,1613,0,14,1], + ["p1",9,"op","chess_char_2_01_a",1,null,1694,1,4,12,7220,0,0,25,1], + ["p1",10,"op","chess_char_2_02_a",1,null,1400,1,6,0,0,0,7112,40,3], + ["p1",11,"op","chess_char_2_04_a",1,null,977,1,9,11,9173,0,0,57,3], + ["p1",12,"op","chess_char_2_05_a",1,null,0,0,13,0,0,5040,3508,21,1], + ["p1",13,"token","token_10000_silent_healrb",null,null,1000,0,97,0,0,0,2351,41,null], ["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null], ["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null], ["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null] ], "enemies": { - "enemy_10018_sgrobh": [1,1,0,1778,6750], + "enemy_10018_sgrobh": [1,1,0,1909,6750], "enemy_10067_ftsjc": [1,1,0,1720,11700], - "enemy_10116_ymgtop": [1,1,0,141,9000], + "enemy_10116_ymgtop": [1,1,0,423,9000], "enemy_1041_lazerd": [1,1,0,418,2430], - "enemy_1116_liprr_2": [1,1,0,28,6750], - "enemy_1169_duphlx": [1,1,0,22,3600], + "enemy_1116_liprr_2": [1,1,0,42,6750], + "enemy_1169_duphlx": [1,1,0,11,3600], "enemy_1195_sfyin": [12,12,0,102,21600], "enemy_1196_msfyin": [24,24,0,0,48], - "enemy_1197_sfshu": [9,9,0,290,20250], + "enemy_1197_sfshu": [9,9,0,308,20250], "enemy_1198_msfshu": [27,27,0,0,81], "enemy_1200_msfjin": [1,1,0,0,30], - "enemy_1234_dsubrl_2": [1,1,0,9887,12600], - "enemy_1292_duskld": [1,1,0,992,9010], - "enemy_1387_winshd": [1,0,0,855,10232], + "enemy_1234_dsubrl_2": [1,1,0,9358,12600], + "enemy_1292_duskld": [1,0,0,1632,12206], + "enemy_1387_winshd": [1,0,0,810,9741], "enemy_1502_crowns": [1,1,0,259,5400], - "enemy_2008_flking": [1,0,0,12348,0], + "enemy_2008_flking": [1,0,0,12286,0], "enemy_9016_acstmr": [1,1,0,0,8] }, - "hooks": [1,104,3300,602,602,708,826,235,87,91,24,22,0,15820,206,28,85,0,1,94,1,0,0], - "events": {"atk":685,"bounty":7,"deploy":16,"die":91,"dmg":805,"engage":10,"fx":108,"heal":207,"layer":1,"skill":46,"spawn":101,"status":219}, - "fx": {"aoe":1,"blink":1,"crit":1,"displace":6,"ember":1,"explode":10,"garrison":1,"heal":6,"pulse":45,"reveal":1,"sleep":8,"summon":24,"undying":1,"zone":2}, - "snaps": "554dbc21 dca8028a bfe874b1 c3ea8fde b234e0fc e2e35296 0e5e8cfd 66160212 74b71698 3535e79e 56ba7f72" + "hooks": [1,104,3300,579,579,690,802,225,86,90,23,21,0,15809,218,31,85,0,1,93,1,0,0], + "events": {"atk":674,"bounty":6,"deploy":16,"die":90,"dmg":774,"engage":9,"fx":102,"heal":197,"layer":1,"skill":44,"spawn":101,"status":226}, + "fx": {"aoe":1,"blink":1,"displace":5,"ember":1,"explode":9,"garrison":1,"heal":5,"pulse":41,"reveal":1,"revive":1,"sleep":9,"summon":24,"undying":1,"zone":2}, + "snaps": "554dbc21 dca8028a ae9d883a 88d27421 03d61d45 d4c987be e46502be 86883b09 cded4829 f2362f71 493376cf" }, "roster-004": { "about": "pass 0 band 2: chess_char_2_08_a chess_char_2_17_a chess_char_2_18_a chess_char_2_07_a chess_char_2_09_a chess_char_2_11_a chess_char_2_13_a chess_char_2_16_a chess_char_2_12_a chess_char_2_06_a chess_char_2_10_a chess_char_2_14_a", @@ -254,51 +254,51 @@ "round": 6, "seed": 4076349243, "waveId": "act1autochess_06", - "time": 140, - "ticks": 4200, - "reason": "timeout", - "killed": 82, + "time": 136.73, + "ticks": 4102, + "reason": "cleared", + "killed": 83, "total": 84, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 323, + "rngDraws": 326, "errors": {"count":0,"kinds":[]}, - "players": {"p1":{"killed":82,"total":84,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":10,"dmg":299429,"bossDmg":0,"heal":36310,"deaths":0,"layerGains":{}}}, + "players": {"p1":{"killed":83,"total":84,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":12,"dmg":306598,"bossDmg":0,"heal":36109,"deaths":0,"layerGains":{}}}, "units": [ - ["p1",1,"op","chess_char_2_07_b",1,"uniequip_002_ghost",2664.2,1,2,0,0,1707,1002,0,0], + ["p1",1,"op","chess_char_2_07_b",1,"uniequip_002_ghost",2664.2,1,2,0,0,1707,1105,0,0], ["p1",2,"op","chess_char_2_08_b",1,"uniequip_002_bubble",3373,1,4,0,336,1472,0,0,1], - ["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",2133,1,6,29,83330,0,0,58,2], - ["p1",4,"op","chess_char_2_11_b",1,"uniequip_002_kazema",2214,1,8,16,80064,11387,0,85,3], - ["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7397,9338,0,130,1], - ["p1",6,"op","chess_char_2_19_a",1,null,1021.2,1,11,1,14957,2535,356,26,3], - ["p1",7,"op","chess_char_2_01_b",1,"uniequip_002_excu",2146,1,12,20,36079,6856,0,22,1], - ["p1",8,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,1,0,0,0,8056,29,5], - ["p1",9,"op","chess_char_2_04_b",1,"uniequip_002_grabds",1270,1,3,4,38154,2115,0,52,3], - ["p1",10,"op","chess_char_2_05_b",1,"uniequip_002_harold",1971.2,1,5,0,0,0,12376,42,2], - ["p1",11,"op","chess_char_2_06_b",0,"uniequip_002_papyrs",1639,1,7,0,0,0,12432,29,14], - ["p1",12,"op","chess_char_2_10_b",1,"uniequip_002_rockr",1406,1,9,5,39113,900,0,113,2], - ["p1",13,"token","token_10000_silent_healrb",null,null,1000,0,103,0,0,0,2088,23,null] + ["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",2133,1,6,29,84155,0,0,57,2], + ["p1",4,"op","chess_char_2_11_b",1,"uniequip_002_kazema",2214,1,8,12,82569,11962,0,90,3], + ["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7371,8191,0,126,1], + ["p1",6,"op","chess_char_2_19_a",1,null,1021.2,1,11,1,14957,2816,356,26,3], + ["p1",7,"op","chess_char_2_01_b",1,"uniequip_002_excu",2146,1,12,20,36079,6946,0,22,1], + ["p1",8,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,1,0,0,0,7934,28,5], + ["p1",9,"op","chess_char_2_04_b",1,"uniequip_002_grabds",1270,1,3,6,38843,2115,0,51,3], + ["p1",10,"op","chess_char_2_05_b",1,"uniequip_002_harold",1971.2,1,5,0,0,0,12708,42,2], + ["p1",11,"op","chess_char_2_06_b",0,"uniequip_002_papyrs",1639,1,7,0,0,0,12126,28,14], + ["p1",12,"op","chess_char_2_10_b",1,"uniequip_002_rockr",1406,1,9,7,42288,900,0,109,2], + ["p1",13,"token","token_10000_silent_healrb",null,null,1000,0,103,0,0,0,1881,22,null] ], "enemies": { - "enemy_10027_vtsk": [1,0,0,10485,16614], + "enemy_10027_vtsk": [1,1,0,9339,24000], "enemy_1006_shield_2": [1,1,0,0,8000], "enemy_10122_uacann_2": [1,1,0,4166,8000], "enemy_1042_frostd": [1,0,1,0,4978], "enemy_1119_vofsd": [1,1,0,125,8800], "enemy_1170_dushld": [1,1,0,221,5600], "enemy_1202_msfzhi": [1,1,0,0,3], - "enemy_1249_lysdb_2": [15,15,0,186,72000], + "enemy_1249_lysdb_2": [15,15,0,124,72000], "enemy_1251_lysyta": [52,52,0,99,114400], "enemy_1299_ymkilr_2": [1,1,0,0,8800], - "enemy_1328_cbjedi": [6,6,0,5477,48000], - "enemy_1402_tgshd_2": [1,1,0,1043,10400], - "enemy_1509_mousek": [1,1,0,9774,17600], + "enemy_1328_cbjedi": [6,6,0,6093,48000], + "enemy_1402_tgshd_2": [1,1,0,1063,10400], + "enemy_1509_mousek": [1,1,0,10145,17600], "enemy_2010_csdcr": [1,1,0,1414,9600] }, - "hooks": [1,102,4200,712,712,1693,1684,767,82,89,37,36,0,28924,152,52,84,1,1,82,0,0,0], - "events": {"atk":893,"bounty":5,"deploy":18,"die":88,"dmg":1628,"engage":7,"fx":69,"heal":143,"leak":1,"skill":73,"spawn":97,"status":437}, - "fx": {"counter":1,"explode":3,"expose":14,"overload":2,"phase":1,"shield":8,"shieldBreak":16,"sleep":7,"summon":5,"telegraph":5,"zone":7}, - "snaps": "de0d6fce 6d6804e9 d7011be0 b72154cf 325314c7 efa5c8d0 ae45ceac e177f8b9 c1acb458 e7de2cbb de6e5396 c820655d de465eb1 281894a7" + "hooks": [1,102,4102,705,705,1698,1689,818,83,90,37,36,0,27981,153,54,84,1,1,83,0,0,0], + "events": {"atk":887,"bounty":6,"deploy":18,"die":89,"dmg":1635,"engage":7,"fx":77,"heal":137,"leak":1,"skill":73,"spawn":97,"status":432}, + "fx": {"counter":1,"explode":3,"expose":20,"overload":2,"phase":1,"shield":10,"shieldBreak":16,"sleep":7,"summon":5,"telegraph":5,"zone":7}, + "snaps": "de0d6fce 6d6804e9 d7011be0 b72154cf 325314c7 efa5c8d0 2da84bdb 375f2f02 3854744f 90d8fdd8 6af39dcd 52647a5a aa32bc23 6da186e3" }, "roster-006": { "about": "pass 0 band 3: chess_char_2_17_b chess_char_2_18_b chess_char_3_06_a chess_char_2_13_b chess_char_2_16_b chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_3_05_a chess_char_2_14_b chess_char_2_19_b chess_char_3_01_a", @@ -800,10 +800,10 @@ "enemy_2052_smgia": [1,0,1,0,35173], "enemy_5601_entlec": [7,7,0,0,81403] }, - "hooks": [1,126,5700,593,593,1848,2658,17845,100,114,25,21,16,24262,248,23,111,12,1,185,29,2,0], + "hooks": [1,126,5700,593,593,1848,2658,17845,100,114,25,21,16,24262,248,34,111,12,1,185,29,2,0], "events": {"atk":1052,"bounty":3,"deploy":15,"die":102,"dmg":2093,"engage":8,"fx":286,"heal":267,"layer":29,"leak":12,"skill":46,"spawn":123,"status":721}, "fx": {"anchor":3,"aoe":2,"bandage":2,"burst":2,"candle":7,"downed":2,"ember":2,"explode":1,"garrison":29,"garrisonGrant":1,"jungleSoul":2,"mote":96,"overload":2,"redistribute":38,"revive":1,"reweave":3,"sandChains":2,"sandChainsCharged":2,"spGift":35,"splash":16,"summon":1,"wake":37}, - "snaps": "6c317ca9 7f45f40d dad79f3d e90c8987 002195fc 2da215da 1d494876 e7b6c5b2 d1226386 4ee87a1d fa35734a 5d6d473f e88eabc4 99f28a48 c9d792a9 4eb8e136 5529c074 daac4c5a 05eacc7a" + "snaps": "6c317ca9 7f45f40d dad79f3d 99472eca c8ff8009 374a306e 39ea6e22 38c51cc6 7bf1c5ec 6882bc3c 97552581 19a285c4 a8e9eb1b 03617b71 5dc61b83 e379f4f2 b33d0274 22a5ce5a 26528e7a" }, "roster-014": { "about": "pass 0 band 6: chess_char_5_11_a chess_char_5_19_a chess_char_5_13_a chess_char_5_14_a chess_char_5_01_b chess_char_5_17_a chess_char_5_12_a chess_char_5_15_a chess_char_5_20_a chess_char_5_21_a chess_char_5_22_a chess_char_5_23_a", @@ -1899,7 +1899,7 @@ "total": 79, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 122, + "rngDraws": 123, "errors": {"count":0,"kinds":[]}, "players": { "p1": { @@ -1909,26 +1909,26 @@ "leaks": 8, "leaksUncounted": 1, "coins": 6, - "dmg": 1024490, + "dmg": 1016277, "bossDmg": 0, - "heal": 157541, - "deaths": 5, - "layerGains": {"sargonShip":34} + "heal": 150903, + "deaths": 7, + "layerGains": {"sargonShip":24} } }, "units": [ - ["p1",1,"op","chess_char_4_23_b",0,"uniequip_003_gvial2",0,0,96,5,47747,11806,6174,36,1], - ["p1",2,"op","chess_char_5_08_a",0,null,0,0,77,2,68956,4400,2199,19,7], - ["p1",3,"op","chess_char_5_01_a",0,null,1967,1,11,6,54512,1073,1073,46,2], - ["p1",4,"op","chess_char_5_05_a",0,null,5777,1,13,14,163803,46673,31954,39,20], - ["p1",5,"op","chess_char_5_06_a",0,null,2933.87,1,15,32,442381,70166,69365,130,41], + ["p1",1,"op","chess_char_4_23_b",0,"uniequip_003_gvial2",0,0,97,5,47747,11806,6174,36,1], + ["p1",2,"op","chess_char_5_08_a",0,null,0,0,77,4,73809,4400,2199,20,7], + ["p1",3,"op","chess_char_5_01_a",0,null,1967,1,11,6,60235,241,241,67,3], + ["p1",4,"op","chess_char_5_05_a",0,null,5777,1,13,21,201321,48835,29892,48,24], + ["p1",5,"op","chess_char_5_06_a",0,null,3659.04,1,15,24,420970,68885,68923,129,37], ["p1",6,"op","chess_char_5_07_a",0,null,660.99,1,9,0,0,1555,0,0,0], - ["p1",7,"op","chess_char_4_24_b",0,"uniequip_003_billro",1978,1,7,3,125716,0,0,37,4], - ["p1",8,"op","chess_char_4_25_b",0,"none",2982,1,89,0,0,4203,6006,0,1], - ["p1",9,"op","chess_char_4_26_b",1,"none",1482,1,12,0,0,16632,31741,64,5], - ["p1",10,"op","chess_char_5_02_a",0,null,1208,1,14,4,51746,13946,9030,130,3], - ["p1",11,"op","chess_char_5_03_a",0,null,1206,1,4,4,42543,0,0,61,6], - ["p1",12,"op","chess_char_5_10_a",0,null,1184,1,8,1,27086,0,0,55,3], + ["p1",7,"op","chess_char_4_24_b",0,"uniequip_003_billro",1978,1,7,7,121204,0,0,36,4], + ["p1",8,"op","chess_char_4_25_b",0,"none",2982,1,89,0,0,4203,6441,0,0], + ["p1",9,"op","chess_char_4_26_b",1,"none",1482,1,12,0,0,16536,32893,66,5], + ["p1",10,"op","chess_char_5_02_a",0,null,1208,1,96,1,29428,7787,4140,76,1], + ["p1",11,"op","chess_char_5_03_a",0,null,1206,1,4,1,42705,0,0,60,6], + ["p1",12,"op","chess_char_5_10_a",0,null,1184,1,100,2,18858,1184,0,40,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] @@ -1936,19 +1936,19 @@ "enemies": { "enemy_10029_vtshld": [1,0,0,0,0], "enemy_1009_lurker": [1,1,0,76,5474], - "enemy_1019_jshoot_2": [21,21,0,23001,141088], - "enemy_1050_lslime": [1,1,0,59543,99533], + "enemy_1019_jshoot_2": [21,21,0,24765,141088], + "enemy_1050_lslime": [1,1,0,53385,99533], "enemy_1168_dumage_2": [1,1,0,2963,16174], "enemy_1208_msfji": [1,1,0,0,25], - "enemy_1299_ymkilr_2": [10,6,4,48982,184211], - "enemy_1422_lrsldr_2": [42,39,3,16426,557807], - "enemy_1433_dsbasi_2": [1,1,0,1073,14183], - "enemy_2025_syufo": [1,0,1,6212,5995] + "enemy_1299_ymkilr_2": [10,6,4,48651,178103], + "enemy_1422_lrsldr_2": [42,39,3,15329,555853], + "enemy_1433_dsbasi_2": [1,1,0,1804,14183], + "enemy_2025_syufo": [1,0,1,6212,5843] }, - "hooks": [1,98,5700,904,904,3483,3735,1662,76,84,93,92,18,27936,433,79,80,8,1,80,14,3,0], - "events": {"atk":1408,"bounty":3,"deploy":15,"die":76,"dmg":3735,"engage":9,"fx":220,"heal":1410,"layer":14,"leak":8,"skill":185,"spawn":95,"status":410}, - "fx": {"anchor":20,"aoe":6,"bloodBattle":7,"burst":3,"camouflage":2,"displace":23,"explode":8,"extraAttack":14,"garrison":14,"garrisonGrant":1,"mote":58,"reborn":1,"shell":4,"spGift":56,"zone":3}, - "snaps": "0c53a603 05d5c701 ee9f4dd1 513e9602 3c21e902 78c6425e e7c025d9 93b89490 8d6a0515 df9c19cd ecab44df 4ae9e6ce 185ecf73 739fe00a 3325d0b1 85dff7e4 fdc8bbd8 f9295138 ca8fd5c7" + "hooks": [1,100,5700,872,872,3257,3516,1623,78,86,90,90,27,27050,368,81,80,8,1,81,6,3,0], + "events": {"atk":1359,"bounty":3,"deploy":17,"die":78,"dmg":3516,"engage":9,"fx":224,"heal":1333,"layer":6,"leak":8,"skill":180,"spawn":95,"status":346}, + "fx": {"anchor":24,"aoe":6,"bloodBattle":7,"burst":3,"camouflage":2,"displace":29,"explode":7,"extraAttack":18,"garrison":6,"garrisonGrant":1,"mote":57,"reborn":1,"shell":4,"spGift":56,"zone":3}, + "snaps": "0c53a603 05d5c701 ee9f4dd1 abd32c30 5366cf6c aa83c7a6 e2beb6c4 1af8d204 cd38375f 4c973f52 7c909eaa 20930338 d767885a 326aae2c a17e7fdb ef8e69f6 fe83f65d d13c3a5b 05175b47" }, "roster-033": { "about": "pass 1 band 6: chess_char_5_11_a chess_char_5_19_a chess_char_5_13_a chess_char_5_14_a chess_char_5_06_b chess_char_5_17_a chess_char_5_12_a chess_char_5_15_a chess_char_5_20_a chess_char_5_21_a chess_char_5_22_a chess_char_5_23_a", @@ -2267,48 +2267,48 @@ "round": 13, "seed": 3127854496, "waveId": "act1autochess_h06", - "time": 197.27, - "ticks": 5918, + "time": 197.43, + "ticks": 5923, "reason": "cleared", "killed": 157, "total": 157, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 56, + "rngDraws": 67, "errors": {"count":0,"kinds":[]}, "players": { - "p1": {"killed":157,"total":157,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":8,"dmg":3448764,"bossDmg":0,"heal":106165,"deaths":3,"layerGains":{"raidShip":22}} + "p1": {"killed":157,"total":157,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":8,"dmg":3449270,"bossDmg":0,"heal":103213,"deaths":4,"layerGains":{"raidShip":22}} }, "units": [ - ["p1",1,"op","chess_char_5_08_b",1,"none",2775,1,2,21,424128,0,0,65,5], - ["p1",2,"op","chess_char_6_19_b",0,"uniequip_003_blkkgt",4754.9,1,4,0,4247,0,0,2,0], - ["p1",3,"op","chess_char_5_01_b",2,"none",2314,1,6,20,279408,0,0,34,1], - ["p1",4,"op","chess_char_5_05_b",1,"uniequip_002_ulpia",8260.78,1,7,20,277301,38897,9528,40,1], - ["p1",5,"op","chess_char_5_06_b",1,"none",828.91,1,156,59,1423194,25037,19397,71,6], - ["p1",6,"op","chess_char_6_17_b",0,"uniequip_003_nearl2",0,0,100,4,92436,7803,0,50,1], - ["p1",7,"op","chess_char_5_07_b",1,"none",2840.8,1,12,8,216499,48084,0,154,6], - ["p1",8,"op","chess_char_6_18_b",0,"none",1463.86,1,9,6,121106,0,0,149,1], - ["p1",9,"op","chess_char_6_20_b",0,"uniequip_003_agoat2",1453.26,1,10,0,0,0,48084,53,1], - ["p1",10,"op","chess_char_5_02_b",1,"uniequip_002_titi",1439,1,1,11,493585,0,28980,24,3], - ["p1",11,"op","chess_char_5_03_b",1,"none",1472,1,3,5,57668,736,177,18,2], - ["p1",12,"op","chess_char_5_10_b",1,"none",1380,1,5,2,59193,0,0,61,1] + ["p1",1,"op","chess_char_5_08_b",1,"none",2775,1,2,23,410973,0,0,63,4], + ["p1",2,"op","chess_char_6_19_b",0,"uniequip_003_blkkgt",4754.9,1,4,0,9232,0,0,4,1], + ["p1",3,"op","chess_char_5_01_b",2,"none",2314,1,6,21,302967,2234,2234,38,1], + ["p1",4,"op","chess_char_5_05_b",1,"uniequip_002_ulpia",0,0,7,13,265087,43586,12120,34,1], + ["p1",5,"op","chess_char_5_06_b",1,"none",2807.31,1,138,63,1454357,22761,18897,69,5], + ["p1",6,"op","chess_char_6_17_b",0,"uniequip_003_nearl2",3263,1,171,3,70544,7507,0,34,0], + ["p1",7,"op","chess_char_5_07_b",1,"none",2840.8,1,12,8,231597,47397,0,154,6], + ["p1",8,"op","chess_char_6_18_b",0,"none",1463.86,1,9,7,121833,0,0,150,1], + ["p1",9,"op","chess_char_6_20_b",0,"uniequip_003_agoat2",1453.26,1,10,0,0,0,47397,49,1], + ["p1",10,"op","chess_char_5_02_b",1,"uniequip_002_titi",1439,1,1,14,488700,0,22565,20,4], + ["p1",11,"op","chess_char_5_03_b",1,"none",1251.2,1,3,3,34511,736,0,16,2], + ["p1",12,"op","chess_char_5_10_b",1,"none",1380,1,5,2,59470,0,0,60,1] ], "enemies": { "enemy_1006_shield_3": [1,1,0,5533,143327], "enemy_1021_bslime": [1,1,0,1535,8815], "enemy_1148_dssbr_2": [1,1,0,0,12899], "enemy_1198_msfshu_2": [1,1,0,0,4], - "enemy_1249_lysdb_2": [60,60,0,33576,1289945], - "enemy_1251_lysyta": [51,51,0,4569,502541], + "enemy_1249_lysdb_2": [60,60,0,34017,1289945], + "enemy_1251_lysyta": [51,51,0,7895,502541], "enemy_1270_nhstlk": [1,1,0,1018,42998], - "enemy_1329_cbshld": [39,39,0,69522,1397432], + "enemy_1329_cbshld": [39,39,0,69420,1397441], "enemy_1389_winbab_2": [1,1,0,4067,25082], "enemy_2001_duckmi": [1,1,0,0,64497] }, - "hooks": [1,171,5918,881,880,4623,4636,545,160,160,28,22,58,16483,2343,92,157,0,1,163,2,1,0], - "events": {"atk":1469,"bounty":4,"deploy":14,"die":160,"dmg":4636,"engage":11,"fx":108,"heal":472,"layer":2,"skill":50,"spawn":169,"status":467}, - "fx": {"aoe":13,"burst":1,"explode":1,"extraAttack":8,"garrison":2,"garrisonGrant":4,"meltdown":1,"overload":4,"reborn":2,"shieldBreak":60,"sleepGuard":3,"sunBurst":3,"talent":3,"undying":3}, - "snaps": "1ac9ec64 7b9548db f09bce85 8fdfbf4c b7b5cfb1 1ffd5f45 b65ed6f3 0dd60492 e327aa8a 368e4eb0 a9b2c7ac 1e89bd0e ea4b335c 50cf09b8 376cd09d e793a857 4e0fc531 33a5eca2 6e3b0cd5 7ea9fd76" + "hooks": [1,172,5923,849,849,4656,4629,523,161,161,27,23,57,16960,2558,101,157,0,1,165,2,0,0], + "events": {"atk":1436,"bounty":4,"deploy":15,"die":161,"dmg":4629,"engage":11,"fx":107,"heal":458,"layer":2,"skill":50,"spawn":169,"status":507}, + "fx": {"aoe":11,"explode":1,"extraAttack":9,"garrison":2,"garrisonGrant":4,"overload":4,"reborn":2,"shieldBreak":60,"sleepGuard":4,"sunBurst":4,"talent":3,"undying":3}, + "snaps": "1ac9ec64 7b9548db f09bce85 54e1e556 17bf8513 514c29fd d782d1a8 21c0bd9b cd87cfad 0641171c 1aa3bfe2 abb2a939 be90ba64 1f4cc818 b37a3ca9 1b4ceefd 10927153 3ff6700f 7f77dd7d 7d7cc900" }, "roster-039": { "about": "pass 2 band 4: chess_char_3_12_a chess_char_3_12_b chess_char_3_18_a chess_char_3_18_b chess_char_3_04_a chess_char_3_05_a chess_char_3_04_b chess_char_3_05_b chess_char_3_01_a chess_char_3_19_a chess_char_3_21_a chess_char_3_01_b token_10028_vigil_wolf", @@ -2322,59 +2322,59 @@ "time": 170, "ticks": 5100, "reason": "timeout", - "killed": 47, + "killed": 41, "total": 52, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 32, + "rngDraws": 22, "errors": {"count":0,"kinds":[]}, "players": { "p1": { - "killed": 47, + "killed": 41, "total": 52, "perfect": false, - "leaks": 5, + "leaks": 11, "leaksUncounted": 1, "coins": 6, - "dmg": 437959, + "dmg": 404027, "bossDmg": 0, - "heal": 13283, - "deaths": 1, - "layerGains": {"miraShip":3,"raidShip":26} + "heal": 15438, + "deaths": 2, + "layerGains": {"miraShip":1,"raidShip":26} } }, "units": [ - ["p1",1,"op","chess_char_3_12_a",1,null,4457.94,1,29,6,56116,1735,7454,222,9], - ["p1",2,"op","chess_char_3_12_b",1,"none",5607.76,1,31,6,52402,1466,5490,156,7], - ["p1",3,"op","chess_char_3_18_a",1,null,1481,1,10,0,5796,0,0,0,7], - ["p1",4,"op","chess_char_3_18_b",1,"none",1845,1,60,3,61231,4973,0,130,9], - ["p1",5,"op","chess_char_3_04_a",2,null,1864,1,9,5,30544,308,0,102,3], - ["p1",6,"op","chess_char_3_05_a",1,null,5876,1,11,0,20218,5530,0,84,1], - ["p1",7,"op","chess_char_3_04_b",2,"uniequip_004_swire2",2588.3,1,74,8,61422,352,301,62,4], - ["p1",8,"op","chess_char_3_05_b",1,"none",6786.23,1,34,3,38798,723,0,33,2], - ["p1",9,"op","chess_char_3_01_a",1,null,1471.8,1,1,1,3517,0,0,36,1], - ["p1",10,"op","chess_char_3_19_a",1,null,1439,1,4,3,34773,0,0,114,12], - ["p1",11,"op","chess_char_3_21_a",1,null,1364,1,7,11,62130,0,0,197,19], - ["p1",12,"op","chess_char_3_01_b",1,"none",1770.9,1,2,1,7067,101,0,36,2], - ["p1",13,"token","token_10028_vigil_wolf",null,null,880,1,5,0,3947,38,38,7,null] + ["p1",1,"op","chess_char_3_12_a",1,null,4457.94,1,29,6,52760,4358,7227,222,8], + ["p1",2,"op","chess_char_3_12_b",1,"none",5607.76,1,31,3,51629,1042,7948,148,8], + ["p1",3,"op","chess_char_3_18_a",1,null,1481,1,10,0,5032,0,0,0,8], + ["p1",4,"op","chess_char_3_18_b",1,"none",1567,1,60,5,75845,4487,0,126,9], + ["p1",5,"op","chess_char_3_04_a",2,null,1864,1,71,3,18018,52,0,18,2], + ["p1",6,"op","chess_char_3_05_a",1,null,5876,1,11,1,20320,7361,0,108,1], + ["p1",7,"op","chess_char_3_04_b",2,"uniequip_004_swire2",2588.3,1,72,6,44983,267,250,91,4], + ["p1",8,"op","chess_char_3_05_b",1,"none",6786.23,1,34,3,35896,13,0,32,2], + ["p1",9,"op","chess_char_3_01_a",1,null,1471.8,1,1,2,11446,0,0,64,2], + ["p1",10,"op","chess_char_3_19_a",1,null,225.6,1,4,1,27741,1213,0,90,12], + ["p1",11,"op","chess_char_3_21_a",1,null,0,0,7,10,44527,1364,0,152,15], + ["p1",12,"op","chess_char_3_01_b",1,"none",1770.9,1,2,1,14346,101,0,64,3], + ["p1",13,"token","token_10028_vigil_wolf",null,null,880,1,5,0,1484,13,13,2,null] ], "enemies": { "enemy_10030_vtwand": [1,0,0,0,0], "enemy_1010_demon": [1,1,0,1229,8100], - "enemy_1061_zomshd": [1,1,0,907,24354], + "enemy_1061_zomshd": [1,1,0,707,18748], "enemy_1169_duphlx": [1,1,0,13,4320], "enemy_1208_msfji_2": [1,1,0,0,30], "enemy_1302_ymtro_2": [1,1,0,0,32400], - "enemy_1381_winman": [21,21,0,1153,131544], - "enemy_1381_winman_2": [12,12,0,1153,101088], - "enemy_1387_winshd": [12,7,0,9602,113227], - "enemy_1438_dspred": [1,1,0,1030,14256], - "enemy_2034_sythef": [1,1,0,139,8640] + "enemy_1381_winman": [21,19,0,1343,127117], + "enemy_1381_winman_2": [12,12,0,1661,101088], + "enemy_1387_winshd": [12,4,0,12356,93033], + "enemy_1438_dspred": [1,1,0,2823,14256], + "enemy_2034_sythef": [1,0,1,139,4935] }, - "hooks": [1,73,5100,1445,1445,1980,1980,2325,48,54,76,73,0,16134,93,54,53,0,1,50,8,0,0], - "events": {"atk":1445,"bounty":3,"deploy":20,"die":54,"dmg":1980,"engage":12,"fx":176,"heal":188,"layer":8,"skill":149,"spawn":66,"status":147}, - "fx": {"aoe":32,"blink":3,"bondMilestone":1,"buff":14,"coin":63,"displace":6,"dp":28,"garrison":8,"shield":1,"volley":19,"wolfShadow":1}, - "snaps": "7039516f 1f992cb5 0f5e59af c4b1bbc5 07a0826f 56c348a1 323da105 91eb067a 068e4335 66187a93 1732df53 b1fbe31f f69a53e8 250909f9 25729cd3 5c78fdcd 2b29dd1f" + "hooks": [1,74,5100,1414,1414,1852,1852,1548,43,51,74,72,0,17814,122,94,53,1,1,48,6,0,0], + "events": {"atk":1414,"bounty":3,"deploy":21,"die":50,"dmg":1851,"engage":12,"fx":157,"heal":185,"layer":6,"leak":1,"skill":146,"spawn":66,"status":189}, + "fx": {"aoe":31,"blink":3,"bondMilestone":1,"buff":14,"camouflage":2,"coin":43,"displace":10,"dp":29,"garrison":6,"shield":1,"shieldBreak":1,"volley":15,"wolfShadow":1}, + "snaps": "7039516f 1f992cb5 0f5e59af 5967d149 d4e48f1e e3f0ef30 f3614575 9eb132fa 5c3f1bfa 71e39059 829f9abd 7a131094 e6eac604 85fca6f7 5724d0df 33d9918d 2cf239b8" }, "roster-040": { "about": "pass 2 band 4: chess_char_4_01_a chess_char_4_17_a chess_char_4_18_a chess_char_4_04_a chess_char_4_07_a chess_char_4_16_a chess_char_4_09_a chess_char_3_19_b chess_char_3_21_b chess_char_4_02_a chess_char_4_12_a chess_char_4_13_a token_10028_vigil_wolf", @@ -2490,14 +2490,14 @@ "round": 10, "seed": 3609857679, "waveId": "act1autochess_h03", - "time": 166.97, - "ticks": 5009, + "time": 161.17, + "ticks": 4835, "reason": "cleared", "killed": 59, "total": 59, "unspawned": 0, "bossHpLeft": null, - "rngDraws": 60, + "rngDraws": 62, "errors": {"count":0,"kinds":[]}, "players": { "p1": { @@ -2507,26 +2507,26 @@ "leaks": 0, "leaksUncounted": 0, "coins": 8, - "dmg": 706144, + "dmg": 704099, "bossDmg": 0, - "heal": 30645, + "heal": 26903, "deaths": 5, "layerGains": {"preciShip":80,"sargonShip":90} } }, "units": [ ["p1",1,"op","chess_char_4_17_b",2,"none",4566.9,1,3,0,0,0,0,0,0], - ["p1",2,"op","chess_char_4_18_b",2,"none",6783,1,5,1,1344,0,0,1,0], - ["p1",3,"op","chess_char_4_23_b",1,"uniequip_004_gvial2",3667.3,1,7,0,5552,649,0,2,1], - ["p1",4,"op","chess_char_4_19_b",1,"none",2165.9,1,10,1,22286,1891,0,3,4], - ["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3339.7,1,11,6,46148,13206,8486,53,1], - ["p1",6,"op","chess_char_5_01_a",2,null,0,0,73,0,39755,11316,3645,54,0], - ["p1",7,"op","chess_char_4_16_b",1,"none",1922.7,1,77,7,105393,6426,2581,116,6], - ["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,6,29127,0,0,39,3], - ["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,21,155751,1023,0,69,3], - ["p1",10,"op","chess_char_4_24_b",2,"none",2442.81,1,12,10,146453,7236,646,38,3], - ["p1",11,"op","chess_char_4_25_b",1,"uniequip_002_cetsyr",2172.3,1,4,0,9645,0,1552,0,3], - ["p1",12,"op","chess_char_5_02_a",1,null,1570.4,1,6,7,144690,903,13735,29,4], + ["p1",2,"op","chess_char_4_18_b",2,"none",6783,1,5,0,1795,0,0,1,0], + ["p1",3,"op","chess_char_4_23_b",1,"uniequip_004_gvial2",3667.3,1,7,1,17065,649,0,7,1], + ["p1",4,"op","chess_char_4_19_b",1,"none",2225.9,1,10,0,22646,1831,0,6,4], + ["p1",5,"op","chess_char_4_22_b",1,"uniequip_002_svrash",3325.87,1,11,4,46179,10905,7277,52,1], + ["p1",6,"op","chess_char_5_01_a",2,null,1221.86,1,77,2,40484,12552,3545,54,0], + ["p1",7,"op","chess_char_4_16_b",1,"none",0,0,46,7,106261,5473,1628,116,4], + ["p1",8,"op","chess_char_4_13_b",2,"uniequip_004_gnosis",2553.2,1,8,6,33473,0,0,44,3], + ["p1",9,"op","chess_char_4_20_b",1,"none",1844.7,1,9,27,127407,1023,0,64,3], + ["p1",10,"op","chess_char_4_24_b",2,"none",2448.2,1,12,6,189788,7081,1602,48,4], + ["p1",11,"op","chess_char_4_25_b",1,"uniequip_002_cetsyr",2172.3,1,4,0,9645,0,1327,0,3], + ["p1",12,"op","chess_char_5_02_a",1,null,1570.4,1,6,5,109356,678,11523,28,4], ["-","~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] ], @@ -2536,17 +2536,17 @@ "enemy_1112_emppnt_2": [1,1,0,0,18432], "enemy_1183_mlasrt": [1,1,0,573,7488], "enemy_1249_lysdb_2": [1,1,0,0,6912], - "enemy_1267_nhpbr_2": [33,33,0,10082,209088], - "enemy_1270_nhstlk_2": [12,12,0,8739,221184], - "enemy_1272_nhtank_2": [6,6,0,10411,103680], + "enemy_1267_nhpbr_2": [33,33,0,9041,209088], + "enemy_1270_nhstlk_2": [12,12,0,8266,221184], + "enemy_1272_nhtank_2": [6,6,0,9254,103680], "enemy_1329_cbshld_2": [1,1,0,1824,14976], - "enemy_1512_mcmstr": [1,1,0,5044,92160], - "enemy_9008_acbunn": [1,1,0,1338,13824] + "enemy_1512_mcmstr": [1,1,0,4908,92160], + "enemy_9008_acbunn": [1,1,0,1686,13824] }, - "hooks": [1,77,5009,530,530,1322,1337,2127,67,64,28,26,0,16734,2065,40,59,0,1,75,50,1,6], - "events": {"atk":637,"bounty":4,"deploy":16,"die":64,"dmg":1179,"engage":10,"fx":180,"heal":172,"layer":50,"skill":54,"spawn":73,"status":273}, - "fx": {"aoe":4,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":3,"dodge":6,"explode":26,"extraAttack":7,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":28,"overload":1,"revive":1,"reweave":3,"shieldBreak":1,"sleepGuard":4,"snow":1,"swordStorm":6,"telegraph":2,"zone":40}, - "snaps": "875d082b 85a4bc0b 9625d9f6 72f888b8 c305d6aa fde1882d 3b825ab8 9f744e02 8ef0fc59 54683ff7 87a57f66 4c384ffd 210d3aff 08bfd1d2 548b73fe bec1eec3 ae8f7277" + "hooks": [1,77,4835,552,552,1304,1319,1848,66,64,27,23,0,16257,1902,44,59,0,1,72,50,1,6], + "events": {"atk":676,"bounty":4,"deploy":16,"die":64,"dmg":1143,"engage":10,"fx":178,"heal":164,"layer":50,"skill":50,"spawn":73,"status":275}, + "fx": {"aoe":4,"bombard":5,"bombardShell":5,"burst":1,"coldWind":3,"devour":4,"dodge":6,"explode":26,"extraAttack":7,"garrison":26,"garrisonGrant":1,"iceSpike":3,"link":3,"mote":28,"overload":1,"revive":1,"reweave":3,"shieldBreak":1,"sleepGuard":4,"snow":1,"swordStorm":4,"telegraph":2,"zone":39}, + "snaps": "875d082b 85a4bc0b 9625d9f6 72f888b8 c305d6aa e14a3f12 84215e7b daa0a5e0 92ebfc5c 936a9b84 d60c8f2e 110bde45 ec55e3fc 6bbb6b96 bd60e4f7 65e65109 8cfddc21" }, "roster-043": { "about": "pass 2 band 5: chess_char_5_08_a chess_char_5_11_a chess_char_5_05_a chess_char_5_06_a chess_char_5_13_a chess_char_5_14_a chess_char_5_07_a chess_char_5_17_a chess_char_5_03_a chess_char_5_10_a chess_char_5_12_a chess_char_5_15_a", diff --git a/test/sim/feedback4-sleep-unblock.test.js b/test/sim/feedback4-sleep-unblock.test.js new file mode 100644 index 00000000..c0269788 --- /dev/null +++ b/test/sim/feedback4-sleep-unblock.test.js @@ -0,0 +1,158 @@ +// GitHub #140 (a player who tested the official mode; the owner's decision of 2026-10-06): 瑕光's S2 put 4 enemies to +// sleep and she could still block the next one; once the skill ended they could no longer be blocked. PRTS 异常效果: +// SLEEPING 沉睡 = 「无法行动+无敌+不可阻挡」, and BLOCK_FREE 不可阻挡 = 「无法阻挡/被阻挡,自动解除阻挡」. So a sleeping enemy +// is let go by its blocker (the slot frees for the next enemy), is never blocked while asleep, stays where it is (it +// cannot move), and when it wakes it is blocked again only by a blocker with a free slot — else it walks on, like any +// unblocked enemy (DESIGN §24.9). Sleeping operators already released what they block (unchanged). +// +// Flat stage: enemies walk row 9 from the gate (9,10) to the goal (9,2), i.e. towards smaller columns (0.5 tile/s). + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; +import { getDefaultSource } from '../../server/sim/simdata.js'; + +const OP = 'test_sleep_blocker'; +const foe = (key, o = {}) => enemyRec({ key, hp: 1e7, speed: 1, ...o }); +/** A block-1 operator at (9,6) that never attacks; enemies A and B (B immune to sleep where a test needs it). */ +function lane(enemies, enemyDefs = {}) { + return makeBattle({ + defs: { + chess: { [OP]: chessRec({ id: OP, stats: { atk: 0, blockCnt: 1, maxHp: 1e6, respawnTime: 999 }, skill: null }) }, + enemies: { enemy_a: foe('enemy_a'), enemy_b: foe('enemy_b'), ...enemyDefs }, + }, + units: [{ chessId: OP, row: 9, col: 6 }], enemies, timeLimit: 120, content: 'none', + }); +} +const kill = (h, e) => h.b.loseHp(e, 1e12); + +test('沉睡 frees the block slot: the sleeper is let go at once and stays put, the next enemy is blocked; awake with no room it walks on', () => { + const h = lane([{ key: 'enemy_a', time: 0 }, { key: 'enemy_b', time: 1 }]); + const u = h.unit(OP); + const A = () => h.enemy('enemy_a'), B = () => h.enemy('enemy_b'); + assert.ok(h.runUntil(() => A()?.blockedBy === u, 30), 'A is blocked'); + const ax = A().x, ay = A().y; + assert.ok(h.b.applyStatus(A(), 'sleep', { duration: 6, source: u }), 'A falls asleep'); + assert.equal(A().blockedBy, null, 'released at once (不可阻挡)'); + assert.deepEqual(u.blocking, [], 'the slot is free'); + assert.ok(h.runUntil(() => B()?.blockedBy === u, 10), 'B is blocked in the freed slot'); + assert.ok(A().s.flags.sleep, 'A still asleep'); + assert.equal(A().blockedBy, null, 'the sleeper is not blocked and takes no slot'); + assert.deepEqual(u.blocking, [B()]); + assert.ok(A().x === ax && A().y === ay, 'the sleeper stays where it fell asleep'); + checkInvariants(h.b); + // A wakes while B holds the only slot: it is not blocked and walks on past the operator + assert.ok(h.runUntil(() => !A().s.flags.sleep, 10), 'A wakes'); + h.step(); + assert.equal(A().blockedBy, null, 'no room: A is not blocked'); + assert.equal(B().blockedBy, u, 'B keeps the slot'); + h.run(2); + assert.ok(A().x < ax - 0.5, `A walked on (x ${A().x.toFixed(2)} from ${ax.toFixed(2)})`); + assert.equal(A().blockedBy, null); + assert.equal(B().blockedBy, u); + checkInvariants(h.b); +}); + +test('沉睡: never blocked while asleep, even beside a blocker with room — blocked again where it stands once it wakes', () => { + const h = lane([{ key: 'enemy_a', time: 0 }, { key: 'enemy_b', time: 1 }]); + const u = h.unit(OP); + const A = () => h.enemy('enemy_a'), B = () => h.enemy('enemy_b'); + assert.ok(h.runUntil(() => A()?.blockedBy === u, 30)); + const ax = A().x; + assert.ok(h.b.applyStatus(A(), 'sleep', { duration: 5, source: u })); + assert.ok(h.runUntil(() => B()?.blockedBy === u, 10), 'B takes the slot'); + kill(h, B()); + h.step(2); + assert.ok(!B().alive && u.blocking.length === 0, 'the slot is free again'); + h.run(1); + assert.ok(A().s.flags.sleep, 'A still asleep'); + assert.equal(A().blockedBy, null, 'a sleeper is not blocked although the operator has room'); + assert.equal(u.blocking.length, 0); + assert.ok(h.runUntil(() => !A().s.flags.sleep, 10), 'A wakes'); + h.step(); + assert.equal(A().blockedBy, u, 'blocked again the moment it wakes (a free slot, still in contact)'); + assert.equal(A().x, ax, 'it never moved'); + h.run(2); + assert.equal(A().blockedBy, u); + assert.equal(A().x, ax); + checkInvariants(h.b); +}); + +test('沉睡 through a plain buff (flags.sleep) is let go on the enemy\'s next update; 晕眩 / a refused sleep keep the block', () => { + // a custom sleep buff: no applyStatus release — ai.js updateEnemy lets it go on its next update + const h = lane([{ key: 'enemy_a', time: 0 }]); + const u = h.unit(OP); + const A = () => h.enemy('enemy_a'); + assert.ok(h.runUntil(() => A()?.blockedBy === u, 30)); + h.b.addBuff(A(), { key: 'test:sleep', duration: 2, flags: { sleep: true } }); + h.step(); + assert.equal(A().blockedBy, null, 'released'); + assert.deepEqual(u.blocking, []); + h.run(2.2); + assert.ok(!A().s.flags.sleep && A().blockedBy === u, 'awake beside a free blocker: blocked again'); + checkInvariants(h.b); + // 晕眩 is not 不可阻挡: a stunned enemy stays blocked; a sleep-immune enemy refuses the sleep and stays blocked + const h2 = lane([{ key: 'enemy_a', time: 0 }, { key: 'enemy_x', time: 8 }], { enemy_x: foe('enemy_x', { immunities: { sleepImmune: true } }) }); + const u2 = h2.unit(OP); + const A2 = () => h2.enemy('enemy_a'), X = () => h2.enemy('enemy_x'); + assert.ok(h2.runUntil(() => A2()?.blockedBy === u2, 30)); + assert.ok(h2.b.applyStatus(A2(), 'stun', { duration: 2 })); + h2.step(3); + assert.equal(A2().blockedBy, u2, 'stunned: still blocked'); + kill(h2, A2()); + assert.ok(h2.runUntil(() => X()?.blockedBy === u2, 30)); + assert.equal(h2.b.applyStatus(X(), 'sleep', { duration: 3 }), false, 'immune'); + h2.step(3); + assert.equal(X().blockedBy, u2, 'no sleep, still blocked'); + checkInvariants(h2.b); +}); + +test('沉睡: the blocker\'s blocked-target list (Battle.blockedTargets — blocked-first targeting, the DEFAULT trigger, hitAllBlocked) drops the sleeper and takes the next enemy', () => { + const h = lane([{ key: 'enemy_a', time: 0 }, { key: 'enemy_b', time: 1 }]); + const u = h.unit(OP); + const A = () => h.enemy('enemy_a'), B = () => h.enemy('enemy_b'); + assert.ok(h.runUntil(() => A()?.blockedBy === u, 30)); + h.b.applyStatus(A(), 'sleep', { duration: 6 }); + assert.deepEqual(h.b.blockedTargets(u, u.profile), [], 'no blocked target while A sleeps and B is not there yet'); + assert.ok(h.runUntil(() => B()?.blockedBy === u, 10)); + assert.deepEqual(h.b.blockedTargets(u, u.profile), [B()], 'B is the blocked target'); +}); + +test('#140: 瑕光 S2 puts the 3 enemies she blocks to sleep and still blocks the next 3; when S2 ends the sleepers wake with no room and walk on', () => { + const ds = getDefaultSource(); + const id = 'chess_char_3_12_a'; + const rec = ds.rawChess(id); + assert.equal(rec.stats.blockCnt, 3, 'block 3'); + const skillIndex = rec.skills.find((s) => s.skillId === 'skchr_blemsh_2').index; + const h = makeBattle({ + // (a MANUAL 重装 skill: cast by a blocked enemy's hit — TAKE_DAMAGE) + defs: { enemies: { enemy_x: foe('enemy_x', { atk: 100, bat: 1 }), enemy_y: foe('enemy_y', { atk: 100, bat: 1 }) } }, timeLimit: 90, + units: [{ chessId: id, row: 9, col: 8, skillIndex }], + enemies: [{ key: 'enemy_x', time: 0, count: 3, interval: 0.5 }, { key: 'enemy_y', time: 6, count: 3, interval: 0.5 }], + }); + const u = h.unit(id); + const xs = () => h.b.enemies.filter((e) => e.defId === 'enemy_x'); + const ys = () => h.b.enemies.filter((e) => e.defId === 'enemy_y'); + assert.ok(h.runUntil(() => xs().length === 3 && xs().every((e) => e.blockedBy === u), 30), 'she blocks the first three'); + u.skill.gainSp(u.skill.spCost * u.skill.maxCharges + 1, 'test'); + assert.ok(h.runUntil(() => u.skill.active, 5), 'S2 cast'); + h.step(); + const pos = new Map(xs().map((e) => [e, e.x])); + assert.ok(xs().every((e) => e.s.flags.sleep && !e.blockedBy), 'the three sleep and are let go'); + assert.equal(u.blocking.length, 0, 'all three slots free'); + // the next three walk in during S2 and are blocked + assert.ok(h.runUntil(() => ys().length === 3 && ys().every((e) => e.blockedBy === u), 12), 'she blocks the next three during S2'); + assert.ok(u.skill.active, 'S2 still running'); + assert.ok(xs().every((e) => e.s.flags.sleep && !e.blockedBy && e.x === pos.get(e)), 'the sleepers stay put, unblocked'); + checkInvariants(h.b); + // S2 ends: the sleepers wake beside her, her three slots full — not blocked, they walk on (the reporter's "技能结束后 + // 则无法被阻挡") + assert.ok(h.runUntil(() => !u.skill.active, 12), 'S2 ends'); + h.step(); + assert.ok(xs().every((e) => !e.s.flags.sleep), 'awake'); + assert.ok(xs().every((e) => !e.blockedBy), 'no room: none of them is blocked'); + assert.deepEqual(new Set(u.blocking), new Set(ys()), 'she keeps the three she holds'); + h.run(3); + for (const e of xs()) assert.ok(e.x < pos.get(e) - 1, `walked on (x ${e.x.toFixed(2)})`); + checkInvariants(h.b); +});