feedback7: merge fb7-gh-sim — PR #348 (take-the-highest chains), PR #293 (REA-Y in the engine), #329 ports (异客 13 tiles, 歌蕾蒂娅 net radius 1, 搅拌机 latest first, 大帝 uncapped), PR #365 (骨刺 forms + PRTS targeting), #298 (instant non-attack skills recast at most once per attack interval, attacks untouched), #364 (先兆者 shell 3×3), #220 (速射手 air first), 余 skips noStack bonds (owner)

- Conflict: docs/PLAYING.md 技能自动释放 bullet — both insertions kept (the 铁钳号 rule from fb7-owner-sim, the #298
  pacing sentence).
- Grok review: MERGE AFTER FIXES — the HIGH #298 finding (the cast replaced the due attack) fixed on the branch
  (db29acf9: per-skill nextCastAt; golden of #298 alone shrank from 84 scenarios to 2).
- Golden union: 4 matches differ from HEAD though the branch alone left them (coop2-NORMAL-3, coop4-ABYSS-7,
  coop2-NORMAL-8-serverrun, solo-HARD-9-boosted): HEAD's side had moved all four (#333's shop RNG …) and on those new
  bot paths the merged rules apply — the diffs are in-battle layer gains / damage (卡西米尔 50 → 45, 精准 80 → 76,
  叙拉古 176 → 168, kills ±2, solo-HARD-9 LP 397 → 399), no outcome change.
- Broad node --test 5571 (5570 pass, 0 fail, 1 skipped); lint 0 errors; typecheck.
This commit is contained in:
sganggs committed 2026-10-08 21:27:04 +08:00
commit 52f56319ac
41 files changed
+2065 -648

No files matched your search

+1 -1
View File
@@ -93,7 +93,7 @@ so old imports keep working: `public/js/ui/gameLogic.js` (`public/js/ui/gameLogi
| `server/sim/content/enemies/` | the enemy kits by special type (`invisible.js`, `times.js`, `element.js`, `dot.js`, `reflection.js`, `fly.js`, `special.js`) and `leaders.js`; `server/sim/content/enemies.js` dispatches them, `server/sim/content/bosses.js` scripts the leaders |
| `server/sim/content/garrisons/`, `items/`, `bands/` | 特质, equipment and strategies: `battle.js` is the battle side, `meta.js` the prep side (`registerMeta`, META §2) |
| `server/sim/content/bonds/` | the 23 bonds: `core.js` the 8 core bonds (both sides), `server/sim/content/bonds/addon/` the 15 add-on bonds (`battle.js`, `meta.js`) |
| `server/sim/content/` (the rest) | `tokens.js` (summons), `devices.js` (terrain and stage devices), `generic.js` (the kit built from a skill's data when a chess has none), `choices.js` (机变 cards in battle) |
| `server/sim/content/` (the rest) | `tokens.js` (summons), `devices.js` (terrain and stage devices), `generic.js` (the kit built from a skill's data when a chess has none), `choices.js` (机变 cards in battle), `traitMods.js` (the module trait line the engine applies to every operator: 「攻击范围内存在N名及以上敌人时攻击速度+X」) |
| `shared/` | imported by the server and the browser: `protocol.js`, `constants.js`, `i18n.js`, `i18nData.js` and `i18nPacks.js` (languages), `packs.js` (the content-pack format), `standIn.js` (补位), `diy.js` (自选编队), `highGround.js`, `loadoutRecord.js` |
### Client (`public/`)
+5 -1
View File
@@ -279,8 +279,12 @@ catch-up frames and hidden tabs (`keepsState`), the game screen's pre-entry buff
event queue (`render/interp.js isCosmeticEvent`) too — or, for a view built mid-battle, UnitInfo `form`, which `render/app/info.js renderInfo` passes to the view; a
`change` clip plays once first, an `end` clip is timed from the fx's `dur` to finish as that state ends, keeping the
current form's death clip until the next form's fx). A blocked or revealed 隐匿 enemy is drawn solid: the sim sends the
stealth bit only while its 隐匿 is on:
stealth bit only while its 隐匿 is on — and 假想敌:骨刺 changes model with that bit, not with an fx (`STEALTH_FORMS`, GitHub
#296, PR #365): the manifest's `*_A` (the three-headed snake) while the bit is on, its `*_B` set (form `revealed`) the
moment it is blocked or revealed, `*_A` again when its 隐匿 is back; no change clip, a running attack, stun or freeze
pose carried over (`render/spine.js syncFormPose`). The forms:
- 掠海漂移体's crawl (`Change`, then `*_02`);
- 假想敌:骨刺 (`*_A` / `*_B`, above);
- 转译基底·α's three forms (`A_Die_B` / `_C` / `_D`, 2 s each, then `B_*` 寻仇者, `C_*` 幽灵, `D_*` 特战术师);
- the 深池逐火 embers (`Die`, then `Idle_2` / `Move_2` / `Die_2`; `Revive` ends as it stands up) and 假想敌:再生's puppet
(`A_Die`, then `B_*`; `B_Revive`);
+3 -3
View File
@@ -77,9 +77,9 @@
- 点击干员显示攻击范围和底部按钮(「撤退」回到整备区、「出售 +1」);右键 / 长按查看详细信息:头像旁是盟约,下面第一项就是干员的**特质**(如「休整期结束时 …」),之后是特性、属性、攻击范围、技能、模组、装备、天赋。属性是**实时数值**:作战中每秒刷新 4 次(当前生命、攻击、防御、法抗、攻击间隔、阻挡),休整期显示棋盘上的干员**开战时**的数值(已算入装备、盟约层数、特质、策略与机变效果);高于基础值为绿色、低于为红色并标出差值(攻击间隔变短算增益),鼠标悬停可看基础值。基础值是官方数据在该阶级的等级、技能等级(和精锐的模组)下、**满潜能**、0 信赖的数值:所有干员(精锐、补位、自选也一样)都带上全部潜能加成——部署费用、再部署时间、攻击力 / 生命 / 防御 / 法抗 / 攻速,以及「天赋效果增强」,召唤物的天赋也按召唤者的满潜能(维护者 2026-10-07 的决定,GitHub #252:官方赛事视频里的刺玫正是满潜能的攻击 435、费用 15)。
- **召唤物手动摆放**:把带可部署召唤物的干员放上棋盘后,它的召唤物会进入整备区(赫默的医疗探机、巫恋的诅咒娃娃、凯瑟琳的 2 个支援装置、浊心斯卡蒂的海嗣、伺夜的狼群、缪尔赛思的流形;同一干员的召唤物叠在一格,不占部署位),像干员一样拖到棋盘上并选方向。凯瑟琳的支援装置给**它面朝的那一格**上的干员加屏障,所以要放在干员旁边、朝向干员。**战术家的援军**(伺夜的狼群、缪尔赛思的流形,也就是选战术点)和官方一样**只能放在召唤者的攻击范围内**:拖动时只有这些格子会亮起;之后在原地给战术家换朝向时,不在新攻击范围里的援军会退回整备区(还在范围里的保持不动;整备区和临时整备区都满、援军无处可退时,这次换朝向不生效并提示「整备区已满」)。撤退或出售干员时它的召唤物一起移除;**移动干员到别的格子时(包括把召唤物拖到干员身上交换位置),它已摆放的召唤物会退回整备区**,需要重新摆放。整备区满时退回的召唤物进入临时整备区,休整期结束还没处理的会在下一回合重新回到整备区。
- 作战中:海嗣、狼群、流形、支援装置开战时就在摆放的位置部署;医疗探机、诅咒娃娃开战时也在摆放的位置免费部署一次(持续 10 / 15 秒,和官方一样不需要先开技能),之后所属干员每次发动技能时再次在原位出现;所属干员被击倒、再部署回场时,技能给的那一个如果还没出现,会立刻出现。巫恋退场时诅咒娃娃随之撤退,赫默退场时已出现的医疗探机不受影响。医疗探机、诅咒娃娃、海嗣(以及自选干员的工程蓄水炮、沙地兽、打字机、本能的召唤)这类不会受到攻击、到时间就离场的召唤物不受任何伤害(活性源石也一样)、也不会被治疗,它们的血条就是倒计时:随时间减少,空了就离场。琳琅诗怀雅二技能的香槟炸弹也不会掉血(活性源石和各种伤害都不会)、不会被治疗,不会因为生命值而消失,只在「见面礼」炸到碰到它的第一个地面敌人后消失(维护者 2026-10-08 的决定);隐匿(没有被反隐、也没有被阻挡)或沉睡的敌人选不中,不会触发它。医疗探机、诅咒娃娃、支援装置、海嗣没有摆放就不会出现;狼群、流形(战术家的援军)没有摆放时会在战术点出现。狼群最后一只狼影被击倒后进入战术点形态:暂时离开战斗(不阻挡、不攻击、不会被攻击,格子仍然占着),经过狼影的恢复时间(25 秒)后在原地满血、带着 1 只狼影回来,之后重新开始每 25 秒加 1 只狼影;伺夜退场时狼群随之撤退,伺夜再部署回场时狼群重新带着 2 只狼影出现。
- **阻挡**看接触:干员阻挡中心距离约 0.7 格以内、与它接触的敌人(地面干员挡不住飞行和近地悬浮的敌人)。敌人卡在两名干员之间时,阻挡它的干员倒下、撤退或被晕眩后,另一名仍与它接触、还有空余阻挡数的干员会**立刻接替阻挡**;都没有空位时敌人才会走掉。被冻结或晕眩的敌人同样会被接触到的干员挡住:干员再部署到它身边就会阻挡它,隐匿的敌人也随之破隐(沉睡、浮空、恐惧的敌人仍然挡不住)。迎面走来的敌人会停在干员格子前面。**阻挡了就一定能打到**:任何干员(包括站在近战位的远程干员和召唤物)总能攻击自己阻挡的敌人,不管它在不在攻击范围内、在干员哪一侧,并且优先攻击它(医疗干员除外:咒愈师以外的医疗挡住敌人时照常治疗队友,不会攻击它;「停止攻击」类技能期间也除外;技能原文另有限制的也按原文:普罗旺斯二技能开启时不以生命值高于 80% 的敌人为目标,寒檀二技能的冰凌随机落在攻击范围内的地块上)。
- **阻挡**看接触:干员阻挡中心距离约 0.7 格以内、与它接触的敌人(地面干员挡不住飞行和近地悬浮的敌人)。敌人卡在两名干员之间时,阻挡它的干员倒下、撤退或被晕眩后,另一名仍与它接触、还有空余阻挡数的干员会**立刻接替阻挡**;都没有空位时敌人才会走掉。被冻结或晕眩的敌人同样会被接触到的干员挡住:干员再部署到它身边就会阻挡它,隐匿的敌人也随之破隐(沉睡、浮空、恐惧的敌人仍然挡不住)。迎面走来的敌人会停在干员格子前面。**阻挡了就一定能打到**:任何干员(包括站在近战位的远程干员和召唤物)总能攻击自己阻挡的敌人,不管它在不在攻击范围内、在干员哪一侧,并且优先攻击它——只有速射手例外:攻击范围内有飞行敌人时先打飞行敌人(特性「优先攻击空中单位」,维护者 2026-10-08 的决定,GitHub #220)(医疗干员除外:咒愈师以外的医疗挡住敌人时照常治疗队友,不会攻击它;「停止攻击」类技能期间也除外;技能原文另有限制的也按原文:普罗旺斯二技能开启时不以生命值高于 80% 的敌人为目标,寒檀二技能的冰凌随机落在攻击范围内的地块上)。
- 开战时干员按**逐列从左到右、每列从上到下**的顺序部署(最左一列最先,与阿戈尔吞噬「更靠左和靠上」的顺序相同),召唤物在所有干员之后。被阻挡的敌人先打阻挡它的干员;没被阻挡的远程敌人先打嘲讽等级高的,同级时打最后部署的(召唤物、最右一列靠下的干员和刚再部署的干员更容易挨打)。隐匿的干员只会被它阻挡的敌人攻击,没被它挡住的敌人的溅射、爆炸、范围技能、留下的伤害区域(包括假想敌:蚀裂被击倒后喷出的毒雾)以及深池伙友卫队、扎罗的减攻速光环也落不到它身上(和官方一样;写明「无视无法选择」的效果如污染秽蚀照常能打到,寒霜的减攻速光环和“墓碑”这类地图效果照常生效);**迷彩**(如忍冬三技能期间击倒敌人后、寒芒克洛丝一技能)同样只会被它阻挡的敌人攻击,也显示为半透明,但躲不开溅射、爆炸和范围伤害(同时打范围内所有单位的普通攻击——如“斩胄之剑”“破胄之锤”“灵幛”——打不到它),也不算隐匿(不触发叙拉古等「隐匿时」效果)。叙拉古的隐匿按时(32 秒 + 每层 0.4 秒)结束;忍冬的迷彩按官方持续到她下一次开技能。**蒂比起飞**期间只挡飞行敌人,地面敌人不会选她为目标(普通攻击、溅射、技能范围、燃烧区域和要选中目标的减益都落不到她身上);飞行和悬浮敌人、写明「无视无法选择」的效果(如污染秽蚀)照常能打到她;地面敌人的光环和全场效果(如“墓碑”让全场攻击力、防御力减半)、她攻击敌人时招来的反制(如重装侦察兵的暴露)照常生效,起飞前已挂上的持续伤害也照常跳。
- **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**开启后攻击范围比平时大**的手动技能(如莫斯提马「序时之匙」、银灰「真银斩」、史尔特尔「黄昏」、白面鸮「脑啡肽」)在**开启后的攻击范围内出现敌人**(治疗技能:有受伤的队友)时就释放,不必等敌人走进平时的攻击范围,这是本作按玩家反馈有意与官方不同的地方;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放(深巡二技能按开启后更远的攻击范围判断),余的二技能「厚礼上宾」改为周围两格(技能范围)内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放(开启后攻击范围扩大的技能——薄绿「风语」、蜜蜡「沙暴扩散」、卡涅利安「食噬之印」、玛恩纳「未宽解的悲哀」,以及安洁莉娜「秘杖·反重力模式」——同样按开启后的攻击范围判断)。**自动触发**的技能按其本身规则:只作用于队友的(如引星棘刺「度算浪波」、浊心斯卡蒂「同葬无光之愿」、纯烬艾雅法拉「无声润物」)技力满就释放,不用等敌人或伤员;伺夜「领袖的呼唤」「领袖的馈赠」只在场上有狼群时释放:狼群处于战术点形态时,「领袖的呼唤」也会释放并让它立刻带着 1 只狼影回到场上(狼影未满时改为加 1 只狼影,已满时把狼群的生命值回满),「领袖的馈赠」也会释放(立即获得费用,强化留给回来的狼群的下一次攻击);又如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。白铁的「铁钳号·原型机」能被我方干员攻击(不受伤害):它在攻击范围 / 技能范围内时,能作用到它的技能会像遇到敌人一样自动开启并消耗弹药(项目维护者 2026-10-08 按官方体验确认;白铁™多功能平台不会被攻击);技能效果只会挑选敌人、打不到它的技能(如德克萨斯「剑雨」)不会为它开启,留给真正的敌人。
- **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**开启后攻击范围比平时大**的手动技能(如莫斯提马「序时之匙」、银灰「真银斩」、史尔特尔「黄昏」、白面鸮「脑啡肽」)在**开启后的攻击范围内出现敌人**(治疗技能:有受伤的队友)时就释放,不必等敌人走进平时的攻击范围,这是本作按玩家反馈有意与官方不同的地方;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放(深巡二技能按开启后更远的攻击范围判断),余的二技能「厚礼上宾」改为周围两格(技能范围)内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放(开启后攻击范围扩大的技能——薄绿「风语」、蜜蜡「沙暴扩散」、卡涅利安「食噬之印」、玛恩纳「未宽解的悲哀」,以及安洁莉娜「秘杖·反重力模式」——同样按开启后的攻击范围判断)。**自动触发**的技能按其本身规则:只作用于队友的(如引星棘刺「度算浪波」、浊心斯卡蒂「同葬无光之愿」、纯烬艾雅法拉「无声润物」)技力满就释放,不用等敌人或伤员;立即生效、本身不是一次攻击的技能(如度算浪波、先锋的回费技能)两次释放之间至少隔一个攻击间隔(释放本身不占用普通攻击),攻速越快放得越勤,迅捷返还的技力再多也不会每一帧都放(GitHub #298);伺夜「领袖的呼唤」「领袖的馈赠」只在场上有狼群时释放:狼群处于战术点形态时,「领袖的呼唤」也会释放并让它立刻带着 1 只狼影回到场上(狼影未满时改为加 1 只狼影,已满时把狼群的生命值回满),「领袖的馈赠」也会释放(立即获得费用,强化留给回来的狼群的下一次攻击);又如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。白铁的「铁钳号·原型机」能被我方干员攻击(不受伤害):它在攻击范围 / 技能范围内时,能作用到它的技能会像遇到敌人一样自动开启并消耗弹药(项目维护者 2026-10-08 按官方体验确认;白铁™多功能平台不会被攻击);技能效果只会挑选敌人、打不到它的技能(如德克萨斯「剑雨」)不会为它开启,留给真正的敌人。
- **推开 / 拉拽**按官方「力度 − 重量」决定距离:例如野鬃二技能(中等力度)把重量 0 / 1 / 2 / 3 的敌人推开约 2.1 / 1.7 / 0.4 / 0.1 格,重量 4 以上推不动——轻型敌人反而推得更远。野鬃二技能沿她的朝向推开:正前方 45° 以内按上面的距离,身后、侧面或贴身的敌人只被轻推一下(约 0.4 格以内);琳琅诗怀雅三技能的金币则从她身边向外推开,落在她前方 2–4 格的范围(官方「范围2-4」)内的地面敌人和她阻挡的敌人身上。攻击速度最低为 20(沼泽、寒霜等减速叠满时也不会更慢)。
- **生命回复不是治疗**(与官方一致):吟游者的特性(魔王、浊心斯卡蒂及其海嗣)、调香师的天赋、瑕光二技能、铃兰三技能、锡人和引星棘刺(一、二技能)的炼金单元给友军的「每秒恢复生命」是提高对方的**生命回复速度**,不是治疗:收割者 / 不屈者 / 武者(如隐德来希、圣约送葬人、折桠)这类「无法被友方治疗」的干员和处于禁疗的干员也能吃到,治疗量加成(如「受到的治疗效果提升」)也不会放大它;铃兰三技能开启后的第一秒没有回复。直接治疗(医疗干员的治疗、技能的「恢复…生命」)仍然对这些干员无效。
- **元素损伤条**在单位自己的血条正下方:左边是元素图标,白条是剩余的元素值,只有真正爆发时才会耗尽;爆发后条变成元素的颜色、图标闪烁,在冷却期间慢慢回满(这段时间不会再积累元素损伤)。
@@ -115,7 +115,7 @@
- **本局禁用**:「标准模拟」与官方相同,关闭拉特兰、阿戈尔、卡西米尔、灵巧、奥术、奇迹、投资人、突袭、独行、绝技这 10 个盟约,它们不会激活。你有这些盟约的成员时(奇迹、投资人也算整备区里的成员),盟约栏末尾显示**灰色带 ✕ 的盟约图标和「本局禁用」**,干员卡、盟约标签和盟约详情也这样标注。「远见」「奇迹」「投资人」计入整备区,盟约详情的「成员」人数也一样计入整备区里的成员(临时整备区不算)。
- **调和**:激活时,场上已有成员的核心盟约人数各 +1(只加 1 人,不论有几名调和干员)。盟约详情会写「含调和 +1」,成员列表上方列出带来这 1 人的调和干员(如缪尔赛思)。
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。它是飞行单位:标注「不可对空」的敌人效果(高能源石虫 / 冰爆源石虫 / 卷心籽的死亡爆炸、水遁忍者的漩涡、鼎沸、“萨科塔昂首”的祈祷邀约、“斩胄之剑” / “破胄之锤”的攻击等)不会波及它。
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。「调和」「协防干员」「独行」「绝技」与官方一样**不显示层数**(盟约栏、详情和结算卡片都不显示),但叠层的特质 / 策略 / 装备照样给它们加层(PRTS 盟约记录:「部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」),这些层数也计入激活层数总和;它们的效果本身与层数无关。
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。「调和」「协防干员」「独行」「绝技」与官方一样**不显示层数**(盟约栏、详情和结算卡片都不显示),但叠层的特质 / 策略 / 装备照样给它们加层(PRTS 盟约记录:「部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」),这些层数也计入激活层数总和——只有策略「余」的【文火慢炖】不给这四个盟约加层(第 8 回合只激活了其中一个时什么也不加;维护者 2026-10-08 的决定);它们的效果本身与层数无关。
- **特质在哪里生效**:「进入休整期时」「休整期结束时」「刷新时」的特质(详情卡的第一项)只在干员部署在作战区时生效;文字里写着「此干员在整备区时也有效」的拉普兰德、调香师、波登可,以及激活【炎】/【投资人】时的琳琅诗怀雅,在整备区里也生效(临时整备区不算),与官方数据一致;「获得时」「售出时」的特质在获得、售出的那一刻生效,不论干员在哪里。「远见」「奇迹」「投资人」的「整备区的…干员也可用于激活盟约」只是把整备区的成员算进人数,不会让赫默等干员的特质在整备区生效。写着「使已激活的…」的特质要求盟约当时已经激活:例如「叙拉古」只数作战区里不同的叙拉古干员,整备区里的拉普兰德不算在这 3 名里;拉普兰德每回合只在她经历的第一次主动刷新时叠层(免费刷新也算),那次刷新时叙拉古没有激活,这一回合就不会再叠。
- **加成怎么叠**:盟约、策略、装备、机变卡和特质给的「攻击力 / 防御力 / 生命值 +X%」与技能的「攻击力 +X%」**相加**后再乘到属性上(官方:直接乘算),例如精准 +130%、独行 +60%、一件 +40% 的装备合计 +230%,而不是 ×2.3×1.6×1.4。「伤害提升至 X%」「受到的伤害 +X%」(维多利亚、谢拉格、奥术、脆弱等)这类伤害倍率之间仍然相乘;但同名效果只算一个——同一战场两名玩家都开了「奥术」时,敌人身上只挂一层奥术易伤,取两人中较高的那一个(不再相乘);两名「灵知」的坚冰、两名「莱恩哈特」/「缄默德克萨斯」的法抗削减也一样只算最强的一个。「坚守」的反伤计入被打的那名成员(个人对领袖伤害统计里能看到)。
- 每局会随机**禁用**若干盟约(标准模拟较少),它们的干员不会出现在商店;开局的「确认本局信息」会列出,选择策略时(「查看禁用盟约与干员」)和对局中(左上角 🔍)也能再查看。
+12 -3
View File
@@ -277,7 +277,9 @@ included), summons (流形's melee copy) and a ranged operator standing on a mel
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`, used by `ai.js acquireTargets` and the skills' DEFAULT trigger, and
`sortEnemyTargets`; "自身这格内" rules name the blocked enemies separately — 瑕光 S2, PRTS 备注). A kit that picks its own
`sortEnemyTargets`; one exception, the owner's decision of 2026-10-08 (GitHub #220): the 速射手 air priority —
priority 'fly', trait 优先攻击空中单位 — comes before its own blocked enemy, so a 速狙 on a melee tile shoots a flyer in its
range first, PRTS 索敌的概念 「阻挡(近战限定)→特殊优先级→…」; every other priority keeps the blocked enemy first; "自身这格内" rules name the blocked enemies separately — 瑕光 S2, PRTS 备注). A kit that picks its own
targets from the range (`beforeAttack`) adds `battle.blockedTargets` to its candidates (深靛, 迷迭香 S3, 荒芜拉普兰德 S2,
佩佩 S2, 灵知 S3, 远牙 S3); only skill texts that exclude targets keep their rule (普罗旺斯 S2: none above 80 % HP; 寒檀
S2: icicles on random tiles of her range). This replaces the
@@ -635,7 +637,9 @@ and the 浮空 weight rule, then `statusApplied { source, target, status, durati
= the target carried no buff of that status before (a refresh / a weaker "取最高" application is not an entry: "进入…时"). Effects follow the official term table
(`gamedata_const.termDescriptionDict`, `ba.*`). Same-key statuses refresh to the longer duration, except the
"同名效果取最高" ones marked *strongest* below: the strongest value wins, a weaker application never overrides it and,
if it outlasts it, resumes when the strong one expires (pass `refresh` to opt out); `stackAs` = the strength such an
if it outlasts it, resumes when the strong one expires (pass `refresh` to opt out) — every such waiting application keeps
its own end, and they resume strongest first (`buff.data.tail` is a chain ordered by strength: of 30 % / 25 % / 20 %
applied one after another, the 25 % follows the 30 %, then the 20 % — GitHub #342, PR #348); `stackAs` = the strength such an
application competes with instead of its `value`, which stays its effect (Raidian S3's 虚弱: PRTS 备注 "在叠加时视为90%…的
虚弱(仅影响叠加优先级,不影响实际效果)").
`battle.applyStrongest(target, key, { duration, value, mods: (v) => mods, source })` gives a content effect that is not a
@@ -963,7 +967,12 @@ instance) and skip `'counter'` / `'reflect'` damage. When the guard trips, the l
(`Battle._deploy` initial; `battle.flags.startOpCooldown`, default 3 — the test harness sets 0 unless told otherwise);
AUTO skills are exempt; kits with their own automatic cast of a MANUAL skill check `skill.opCooling` (波登可 S1, 雪猎
special bullets, 流形 copy). While a cast "next attack" (instant / charges with an attack override) waits for its
attack, no further charge is cast. `gainSp` is ignored while a duration/ammo/toggle skill
attack, no further charge is cast. An instant / charges skill with no attack override of its own (a throw, a heal, a
buff, a DP gain …) is cast from the tick (the rules above, the DEFAULT of units that never attack, content trigger
ranges) at most once per attack interval of its unit — `skill.nextCastAt`, set by such a cast, reset on deployment —:
attack speed paces it, and an SP refund that refills the bar at once (迅捷) no longer recasts it every tick (GitHub #298,
引星棘刺 S1; [ASSUMED] one attack interval: PRTS prints no 前后摇 for such casts). The cast is no attack: the unit's
attacks and heals keep their own rhythm, and its first cast comes as soon as it is ready (#124). `gainSp` is ignored while a duration/ammo/toggle skill
runs (its bar shows the skill), whatever the reason, and — any reason but `'init'` — while the unit has the `noSp`
flag (阻回: "停止并阻止任意形式的技力回复"; the operators' 凋亡 burst, §3).
- Kinds: `duration` (mods for `duration` s), `ammo` (mods until `ammo` attacks were made, optional duration cap),
+1 -1
View File
@@ -119,7 +119,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, and 乌尔比安 knocked out while his S3 has moved him always on his deployment tile when it is free (the owner's deviation of 2026-10-07, `Unit.downAtHome`, §25.17.3) — 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 × the blocker's 阻挡半径倍率 for blockFly units vs flyers (起飞; mod `blockRadiusScale` = 倍率 − 1 — PRTS 游戏数据基础 "飞行阻挡半径 = 0.8944 × 阻挡半径倍率", 凯尔希·思衡托 +0.23), 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 stunned or frozen enemy is checked too — 晕眩 / 冻结 hold no 不可阻挡 (PRTS 异常效果: the STUN / FROZEN state machines stop its moves and attacks) —, so an operator redeployed beside a held enemy blocks it where it stands and lifts its 隐匿; one held short of contact is blocked once it walks in after the status (GitHub #232; until 0.2.0 the check waited for the status to end). 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 × the blocker's 阻挡半径倍率 for blockFly units vs flyers (起飞; mod `blockRadiusScale` = 倍率 − 1 — PRTS 游戏数据基础 "飞行阻挡半径 = 0.8944 × 阻挡半径倍率", 凯尔希·思衡托 +0.23), 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 stunned or frozen enemy is checked too — 晕眩 / 冻结 hold no 不可阻挡 (PRTS 异常效果: the STUN / FROZEN state machines stop its moves and attacks) —, so an operator redeployed beside a held enemy blocks it where it stands and lifts its 隐匿; one held short of contact is blocked once it walks in after the status (GitHub #232; until 0.2.0 the check waited for the status to end). 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 — a 速射手's air priority (优先攻击空中单位) excepted, which comes before the enemy it blocks (the owner's decision of 2026-10-08, GitHub #220) — ("可以选择且优先选择阻挡单位", 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; 「同时攻击阻挡的所有敌人」 — 强攻手 / 重剑手 / 推击手 and the skills worded so, `hitAllBlocked` — up to the block count, never fewer than 1, since 0.2.0: PRTS 分支特性信息 "普通攻击最大目标数等于阻挡数(不会低于1)"), 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 not even by that one when its 索敌不受阻挡影响 (自制投石机, `profile.blockFree`: it selects as if unblocked, §25.18) —, 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); any enemy whose block ends after its strike (its blocker stunned by it, knocked out, retreated) stands for the rest of that clip too — PRTS 状态机: the enemy's COMBAT state ends with its attack — and a displacement ends a stand (0.2.0). Every enemy attack strikes at its clip's damage frame after its swing starts — the first one of a contact too —, and a stun / freeze / sleep / 浮空, hiding, 缴械, 恐惧 or 战栗 (blocked) before that frame cuts the swing: no strike, the next swing starts from its wind-up (0.2.0, `ai.js attackWindup`; PRTS 状态机 / 异常效果 STUNNED; no clip known: no wind-up). 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).
+35
View File
@@ -235,6 +235,41 @@ export class SpineActor {
else if (this.mode === 'stun' && this.has(this.roles.stun?.loop)) this._play(this.roles.stun.loop, true);
}
/**
* Re-pose on the clip set in force right after an immediate form switch (render/units.js STEALTH_FORMS: 假想敌:骨刺's
* stealth bit; PR #365): a running attack goes on in the new set's attack clip at the same point relative to its
* strike frame (a pending wind-up still strikes at windUntil), a stun / the resting state restarts on the new set's
* clip without a crossfade, and the skeleton is posed at once — also while a freeze holds it (`frozen`).
*/
syncFormPose() {
if (this.dead) return;
if (this.mode === 'attack') {
const track = this.spine.state.tracks[0];
const clip = this._attackClip();
if (!track || !clip) return;
const dur = this.dur(clip.loop);
const oldHit = this._hitTime(this.current, this.dur(this.current));
const hit = this._hitTime(clip.loop, dur);
const start = clampN(track.trackTime + hit - oldHit, 0, dur);
const lead = this.wound && this.windUntil != null
? Math.max(0, this.windUntil - this.clock) : 0;
const ts = lead > 0 ? Math.max(0, hit - start) / lead : track.timeScale;
const tailTs = lead > 0 ? this.windTs : ts;
this._play(clip.loop, track.loop, { start, timeScale: ts, mix: 0 });
this.attackUntil = this.clock + lead +
Math.max(0, dur - (lead > 0 ? hit : start)) / tailTs;
} else if (this.mode === 'stun') {
const name = this.has(this.roles.stun?.loop) ? this.roles.stun.loop : this._baseName();
this._play(name, true, { mix: 0 });
} else if (this.mode === 'base') {
this._play(this._baseName(), true, { mix: 0 });
} else {
return;
}
// Apply the new attachments even when normal updates are frozen.
this.spine.update(0);
}
/** Play a form's transition clip once; attacks and the resting state wait for it (mode 'change'). */
_change(clip) {
if (this.mode !== 'change') this.stunWanted = this.mode === 'stun';
+25
View File
@@ -247,6 +247,9 @@ export const WOLF_PIPS = Object.freeze({ size: 0.1, min: 5, max: 9, gap: 2, pad:
* → UnitView.setForm), per Spine id; the mode's roles override the manifest's (data/assets.json anims), `change` plays
* once first:
* - 掠海漂移体 (PRTS: 受晕眩/沉睡/冻结影响后进入爬行模式 — for good) crawls on its *_02 clips after 'Change';
* - 假想敌:骨刺 (GitHub #296, PR #365): not an fx — its form follows the snapshot's stealth bit (STEALTH_FORMS): the
* manifest's *_A (the three-headed snake) while its 隐匿 is on, 'revealed' *_B the moment it is blocked or revealed, *_A
* again when the 隐匿 is back; no change clip, a running attack / stun pose carried over (SpineActor.syncFormPose);
* - 暴鸰 flies on its bomb-less *_2 clips once its one bomb left (the official prefab's mode S1: Move→Move_2, Idle→Idle_2,
* Die→Die_2; user feedback after 0.1.0, D4: the bomb used to stay under the drone — no change clip, the drop is its
* Attack clip);
@@ -281,6 +284,12 @@ export const WOLF_PIPS = Object.freeze({ size: 0.1, min: 5, max: 9, gap: 2, pad:
* built for the knock-out — and one rebuilt while it is down — the 替身's death clip; it stands up as the 本体.
* A kind without a clip set of this skeleton (barriers, charges, …) changes nothing. 吉兆飞鳞's 晕眩模式 is its Stun clip.
*/
/**
* Enemy forms that follow the snapshot's stealth bit (UF.STEALTH: the sim sends it only while the enemy's 隐匿 is on —
* not blocked, not revealed) instead of a sim fx, per Spine id: the FORMS kind drawn while the bit is OFF; the manifest's
* clips while it is on. Switched at once (UnitView.sync, and for a model loaded later the load callback).
*/
export const STEALTH_FORMS = Object.freeze({ enemy_9008_acbunn: 'revealed' });
const loop = (name, via = null) => Object.freeze(via ? { begin: null, loop: name, end: null, via } : { begin: null, loop: name, end: null });
const clipSet = (idle, move, die, attack = null) => Object.freeze({
idle, deploy: idle, die, move: loop(move),
@@ -331,6 +340,9 @@ export const FORMS = Object.freeze({
enemy_2025_syufo: Object.freeze({
crawl: Object.freeze({ change: 'Change', roles: clipSet('Idle_02', 'Move_02', 'Die_02', 'Attack_02') }),
}),
enemy_9008_acbunn: Object.freeze({
revealed: Object.freeze({ change: null, roles: clipSet('Idle_B', 'Move_B', 'Die_B', 'Attack_B') }),
}),
enemy_10081_mpplai: Object.freeze({
translator_fuchou: Object.freeze({ change: 'A_Die_B', roles: clipSet('B_Idle', 'B_Move', 'B_Die', 'B_Attack') }),
translator_youling: Object.freeze({ change: 'A_Die_C', roles: clipSet('C_Idle', 'C_Move', 'C_Die') }),
@@ -663,6 +675,8 @@ export class UnitView {
if (this.flags & UF.SKILL) this.actor.setSkill(true);
this.actor.setBase(this._baseFromAnim());
if (deployed != null) { this.actor.deploy(); if (deployed > 0) this.actor.update(deployed); }
// a stealth-driven form (STEALTH_FORMS): the pose of the clip set in force now, even under a frozen stun
if (STEALTH_FORMS[id]) this.actor.syncFormPose();
}
}, () => {
if (req === this._spineReq) { this._spineBusy = false; this._stuckAt = 0; }
@@ -814,6 +828,17 @@ export class UnitView {
const prevFlags = this.flags;
this.flags = s.flags | 0;
this.anim = s.anim | 0;
// a stealth-driven form (STEALTH_FORMS: 假想敌:骨刺) follows this snapshot's stealth bit at once — before a DIE in the
// same snapshot, so it dies in the form it had
const stealthForm = this.isEnemy && this.alive ? STEALTH_FORMS[this.info.spine || this.info.defId] : null;
if (stealthForm) {
const form = this.flags & UF.STEALTH ? null : stealthForm;
if (form !== this.form) {
this.setForm(form);
this.actor?.syncFormPose();
if (this.imp) this.imp.dirty = true;
}
}
if (this.isEnemy && Math.abs(s.vx) > 0.08) this.visFacing = s.vx < 0 ? -1 : 1;
if ((prevFlags ^ this.flags) & UF.SKILL) this.setSkill(!!(this.flags & UF.SKILL));
if (this.anim === ANIM.DIE && this.alive) this.die();
+8
View File
@@ -10,6 +10,7 @@ import { SkillRuntime } from '../skills.js';
import { resolveProfile } from '../professions.js';
import { normalizeToken } from '../simdata.js';
import { setupUnitKit } from '../content/index.js';
import { installTraitAttackSpeed } from '../content/traitMods.js';
import { clone } from './util.js';
export class BattlePlayers {
@@ -220,6 +221,13 @@ export class BattlePlayers {
if (t && typeof t.install === 'function') this._safe(() => t.install(this, u), 'talent.install', u);
}
if (typeof u.kit.install === 'function') this._safe(() => u.kit.install(this, u), 'kit.install', u);
// trait lines the ENGINE owns for every operator (content/traitMods.js: the module attack-speed riders whose
// condition is a pure function of the field — 「攻击范围内存在N名及以上敌人时攻击速度+X」). Runs after kit.install:
// a kit that implements its own line for this trait is never double-counted — traitMods.js refuses every condition
// shape a kit already owns (`reason: 'kit'`), and the kits whose line it DOES take over (the two REA-Y ones) had
// their hand-written copy deleted in the same change. Calling it before kit.install would let a kit's own buff and
// this rule both land on the same unit.
this._safe(() => installTraitAttackSpeed(this, u), 'traitMods.attackSpeed', u);
return u.kit;
}
}
+25 -9
View File
@@ -274,23 +274,39 @@ export class BattleStatus {
}
/**
* "同名效果取最高": keep the strongest value; a weaker one that outlasts it resumes afterwards (buff.data.tail). A
* "同名效果取最高": keep the strongest value, then resume weaker effects in order of strength at their original expiry times.
* `buff.data.tail` links the waiting effects, with decreasing strength and increasing expiry times. A
* `plain` template (applyStrongest) is an ordinary invisible buff, not a status. `stackAs` (finite): the strength this
* application competes with instead of its value (applyStatus opts.stackAs); kept in `data.stackAs` / the tail.
*/
_applyValuedStatus(target, key, tpl, duration, value, source, stackAs = null) {
const as = Number.isFinite(stackAs) ? stackAs : null;
const strength = (v, s = null) => Math.abs(Number.isFinite(s) ? s : Number.isFinite(v) ? v : tpl.valued);
// (no stackAs: the very objects of before — data { value, tail }, tails { value, until })
const entry = (v, s, extra) => (Number.isFinite(s) ? { value: v, stackAs: s, ...extra } : { value: v, ...extra });
const after = (tail, until) => {
while (tail && tail.until <= until) tail = tail.tail;
return tail || null;
};
const insertTail = (tail, incoming) => {
if (!tail) return incoming;
const diff = strength(incoming.value, incoming.stackAs) - strength(tail.value, tail.stackAs);
if (diff > 1e-12) return { ...incoming, tail: after(tail, incoming.until) };
if (diff < -1e-12) {
if (incoming.until <= tail.until) return tail;
return { ...tail, tail: insertTail(tail.tail, incoming) };
}
// Equal-priority waiting effects keep the longest-lived application's value, including its stackAs.
const kept = incoming.until > tail.until ? incoming : tail;
return { ...kept, tail: after(tail.tail, kept.until) };
};
const make = (v, dur, tail, s = null) => ({
...(tpl.buff || null), // extra buff fields of the status (抵抗: the 麻痹 decay tick)
key, duration: dur, refresh: 'replace', mods: tpl.mods(v), flags: tpl.flags || null, status: tpl.plain ? null : key,
visible: !tpl.plain, source,
data: entry(v, s, { tail }),
onExpire: ({ battle, unit, buff }) => {
const t = buff.data.tail;
if (t && t.until - battle.time > 1e-6 && unit.alive) battle.addBuff(unit, make(t.value, t.until - battle.time, null, t.stackAs));
const t = after(buff.data.tail, battle.time + 1e-6);
if (t && unit.alive) battle.addBuff(unit, make(t.value, t.until - battle.time, t.tail || null, t.stackAs));
},
});
const old = target.buffs.find((b) => b.key === key && (tpl.plain || b.status === key));
@@ -299,17 +315,17 @@ export class BattleStatus {
const oldAs = old.data && Number.isFinite(old.data.stackAs) ? old.data.stackAs : null;
const oldEnd = this.time + old.timeLeft, newEnd = this.time + duration;
const oldTail = old.data && old.data.tail;
const longerTail = (a, b) => (!a ? b : !b ? a : (b.until > a.until ? b : a));
if (strength(value, as) > strength(oldV, oldAs) + 1e-12) {
// stronger: takes over now; the weaker old one (or its tail) resumes if it lasts longer
const tail = longerTail(oldEnd > newEnd ? entry(oldV, oldAs, { until: oldEnd }) : null, oldTail && oldTail.until > newEnd ? oldTail : null);
// Stronger: take over now, retaining every old effect that can still become active afterwards.
const tail = after(entry(oldV, oldAs, { until: oldEnd, tail: oldTail }), newEnd);
this.addBuff(target, make(value, duration, tail, as));
} else if (strength(value, as) < strength(oldV, oldAs) - 1e-12) {
// weaker: never overrides; remembered as the tail when it outlasts the running one
if (newEnd > oldEnd && (!oldTail || newEnd > oldTail.until)) old.data = { ...old.data, value: oldV, tail: entry(value, as, { until: newEnd }) };
// Weaker: insert among the waiting effects instead of replacing the one with the latest expiry.
if (newEnd > oldEnd) old.data = { ...old.data, value: oldV, tail: insertTail(oldTail, entry(value, as, { until: newEnd })) };
} else if (duration > old.timeLeft) {
old.timeLeft = duration;
old.duration = Math.max(old.duration, duration);
old.data.tail = after(oldTail, newEnd);
}
}
+4 -3
View File
@@ -16,8 +16,9 @@
// act1autochess_band13_buff 克莱门莎 崇高牺牲 a <bond_id> operator knocked down ⇒ +its tier (等阶) <bond_id> layers
// (bond_add_type by_charlevel; no "已激活" in the text ⇒ requireActive false)
// act1autochess_band16_buff 大帝 加急调派 "每次部署后再部署时间减少50%": every deployment of an operator stacks one
// redeploy ×(1 + respawn_time) for the rest of the battle [ASSUMED cumulative:
// "每次" — the first redeploy is −50 % under either reading]
// redeploy ×(1 + respawn_time) for the rest of the battle, with no cap (PRTS
// 卫戍协议:盟约 下半 / PRTS盟约记录 备注 "※该策略效果可无限叠加"; a 20-stack cap
// until 0.2.2 — GitHub #328, PR #329)
// act1autochess_band17_buff 桑葚 药枚实验 at combat start the player's units on the front-most (最右边) column: each
// attack has `prob` to gain 1 shield layer (max 1)
// act1autochess_band18_buff 休谟斯 回收利用 a ground (地面) operator's skill ends ⇒ a random operator on its 4
@@ -106,7 +107,7 @@ const BY_KEY = {
battle.on('deploy', (c) => {
const u = c.unit;
if (!isOp(u) || u.ownerId !== ps.playerId) return;
battle.addBuff(u, { key, mods: { redeployMul: mul }, refresh: 'stack', stacks: 1, maxStacks: 20, persist: true, allowDead: true });
battle.addBuff(u, { key, mods: { redeployMul: mul }, refresh: 'stack', stacks: 1, maxStacks: Infinity, persist: true, allowDead: true });
});
},
+12 -4
View File
@@ -19,6 +19,7 @@
// shop: onBuy — a free pick / grant never uses it up)
// band_cost_coin_reach_cnt_gain_chess_from_pool 潘格尼尼 once coin_cnt funds spent in total: 1 chess of pool (elite 拉特兰 ≥ T4)
// round_start_bond_check_gain_layer 余 round start of `round`: exactly factioncount active bonds → +count1, else each active +count2
// — never to a bond without layers (noStack; the owner's decision of 2026-10-08)
// up_shop_add_special_goods {count,choice,pool} 凯瑟琳 every level-up: pick 1 of `count` items of pool (free)
// coin_carry_over {capital,interest,max} 坎诺特 leftover ≥ capital at round start → +min(max, ⌊left/capital⌋×interest) income
// round_start_all_player_change_enemy_2 鸭爵 global: every alive player's battle from `round` (own + teammates;
@@ -244,15 +245,22 @@ K.band_cost_coin_reach_cnt_gain_chess_from_pool = (ps) => ({
},
});
/**
* 余 【文火慢炖】 「第8回合开始时,若仅激活了1个盟约,使其增加36层;否则使所有已激活盟约增加12层」. The owner's decision of 2026-10-08
* overrides PRTS here (下半/PRTS盟约记录: layers of a bond that shows none 「仍然对其生效」): a bond without layers — bonds.json
* `noStack`: 绝技, 独行, 调和, 协防干员 — gets none. The count stays the sentence's (every active bond), so one active bond
* that is such a bond means nothing is added, and with two active bonds a layered one still gets count2. Only this
* strategy: 协防's 「使所有已激活的盟约层数」 (bonds/addon/meta.js) and 华法琳's 【重点监护】 keep adding to every bond they name.
*/
K.round_start_bond_check_gain_layer = (ps) => ({
onRoundStart(ctx) {
const p = ps[0];
if (ctx.round !== int(p.round, -1)) return;
const active = Object.entries(ctx.bonds()).filter(([, b]) => b && b.active).map(([id]) => id);
if (active.length === int(p.factioncount, 1)) {
for (const b of active) ctx.addLayers(b, int(p.count1, 0), { requireActive: true, reason: 'band' });
} else {
for (const b of active) ctx.addLayers(b, int(p.count2, 0), { requireActive: true, reason: 'band' });
const n = active.length === int(p.factioncount, 1) ? int(p.count1, 0) : int(p.count2, 0);
for (const b of active) {
if (ctx.gd.bond(b)?.noStack) continue;
ctx.addLayers(b, n, { requireActive: true, reason: 'band' });
}
},
});
+15 -8
View File
@@ -1,7 +1,7 @@
// server/sim/content/enemies/fly.js — FLY 飞行 kits (auras, 暴鸰, the “萨科塔”, 假想敌:黑云, shells, seeds) and their part of KITS
// (split from content/enemies.js).
import { TICK, MOVE_SCALE, PROJECTILE_SPEEDS } from '../../constants.js';
import { TICK, MOVE_SCALE, PROJECTILE_SPEEDS, ALLY_COLLIDER_RADIUS } from '../../constants.js';
import {
num, T, hurt, targetsNear, allTargets, areaAllies, areaAlliesInTiles, fieldAllies, byPriority, spawnChildren,
stepToward, setForm, expose,
@@ -27,9 +27,14 @@ export const BOMBD_RELEASE = 8 / 30;
* 提升至200%"; the cast's end clip is already the bomb-less Idle_2 (`_endAnimKey`). */
export const BOMBD_POST_DELAY = 0.667;
/** 帝国炮火先兆者 shell (PRTS "普通攻击向目标所在位置发射一枚于3秒后命中的弹道,弹道对半径1.2范围内的所有我方单位造成攻击力
* 100%的无来源物理伤害 … ※弹道始终使用缓存攻击力"): flight time and blast radius. */
const SHELL_FLIGHT = 3, SHELL_RADIUS = 1.2;
/** 帝国炮火先兆者 shell (PRTS 天赋 of both 先兆者 "普通攻击向目标所在位置发射一枚于3秒后命中的弹道,弹道对半径1.2范围内的所有我方单位造成
* 攻击力100%的无来源物理伤害(此弹道不会强制击中主目标,碰撞无视迷彩)… ※弹道始终使用缓存攻击力"): flight time and the written
* blast radius. The landing is a 碰撞 — PRTS 作战机制 §碰撞体积: collider tests are the common way, an ally's collider a
* circle of ALLY_COLLIDER_RADIUS — so an ally is hit when its collider touches the 1.2 circle: centre distance ≤ 1.45
* (SHELL_REACH), the impact tile and the 8 around it (a diagonal tile centre is √2 ≈ 1.414 away; two tiles, 2, stay
* out) — the 3×3 a player saw in the official game (GitHub #364; until 0.2.2 the bare 1.2: the cross only).
* [ASSUMED] for this shell (its text names the 碰撞); other content zones keep their point radius (constants.js). */
const SHELL_FLIGHT = 3, SHELL_RADIUS = 1.2, SHELL_REACH = SHELL_RADIUS + ALLY_COLLIDER_RADIUS;
/** 假想敌:黑云 抓取: the blackboard radius counts ×2.5 (PRTS "2.5倍可变半径": range_radius 1.5 → 3.75), at most 3 prey,
* "短暂延迟后" the 延迟吞噬 lands (delay [ASSUMED] 0.5 s); its SP (= 全弹发射 hits) caps at the data's spData.maxSp. */
@@ -207,7 +212,8 @@ function kitBombd(ab) {
/**
* 帝国炮火先兆者 / 中枢先兆者 (PRTS): every normal attack fires a shell at the target's position that lands SHELL_FLIGHT s
* later and deals 100 % of the ATK at launch as physical damage without a source to every ally within SHELL_RADIUS
* later and deals 100 % of the ATK at launch as physical damage without a source to every ally whose collider touches the
* SHELL_RADIUS circle (SHELL_REACH)
* (it may miss the target that moved away; "碰撞无视迷彩"). The landing is the shell's area selection (areaAllies of the
* enemy that fired it: no unblocking 隐匿 ally; the damage stays 无来源). The attack itself is the engine's (cooldown,
* pause, 'atk' event of kind 'mortar'); the damage is the shell's (ai.js `profile.deferHit`).
@@ -219,10 +225,11 @@ function kitShell() {
const atk = e.s.atk * (e.profile.atkScale ?? 1);
for (const t of c.targets) {
const x = t.x, y = t.y;
b.fx('bombardShell', { x, y, id: e.id, r: SHELL_RADIUS, t: SHELL_FLIGHT });
// (the telegraph and the blast are drawn at the reach: every ally whose centre lies in the circle is hit)
b.fx('bombardShell', { x, y, id: e.id, r: SHELL_REACH, t: SHELL_FLIGHT });
b.after(SHELL_FLIGHT, () => {
b.fx('bombard', { x, y, r: SHELL_RADIUS, kind: 'emppnt' });
for (const u of areaAllies(b, e, x, y, SHELL_RADIUS)) hurt(b, null, u, atk, 'phys', { isSkill: false, tags: ['shell'] });
b.fx('bombard', { x, y, r: SHELL_REACH, kind: 'emppnt' });
for (const u of areaAllies(b, e, x, y, SHELL_REACH)) hurt(b, null, u, atk, 'phys', { isSkill: false, tags: ['shell'] });
});
}
},
+18 -3
View File
@@ -2,6 +2,7 @@
// content/enemies.js).
import { canTargetAlly, enemyStealthed } from '../../targeting.js';
import { ALLY_COLLIDER_RADIUS } from '../../constants.js';
import { T, elem, hurt, targetsNear, byPriority, auraBuff } from './helpers.js';
import { stealth, onHitStatus, skill, kitStealth } from './archetypes.js';
import { hypot } from '../../detmath.js';
@@ -9,7 +10,8 @@ import { hypot } from '../../detmath.js';
// ---------------------------------------------------------------------------------------------------------------
// constants (numbers that exist nowhere in the data)
/** "数个目标" of 假想敌:骨刺 while stealthed [ASSUMED]. */
/** 假想敌:骨刺's targets at once while its 隐匿 holds: 3 — the level's own description 「隐匿;隐匿状态下同时攻击3个目标。」
* (enemy_database; the handbook line says 「数个目标」) and PRTS 天赋 「隐匿期间可同时攻击3个目标」 (GitHub #365). */
const ACBUNN_TARGETS = 3;
/** 重弩突袭者 直击 reach along a row/column [ASSUMED]; the skill's length and its charge before the bolt (PRTS 直击 "蓄力1.4s后
@@ -114,11 +116,24 @@ function kitShadowBlade(ab) {
}];
}
/**
* 假想敌:骨刺 — PRTS 天赋 (级别0): 「自身普通攻击索敌不受阻挡影响」 — profile `blockFree` (as 自制投石机): blocked, and so out of its
* 隐匿, it still picks by priority among the allies in reach, not its blocker first, and a 隐匿 / 迷彩 blocker is no target
* (targeting.js canTargetAlly); 「不会攻击飞行单位」 — profile `canTarget`; 「隐匿(解除阻挡0秒后恢复)」 — stealth(); 「隐匿期间
* 可同时攻击3个目标」: while its 隐匿 holds, the first ACBUNN_TARGETS by priority among the allies its normal attack could hit
* (ai.js attackTargets: the same reach — rangeRadius + the ally collider — and the same rules); one otherwise. Until
* 0.2.2 (PR #365 by @Sukvii, the PRTS lines checked for this port): blocker first, flyers hit, the 3 picked within the bare
* rangeRadius.
*/
function kitBoneSpike() {
return [stealth(), {
spawn(b, e) {
e.profile.blockFree = true;
e.profile.canTarget = (u) => !u.isFlying;
},
before(c, b, e) {
if (!enemyStealthed(e)) return; // 隐匿状态下同时攻击数个目标 (back 0 s after a block)
const l = byPriority(e, targetsNear(b, e, e.base.rangeRadius));
if (!enemyStealthed(e)) return;
const l = byPriority(e, targetsNear(b, e, e.base.rangeRadius + ALLY_COLLIDER_RADIUS).filter(e.profile.canTarget));
if (l.length) c.targets = l.slice(0, ACBUNN_TARGETS);
},
}];
+5
View File
@@ -74,6 +74,11 @@ export default {
`../shared/tierN.js` are usable too, but same-named helpers are not interchangeable: `num` of tier 1 / 4 also reads
numeric strings, those of tiers 3 / 5 / 6 do not; `toggleBuff`, `talentBb`, `moduleBb`, `instantKind` take different
arguments per tier. Read the helper before using it.
- **Not in a kit**: a module's trait line 「攻击范围内存在N名及以上敌人时攻击速度+X」 (REA-Y: 圣约送葬人, 隐德来希) is applied
to every operator by the engine (`../traitMods.js`, called from `battle/players.js` `_setupUnit` after `kit.install`);
a kit adding it too would count it twice. The other conditional attack-speed lines of modules stay in their kits —
traitMods.js lists them and refuses them, and `test/sim/trait_attack_speed.test.js` scans every chess, 补位 and 自选
loadout so a new record with an unknown shape is noticed.
- **The battle API** documented in docs/SIM.md: units (§2), buffs / mods / statuses (§3), the damage & heal pipeline
(§4), the hook bus and its re-entrancy rule (§5), the engine helpers content must use (§6), the skill runtime (§7.1);
engine modules by relative path (`../../../constants.js`, `targeting.js`, `body.js` for huge enemies, `dir.js` for
@@ -7,7 +7,6 @@ import {
AURA, num, tbb, grid, nationOf, enemiesOnRange, targetsInRange, whileDeployed, reveal, skillActive, isSel, alt,
instantKind, withDefaults,
} from '../shared/tier4.js';
import { hypot } from '../../../detmath.js';
/** Is module `id` the active one? (elites; `'none'` / normal chess ⇒ false) */
const moduleIs = (def, id) => !!(def?.raw?.module?.active && def.raw.module.id === id);
@@ -31,7 +30,9 @@ export default withDefaults({
return {
skills: alt(def, {
// 铳骑主考官: the next attack is a triple hit of atk_scale each and reloads `charge` bullet(s) into ONE other
// 拉特兰 operator around him (8 tiles, [ASSUMED] nearest first) whose ammo skill runs
// 拉特兰 operator around him whose ammo skill runs — PRTS 备注 「补弹范围为周围8格,仅选择可补弹的干员为目标,优先选择最后
// 部署的干员」: the latest deployment first (battle-wide deploySeq: a redeploy is a new deployment). Until 0.2.2 the
// nearest first [ASSUMED] (GitHub #325, PR #329)
skchr_rmixer_1: () => ({
kind: instantKind(def),
attack: {
@@ -40,7 +41,7 @@ export default withDefaults({
const n = num(bb.charge, 1);
const cand = battle.alliesInRadius(unit.x, unit.y, 1.5, unit.ownerId)
.filter((a) => a !== unit && a.kind === 'op' && nationOf(a) === 'laterano' && a.skill && a.skill.active && a.skill.kind === 'ammo')
.sort((a, b) => hypot(a.x - unit.x, a.y - unit.y) - hypot(b.x - unit.x, b.y - unit.y) || a.id - b.id);
.sort((a, b) => b.deploySeq - a.deploySeq || a.id - b.id);
if (!cand[0] || !(n > 0)) return;
cand[0].skill.addAmmo(n);
battle.fx('reload', { x: cand[0].x, y: cand[0].y, id: cand[0].id, n });
@@ -12,14 +12,17 @@ const ABYSSAL = new Set(['char_143_ghost', 'char_263_skadi', 'char_474_glady', '
const isAbyssal = (u) => u?.def?.raw?.groupId === 'abyssal' || ABYSSAL.has(u?.def?.charId ?? u?.def?.raw?.charId);
export default withDefaults({
// ===== 歌蕾蒂娅 (hookmaster) S3 缺水的碎漩狂舞 — bind a far target, tornado: slow, 85 % arts pulses + pull, final pull
// ===== 歌蕾蒂娅 (hookmaster) S3 缺水的碎漩狂舞 — bind a far target, tornado (r 1.5): slow, 85 % arts pulses + pull,
// final pull (the 捕网, r 1)
// S1 缺水的大洋裂断 (charges: next attack pulls the target to her front, 150 %/180 %); S2 缺水的掌握怒海 (BAT +0.5 s,
// wider range, ≤ 2 targets — blocked first — at 135 %/150 % and pulled to her front); module HOK-Y (淡金坠饰):
// a pull towards herself of an enemy farther than 2.5 tiles is one force level stronger
chess_char_4_12_a: (bb, chess, def) => {
const t0 = tbb(def, 0), t1 = tbb(def, 1), mb = moduleBb(def);
const tb = def.traitBb || {};
const R = 1.5; // [ASSUMED] tornado radius (no blackboard key)
// S3 radii (no blackboard key) — PRTS 备注: 「龙卷风半径1.5」 (slow, pulses) and 「技能结束时的拖拽为捕网,捕网半径为1,无伤害」
// (the skill-end pull; until 0.2.2 it reused the tornado's 1.5 — GitHub #324, PR #329)
const R = 1.5, NET_R = 1;
const force = num(bb.force, num(bb['attack@force'], 0));
const iv = Math.max(0.1, num(bb.interval, 1.5));
const g = grid(def.skill?.rangeGrid);
@@ -76,7 +79,7 @@ export default withDefaults({
const T = unit.mem.tornado;
unit.mem.tornado = null;
if (!T || reason === 'death' || !unit.alive) return;
for (const e of battle.foesInRadius(T.x, T.y, R)) pullSelf(battle, unit, e, force);
for (const e of battle.foesInRadius(T.x, T.y, NET_R)) pullSelf(battle, unit, e, force); // the 捕网
battle.fx('pull', { x: T.x, y: T.y, id: unit.id });
},
},
@@ -2,7 +2,7 @@
// Conventions of the tier-5 kits: ../shared/tier5.js; kit contract and rules: ../README.md.
import {
num, on, talent, batPct, mods, inFaction, isOp, selectedId, lazySkills, skillRange, crowdAspd,
num, on, talent, batPct, mods, inFaction, isOp, selectedId, lazySkills, skillRange,
} from '../shared/tier5.js';
const isLaterano = (u) => inFaction(u, 'lateranoShip', ['laterano']);
@@ -100,7 +100,9 @@ export default {
} },
],
install(battle, unit) {
crowdAspd(battle, unit, 'excu2:module', num(tm.attack_speed), num(tm['trigger_cnt[equip]']));
// REA-Y "攻击范围内存在2名及以上敌人时攻击速度+12": the ENGINE owns this trait line now
// (server/sim/content/traitMods.js, applied from battle/players.js _setupUnit for every operator) — a second
// implementation here would count the same 12 points twice.
if (sid === 'skchr_excu2_3') { // S3: +attack@atk ATK per bullet spent
battle.on('ammoUsed', (c) => {
if (c.unit !== unit || !unit.skill?.active) return;
@@ -3,7 +3,7 @@
import { mitigate } from '../../../damage.js';
import {
RING1, num, on, talent, talentRec, traitBb, skillGrid, mods, dist, lazySkills, instantKind, crowdAspd,
RING1, num, on, talent, talentRec, skillGrid, mods, dist, lazySkills, instantKind,
} from '../shared/tier5.js';
/** 隐德来希 S3 "每次攻击对心烛至少造成35%攻击力的伤害". */
@@ -23,7 +23,7 @@ export default {
// for atk_scale × ATK phys every `interval` s — ground enemies only unless the carrier has taken off (PRTS 备注).
// Module REA-Y (elite): ASPD +12 with ≥ 2 enemies in range.
chess_char_5_06_a: (bb, chess, def) => {
const t0 = talent(chess, 0), t1 = talent(chess, 1), tb = traitBb(chess);
const t0 = talent(chess, 0), t1 = talent(chess, 1);
const hpScale = num(bb['attack@max_hp_scale'], 0.6), defScale = num(bb['attack@def_scale'], 1), resScale = num(bb['attack@magic_resistance_scale'], 1);
const nCandles = Math.max(1, num(bb['attack@max_target'], 3));
const candleKey = talentRec(chess, 2)?.bbStr?.take_extra_enemy_key ?? CANDLE_KEY;
@@ -140,7 +140,9 @@ export default {
} },
],
install(battle, unit) {
crowdAspd(battle, unit, 'etlchi:module', num(tb.attack_speed), num(tb.cnt));
// REA-Y "攻击范围内存在2名及以上敌人时攻击速度+12": the ENGINE owns this trait line now
// (server/sim/content/traitMods.js, applied from battle/players.js _setupUnit for every operator) — a second
// implementation here would count the same 12 points twice.
const mine = (e) => e && e.mem && e.mem.candleOwner === unit;
// "心烛只受隐德来希攻击的影响": other sources' element gauges never fill a candle either
battle.on('elementHit', (c) => { if (mine(c.target) && c.source !== unit) c.dmg.cancel = true; }, { owner: unit });
@@ -10,7 +10,12 @@ import { powi } from '../../../detmath.js';
// ------------------------------------------------------------------------------------------------------------------
// 异客 chess_char_6_05 (链术师) — S3 辉煌裂片; 机理分析; 孤卒
const STORM_RADIUS = 1.5; // [ASSUMED] storm zone radius (not in data)
/** S3 雷暴区域: PRTS 备注 「以其判定中心所在格为中心生成一个x-1的雷暴区域」 — range x-1 (range_table: the diamond of radius 2,
* 13 tiles) around the tile of the target's centre (GitHub #322, PR #329). Until 0.2.2 a 1.5-tile circle around the
* target's exact position [ASSUMED]. The chain of each strike bounces on its own radius, inside or outside the zone. */
const STORM_GRID = Object.freeze([[2, 0], [1, -1], [1, 0], [1, 1], [0, -2], [0, -1], [0, 0], [0, 1], [0, 2], [-1, -1], [-1, 0], [-1, 1], [-2, 0]]);
/** The storm fx's ring radius (tiles): the diamond's reach — drawn only, the zone is STORM_GRID. */
const STORM_FX_RADIUS = 2;
function pasngr(bb, chess, def) {
const t0 = tbb(def, 0), t1 = tbb(def, 1), tb = def?.traitBb || {};
@@ -69,16 +74,18 @@ function pasngr(bb, chess, def) {
const cands = enemiesIn(battle, unit, keys);
if (!cands.length) return;
const tgt = cands.reduce((a, b) => (b.hp > a.hp ? b : a));
const cx = tgt.x, cy = tgt.y;
// 判定中心所在格: the tile of the target's centre (the rounding of body.js posKey); fixed for the storm's 4 s
const cx = Math.round(tgt.x), cy = Math.round(tgt.y);
const zoneKeys = absoluteRangeKeys(STORM_GRID, cy, cx, 1, 0);
const dur = num(bb.duration, 4), iv = Math.max(0.1, num(bb.interval, 0.5)), scale = num(bb.atk_scale, 1);
const n = Math.max(1, Math.round(dur / iv));
battle.fx('storm', { x: cx, y: cy, id: unit.id, duration: dur, r: STORM_RADIUS });
battle.fx('storm', { x: cx, y: cy, id: unit.id, duration: dur, r: STORM_FX_RADIUS });
let k = 0;
const seq = unit.deploySeq;
const h = battle.every(iv, () => {
if (!live(unit) || unit.deploySeq !== seq) { h.cancel(); return; } // the storm ends when she leaves the field
if (++k >= n) h.cancel();
const zone = battle.foesInRadius(cx, cy, STORM_RADIUS).filter((e) => canTargetEnemy(unit, e, ANY));
const zone = enemiesIn(battle, unit, zoneKeys); // every enemy she can select whose body is on the 13 tiles
const e = battle.rng.pick(zone);
if (e) strike(battle, unit, e, scale);
}, { owner: unit });
+1 -8
View File
@@ -96,13 +96,6 @@ const skillRange = (chess, def, extra = null) => {
const g = skillGrid(chess, def);
return g || extra ? { ...(g ? { rangeGrid: g } : {}), ...(extra || {}) } : undefined;
};
/** "攻击范围内存在N名及以上敌人时攻击速度+X" (modules REA-Y). */
function crowdAspd(battle, unit, key, aspd, cnt) {
if (!aspd || !(cnt > 0)) return;
whileOn(battle, unit, 0.2, () => {
if (battle.enemiesInKeys(unit.rangeKeys, unit, unit.profile).length >= cnt) battle.addBuff(unit, { key, duration: 0.3, mods: { aspd } });
});
}
/** "攻击范围内存在元素损伤爆发的敌人时,技力自然恢复速度+X/秒" (modules PRI-Y). */
function burstSpUp(battle, unit, key, sp) {
if (!(sp > 0)) return;
@@ -208,6 +201,6 @@ function enemiesInGrid(battle, unit, grid, n = 0, { ignoreStealth = false } = {}
export {
HALF_HP, NEVER, AURA_IV, AURA_DUR, RING1, num, on, inRange, talent, talentRec, traitBb, skillGrid, maxCharges, batPct,
mods, dist, inFaction, isAbyssal, isOp, selectedId, lazySkills, instantKind, skillRange, crowdAspd, burstSpUp,
mods, dist, inFaction, isAbyssal, isOp, selectedId, lazySkills, instantKind, skillRange, burstSpUp,
leaderOf, elementHit, whileOn, spAura, burstDamageUp, lowHpHealUp, moduleRangeUp, permBuff, enemiesInGrid,
};
+2 -1
View File
@@ -27,7 +27,8 @@
// 6_04 浊心斯卡蒂 S3 HP drain is non-lethal (like the geek drain); 鼓舞 = flat ATK added after the target's multipliers;
// 海嗣 is inert (no attack) and only extends her aura (and her DEFAULT trigger); it expires after its
// duration and is re-summoned after its redeploy time when DP ≥ its cost (auto-redeploy emulation).
// 6_05 异客 storm zone radius 1.5 tiles (not in data); strikes chain like her trait (4 targets, trait falloff);
// 6_05 异客 storm zone = range x-1 (the 13-tile diamond) around the tile of the target's centre (PRTS S3 备注);
// strikes chain like her trait (4 targets, trait falloff) on their own bounce radius;
// the storm stops when she leaves the field.
// 6_06 佩佩 splash stun via `damaged` (isSplash) hook; module ×1.15 when ≥3 enemies in the splash area.
// 6_07 维娜 "attack enemies blocked by allies in talent range" = those enemies' tiles are added to her range;
+218
View File
@@ -0,0 +1,218 @@
// server/sim/content/traitMods.js — trait (特性) modifiers the ENGINE consumes for every operator (DESIGN §5, §16).
//
// WHY THIS FILE EXISTS. Several modules add a line to the operator's TRAIT, and the official tables split that line in
// two: `battle_equip_table` `overrideTraitDataBundle.candidates[].blackboard` carries the NUMBER
// (`{key:'attack_speed', value:12}`) while the CONDITION exists only in the candidate's sentence
// (`additionalDescription` / `overrideDescripton` → data/chess.json `trait.moduleDesc` / `trait.desc`). build-data.mjs
// (`applyModuleTraitParts`) folds that blackboard into `trait.bb` unconditionally, so the number reached
// `resolveProfile` (professions.js — it spreads `def.traitBb` into the profile) and stopped there: no sim file read
// `profile.attack_speed` (every other reader of an `attack_speed` blackboard — bonds, devices, enemies — maps it to
// `mods.aspd` itself), and a module reading 「攻击范围内存在2名及以上敌人时攻击速度+12」 therefore did nothing at all.
//
// THE RULE (engine-level, no operator-id whitelist). Any operator whose resolved def carries `attack_speed` on a
// trait-level blackboard AND whose own trait text states a condition this file can evaluate gets that stat while — and
// only while — the condition holds. The condition is read from the clause the official sentence puts BEFORE 「攻击速度」.
//
// THIS FILE EVALUATES EXACTLY ONE CONDITION SHAPE, on purpose:
// 「攻击范围内存在N名及以上敌人时」 — the REA-Y module line (圣约送葬人 chess_char_5_01_b +12, 隐德来希
// chess_char_5_06_b +12). It is the shape the engine was missing, and its state is a pure function of the battlefield
// (the engine's own targeting), so it needs nothing from a kit.
// A condition this file does not evaluate installs NOTHING — never a permanently-on stat. `traitAttackSpeedRule(def)`
// reports what it found (`clause`, `value`, `condition`, `reason`) so callers and tests can see the decision.
//
// WHY NOT MORE — the other trait attack-speed lines already have a hand-written implementation in the owning kit, and
// adding a generic rule for them would apply the same stat TWICE. Each is listed here with where it lives (one kit file
// per operator since 0.2.0, `kits/ops/<chessId>-<codename>.js`):
// 被击倒时不撤退…攻击速度+30(单次部署只触发1次) 斯卡蒂 chess_char_3_05_b (DRE-Y) → ops/chess_char_3_05-skadi.js
// 耀骑士临光 6_17_b (DRE-Y) 同理 → ops/chess_char_6_17-nearl2.js
// 未阻挡敌人时攻击速度+8 史尔特尔 5_07_b (AFT-X) → ops/chess_char_5_07-surtr.js
// 未阻挡敌人时攻击速度+8 维娜·维多利亚 6_07_b (AFT-X) → ops/chess_char_6_07-siege2.js
// 不阻挡敌人时…攻击速度+10 号角 5_08_b (FOR-Y) → ops/chess_char_5_08-horn.js
// 能够阻挡一个敌人,生命值高于50%时攻击速度+10 山 5_17_b (FGT-Y) → ops/chess_char_5_17-f12yin.js
// 范围内存在地面敌人时攻击速度+8 空弦 3_21_b (MAR-Y) → shared/tier3.js groundAspd
// 能天使 3_01_b (MAR-Y) 同理 → ops/chess_char_3_01-angel.js
// 攻击范围内存在受到元素损伤的友方单位时攻击速度+8 纯烬艾雅法拉 6_20_b (WDM-Y) → ops/chess_char_6_20-agoat2.js
// 拥有已储存的攻击能量时,攻击速度+30 黑键 / 维伊 (自选, MSC-Y) → ops/op-ebnhlz.js / ops/op-veen.js
// Generalising one of those means deleting the kit implementation in the same change; until then this file refuses them
// (`condition: null`), which is also what keeps them bit-for-bit unchanged.
//
// Two more boundaries found by scanning every record × every module choice (the scan is pinned by
// test/sim/trait_attack_speed.test.js):
// * 仇白 (chess_char_6_15_b) LOR-Y states the same 「攻击范围内存在2名及以上敌人时攻击速度+12」, but its build-data
// candidate targets a NAMED talent, so the number lands in `talents[i]` and never on a trait-level blackboard — her
// kit reads exactly that. This file therefore does not see the line at all, which is the right outcome: applying it
// here as well would double it.
// * A module that ALREADY wanted its line applied here can carry the number on a hidden talent (index −1) instead
// of the trait blackboard — 圣约送葬人 REA-Y does (`uniequip_003_excu2` `talentChanges[0].bb.attack_speed`), while
// 隐德来希 REA-Y carries it straight on `trait.bb`. Both routes are read (see `attackSpeedSources`).
//
// 自选 picks (shared/diy.js) are scanned too: 黑键 / 维伊 MSC-Y 「拥有已储存的攻击能量时,攻击速度+30」 rides a hidden
// module talent like 圣约送葬人's, and their 自选 kits own it (ops/op-ebnhlz.js, ops/op-veen.js — refused as 'kit').
//
// Tests: test/sim/trait_attack_speed.test.js. PR #293 by @LimitlessHPPK (0.2.2); wired from battle/players.js _setupUnit.
/** Finite number or `d` (blackboard values may be strings). Kept local: this file is imported by the engine itself
* (battle/players.js), so it must not pull a kit file into the static import graph — the kits load through guarded
* dynamic imports (content/kits/index.js) precisely so a broken kit degrades instead of breaking the server. */
const num = (v, d = 0) => (typeof v === 'number' && Number.isFinite(v) ? v : d);
/** Trait blackboard of a raw chess record (`chess.trait.bb`, the module's lines folded in by build-data.mjs). */
const traitBb = (chess) => (chess && typeof chess.trait === 'object' && chess.trait ? chess.trait.bb ?? {} : {});
/**
* The condition shapes this file evaluates, in match order. Each takes the clause before 「攻击速度」 and returns
* `(battle, unit) => boolean`, or null when the clause is not that shape.
*/
const CONDITIONS = [
/**
* 「攻击范围内存在N名及以上敌人时」 (modules REA-Y). N is read from the SENTENCE and defaults to 2: the official
* module's own blackboard does carry the same count (`trigger_cnt[equip]` / `cnt` = 2 on the two REA-Y records), but
* the number that decides what the TEXT promises has to come from the text — a blackboard count is a kit-side
* implementation detail (records carry `cnt` / `trigger_cnt[equip]` for other talents too). Reading it here instead
* would make this file's decision depend on the module's internal layout.
* Counted through the engine's own targeting — `battle.enemiesInKeys(unit.rangeKeys, unit, unit.profile)`: the unit's
* live range keys (a skill that widens the range widens this too) and its own profile, so 隐匿 / 不可选中 / 飞行 follow
* the operator's real attack rules. That is the call the two REA-Y kits used before this file took the line over
* (shared/tier5.js `crowdAspd`, deleted in the same change so the stat is counted once).
*/
function crowdInRange(clause) {
if (!/攻击范围内(?:存在|有)/.test(clause)) return null;
if (!/敌人/.test(clause)) return null; // excludes 「…存在受到元素损伤的友方单位时」
const m = clause.match(/存在\s*(\d+)\s*名/);
const need = m ? Math.max(1, +m[1]) : 2;
return (battle, unit) => battle.enemiesInKeys(unit.rangeKeys, unit, unit.profile).length >= need;
},
];
/**
* Trait attack-speed conditions a hand-written kit already owns (documented above; refused here, never applied).
* Every entry is one that a real record in data/chess.json hits — the closed list is checked against the shipped data
* by test/sim/trait_attack_speed.test.js, so a new record that carries one of these lands in 'kit', and anything else
* lands in 'unknown' (nothing installed either way; both are inert, the difference is only in the report).
*/
const KIT_OWNED = [
/被击倒时不撤退/, // DRE-Y — 斯卡蒂 3_05_b / 耀骑士临光 6_17_b
/未阻挡敌人时/, // AFT-X — 史尔特尔 5_07_b / 维娜·维多利亚 6_07_b
/不阻挡敌人时/, // FOR-Y — 号角 5_08_b
/生命值高于\s*\d+(?:\.\d+)?\s*%/, // FGT-Y — 山 5_17_b
/存在地面敌人/, // MAR-Y — 空弦 3_21_b / 能天使 3_01_b (shared/tier3.js groundAspd)
/受到元素损伤的友方单位/, // WDM-Y — 纯烬艾雅法拉 6_20_b
/拥有已储存的攻击能量时/, // MSC-Y — 黑键 / 维伊 (自选 picks; ops/op-ebnhlz.js, ops/op-veen.js)
];
/**
* Lines that only ever apply in another game mode — 集成战略 (ISW modules, e.g. 水月 ISW-A `attack_speed: 50`). This
* mode is 卫戍协议, and the rest of the repo already ignores their mode-gated halves (shared/loadoutRecord.js
* `traitRangeExtend` skips 空弦 ISW-A for exactly this reason), so they are refused here too.
*/
const MODE_SPECIFIC = [/在集成战略中/];
/** The buff key the engine rule installs (one per operator; `refresh:'keep'` so a check never extends its duration). */
export const TRAIT_ASPD_BUFF = 'trait:attack_speed';
/** Strip the official inline markup (`<@ba.vup>+12</>`, `<$ba.dt.element>元素损伤</>`) from a trait sentence. */
export const plainText = (s) => String(s ?? '').replace(/<[^>]*>/g, '');
/**
* The clause a trait sentence puts before its LAST 「攻击速度」 mention — the condition. '' when the sentence has no
* 「攻击速度」 at all (e.g. 圣约送葬人's REA-Y record, whose own trait sentence is about the reaper self-heal), or when
* the attack-speed part comes first and is unconditional (「攻击速度+18,攻击力+3%」 → clause '', since nothing
* precedes it).
*/
export function conditionClause(text) {
const s = plainText(text);
const i = s.lastIndexOf('攻击速度');
return i <= 0 ? '' : s.slice(0, i).trim();
}
/** The trait/statement texts of `def` a condition may be read from, most specific (the module's added line) first. */
function traitClauses(def) {
const raw = def?.raw ?? {};
const t = raw.trait && typeof raw.trait === 'object' ? raw.trait : {};
const out = [];
for (const s of [t.moduleDescRaw, t.moduleDesc, t.descRaw, t.desc, typeof raw.trait === 'string' ? raw.trait : null]) {
const p = plainText(s).trim();
if (p && !out.includes(p)) out.push(p);
}
return out;
}
/**
* The blackboards of `def` a trait-level attack-speed line may live on, most authoritative first: the trait blackboard
* (the module line build-data folds in — 隐德来希 REA-Y), then the hidden module talents (data index −1) — the route
* 圣约送葬人 REA-Y and the MAR-Y / WDM-Y lines above take. A line merged into a NAMED talent is deliberately not read
* here (仇白 LOR-Y: her kit owns it).
*/
function attackSpeedSources(def) {
const out = [];
const tb = traitBb(def?.raw ?? {});
if (Number.isFinite(tb.attack_speed)) out.push(tb);
const talents = def?.raw?.talents;
if (Array.isArray(talents)) {
for (const t of talents) {
if (!t || t.index !== -1 || !t.bb || !Number.isFinite(t.bb.attack_speed)) continue;
if (!out.includes(t.bb)) out.push(t.bb);
}
}
return out;
}
/**
* The trait attack-speed line of `def` as the engine will apply it.
* @param {object} def normalised unit def (sim/simdata.js)
* @returns {{value:number, condition:((battle:object, unit:object)=>boolean)|null, clause:string,
* source:string|null, reason:string}|null}
* null = this def has no trait attack-speed line at all.
* `condition !== null` = the engine applies `value` while it holds.
* `condition === null` = nothing is installed; `reason` says why: 'kit' (a hand-written kit owns the condition),
* 'mode' (the line only applies in 集成战略), 'unknown' (a clause shape this file does not know), 'no-text' (the data
* build dropped the sentence).
*/
export function traitAttackSpeedRule(def) {
const sources = attackSpeedSources(def);
if (!sources.length) return null;
const value = num(sources[0].attack_speed);
if (!value) return null;
for (const text of traitClauses(def)) {
const clause = conditionClause(text);
if (!clause) continue;
for (const match of CONDITIONS) {
const condition = match(clause);
if (condition) return { value, condition, clause, source: text, reason: 'ok' };
}
if (MODE_SPECIFIC.some((re) => re.test(clause))) return { value, condition: null, clause, source: text, reason: 'mode' };
if (KIT_OWNED.some((re) => re.test(clause))) return { value, condition: null, clause, source: text, reason: 'kit' };
return { value, condition: null, clause, source: text, reason: 'unknown' };
}
return { value, condition: null, clause: '', source: null, reason: 'no-text' };
}
/**
* Apply a def's trait attack-speed line to an operator: the stat is on exactly while its condition holds. Called once
* per operator from `BattlePlayers._setupUnit` (after `kit.install`). Evaluated on the engine's `tick` hook (one step =
* constants.TICK) so entering / leaving the condition changes the real attack cadence — ai.js `updateAlly` sets
* `u.atkCd = u.s.interval`, and `unit.s.interval = bat × 100 / aspd` (units.js stat aggregation). The buff is `refresh:'keep'` — a
* re-check can never extend or blank the stat, and the engine drops non-`persist` buffs on death, while the
* `deploy` / `battleStart` checks put it back on the next deployment.
* @param {object} battle
* @param {object} unit
* @returns {object|null} the rule it resolved (`condition: null` when it installed nothing), or null without a line
*/
export function installTraitAttackSpeed(battle, unit) {
if (!unit || unit.kind !== 'op' || unit._traitAspdInstalled) return null;
let rule = null;
try { rule = traitAttackSpeedRule(unit.def); } catch { rule = null; }
if (!rule || !rule.condition) return rule;
unit._traitAspdInstalled = true;
const mods = { aspd: rule.value };
const check = () => {
const want = !!unit.alive && !!unit.deployed && rule.condition(battle, unit);
const has = !!unit.findBuff(TRAIT_ASPD_BUFF);
if (want && !has) battle.addBuff(unit, { key: TRAIT_ASPD_BUFF, mods, refresh: 'keep', tags: ['trait'] });
else if (!want && has) battle.removeBuff(unit, TRAIT_ASPD_BUFF);
};
battle.on('tick', check, { owner: unit });
battle.on('battleStart', check, { owner: unit });
battle.on('deploy', (ctx) => { if (ctx.unit === unit) check(); }, { owner: unit });
return rule;
}
+17 -2
View File
@@ -108,6 +108,7 @@ export class SkillRuntime {
// the official skill strategies automate the manual 开启: only MANUAL skills wait for the operation cooldown
this.manual = String(d.skillType ?? 'MANUAL').toUpperCase() === 'MANUAL';
this.opReadyAt = -Infinity; // no automatic cast before this battle time (AUTO_OP_COOLDOWN)
this.nextCastAt = -Infinity; // instant / charges with no attack of its own: no cast from the tick before (GitHub #298)
if (this.kind === 'passive') this.baseSpCost = 0;
this._spCostMul = 1; // content may set spCostMul (e.g. 绝技 ×0.7) — see the accessor below
this.sp = 0;
@@ -269,6 +270,7 @@ export class SkillRuntime {
this._trigKeys = null;
this._trigSet = null;
this.opReadyAt = -Infinity; // (Battle._deploy starts the operation cooldown of the battle-start deployment)
this.nextCastAt = -Infinity;
if (this.noSkill) return;
if (this.kind === 'passive') {
this._startPassive();
@@ -389,6 +391,9 @@ export class SkillRuntime {
if (this.active && this.isTimed) return;
// a cast "next attack" still waits for its attack: another charge now would be spent on the same attack
if (this.pending || this._opCooling()) return;
// an instant / charge skill with no attack of its own is cast at most once per attack interval of its unit
// (activate sets nextCastAt; GitHub #298): the casts checked here wait for it — the unit's attacks are not touched
if (!this.isTimed && !this.spec.attack && this.battle.time < this.nextCastAt - 1e-9) return;
if (TICK_RULES.has(this.rule)) {
if (this._tickRuleSatisfied()) this.activate(this.rule);
} else if (this.rule !== 'TAKE_DAMAGE' && this.rule !== 'NEVER') {
@@ -493,7 +498,8 @@ export class SkillRuntime {
if (this.pending || this._opCooling()) return false;
if (!this._defaultCondition()) return false;
if (this.triggerAllies && !this._allyTriggerSatisfied()) return false;
return this.activate('DEFAULT');
this._beforeAttack = true; // the attack follows this cast in the same check (ai.js updateAlly): attacks pace it
try { return this.activate('DEFAULT'); } finally { this._beforeAttack = false; }
}
/** TAKE_DAMAGE trigger + INCREASE_WHEN_TAKEN_DAMAGE SP. */
@@ -541,7 +547,16 @@ export class SkillRuntime {
// bullets added in skillStart (拉特兰's ×(1.05 + 0.015 × layers), 逃犯引渡手续, talents): the bar's full mark
// (community report #35: the extra bullets sat above a full bar until fewer than the base count were left)
if (this.active && this.ammoLeft > this.ammoMax) this.ammoMax = this.ammoLeft;
if (!this.isTimed && !this.pending) this.end('instant');
if (!this.isTimed && !this.pending) {
this.end('instant');
// an instant / charge skill with no attack of its own (no spec.attack: a throw, a heal, a buff, DP …) waits one
// attack interval of its unit before it is cast again from the tick (GitHub #298: 引星棘刺 S1 度算浪波, an SP_FULL
// cast, was recast every tick once 迅捷's refund refilled its bar at once, whatever the attack speed) — on the skill
// only: the cast is no attack, the unit's attacks / heals keep their own rhythm. [ASSUMED] one attack interval: PRTS
// prints no 前后摇 for such casts. A cast made right before an attack (DEFAULT) is paced by those attacks already;
// a cast onEnd opened again owns its own gap.
if (!this.active && !this.spec.attack && !this._beforeAttack && u.alive && u.s.interval > 0) this.nextCastAt = b.time + u.s.interval;
}
return true;
}
+8 -2
View File
@@ -193,7 +193,12 @@ const PRIORITY_FNS = {
};
/**
* Sort candidate enemies for an attacker (in place) and return them.
* Sort candidate enemies for an attacker (in place) and return them: the enemies it blocks first (the user's rule after
* playtest #6, "阻挡了就一定要能打到", for every blocker — DESIGN §20.3), then the special priority, taunt, the remaining
* route, the spawn order. One exception, the owner's decision of 2026-10-08 (GitHub #220 by TsangAsuna, #205): the 速射手 air priority
* (trait 优先攻击空中单位 — priority 'fly', a skill's too) comes before its own blocked enemy, so a 速狙 on a melee tile
* blocking a ground enemy shoots the flyer in its range (PRTS 索敌的概念 「阻挡(近战限定)→特殊优先级→…」); every other
* priority keeps the blocked enemy first.
* @param {object} attacker ally unit
* @param {object[]} cands enemies
* @param {string|null} priority profile/skill priority key
@@ -201,6 +206,7 @@ const PRIORITY_FNS = {
export function sortEnemyTargets(battle, attacker, cands, priority) {
if (cands.length <= 1) return cands;
const pf = priority ? (PRIORITY_FNS[priority] || (priority === 'nearest' || priority === 'farthest' ? null : null)) : null;
const airFirst = priority === 'fly';
const ax = attacker.x, ay = attacker.y;
const keyed = cands.map((e) => ({
e,
@@ -210,7 +216,7 @@ export function sortEnemyTargets(battle, attacker, cands, priority) {
d: battle.remainingDistance(e),
s: e.spawnSeq,
}));
keyed.sort((a, b) => a.b - b.b || a.p - b.p || a.t - b.t || a.d - b.d || a.s - b.s);
keyed.sort((a, b) => (airFirst ? a.p - b.p : 0) || a.b - b.b || a.p - b.p || a.t - b.t || a.d - b.d || a.s - b.s);
for (let i = 0; i < keyed.length; i++) cands[i] = keyed[i].e;
return cands;
}
+28
View File
@@ -302,6 +302,22 @@ test('余 文火慢炖: R8 round start — exactly 1 active bond +36 layers, oth
cover('band_yu');
});
test('余 文火慢炖 adds no layers to a bond without layers (noStack: 独行, 绝技 …) — the owner\'s decision of 2026-10-08', () => {
assert.deepEqual(Object.values(DATA.bonds).filter((b) => b.noStack).map((b) => b.name).sort(), ['协防干员', '独行', '绝技', '调和'].sort());
for (const [bonus, want] of [
[{ soloShip: 1 }, {}], // the one active bond is 独行: nothing at all
[{ suntShip: 5 }, {}], // … or 绝技
[{ soloShip: 1, yanShip: 20 }, { yanShip: 12 }], // two active (the sentence's count): the layered one +12, 独行 none
]) {
const s = setup({ band: 'band_yu' });
Object.assign(s.ps.bondCountBonus, bonus); s.ps.recompute();
for (const id of Object.keys(bonus)) assert.ok(s.ps.bonds[id]?.active, `${id} active`);
s.roundStart(8);
const got = Object.fromEntries(Object.entries(s.ps.layers).filter(([, v]) => v > 0));
assert.deepEqual(got, want, JSON.stringify(bonus));
}
});
test('凯瑟琳 定向投放: every shop upgrade offers 3 different shop items (any tier — community report of 2026-10-06), 1 free pick', () => {
const { m, ps } = setup({ band: 'band_cathy', seed: 9 });
for (const lvl of [2, 3]) {
@@ -587,6 +603,18 @@ test('大帝 加急调派: every deployment halves the operator\'s next redeploy
cover('band_emperor');
});
test('大帝 加急调派 stacks without a cap: the 21st and 22nd deployments still halve the next redeploy (PRTS "※该策略效果可无限叠加"; GitHub #328, PR #329)', () => {
const h = fight({ band: 'band_emperor', units: [{ chessId: 't_op', row: 10, col: 4 }] });
h.step(1);
const u = h.unit('t_op');
for (let n = 1; n <= 22; n++) {
h.b.retreat(u, { reason: 'raid' });
close(u.respawnAt - u.deathAt, 20 / 2 ** n, `after deployment ${n}`);
if (n < 22) assert.ok(h.b._deploy(u), `deployment ${n + 1}`);
}
assert.equal(u.findBuff('band:band_emperor').stacks, 22);
});
test('桑葚 药枚实验: the units on the right-most column get a 25 % chance per attack of 1 shield layer (max 1)', () => {
const ops = { t_op: op('t_op'), t_b: op('t_b') };
for (const [v, want] of [[0.24, 1], [0.26, 0]]) {
+31
View File
@@ -71,6 +71,37 @@ test('迅捷: member skill end +12 SP (p=1 at high L), ≥40 layers every operat
assert.equal(off.s_x, 0);
});
test('迅捷 refills 引星棘刺 S1 at once: it is cast at most once per attack interval, and her attacks go on untouched (GitHub #298)', () => {
// S1 度算浪波: AUTO, no duration, no attack of its own, SP_FULL, cost 6; 迅捷 refunds 12 + 15 SP at ≥ 40 layers
const run = ({ aspd = 0, enemy = true, silence = false } = {}) => {
const h = makeBattle({
seed: 1, bonds: { swiftShip: bond(2, 229, 3) }, autoFinish: false, timeLimit: 30,
units: [{ chessId: 'chess_char_5_15_b', skillIndex: 0, row: 10, col: 4 }],
enemies: enemy ? [{ key: 'enemy_still', route: { motion: 'WALK', start: [10, 6], end: [10, 6], checkpoints: [] } }] : [],
defs: { enemies: { enemy_still: enemyRec({ key: 'enemy_still', hp: 1e9, atk: 0, speed: 0, blockCnt: 0 }) } },
});
h.step();
const u = h.unit('chess_char_5_15_b');
if (aspd) h.b.addBuff(u, { key: 'test:aspd', mods: { aspd } });
if (silence) h.b.addBuff(u, { key: 'test:mute', flags: { silence: true } });
if (enemy) assert.ok(h.b.enemiesInKeys(u.rangeKeys, u, u.profile).length, 'an enemy stays in range');
u.skill.gainSp(u.skill.spCost, 'test');
const n0 = u.skill.activations, a0 = u.stats.attacks;
h.run(6);
checkInvariants(h.b);
return { n: u.skill.activations - n0, attacks: u.stats.attacks - a0, interval: u.s.interval };
};
const base = run(), muted = run({ silence: true });
// the first at once, then one per interval (1.36 s): 5 in 6 s — every tick before (180)
assert.ok(base.n >= 4 && base.n <= 5, `one cast per attack interval (${base.interval.toFixed(2)} s): ${base.n} in 6 s`);
assert.ok(base.attacks > 0 && base.attacks === muted.attacks, `the casts take no attack (${base.attacks} vs ${muted.attacks} silenced)`);
const fast = run({ aspd: 500 }), fastMuted = run({ aspd: 500, silence: true });
assert.ok(fast.n >= 23 && fast.n <= 25, `ASPD +500 (interval ${fast.interval.toFixed(2)} s): ${fast.n} casts`);
assert.equal(fast.attacks, fastMuted.attacks, 'still no attack taken');
const alone = run({ enemy: false });
assert.ok(alone.n >= 4 && alone.n <= 5 && alone.attacks === 0, `no enemy: still fires at full SP (#124), one per interval — ${alone.n}`);
});
test('迅捷 / 突袭: SP gifts after end do not recharge a zero-SP deployment skill or trigger a ready raid', () => {
const h = makeBattle({
defs: { chess: { t_deploy: chessRec({ id: 't_deploy', bonds: ['swiftShip', 'raidShip'], skill: { spCost: 0 } }) } },
+83
View File
@@ -186,6 +186,67 @@ test(`${nm('enemy_9008_acbunn')}: attacks several targets at once while stealthe
assert.ok(atk.targets.length >= 2, `targets ${atk.targets.length}`);
});
// 骨刺 — PRTS 天赋 (级别0) 「自身普通攻击索敌不受阻挡影响 / 不会攻击飞行单位,隐匿(解除阻挡0秒后恢复) / 隐匿期间可同时攻击3个目标」
// and the level description 「隐匿;隐匿状态下同时攻击3个目标。」 (PR #365, the targeting part checked against PRTS).
function boneArena(o = {}) {
const h = arena({
units: ['t_wall', 't_wall2', 't_wall3', 't_wall4'].map((chessId, i) => ({ chessId, row: 9 + (i % 2), col: 6 + Math.floor(i / 2) })),
captureNoisy: true, hooks: ['attack'], ...o,
});
h.step();
for (const u of h.allies()) h.b.addBuff(u, { key: 'test:noBlock', persist: true, flags: { noBlock: true } });
const e = put(h, 'enemy_9008_acbunn', [9, 6]);
return { h, e };
}
/** The targets of `e`'s next attack. */
function boneNext(h, e) {
const n = h.hooksOf('attack').filter((c) => c.attacker === e).length;
assert.ok(h.runUntil(() => h.hooksOf('attack').filter((c) => c.attacker === e).length > n, 8), 'a new attack');
return h.hooksOf('attack').filter((c) => c.attacker === e)[n].targets;
}
test(`${nm('enemy_9008_acbunn')}: 3 targets while hidden, 1 once blocked or revealed — by priority, not its blocker first; 3 again when hidden`, () => {
for (const cause of ['block', 'reveal']) {
const { h, e } = boneArena();
const wall = h.unit('t_wall'), taunter = h.unit('t_wall4');
assert.equal(new Set(boneNext(h, e)).size, 3, `${cause}: hidden — three distinct targets of the four in reach`);
h.b.addBuff(taunter, { key: 'test:taunt', mods: { taunt: 5 } });
if (cause === 'block') { h.b.removeBuff(wall, 'test:noBlock'); h.step(2); assert.equal(e.blockedBy, wall); }
else h.b.addBuff(e, { key: 'test:reveal', persist: true, flags: { reveal: true } });
assert.deepEqual(boneNext(h, e), [taunter], `${cause}: one target, the taunt-5 one (索敌不受阻挡影响)`);
if (cause === 'block') { h.b.addBuff(wall, { key: 'test:noBlock', persist: true, flags: { noBlock: true } }); h.b.releaseBlocked(wall); }
else h.b.removeBuff(e, 'test:reveal');
assert.equal(boneNext(h, e).length, 3, `${cause}: hidden again (0 s after the block) — three`);
checkInvariants(h.b);
}
});
test(`${nm('enemy_9008_acbunn')}: never targets a flying unit (炎佑, taunt 5), hidden or revealed`, () => {
for (const revealed of [false, true]) {
const { h, e } = boneArena({ content: 'full', extraContent: [] });
const [air] = spawnYanyou(h.b, 'p1', { count: 1, hp: 50000 });
assert.ok(air?.isFlying);
air.x = 6.5; air.y = 9;
h.b.applyStatus(air, 'stun', { duration: 30, force: true });
h.b.addBuff(air, { key: 'test:taunt', mods: { taunt: 5 } });
if (revealed) h.b.addBuff(e, { key: 'test:reveal', persist: true, flags: { reveal: true } });
const t = boneNext(h, e);
assert.equal(t.length, revealed ? 1 : 3);
assert.ok(!t.includes(air), `${revealed ? 'revealed' : 'hidden'}: the flyer is no target`);
}
});
test(`${nm('enemy_9008_acbunn')}: its 3 hidden targets come from its normal attack's reach (2 + the 0.25 ally collider)`, () => {
const { h, e } = boneArena();
const edge = h.unit('t_wall3'), outside = h.unit('t_wall4');
edge.x = e.x + 2.1; edge.y = e.y;
outside.x = e.x + 2.3; outside.y = e.y;
h.b.addBuff(edge, { key: 'test:taunt', mods: { taunt: 5 } });
const t = boneNext(h, e);
assert.ok(t.includes(edge), 'the ally 2.1 away is one of the three');
assert.ok(!t.includes(outside), 'the one 2.3 away is out of reach');
});
test(`${nm('enemy_1175_dushdo_2')}: stealth; next to 深池伙友卫队精英 its attack interval drops by traitAbility.base_attack_time`, () => {
const h = arena();
h.step();
@@ -1037,6 +1098,28 @@ test(`${nm('enemy_1112_emppnt')} / 中枢: every attack is a shell that lands 3
}
});
test(`${nm('enemy_1112_emppnt_2')} / 先兆者: the shell's 1.2 circle takes every ally whose collider touches it — the impact tile and the 8 around it, not the cross (GitHub #364)`, () => {
for (const key of ['enemy_1112_emppnt_2', 'enemy_1112_emppnt']) {
// the enemy on (10, 8) reaches only the wall on (10, 6): the impact; the others stand around it, out of its reach
const h = arena({ units: [
{ chessId: 't_wall', row: 10, col: 6 }, { chessId: 't_wall2', row: 10, col: 5 },
{ chessId: 't_wall3', row: 9, col: 5 }, { chessId: 't_wall4', row: 10, col: 4 },
], captureNoisy: true });
h.step();
const e = put(h, key, [10, 8], { route: 2 });
assert.ok(h.runUntil(() => e.stats.attacks >= 1, 10), 'attacks');
const shell = h.eventsOf('fx').find((ev) => ev[1] === 'bombardShell');
assert.ok(Math.abs(shell[4].r - 1.45) < 1e-9, 'drawn at the reach: 1.2 + the 0.25 collider');
h.run(3.1);
const taken = (id) => h.unit(id).stats.taken;
for (const [id, what] of [['t_wall', 'the impact'], ['t_wall2', 'its orthogonal neighbour (1 away)'], ['t_wall3', 'a diagonal neighbour (√2 away)']]) {
assert.ok(Math.abs(taken(id) - e.s.atk) < 1e-6, `${key}: ${what} takes the shell`);
}
assert.equal(taken('t_wall4'), 0, `${key}: two tiles away stays out`);
checkInvariants(h.b);
}
});
test(`${nm('enemy_10084_hlegle')}: never attacks a flying ally (不会攻击飞行单位) — a ground one in range instead`, () => {
// a flying ally (the 炎佑 is one) deployed after the wall, so the engine's order (latest deployed) would pick it
const h = arena({ units: [{ chessId: 't_wall', row: 10, col: 6 }, { chessId: 't_wall2', row: 10, col: 8 }] });
+24
View File
@@ -113,6 +113,30 @@ test('信仰搅拌机 S1 铳骑主考官 (自动触发 ⇒ DEFAULT: hurt SP, fir
}
});
test('信仰搅拌机 S1 reloads the latest-deployed 拉特兰 ammo user around him, not the nearest (PRTS 备注; GitHub #325, PR #329)', () => {
for (const redeploy of [false, true]) {
// 隐现 ×2: the orthogonal neighbour deployed first, the diagonal one after it (both on his 8 surrounding tiles)
const h = battle([U('chess_char_4_01_a', 10, 5, 0), U('chess_char_1_01_a', 10, 4, null, null, { uid: 2 }), U('chess_char_1_01_a', 9, 4, null, null, { uid: 3 })]);
h.step();
const u = h.unit('chess_char_4_01_a'), near = h.unit(2), far = h.unit(3);
if (redeploy) { // a redeploy is the latest deployment
h.b.retreat(near, { reason: 'raid' });
assert.ok(h.b._deploy(near));
}
const latest = redeploy ? near : far, other = redeploy ? far : near;
assert.ok(latest.deploySeq > other.deploySeq);
for (const a of [near, far]) { assert.ok(a.skill.activate('test', { free: true })); a.skill.ammoLeft = 5; }
const e = h.spawn('enemy_dummy', { pos: [10, 6] });
assert.ok(u.skill.activate('test', { free: true }));
h.b.forceAttack(u, [e]);
h.run(0.5);
const reloads = h.eventsOf('fx').filter((x) => x[1] === 'reload');
assert.ok(reloads.length > 0, 'a reload happened');
assert.ok(reloads.every((x) => x[4].id === latest.id), `${redeploy ? 'after a redeploy, the redeployed one' : 'the later-deployed diagonal one'} is reloaded`);
checkInvariants(h.b);
}
});
test('信仰搅拌机 S2 八臂电锯侠: ammo skill, ATK/DEF up; a lethal hit is negated for ammo_cost bullets; with fewer it is still negated, every bullet goes and the skill ends (PRTS 备注)', () => {
for (const id of pair('chess_char_4_01_a')) {
const bb = D(id, 1).skill.bb;
+21
View File
@@ -454,6 +454,27 @@ test('歌蕾蒂娅 S3: binds the farthest target, tornado pulses 85 % ATK arts e
checkInvariants(h.b);
});
test('歌蕾蒂娅 S3: the skill-end 捕网 has radius 1, the tornado 1.5 (PRTS 备注; GitHub #324, PR #329)', () => {
const id = 'chess_char_4_12_a';
const h = makeBattle({ defs: { enemies: { enemy_dummy: dummy() } }, units: [{ chessId: id, row: 10, col: 3 }], timeLimit: 200, autoFinish: false });
h.step();
const u = h.unit(id);
h.spawn('enemy_dummy', { pos: [10, 6] }); // the farthest enemy: bound, the tornado sits on it
h.step();
assert.ok(u.skill.activate('test', { free: true }));
assert.deepEqual([u.mem.tornado.x, u.mem.tornado.y], [6, 10]);
const at = (x, y) => { const e = h.spawn('enemy_dummy', { pos: [10, 6] }); e.x = x; e.y = y; return e; };
const inner = at(6, 9.1), outer = at(6, 11.25); // 0.9 and 1.25 from the centre
h.step();
for (const e of [inner, outer]) assert.ok(e.findBuff(`glady:slow:${u.id}`), 'both inside the tornado (slowed)');
const p0 = [inner.x, inner.y, outer.x, outer.y];
u.skill.end('test');
h.run(0.6);
assert.ok(Math.hypot(inner.x - p0[0], inner.y - p0[1]) > 0.3, 'the net pulls the enemy 0.9 from the centre');
assert.deepEqual([outer.x, outer.y], [p0[2], p0[3]], 'the one 1.25 from the centre stays: outside the net');
checkInvariants(h.b);
});
test('歌蕾蒂娅 阿戈尔的波涛: 深海猎人 regen 2.5 %/s and −25 % damage from 海怪; elite drag damage', () => {
const id = 'chess_char_4_12_a', t0 = D(id).talents[0].bb;
const sea = withTags(dummy({ key: 'enemy_sea', atk: 0 }), ['seamonster']);
+26
View File
@@ -1097,6 +1097,32 @@ test('异客: the S3 storm stops when she leaves the field', () => {
assert.equal(h.eventsOf('fx').filter((e) => e[1] === 'lightning').length, n, 'no strike after her retreat');
});
test('异客: the S3 storm is range x-1 — the 13-tile diamond — around the tile of the target\'s centre (PRTS 备注; GitHub #322, PR #329)', () => {
// the storm's target stands off its tile centre at (5.4, 10.4) — tile (10, 5) — and is removed right after the cast, so
// every strike starts on the probe (no chain bounce from the target) when, and only when, the probe is in the zone
const strikes = ([dx, dy]) => {
const h = battle({ units: [{ chessId: 'chess_char_6_05_a', row: 10, col: 3 }], autoFinish: false });
h.step();
const u = h.unit('chess_char_6_05_a');
const tgt = h.spawn('enemy_dummy2', { pos: [10, 5], mods: { hpMul: 2 } });
tgt.x = 5.4; tgt.y = 10.4;
h.step();
assert.ok(u.skill.activate('test', { free: true }));
const storm = h.eventsOf('fx').find((e) => e[1] === 'storm');
assert.deepEqual([storm[2], storm[3]], [5, 10], 'the storm sits on the centre of the target\'s tile');
h.b.dealDamage(null, tgt, { amount: 1e12, type: 'true' });
const p = h.spawn('enemy_dummy', { pos: [10, 5] });
p.x = 5 + dx; p.y = 10 + dy;
h.run(4.2);
checkInvariants(h.b);
return h.dmg.filter((d) => d.src === u.id && d.tgt === p.id && d.tags.includes('storm')).length;
};
// in the diamond (|Δrow| + |Δcol| ≤ 2 by tile): (2, 0) / (0, −2) / (1.95, 0.4) lay outside the old 1.5 circle
for (const off of [[0, 0], [2, 0], [0, -2], [-2, 0], [1.45, 1.45], [1.95, 0.4]]) assert.equal(strikes(off), 8, `(${off}) struck by all 8 strikes`);
// outside it: tile (+2, +1), and tile (+3, 0) — (1.55, 1.05) was inside the old circle around the target's position
for (const off of [[2, 1], [1.55, 1.05], [0, 2.6]]) assert.equal(strikes(off), 0, `(${off}) never struck`);
});
test('蕾缪安: 通缉 needs a continuous 8 s stay in a 拉特兰 range (leaving restarts the count)', () => {
const h = battle({
units: [{ chessId: 'chess_char_6_01_a', row: 12, col: 2 }, { chessId: 'test_lat_a', row: 10, col: 2 }],
+2
View File
@@ -292,6 +292,8 @@ test('her stock (the owner\'s decision of 2026-10-06): the placed 棋子 occupy
assert.deepEqual([u.skill.activations, w.stock], [1, 7], `${S}: 立即获得两枚棋子`);
h.step();
assert.ok(!u.s.flags.noSp, `${S}: below the cap, no 阻回`);
assert.ok(u.skill.nextCastAt > h.b.time, `${S}: the next cast waits one attack interval (GitHub #298)`);
h.run(u.skill.nextCastAt - h.b.time); // wait it out
u.skill.gainSp(999);
h.step();
assert.deepEqual([u.skill.activations, w.stock], [2, 8], `${S}: 最多拥有8枚`);
@@ -0,0 +1,56 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
test('Dorothy, Pozëmka, and Schwarz DEF reductions fall back to the strongest unexpired application', () => {
const h = makeBattle({
autoFinish: false,
timeLimit: 10,
flags: { dpInit: 0, dpPerSec: 0 },
defs: { enemies: { enemy_dummy: enemyRec({ key: 'enemy_dummy', hp: 1e9, def: 1000, speed: 0 }) } },
units: [
{ uid: 1, diy: { slot: 'chess_char_6_diy1_a', charId: 'char_4048_doroth', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 4 },
{ uid: 2, kind: 'token', tokenId: 'token_10025_doroth_recttp', ownerUid: 1, row: 9, col: 6 },
{ uid: 3, diy: { slot: 'chess_char_6_diy2_a', charId: 'char_4055_bgsnow', skillIndex: 0, uniEquipId: null }, elite: true, row: 10, col: 5 },
{ uid: 4, kind: 'token', tokenId: 'token_10026_bgsnow_subbow', ownerUid: 3, row: 9, col: 5 },
{ uid: 5, diy: { slot: 'chess_char_5_diy1_a', charId: 'char_340_shwaz', skillIndex: 2, uniEquipId: null }, elite: true, row: 9, col: 4 },
],
});
h.step();
// Keep the real kits and projectile travel; prevent extra attacks refreshing their reductions.
for (const unit of h.allies()) unit.atkCd = 100;
const trap = h.unit(2), typewriter = h.unit(4), schwarz = h.unit(5);
const enemy = h.spawn('enemy_dummy', { pos: [9, 6] });
h.step();
assert.equal(enemy.s.def, 700, 'Dorothy S1 applies the initial 30% reduction');
h.run(0.9 - h.b.time);
assert.equal(h.b.forceAttack(typewriter, [enemy]), true);
h.run(1.8 - h.b.time);
schwarz.skill.gainSp(999);
schwarz.atkCd = 0;
h.step();
assert.equal(schwarz.skill.active, true, 'Schwarz S3 guarantees her armor-piercing talent');
schwarz.atkCd = 100;
h.run(2.1 - h.b.time);
const reductions = h.hooksOf('statusApplied').filter((event) => event.target === enemy && event.status === 'defDown');
assert.deepEqual(reductions.map(({ source, value, duration }) => [source.id, value, duration]), [
[trap.id, 0.3, 5],
[typewriter.id, 0.25, 5],
[schwarz.id, 0.2, 5],
]);
for (const [index, expectedTime] of [[0, 0], [1, 1], [2, 2]]) {
assert.ok(Math.abs(reductions[index].t - expectedTime) < 0.1, `reduction ${index + 1} lands near ${expectedTime}s`);
}
assert.equal(enemy.s.def, 700, 'weaker applications do not reduce the active strength');
h.run(5.1 - h.b.time);
assert.equal(enemy.s.def, 750, 'the typewriter reduction survives Dorothy S1 expiring');
h.run(6.1 - h.b.time);
assert.equal(enemy.s.def, 800, 'Schwarz reduction survives the typewriter reduction expiring');
h.run(7.1 - h.b.time);
assert.equal(enemy.s.def, 1000, 'DEF returns to its base value after the final application expires');
assert.deepEqual(h.b.errors, []);
h.invariants();
});
+69 -69
View File
@@ -794,30 +794,30 @@
"total": 117,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 267,
"rngDraws": 261,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":16,"total":117,"perfect":false,"leaks":101,"leaksUncounted":0,"coins":0,"dmg":110512,"bossDmg":0,"heal":0,"deaths":23,"layerGains":{}}},
"players": {"p1":{"killed":16,"total":117,"perfect":false,"leaks":101,"leaksUncounted":0,"coins":0,"dmg":99595,"bossDmg":0,"heal":0,"deaths":23,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_heyak",0,0,141,0,11532,5277,0,17,4],
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_heyak",0,0,142,3,8656,5277,0,6,1],
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_heyak",0,0,142,2,13429,5277,0,8,1],
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_heyak",0,0,133,0,8167,5277,0,12,0],
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_heyak",745.03,1,143,1,9977,4487,0,10,3],
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_heyak",0,0,5,1,7936,1744,0,7,1],
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_heyak",0,0,8,2,22948,1744,0,10,1],
["p1",7,"op","chess_char_5_diy1_b",0,"uniequip_004_heyak",0,0,80,3,4698,3478,0,7,2],
["p1",8,"op","chess_char_5_diy2_b",1,"uniequip_004_heyak",0,0,82,2,7236,3478,0,9,1],
["p1",9,"op","chess_char_5_diy1_b",2,"uniequip_004_heyak",0,0,79,0,7099,3478,0,10,0],
["p1",10,"op","chess_char_6_diy1_a",0,null,0,0,10,1,1508,1380,0,4,1],
["p1",11,"op","chess_char_6_diy2_a",1,null,0,0,75,0,1697,2760,0,3,0],
["p1",12,"op","chess_char_6_diy1_a",2,null,0,0,3,3,19057,1380,0,7,1],
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_heyak",745.03,1,143,2,10896,4487,0,11,4],
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_heyak",0,0,5,1,5394,1744,0,5,1],
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_heyak",0,0,8,2,16520,1744,0,9,1],
["p1",7,"op","chess_char_5_diy1_b",0,"uniequip_004_heyak",0,0,81,3,4698,3478,0,7,2],
["p1",8,"op","chess_char_5_diy2_b",1,"uniequip_004_heyak",0,0,9,2,5566,1739,0,5,1],
["p1",9,"op","chess_char_5_diy1_b",2,"uniequip_004_heyak",0,0,80,0,7934,3478,0,12,0],
["p1",10,"op","chess_char_6_diy1_a",0,null,0,0,75,1,1168,2760,0,4,1],
["p1",11,"op","chess_char_6_diy2_a",1,null,0,0,76,0,1697,2760,0,3,0],
["p1",12,"op","chess_char_6_diy1_a",2,null,0,0,3,3,12593,1380,0,4,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null]
],
"enemies": {"enemy_1005_yokai":[51,11,40,0,33724],"enemy_1005_yokai_2":[54,5,49,20376,35463],"enemy_1112_emppnt":[12,0,12,0,41325]},
"hooks": [1,143,6811,222,233,299,299,0,39,140,15,15,0,7842,192,0,117,101,1,39,0,0,0],
"events": {"atk":346,"deploy":24,"die":39,"dmg":299,"engage":11,"fx":280,"leak":101,"skill":30,"spawn":131,"status":345},
"fx": {"bombard":32,"bombardShell":32,"tornado":192,"tornadoPulse":24},
"snaps": "1059b89c 6d126d74 36b0162d 5f3c3def bea20f53 df2b0823 0facf77d 1bebe14e 3f3f75b7 bb7367a6 b9de1305 563b9e34 4d8ac78e 5cbb2592 9e319bc1 262aa531 28129a72 6a5bd285 565917fe 7062b7e8 ebd4bd17 166fe5f3 abc7bb22"
"enemies": {"enemy_1005_yokai":[51,10,41,0,27983],"enemy_1005_yokai_2":[54,6,48,16577,33757],"enemy_1112_emppnt":[12,0,12,0,37856]},
"hooks": [1,143,6811,196,203,284,284,0,39,140,16,16,0,7583,188,0,117,101,1,39,0,0,0],
"events": {"atk":320,"deploy":24,"die":39,"dmg":284,"engage":11,"fx":198,"leak":101,"skill":32,"spawn":131,"status":299},
"fx": {"bombard":25,"bombardShell":25,"tornado":131,"tornadoPulse":17},
"snaps": "1059b89c 0d0ab14e fff04efc 4b100ad5 3133142e 42f10f08 552cfc97 2c32a76e 2878d371 3d4145d0 7b9c9b96 60fdd264 d214d7b9 22d073f8 5ee9268f fc13e42a f98c6910 8622b2f9 848f0531 0f883096 7cba8373 7565b444 88e454e7"
},
"diy-021": {
"about": "6_diy1_b=heyak/S1 6_diy2_b=heyak/S2 6_diy1_b=heyak/S3 6_diy2_b=heyak/S1/002_heyak 6_diy1_b=heyak/S2/002_heyak 6_diy2_b=heyak/S3/002_heyak 6_diy1_b=heyak/S1/003_heyak 6_diy2_b=heyak/S2/003_heyak 6_diy1_b=heyak/S3/003_heyak 6_diy2_b=heyak/S1/004_heyak 6_diy1_b=heyak/S2/004_heyak 6_diy2_b=heyak/S3/004_heyak",
@@ -2403,15 +2403,15 @@
"bossHpLeft": null,
"rngDraws": 0,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":51,"total":96,"perfect":false,"leaks":45,"leaksUncounted":0,"coins":0,"dmg":294912,"bossDmg":0,"heal":232713,"deaths":15,"layerGains":{}}},
"players": {"p1":{"killed":51,"total":96,"perfect":false,"leaks":45,"leaksUncounted":0,"coins":0,"dmg":294912,"bossDmg":0,"heal":232708,"deaths":15,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_shu",3270,1,118,5,16598,16808,5472,47,2],
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_shu",3270,1,120,1,2287,16353,17674,22,1],
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_shu",3270,1,119,5,30488,15560,32515,90,3],
["p1",4,"op","chess_char_6_diy1_a",0,null,2333.85,1,117,3,24548,27318,9004,88,2],
["p1",5,"op","chess_char_6_diy2_a",1,null,2513,1,99,1,8037,34719,37158,50,2],
["p1",6,"op","chess_char_6_diy1_a",2,null,0,0,104,6,30371,33925,18939,121,1],
["p1",7,"op","chess_char_6_diy2_b",0,"none",0,0,102,6,40221,40728,12451,139,3],
["p1",6,"op","chess_char_6_diy1_a",2,null,0,0,104,6,30371,33925,18935,121,1],
["p1",7,"op","chess_char_6_diy2_b",0,"none",0,0,102,6,40221,40723,12451,139,3],
["p1",8,"op","chess_char_6_diy1_b",1,"none",0,0,95,3,17536,32106,25999,78,2],
["p1",9,"op","chess_char_6_diy2_b",2,"none",0,0,5,3,17720,6178,25141,72,2],
["p1",10,"op","chess_char_6_diy1_b",0,"uniequip_002_shu",0,0,8,0,15926,8337,2961,37,0],
@@ -2421,11 +2421,11 @@
["-","~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_1229_darmy":[27,18,9,42204,86393],"enemy_1229_darmy_2":[57,33,18,151066,218478],"enemy_9007_acelem":[12,0,11,64874,94148]},
"enemies": {"enemy_1229_darmy":[27,18,9,42200,86393],"enemy_1229_darmy_2":[57,33,18,151051,218478],"enemy_9007_acelem":[12,0,11,64889,94148]},
"hooks": [1,120,6900,1655,1655,4214,4508,24466,66,104,21,20,0,16520,0,98,96,38,1,66,0,42,0],
"events": {"atk":1711,"deploy":21,"die":66,"dmg":4508,"engage":12,"fx":146,"heal":369,"leak":38,"skill":41,"spawn":111,"status":104},
"fx": {"beam":16,"burst":42,"healField":46,"teleport":42},
"snaps": "37fbb702 9cbcf13c 0a89a448 750d277b d9592437 862401cb 093e7be6 1be132e4 3c1f9fb3 49416bde 2829f9ff 004d18cc 4a040151 bbe05ad5 c52beaeb 202f1244 3901f434 601d0167 a4eb9728 5fc3c9a2 2267d1c9 4e912d69 fd2bfd2e"
"snaps": "37fbb702 9cbcf13c 0a89a448 750d277b d9592437 be9b7730 7dbdaeb3 ac878763 fcb3fa2d 0d8b02dc 4a580dd9 9469a886 763426bb cd35262b 13c864f5 dcf9735a cfb24522 27704e21 83f9d1ea 25515c68 00b3c803 21fe5f4f 6cf1426c"
},
"diy-060": {
"about": "5_diy1_a=pallas/S1 5_diy2_a=pallas/S2 5_diy1_a=pallas/S3 5_diy2_b=pallas/S1 5_diy1_b=pallas/S2 5_diy2_b=pallas/S3 5_diy1_b=pallas/S1/002_pallas 5_diy2_b=pallas/S2/002_pallas 5_diy1_b=pallas/S3/002_pallas 5_diy2_b=pallas/S1/003_pallas 5_diy1_b=pallas/S2/003_pallas 5_diy2_b=pallas/S3/003_pallas",
@@ -5248,29 +5248,29 @@
"bossHpLeft": null,
"rngDraws": 0,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":0,"total":123,"perfect":false,"leaks":123,"leaksUncounted":0,"coins":0,"dmg":306066,"bossDmg":0,"heal":32670,"deaths":17,"layerGains":{}}},
"players": {"p1":{"killed":0,"total":123,"perfect":false,"leaks":123,"leaksUncounted":0,"coins":0,"dmg":307696,"bossDmg":0,"heal":37911,"deaths":17,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_radian",1158,1,4,0,33930,0,0,127,4],
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_radian",1158,1,5,0,41774,0,0,124,4],
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_radian",1158,1,11,0,60012,0,0,137,3],
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_radian",993,1,13,0,40817,0,0,140,4],
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_radian",993,1,6,0,43040,0,0,121,4],
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_radian",1158,1,4,0,34135,0,0,127,4],
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_radian",1158,1,5,0,41953,0,0,124,4],
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_radian",1158,1,11,0,60038,0,0,137,3],
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_radian",993,1,13,0,40604,0,0,140,4],
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_radian",993,1,6,0,42960,0,0,121,4],
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_radian",993,1,14,0,62016,0,0,132,3],
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,147,0,1742,13278,7559,25,1],
["p1",8,"op","chess_char_5_diy2_a",1,null,0,0,148,0,5534,14951,4707,35,1],
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,149,0,3483,17332,8610,34,1],
["p1",8,"op","chess_char_5_diy2_a",1,null,0,0,147,0,5147,16241,5568,34,1],
["p1",9,"op","chess_char_5_diy1_a",2,null,0,0,130,0,1625,4555,220,8,0],
["p1",10,"op","chess_char_5_diy2_b",0,"none",0,0,149,0,8888,10867,5572,46,1],
["p1",10,"op","chess_char_5_diy2_b",0,"none",0,0,148,0,9046,11213,5918,46,1],
["p1",11,"op","chess_char_5_diy1_b",1,"none",0,0,133,0,3696,5730,435,15,0],
["p1",12,"op","chess_char_5_diy2_b",2,"none",0,0,134,0,2992,10413,14176,43,1],
["p1",12,"op","chess_char_5_diy2_b",2,"none",0,0,134,0,2992,9964,17159,47,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1010_demon":[54,0,53,29077,128397],"enemy_1422_lrsldr_2":[39,0,39,21409,104996],"enemy_1425_lrcmra_2":[30,0,30,9310,72672]},
"hooks": [1,149,6900,1053,1053,986,986,1657,17,139,26,26,0,29616,226,17,123,122,1,18,0,0,0],
"events": {"atk":1053,"deploy":23,"die":17,"dmg":986,"engage":12,"fx":9,"heal":67,"leak":122,"skill":52,"spawn":138,"status":234},
"enemies": {"enemy_1010_demon":[54,0,53,37546,132382],"enemy_1422_lrsldr_2":[39,0,39,17859,103701],"enemy_1425_lrcmra_2":[30,0,30,9630,71612]},
"hooks": [1,149,6900,1072,1072,1001,1001,2279,17,139,26,26,0,30067,226,17,123,122,1,20,0,0,0],
"events": {"atk":1072,"deploy":23,"die":17,"dmg":1001,"engage":12,"fx":9,"heal":71,"leak":122,"skill":52,"spawn":138,"status":234},
"fx": {"shield":8,"undying":1},
"snaps": "422f4ca7 82d85ded 1385aefd a28b5e3b a21d6686 d83862af 7e5ba79d e77bedc9 b9807ab8 28b73dbc 909a17e9 b7d76569 d8a709e8 011fc05a 22f68ccc 8d128c96 d14e6a62 7b6b8033 bc6418b2 000d2486 8070923b 23199b14 c39148dc"
"snaps": "422f4ca7 82d85ded f9912cdd d2129b24 b99085df a39a1c44 8adc939d 29eff8bc 80335586 a23ffeab ddb0ec35 0c8321e8 31df363f 198f71d3 407a9f76 14edeeda 025ea6cd 9741503f 4b8237b4 98be51c7 45f5682e b173bb87 5b2dd744"
},
"diy-123": {
"about": "5_diy1_b=slent2/S1/002_slent2 5_diy2_b=slent2/S2/002_slent2 5_diy1_b=slent2/S3/002_slent2 5_diy2_b=slent2/S1/003_slent2 5_diy1_b=slent2/S2/003_slent2 5_diy2_b=slent2/S3/003_slent2 6_diy1_a=slent2/S1 6_diy2_a=slent2/S2 6_diy1_a=slent2/S3 6_diy2_b=slent2/S1 6_diy1_b=slent2/S2 6_diy2_b=slent2/S3",
@@ -5290,26 +5290,26 @@
"bossHpLeft": null,
"rngDraws": 11,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":12,"total":36,"perfect":false,"leaks":24,"leaksUncounted":0,"coins":0,"dmg":163954,"bossDmg":0,"heal":112142,"deaths":21,"layerGains":{}}},
"players": {"p1":{"killed":12,"total":36,"perfect":false,"leaks":24,"leaksUncounted":0,"coins":0,"dmg":158628,"bossDmg":0,"heal":111125,"deaths":21,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_slent2",0,0,57,0,8245,5577,12709,42,2],
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_slent2",0,0,54,0,22019,9588,6117,65,3],
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_slent2",0,0,56,0,3686,6734,13453,37,1],
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_slent2",0,0,52,0,21984,8140,11594,47,2],
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_slent2",819.96,1,60,0,28128,4330,2400,38,2],
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_slent2",1068.01,1,58,0,17952,2762,11014,44,1],
["p1",7,"op","chess_char_6_diy1_a",0,null,724.73,1,55,0,10681,2855,6823,50,2],
["p1",8,"op","chess_char_6_diy2_a",1,null,0,0,53,0,12848,6062,5102,58,2],
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,48,1,5235,7339,11935,51,1],
["p1",10,"op","chess_char_6_diy2_b",0,"none",0,0,47,0,10824,44211,14116,51,2],
["p1",11,"op","chess_char_6_diy1_b",1,"none",0,0,59,0,13464,3751,878,38,2],
["p1",12,"op","chess_char_6_diy2_b",2,"none",0,0,51,0,8888,50121,16001,60,1]
["p1",1,"op","chess_char_5_diy1_b",0,"uniequip_002_slent2",0,0,57,0,11446,6763,12809,48,2],
["p1",2,"op","chess_char_5_diy2_b",1,"uniequip_002_slent2",1865,1,54,0,21728,5602,7475,71,3],
["p1",3,"op","chess_char_5_diy1_b",2,"uniequip_002_slent2",1116.5,1,56,0,7663,8790,15155,51,2],
["p1",4,"op","chess_char_5_diy2_b",0,"uniequip_003_slent2",0,0,53,0,19296,9125,8872,43,2],
["p1",5,"op","chess_char_5_diy1_b",1,"uniequip_003_slent2",0,0,60,0,23328,4125,1375,31,2],
["p1",6,"op","chess_char_5_diy2_b",2,"uniequip_003_slent2",0,0,58,0,12288,3897,9218,35,1],
["p1",7,"op","chess_char_6_diy1_a",0,null,0,0,55,0,7895,4480,6020,42,2],
["p1",8,"op","chess_char_6_diy2_a",1,null,1392.65,1,52,0,16254,3375,4934,67,2],
["p1",9,"op","chess_char_6_diy1_a",2,null,0,0,50,1,7402,9240,11944,58,1],
["p1",10,"op","chess_char_6_diy2_b",0,"none",0,0,49,0,10824,49145,18299,58,2],
["p1",11,"op","chess_char_6_diy1_b",1,"none",0,0,59,0,12056,4225,1168,35,2],
["p1",12,"op","chess_char_6_diy2_b",2,"none",0,0,51,0,8448,39925,13856,59,1]
],
"enemies": {"enemy_1005_yokai_2":[12,12,0,334,2893],"enemy_1005_yokai_3":[12,0,8,76865,64685],"enemy_9009_acfort":[12,0,11,74270,96377]},
"hooks": [1,60,6900,1019,1019,858,858,1795,33,52,21,21,0,24163,0,0,36,19,1,32,0,0,0],
"events": {"atk":1103,"deploy":24,"die":33,"dmg":858,"engage":12,"fx":49,"heal":250,"leak":19,"skill":42,"spawn":48,"status":28},
"fx": {"beam":46,"undying":3},
"snaps": "a5b373d8 cade2571 25bd2c7f 01adb744 fbb45b57 6848708b 0a96115c 7e128c01 21df8c78 e6903500 2f41b832 aa0ba68b 8641acbf 21932ee2 038f1d76 a983d0f1 3ced4395 63969cf1 ccc7caac 2ce3eb58 ecd3dfd9 4ef03b03 eb5d21bd"
"enemies": {"enemy_1005_yokai_2":[12,12,0,335,2893],"enemy_1005_yokai_3":[12,0,8,77142,51740],"enemy_9009_acfort":[12,0,11,71214,103995]},
"hooks": [1,60,6900,1033,1033,846,846,1544,33,52,22,20,0,24223,0,0,36,19,1,36,0,0,0],
"events": {"atk":1104,"deploy":24,"die":33,"dmg":846,"engage":12,"fx":49,"heal":260,"leak":19,"skill":42,"spawn":48,"status":29},
"fx": {"beam":45,"undying":4},
"snaps": "a5b373d8 cade2571 25bd2c7f 01adb744 fbb45b57 e30ff540 3b72daed 51b4c8b9 06683530 b735ddf5 82033c8c 713018f8 4395803f 890d91a1 2e056bbf 2e2e98df d852cfe2 19169929 86dc378b 79c1efd7 d9de2b5f 90c00c25 ea46d144"
},
"diy-124": {
"about": "6_diy1_b=slent2/S1/002_slent2 6_diy2_b=slent2/S2/002_slent2 6_diy1_b=slent2/S3/002_slent2 6_diy2_b=slent2/S1/003_slent2 6_diy1_b=slent2/S2/003_slent2 6_diy2_b=slent2/S3/003_slent2 5_diy1_a=haruka/S1 5_diy2_a=haruka/S2 5_diy1_a=haruka/S3 5_diy2_b=haruka/S1 5_diy1_b=haruka/S2 5_diy2_b=haruka/S3",
@@ -5329,15 +5329,15 @@
"bossHpLeft": null,
"rngDraws": 0,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":3,"total":147,"perfect":false,"leaks":144,"leaksUncounted":0,"coins":0,"dmg":388624,"bossDmg":0,"heal":186324,"deaths":1,"layerGains":{}}},
"players": {"p1":{"killed":3,"total":147,"perfect":false,"leaks":144,"leaksUncounted":0,"coins":0,"dmg":388624,"bossDmg":0,"heal":186275,"deaths":1,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_6_diy1_b",0,"uniequip_002_slent2",2298,1,6,0,21267,0,30997,124,4],
["p1",2,"op","chess_char_6_diy2_b",1,"uniequip_002_slent2",2298,1,11,0,30498,0,13312,138,5],
["p1",3,"op","chess_char_6_diy1_b",2,"uniequip_002_slent2",2298,1,14,0,17697,0,14062,99,2],
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_slent2",1965,1,4,0,31024,0,5800,77,4],
["p1",4,"op","chess_char_6_diy2_b",0,"uniequip_003_slent2",1965,1,4,0,31024,0,5870,76,4],
["p1",5,"op","chess_char_6_diy1_b",1,"uniequip_003_slent2",1965,1,5,0,52007,0,10668,130,5],
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_slent2",2358,1,7,0,33734,19361,24981,114,2],
["p1",7,"op","chess_char_5_diy1_a",0,null,1556.5,1,12,1,18973,0,14769,124,5],
["p1",6,"op","chess_char_6_diy2_b",2,"uniequip_003_slent2",2358,1,7,0,33734,19312,24981,114,2],
["p1",7,"op","chess_char_5_diy1_a",0,null,1556.5,1,12,1,18973,0,14651,124,5],
["p1",8,"op","chess_char_5_diy2_a",1,null,1556.5,1,15,0,36496,0,16816,75,2],
["p1",9,"op","chess_char_5_diy1_a",2,null,1556.5,1,8,1,31939,48618,9950,102,3],
["p1",10,"op","chess_char_5_diy2_b",0,"none",1531.8,1,10,1,20540,21770,18960,127,5],
@@ -5347,11 +5347,11 @@
["-","~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_1010_demon_2":[36,0,34,129029,143213],"enemy_1071_dftman":[51,1,50,10701,69708],"enemy_1072_dlancer":[60,2,56,49435,175702]},
"hooks": [1,163,6900,1602,1602,1824,1824,1019,4,144,42,38,0,42759,60,66,147,140,1,4,0,0,0],
"events": {"atk":1836,"deploy":13,"die":4,"dmg":1818,"engage":12,"fx":794,"heal":616,"leak":140,"skill":80,"spawn":162,"status":757},
"enemies": {"enemy_1010_demon_2":[36,0,34,129029,143213],"enemy_1071_dftman":[51,1,50,10701,69708],"enemy_1072_dlancer":[60,2,56,49387,175702]},
"hooks": [1,163,6900,1601,1601,1824,1824,1018,4,144,42,38,0,42759,60,66,147,140,1,4,0,0,0],
"events": {"atk":1835,"deploy":13,"die":4,"dmg":1818,"engage":12,"fx":794,"heal":614,"leak":140,"skill":80,"spawn":162,"status":757},
"fx": {"aoe":98,"heal":310,"levitate":60,"shield":326},
"snaps": "1b8091e0 73c802f9 110ffccb 73e97743 2c3d94d2 37aed2ff 5fdabeab 69cb41ab 491632d5 aef350f1 30f8d397 418bbed0 483f862b e8d524ca 1a85cf2d 0771fb68 0ed53d18 5abc2708 a6f0038b 0f8d27f3 f803bfaf 669dc197 e8d45f8a"
"snaps": "1b8091e0 73c802f9 110ffccb 73e97743 2c3d94d2 37aed2ff 5fdabeab 69cb41ab 491632d5 aef350f1 30f8d397 418bbed0 483f862b e8d524ca 1a85cf2d 0771fb68 0ed53d18 5abc2708 83618567 204d2f27 488b9d83 57bbc743 6a9e58ce"
},
"diy-125": {
"about": "5_diy1_b=haruka/S1/002_haruka 5_diy2_b=haruka/S2/002_haruka 5_diy1_b=haruka/S3/002_haruka 6_diy1_a=haruka/S1 6_diy2_a=haruka/S2 6_diy1_a=haruka/S3 6_diy2_b=haruka/S1 6_diy1_b=haruka/S2 6_diy2_b=haruka/S3 6_diy1_b=haruka/S1/002_haruka 6_diy2_b=haruka/S2/002_haruka 6_diy1_b=haruka/S3/002_haruka",
@@ -5822,22 +5822,22 @@
["p1",3,"op","chess_char_6_diy1_a",2,null,1810.9,1,11,0,0,0,53690,111,2],
["p1",4,"op","chess_char_6_diy2_b",0,"none",2005.9,1,13,0,0,0,46002,87,4],
["p1",5,"op","chess_char_6_diy1_b",1,"none",2005.9,1,6,18,704057,0,15788,61,3],
["p1",6,"op","chess_char_6_diy2_b",2,"none",2005.9,1,14,0,0,0,26930,79,3],
["p1",6,"op","chess_char_6_diy2_b",2,"none",2005.9,1,14,0,0,0,27264,78,3],
["p1",7,"op","chess_char_5_diy1_a",0,null,0,0,7,0,0,81970,41023,72,12],
["p1",8,"op","chess_char_5_diy2_a",1,null,1492,1,9,0,0,98191,51694,93,4],
["p1",9,"op","chess_char_5_diy1_a",2,null,217.03,1,10,0,0,107130,60043,84,0],
["p1",9,"op","chess_char_5_diy1_a",2,null,217.03,1,10,0,0,107130,60487,84,0],
["p1",10,"op","chess_char_5_diy2_b",0,"none",1653,1,12,0,0,22049,53056,73,18],
["p1",11,"op","chess_char_5_diy1_b",1,"none",1653,1,15,0,0,64626,18934,43,2],
["p1",11,"op","chess_char_5_diy1_b",1,"none",1653,1,15,0,0,64626,18156,43,2],
["p1",12,"op","chess_char_5_diy2_b",2,"none",1653,1,8,0,0,90649,44508,81,0],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null]
],
"enemies": {"enemy_1006_shield_3":[21,0,16,372723,566446],"enemy_1043_zomsbr":[66,60,5,22690,479046],"enemy_1402_tgshd_2":[36,10,20,69202,794079]},
"hooks": [1,138,6900,1408,1408,1120,1120,13592,71,112,55,52,70,22853,566,8,123,41,1,71,0,0,0],
"events": {"atk":2335,"deploy":12,"die":71,"dmg":1110,"engage":2,"fx":458,"heal":1178,"leak":41,"skill":107,"spawn":138,"status":209},
"hooks": [1,138,6900,1407,1407,1120,1120,13591,71,112,55,52,70,22853,566,8,123,41,1,71,0,0,0],
"events": {"atk":2334,"deploy":12,"die":71,"dmg":1110,"engage":2,"fx":458,"heal":1178,"leak":41,"skill":107,"spawn":138,"status":209},
"fx": {"aoe":70,"expose":272,"healAoe":70,"shield":40,"takeoff":6},
"snaps": "0a2a0a58 9edb0805 ec947f3f 7ffcd830 23c8b514 9268451b 1587cab5 93203dcf 675b3282 b8e42ac4 d194e3af c0d85745 cb282efe f152e307 02944050 4dab08fa 73f1afb0 1a6a7b38 89999054 6738780f f5ef296b 9876bcdc 9e74e0c9"
"snaps": "0a2a0a58 9edb0805 ec947f3f 7ffcd830 23c8b514 9268451b 1587cab5 93203dcf 675b3282 b8e42ac4 d194e3af c0d85745 4520de90 8fa30b22 3c71ad2d 7af3c5a9 1b07ebf9 d6841316 a0cdffb6 e3a8979b d1066696 fe5e3f62 b6c3a091"
},
"diy-134": {
"about": "5_diy1_b=monstr/S1/002_monstr 5_diy2_b=monstr/S2/002_monstr 5_diy1_b=monstr/S3/002_monstr 6_diy1_a=monstr/S1 6_diy2_a=monstr/S2 6_diy1_a=monstr/S3 6_diy2_b=monstr/S1 6_diy1_b=monstr/S2 6_diy2_b=monstr/S3 6_diy1_b=monstr/S1/002_monstr 6_diy2_b=monstr/S2/002_monstr 6_diy1_b=monstr/S3/002_monstr",
@@ -6036,7 +6036,7 @@
],
"enemies": {"enemy_1288_duskls":[27,4,22,19614,114889],"enemy_1288_duskls_2":[57,12,45,7003,313267],"enemy_1292_duskld":[51,9,38,77789,451811]},
"hooks": [1,153,6900,1638,1638,2202,2465,332,35,140,20,18,0,28180,18680,57,135,105,1,84,0,0,6],
"events": {"atk":1961,"deploy":18,"die":35,"dmg":2465,"engage":9,"fx":119,"heal":279,"leak":105,"skill":42,"spawn":147,"status":1300},
"events": {"atk":1961,"deploy":18,"die":35,"dmg":2465,"engage":9,"fx":119,"heal":279,"leak":105,"skill":42,"spawn":147,"status":1298},
"fx": {"aoe":5,"dodge":43,"ember":49,"revive":22},
"snaps": "3e9a4d11 f9a475f7 d909dafe 07c79a9f 76e65988 7dff2527 631c9a48 90e879b4 cfe3ca91 ccb275cc 2445e31f 2132570f ae5cb98a c2376534 c8afbc93 fbbb00d5 98187a92 3228cfb6 f8b78613 5724ac77 b963f388 bb322dda fee7681a"
}
+257 -254
View File
@@ -23,36 +23,36 @@
"killed": 20,
"total": 16,
"unspawned": 0,
"bossHpLeft": 860227,
"rngDraws": 465,
"bossHpLeft": 757974,
"rngDraws": 450,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":20,"total":16,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1654181,"bossDmg":1231515,"heal":19498,"deaths":1,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1792443,"bossDmg":1508258,"heal":66028,"deaths":1,"layerGains":{}}
"p1": {"killed":20,"total":16,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1829160,"bossDmg":1406273,"heal":22241,"deaths":0,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1719718,"bossDmg":1435752,"heal":63732,"deaths":0,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",2234.8,1,13,0,0,13169,12811,0,1],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",1997.61,1,15,0,0,3014,537,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,17,2,25157,2919,1127,5,1],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1470,1,7,5,459003,0,0,71,1],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,9,0,0,1266,0,0,0],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1219.11,1,19,1,107433,13649,2379,0,5],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1442,1,24,9,580897,1076,1076,98,10],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,11,0,0,2042,0,0,0],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,26,0,351605,2090,1133,51,51],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,63,1,78633,3517,100,143,4],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",1317.39,1,13,0,0,14130,13618,0,1],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,15,0,0,3434,1310,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,17,3,36610,2365,439,5,1],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",764.36,1,7,4,473435,974,0,71,1],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,9,0,0,754,0,0,0],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1219.11,1,19,0,105231,11005,2072,0,5],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1442,1,24,8,659439,0,0,99,10],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,11,0,0,0,0,0,0],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,26,1,355289,2720,1289,51,51],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",925.86,1,21,1,122388,6297,3048,213,5],
["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,28,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,22,0,11014,1990,0,10,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4032,1,33,6,846545,20760,0,191,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,16,2,147542,1160,0,0,2],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",2314,1,68,0,0,2314,0,0,0],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3593,1,23,0,231615,2968,0,134,4],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2059.08,1,8,0,0,4454,0,0,0],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",619.79,1,10,0,9427,9216,133,0,0],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2753.55,1,20,0,0,2045,8642,12,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1582.9,1,25,0,254960,2470,0,204,4],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,12,0,289048,4343,0,0,3],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1662.1,1,27,0,0,1420,57252,46,2],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4032,1,33,9,846708,23488,0,191,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2612.11,1,16,1,75440,1160,0,0,1],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",551.11,1,18,0,0,2133,0,0,0],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",3593,1,23,0,231615,1808,0,134,4],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",1743.26,1,8,0,0,3233,0,0,0],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",1395.45,1,10,0,7542,9717,133,0,0],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2753.55,1,20,0,0,1044,6778,10,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1582.9,1,25,0,254960,2921,0,204,4],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",464.73,1,12,0,290148,4047,0,0,3],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1662.1,1,27,0,0,2964,56821,49,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],
@@ -64,25 +64,26 @@
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,4603,2788,336,7,1],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,51,0,0,0,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,6600,0,55,0,3329,0,0,25,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,138,0,60,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,1442,1,64,1,23084,0,0,32,1],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,1442,1,58,0,39947,928,130,53,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,125.89,0,61,0,7780,1864,0,12,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,138,0,62,0,0,0,0,0,null],
["p2","~3","token","token_10019_nearl2_sword",null,null,6127.12,0,65,0,3329,593,0,25,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1442,0,69,0,6040,0,0,12,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,70,0,0,0,0,0,null],
["p2","~4","token","token_10019_nearl2_sword",null,null,6600,1,72,0,3318,0,0,5,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1442,1,74,0,3,0,0,3,null]
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1442,0,67,0,6040,0,0,12,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,69,0,0,0,0,0,null],
["p1","~6","token","token_10030_mlyss_wtrman",null,null,1442,1,71,0,674,0,0,4,null],
["p2","~4","token","token_10019_nearl2_sword",null,null,6600,1,72,0,3318,0,0,5,null]
],
"enemies": {
"enemy_1005_yokai": [4,4,0,0,13759],
"enemy_1006_shield_2": [14,14,0,24795,601974],
"enemy_1006_shield_2": [14,14,0,26659,601974],
"enemy_1061_zomshd": [2,2,0,3356,90856],
"enemy_9013_acstmk": [1,0,0,45645,2739773],
"enemy_9016_acstmr": [8,7,0,0,261]
"enemy_9013_acstmk": [1,0,0,46873,2842026],
"enemy_9016_acstmr": [8,7,0,0,262]
},
"hooks": [1,72,6000,1206,1206,3099,3496,3972,31,41,95,89,9,39824,2903,15,29,0,1,32,0,23,0],
"events": {"atk":1542,"deploy":37,"die":41,"dmg":3212,"engage":18,"fx":658,"heal":385,"skill":184,"spawn":69,"status":710},
"fx": {"airstrike":5,"beam":54,"blessing":1,"blink":1,"bombard":9,"bombardShell":9,"burst":23,"copy":3,"dp":4,"drone":335,"droneLock":17,"drones":3,"finale":2,"firewall":1,"garrisonGrant":1,"lightning":124,"link":1,"lock":9,"radiantSword":4,"shell":8,"slash":2,"snow":5,"split":3,"storm":10,"summon":8,"talent":4,"tide":5,"wanted":7},
"snaps": "44bc2b70 b3daaf96 baf6929d 143779f3 c1387369 80ca050e 4e208395 6728039d 417e6ec7 e83606e2 06a61aec 78738a35 a6c6ae9c c7844f12 05ba7c16 89e6563a eafb1f1c 5f815a0a f048b25a cef59d00"
"hooks": [1,71,6000,1315,1315,3203,3599,4686,29,40,95,90,9,40549,2884,15,29,0,1,29,0,22,0],
"events": {"atk":1651,"deploy":36,"die":40,"dmg":3315,"engage":19,"fx":656,"heal":392,"skill":185,"spawn":70,"status":709},
"fx": {"airstrike":4,"beam":54,"blink":1,"bombard":9,"bombardShell":9,"burst":22,"copy":3,"dp":5,"drone":335,"droneLock":17,"drones":3,"finale":1,"firewall":1,"garrisonGrant":1,"lightning":125,"link":1,"lock":9,"radiantSword":4,"shell":8,"slash":1,"snow":5,"split":4,"storm":10,"summon":8,"talent":4,"tide":5,"wanted":7},
"snaps": "44bc2b70 b3daaf96 baf6929d 143779f3 c1387369 4a4bee86 07473117 ab60a7ca 0ba1a293 14634e1a ed26e3f7 3cd4984e c4289383 b3b5efd4 892e5b14 4d08cebe c4e5e3ee 73e42870 7083cd44 45f8ad22"
},
"boss-boss_1-solo": {
"about": "假想敌:胄 solo pool 1800000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -258,56 +259,56 @@
"round": 14,
"seed": 1428555614,
"waveId": "act1autochess_h07_03",
"time": 193.43,
"ticks": 5803,
"time": 191.33,
"ticks": 5740,
"reason": "cleared",
"killed": 19,
"total": 6,
"unspawned": 0,
"bossHpLeft": 0,
"rngDraws": 1423,
"rngDraws": 1408,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":19,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1572377,"bossDmg":1451138,"heal":8263,"deaths":4,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2612061,"bossDmg":2548862,"heal":37169,"deaths":3,"layerGains":{}}
"p1": {"killed":19,"total":6,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1598736,"bossDmg":1477507,"heal":4950,"deaths":4,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2585697,"bossDmg":2522493,"heal":40534,"deaths":3,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3700,1,7,0,0,0,2781,0,1],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",3700,1,7,0,0,0,2673,0,1],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,11,0,0,0,0,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,9,0,22,1430,182,22,1],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1960.01,1,13,6,882653,4034,609,59,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",949.1,1,17,4,348059,8347,1419,62,3],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",0,0,62,1,26518,15143,2047,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,66,2,174259,8329,1184,52,3],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,5,0,67,0,42,16,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,15,0,41,0,0,11,12],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,23,0,96779,0,0,195,5],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3101,1,9,1,43,3444,505,43,1],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1745.65,1,13,2,914910,4480,538,59,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",252.98,1,17,4,348597,8106,407,62,3],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",0,0,62,0,26511,12270,434,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,66,2,169698,7389,372,48,3],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,5,1,32,0,20,15,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1322,1,15,0,38,0,0,10,10],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,23,0,95529,0,0,193,5],
["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,26,0,0,0,0,0,null],
["p2",12,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",3801.6,1,31,1,463521,0,0,198,3],
["p2",12,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",3801.6,1,31,1,462835,0,0,197,3],
["p2",13,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",2767,1,60,0,25,2767,0,7,1],
["p2",14,"op","chess_char_5_01_b",1,"uniequip_002_excu2",2314,1,55,0,9,3172,480,9,1],
["p2",15,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",6287.75,1,32,0,318017,2551,0,90,3],
["p2",16,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",3175.48,1,16,6,209954,2551,646,71,4],
["p2",17,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2436.5,1,20,0,1410660,6075,1629,347,6],
["p2",18,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2088.9,1,22,0,0,3550,4783,57,1],
["p2",19,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1582.9,1,25,0,77225,1525,0,137,4],
["p2",14,"op","chess_char_5_01_b",1,"uniequip_002_excu2",2314,1,57,0,9,3172,480,9,1],
["p2",15,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",6287.75,1,32,0,315064,2551,0,89,3],
["p2",16,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",3175.48,1,16,7,206450,2551,646,71,4],
["p2",17,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2436.5,1,20,1,1392349,6026,1618,341,6],
["p2",18,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2088.9,1,22,0,0,3550,4850,56,1],
["p2",19,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1582.9,1,25,0,76316,1525,0,135,4],
["p2",20,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1541,1,59,0,103614,1541,0,19,2],
["p2",21,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1662.1,1,18,0,0,2296,29631,59,2],
["p2",21,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1662.1,1,18,0,0,2296,32939,56,2],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,1586.2,1,24,0,43967,0,0,87,1],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,1586.2,1,24,0,43364,0,0,86,1],
["p2","~1","token","token_10019_nearl2_sword",null,null,8280,0,41,0,9679,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1989.96,0,42,0,3,0,0,3,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,49,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,1586.2,0,50,0,4,0,0,4,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,6600,0,51,0,9679,0,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1989.96,0,54,0,0,0,0,0,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,3039.44,0,56,0,4,2481,0,4,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,5520,0,57,0,1,0,0,1,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,4834.05,0,54,0,6,686,0,6,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,5520,0,55,1,1,0,0,1,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1989.96,0,56,0,0,0,0,0,null],
["p2","~3","token","token_10019_nearl2_sword",null,null,6600,0,61,0,9679,0,0,0,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,64,0,0,0,0,0,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,63,0,0,0,0,0,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1586.2,0,65,0,0,0,0,0,null]
],
"enemies": {
@@ -315,21 +316,20 @@
"enemy_1198_msfshu_2": [6,6,0,0,102],
"enemy_1209_sfden_2": [2,2,0,0,154793],
"enemy_1210_msfden_2": [2,2,0,0,386],
"enemy_9021_acduml": [1,1,0,1286,4000000],
"enemy_9023_acdums": [9,7,0,53675,348]
"enemy_9021_acduml": [1,1,0,0,4000000],
"enemy_9023_acdums": [9,7,0,51573,343]
},
"hooks": [1,66,5803,1515,1515,4109,4992,3727,27,41,66,60,358,42326,2036,3,22,0,1,27,0,12,0],
"events": {"atk":1784,"deploy":40,"die":41,"dmg":4700,"engage":20,"fx":1252,"heal":433,"skill":126,"spawn":59,"status":886},
"hooks": [1,66,5740,1515,1515,4047,4898,2384,27,41,64,59,352,41270,2004,3,22,0,1,26,0,9,0],
"events": {"atk":1799,"deploy":40,"die":41,"dmg":4608,"engage":20,"fx":1249,"heal":429,"skill":123,"spawn":59,"status":880},
"fx": {
"airstrike": 128,
"airstrike": 131,
"appear": 2,
"beam": 11,
"blessing": 1,
"blink": 2,
"bombard": 68,
"bombard": 69,
"bombardShell": 69,
"buff": 3,
"burst": 12,
"burst": 9,
"copy": 1,
"disappear": 2,
"displace": 1,
@@ -337,19 +337,19 @@
"drone": 208,
"droneLock": 18,
"drones": 2,
"explode": 455,
"explode": 446,
"finale": 1,
"firewall": 1,
"garrisonGrant": 1,
"lightning": 32,
"lightning": 41,
"link": 7,
"lock": 69,
"lure": 3,
"phase": 43,
"radiantSword": 3,
"scorchBurst": 5,
"scorchBurst": 1,
"slash": 11,
"snow": 45,
"snow": 46,
"split": 4,
"steal": 6,
"storm": 3,
@@ -360,7 +360,7 @@
"tide": 3,
"wanted": 1
},
"snaps": "8c6eb9d3 796da093 9e3c16b7 fa7000d5 b0ba5df0 c13bfdd0 b5a04ea3 1a096951 99f7023b ee7eca69 27c012ea 189a5d94 675b6a80 c16a2d26 fb45381b dc80cb4d 62258e5c f5fd826c 841da98a c3777617"
"snaps": "8c6eb9d3 796da093 9e3c16b7 fa7000d5 926c8ad7 67aaf44d 103ae7ee 039c0ebf 643fb2b7 a0782c00 fecc7c1b cb345ec9 91041fc2 a804912d eb1541bb 9a8fa181 5b07d6da 6e725bcd 0274cab6 809f5b01"
},
"boss-boss_3-solo": {
"about": "假想敌:管 solo pool 2000000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -425,81 +425,81 @@
"time": 200,
"ticks": 6000,
"reason": "forced",
"killed": 14,
"killed": 11,
"total": 14,
"unspawned": 0,
"bossHpLeft": 7089603,
"rngDraws": 790,
"bossHpLeft": 7442429,
"rngDraws": 603,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":14,"total":14,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1232553,"bossDmg":447559,"heal":40856,"deaths":14,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1161150,"bossDmg":862838,"heal":21008,"deaths":14,"layerGains":{}}
"p1": {"killed":11,"total":14,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":0,"dmg":965296,"bossDmg":315248,"heal":24127,"deaths":18,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":false,"leaks":2,"leaksUncounted":0,"coins":0,"dmg":976369,"bossDmg":642323,"heal":8096,"deaths":19,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",0,0,54,0,155980,8234,420,28,2],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",1373.6,1,60,2,14823,8720,1235,14,1],
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,49,0,52926,18466,3851,26,12],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",1187.98,1,52,0,28347,13246,275,19,2],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",911.19,1,55,2,447919,3364,765,111,19],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",298.05,1,61,2,96428,10974,3306,39,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1227.14,1,19,1,132749,4452,2194,209,6],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1176.62,1,57,6,174306,2559,513,45,19],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,50,0,0,4335,26103,44,3],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1293.14,1,15,0,129076,4452,2194,71,3],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2307.16,1,62,0,0,7381,302,0,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",283.08,1,26,0,286831,5830,2567,256,8],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1851.09,1,56,0,25219,5698,400,31,7],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,53,0,105158,8945,1417,187,5],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",2495.49,1,59,0,245655,5833,2774,55,5],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,51,0,203707,3584,462,170,3],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",1613.93,1,14,0,0,4452,3653,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",0,0,20,0,75126,9265,7033,87,5],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",0,0,4,1,66605,3677,1688,66,19],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1224.14,1,58,0,152849,2741,713,72,13],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",1529.8,1,61,4,131692,6704,420,24,1],
["p1",2,"op","chess_char_4_01_b",2,"uniequip_002_rmixer",3310.19,1,60,0,17700,8610,1117,15,2],
["p1",3,"op","chess_char_5_14_b",1,"none",0,0,49,0,44334,11949,2625,19,9],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",0,0,59,0,16983,9696,260,10,1],
["p1",5,"op","chess_char_5_15_b",1,"uniequip_002_thorn2",0,0,55,4,362158,4171,661,91,13],
["p1",6,"op","chess_char_5_20_b",2,"uniequip_002_aglina",0,0,57,1,79306,9954,1941,30,4],
["p1",7,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",0,0,19,0,109465,4097,2126,150,4],
["p1",8,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1058.29,1,64,1,97795,2559,395,36,12],
["p1",9,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",0,0,52,0,0,3684,12457,28,2],
["p1",10,"op","chess_char_4_02_b",2,"uniequip_002_mostma",1695,1,68,0,105862,4163,2126,50,2],
["p2",11,"op","chess_char_4_11_b",1,"uniequip_002_cathy",2188.83,1,66,0,377,7381,183,1,1],
["p2",12,"op","chess_char_4_04_b",1,"uniequip_002_ines",1498.75,1,56,0,184372,6810,464,178,8],
["p2",13,"op","chess_char_4_07_b",1,"uniequip_002_bpipe",1732.75,1,63,0,29484,5698,282,37,9],
["p2",14,"op","chess_char_4_16_b",2,"uniequip_002_texas2",0,0,58,0,105272,10473,1325,156,6],
["p2",15,"op","chess_char_4_09_b",1,"uniequip_002_mizuki",0,0,53,0,214160,6276,721,42,4],
["p2",16,"op","chess_char_4_06_b",1,"uniequip_002_kroos2",0,0,54,0,149179,3584,462,129,2],
["p2",17,"op","chess_char_4_10_b",1,"uniequip_002_aroma",0,0,14,0,0,3064,652,0,0],
["p2",18,"op","chess_char_4_12_b",2,"uniequip_002_glady",1838.43,1,67,0,64191,5913,3287,74,5],
["p2",19,"op","chess_char_4_13_b",1,"uniequip_002_gnosis",1501.32,1,62,1,69313,2825,348,69,20],
["p2",20,"op","chess_char_4_14_b",1,"uniequip_002_lionhd",1105.8,1,65,0,160021,2518,372,71,13],
["p2",21,"token","token_10041_cathy_catsld",null,null,100,1,21,0,0,0,0,0,null],
["p2",22,"token","token_10041_cathy_catsld",null,null,100,1,22,0,0,0,0,0,null]
],
"enemies": {"enemy_1010_demon":[4,4,0,2399,200612],"enemy_1438_dspred":[10,10,0,18904,882694],"enemy_1521_dslily":[1,0,0,107103,1310397]},
"hooks": [1,60,6000,1412,1519,3473,3619,11042,42,44,137,135,14,39147,1338,16,15,0,1,42,0,41,1],
"events": {"atk":1655,"deploy":45,"die":44,"dmg":3602,"engage":17,"fx":970,"heal":108,"skill":272,"spawn":37,"status":1032},
"enemies": {"enemy_1010_demon":[4,4,0,2399,200612],"enemy_1438_dspred":[10,7,3,20089,783482],"enemy_1521_dslily":[1,0,0,91219,957571]},
"hooks": [1,66,6000,1142,1221,2845,2969,7176,48,53,118,115,15,33079,1090,15,15,3,1,51,0,28,1],
"events": {"atk":1370,"deploy":51,"die":50,"dmg":2956,"engage":18,"fx":751,"heal":83,"leak":3,"skill":233,"spawn":37,"status":933},
"fx": {
"aoe": 35,
"appear": 5,
"aoe": 24,
"appear": 9,
"blink": 2,
"burst": 41,
"charge": 7,
"counter": 14,
"crit": 87,
"dilemma": 5,
"disappear": 5,
"displace": 31,
"burst": 28,
"charge": 9,
"counter": 15,
"crit": 62,
"dilemma": 4,
"disappear": 9,
"displace": 28,
"dodge": 1,
"explosion": 13,
"frostNova": 19,
"frostNova": 20,
"grow": 1,
"iceSpike": 163,
"iceSpike": 119,
"overclock": 1,
"pull": 5,
"quadShot": 3,
"ripple": 23,
"rockfall": 18,
"sentry": 1,
"sermon": 1,
"shadowWeave": 1,
"shield": 4,
"soul": 6,
"sp": 18,
"pull": 4,
"quadShot": 2,
"ripple": 14,
"rockfall": 17,
"sentry": 3,
"sermon": 2,
"shadowWeave": 4,
"shield": 5,
"soul": 4,
"sp": 6,
"stealth": 8,
"swordRain": 31,
"swordStorm": 5,
"swordRain": 35,
"swordStorm": 6,
"tentacle": 6,
"tide": 5,
"tornado": 5,
"tornadoPulse": 25,
"tornadoPulse": 20,
"weightless": 4,
"zone": 371
"zone": 256
},
"snaps": "c2a7999d 8a4c19ba a9160fe3 d39e3b10 d5866259 fc603481 26c069da 85780057 16e37456 679dc6cc 77114310 92b28c26 ac45852c 83b4f7a0 bb89c9c7 b1813bcb 8e3bcb45 ff125e0a 8d10ee54 5933334e"
"snaps": "c2a7999d e9680942 e3840c7b c97157bb 354f818f 3033850d 40141175 5bb20676 7decc120 5f1771eb 27360f23 279c31ce f92ff344 600f9424 a1bc5b00 660e7954 a15ab6cb bc529b51 2952b6f1 8bf7f205"
},
"boss-boss_4-solo": {
"about": "盐风主教昆图斯 solo pool 4200000: chess_char_4_17_b chess_char_4_18_b chess_char_4_23_b chess_char_4_19_b chess_char_4_22_b chess_char_4_20_b chess_char_4_21_b chess_char_4_24_b chess_char_4_25_b chess_char_4_26_b",
@@ -549,100 +549,101 @@
"round": 14,
"seed": 2302764647,
"waveId": "act2autochess_h07_05",
"time": 128.53,
"ticks": 3856,
"time": 133.67,
"ticks": 4010,
"reason": "cleared",
"killed": 18,
"killed": 21,
"total": 8,
"unspawned": 0,
"bossHpLeft": 0,
"rngDraws": 1055,
"rngDraws": 1068,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":18,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1203964,"bossDmg":884209,"heal":18083,"deaths":2,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1184403,"bossDmg":675791,"heal":65228,"deaths":1,"layerGains":{}}
"p1": {"killed":21,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1395002,"bossDmg":992386,"heal":27672,"deaths":0,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1093758,"bossDmg":567614,"heal":66447,"deaths":1,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",8066,1,7,0,24421,1782,7413,9,2],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",0,0,12,0,34939,4696,0,8,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3914.04,1,14,0,70411,3401,423,29,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2028.6,1,1,1,563579,2298,1308,41,3],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",2667.54,1,16,1,58403,887,0,15,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",2205.24,1,3,0,14550,8476,757,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1989.96,1,18,1,160434,2156,453,9,6],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2301.84,1,5,0,91022,3776,6991,55,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1824.36,1,20,3,123974,4575,548,20,20],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",0,0,9,0,35679,2432,47,45,2],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",8066,1,7,0,83881,6608,12367,25,2],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",4055.82,1,12,1,89789,2931,42,21,2],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",4279.38,1,14,0,93051,8699,19,39,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",2028.6,1,1,6,488163,2222,958,42,4],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",2667.54,1,16,1,105530,1370,0,25,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",2205.24,1,3,0,19226,8775,554,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",1989.96,1,18,2,217799,4129,453,21,7],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2301.84,1,5,0,69010,3091,11264,39,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",1824.36,1,20,2,130257,4575,548,22,22],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",2612.34,1,9,0,75493,4481,460,93,3],
["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,22,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,10,0,17904,1810,142,31,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4677.12,1,60,4,367230,8033,0,114,5],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",3818.46,1,13,2,155798,9830,0,34,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",462.64,1,15,0,19207,3617,1020,17,1],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",7293.79,1,56,4,211027,13812,0,61,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",6148.88,1,2,2,251161,2788,2336,27,2],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",4486.14,1,17,0,55123,7766,2955,8,1],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2620.62,1,4,0,0,2998,16967,29,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1985.82,1,19,0,21440,1047,0,29,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",2126.58,1,63,1,80761,1541,0,11,2],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",2085.18,1,21,0,0,5444,41951,33,2],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,10,0,12026,1668,0,21,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",4677.12,1,69,5,337357,8718,0,94,7],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",3818.46,1,13,0,138400,4725,0,30,3],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",836.8,1,15,0,33928,3917,1672,28,1],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",6547.48,1,67,2,194567,12526,0,53,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",6148.88,1,2,3,183358,2788,2379,25,2],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",4561.13,1,17,0,74662,7836,3907,8,1],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2620.62,1,4,0,0,2298,9840,19,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",1985.82,1,19,0,26615,1236,0,35,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",2126.58,1,66,0,88959,1541,0,12,2],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",2085.18,1,21,0,0,4690,48648,35,2],
["p2","~1","token","token_10019_nearl2_sword",null,null,1907.57,0,32,0,8360,6372,0,11,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,2301.84,0,36,0,4615,0,0,11,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,1434.97,0,39,0,3325,3275,0,8,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,6171.28,0,46,0,7553,429,0,0,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,48,0,0,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,53,0,707,1668,0,2,null],
["p2","~3","token","token_10019_nearl2_sword",null,null,7526.44,1,58,0,6744,754,0,0,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,4822.74,1,62,0,0,697,0,0,null]
["p1","~1","token","token_10030_mlyss_wtrman",null,null,2301.84,0,36,0,5082,0,0,11,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,2760.11,0,39,0,0,1446,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,6171.28,0,47,0,7553,429,0,0,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,49,0,0,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1820.54,1,57,0,4744,1079,1008,8,1],
["p2","~3","token","token_10019_nearl2_sword",null,null,7543.59,0,61,0,0,736,0,0,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,3148,1,63,0,950,2372,0,2,null]
],
"enemies": {
"enemy_1010_demon_2": [2,2,0,1410,128995],
"enemy_2009_csaudc": [4,4,0,1342,137594],
"enemy_2010_csdcr": [2,2,0,2735,103196],
"enemy_2016_csphtm": [2,2,0,85016,1560000],
"enemy_2017_csphts": [11,10,0,4530,458583]
"enemy_2009_csaudc": [4,4,0,2459,137594],
"enemy_2010_csdcr": [2,2,0,2819,103196],
"enemy_2016_csphtm": [2,2,0,89739,1560000],
"enemy_2017_csphts": [13,13,0,3912,558976]
},
"hooks": [1,61,3856,721,721,2033,2392,2545,25,40,68,57,43,28751,1223,39,21,0,1,23,0,16,87],
"events": {"atk":916,"deploy":40,"die":40,"dmg":2192,"engage":22,"fx":594,"heal":389,"skill":125,"spawn":51,"status":599},
"hooks": [1,67,4010,792,792,2078,2476,3029,25,45,76,63,55,30831,1272,36,23,0,1,25,0,18,82],
"events": {"atk":994,"deploy":44,"die":45,"dmg":2280,"engage":22,"fx":647,"heal":431,"skill":139,"spawn":53,"status":611},
"fx": {
"airstrike": 12,
"blink": 20,
"bombard": 27,
"bombardShell": 27,
"burst": 16,
"cleanse": 2,
"copy": 1,
"dodge": 87,
"dp": 2,
"drone": 115,
"droneLock": 8,
"airstrike": 19,
"blink": 26,
"bombard": 30,
"bombardShell": 31,
"burst": 18,
"cleanse": 1,
"copy": 2,
"dodge": 82,
"dp": 3,
"drone": 126,
"droneLock": 6,
"drones": 2,
"emergency": 1,
"explode": 36,
"extraAttack": 5,
"finale": 4,
"explode": 35,
"extraAttack": 12,
"finale": 3,
"firewall": 2,
"garrisonGrant": 1,
"lightning": 71,
"link": 3,
"lock": 27,
"lightning": 81,
"link": 4,
"lock": 36,
"lure": 2,
"palsy": 1,
"radiantSword": 3,
"scorchBurst": 2,
"shield": 11,
"slash": 37,
"snow": 22,
"split": 2,
"shield": 10,
"slash": 28,
"snow": 27,
"split": 1,
"steal": 1,
"storm": 6,
"storm": 7,
"strike": 3,
"summon": 16,
"sunBurst": 4,
"summon": 18,
"sunBurst": 6,
"talent": 4,
"telegraph": 2,
"tide": 3,
"wanted": 7
"wanted": 8
},
"snaps": "7db84769 ccd2ecf5 49d646d8 1185f93a 64db5dca 1a9e67db 3a4e3045 5b3ba069 0dcc32cf 7d6016ed 4061bb14 3c99a82f a50f8c81"
"snaps": "7db84769 b15c599d 8eb18e6b 43f9d7e1 fe726606 10bd666b a4556d8b f9f483be a0299991 c0d0b79c 1368de98 fd0c398e 99040fb8 01e6b701"
},
"boss-boss_5-solo": {
"about": "卢西恩,“猩红血钻” solo pool 780000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
@@ -1111,67 +1112,68 @@
"killed": 8,
"total": 8,
"unspawned": 0,
"bossHpLeft": 3747749,
"rngDraws": 1619,
"bossHpLeft": 3630612,
"rngDraws": 1662,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1664353,"bossDmg":722232,"heal":20932,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3117516,"bossDmg":1090251,"heal":121649,"deaths":12,"layerGains":{}}
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1794629,"bossDmg":784436,"heal":15524,"deaths":4,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":3269574,"bossDmg":1142028,"heal":135845,"deaths":10,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",5902.25,1,15,0,2317,10287,12324,32,3],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2893.32,1,11,0,20047,281,0,22,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",1834.75,1,17,0,42326,1612,0,24,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",430.19,1,7,1,1229734,2143,0,58,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1329.31,1,9,0,34472,14154,0,46,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",0,0,66,0,35830,9923,233,0,3],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,0,69036,0,0,0,11],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,26444,0,7415,77,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,174620,13925,0,30,30],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,60,0,22306,9359,349,73,2],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",2601.77,1,66,0,0,11336,7438,0,2],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,11,0,25365,227,0,37,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",2535.79,1,17,0,53146,4017,0,51,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",204.28,1,7,1,1342715,2333,0,58,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",1933,1,9,0,33316,9693,0,46,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",954.58,1,55,0,35830,12093,529,0,4],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,1,69554,0,0,0,11],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,25534,0,6790,69,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,149440,13231,0,27,27],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,60,0,52895,9393,349,86,3],
["p1",11,"token","token_10017_skadi2_dedant",null,null,138,0,28,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,24,0,0,1640,318,4,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",2052.07,1,68,4,561677,30058,0,292,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,59,0,341242,18065,0,56,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,65,0,3198,6795,1515,24,1],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",0,0,55,1,514814,17168,0,62,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",942.17,1,8,0,674333,8720,0,73,3],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",0,0,14,0,769354,8634,2644,263,5],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",3038.4,1,20,0,0,0,15762,80,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",0,0,10,0,97174,1439,0,119,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",0,0,62,0,132239,19665,0,24,3],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1183.78,1,25,0,0,15377,101728,82,2],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",2934.34,1,68,4,710575,23146,0,309,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,59,0,232725,17856,0,43,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,65,0,10825,7320,2355,28,2],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",0,0,58,1,487339,27040,0,77,3],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",154.97,1,8,0,687440,9532,0,73,3],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",0,0,14,0,764724,13832,5897,258,5],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",1662.91,1,20,0,0,1375,16017,80,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",0,0,10,0,95777,1439,0,122,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1665.1,1,41,0,217005,24166,0,59,4],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",1592.1,1,25,0,0,15377,111577,83,2],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["-","~4","device","trap_1105_accrate",null,null,100,1,4,0,0,0,0,0,null],
["-","~5","device","trap_1105_accrate",null,null,100,1,5,0,0,0,0,0,null],
["-","~6","device","trap_1105_accrate",null,null,100,1,6,0,0,0,0,0,null],
["p2","~1","token","token_10019_nearl2_sword",null,null,2916.93,0,47,0,6778,5363,0,21,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,48,1,1801,1538,0,22,1],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,207,3142,0,9,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,50,0,463,1322,0,7,null],
["p2","~1","token","token_10019_nearl2_sword",null,null,2330.77,0,47,0,6777,5949,0,20,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,48,0,1485,1538,0,22,1],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,152,3142,0,9,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,50,0,447,1322,0,7,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,3730,0,52,0,4750,270,0,10,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,138,0,57,0,0,0,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,6950.53,0,58,0,0,963,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,61,0,0,385,293,0,1],
["p1","~2","token","token_10040_siege2_vlion",null,null,3897.9,0,63,0,0,137,0,0,null],
["p2","~3","token","token_10019_nearl2_sword",null,null,133.02,0,64,0,16707,6165,0,9,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,67,0,0,0,0,0,null]
["p1","~1","token","token_10017_skadi2_dedant",null,null,138,0,56,0,0,0,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,0,0,57,0,22974,6599,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,61,0,0,155,99,0,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,62,0,0,0,0,0,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,4000,0,63,0,0,0,0,0,null],
["p2","~3","token","token_10019_nearl2_sword",null,null,0,0,64,0,33414,6564,0,17,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,67,0,0,0,0,0,null]
],
"enemies": {
"enemy_1267_nhpbr_2": [6,6,0,1400,141894],
"enemy_9007_acelem": [2,2,0,17143,309587],
"enemy_9017_achunt_2": [2,0,0,58691,1852251],
"enemy_9018_actrpa": [1,0,0,44364,722451],
"enemy_9019_actrpb": [1,0,0,28255,660498],
"enemy_9020_actrpc": [1,0,0,23012,1168947]
"enemy_1267_nhpbr_2": [6,6,0,1500,141894],
"enemy_9007_acelem": [2,2,0,18532,309587],
"enemy_9017_achunt_2": [2,0,0,59249,1969388],
"enemy_9018_actrpa": [1,0,0,55276,817281],
"enemy_9019_actrpb": [1,0,0,32171,721099],
"enemy_9020_actrpc": [1,0,0,23279,1188232]
},
"hooks": [1,66,6000,1682,1682,6586,12669,2825,29,42,95,91,286,44763,2551,19,13,0,1,30,0,16,0],
"events": {"atk":2097,"deploy":47,"die":42,"dmg":12154,"engage":23,"fx":2024,"heal":885,"skill":186,"spawn":52,"status":769},
"hooks": [1,66,6000,1758,1758,7275,13707,2687,28,39,95,89,290,48364,2980,15,13,0,1,36,0,16,0],
"events": {"atk":2126,"deploy":47,"die":39,"dmg":13183,"engage":22,"fx":2170,"heal":969,"skill":184,"spawn":53,"status":815},
"fx": {
"airstrike": 88,
"beam": 27,
"airstrike": 90,
"beam": 30,
"blessing": 1,
"blink": 5,
"bombard": 72,
@@ -1180,37 +1182,38 @@
"camouflage": 1,
"copy": 3,
"dash": 24,
"displace": 20,
"dp": 2,
"drone": 190,
"droneLock": 9,
"drones": 3,
"displace": 14,
"dp": 3,
"drone": 320,
"droneLock": 14,
"drones": 4,
"extraAttack": 7,
"finale": 4,
"firewall": 3,
"firewall": 2,
"garrisonGrant": 1,
"lightning": 117,
"lightning": 124,
"link": 1141,
"lock": 72,
"lure": 2,
"phase": 11,
"phase": 12,
"radiantSword": 3,
"revive": 3,
"scorchBurst": 1,
"shieldBreak": 9,
"shieldBreak": 10,
"slash": 44,
"snow": 26,
"snow": 27,
"split": 2,
"steal": 5,
"storm": 11,
"summon": 5,
"sunBurst": 3,
"talent": 6,
"tide": 3,
"talent": 5,
"tide": 4,
"undying": 1,
"wanted": 6,
"zone": 6
},
"snaps": "2a921421 57801ce9 151fd5ae 991b7440 79722413 1faab651 85db54ec 88d6f82e 6d938e5f fc881a30 9c294f55 4373e468 af475c5f df3e9b70 b0474d4c 538602b8 4e66de79 e70dcab9 481e5d84 e8e351b1"
"snaps": "2a921421 57801ce9 e8f35705 fba04fd7 d2a6440c a3b24855 aaf86c72 6f6a7236 9ab1a1c0 cc7ebf44 74e3130d 4886a375 96efb424 6885b63d 54551891 71bfe69c 67cfb8f5 a4f55f17 8e12a9a1 ca3b950e"
},
"hidden-boss_9-solo": {
"about": "假想敌:铳 solo pool 2800000: chess_char_5_08_b chess_char_5_11_b chess_char_5_05_b chess_char_5_06_b chess_char_5_13_b chess_char_5_07_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b chess_char_5_12_b",
+21 -21
View File
@@ -600,7 +600,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":250,"bossDamage":524924,"dmgDealt":3182383,"gold":139,"itemsEquipped":12,"kills":336,"leaks":7,"lpLost":7,"merges":5,"perfectRounds":12,"refreshes":23},
"stats": {"activatedLayers":250,"bossDamage":511134,"dmgDealt":3170636,"gold":139,"itemsEquipped":12,"kills":335,"leaks":7,"lpLost":7,"merges":5,"perfectRounds":12,"refreshes":23},
"lineup": ["3_19_b@11,7[2_04_e_b]","5_12_a@11,8","4_10_a@10,5[1_02_e_b+2_01_e_a]","6_06_a@9,4","1_08_b@9,5","6_16_a@9,7","5_20_a@9,8[3_05_e_a+3_02_e_b]","3_13_b@9,9"],
"bonds": ["arcaneShip 2/86","miraShip 1/12","preciShip 0/10","sargonShip 0/10","siracusaShip 1/110","skillfulShip 1/36","soloShip 1/6","suntShip 1/0","swiftShip 0/32"]
},
@@ -614,7 +614,7 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":199,"bossDamage":892576,"dmgDealt":3768838,"gold":137,"itemsEquipped":18,"kills":323,"leaks":6,"lpLost":4,"merges":5,"perfectRounds":11,"refreshes":13},
"stats": {"activatedLayers":199,"bossDamage":906366,"dmgDealt":3780569,"gold":137,"itemsEquipped":18,"kills":323,"leaks":6,"lpLost":4,"merges":5,"perfectRounds":11,"refreshes":13},
"lineup": ["4_20_a@12,8[3_06_e_a]","1_17_b@11,7[3_08_e_a]","4_02_b@11,8[3_02_e_a+4_03_e_b]","4_01_b@10,7[4_04_e_a+1_02_e_a]","6_17_a@10,8[4_06_e_a+4_08_e_a]","6_19_a@9,4[1_05_e_a+1_01_e_a]","4_14_a@9,8","5_07_a@9,9"],
"bonds": ["arcaneShip 2/34","kazimierzShip 1/38","kjeragShip 0/8","lateranoShip 0/8","preciShip 2/55","raidShip 1/0","sargonShip 0/10","suntShip 1/0","swiftShip 1/72"]
}
@@ -1107,28 +1107,28 @@
},
{
"round": 10,
"ai_0": [1,21,13,0,2,5,8,"a8c1652a",208,"arcaneShip 2/16, deputShip 0/2, indomShip 1/22, kazimierzShip 1/50, kjeragShip 0/5, preciShip 2/80, suntShip 1/2, swiftShip 1/38",13,25,12,86],
"ai_0": [1,21,13,0,2,5,8,"a8c1652a",199,"arcaneShip 2/16, deputShip 0/2, indomShip 1/22, kazimierzShip 1/45, kjeragShip 0/5, preciShip 2/76, suntShip 1/2, swiftShip 1/38",13,25,12,86],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/38, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, soloShip 0/6, steadShip 0/2, swiftShip 0/22",null,null,null,60],
"ai_2": [1,13,13,0,2,5,8,"b6041c07",181,"arcaneShip 2/27, deputShip 1/26, egirShip 0/10, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, raidShip 0/4, siracusaShip 0/4, skillfulShip 1/10, soloShip 1/24, steadShip 1/68, swiftShip 0/21, victoriaShip 1/26, yanShip 0/2",25,25,0,88],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, preciShip 0/2, sargonShip 0/9, siracusaShip 0/26, skillfulShip 0/4, soloShip 0/9, swiftShip 0/6",null,null,null,63]
},
{
"round": 11,
"ai_0": [1,11,14,0,3,5,8,"7de6a28e",274,"arcaneShip 2/22, deputShip 0/2, indomShip 0/22, kazimierzShip 1/81, kjeragShip 0/16, preciShip 2/98, suntShip 1/2, swiftShip 1/71",10,25,15,101],
"ai_0": [1,11,14,0,3,5,8,"7de6a28e",265,"arcaneShip 2/22, deputShip 0/2, indomShip 0/22, kazimierzShip 1/76, kjeragShip 0/16, preciShip 2/94, suntShip 1/2, swiftShip 1/71",9,25,16,101],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/38, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, soloShip 0/6, steadShip 0/2, swiftShip 0/22",null,null,null,60],
"ai_2": [1,7,14,0,1,5,8,"e9b19bda",237,"arcaneShip 1/37, deputShip 2/32, egirShip 0/10, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, raidShip 0/4, siracusaShip 0/4, skillfulShip 1/16, soloShip 1/30, steadShip 1/82, suntShip 1/4, swiftShip 0/21, victoriaShip 1/36, yanShip 0/4",19,25,6,102],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, preciShip 0/2, sargonShip 0/9, siracusaShip 0/26, skillfulShip 0/4, soloShip 0/9, swiftShip 0/6",null,null,null,63]
},
{
"round": 12,
"ai_0": [1,1,15,0,0,6,8,"2ed340cf",320,"arcaneShip 2/22, deputShip 0/2, indomShip 0/22, kazimierzShip 1/99, kjeragShip 0/16, preciShip 2/111, suntShip 1/2, swiftShip 1/86",8,47,39,115],
"ai_0": [1,1,15,0,0,6,8,"2ed340cf",311,"arcaneShip 2/22, deputShip 0/2, indomShip 0/22, kazimierzShip 1/94, kjeragShip 0/16, preciShip 2/107, suntShip 1/2, swiftShip 1/86",8,47,39,115],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/38, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, soloShip 0/6, steadShip 0/2, swiftShip 0/22",null,null,null,60],
"ai_2": [0,0,13,0,0,5,0,"811c9dc5",0,"arcaneShip 0/55, deputShip 0/36, egirShip 0/10, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, raidShip 0/4, siracusaShip 0/6, skillfulShip 0/26, soloShip 0/34, steadShip 0/98, suntShip 0/6, swiftShip 0/21, victoriaShip 0/42, yanShip 0/6",20,47,27,114],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, preciShip 0/2, sargonShip 0/9, siracusaShip 0/26, skillfulShip 0/4, soloShip 0/9, swiftShip 0/6",null,null,null,63]
},
{
"round": 13,
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/32, deputShip 0/2, indomShip 0/22, kazimierzShip 0/114, kjeragShip 0/19, preciShip 0/125, suntShip 0/2, swiftShip 0/107",11,39,28,126],
"ai_0": [0,0,12,0,0,6,0,"811c9dc5",0,"arcaneShip 0/32, deputShip 0/2, indomShip 0/22, kazimierzShip 0/109, kjeragShip 0/19, preciShip 0/121, suntShip 0/2, swiftShip 0/107",11,39,28,126],
"ai_1": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/4, deputShip 0/8, emptyShip 0/4, kazimierzShip 0/38, kjeragShip 0/2, preciShip 0/2, sargonShip 0/8, siracusaShip 0/6, soloShip 0/6, steadShip 0/2, swiftShip 0/22",null,null,null,60],
"ai_2": [0,0,0,0,0,5,0,"811c9dc5",0,"arcaneShip 0/55, deputShip 0/36, egirShip 0/10, kazimierzShip 0/4, kjeragShip 0/12, lateranoShip 0/4, raidShip 0/4, siracusaShip 0/6, skillfulShip 0/26, soloShip 0/34, steadShip 0/98, suntShip 0/6, swiftShip 0/21, victoriaShip 0/42, yanShip 0/6",null,null,null,114],
"ai_3": [0,0,0,0,0,4,0,"811c9dc5",0,"arcaneShip 0/2, deputShip 0/6, preciShip 0/2, sargonShip 0/9, siracusaShip 0/26, skillfulShip 0/4, soloShip 0/9, swiftShip 0/6",null,null,null,63]
@@ -1145,9 +1145,9 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3420814,"gold":126,"itemsEquipped":13,"kills":113,"leaks":135,"lpLost":54,"merges":3,"perfectRounds":5,"refreshes":15},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":3412269,"gold":126,"itemsEquipped":13,"kills":112,"leaks":136,"lpLost":54,"merges":3,"perfectRounds":5,"refreshes":15},
"lineup": [],
"bonds": ["arcaneShip 0/32","deputShip 0/2","indomShip 0/22","kazimierzShip 0/114","kjeragShip 0/19","preciShip 0/125","suntShip 0/2","swiftShip 0/107"]
"bonds": ["arcaneShip 0/32","deputShip 0/2","indomShip 0/22","kazimierzShip 0/109","kjeragShip 0/19","preciShip 0/121","suntShip 0/2","swiftShip 0/107"]
},
"ai_1": {
"alive": false,
@@ -1265,12 +1265,12 @@
{
"round": 12,
"ai_0": [1,19,12,0,0,6,8,"9673c983",228,"arcaneShip 1/39, deputShip 1/14, emptyShip 1/8, indomShip 0/8, kjeragShip 0/2, preciShip 1/41, siracusaShip 1/73, soloShip 1/25, steadShip 0/2, suntShip 1/8, swiftShip 1/20, victoriaShip 0/8, visiShip 0/8",36,46,10,118],
"ai_1": [1,23,12,0,2,5,8,"7af40c48",229,"arcaneShip 1/30, indomShip 0/8, kjeragShip 0/13, miraShip 1/8, preciShip 1/9, siracusaShip 2/176, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",46,46,0,112]
"ai_1": [1,23,12,0,2,5,8,"7af40c48",221,"arcaneShip 1/30, indomShip 0/8, kjeragShip 0/13, miraShip 1/8, preciShip 1/9, siracusaShip 2/168, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",46,46,0,112]
},
{
"round": 13,
"ai_0": [1,19,12,0,0,6,8,"6b48239d",268,"arcaneShip 1/47, deputShip 1/16, emptyShip 1/10, indomShip 0/8, kjeragShip 0/2, preciShip 1/48, siracusaShip 1/83, soloShip 1/27, steadShip 0/2, suntShip 1/10, swiftShip 1/27, victoriaShip 0/8, visiShip 0/8",28,39,11,128],
"ai_1": [1,23,14,0,0,6,8,"03a221b4",274,"arcaneShip 1/32, indomShip 0/8, kjeragShip 0/13, miraShip 1/11, preciShip 1/9, siracusaShip 2/216, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",39,39,0,126]
"ai_1": [1,23,14,0,0,6,8,"03a221b4",270,"arcaneShip 1/32, indomShip 0/8, kjeragShip 0/13, miraShip 1/11, preciShip 1/9, siracusaShip 2/212, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",39,39,0,126]
}
],
"players": {
@@ -1284,7 +1284,7 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":312,"bossDamage":209415,"dmgDealt":3316971,"gold":143,"itemsEquipped":17,"kills":257,"leaks":34,"lpLost":11,"merges":3,"perfectRounds":8,"refreshes":26},
"stats": {"activatedLayers":312,"bossDamage":190349,"dmgDealt":3265362,"gold":143,"itemsEquipped":17,"kills":253,"leaks":34,"lpLost":11,"merges":3,"perfectRounds":8,"refreshes":26},
"lineup": ["2_14_a@12,3","5_10_a@11,3[1_01_e_a+4_08_e_a]","1_17_a@11,6[1_02_e_a+1_05_e_a]","1_06_b@11,7","4_14_a@10,3[4_06_e_a+4_02_e_a]","4_10_a@10,7[3_11_e_a+6_01_e_a]","1_08_b@10,8[5_02_e_a+4_03_e_b]","3_18_a@9,9[3_04_e_a+4_05_e_a]"],
"bonds": ["arcaneShip 1/57","deputShip 1/18","emptyShip 1/12","indomShip 0/8","kjeragShip 0/2","preciShip 1/57","siracusaShip 1/93","soloShip 1/29","steadShip 0/2","suntShip 1/12","swiftShip 1/34","victoriaShip 0/8","visiShip 0/8"]
},
@@ -1298,9 +1298,9 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":298,"bossDamage":1140585,"dmgDealt":5153261,"gold":136,"itemsEquipped":19,"kills":284,"leaks":7,"lpLost":5,"merges":4,"perfectRounds":11,"refreshes":26},
"stats": {"activatedLayers":294,"bossDamage":1159651,"dmgDealt":5116628,"gold":136,"itemsEquipped":19,"kills":284,"leaks":7,"lpLost":5,"merges":4,"perfectRounds":11,"refreshes":26},
"lineup": ["5_20_a@11,3[3_11_e_a+5_02_e_a]","1_17_a@11,6[5_07_e_a+1_02_e_b]","3_19_a@11,7","6_18_a@10,3[3_06_e_b]","4_10_a@10,7[2_01_e_b+3_04_e_a]","1_08_b@10,8","6_05_a@9,8[1_01_e_b+4_07_e_a]","3_18_a@9,9[4_05_e_a+4_06_e_b]"],
"bonds": ["arcaneShip 1/38","indomShip 0/8","kjeragShip 0/13","miraShip 1/11","preciShip 1/9","siracusaShip 2/234","soloShip 1/0","steadShip 0/8","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
"bonds": ["arcaneShip 1/38","indomShip 0/8","kjeragShip 0/13","miraShip 1/11","preciShip 1/9","siracusaShip 2/230","soloShip 1/0","steadShip 0/8","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
}
}
},
@@ -1314,7 +1314,7 @@
"combat": "client",
"boost": {"lp":400,"layers":300},
"setup": {"stageId":"act1autochess_m02","bossId":"boss_5","hiddenBossId":"boss_10","factions":["TIMES","ELEMENT","DOT"]},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":397,"bossPoolLeft":0,"virtualMs":113700},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":399,"bossPoolLeft":0,"virtualMs":113700},
"rng": {"setup":25,"shop":346,"waves":0,"draft":42,"bots":10350,"meta":3},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
@@ -1377,12 +1377,12 @@
"victory": true,
"roundsPassed": 15,
"eliminatedRound": null,
"lp": 397,
"lp": 399,
"bandId": "band_paganini",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0,
"reward": 425,
"stats": {"activatedLayers":2281,"bossDamage":4580000,"dmgDealt":6374751,"gold":175,"itemsEquipped":25,"kills":376,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":34},
"stats": {"activatedLayers":2281,"bossDamage":4580000,"dmgDealt":6374793,"gold":175,"itemsEquipped":25,"kills":378,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":34},
"lineup": ["4_02_b@12,7[3_08_e_a+1_02_e_b]","6_01_a@12,8[3_03_e_a+3_02_e_a]","6_16_a@11,5","3_18_a@11,8[4_11_e_a+4_05_e_b]","4_10_a@10,4[1_05_e_b]","6_05_a@10,5","4_01_b@10,6[4_06_e_a+4_05_e_a]","1_08_b@9,9[4_10_e_a]"],
"bonds": ["arcaneShip 2/389","deputShip 0/300","egirShip 0/300","emptyShip 0/300","indomShip 0/308","investShip 0/300","kazimierzShip 0/308","kjeragShip 0/300","lateranoShip 1/322","maniShip 0/300","miraShip 0/300","preciShip 1/302","raidShip 0/300","sargonShip 0/300","siracusaShip 1/345","skillfulShip 0/335","soloShip 1/307","steadShip 0/300","suntShip 1/300","swiftShip 1/316","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
}
@@ -1398,7 +1398,7 @@
"combat": "client",
"boost": {"lp":400,"layers":300},
"setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":698,"bossPoolLeft":0,"virtualMs":124650},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":793,"bossPoolLeft":0,"virtualMs":124650},
"rng": {"setup":25,"shop":750,"waves":0,"draft":61,"bots":22165,"meta":198},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
@@ -1474,12 +1474,12 @@
"victory": true,
"roundsPassed": 15,
"eliminatedRound": null,
"lp": 349,
"lp": 397,
"bandId": "band_sarkazb",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 8,
"reward": 425,
"stats": {"activatedLayers":3346,"bossDamage":8584552,"dmgDealt":21757593,"gold":154,"itemsEquipped":21,"kills":287,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":21},
"stats": {"activatedLayers":3346,"bossDamage":7712110,"dmgDealt":20535636,"gold":154,"itemsEquipped":21,"kills":287,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":21},
"lineup": ["4_09_b@11,5[3_11_e_a+3_11_e_b]","6_11_a@11,8","3_13_b@10,3","3_05_b@10,8[1_01_e_a+6_09_e_a]","6_16_a@9,6","6_05_a@9,7[3_02_e_a]","4_14_b@9,8[2_01_e_a+1_02_e_b]","5_07_a@9,9[4_10_e_b]"],
"bonds": ["arcaneShip 1/365","deputShip 0/304","egirShip 1/364","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 0/308","lateranoShip 0/300","maniShip 1/300","miraShip 0/300","preciShip 1/345","raidShip 1/405","sargonShip 1/300","siracusaShip 0/300","skillfulShip 1/338","soloShip 1/307","steadShip 0/338","suntShip 1/300","swiftShip 1/322","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
},
@@ -1488,12 +1488,12 @@
"victory": true,
"roundsPassed": 15,
"eliminatedRound": null,
"lp": 349,
"lp": 396,
"bandId": "band_doberm",
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 8,
"reward": 425,
"stats": {"activatedLayers":2801,"bossDamage":5304736,"dmgDealt":21479720,"gold":191,"itemsEquipped":32,"kills":287,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":55},
"stats": {"activatedLayers":2801,"bossDamage":6180513,"dmgDealt":24059056,"gold":191,"itemsEquipped":32,"kills":287,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":55},
"lineup": ["4_10_a@11,5[3_05_e_a+4_03_e_a]","6_05_a@11,8[4_08_e_a+6_09_e_a]","6_16_a@10,3[3_11_e_a+1_05_e_b]","2_13_a@10,7[4_04_e_a+4_05_e_a]","1_08_b@10,8[4_07_e_b+5_07_e_a]","6_08_a@9,6[2_01_e_b+3_01_e_b]","5_20_a@9,7[1_05_e_a+3_04_e_a]","6_08_a@9,8[3_03_e_a+1_01_e_a]","3_19_b@9,9[2_04_e_a+3_07_e_b]"],
"bonds": ["arcaneShip 1/371","deputShip 0/332","egirShip 0/300","emptyShip 0/308","indomShip 0/300","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 1/334","preciShip 1/311","raidShip 0/300","sargonShip 0/300","siracusaShip 1/465","skillfulShip 1/356","soloShip 1/322","steadShip 0/312","suntShip 1/308","swiftShip 1/334","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
}
+238 -239
View File
@@ -24,47 +24,47 @@
"total": 31,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 63,
"rngDraws": 71,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":26,"total":31,"perfect":false,"leaks":5,"leaksUncounted":0,"coins":8,"dmg":178506,"bossDmg":0,"heal":12998,"deaths":6,"layerGains":{}}},
"players": {"p1":{"killed":26,"total":31,"perfect":false,"leaks":5,"leaksUncounted":0,"coins":8,"dmg":187475,"bossDmg":0,"heal":12128,"deaths":6,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_1_02_a",1,null,3115.05,1,7,0,0,792,0,0,1],
["p1",2,"op","chess_char_1_04_a",1,null,2758.75,1,9,5,21996,544,0,44,2],
["p1",3,"op","chess_char_1_10_a",0,null,1171.3,1,48,1,2993,2147,2862,24,13],
["p1",4,"op","chess_char_1_08_a",1,null,0,0,12,4,27726,8360,0,27,2],
["p1",5,"op","chess_char_1_12_a",1,null,0,0,15,3,21392,5549,1509,23,1],
["p1",6,"op","chess_char_1_01_a",1,null,1403.75,1,4,3,9478,0,0,21,1],
["p1",1,"op","chess_char_1_02_a",1,null,2478.05,1,7,0,0,1435,0,0,1],
["p1",2,"op","chess_char_1_04_a",1,null,146.83,1,9,3,23185,4942,0,47,2],
["p1",3,"op","chess_char_1_10_a",0,null,424.05,1,48,0,3856,3742,3046,28,13],
["p1",4,"op","chess_char_1_08_a",1,null,0,0,12,3,11737,6202,0,18,1],
["p1",5,"op","chess_char_1_12_a",1,null,0,0,15,4,20668,5498,1459,23,1],
["p1",6,"op","chess_char_1_01_a",1,null,558.75,1,4,3,14495,895,0,38,1],
["p1",7,"op","chess_char_1_03_a",1,null,964.63,1,5,0,0,898,0,0,0],
["p1",8,"op","chess_char_1_06_a",1,null,0,0,11,4,14905,2765,5774,37,1],
["p1",9,"op","chess_char_1_07_a",0,null,0,0,13,4,17329,2979,0,27,0],
["p1",10,"op","chess_char_1_09_a",1,null,2032.5,1,6,1,25161,5400,0,23,1],
["p1",11,"op","chess_char_1_13_a",1,null,0,0,14,0,11348,4262,0,25,1],
["p1",12,"op","chess_char_1_14_a",1,null,997.48,1,8,1,26177,2355,0,15,1],
["p1",8,"op","chess_char_1_06_a",1,null,597.56,1,49,1,11022,3038,3553,23,1],
["p1",9,"op","chess_char_1_07_a",0,null,0,0,13,3,14602,2371,0,23,0],
["p1",10,"op","chess_char_1_09_a",1,null,1268.3,1,6,0,34377,6304,0,27,1],
["p1",11,"op","chess_char_1_13_a",1,null,1141.25,1,50,0,9909,3261,0,20,1],
["p1",12,"op","chess_char_1_14_a",1,null,653.21,1,8,9,43626,3827,0,18,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["-","~3","device","trap_1105_accrate",null,null,100,1,3,0,0,0,0,0,null],
["p1","~1","token","char_605_cmedic",null,null,1202,1,16,0,0,0,2853,32,2]
["p1","~1","token","char_605_cmedic",null,null,1202,1,16,0,0,0,4070,32,2]
],
"enemies": {
"enemy_10001_trslim": [1,1,0,9,4320],
"enemy_1005_yokai_3": [1,1,0,6138,28800],
"enemy_1010_demon_2": [1,1,0,3684,14400],
"enemy_1005_yokai_3": [1,1,0,4900,28800],
"enemy_1010_demon_2": [1,1,0,3877,14400],
"enemy_1026_aghost": [1,1,0,0,3648],
"enemy_1112_emppnt_2": [1,0,0,0,8001],
"enemy_1116_liprr": [18,17,0,283,91496],
"enemy_1168_dumage": [1,1,0,1384,4320],
"enemy_1112_emppnt_2": [1,0,0,0,10957],
"enemy_1116_liprr": [18,17,0,382,92690],
"enemy_1168_dumage": [1,1,0,2482,4320],
"enemy_1198_msfshu_2": [1,1,0,0,4],
"enemy_1229_darmy_2": [1,1,0,669,5280],
"enemy_1288_duskls": [1,1,0,251,4805],
"enemy_1288_duskls": [1,1,0,396,4805],
"enemy_1381_winman": [1,1,0,13,5568],
"enemy_1500_skulsr": [1,0,0,720,4020],
"enemy_2004_balloon": [1,0,0,0,3844],
"enemy_1500_skulsr": [1,0,0,478,5672],
"enemy_2004_balloon": [1,0,0,0,7012],
"enemy_9010_acpupp": [1,0,0,0,0]
},
"hooks": [1,48,2700,375,375,667,669,326,32,32,26,24,16,15535,115,29,31,0,1,33,0,0,0],
"events": {"atk":419,"bounty":4,"deploy":14,"die":32,"dmg":669,"engage":10,"fx":76,"heal":322,"skill":51,"spawn":47,"status":128},
"fx": {"aoe":2,"bombard":8,"bombardShell":9,"counter":15,"crit":15,"dp":2,"ember":1,"explode":6,"heal":13,"phase":1,"summon":1,"taunt":1,"telegraph":1,"zone":1},
"snaps": "4fb2b17d c3ab03ad ba4fbecd be5ad383 5c83697d 2c7bd427 95651ac0 d4f0d811 4c663319"
"hooks": [1,50,2700,386,386,697,699,322,32,32,25,23,16,14727,101,32,31,0,1,33,0,0,0],
"events": {"atk":433,"bounty":4,"deploy":16,"die":32,"dmg":699,"engage":10,"fx":76,"heal":318,"skill":49,"spawn":47,"status":113},
"fx": {"aoe":1,"bombard":9,"bombardShell":9,"counter":12,"crit":19,"dp":1,"ember":1,"explode":5,"heal":13,"liberate":1,"phase":1,"summon":1,"taunt":1,"telegraph":1,"zone":1},
"snaps": "4fb2b17d c3ab03ad ba4fbecd 8acf7af3 38caabab f066cab4 47ee9cdf f118754f 0cea250b"
},
"roster-002": {
"about": "pass 0 band 2: chess_char_1_02_b chess_char_1_20_a chess_char_1_04_b chess_char_1_08_b chess_char_1_18_a chess_char_1_19_a chess_char_1_17_a chess_char_1_01_b chess_char_1_03_b chess_char_1_06_b chess_char_1_07_b chess_char_1_09_b",
@@ -1016,40 +1016,40 @@
"time": 230,
"ticks": 6900,
"reason": "timeout",
"killed": 87,
"killed": 92,
"total": 126,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 576,
"rngDraws": 580,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":87,"total":126,"perfect":false,"leaks":39,"leaksUncounted":0,"coins":6,"dmg":4144019,"bossDmg":0,"heal":71137,"deaths":9,"layerGains":{"preciShip":4}}
"p1": {"killed":92,"total":126,"perfect":false,"leaks":34,"leaksUncounted":0,"coins":6,"dmg":4413978,"bossDmg":0,"heal":82323,"deaths":8,"layerGains":{"preciShip":4}}
},
"units": [
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",1353.95,1,115,8,466769,7558,1098,87,4],
["p1",2,"op","chess_char_6_03_a",2,null,1302.82,1,108,2,60032,17828,6704,48,2],
["p1",3,"op","chess_char_6_06_a",2,null,0,0,151,9,418619,19914,422,91,2],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,88,9,226865,26968,5973,297,2],
["p1",5,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,10,0,140799,0,0,47,5],
["p1",6,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,85,1,322660,3069,854,126,3],
["p1",7,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,1,1,29902,1242,1037,14,4],
["p1",8,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,3,0,0,2192,52244,80,4],
["p1",9,"op","chess_char_6_01_a",2,null,1158,1,92,35,805723,1346,119,83,4],
["p1",10,"op","chess_char_6_02_a",2,null,1687,1,7,19,1300307,1242,169,76,3],
["p1",11,"op","chess_char_6_04_a",2,null,1202,1,110,1,71446,10286,1562,0,4],
["p1",12,"op","chess_char_6_05_a",2,null,1215,1,59,0,300898,2559,955,69,5],
["p1",1,"op","chess_char_5_19_b",2,"uniequip_002_mlynar",2863.48,1,115,8,547728,6081,1130,87,4],
["p1",2,"op","chess_char_6_03_a",2,null,2703,1,108,3,96724,21638,8650,75,2],
["p1",3,"op","chess_char_6_06_a",2,null,2223,1,142,8,482800,21116,738,97,3],
["p1",4,"op","chess_char_5_17_b",1,"uniequip_002_f12yin",2738,1,88,6,219859,29728,8163,298,2],
["p1",5,"op","chess_char_5_20_b",2,"uniequip_002_aglina",1382,1,10,0,138378,0,0,46,5],
["p1",6,"op","chess_char_5_21_b",1,"uniequip_002_sntlla",1629,1,85,2,283224,3069,854,125,3],
["p1",7,"op","chess_char_5_22_b",1,"uniequip_002_nymph",1611,1,1,2,30193,1242,1037,14,4],
["p1",8,"op","chess_char_5_23_b",1,"uniequip_002_reckpr",1610,1,3,0,0,2192,58777,82,4],
["p1",9,"op","chess_char_6_01_a",2,null,1158,1,92,34,857246,1346,119,83,4],
["p1",10,"op","chess_char_6_02_a",2,null,1687,1,7,26,1374046,1242,169,76,3],
["p1",11,"op","chess_char_6_04_a",2,null,1202,1,110,2,75912,10286,1730,0,4],
["p1",12,"op","chess_char_6_05_a",2,null,1215,1,59,0,307866,2559,955,69,5],
["p1",13,"token","token_10017_skadi2_dedant",null,null,100,0,13,0,0,0,0,0,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,60,0,0,0,0,0,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,111,0,0,0,0,0,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,148,0,0,0,0,0,null]
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,149,0,0,0,0,0,null]
],
"enemies": {
"enemy_10045_parrot": [1,0,1,0,49131],
"enemy_10098_crhro": [1,1,0,8717,185752],
"enemy_10124_uashld_2": [12,3,6,43100,1102888],
"enemy_10098_crhro": [1,1,0,9588,185752],
"enemy_10124_uashld_2": [12,6,5,49762,1290422],
"enemy_1019_jshoot_2": [1,1,0,1515,16718],
"enemy_1043_zomsbr": [72,56,16,133,1008813],
"enemy_1061_zomshd": [30,21,8,17571,1673893],
"enemy_1043_zomsbr": [72,56,16,151,1022835],
"enemy_1061_zomshd": [30,23,7,16313,1746604],
"enemy_1072_dlancer": [1,1,0,352,37150],
"enemy_1165_duhond": [1,1,0,127,17337],
"enemy_1196_msfyin_2": [1,1,0,0,9],
@@ -1060,10 +1060,10 @@
"enemy_2001_duckmi_2": [1,0,1,0,13530],
"enemy_9006_actoxi": [1,0,1,0,3658]
},
"hooks": [1,150,6900,1031,1111,7275,7468,68979,96,135,42,40,22,39588,1096,52,126,35,1,105,2,6,6],
"events": {"atk":2342,"bounty":3,"deploy":24,"die":100,"dmg":7403,"engage":10,"fx":539,"heal":125,"layer":2,"leak":35,"skill":82,"spawn":142,"status":2634},
"fx": {"aoe":8,"bombard":22,"bombardShell":22,"burst":6,"crit":46,"dodge":6,"explode":20,"firewall":2,"garrison":2,"iceSpike":99,"lightning":141,"lock":22,"lure":3,"revive":1,"snow":73,"soul":1,"sp":2,"storm":5,"talent":2,"telegraph":7,"tide":4,"wanted":40,"weightless":5},
"snaps": "c8992dd8 ef513924 6c381013 9a9944c6 820e2ebc de2648be 660ed54d ac3fc2b6 d1d36151 a862bf89 bd738773 54785a54 82f3a510 45d4ecfb ad4dd27f 4fcc42fe a68d6587 4210cb26 1b937847 5950e793 b1442398 8ea99017 6da4d589"
"hooks": [1,150,6900,1070,1146,7889,8141,69291,100,137,43,40,22,39797,1133,51,126,33,1,109,2,10,7],
"events": {"atk":2528,"bounty":3,"deploy":24,"die":104,"dmg":8076,"engage":10,"fx":554,"heal":128,"layer":2,"leak":33,"skill":83,"spawn":142,"status":2686},
"fx": {"aoe":8,"bombard":22,"bombardShell":22,"burst":10,"crit":39,"dodge":7,"explode":20,"firewall":2,"garrison":2,"iceSpike":99,"lightning":157,"lock":22,"lure":3,"revive":1,"snow":75,"soul":1,"sp":1,"storm":5,"talent":2,"telegraph":7,"tide":4,"wanted":40,"weightless":5},
"snaps": "c8992dd8 ef513924 6c381013 9a9944c6 820e2ebc de2648be 660ed54d ac3fc2b6 4b057061 c76044d5 1e3183dd 69b18662 97b0a95e ea897c2c 02baa76c cc3b79ca 5fe0ed47 a83d31e0 017f9645 bb7476b6 45f4958f 3b6429a1 4e60499c"
},
"roster-017": {
"about": "pass 0 band 6: chess_char_6_15_a chess_char_6_19_a chess_char_6_07_a chess_char_6_17_a chess_char_6_08_a chess_char_6_09_a chess_char_6_11_a chess_char_6_12_a chess_char_6_13_a chess_char_6_14_a chess_char_6_16_a chess_char_6_18_a",
@@ -1153,7 +1153,7 @@
"total": 117,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 483,
"rngDraws": 502,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
@@ -1163,9 +1163,9 @@
"leaks": 0,
"leaksUncounted": 0,
"coins": 12,
"dmg": 1561453,
"dmg": 1560928,
"bossDmg": 0,
"heal": 30059,
"heal": 29516,
"deaths": 1,
"layerGains": {"lateranoShip":12,"preciShip":12,"sargonShip":26}
}
@@ -1174,50 +1174,50 @@
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",4558.4,1,3,0,0,0,0,0,0],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",3620.85,1,6,0,0,0,0,0,0],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",3820.43,1,9,0,0,0,0,0,0],
["p1",4,"op","chess_char_6_20_a",2,null,1523.98,1,4,0,0,0,20701,40,2],
["p1",5,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1811.04,1,7,12,169583,0,0,16,5],
["p1",6,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",4314.46,1,10,5,53428,1556,548,8,2],
["p1",7,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1968.74,1,13,3,31605,8646,121,0,4],
["p1",8,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,15,16,207958,8900,2823,36,7],
["p1",9,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2054.98,1,5,3,77599,0,1994,8,1],
["p1",10,"op","chess_char_6_09_b",0,"uniequip_002_cello",1628.7,1,8,0,14472,0,0,4,4],
["p1",11,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",2332.18,1,11,18,148198,1500,188,142,4],
["p1",12,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",4826.73,1,14,57,774576,7688,564,73,4],
["p1",4,"op","chess_char_6_20_a",2,null,1523.98,1,4,0,0,0,20541,39,2],
["p1",5,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",1811.04,1,7,12,169697,0,0,16,5],
["p1",6,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",4314.46,1,10,5,53428,1556,486,8,2],
["p1",7,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",1968.74,1,13,4,29961,8646,173,0,4],
["p1",8,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",0,0,15,16,204487,8769,2713,36,7],
["p1",9,"op","chess_char_6_08_b",2,"uniequip_002_reed2",2054.98,1,5,3,63902,0,1900,8,1],
["p1",10,"op","chess_char_6_09_b",0,"uniequip_002_cello",1628.7,1,8,0,14472,0,0,5,5],
["p1",11,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",2332.18,1,11,20,138355,1500,50,136,4],
["p1",12,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",4826.73,1,14,58,801146,7190,535,73,4],
["p1",13,"token","token_10017_skadi2_dedant",null,null,100,0,16,0,0,0,0,0,null],
["p1",14,"token","token_10030_mlyss_wtrman",null,null,2540.38,1,12,4,48440,1886,0,89,1],
["p1",14,"token","token_10030_mlyss_wtrman",null,null,2540.38,1,12,1,50974,1886,0,89,1],
["-","~1","device","trap_1105_accrate",null,null,100,1,1,0,0,0,0,0,null],
["-","~2","device","trap_1105_accrate",null,null,100,1,2,0,0,0,0,0,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,37,0,6556,3093,866,11,null],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,37,0,6556,3091,864,11,null],
["p1","~1","token","token_10017_skadi2_dedant",null,null,123.2,0,59,0,0,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1003.7,0,70,2,9216,2375,839,12,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,2008.19,0,99,1,9168,996,777,12,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,1113.22,0,70,0,8395,2266,839,12,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,2008.19,0,99,2,8901,996,777,12,null],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,101,0,0,0,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1171.51,0,131,1,10654,1773,638,12,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,603.66,0,131,1,10654,2271,638,12,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,123.2,1,137,0,0,0,0,0,null]
],
"enemies": {
"enemy_1005_yokai": [1,1,0,0,1327],
"enemy_1009_lurker": [1,1,0,0,3650],
"enemy_1023_jmage": [1,1,0,697,3650],
"enemy_1092_mdgint": [1,1,0,754,165888],
"enemy_1092_mdgint": [1,1,0,751,165888],
"enemy_1166_dusbr": [1,1,0,0,4977],
"enemy_1197_sfshu_2": [1,1,0,0,5557],
"enemy_1198_msfshu_2": [3,3,0,0,21],
"enemy_1210_msfden_2": [1,1,0,0,75],
"enemy_1229_darmy": [27,27,0,0,188117],
"enemy_1229_darmy_2": [57,57,0,0,520059],
"enemy_1275_dwlock_2": [21,21,0,24231,487711],
"enemy_1275_dwlock_2": [21,21,0,23653,487711],
"enemy_1283_sgkill": [1,1,0,2234,19907],
"enemy_1364_spnaxe_2": [1,1,0,917,62641],
"enemy_1364_spnaxe_2": [1,1,0,917,62117],
"enemy_1367_dseed": [2,2,0,0,9953],
"enemy_1439_dslntf": [1,1,0,0,49766],
"enemy_2002_bearmi_2": [1,1,0,0,18248],
"enemy_9008_acbunn": [1,1,0,2434,19907]
"enemy_9008_acbunn": [1,1,0,2771,19907]
},
"hooks": [1,145,5849,513,513,1336,1487,3519,124,130,34,30,37,24659,381,5,122,0,1,124,11,6,0],
"events": {"atk":606,"bounty":6,"deploy":21,"die":130,"dmg":1407,"engage":12,"fx":260,"heal":185,"layer":11,"skill":64,"spawn":145,"status":385},
"fx": {"beam":4,"bombard":31,"bombardShell":31,"burst":6,"camouflage":2,"copy":1,"dp":4,"explode":1,"garrison":8,"lightning":87,"link":1,"lock":37,"lure":2,"scorchBurst":17,"snow":7,"split":4,"storm":7,"summon":2,"talent":1,"tide":4,"wanted":3},
"snaps": "19eb7843 2593e6ad ced96303 9384a4db 45274713 75c80112 cd1ef1a1 6ff0d830 14dbf244 b55f9101 060019d3 8b4d624b 170d8186 4e4fbe70 f9844c3a d8a1becb eec26a32 99466fc4 2cf9d317 4ac77e1f"
"hooks": [1,145,5849,507,507,1319,1469,3395,124,130,35,31,37,24779,382,5,122,0,1,124,11,6,0],
"events": {"atk":597,"bounty":6,"deploy":21,"die":130,"dmg":1389,"engage":12,"fx":256,"heal":181,"layer":11,"skill":66,"spawn":145,"status":387},
"fx": {"beam":4,"bombard":31,"bombardShell":31,"burst":6,"camouflage":2,"copy":1,"dp":4,"explode":1,"garrison":8,"lightning":85,"link":1,"lock":37,"lure":2,"scorchBurst":15,"snow":7,"split":4,"storm":7,"summon":2,"talent":1,"tide":4,"wanted":3},
"snaps": "19eb7843 2593e6ad ced96303 0eb26dfa ec0145aa 242536e7 f29e47e6 6cadbb8b 646db1a6 6781eee8 c12a0126 6a5987c6 6fa6fdf6 4d6c0b9b 4b882db3 b7a0d66f 8fd2cbdc d79f7ad3 8d4ba4b8 2d341070"
},
"roster-019": {
"about": "pass 0 band 7: chess_char_6_15_b chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_6_17_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b",
@@ -1228,14 +1228,14 @@
"round": 13,
"seed": 116466041,
"waveId": "act1autochess_h06",
"time": 197.33,
"ticks": 5920,
"time": 200.93,
"ticks": 6028,
"reason": "cleared",
"killed": 108,
"total": 108,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 755,
"rngDraws": 672,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
@@ -1247,27 +1247,27 @@
"coins": 12,
"dmg": 3002858,
"bossDmg": 0,
"heal": 141615,
"heal": 151109,
"deaths": 0,
"layerGains": {"deputShip":12}
}
},
"units": [
["p1",1,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",6796.8,1,14,25,906021,52826,0,205,3],
["p1",2,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",6087.4,1,9,8,258235,2105,0,8,4],
["p1",3,"op","chess_char_5_01_b",0,"uniequip_003_excu2",4149.22,1,10,0,12329,1953,300,5,0],
["p1",1,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",6263.63,1,14,26,881807,49881,0,208,3],
["p1",2,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",6087.4,1,9,8,257902,2105,0,9,3],
["p1",3,"op","chess_char_5_01_b",0,"uniequip_003_excu2",4049.22,1,10,0,8361,1953,200,3,0],
["p1",4,"op","chess_char_5_05_b",0,"none",12815,1,7,0,0,1000,100,0,0],
["p1",5,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",10599.35,1,24,21,389315,46523,0,97,3],
["p1",6,"op","chess_char_6_13_b",1,"uniequip_002_angel2",4824.27,1,2,30,616754,10442,1358,64,2],
["p1",7,"op","chess_char_6_14_b",2,"uniequip_002_lumen",4177.8,1,3,0,0,0,9404,76,1],
["p1",8,"op","chess_char_6_16_b",2,"uniequip_002_halo2",3165.8,1,4,1,79775,0,0,48,2],
["p1",9,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",3390.2,1,8,17,556140,1000,0,72,3],
["p1",10,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",3475.3,1,12,0,0,27274,130454,67,2],
["p1",5,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",10122.44,1,24,20,425280,48361,0,105,3],
["p1",6,"op","chess_char_6_13_b",1,"uniequip_002_angel2",4873,1,2,28,540782,10588,1317,60,2],
["p1",7,"op","chess_char_6_14_b",2,"uniequip_002_lumen",4177.8,1,3,0,0,0,9754,78,1],
["p1",8,"op","chess_char_6_16_b",2,"uniequip_002_halo2",3165.8,1,4,1,81240,0,0,48,2],
["p1",9,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",3390.2,1,8,17,575679,1000,0,72,3],
["p1",10,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",3475.3,1,12,0,0,37945,139738,69,2],
["p1",11,"op","chess_char_5_02_b",0,"none",2945.8,1,1,0,0,0,0,0,0],
["p1",12,"op","chess_char_5_03_b",0,"uniequip_003_blaze2",3414.4,1,5,4,136237,0,0,68,9],
["p1",12,"op","chess_char_5_03_b",0,"uniequip_003_blaze2",3414.4,1,5,5,170767,0,0,86,11],
["p1","~1","token","token_10019_nearl2_sword",null,null,6491.98,0,27,0,0,1788,0,0,null],
["p1","~2","token","token_10019_nearl2_sword",null,null,6324.82,0,82,1,19770,1916,0,20,null],
["p1","~3","token","token_10019_nearl2_sword",null,null,4911.82,0,115,1,28283,1688,0,9,null]
["p1","~2","token","token_10019_nearl2_sword",null,null,6438.66,0,80,1,21873,1764,0,18,null],
["p1","~3","token","token_10019_nearl2_sword",null,null,2379.55,0,114,2,39169,4220,0,25,null]
],
"enemies": {
"enemy_1005_yokai_2": [1,1,0,51,4443],
@@ -1276,19 +1276,19 @@
"enemy_1112_emppnt": [1,1,0,0,37265],
"enemy_1166_dusbr_2": [1,1,0,30,12613],
"enemy_1198_msfshu": [1,1,0,0,9],
"enemy_1229_darmy": [28,28,0,5500,337106],
"enemy_1229_darmy_2": [57,57,0,33649,898662],
"enemy_1229_darmy": [28,28,0,6548,337106],
"enemy_1229_darmy_2": [57,57,0,32511,898662],
"enemy_1283_sgkill_2": [1,1,0,14433,51598],
"enemy_1367_dseed": [1,1,0,0,8600],
"enemy_1439_dslntf_2": [1,1,0,20025,94596],
"enemy_2003_rockman": [1,1,0,1417,214991],
"enemy_9007_acelem": [12,12,0,39646,1238347],
"enemy_9009_acfort": [1,1,0,4054,68797]
"enemy_9007_acelem": [12,12,0,41756,1238347],
"enemy_9009_acfort": [1,1,0,3643,68797]
},
"hooks": [1,125,5920,886,886,2764,3341,664,108,113,29,26,59,22540,1430,76,108,0,1,108,2,6,0],
"events": {"atk":1273,"bounty":6,"deploy":17,"die":113,"dmg":3139,"engage":10,"fx":524,"heal":534,"layer":2,"skill":55,"spawn":123,"status":434},
"fx": {"airstrike":19,"aoe":9,"beam":20,"blink":2,"bombard":10,"bombardShell":10,"burst":6,"cleanse":2,"displace":4,"drone":341,"droneLock":50,"drones":3,"emergency":1,"extraAttack":1,"finale":4,"garrison":2,"garrisonGrant":1,"link":5,"radiantSword":3,"slash":22,"steal":2,"summon":3,"sunBurst":1,"talent":3},
"snaps": "01a34277 0e7ad63e efd260c2 e5d761b8 09e06c1f e2015a51 1b84ecbd 66a4bb37 d5349acd 92a0e019 e2770fd2 ad03eaae 979013d9 6daad463 50ac1a94 3bd9f1b9 c168ed0a 0203a659 b9f0ce4f c81058f7"
"hooks": [1,125,6028,925,925,2810,3369,649,108,113,30,27,55,22777,1447,79,108,0,1,108,2,7,0],
"events": {"atk":1296,"bounty":6,"deploy":17,"die":113,"dmg":3164,"engage":10,"fx":532,"heal":537,"layer":2,"skill":57,"spawn":123,"status":448},
"fx": {"airstrike":17,"aoe":11,"beam":24,"blink":2,"bombard":10,"bombardShell":10,"burst":7,"cleanse":2,"displace":4,"drone":353,"droneLock":45,"drones":3,"emergency":1,"finale":3,"garrison":2,"garrisonGrant":1,"link":4,"radiantSword":3,"slash":21,"steal":2,"summon":3,"sunBurst":1,"talent":3},
"snaps": "01a34277 0e7ad63e efd260c2 caa9f746 70118f40 b3efb03c de6d6921 949345a2 044a2b7f 708e7b55 4d219240 d5449fa5 16d7d388 9acd59d5 6a170c8e 5cb70efa f2ae5891 7f3e163c 63a11ec8 d5bcbf3a"
},
"roster-020": {
"about": "pass 1 band 1: chess_char_1_02_a chess_char_1_04_a chess_char_1_10_a chess_char_1_08_a chess_char_1_12_a chess_char_1_01_a chess_char_1_03_a chess_char_1_06_a chess_char_1_07_a chess_char_1_09_a chess_char_1_13_a chess_char_1_14_a",
@@ -1626,24 +1626,24 @@
"time": 140,
"ticks": 4200,
"reason": "timeout",
"killed": 18,
"killed": 19,
"total": 71,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 164,
"rngDraws": 169,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":18,"total":71,"perfect":false,"leaks":55,"leaksUncounted":0,"coins":6,"dmg":183879,"bossDmg":0,"heal":1883,"deaths":21,"layerGains":{}}},
"players": {"p1":{"killed":19,"total":71,"perfect":false,"leaks":54,"leaksUncounted":0,"coins":6,"dmg":188151,"bossDmg":0,"heal":1883,"deaths":21,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_3_12_a",0,null,0,0,96,1,7787,4668,1883,19,4],
["p1",2,"op","chess_char_3_16_a",0,null,0,0,6,1,3499,2173,0,12,1],
["p1",3,"op","chess_char_3_18_a",0,null,0,0,99,3,10069,2962,0,12,5],
["p1",1,"op","chess_char_3_12_a",0,null,0,0,97,1,7787,4668,1883,19,4],
["p1",2,"op","chess_char_3_16_a",0,null,1616.62,1,104,1,3518,2729,0,13,1],
["p1",3,"op","chess_char_3_18_a",0,null,0,0,99,4,14899,2962,0,12,9],
["p1",4,"op","chess_char_3_08_a",0,null,0,0,4,4,24642,1594,0,8,1],
["p1",5,"op","chess_char_3_09_a",0,null,1267,1,103,0,13020,1267,0,21,3],
["p1",5,"op","chess_char_3_09_a",0,null,1267,1,103,0,13472,1267,0,22,3],
["p1",6,"op","chess_char_3_10_a",0,null,0,0,87,1,29177,3300,0,17,4],
["p1",7,"op","chess_char_3_11_a",0,null,1316,1,101,1,12760,1316,0,8,1],
["p1",7,"op","chess_char_3_11_a",0,null,1316,1,101,1,11143,1316,0,7,1],
["p1",8,"op","chess_char_3_13_a",0,null,0,0,54,0,4477,4727,0,26,0],
["p1",9,"op","chess_char_3_14_a",0,null,0,0,75,0,4055,2502,0,5,0],
["p1",10,"op","chess_char_3_17_a",0,null,1265.45,1,97,3,44464,3227,0,53,10],
["p1",10,"op","chess_char_3_17_a",0,null,938.6,1,96,2,45052,3553,0,53,10],
["p1",11,"op","chess_char_3_19_a",0,null,0,0,78,1,17767,2878,0,32,0],
["p1",12,"op","chess_char_3_20_a",0,null,0,0,100,1,5659,3654,0,23,1],
["p1",13,"token","token_10028_vigil_wolf",null,null,739.7,0,15,1,4873,969,0,9,null],
@@ -1654,22 +1654,22 @@
],
"enemies": {
"enemy_10042_prtrop": [12,0,12,8673,25590],
"enemy_10042_prtrop_2": [12,0,10,1579,15250],
"enemy_10043_sailor": [39,11,28,18236,171566],
"enemy_10042_prtrop_2": [12,0,10,2136,15720],
"enemy_10043_sailor": [39,12,27,19228,175686],
"enemy_1005_yokai_2": [1,1,0,68,2678],
"enemy_10156_mncrer": [1,1,0,825,6912],
"enemy_1116_liprr_2": [1,1,0,1518,12960],
"enemy_1195_sfyin_2": [1,1,0,147,5357],
"enemy_1196_msfyin_2": [2,2,0,0,10],
"enemy_1251_lysyta_2": [1,1,0,198,5530],
"enemy_1355_mrfly_2": [1,0,1,0,6038],
"enemy_1513_dekght_2": [1,0,1,4110,1400],
"enemy_1355_mrfly_2": [1,0,1,0,5692],
"enemy_1513_dekght_2": [1,0,1,3445,1400],
"enemy_9010_acpupp": [1,0,1,0,1540]
},
"hooks": [1,102,4200,383,383,1447,1501,12,40,94,30,30,0,9243,22,42,73,53,1,43,0,0,0],
"events": {"atk":436,"bounty":3,"deploy":26,"die":41,"dmg":1499,"engage":14,"fx":27,"heal":11,"leak":53,"skill":60,"spawn":90,"status":63},
"fx": {"dp":5,"explode":4,"liberate":1,"phase":1,"revive":4,"shield":2,"sp":1,"strike":1,"summon":2,"telegraph":3,"wolfShadow":1,"wolfShadowLost":2},
"snaps": "1a7d12d1 7955f440 d99919a7 09818fe9 5d85594c eb4b0484 1591b58e c034a33b 2c27bf89 7a9f0e18 14dd1382 37e5dbf7 b8884791 2ad0053c"
"hooks": [1,103,4200,388,388,1457,1512,12,41,94,34,34,0,9236,19,43,73,52,1,45,0,0,0],
"events": {"atk":441,"bounty":3,"deploy":27,"die":42,"dmg":1510,"engage":14,"fx":33,"heal":11,"leak":52,"skill":68,"spawn":90,"status":58},
"fx": {"dp":9,"explode":4,"liberate":1,"phase":1,"revive":4,"shield":2,"sp":3,"strike":1,"summon":2,"telegraph":3,"wolfShadow":1,"wolfShadowLost":2},
"snaps": "1a7d12d1 7955f440 d99919a7 8f3de359 ca4544ed ac2e998f dbf58d8a f303c36c caf236c9 7253f57f c1c36e19 a1a42928 20f83061 78ac55cb"
},
"roster-027": {
"about": "pass 1 band 4: chess_char_3_06_b chess_char_3_12_b chess_char_3_02_b chess_char_3_03_b chess_char_3_04_b chess_char_3_05_b chess_char_3_21_a chess_char_3_01_b chess_char_3_08_b chess_char_3_09_b chess_char_3_10_b chess_char_3_11_b",
@@ -2017,64 +2017,64 @@
"round": 12,
"seed": 3754088384,
"waveId": "act1autochess_h05",
"time": 221.1,
"ticks": 6633,
"time": 219.47,
"ticks": 6584,
"reason": "cleared",
"killed": 47,
"killed": 35,
"total": 167,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 178,
"rngDraws": 182,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 47,
"killed": 35,
"total": 167,
"perfect": false,
"leaks": 120,
"leaks": 132,
"leaksUncounted": 0,
"coins": 2,
"dmg": 403494,
"dmg": 366322,
"bossDmg": 0,
"heal": 47666,
"deaths": 26,
"layerGains": {"indomShip":30}
"heal": 45493,
"deaths": 31,
"layerGains": {"indomShip":35}
}
},
"units": [
["p1",1,"op","chess_char_5_11_a",0,null,0,0,175,1,14689,14105,7927,29,5],
["p1",2,"op","chess_char_5_19_a",0,null,0,0,187,4,37230,11144,3583,17,2],
["p1",3,"op","chess_char_5_13_a",0,null,2313.27,1,197,9,97109,16590,1330,31,1],
["p1",4,"op","chess_char_5_14_a",0,null,719.25,1,198,2,10998,7283,2806,33,3],
["p1",5,"op","chess_char_5_06_b",0,"uniequip_003_etlchi",0,0,10,18,148358,13823,9857,39,9],
["p1",6,"op","chess_char_5_17_a",0,null,2250,1,200,0,15762,27066,1599,29,8],
["p1",7,"op","chess_char_5_12_a",1,null,0,0,3,3,11483,1787,383,10,1],
["p1",8,"op","chess_char_5_15_a",0,null,697.29,1,199,3,15771,1390,235,69,20],
["p1",9,"op","chess_char_5_20_a",0,null,0,0,180,0,7091,3427,103,15,1],
["p1",10,"op","chess_char_5_21_a",0,null,0,0,186,0,4781,7990,180,8,0],
["p1",11,"op","chess_char_5_22_a",0,null,0,0,193,7,37672,2726,252,60,2],
["p1",12,"op","chess_char_5_23_a",0,null,0,0,183,0,0,5809,18905,32,12],
["p1",1,"op","chess_char_5_11_a",0,null,0,0,7,3,18161,12816,11936,37,6],
["p1",2,"op","chess_char_5_19_a",0,null,0,0,181,1,24090,10511,2980,9,1],
["p1",3,"op","chess_char_5_13_a",0,null,0,0,182,4,69847,16642,1033,31,1],
["p1",4,"op","chess_char_5_14_a",0,null,0,0,200,0,2553,6628,1432,9,2],
["p1",5,"op","chess_char_5_06_b",0,"uniequip_003_etlchi",0,0,88,14,152980,19394,11582,36,8],
["p1",6,"op","chess_char_5_17_a",0,null,0,0,198,0,16224,20093,192,30,7],
["p1",7,"op","chess_char_5_12_a",1,null,0,0,3,3,14164,3395,1991,13,2],
["p1",8,"op","chess_char_5_15_a",0,null,0,0,201,5,18715,1950,98,75,20],
["p1",9,"op","chess_char_5_20_a",0,null,0,0,184,0,7091,3502,178,15,1],
["p1",10,"op","chess_char_5_21_a",0,null,0,0,189,0,5581,7990,180,9,0],
["p1",11,"op","chess_char_5_22_a",0,null,0,0,193,5,34367,5558,1385,49,2],
["p1",12,"op","chess_char_5_23_a",0,null,1422,1,202,0,0,6488,11768,20,7],
["-","~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],
["p1","~1","token","token_10015_dusk_drgn",null,null,1715.06,0,38,0,2547,787,505,8,null]
["p1","~1","token","token_10015_dusk_drgn",null,null,1947.38,0,38,0,2547,787,737,8,null]
],
"enemies": {
"enemy_1005_yokai_3": [1,0,1,6184,8755],
"enemy_1009_lurker": [72,17,55,579,106043],
"enemy_10159_mntrjn": [1,0,1,0,25571],
"enemy_1019_jshoot_2": [51,22,29,17053,115897],
"enemy_1005_yokai_3": [1,0,1,5891,11900],
"enemy_1009_lurker": [72,17,55,921,93670],
"enemy_10159_mntrjn": [1,0,1,0,25020],
"enemy_1019_jshoot_2": [51,12,39,19173,94135],
"enemy_1118_lidbox_2": [1,1,0,39,14774],
"enemy_1196_msfyin": [1,1,0,0,3],
"enemy_1252_lysytb_2": [1,0,1,307,12737],
"enemy_1364_spnaxe_2": [1,0,1,5874,7375],
"enemy_1404_msnip": [36,6,30,75984,145857],
"enemy_1516_jakill": [1,0,1,7909,6021],
"enemy_9011_acrefr": [1,0,1,0,9501]
"enemy_1252_lysytb_2": [1,0,1,338,11250],
"enemy_1364_spnaxe_2": [1,0,1,5266,7966],
"enemy_1404_msnip": [36,4,32,77659,149942],
"enemy_1516_jakill": [1,0,1,6133,5143],
"enemy_9011_acrefr": [1,0,1,336,2479]
},
"hooks": [1,200,6633,907,907,3373,3390,8730,73,194,64,64,0,20306,167,105,167,120,1,76,6,0,10],
"events": {"atk":1015,"bounty":1,"deploy":31,"die":74,"dmg":3386,"engage":12,"fx":64,"heal":73,"layer":6,"leak":120,"skill":128,"spawn":182,"status":171},
"fx": {"aoe":3,"crit":2,"dodge":10,"dollEnd":1,"garrison":6,"healAoe":5,"hpShare":1,"palsy":1,"reborn":1,"revive":4,"sp":4,"substitute":3,"summon":1,"swap":2,"zone":20},
"snaps": "0092f82c ff2b39c2 ec300a50 280fca14 776e2fec 99dfc47a 3e6b6598 13676f11 9744c299 6f00c4e5 040ad1c7 d83a4d16 3926c670 f34f4c6b de9b6acf 4477f6ab 44c403f0 c156273c 20f49ab4 dffa19a7 3a1bdade 6cead901 e5e5f0d6"
"hooks": [1,202,6584,853,853,3205,3222,10125,66,199,57,57,0,18157,181,97,167,132,1,69,7,0,8],
"events": {"atk":955,"bounty":1,"deploy":33,"die":67,"dmg":3218,"engage":12,"fx":68,"heal":56,"layer":7,"leak":132,"skill":114,"spawn":182,"status":191},
"fx": {"aoe":2,"crit":4,"dodge":8,"dollEnd":3,"garrison":7,"healAoe":6,"hpShare":1,"palsy":1,"reborn":2,"revive":6,"sp":4,"substitute":3,"summon":1,"zone":20},
"snaps": "0092f82c 0dd327ad 53a3d810 58ba1139 90f0fe1e 37f52c37 cd5a8c36 c2f3acff ca099967 25de291a 2f88cb0f 5966eb8a c429d9ee 2513f9c2 c0f223dd 17c04486 0f59775e 7a41457d 4c1669d7 078e9615 676431e7 b6538f95"
},
"roster-034": {
"about": "pass 1 band 6: chess_char_5_08_b chess_char_5_11_b chess_char_5_19_b chess_char_5_13_b chess_char_5_14_b chess_char_5_07_b chess_char_5_17_b chess_char_5_10_b chess_char_5_12_b chess_char_5_15_b chess_char_5_20_b chess_char_5_21_b",
@@ -2088,47 +2088,47 @@
"time": 230,
"ticks": 6900,
"reason": "timeout",
"killed": 81,
"killed": 86,
"total": 104,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 639,
"rngDraws": 621,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":81,"total":104,"perfect":false,"leaks":23,"leaksUncounted":0,"coins":6,"dmg":4772471,"bossDmg":0,"heal":144036,"deaths":1,"layerGains":{}}},
"players": {"p1":{"killed":86,"total":104,"perfect":false,"leaks":18,"leaksUncounted":0,"coins":8,"dmg":5013885,"bossDmg":0,"heal":163673,"deaths":1,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_5_08_b",0,"uniequip_003_horn",3000,1,7,16,1491888,37623,1068,96,77],
["p1",2,"op","chess_char_5_11_b",0,"uniequip_003_demkni",4064.38,1,10,1,73736,11343,63644,194,34],
["p1",3,"op","chess_char_5_19_b",0,"none",3682,1,11,5,219874,7768,7934,139,5],
["p1",4,"op","chess_char_5_13_b",0,"uniequip_003_ghost2",3448.92,1,13,4,163179,41271,41387,210,5],
["p1",5,"op","chess_char_5_14_b",0,"none",3261,1,15,0,16840,27231,26974,217,13],
["p1",6,"op","chess_char_5_07_b",0,"uniequip_003_surtr",2680,1,8,9,325026,17428,924,114,60],
["p1",7,"op","chess_char_5_17_b",0,"uniequip_003_f12yin",2728,1,100,8,227632,3038,310,107,46],
["p1",8,"op","chess_char_5_10_b",0,"uniequip_003_lisa",1470,1,4,6,170650,0,0,117,6],
["p1",9,"op","chess_char_5_12_b",1,"uniequip_003_dusk",1667,1,5,21,1526499,0,0,91,5],
["p1",10,"op","chess_char_5_15_b",0,"none",1907.5,1,12,1,58730,0,0,166,123],
["p1",11,"op","chess_char_5_20_b",0,"uniequip_003_aglina",1352,1,14,5,202363,1794,1794,138,6],
["p1",12,"op","chess_char_5_21_b",0,"none",3330.35,1,6,5,296053,0,0,58,3],
["p1",1,"op","chess_char_5_08_b",0,"uniequip_003_horn",3000,1,7,25,1690098,32737,709,96,86],
["p1",2,"op","chess_char_5_11_b",0,"uniequip_003_demkni",4176.51,1,10,0,29905,12253,69243,182,38],
["p1",3,"op","chess_char_5_19_b",0,"none",3682,1,11,3,184370,9411,9828,136,5],
["p1",4,"op","chess_char_5_13_b",0,"uniequip_003_ghost2",3050.48,1,13,3,99196,55309,54515,210,5],
["p1",5,"op","chess_char_5_14_b",0,"none",3261,1,15,0,16840,26781,26785,217,15],
["p1",6,"op","chess_char_5_07_b",0,"uniequip_003_surtr",1804.11,1,8,6,337400,27088,605,137,60],
["p1",7,"op","chess_char_5_17_b",0,"uniequip_003_f12yin",214.49,1,97,4,324747,5435,193,119,54],
["p1",8,"op","chess_char_5_10_b",0,"uniequip_003_lisa",1470,1,4,4,161523,0,0,118,6],
["p1",9,"op","chess_char_5_12_b",1,"uniequip_003_dusk",1667,1,5,33,1577956,0,0,89,5],
["p1",10,"op","chess_char_5_15_b",0,"none",1907.5,1,12,0,47882,0,0,166,89],
["p1",11,"op","chess_char_5_20_b",0,"uniequip_003_aglina",1352,1,14,3,205107,1794,1794,138,7],
["p1",12,"op","chess_char_5_21_b",0,"none",3330.35,1,6,5,338859,0,0,67,3],
["-","~1","device","trap_1105_accrate",null,null,100,0,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,8642,46438],
"enemy_10094_crstf": [1,1,0,184,24767],
"enemy_1043_zomsbr": [1,1,0,38,16245],
"enemy_10018_sgrobh": [1,1,0,7268,46438],
"enemy_10094_crstf": [1,1,0,158,24767],
"enemy_1043_zomsbr": [1,1,0,19,16095],
"enemy_1165_duhond_2": [1,1,0,114,24767],
"enemy_1204_msfhu": [1,1,0,0,12],
"enemy_1288_duskls": [1,1,0,3189,30964],
"enemy_1381_winman": [27,24,3,16551,928870],
"enemy_1381_winman_2": [39,28,10,38254,1664969],
"enemy_1387_winshd": [30,23,4,78842,1981114],
"enemy_1425_lrcmra_2": [1,0,1,1682,40234],
"enemy_2009_csaudc": [1,0,1,0,14091]
"enemy_1288_duskls": [1,1,0,1594,30964],
"enemy_1381_winman": [27,23,4,6312,906059],
"enemy_1381_winman_2": [39,29,9,26620,1665434],
"enemy_1387_winshd": [30,26,2,114701,2095022],
"enemy_1425_lrcmra_2": [1,1,0,11329,154793],
"enemy_2009_csaudc": [1,1,0,2690,49534]
},
"hooks": [1,120,6900,2644,2644,3994,3993,8937,82,102,383,379,0,34812,5720,95,104,19,1,83,0,0,8],
"events": {"atk":3066,"bounty":3,"deploy":13,"die":83,"dmg":3982,"engage":12,"fx":371,"heal":34,"leak":19,"skill":762,"spawn":119,"status":794},
"fx": {"aoe":13,"crit":20,"dodge":8,"ember":1,"healAoe":34,"hpShare":5,"sp":147,"zone":143},
"snaps": "de3bd2e2 66e9b470 efceaf9d 858e310e 0f4624f4 096d020b d67a49c3 252c2d4e e44385a0 49f2051f a4fa6a66 dde9cff7 bfc37778 e988e30c 033979c4 7d8f533e d2e87327 180843ba 330c5c8a c36250df a65b77ec 6da88ede b9087957"
"hooks": [1,120,6900,2623,2623,4011,4006,10250,87,103,373,370,0,31826,6617,100,104,15,1,88,0,0,14],
"events": {"atk":3112,"bounty":4,"deploy":13,"die":88,"dmg":3995,"engage":12,"fx":345,"heal":38,"leak":15,"skill":743,"spawn":119,"status":810},
"fx": {"aoe":15,"crit":21,"dodge":14,"ember":1,"healAoe":38,"hpShare":5,"sp":140,"zone":111},
"snaps": "de3bd2e2 efefe0a2 43519431 531ae1eb aa084bc8 507c87eb 0f5120b4 9a46e524 080c06c4 5b040f54 0c7a6be0 c2043b05 ae95d0ab e2cd74c3 5ca51241 aaf4ddc9 9f2d19ce dd8d93e5 7dbbf487 3f8ce0c6 325e24cf 4a98b418 a0f8abef"
},
"roster-035": {
"about": "pass 1 band 6: chess_char_6_03_a chess_char_6_06_a chess_char_6_07_a chess_char_5_22_b chess_char_5_23_b chess_char_6_01_a chess_char_6_02_a chess_char_6_04_a chess_char_6_05_a chess_char_6_08_a chess_char_6_09_a chess_char_6_11_a token_10017_skadi2_dedant token_10030_mlyss_wtrman",
@@ -2264,26 +2264,26 @@
"round": 13,
"seed": 1486935477,
"waveId": "act1autochess_h06",
"time": 216.4,
"ticks": 6492,
"time": 218.23,
"ticks": 6547,
"reason": "cleared",
"killed": 51,
"killed": 47,
"total": 86,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 136,
"rngDraws": 99,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 51,
"killed": 47,
"total": 86,
"perfect": false,
"leaks": 80,
"leaks": 84,
"leaksUncounted": 0,
"coins": 4,
"dmg": 1189101,
"dmg": 1133862,
"bossDmg": 0,
"heal": 36013,
"heal": 36020,
"deaths": 26,
"layerGains": {"sargonShip":6}
}
@@ -2292,15 +2292,15 @@
["p1",1,"op","chess_char_6_06_b",0,"uniequip_003_pepe",0,0,8,1,40812,3361,372,9,3],
["p1",2,"op","chess_char_6_15_b",0,"uniequip_003_qiubai",0,0,6,3,99385,3407,823,19,4],
["p1",3,"op","chess_char_6_07_b",0,"uniequip_003_siege2",0,0,9,0,82237,2661,0,11,4],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,174,0,0,19492,10978,0,4],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,7,287103,1502,0,31,16],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",1663,1,150,0,135318,7690,4364,84,3],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,177,2,23410,4222,166,11,1],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,162,1,63830,6930,1311,115,3],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,173,0,0,18848,10334,0,4],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,7,305406,1502,0,33,17],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",0,0,91,0,85304,7960,4634,55,2],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,176,2,24741,4222,166,12,1],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,161,1,67066,7571,1952,118,2],
["p1",9,"op","chess_char_6_12_b",0,"uniequip_003_rosmon",0,0,64,1,236193,7253,1949,31,9],
["p1",10,"op","chess_char_6_13_b",0,"none",2005,1,171,5,147703,12336,5932,62,6],
["p1",10,"op","chess_char_6_13_b",0,"none",2005,1,171,3,102535,10222,3817,49,5],
["p1",11,"op","chess_char_6_14_b",0,"uniequip_003_lumen",0,0,68,0,0,8834,5745,7,2],
["p1",12,"op","chess_char_6_16_b",0,"none",0,0,173,2,68279,5296,0,20,1],
["p1",12,"op","chess_char_6_16_b",0,"none",1324,1,175,3,86651,8113,2817,28,2],
["p1",13,"token","token_10017_skadi2_dedant",null,null,100,0,17,0,0,0,0,0,null],
["p1",14,"token","token_10030_mlyss_wtrman",null,null,0,0,11,0,1413,4338,2730,14,1],
["-","~1","device","trap_1105_accrate",null,null,100,0,1,0,0,0,0,0,null],
@@ -2310,28 +2310,28 @@
["p1","~1","token","token_10017_skadi2_dedant",null,null,100,0,54,0,0,0,0,0,null],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,77,0,39,1608,0,1,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,106,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,114,0,251,3448,1643,8,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,163,0,3054,1497,0,4,1],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,175,0,0,0,0,0,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1804.5,1,176,0,0,0,0,0,1]
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,117,0,1818,2002,680,5,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,162,0,188,1805,0,5,1],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,0,174,0,0,0,0,0,null],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1804.5,1,177,0,0,0,0,0,1]
],
"enemies": {
"enemy_10042_prtrop": [1,1,0,698,17199],
"enemy_10118_ymgprc": [1,0,1,5032,115511],
"enemy_1045_hammer_2": [18,4,14,12429,339260],
"enemy_1061_zomshd": [60,12,48,33940,682093],
"enemy_1045_hammer_2": [18,4,14,15105,341760],
"enemy_1061_zomshd": [60,12,48,33152,636194],
"enemy_1071_dftman": [1,1,0,0,8026],
"enemy_1172_dugago": [1,0,1,1324,39158],
"enemy_1172_dugago": [1,0,1,2010,40965],
"enemy_1210_msfden_2": [1,0,1,0,55],
"enemy_1269_nhfly": [45,33,12,24133,24943],
"enemy_1321_wdarft": [1,0,1,0,2107],
"enemy_1501_demonk": [1,0,1,12765,34202],
"enemy_1269_nhfly": [45,29,16,21835,14954],
"enemy_1321_wdarft": [1,0,1,0,0],
"enemy_1501_demonk": [1,0,1,12642,34412],
"enemy_2052_smgia": [1,0,1,0,21689]
},
"hooks": [1,175,6492,504,504,2453,2631,28400,82,166,61,59,39,10872,1110,32,131,80,1,57,1,2,0],
"events": {"atk":605,"bounty":2,"deploy":41,"die":86,"dmg":2534,"engage":15,"fx":82,"heal":20,"layer":1,"leak":80,"skill":120,"spawn":156,"status":656},
"fx": {"airstrike":11,"aoe":8,"burst":2,"copy":5,"explode":29,"garrison":1,"heal":4,"healField":2,"link":2,"revive":1,"split":1,"stone":1,"summon":15},
"snaps": "e7f57cb9 5d2f7fdf a34e9406 8a58323a 7d07bc24 abc407ce 319e7ebf 95f85fd9 9a3a7827 7b2bbf31 12b76db4 084b6d1d ed65c31c dbb67422 b1a1c69a 25e7a551 0c4ac123 51e0bc76 65d11431 1570d59d 03fbdd0e 985fd819"
"hooks": [1,175,6547,476,476,2445,2593,26429,78,167,60,57,31,10377,1183,31,131,84,1,56,1,2,0],
"events": {"atk":583,"bounty":2,"deploy":41,"die":83,"dmg":2512,"engage":15,"fx":75,"heal":20,"layer":1,"leak":84,"skill":117,"spawn":156,"status":682},
"fx": {"airstrike":7,"aoe":8,"burst":2,"copy":5,"explode":26,"garrison":1,"heal":4,"healField":2,"link":2,"revive":1,"split":1,"stone":1,"summon":15},
"snaps": "e7f57cb9 5d2f7fdf a34e9406 8a58323a 7d07bc24 abc407ce 319e7ebf 676fd561 9476379e 1f8fd930 05f6ae3b df6151b2 b08fe9cc 7c94318f 7ad4cf67 9dd8fd94 4792f6d4 49d18528 573fd27f 632af769 aeb18398 660866c0"
},
"roster-038": {
"about": "pass 1 band 7: chess_char_5_08_b chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_5_06_b chess_char_6_17_b chess_char_5_07_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b chess_char_5_10_b",
@@ -2948,57 +2948,56 @@
"round": 13,
"seed": 328939409,
"waveId": "act1autochess_h06",
"time": 205.47,
"ticks": 6164,
"time": 204.23,
"ticks": 6127,
"reason": "cleared",
"killed": 34,
"killed": 51,
"total": 86,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 145,
"rngDraws": 277,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 34,
"killed": 51,
"total": 86,
"perfect": false,
"leaks": 53,
"leaks": 35,
"leaksUncounted": 0,
"coins": 2,
"dmg": 1294259,
"coins": 0,
"dmg": 1359874,
"bossDmg": 0,
"heal": 11340,
"deaths": 7,
"layerGains": {"egirShip":5,"swiftShip":78}
"heal": 8755,
"deaths": 5,
"layerGains": {"egirShip":5,"swiftShip":62}
}
},
"units": [
["p1",1,"op","chess_char_6_19_b",1,"none",2858.76,1,99,10,142277,13040,68,27,11],
["p1",2,"op","chess_char_5_13_b",1,"none",3202.92,1,81,9,136856,12306,407,36,2],
["p1",3,"op","chess_char_6_20_b",1,"none",1480.68,1,4,0,0,782,10844,14,5],
["p1",4,"op","chess_char_5_12_b",0,"none",1800.36,1,52,10,581523,1800,0,46,40],
["p1",5,"op","chess_char_5_20_b",2,"none",1292,1,59,0,251904,1314,22,46,5],
["p1",6,"op","chess_char_6_05_b",2,"none",1557.36,1,100,5,181698,5922,0,25,3],
["p1",1,"op","chess_char_6_19_b",1,"none",2858.76,1,64,19,258702,5492,68,46,18],
["p1",2,"op","chess_char_5_13_b",1,"none",3202.92,1,81,1,42223,6473,68,19,1],
["p1",3,"op","chess_char_6_20_b",1,"none",1480.68,1,4,0,0,782,8597,11,5],
["p1",4,"op","chess_char_5_12_b",0,"none",1800.36,1,51,3,264406,2572,0,31,27],
["p1",5,"op","chess_char_5_20_b",2,"none",1292,1,58,2,250771,1314,22,46,5],
["p1",6,"op","chess_char_6_05_b",2,"none",1557.36,1,65,25,543772,6036,0,46,11],
["-","~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_10027_vtsk": [1,0,1,12336,20415],
"enemy_10098_crhro": [1,0,1,4066,17712],
"enemy_1045_hammer_2": [19,4,15,13006,404659],
"enemy_1061_zomshd": [60,28,32,4033,906454],
"enemy_10027_vtsk": [1,0,1,13505,16174],
"enemy_10098_crhro": [1,0,1,3357,9770],
"enemy_1045_hammer_2": [19,11,8,0,443111],
"enemy_1061_zomshd": [60,39,21,3592,888063],
"enemy_1166_dusbr_2": [1,1,0,0,7299],
"enemy_1207_sfji": [1,1,0,0,18248],
"enemy_1208_msfji": [1,0,1,0,0],
"enemy_1292_duskld": [1,0,1,0,2528],
"enemy_1430_lrrook": [1,0,1,0,18234],
"enemy_2017_csphts": [1,0,1,1723,1260]
"enemy_1207_sfji": [1,0,1,0,8734],
"enemy_1292_duskld": [1,0,1,0,2868],
"enemy_1430_lrrook": [1,0,1,0,46482],
"enemy_2017_csphts": [1,0,1,2215,2257]
},
"hooks": [1,103,6164,249,249,2817,2826,30376,41,94,66,66,0,9498,399,25,87,53,1,74,9,1,0],
"events": {"atk":463,"bounty":1,"deploy":13,"die":41,"dmg":2826,"engage":5,"fx":129,"heal":44,"layer":9,"leak":53,"skill":132,"spawn":96,"status":835},
"fx": {"burst":1,"dollEnd":1,"explode":1,"garrison":8,"layer":1,"lightning":58,"shield":5,"slash":11,"sp":28,"storm":3,"substitute":3,"summon":1,"swap":2,"telegraph":1,"weightless":5},
"snaps": "114138f7 91b6a2fa cc32ef2d c73b8b92 cfc9b573 7b3dd24c 0c53a125 af35b361 6fca06d6 2c88ae52 e6b5584c b5db6ac3 b1fe0c6c 1b308f78 394e7946 d6b4633c 08432cc7 25ebf66b 22366e40 448fd11e e921151e"
"hooks": [1,100,6127,238,238,2150,2158,21451,56,91,67,67,0,10009,674,24,86,35,1,83,8,0,1],
"events": {"atk":490,"deploy":11,"die":56,"dmg":2158,"engage":5,"fx":311,"heal":32,"layer":8,"leak":35,"skill":134,"spawn":95,"status":1019},
"fx": {"dodge":1,"dollEnd":1,"explode":2,"garrison":7,"layer":1,"lightning":223,"shield":5,"slash":18,"sp":35,"storm":11,"substitute":1,"telegraph":1,"weightless":5},
"snaps": "114138f7 91b6a2fa 7610acfd 3d21f676 8b814efe f52c75c7 7bf31005 97bafb07 219c56f9 4f52058d fb2c665c 9e07511a c65f6592 2f5b9396 d20a5349 7cff5211 3a98f4c8 d3c065bc 6e8f918b f69dface ad3b6031"
},
"roster-049": {
"about": "pass 3 band 5: chess_char_4_23_b chess_char_3_04_b chess_char_4_09_b chess_char_3_21_b chess_char_4_13_b",
+191
View File
@@ -19,6 +19,9 @@ import { installFakePixi, fakeViewCtx } from './fakepixi.js';
import { presetCamera } from '../../public/js/render/projection.js';
import { UF, ANIM } from '../../shared/constants.js';
import { makeBattle, chessRec } from '../helpers/battleHarness.js';
import * as enemiesMod from '../../server/sim/content/enemies.js';
import { enemyStealthed } from '../../server/sim/targeting.js';
import { flagsOf } from '../../server/sim/snapshot.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const assets = JSON.parse(readFileSync(path.join(ROOT, 'data/assets.json'), 'utf8'));
@@ -133,6 +136,194 @@ async function enemy(id, info = {}) {
return v;
}
describe('骨刺 effective stealth drives A/B clips', () => {
const BONE = 'enemy_9008_acbunn';
const near = (a, b) => assert.ok(Math.abs(a - b) < 1e-6, `${a} ≈ ${b}`);
function arena() {
const h = makeBattle({
content: 'generic', extraContent: [enemiesMod], seed: 7, autoFinish: false,
defs: { chess: { wall: chessRec({ id: 'wall', profession: 'TANK', stats: { atk: 0, maxHp: 1e7, def: 0, res: 0, blockCnt: 3 }, rangeGrid: [[0, 0]], skill: null }) } },
kits: { wall: () => ({ trait: { noAttack: true } }) }, units: [{ chessId: 'wall', row: 9, col: 6 }],
});
h.step();
const e = h.spawn(BONE, { pos: [9, 6], routeIndex: 0, mods: { speedMul: 0 } });
return { h, e, wall: h.unit('wall') };
}
function pose(v, h, e, hidden) {
const flags = flagsOf(e);
assert.equal(enemyStealthed(e), hidden);
assert.equal(!!(flags & UF.STEALTH), hidden);
v.sync({ ...sample(flags), x: e.x, y: e.y }, h.b.time);
assert.equal(v.form, hidden ? null : 'revealed');
assert.equal(clip(v), hidden ? 'Idle_A' : 'Idle_B');
v.fadeIn = 1;
v.update(1 / 60, cam(), h.b.time);
assert.equal(v.root.alpha, hidden ? 0.45 : 1);
}
test('骨刺: actual blocking, blocker stun, reveal and expiry give A → B → A → B → A', async () => {
const { h, e, wall } = arena();
const v = await enemy(BONE);
try {
pose(v, h, e, true);
h.step(2); assert.equal(e.blockedBy, wall); pose(v, h, e, false);
h.b.applyStatus(wall, 'stun', { duration: 30, force: true });
h.step(2); assert.equal(e.blockedBy, null); pose(v, h, e, true);
h.b.addBuff(e, { key: 'test:reveal', duration: 1, flags: { reveal: true } });
pose(v, h, e, false);
h.run(1.1); pose(v, h, e, true);
} finally { v.destroy(); }
});
test('骨刺: overlapping block and reveal keep B until both end', async () => {
const { h, e, wall } = arena();
const v = await enemy(BONE);
try {
h.step(2); assert.equal(e.blockedBy, wall);
h.b.addBuff(e, { key: 'test:reveal', duration: 0.1, flags: { reveal: true } });
pose(v, h, e, false);
h.run(0.2); assert.equal(e.blockedBy, wall); pose(v, h, e, false);
h.b.addBuff(e, { key: 'test:reveal', duration: 1, flags: { reveal: true } });
h.b.applyStatus(wall, 'stun', { duration: 30, force: true });
h.step(2); assert.equal(e.blockedBy, null); pose(v, h, e, false);
h.run(1.1); pose(v, h, e, true);
} finally { v.destroy(); }
});
test('骨刺: switching an ongoing attack maps A 0.8 ↔ B 1.0 without another attack or a repeated-snapshot restart', async () => {
const v = await enemy(BONE);
try {
v.sync(sample(UF.STEALTH), 1);
v.atkInterval = 4;
v.onAttack(null, 1.1);
v.actor.clock = 2;
v.actor.spine.state.tracks[0].trackTime = 0.8;
const last = v.lastAtk, interval = v.atkInterval;
v.imp = { dirty: false };
v.sync(sample(0), 2);
assert.equal(clip(v), 'Attack_B');
near(v.actor.spine.state.tracks[0].trackTime, 1);
near(v.actor.attackUntil, 2.5);
assert.equal(v.imp.dirty, true);
const track = v.actor.spine.state.tracks[0];
v.sync(sample(0), 2.1);
assert.equal(v.actor.spine.state.tracks[0], track);
v.sync(sample(UF.STEALTH), 2.2);
assert.equal(clip(v), 'Attack_A');
near(v.actor.spine.state.tracks[0].trackTime, 0.8);
near(v.actor.attackUntil, 2.7);
assert.equal(v.lastAtk, last);
assert.equal(v.atkInterval, interval);
assert.equal(v.actor.wound, false);
v.imp = null;
} finally { v.imp = null; v.destroy(); }
});
for (const fromB of [false, true]) {
test(`骨刺: ${fromB ? 'B → A truncated' : 'A → B'} wind-up retains its deadline and tail speed`, async () => {
const v = await enemy(BONE);
try {
v.sync(sample(fromB ? 0 : UF.STEALTH), 1);
v.actor.clock = 2;
const lead = fromB ? 0.633 : 0.333;
assert.ok(v.actor.windUp(4, lead));
v.actor.spine.state.tracks[0].trackTime = fromB ? 0.1 : 0.2;
const until = v.actor.windUntil, last = v.lastAtk, interval = v.atkInterval;
v.sync(sample(fromB ? UF.STEALTH : 0), 2);
const track = v.actor.spine.state.tracks[0];
assert.equal(clip(v), fromB ? 'Attack_A' : 'Attack_B');
near(track.trackTime, fromB ? 0 : 0.4);
near(track.timeScale, fromB ? 0.533 / lead : 1);
assert.equal(v.actor.windUntil, until);
assert.equal(v.actor.windTs, 1);
assert.equal(v.actor.wound, true);
near(v.actor.attackUntil, 2 + lead + 1.5 - (fromB ? 0.533 : 0.733));
assert.equal(v.lastAtk, last);
assert.equal(v.atkInterval, interval);
v.sync(sample(fromB ? UF.STEALTH : 0), 2.1);
assert.equal(v.actor.spine.state.tracks[0], track);
v.actor.update(lead + 0.001);
assert.equal(track.timeScale, 1, 'the original tail speed resumes');
} finally { v.destroy(); }
});
}
for (const control of [UF.FROZEN, UF.STUNNED, UF.SLEEP]) {
test(`骨刺: control ${control} keeps the skeleton frozen while B/A attachments are applied immediately`, async () => {
const v = await enemy(BONE);
try {
v.sync(sample(UF.STEALTH | control), 1);
let zeroUpdates = 0;
const update = v.actor.spine.update.bind(v.actor.spine);
v.actor.spine.update = (dt) => { if (dt === 0) zeroUpdates++; update(dt); };
for (const hidden of [false, true]) {
v.sync(sample(control | (hidden ? UF.STEALTH : 0)), 2);
assert.equal(clip(v), hidden ? 'Idle_A' : 'Idle_B');
assert.equal(v.actor.mode, 'stun');
assert.equal(v.actor.frozen, true);
assert.equal(v.actor.spine.state.tracks[0].mixDuration, 0);
}
assert.equal(zeroUpdates, 2);
v.sync(sample(UF.STEALTH, ANIM.MOVE), 3);
assert.equal(v.actor.frozen, false);
assert.equal(clip(v), 'Move_A');
} finally { v.destroy(); }
});
}
test('骨刺: a first snapshot with flags = 0 selects B even though previous flags were also 0', async () => {
const v = await enemy(BONE);
try {
assert.equal(v.flags, 0);
v.sync(sample(0), 1);
assert.equal(v.form, 'revealed');
assert.equal(clip(v), 'Idle_B');
} finally { v.destroy(); }
});
for (const finalFlags of [0, UF.STEALTH, UF.FROZEN]) {
test(`骨刺: delayed model loading applies the latest snapshot (${finalFlags}), including frozen B`, async () => {
const a = store(BONE);
let resolve;
a.spine.acquire = () => new Promise((r) => { resolve = r; });
const v = new UnitView(fakeViewCtx(fake.P, { assets: a, cam }), { id: 9, side: 'enemy', kind: 'enemy', defId: BONE, spine: BONE, maxHp: 1000 });
const proto = fake.P.spine.Spine.prototype, update = proto.update;
let zeroUpdates = 0;
proto.update = function (dt) { if (dt === 0) zeroUpdates++; return update.call(this, dt); };
try {
v.sync(sample(0), 1);
assert.equal(v.actor, null);
assert.equal(v.form, 'revealed');
v.sync(sample(finalFlags), 2);
resolve({ animations: Object.keys(a.spineEntry().animations).map((name) => ({ name })) });
await tick(); await tick();
assert.ok(v.actor);
assert.equal(clip(v), finalFlags & UF.STEALTH ? 'Idle_A' : 'Idle_B');
assert.ok(zeroUpdates > 0, 'the loaded pose is applied at zero time');
assert.equal(v.actor.frozen, !!(finalFlags & UF.FROZEN));
} finally { proto.update = update; v.destroy(); }
});
}
for (const hidden of [true, false]) {
test(`骨刺: ${hidden ? 'A' : 'B'} idle, move and death; a DIE snapshot selects its form before dying`, async () => {
const flags = hidden ? UF.STEALTH : 0, suffix = hidden ? 'A' : 'B';
const v = await enemy(BONE), w = await enemy(BONE);
try {
v.sync(sample(flags, ANIM.MOVE), 1); assert.equal(clip(v), `Move_${suffix}`);
v.sync(sample(flags, ANIM.IDLE), 2); assert.equal(clip(v), `Idle_${suffix}`);
v.die(); assert.equal(clip(v), `Die_${suffix}`);
w.sync(sample(hidden ? 0 : UF.STEALTH), 1);
w.sync(sample(flags, ANIM.DIE), 2); assert.equal(clip(w), `Die_${suffix}`);
w.sync(sample(hidden ? 0 : UF.STEALTH, ANIM.DIE), 3);
frames(w, 10);
assert.equal(clip(w), `Die_${suffix}`, 'death keeps its chosen form');
assert.equal(w.form, hidden ? null : 'revealed');
} finally { v.destroy(); w.destroy(); }
});
}
});
describe('转译基底·α forms (user report after 0.1.0, #5)', () => {
const TR = 'enemy_10081_mpplai';
test('every clip of the three forms exists in the skeleton; the change clips last the official 2 s; the manifest keeps the original form', () => {
+30
View File
@@ -66,6 +66,36 @@ test('#1 the user\'s case: ground-only operators never hit a 近地悬浮 enemy
}
});
test('#220 (the owner\'s decision of 2026-10-08): a 速射手 blocking a ground enemy shoots a flyer in its range; a 神射手 keeps hitting the enemy it blocks', () => {
const defs = { enemies: {
e_walker: enemyRec({ key: 'e_walker', hp: 1e8, atk: 0, def: 800, speed: 1 }),
e_soft: enemyRec({ key: 'e_soft', hp: 1e8, atk: 0, def: 0, speed: 0 }),
e_bat: enemyRec({ key: 'e_bat', hp: 1e8, atk: 0, def: 0, speed: 0, motion: 'FLY' }),
} };
const shots = (chessId, other) => {
const h = makeBattle({ seed: 3, autoFinish: false, timeLimit: 120, captureNoisy: true, defs, units: [{ chessId, row: 10, col: 4 }] });
h.step();
const u = h.unit(chessId);
h.b.addBuff(u, { key: 'test:mute', flags: { silence: true } });
const walker = h.spawn('e_walker', { pos: [10, 7], routeIndex: 0, route: { motion: 'WALK', start: [10, 7], end: [10, 0], checkpoints: [] } });
assert.ok(h.runUntil(() => walker.blockedBy === u, 10), `${u.name} on a melee tile blocks the walker`);
const o = h.spawn(other, { pos: [10, 6], routeIndex: 0, route: { motion: other === 'e_bat' ? 'FLY' : 'WALK', start: [10, 6], end: [10, 6], checkpoints: [] } });
h.step();
assert.ok(h.b.enemiesInKeys(u.rangeKeys, u, u.profile).includes(o), `${other} in range`);
const n0 = h.hooksOf('damaged').length;
h.run(4);
const hits = (t) => h.hooksOf('damaged').slice(n0).filter((c) => c.source === u && c.target === t).length;
checkInvariants(h.b);
return { blocked: hits(walker), other: hits(o) };
};
const exu = shots('chess_char_3_01_a', 'e_bat'); // 能天使 (速射手: 优先攻击空中单位)
assert.ok(exu.other > 0 && exu.blocked === 0, `速射手: the flyer over her own blocked enemy (${JSON.stringify(exu)})`);
for (const other of ['e_soft', 'e_bat']) {
const far = shots('chess_char_4_20_a', other); // 远牙 (神射手: 优先攻击防御力最低)
assert.ok(far.blocked > 0 && far.other === 0, `神射手: still the enemy it blocks, not ${other} (${JSON.stringify(far)})`);
}
});
test('#1 ground-only splash spares a hovering enemy next to its ground target (迷迭香 + aftershocks)', () => {
const h = makeBattle({ units: [{ chessId: 'chess_char_6_12_a', row: 10, col: 3 }], seed: 3, autoFinish: false, timeLimit: 120, captureNoisy: true, defs: SYN });
h.step();
+125
View File
@@ -0,0 +1,125 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
function setup() {
const h = makeBattle({
defs: { enemies: { enemy_test: enemyRec({ key: 'enemy_test', hp: 1e6, atk: 100, def: 1000, speed: 0 }) } },
enemies: [{ key: 'enemy_test', pos: [11, 8] }], content: 'none', autoFinish: false,
});
h.step();
return { h, e: h.enemy('enemy_test') };
}
const orders = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]];
const approx = (actual, expected) => assert.ok(Math.abs(actual - expected) < 1e-6, `${actual} ≈ ${expected}`);
for (const plain of [false, true]) {
for (const order of orders) {
test(`${plain ? 'applyStrongest' : 'fragile'}: three effects expire in strength order after application order ${order}`, () => {
const { h, e } = setup();
const effects = [{ value: 0.8, duration: 2 }, { value: 0.6, duration: 5 }, { value: 0.3, duration: 10 }];
const key = plain ? 'test:strongest' : 'fragile';
for (const i of order) {
if (plain) h.b.applyStrongest(e, key, { ...effects[i], mods: (v) => ({ dmgTakenMul: 1 + v }) });
else h.b.applyStatus(e, key, effects[i]);
}
for (const [elapsed, multiplier, damage] of [[0, 1.8, 180], [2.1, 1.6, 160], [3, 1.3, 130], [5, 1, 100]]) {
if (elapsed) h.run(elapsed);
approx(e.s.dmgTakenMul, multiplier);
assert.equal(h.b.dealDamage(null, e, { amount: 100, type: 'true' }), damage);
assert.equal(e.buffs.filter((b) => b.key === key).length, multiplier === 1 ? 0 : 1);
}
assert.deepEqual(h.b.errors, []);
});
}
}
test('strongest statuses: a weaker effect that expires under the winner never resumes', () => {
const { h, e } = setup();
h.b.applyStatus(e, 'fragile', { value: 0.6, duration: 3 });
h.b.applyStatus(e, 'fragile', { value: 0.3, duration: 10 });
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 5 });
h.run(5.1);
approx(e.s.dmgTakenMul, 1.3);
h.run(5);
assert.equal(e.findBuff('fragile'), null);
});
test('strongest statuses: equal-strength refresh skips expired fallbacks but keeps the later one', () => {
const { h, e } = setup();
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) h.b.applyStatus(e, 'fragile', { value, duration });
h.run(1);
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 6 });
h.b.applyStatus(e, 'fragile', { value: 0.8, duration: 1 });
h.run(5.1);
approx(e.s.dmgTakenMul, 1.8);
h.run(1);
approx(e.s.dmgTakenMul, 1.3);
h.run(3);
assert.equal(e.findBuff('fragile'), null);
});
test('strongest statuses: refreshing a waiting effect preserves the weaker later fallback', () => {
const { h, e } = setup();
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) h.b.applyStatus(e, 'fragile', { value, duration });
h.run(1);
h.b.applyStatus(e, 'fragile', { value: 0.6, duration: 6 });
h.run(4.1);
approx(e.s.dmgTakenMul, 1.6);
h.run(2);
approx(e.s.dmgTakenMul, 1.3);
h.run(3);
assert.equal(e.findBuff('fragile'), null);
});
test('strongest statuses: stackAs controls every fallback without changing its actual effect', () => {
const { h, e } = setup();
h.b.applyStatus(e, 'weaken', { value: 0.1, stackAs: 0.9, duration: 2 });
h.b.applyStatus(e, 'weaken', { value: 0.2, stackAs: 0.8, duration: 5 });
h.b.applyStatus(e, 'weaken', { value: 0.3, duration: 10 });
approx(e.s.atk, 90);
h.run(2.1);
approx(e.s.atk, 80);
h.run(3);
approx(e.s.atk, 70);
h.run(5);
approx(e.s.atk, 100);
});
test('strongest statuses: equal-priority waiting effects keep the longest-lived application value', () => {
const { h, e } = setup();
h.b.applyStatus(e, 'weaken', { value: 0.9, duration: 2 });
h.b.applyStatus(e, 'weaken', { value: 0.1, stackAs: 0.8, duration: 5 });
h.b.applyStatus(e, 'weaken', { value: 0.3, duration: 10 });
h.b.applyStatus(e, 'weaken', { value: 0.2, stackAs: 0.8, duration: 7 });
h.b.applyStatus(e, 'weaken', { value: 0.4, stackAs: 0.8, duration: 6 });
h.b.applyStatus(e, 'weaken', { value: 0.5, stackAs: 0.8, duration: 7 });
h.run(2.1);
approx(e.s.atk, 80);
h.run(3);
approx(e.s.atk, 80);
h.run(2);
approx(e.s.atk, 70);
h.run(3);
approx(e.s.atk, 100);
});
for (const plain of [false, true]) {
test(`${plain ? 'removeBuff' : 'removeStatus'} discards every waiting strongest effect`, () => {
const { h, e } = setup();
const key = plain ? 'test:strongest' : 'fragile';
for (const [value, duration] of [[0.8, 2], [0.6, 5], [0.3, 10]]) {
if (plain) h.b.applyStrongest(e, key, { value, duration, mods: (v) => ({ dmgTakenMul: 1 + v }) });
else h.b.applyStatus(e, key, { value, duration });
}
assert.equal(plain ? h.b.removeBuff(e, key) : h.b.removeStatus(e, key), 1);
h.b.applyStatus(e, 'fragile', { value: 0.1, duration: 1 });
h.run(2.1);
approx(e.s.dmgTakenMul, 1);
h.run(8);
approx(e.s.dmgTakenMul, 1);
assert.equal(e.findBuff(key), null);
assert.deepEqual(h.b.errors, []);
});
}
+416
View File
@@ -0,0 +1,416 @@
// test/sim/trait_attack_speed.test.js — the ENGINE-level consumption of a trait's attack-speed rider
// (server/sim/content/traitMods.js, wired from server/sim/battle/players.js `_setupUnit` after `kit.install`; PR #293 by
// @LimitlessHPPK).
//
// Background (the def shape is docs/SIM.md `traitBb`; the profile merge is professions.js resolveProfile): build-data.mjs folds
// a module's trait blackboard into `trait.bb` unconditionally, so `{key:'attack_speed', value:12}` reached
// `resolveProfile` and stopped there — nothing read it, because the CONDITION only exists in the module sentence
// (「攻击范围内存在2名及以上敌人时攻击速度+12」). The line was therefore inert as DATA, and the two operators that
// carry it implemented it by hand in their kits (shared/tier5.js `crowdAspd`, deleted by this change): the engine now
// applies the same line for everyone.
//
// The tests below pin the four things that make this a fix rather than a special case:
// 1. the two REA-Y beneficiaries get +12 ASPD POINTS, and only while the engine's own targeting sees the required
// enemies — no bonus with 1 enemy in range, a measurably shorter attack interval from 2, back to base when the
// condition is left, and back on when it holds again;
// 2. the SAME engine rule serves both, although their numbers live on DIFFERENT blackboards (圣约送葬人 REA-Y:
// a hidden module talent, index −1; 隐德来希 REA-Y: `trait.bb` folded in by build-data) — there is no operator-id
// whitelist anywhere in the path;
// 3. operators with no such line are untouched;
// 4. the trait attack-speed lines a hand-written kit already implemented are neither double-counted nor changed
// (史尔特尔 / 维娜·维多利亚 / 山 / 空弦 / 斯卡蒂), and the whole shipped data set is closed against silent gaps.
//
// ASPD is a POINT score, not a percentage: `aspd = clamp(base + Σaspd, 20, 600)` and
// `interval = bat × (1 + ΣbatPct) × 100 / aspd` (the units.js header), so +12 means 100 → 112 (interval × 100/112), which is
// how every existing module / item / enemy implementation reads `attack_speed` too.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
import { getDefaultSource } from '../../server/sim/simdata.js';
import { TICK } from '../../server/sim/constants.js';
import { traitAttackSpeedRule, installTraitAttackSpeed, conditionClause, plainText, TRAIT_ASPD_BUFF } from '../../server/sim/content/traitMods.js';
import { diySlotIds, diySlot, diyPool, isDiyModule } from '../../shared/diy.js';
import { unitForm } from '../../shared/standIn.js';
const ds = getDefaultSource();
const HOOKS = ['attack', 'damaged', 'deploy', 'skillStart', 'skillEnd', 'tick'];
const dummy = (key, o = {}) => enemyRec({ key, hp: 1e7, speed: 0, ...o });
const DEFS = { enemies: { enemy_dummy: dummy('enemy_dummy') } };
/** Battle + one tick (t = 0 spawns exist afterwards). `autoFinish:false`: the battles below outlive their enemies. */
const run = (o) => makeBattle({ seed: 7, autoFinish: false, timeLimit: 400, hooks: HOOKS, captureNoisy: true, ...o }).step();
/** No skill casts while a window is measured (a silence buff, as the other kit tests do). */
const mute = (h, u) => h.b.addBuff(u, { key: 'test:mute', flags: { silence: true } });
const approx = (a, b, msg = '', rel = 1e-9) => assert.ok(Math.abs(a - b) <= rel * Math.max(1, Math.abs(b)), `${msg} ${a} ≈ ${b}`);
const median = (a) => { const s = [...a].sort((x, y) => x - y); return s[s.length >> 1]; };
/** The enemies the ENGINE counts for `u` (the call traitMods.js itself makes: live range keys + its own profile). */
const inRange = (h, u) => h.b.enemiesInKeys(u.rangeKeys, u, u.profile);
/** The cadence the engine can actually produce: `atkCd` counts down in whole ticks, so a gap is ⌈interval/TICK⌉ ticks. */
const cadence = (interval) => Math.ceil(interval / TICK - 1e-9) * TICK;
/** Run `seconds` and return the gaps (s) between `u`'s consecutive ordinary attacks inside that window. */
function attackGaps(h, u, seconds) {
const t0 = h.b.time;
h.run(seconds);
const ts = h.hooksOf('attack').filter((c) => c.attacker === u && !c.isSkill && c.t >= t0 - 1e-9).map((c) => c.t);
const gaps = [];
for (let i = 1; i < ts.length; i++) gaps.push(ts[i] - ts[i - 1]);
return gaps;
}
/** `chessId` on the board with `moduleId`, 1 dummy in range (the placement every REA-Y module test uses). */
function board(id, o = {}) {
return run({ defs: DEFS, units: [{ chessId: id, row: 10, col: 4, ...o }], enemies: [{ key: 'enemy_dummy', pos: [10, 5] }] });
}
/** Spawn dummies on the candidate tiles (skipping occupied ones) until `n` of them stand in `u`'s own range. */
function spawnInRange(h, u, n, tiles = [[10, 5], [11, 5], [9, 5], [9, 4], [8, 5]]) {
const occupied = new Set(h.enemies().map((e) => `${Math.round(e.y)},${Math.round(e.x)}`));
const out = [];
for (const pos of tiles) {
if (inRange(h, u).length >= n) break;
if (occupied.has(pos.join(','))) continue;
out.push(h.spawn('enemy_dummy', { pos }));
occupied.add(pos.join(','));
h.step();
}
assert.ok(inRange(h, u).length >= n, `${u.defId}: ${n} enemies stand in its range (${inRange(h, u).length})`);
return out;
}
const done = (h) => { checkInvariants(h.b); assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0])); };
/** The two operators the engine serves; `legacy` = the buff key their deleted hand-written copy used. */
const REA_Y = [
// 圣约送葬人 chess_char_5_01_b: the module's number rides a HIDDEN module talent (`talentChanges[0].bb.attack_speed`)
{ id: 'chess_char_5_01_b', moduleId: 'uniequip_003_excu2', legacy: 'excu2:module', base: 100 },
// 隐德来希 chess_char_5_06_b: build-data folds it straight into `trait.bb`
{ id: 'chess_char_5_06_b', moduleId: 'uniequip_003_etlchi', legacy: 'etlchi:module', base: 105 },
];
// =================================================================================================================
// 1. the two REA-Y beneficiaries: entered, left and re-entered, with the cadence measured
test('圣约送葬人 / 隐德来希 REA-Y: 1 名敌人 → 正好 base,2 名 → 正好 +12 点(不是 +24)且实测攻击间隔按 ASPD 比缩短,击杀后恢复,重新进范围再加回', () => {
for (const { id, moduleId, legacy, base } of REA_Y) {
const h = board(id, { moduleId });
const u = h.unit(id);
const rule = traitAttackSpeedRule(u.def);
assert.equal(rule.value, 12, `${id}: the REA-Y line is +12`);
assert.equal(rule.reason, 'ok', `${id}: the engine owns it`);
assert.equal(rule.clause, '攻击范围内存在2名及以上敌人时', `${id}: read from the module sentence`);
assert.equal(u.base.aspd, base, `${id}: base ASPD`);
mute(h, u); // a skill that widened the range would move the measured condition under our feet
// --- one enemy in range: the 「存在2名及以上敌人」 clause does not hold, so nothing is installed
h.run(1);
assert.equal(inRange(h, u).length, 1, `${id}: exactly one enemy stands in its range`);
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, `${id}: 1 enemy → the module line is NOT installed`);
assert.equal(u.findBuff(legacy), null, `${id}: the kit copy of the line is gone (${legacy})`);
assert.equal(u.s.aspd, u.base.aspd, `${id}: 1 enemy → exactly the base (NOT base + 12: no kit + engine double count)`);
approx(u.s.interval, u.base.bat * 100 / u.base.aspd, `${id}: interval = bat × 100 / aspd`);
const g1 = attackGaps(h, u, 8);
assert.ok(g1.length >= 4, `${id}: enough attacks to measure (${g1.length})`);
approx(median(g1), cadence(u.s.interval), `${id}: baseline cadence matches the interval`, 0.02);
// --- a second enemy walks in: the stat is on within one tick
const interval0 = u.s.interval;
const second = h.spawn('enemy_dummy', { pos: [11, 5] });
h.step();
assert.equal(inRange(h, u).length, 2, `${id}: the second enemy is in range (the engine's own targeting)`);
assert.equal(u.s.aspd, u.base.aspd + 12, `${id}: 2 enemies → +12 ASPD POINTS`);
assert.notEqual(u.s.aspd, u.base.aspd + 24, `${id}: the engine rule only — the hand-written copy is deleted`);
approx(u.s.interval, u.base.bat * 100 / (u.base.aspd + 12), `${id}: the shortened interval`);
assert.ok(u.s.interval < interval0 - 1e-9, `${id}: the attack interval really shrank`);
const buff = u.findBuff(TRAIT_ASPD_BUFF);
assert.ok(buff, `${id}: the engine rule installed the buff`);
assert.equal(buff.mods.aspd, 12, `${id}: 12 points`);
const g2 = attackGaps(h, u, 8);
approx(median(g2), cadence(u.s.interval), `${id}: buffed cadence matches the shortened interval`, 0.02);
assert.ok(median(g2) < median(g1) - 0.02, `${id}: measured gaps shrank (${median(g2).toFixed(4)} < ${median(g1).toFixed(4)})`);
approx(median(g1) / median(g2), (u.base.aspd + 12) / u.base.aspd, `${id}: the measured cadence ratio is the ASPD ratio`, 0.03);
// --- the condition is left: the stat goes away again
h.b.dealDamage(null, second, { type: 'true', amount: 1e9 });
h.step();
assert.equal(second.alive, false, `${id}: the second enemy is dead`);
assert.equal(u.s.aspd, u.base.aspd, `${id}: back to the baseline`);
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, `${id}: buff removed`);
const g3 = attackGaps(h, u, 8);
approx(median(g3), cadence(u.s.interval), `${id}: cadence back to the baseline interval`, 0.02);
approx(median(g3), median(g1), `${id}: and back to the measured baseline cadence`, 0.03);
// --- and it comes back on a new enemy
h.spawn('enemy_dummy', { pos: [11, 5] });
h.step();
assert.equal(u.s.aspd, u.base.aspd + 12, `${id}: re-entering the condition re-applies it`);
assert.equal(u.findBuff(TRAIT_ASPD_BUFF)?.mods.aspd, 12, `${id}: still 12 points`);
done(h);
}
});
test('两名受益者的取值来源不同(隐藏天赋 / traitBb),引擎两条路都读;默认模组都没有这条词条', () => {
// 圣约送葬人 REA-Y: `talentChanges[0]` targets the hidden talent, so the number is NOT on the trait blackboard
const excu2 = ds.getChess('chess_char_5_01_b', { moduleId: 'uniequip_003_excu2' });
const hidden = (excu2.raw.talents ?? []).filter((t) => t.index === -1);
assert.equal(excu2.raw.trait.bb.attack_speed, undefined, '圣约送葬人: the number is not on trait.bb');
assert.equal(hidden.length, 1, '圣约送葬人: …it is on the hidden module talent (index −1)');
assert.equal(hidden[0].bb.attack_speed, 12, '圣约送葬人: +12 there');
const r1 = traitAttackSpeedRule(excu2);
assert.equal(r1.value, 12);
assert.equal(r1.reason, 'ok');
assert.equal(r1.clause, '攻击范围内存在2名及以上敌人时');
assert.equal(plainText(r1.source), '攻击范围内存在2名及以上敌人时攻击速度+12');
// 隐德来希 REA-Y: build-data folded the line into trait.bb, and she has no hidden module talent at all
const etlchi = ds.getChess('chess_char_5_06_b', { moduleId: 'uniequip_003_etlchi' });
assert.equal(etlchi.raw.trait.bb.attack_speed, 12, '隐德来希: the number is on trait.bb');
assert.deepEqual((etlchi.raw.talents ?? []).filter((t) => t.index === -1), [], '隐德来希: no hidden module talent');
const r2 = traitAttackSpeedRule(etlchi);
assert.equal(r2.value, 12);
assert.equal(r2.reason, 'ok');
assert.equal(r2.clause, '攻击范围内存在2名及以上敌人时');
assert.equal(plainText(r2.source), '攻击范围内存在2名及以上敌人时攻击速度+12');
// the module has to be EQUIPPED: without it neither carries the line (no permanent rider on the default loadout)
assert.equal(traitAttackSpeedRule(ds.getChess('chess_char_5_01_b')), null, '圣约送葬人 默认模组: no such line');
assert.equal(traitAttackSpeedRule(ds.getChess('chess_char_5_06_b')), null, '隐德来希 默认模组: no such line');
});
// =================================================================================================================
// 2. regression — operators without the line
test('回归: 没有该词条的干员完全不变(3 名敌人也不动)', () => {
for (const id of ['chess_char_1_01_a', 'chess_char_5_01_a']) { // 隐现; 圣约送葬人的 NORMAL 棋子(未装备 REA-Y)
assert.equal(traitAttackSpeedRule(ds.getChess(id)), null, `${id}: no trait attack-speed line`);
const h = board(id);
const u = h.unit(id);
mute(h, u);
h.run(0.5);
const as0 = u.s.aspd;
spawnInRange(h, u, 3);
h.run(0.5);
assert.equal(u.s.aspd, as0, `${id}: ASPD unchanged with 3 enemies in range`);
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, `${id}: no engine buff`);
done(h);
}
});
test('回归: 手写实现的那几条特性词条,引擎显式拒绝接管(防止二次加成)', () => {
const cases = [
['chess_char_3_05_b', null, 30, 'kit'], // DRE-Y 被击倒时不撤退 +30(kit: ops/chess_char_3_05-skadi.js)
['chess_char_6_17_b', 'uniequip_003_nearl2', 30, 'kit'], // DRE-Y 耀骑士临光
['chess_char_5_07_b', null, 8, 'kit'], // AFT-X 未阻挡 +8(史尔特尔)
['chess_char_6_07_b', null, 8, 'kit'], // AFT-X 维娜·维多利亚
['chess_char_5_08_b', 'uniequip_003_horn', 10, 'kit'], // FOR-Y 号角 不阻挡敌人时 +10
['chess_char_5_17_b', null, 10, 'kit'], // FGT-Y 生命值高于50% +10(山)
['chess_char_3_21_b', null, 8, 'kit'], // MAR-Y 空弦 范围内地面敌人 +8
['chess_char_3_01_b', 'uniequip_003_angel', 8, 'kit'], // MAR-Y 能天使
['chess_char_6_20_b', 'uniequip_003_agoat2', 8, 'kit'], // WDM-Y 纯烬艾雅法拉
['chess_char_4_09_b', 'uniequip_004_mizuki', 50, 'mode'], // ISW-A 水月(集成战略专用,本模式不适用)
];
for (const [id, moduleId, value, reason] of cases) {
const def = ds.getChess(id, moduleId ? { moduleId } : null);
const r = traitAttackSpeedRule(def);
assert.ok(r, `${id}: carries a trait attack-speed line`);
assert.equal(r.value, value, `${id}: value`);
assert.equal(r.condition, null, `${id}: the engine installs nothing`);
assert.equal(r.reason, reason, `${id}: refused as '${reason}'`);
assert.ok(r.clause.length > 0, `${id}: the clause it read is reported (${JSON.stringify(r.clause)})`);
}
});
test('回归: 史尔特尔 / 维娜·维多利亚 / 山 / 空弦 / 斯卡蒂 的数值与改动前一致(各自的 kit 路径照旧)', () => {
// 史尔特尔 AFT-X: 未阻挡敌人时 +8
{
const h = run({ defs: DEFS, units: [{ chessId: 'chess_char_5_07_b', row: 10, col: 4 }], enemies: [{ key: 'enemy_dummy', pos: [10, 9] }] });
const u = h.unit('chess_char_5_07_b');
mute(h, u);
const as = u.def.traitBb.attack_speed;
assert.equal(as, 8, '史尔特尔: trait.bb 上的 +8');
h.run(1);
assert.equal(u.blocking.length, 0, '未阻挡');
assert.equal(u.s.aspd, u.base.aspd + as, '未阻挡: kit 的 +8');
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, 'engine 未接管');
done(h);
}
// 维娜·维多利亚 AFT-X: 未阻挡敌人时 +8
{
const h = run({ defs: DEFS, units: [{ chessId: 'chess_char_6_07_b', row: 10, col: 4 }], enemies: [{ key: 'enemy_dummy', pos: [10, 9] }] });
const u = h.unit('chess_char_6_07_b');
mute(h, u);
const as = u.def.traitBb.attack_speed;
assert.equal(as, 8, '维娜·维多利亚: trait.bb 上的 +8');
h.run(1);
assert.equal(u.blocking.length, 0, '未阻挡');
assert.equal(u.s.aspd, u.base.aspd + as, '未阻挡: kit 的 +8');
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, 'engine 未接管');
done(h);
}
// 山 FGT-Y: 生命值高于 50% 时 +10
{
const h = board('chess_char_5_17_b');
const u = h.unit('chess_char_5_17_b');
mute(h, u);
const as = u.def.traitBb.attack_speed;
assert.equal(as, 10, '山: trait.bb 上的 +10');
h.run(1);
assert.ok(u.hpRatio > 0.5, 'full HP');
assert.equal(u.s.aspd, u.base.aspd + as, 'HP > 50 %: kit 的 +10');
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, 'engine 未接管');
done(h);
}
// 空弦 MAR-Y: 范围内存在地面敌人时 +8 —— 数值在隐藏天赋上(与圣约送葬人同一个来源),kit(shared/tier3.js groundAspd)负责
{
const h = board('chess_char_3_21_b');
const u = h.unit('chess_char_3_21_b');
mute(h, u);
const as = (u.def.raw.talents ?? []).find((t) => t.index === -1)?.bb.attack_speed;
assert.equal(as, 8, '空弦: MAR-Y 的 +8 在隐藏天赋 (index −1) 上');
h.run(1);
assert.equal(u.s.aspd, u.base.aspd + as, '地面敌人在范围内: kit 的 +8');
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, 'engine 未接管');
done(h);
}
// 斯卡蒂 DRE-Y: 被击倒时不撤退 + 攻击速度 +30(kit 的 trait:skadi_tide)
{
const h = run({ defs: DEFS, units: [{ chessId: 'chess_char_3_05_b', row: 10, col: 4 }], enemies: [{ key: 'enemy_dummy', pos: [10, 9] }] });
const u = h.unit('chess_char_3_05_b');
mute(h, u);
const as = u.def.traitBb.attack_speed;
assert.equal(as, 30, '斯卡蒂: trait.bb 上的 +30');
h.run(0.5);
assert.equal(u.s.aspd, u.base.aspd, 'before the lethal blow');
h.b.dealDamage(null, u, { type: 'true', amount: 1e9 });
h.step();
assert.equal(u.alive, true, 'DRE-Y 不撤退');
assert.ok(u.findBuff('trait:skadi_tide'), 'kit 的复活 buff');
assert.equal(u.s.aspd, u.base.aspd + as, '复活后 +30(只有 kit 这一份)');
assert.equal(u.findBuff(TRAIT_ASPD_BUFF), null, 'engine 未接管');
done(h);
}
});
// =================================================================================================================
// 3. the rule itself: what it reads, and that it installs nothing when unsure
test('规则解析: 条件子句 + 取值被显式报出;读不出条件 / 不认识的形状 → 什么都不装', () => {
const def = ds.getChess('chess_char_5_01_b', { moduleId: 'uniequip_003_excu2' });
const r = traitAttackSpeedRule(def);
assert.equal(r.value, 12);
assert.equal(r.reason, 'ok');
assert.equal(r.clause, '攻击范围内存在2名及以上敌人时');
assert.equal(typeof r.condition, 'function');
assert.equal(plainText(r.source), '攻击范围内存在2名及以上敌人时攻击速度+12');
assert.equal(conditionClause(r.source), '攻击范围内存在2名及以上敌人时');
// a line whose 「攻击速度」 comes first is unconditional → no clause → nothing installed (never a permanent stat)
const tail = { ...def, raw: { ...def.raw, trait: { desc: '攻击速度+9,攻击力+1%', bb: { attack_speed: 9 } } } };
const rt = traitAttackSpeedRule(tail);
assert.equal(rt.value, 9);
assert.equal(rt.reason, 'no-text', 'no condition clause → refused');
assert.equal(rt.clause, '');
assert.equal(rt.source, null);
assert.equal(rt.condition, null, 'the engine installs nothing');
// an unknown clause shape
const odd = { ...def, raw: { ...def.raw, trait: { desc: '心情好的时候攻击速度+9', bb: { attack_speed: 9 } } } };
const ro = traitAttackSpeedRule(odd);
assert.equal(ro.value, 9);
assert.equal(ro.reason, 'unknown');
assert.equal(ro.condition, null, 'an unrecognised condition never becomes a permanent stat');
// no trait attack-speed line at all
assert.equal(traitAttackSpeedRule(ds.getChess('chess_char_1_01_a')), null);
assert.equal(traitAttackSpeedRule(null), null);
// tokens are never touched
assert.equal(installTraitAttackSpeed({}, { kind: 'token' }), null);
});
test('规则解析: 仇白 LOR-Y 同文案但数值写进具名天赋 —— 引擎连这条词条都看不到(她的 kit 负责,绝不二次加成)', () => {
const def = ds.getChess('chess_char_6_15_b', { moduleId: 'uniequip_003_qiubai' });
assert.equal(def.raw.trait.bb.attack_speed, undefined, 'trait.bb 上没有攻速');
assert.deepEqual((def.raw.talents ?? []).filter((t) => t.index === -1), [], '也没有隐藏天赋');
assert.equal(def.raw.talents[0].bb.attack_speed, 12, '数值在具名天赋 入隙 (index 0) 上');
assert.equal(def.raw.talents[0].bb.cnt, 2, '连条件计数也一起');
assert.equal(traitAttackSpeedRule(def), null, '引擎看不到 → 由 kits/ops/chess_char_6_15-qiubai.js 消费');
});
test('覆盖锁定: 全数据 266 个棋子 × 全部模组选择里,被引擎接管的只有那两条 REA-Y,其余全部显式拒绝(unknown = 0)', () => {
const installed = [];
const refused = [];
const reasons = {};
let scanned = 0;
for (const id of ds.chessIds()) {
const rec = ds.rawChess(id);
if (!rec) continue;
const choices = [null, ...(rec.modules || []).map((m) => ({ moduleId: m.uniEquipId })), { moduleId: 'none' }];
for (const lo of choices) {
let d;
try { d = ds.getChess(id, lo); } catch { continue; }
if (!d) continue;
scanned++;
const r = traitAttackSpeedRule(d);
if (!r) continue;
const tag = `${id}|${lo?.moduleId ?? 'default'}|${r.value}|${r.reason}`;
if (r.condition) installed.push(tag);
else { refused.push(tag); reasons[r.reason] = (reasons[r.reason] ?? 0) + 1; }
}
}
assert.ok(scanned > 500, `the whole data set was scanned (${scanned} defs)`);
assert.deepEqual(installed.sort(), [
'chess_char_5_01_b|uniequip_003_excu2|12|ok', // 圣约送葬人 REA-Y(数值在隐藏天赋上)
'chess_char_5_06_b|uniequip_003_etlchi|12|ok', // 隐德来希 REA-Y(数值在 trait.bb 上)
]);
assert.deepEqual(Object.keys(reasons).sort(), ['kit', 'mode'], 'no silent gap — every refusal carries a known reason');
assert.equal(reasons.unknown ?? 0, 0, 'nothing falls through as unknown');
assert.ok((reasons.kit ?? 0) >= 13, `every other carrier is refused explicitly (${reasons.kit} kit)`);
assert.equal(reasons.mode, 1, '集成战略专用的一条(水月 ISW-A)');
assert.ok(refused.length >= 13, `refused lines (${refused.length})`);
for (const t of refused) {
assert.ok(/\|(kit|mode)$/.test(t), `refused with a known reason: ${t}`);
}
assert.ok(refused.some((t) => t.startsWith('chess_char_3_05_b|default|30|kit')), '斯卡蒂 DRE-Y is in the list');
assert.ok(refused.some((t) => t.startsWith('chess_char_3_21_b|default|8|kit')), '空弦 MAR-Y is in the list');
assert.ok(refused.some((t) => t.startsWith('chess_char_4_09_b|uniequip_004_mizuki|50|mode')), '水月 ISW-A is in the list');
});
test('覆盖锁定(补位 / 自选): 每个补位干员和每个自选组合(池内干员 × 两种形态 × 技能 × 可选模组)都不被引擎接管', () => {
// 0.2.2 maintainer addition to PR #293: the battle also fields 补位 stand-ins (getChess(id, { standIn: true })) and
// 自选 picks (getChess(slot, { diy })) — 6★ operators whose own modules carry trait attack-speed lines. None may gain
// the engine rule: their lines are either absent or owned by their kits (黑键 / 维伊 MSC-Y, 赤刃明霄陈 AFT, 重岳 / 贝洛内
// FGT-Y, 止颂 DRE-Y → 'kit'); a module of another game mode is not a legal pick (isDiyModule, validateDiyPicks).
const applied = [];
const reasons = {};
const tally = { standIn: 0, diy: 0 };
const see = (kind, tag, d) => {
tally[kind]++;
const r = traitAttackSpeedRule(d);
if (!r) return;
if (r.condition) applied.push(`${kind} ${tag}`);
reasons[r.reason] = (reasons[r.reason] ?? 0) + 1;
};
for (const id of ds.chessIds()) {
let d;
try { d = ds.getChess(id, { standIn: true }); } catch { d = null; }
if (d) see('standIn', `${id}|${d.charId}`, d);
}
const backups = ds.rawBackups();
for (const slotId of diySlotIds(ds)) {
const slot = diySlot(slotId, ds);
for (const chessId of [slot.baseId, slot.goldenId]) {
const status = ds.rawChess(chessId)?.status;
for (const charId of diyPool(slot.tier, { data: ds })) {
const form = unitForm(backups, charId, status);
if (!form) continue;
const skills = (form.skills ?? []).map((sk) => sk.index);
for (const skillIndex of skills.length ? skills : [null]) {
for (const uniEquipId of [null, ...(form.modules ?? []).filter(isDiyModule).map((m) => m.uniEquipId)]) {
let d;
try { d = ds.getChess(chessId, { diy: { charId, skillIndex, uniEquipId } }); } catch { d = null; }
if (d) see('diy', `${chessId}|${charId}|${skillIndex}|${uniEquipId}`, d);
}
}
}
}
}
assert.ok(tally.standIn > 100, `every stand-in was scanned (${tally.standIn})`);
assert.ok(tally.diy > 1000, `every 自选 combination was scanned (${tally.diy})`);
assert.deepEqual(applied, [], 'no stand-in or 自选 pick gains the engine rule');
assert.deepEqual(Object.keys(reasons).sort(), ['kit'], 'every line they carry is a kit-owned shape (no unknown, no other mode)');
});
+2 -1
View File
@@ -1,6 +1,7 @@
// Community report of 2026-10-06 「独行这种盟约是不能叠层的(顺便排查下还有没有类似的)」 (item 21). PRTS 卫戍协议:盟约 下半/
// PRTS盟约记录, its header: 「下述盟约中部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」 — the four
// bonds.json `noStack` bonds (调和 协防干员 独行 绝技) do take layers (助力, 余, 华法琳 …), the game only never shows them. The
// bonds.json `noStack` bonds (调和 协防干员 独行 绝技) do take layers (助力, 华法琳 … — not 余's 文火慢炖, the owner's decision
// of 2026-10-08), the game only never shows them. The
// bond strip and its popup hide them since 0.1.4 (PR #66, test/ui/feedback1b-bonds.test.js); the result card still printed
// them (「独行 24」) and ordered its six bonds by them. resultBonds: no number, no rank for a hidden count.
import { test } from 'node:test';