feedback5 CB2: the countdown summons (医疗探机, 诅咒娃娃, 海嗣; 自选 工程蓄水炮, 沙地兽, 打字机, 本能的召唤) take no damage — 活性源石 included —, are never healed, and their bar shows the life left (community report of 2026-10-06 「赫默的无人机、浊心斯卡蒂的海嗣等这种召唤物不会受到外来伤害…血量随时间减少」)

Official: PRTS 斯卡蒂的海嗣 备注 「持有禁疗、无敌」, the token prefabs of 海嗣 and 沙地兽 carry abnormal flags 5 + 7 (无敌,
禁疗), 工程蓄水炮 7 (禁疗); the report describes the official mode for the drone too — [ASSUMED] 无敌 + 禁疗 for the summons
the base prefabs give no flag (医疗探机, 诅咒娃娃, 打字机, 本能的召唤), and the bar as a timer.
Cause: content/tokens.js / the kits only made them untargetable: a 海嗣 on 活性源石 lost 70 of its 100 HP every second, a
hurt drone drew every medic's heals, and the bar was plain HP. Now: COUNTDOWN_SUMMONS / startCountdown (called where each
lifetime is scheduled: tokens.js scheduleLifetime, 浊心斯卡蒂, 温蒂, 莱伊, 鸿雪, 酒神 kits) give 无敌 + 禁疗 and
Unit.countdown; snapshot.js sends hp = maxHp × the share of the life left (the sim HP stays full, so no selector sees a
low-HP summon). Attacked summons (沙之碑, 狼群 …) are unchanged. The 吟游者 trait therefore cannot refill such a bar (item 32).

Golden: snapshot hashes only (the bar) — roster-003, -016, -018, -035, -045, -046, bond-visiShip-low / -high,
boss-boss_1 / 3 / 5 / 7-pair, unite-1; gameplay too — roster-005 (medics no longer heal the drone), roster-037 and
hidden-boss_9-pair (a 海嗣 / drone under enemy and leader damage stays up: fights change).
Tests: test/sim/feedback5-countdown-summons.test.js (7; fails on the parent: no COUNTDOWN_SUMMONS — and there the 海嗣
lost 70 HP to an 活性源石 tick). Docs: SIM.md token pieces, PLAYING.md 作战中.
This commit is contained in:
sganggs committed 2026-10-07 02:27:33 +08:00
1 parent 64f8bf912a
commit 17c96843ee
15 files changed
+230 -96

No files matched your search

+1 -1
View File
@@ -73,7 +73,7 @@
- 拖动时模型一直在指针 / 手指下(脚在指针下方约半格,鼠标和触屏一样),**指针所在的格子**就是落点并高亮;装备拖到哪个格子,就配发给那个格子上的干员;在商店栏上松手则放回原位。
- 点击干员显示攻击范围和底部按钮(「撤退」回到整备区、「出售 +1」);右键 / 长按查看详细信息:头像旁是盟约,下面第一项就是干员的**特质**(如「休整期结束时 …」),之后是特性、属性、攻击范围、技能、模组、装备、天赋。属性是**实时数值**:作战中每秒刷新 4 次(当前生命、攻击、防御、法抗、攻击间隔、阻挡),休整期显示棋盘上的干员**开战时**的数值(已算入装备、盟约层数、特质、策略与机变效果);高于基础值为绿色、低于为红色并标出差值(攻击间隔变短算增益),鼠标悬停可看基础值。
- **召唤物手动摆放**:把带可部署召唤物的干员放上棋盘后,它的召唤物会进入整备区(赫默的医疗探机、巫恋的诅咒娃娃、凯瑟琳的 2 个支援装置、浊心斯卡蒂的海嗣、伺夜的狼群、缪尔赛思的流形;同一干员的召唤物叠在一格,不占部署位),像干员一样拖到棋盘上并选方向。凯瑟琳的支援装置给**它面朝的那一格**上的干员加屏障,所以要放在干员旁边、朝向干员。**战术家的援军**(伺夜的狼群、缪尔赛思的流形,也就是选战术点)和官方一样**只能放在召唤者的攻击范围内**:拖动时只有这些格子会亮起;之后在原地给战术家换朝向时,不在新攻击范围里的援军会退回整备区(还在范围里的保持不动;整备区和临时整备区都满、援军无处可退时,这次换朝向不生效并提示「整备区已满」)。撤退或出售干员时它的召唤物一起移除;**移动干员到别的格子时(包括把召唤物拖到干员身上交换位置),它已摆放的召唤物会退回整备区**,需要重新摆放。整备区满时退回的召唤物进入临时整备区,休整期结束还没处理的会在下一回合重新回到整备区。
- 作战中:海嗣、狼群、流形、支援装置开战时就在摆放的位置部署;医疗探机、诅咒娃娃开战时也在摆放的位置免费部署一次(持续 10 / 15 秒,和官方一样不需要先开技能),之后所属干员每次发动技能时再次在原位出现;所属干员被击倒、再部署回场时,技能给的那一个如果还没出现,会立刻出现。巫恋退场时诅咒娃娃随之撤退,赫默退场时已出现的医疗探机不受影响。医疗探机、诅咒娃娃、支援装置、海嗣没有摆放就不会出现;狼群、流形(战术家的援军)没有摆放时会在战术点出现。狼群最后一只狼影被击倒后进入战术点形态:暂时离开战斗(不阻挡、不攻击、不会被攻击,格子仍然占着),经过狼影的恢复时间(25 秒)后在原地满血、带着 1 只狼影回来,之后重新开始每 25 秒加 1 只狼影;伺夜退场时狼群随之撤退,伺夜再部署回场时狼群重新带着 2 只狼影出现。
- 作战中:海嗣、狼群、流形、支援装置开战时就在摆放的位置部署;医疗探机、诅咒娃娃开战时也在摆放的位置免费部署一次(持续 10 / 15 秒,和官方一样不需要先开技能),之后所属干员每次发动技能时再次在原位出现;所属干员被击倒、再部署回场时,技能给的那一个如果还没出现,会立刻出现。巫恋退场时诅咒娃娃随之撤退,赫默退场时已出现的医疗探机不受影响。医疗探机、诅咒娃娃、海嗣(以及自选干员的工程蓄水炮、沙地兽、打字机、本能的召唤)这类不会受到攻击、到时间就离场的召唤物不受任何伤害(活性源石也一样)、也不会被治疗,它们的血条就是倒计时:随时间减少,空了就离场。医疗探机、诅咒娃娃、支援装置、海嗣没有摆放就不会出现;狼群、流形(战术家的援军)没有摆放时会在战术点出现。狼群最后一只狼影被击倒后进入战术点形态:暂时离开战斗(不阻挡、不攻击、不会被攻击,格子仍然占着),经过狼影的恢复时间(25 秒)后在原地满血、带着 1 只狼影回来,之后重新开始每 25 秒加 1 只狼影;伺夜退场时狼群随之撤退,伺夜再部署回场时狼群重新带着 2 只狼影出现。
- **阻挡**看接触:干员阻挡中心距离约 0.7 格以内、与它接触的敌人(地面干员挡不住飞行和近地悬浮的敌人)。敌人卡在两名干员之间时,阻挡它的干员倒下、撤退或被晕眩后,另一名仍与它接触、还有空余阻挡数的干员会**立刻接替阻挡**;都没有空位时敌人才会走掉。迎面走来的敌人会停在干员格子前面。**阻挡了就一定能打到**:任何干员(包括站在近战位的远程干员和召唤物)总能攻击自己阻挡的敌人,不管它在不在攻击范围内、在干员哪一侧,并且优先攻击它(医疗干员除外:咒愈师以外的医疗挡住敌人时照常治疗队友,不会攻击它;「停止攻击」类技能期间也除外;技能原文另有限制的也按原文:普罗旺斯二技能开启时不以生命值高于 80% 的敌人为目标,寒檀二技能的冰凌随机落在攻击范围内的地块上)。
- 开战时干员按**逐列从左到右、每列从上到下**的顺序部署(最左一列最先,与阿戈尔吞噬「更靠左和靠上」的顺序相同),召唤物在所有干员之后。被阻挡的敌人先打阻挡它的干员;没被阻挡的远程敌人先打嘲讽等级高的,同级时打最后部署的(召唤物、最右一列靠下的干员和刚再部署的干员更容易挨打)。隐匿的干员只会被它阻挡的敌人攻击,没被它挡住的敌人的溅射、爆炸、范围技能、留下的伤害区域(包括假想敌:蚀裂被击倒后喷出的毒雾)以及深池伙友卫队、扎罗的减攻速光环也落不到它身上(和官方一样;写明「无视无法选择」的效果如污染秽蚀照常能打到,寒霜的减攻速光环和“墓碑”这类地图效果照常生效);**迷彩**(如忍冬三技能期间击倒敌人后、寒芒克洛丝一技能)同样只会被它阻挡的敌人攻击,也显示为半透明,但躲不开溅射、爆炸和范围伤害(同时打范围内所有单位的普通攻击——如“斩胄之剑”“破胄之锤”“灵幛”——打不到它),也不算隐匿(不触发叙拉古等「隐匿时」效果)。叙拉古的隐匿按时(32 秒 + 每层 0.4 秒)结束;忍冬的迷彩按官方持续到她下一次开技能。**蒂比起飞**期间只挡飞行敌人,地面敌人不会选她为目标(普通攻击、溅射、技能范围、燃烧区域和要选中目标的减益都落不到她身上);飞行和悬浮敌人、写明「无视无法选择」的效果(如污染秽蚀)照常能打到她;地面敌人的光环和全场效果(如“墓碑”让全场攻击力、防御力减半)、她攻击敌人时招来的反制(如重装侦察兵的暴露)照常生效,起飞前已挂上的持续伤害也照常跳。
- **技能自动释放**按官方「技能策略」:一般的手动技能在技能就绪、即将攻击 / 治疗且初始攻击范围内有敌人(治疗技能:有受伤的队友)时释放;**带独立技能范围**的技能(如德克萨斯「剑雨」、凛御银灰「御敌的锋锐」、锏「归于宁静」,不含「攻击范围扩大」类)在**技能范围内出现敌人**时就释放,不必先攻击;**开启后攻击范围比平时大**的手动技能(如莫斯提马「序时之匙」、银灰「真银斩」、史尔特尔「黄昏」、白面鸮「脑啡肽」)在**开启后的攻击范围内出现敌人**(治疗技能:有受伤的队友)时就释放,不必等敌人走进平时的攻击范围,这是本作按玩家反馈有意与官方不同的地方;**重装干员**的手动技能在受到伤害时释放(例外:深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放(深巡二技能按开启后更远的攻击范围判断),余的二技能「厚礼上宾」改为周围两格(技能范围)内有敌人时就释放,这是本作有意与官方不同的地方;深巡一技能仍要受到伤害);执旗手 / 战术家 / 吟游者技能就绪即释放,解放者 / 阵法术师在初始攻击范围内有敌人时释放(开启后攻击范围扩大的技能——薄绿「风语」、蜜蜡「沙暴扩散」、卡涅利安「食噬之印」、玛恩纳「未宽解的悲哀」,以及安洁莉娜「秘杖·反重力模式」——同样按开启后的攻击范围判断)。**自动触发**的技能按其本身规则:只作用于队友的(如引星棘刺「度算浪波」、浊心斯卡蒂「同葬无光之愿」、纯烬艾雅法拉「无声润物」)技力满就释放,不用等敌人或伤员;伺夜「领袖的呼唤」「领袖的馈赠」只在场上有狼群时释放:狼群处于战术点形态时,「领袖的呼唤」也会释放并让它立刻带着 1 只狼影回到场上(狼影未满时改为加 1 只狼影,已满时把狼群的生命值回满),「领袖的馈赠」也会释放(立即获得费用,强化留给回来的狼群的下一次攻击);又如古米「备用军粮」在身边(技能范围内)有受伤的队友时就会发动,下一次攻击改为治疗该队友(不用先接敌;她的二技能「食粮烹制」是手动技能,按重装规则在受到伤害时释放);塞雷娅「急救」在她即将攻击时、身边有生命不高于一半的队友才发动,这次攻击直接改为治疗;新约能天使二技能的护盾按官方给攻击范围内**仇恨最高**的队友(嘲讽等级高的优先,同级时给最后部署的),不一定是站在最前面挨打的那个。手动技能的自动释放有 3 秒冷却:开战部署后先等 3 秒,之后同一干员两次自动释放至少间隔 3 秒(可充能的技能不会同时放完)。
+4 -1
View File
@@ -126,7 +126,10 @@ owner's decision of 2026-10-06, kits/ops/op-wang.js); a skill's summon (赫默 S
and regardless of the holding (PRTS §作战阶段 "所有手动部署的召唤物,无视所属干员的持有状态…作战开始时立即部署一次"), then
waits on its tile and takes the field there each time the skill gives one (stock ≤ 1, after the token's redeploy time
once it left, free; never while its owner is off the field [ASSUMED] — a stocked one deploys as soon as the owner is
back; the doll also leaves when 巫恋 leaves, the drone stays when 赫默 leaves: PRTS token 备注). A skill's summon, a
back; the doll also leaves when 巫恋 leaves, the drone stays when 赫默 leaves: PRTS token 备注). The countdown summons —
those that "不会受到攻击" and leave after a fixed time (医疗探机, 诅咒娃娃, 海嗣; 自选: 工程蓄水炮, 沙地兽, “打字机”, 本能的召唤;
content/tokens.js `COUNTDOWN_SUMMONS`) — hold 无敌 + 禁疗 from each deployment (`startCountdown`) and their snapshot hp is
maxHp × the share of their life left (`unit.countdown`; community report of 2026-10-06, 0.2.0). A skill's summon, a
device or 海嗣 that was not placed never appears (the hidden 待部署区 deploys nothing by itself); the tacticians' 狼群 /
流形 still come as their 援军 on a tactical point without a piece (content/tokens.js `tacticalPoint`). Their piece stands
inside the tactician's attack range — the prep enforces "只能部署在召唤者攻击范围内" (tokens.json `ownerRange`, player
+1
View File
@@ -32,6 +32,7 @@ export class BattleDeploy {
u.removed = false;
u.hidden = false;
u.body = null;
u.countdown = null; // a countdown summon's content starts its new life in the deploy hook (content/tokens.js startCountdown)
u.x = C0; u.y = R0; u.tileR = R0; u.tileC = C0;
u.blocking = [];
u.deploySeq = ++this._deploySeq;
@@ -4,6 +4,7 @@
import { aggregateMods } from '../../../buffs.js';
import { COLS } from '../../../constants.js';
import { bardRegen } from '../../../professions.js';
import { startCountdown } from '../../tokens.js';
import {
num, bv, tbb, tdesc, moduleBb, parseN, live, enemiesIn, isTok, onDefaultSkill, selectedSkill, aura,
} from '../shared/tier6.js';
@@ -148,6 +149,7 @@ function skadi2(bb, chess, def) {
if (t.profile) t.profile.noAttack = true;
const dur = num(t.def?.talents?.[0]?.bb?.duration, parseN(tdesc(def, 0), /持续(\d+(?:\.\d+)?)秒/, 25));
const seq = t.deploySeq, r = t.tileR, c = t.tileC;
startCountdown(battle, t, dur); // a countdown summon: 无敌, 禁疗, its bar = the life left (content/tokens.js)
battle.after(dur, () => {
if (!t.alive || t.deploySeq !== seq) return;
battle.retreat(t, { reason: 'expired', permanent: true });
+4 -1
View File
@@ -44,6 +44,7 @@ import { bodyKeys, bodyInKeys } from '../../../body.js';
import { toLocal } from '../../../dir.js';
import { hasHp } from '../../../damage.js';
import { COLS } from '../../../constants.js';
import { startCountdown } from '../../tokens.js';
const S1 = 'skchr_bgsnow_1';
const S2 = 'skchr_bgsnow_2';
@@ -141,9 +142,11 @@ function typewriterKit(t, owner, chess) {
} }],
install(battle, unit) {
if (sk?.id === TS1) installIamb(battle, unit, num(tb.prob), num(tb.atk_scale, 1), skillOn);
// 持续25秒: leaves `life` s after each deployment
// 持续25秒: leaves `life` s after each deployment — a countdown summon (content/tokens.js startCountdown): 无敌,
// 禁疗 [ASSUMED], its bar = the life left
battle.on('deploy', (ctx) => {
if (ctx.unit !== unit || !(life > 0)) return;
startCountdown(battle, unit, life);
const seq = unit.deploySeq;
battle.after(life, () => { if (unit.alive && unit.deploySeq === seq) battle.retreat(unit, { reason: 'expired', permanent: true }); }, { owner: unit });
}, { owner: unit });
+2 -1
View File
@@ -63,7 +63,7 @@
import { num, up, traitBb, talentBb, skillRec, once } from '../shared/tier1.js';
import { isElite, onElementHit, elementDmg } from '../shared/tier6.js';
import { releaseSkillSummon } from '../../tokens.js';
import { releaseSkillSummon, startCountdown } from '../../tokens.js';
import { absoluteRangeKeys } from '../../../targeting.js';
import { bodyInKeys } from '../../../body.js';
import { hasHp, burstLocked } from '../../../damage.js';
@@ -124,6 +124,7 @@ function callKit(t, owner, b2) {
lured = [];
endAt = battle.time + life;
atk = owner.s.atk; // 缓存攻击力: her ATK at the deployment (PRTS 备注)
startCountdown(battle, tok, life); // a countdown summon (content/tokens.js): 无敌, 禁疗 [ASSUMED], its bar = the life left
const seq = tok.deploySeq;
battle.after(life, () => { if (tok.alive && tok.deploySeq === seq) battle.retreat(tok, { reason: 'expired', permanent: true }); }, { owner: tok });
}, { owner: tok });
+2
View File
@@ -62,6 +62,7 @@ import { acquireTargets, effectiveProfile } from '../../../ai.js';
import { sortEnemyTargets } from '../../../targeting.js';
import { bodyInKeys } from '../../../body.js';
import { PUSH_DIRECTIONAL_MIN_DIST } from '../../../constants.js';
import { startCountdown } from '../../tokens.js';
const S1 = 'skchr_ray_1';
const S2 = 'skchr_ray_2';
@@ -225,6 +226,7 @@ export default {
m.rayBullets = 0;
m.rayCounted = 0;
battle.setExtraRange(unit, m.rayAreaKeys);
startCountdown(battle, t, life); // a countdown summon (content/tokens.js): its bar = the life left
const seq = t.deploySeq;
battle.after(life, () => { if (t.alive && t.deploySeq === seq) battle.retreat(t, { reason: 'expired', permanent: true }); }, { owner: unit });
}, { owner: unit });
+3 -1
View File
@@ -48,6 +48,7 @@
import { num, traitBb, skillRec, up, giveSp } from '../shared/tier1.js';
import { absoluteRangeKeys } from '../../../targeting.js';
import { COLS } from '../../../constants.js';
import { startCountdown } from '../../tokens.js';
const S1 = 'skchr_weedy_1';
const S2 = 'skchr_weedy_2';
@@ -174,9 +175,10 @@ function cannonKit(t, owner) {
},
install(battle, unit) {
battle.addBuff(unit, { key: 'trait:weedyCannon', flags: { noHeal: true }, persist: true, allowDead: true }); // 持有禁疗
// 部署后20秒自动撤退
// 部署后20秒自动撤退 — a countdown summon (content/tokens.js startCountdown): 无敌 [ASSUMED], 禁疗, its bar = the life left
battle.on('deploy', (ctx) => {
if (ctx.unit !== unit || !(life > 0)) return;
startCountdown(battle, unit, life);
const seq = unit.deploySeq;
battle.after(life, () => { if (unit.alive && unit.deploySeq === seq) battle.retreat(unit, { reason: 'expired', permanent: true }); }, { owner: unit });
}, { owner: unit });
+39 -4
View File
@@ -5,8 +5,10 @@
// stats / talents / trait / skill at the owner's phase — a golden owner gets the `_b` variant).
// Every tokenId of data/tokens.json has a kit, so `battle.spawnToken(owner, tokenId, r, c)` works with data defaults
// even when the summoner's kit passes no options:
// 医疗探机 heal profile (data), untargetable, self-destructs after its withdraw skill time (10 s)
// 诅咒娃娃 no attack, aura: enemies in its range ATK/DEF + bb.atk/bb.def (−25 %/−30 %), 15 s (skill duration);
// 医疗探机 heal profile (data), untargetable, self-destructs after its withdraw skill time (10 s); a countdown
// summon (COUNTDOWN_SUMMONS: 无敌, 禁疗, its bar = the life left)
// 诅咒娃娃 no attack, aura: enemies in its range ATK/DEF + bb.atk/bb.def (−25 %/−30 %), 15 s (skill duration; a
// countdown summon);
// leaves when 巫恋 leaves (PRTS 备注) — the drone stays when 赫默 leaves (PRTS 医疗探机 备注)
// 沙之碑 on appear: owner ATK × atk_scale arts + stun (skill range 3×3), blocks 3 (no attack), talent duration 20 s
// 战术装备 on appear: stun around (bb.stun), blocked enemies DEF + talent def (−160), talent duration 25 s
@@ -14,7 +16,7 @@
// 斯卡蒂的海嗣 untargetable range extension of 浊心斯卡蒂: her trait's 生命回复速度 on the allies in its range (owner
// ATK × trait ratio /s);
// while the owner's skill runs: owner ATK × atk_scale true dmg/s to enemies + 鼓舞 owner ATK × bb.atk;
// talent duration 25/30 s, then redeploys after respawnTime (30/25 s, DP cost from data)
// talent duration 25/30 s (a countdown summon), then redeploys after respawnTime (30/25 s, DP cost from data)
// “耀阳” on appear: owner ATK × atk_scale true + stun in the skill grid (+1 hit if the previously deployed
// operator is 卡西米尔); golden trait ×atk_scale vs blocked enemies; lasts while the owner's skill runs
// 纸偶 on appear: token ATK × damage_scale arts to the 8 surrounding tiles; does not block (data)
@@ -76,6 +78,7 @@
// makes the token (Battle.spawnToken also refuses summons the owner's loadout does not produce).
//
// Exports for other content: spawnYanyou, spawnMapChar, findSummonTile, summonToken, tacticalPoint, wolfShadows,
// COUNTDOWN_SUMMONS / startCountdown (the countdown summons' 无敌 + 禁疗 and timer bar, shared with the kits that time them),
// wolfShadowInterval, installWolfTacticalPoint, wolfTacticalPoint, wolfReturnNow (the 狼群's 战术点形态, shared with the
// 伺夜 kit's own pack, kits/ops/chess_char_3_19-vigil.js), releaseSkillSummon, SKILL_SUMMON_START_DEPLOY, CAT_SHIELD_KEY,
// TOKEN_IDS; mapCharTalents and touchGospel (Touch's 攫升 / 超脱 and 恳切福音, shared with the Touch 补位 stand-in kit,
@@ -192,10 +195,42 @@ function untargetable(battle, unit) {
battle.addBuff(unit, { key: 'trait:untargetable', flags: { untargetable: true }, persist: true, allowDead: true });
}
/** Withdraw the token `seconds` after this deployment (the same deployment only). */
/**
* Countdown summons — the summons that "不会受到攻击" and leave after a fixed time: 赫默's 医疗探机 (10 s, skcom_withdraw),
* 巫恋's 诅咒娃娃 (15 s, its skill), 浊心斯卡蒂's 斯卡蒂的海嗣 (talent 远古血亲 25 / 30 s) and, among the 自选 operators, 温蒂's
* 工程蓄水炮 (20 s), 莱伊's 沙地兽 (25 s), 鸿雪's “打字机” (25 s) and 酒神's 本能的召唤 (10 s). Community report of 2026-10-06
* (the official mode): they take no outside damage — 活性源石 included —, no operator heals them and their bar runs down
* like a timer, the summon leaving when it is empty. PRTS / client data: 海嗣 and 沙地兽 hold 无敌 + 禁疗 (PRTS 海嗣 备注
* 「持有禁疗、无敌」; token prefabs: abnormal flags 5 + 7), 工程蓄水炮 禁疗 (PRTS 备注); the others have no abnormal flag in the
* base-game prefabs — [ASSUMED] the report's 无敌 + 禁疗 for every one of them. startCountdown gives them both (their HP never
* moves) and the bar (Unit.countdown, shown by snapshot.js as the share of the life left). The other timed summons (沙之碑,
* 战术装备, “小自在”, 结构性原理 …) block and are attacked: their HP stays HP. Until 0.2.0 a 海嗣 on 活性源石 lost 70 HP/s (of
* 100) and a hurt drone drew every medic's heals.
*/
export const COUNTDOWN_SUMMONS = Object.freeze(new Set([
TOKEN_IDS.healDrone, TOKEN_IDS.curseDoll, TOKEN_IDS.seaborn,
'token_10009_weedy_cannon', 'token_10034_ray_sndbst', 'token_10026_bgsnow_subbow', 'token_10054_phatm2_encdool',
]));
const COUNTDOWN_KEY = 'token:countdown';
/**
* A countdown summon's life on the field starts (COUNTDOWN_SUMMONS; call it from the `deploy` hook of each deployment,
* where its kit schedules its withdrawal): `seconds` from now its bar is empty. It holds 无敌 and 禁疗 from then on. A
* summon that is not a countdown one is left alone (false).
*/
export function startCountdown(battle, unit, seconds) {
const s = num(seconds, 0);
if (!unit || !COUNTDOWN_SUMMONS.has(unit.defId) || !(s > 0)) return false;
unit.countdown = { from: battle.time, until: battle.time + s };
if (!unit.findBuff(COUNTDOWN_KEY)) battle.addBuff(unit, { key: COUNTDOWN_KEY, flags: { invulnerable: true, noHeal: true, healFree: true }, persist: true, allowDead: true });
return true;
}
/** Withdraw the token `seconds` after this deployment (the same deployment only); a countdown summon's bar runs with it. */
function scheduleLifetime(battle, unit, seconds) {
const s = num(seconds, 0);
if (!(s > 0)) return;
startCountdown(battle, unit, s);
const seq = unit.deploySeq;
unit.mem.expiresAt = battle.time + s;
battle.after(s, () => { if (unit.alive && unit.deploySeq === seq) battle.retreat(unit, { reason: 'expired', permanent: true }); }, { owner: unit });
+7 -2
View File
@@ -1,6 +1,7 @@
// server/sim/snapshot.js — compact serialization for clients (DESIGN §8.2).
//
// b.snap = { fieldId, t, units: [[id, x, y, hp, maxHp, sp, spMax, flags, anim]], dp, killed, total }
// (hp of a countdown summon — unit.countdown, content/tokens.js startCountdown — is maxHp × the share of its life left)
// UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?,
// form?, skillIndex?, moduleId?, items?, standInFor?, diy? } (standInFor = the replaced operator's charId of a 补位
// stand-in; diy = a 自选 piece's pick { charId, skillIndex, uniEquipId } — defId is its slot, spine / avatar the operator's;
@@ -107,9 +108,13 @@ export function unitTuple(u, t) {
sp = spMax * (sk.timeLeft / sk.duration);
}
}
// hp is rounded up (a living unit never shows 0) but never above the rounded max HP
// hp is rounded up (a living unit never shows 0) but never above the rounded max HP. A countdown summon (医疗探机, 海嗣 …:
// content/tokens.js startCountdown) shows the share of its life left instead — its bar runs down like a timer and it
// leaves when it is empty (community report of 2026-10-06; its HP itself never moves: 无敌, 禁疗)
const maxHp = Math.max(1, Math.round(u.s.maxHp));
const hp = u.alive ? Math.min(Math.max(1, Math.ceil(u.hp)), maxHp) : 0;
const cd = u.alive ? u.countdown : null;
const left = cd ? Math.max(0, Math.min(1, (cd.until - t) / Math.max(1e-9, cd.until - cd.from))) : 1;
const hp = u.alive ? Math.min(Math.max(1, Math.ceil(cd ? maxHp * left : u.hp)), maxHp) : 0;
return [u.id, r2(u.x), r2(u.y), hp, maxHp, r1(sp), spMax, flagsOf(u), animOf(u, t)];
}
+3
View File
@@ -81,6 +81,9 @@ export class Unit {
this.burstPending = null; // { [element]: true } while that element's burst resolves (damage.js burstLocked)
this.tags = new Set(init.tags || []);
this.mem = {}; // free scratch space for content (per unit)
// a countdown summon's life on the field ({ from, until } battle times; content/tokens.js startCountdown, cleared by
// every deployment): its bar shows the time left (snapshot.js unitTuple), its HP never moves (无敌 + 禁疗)
this.countdown = null;
this.trait = {}; // profession runtime state
this.stats = { dmg: 0, kills: 0, heal: 0, taken: 0, attacks: 0 };
this.hidden = false; // enemies inside a DISAPPEAR segment
+2 -2
View File
@@ -1712,7 +1712,7 @@
"hooks": [1,28,1790,191,191,208,208,32,18,21,16,16,0,8141,3,27,17,1,1,18,0,0,0],
"events": {"atk":226,"deploy":11,"die":20,"dmg":208,"engage":6,"fx":54,"heal":32,"leak":1,"skill":32,"spawn":26,"status":26},
"fx": {"aoe":1,"displace":19,"liberate":3,"phase":4,"pull":19,"strike":3,"summon":2,"volley":3},
"snaps": "4f49e250 3e34819c f8f00406 187f8d75 6cef4054 31a272c7"
"snaps": "351c8009 987b22dd 7d790a0c fadc11f1 fb446f70 8805598c"
},
"bond-visiShip-high": {
"about": "远见 high: tier 1 layers 999; chess_char_4_17_b chess_char_4_18_b chess_char_3_03_b chess_char_4_19_b chess_char_4_16_b chess_char_2_02_b chess_char_4_20_b chess_char_4_21_b token_10000_silent_healrb",
@@ -1750,7 +1750,7 @@
"hooks": [1,34,2169,158,158,244,298,40,22,25,15,14,0,3657,17,18,18,0,1,22,0,0,9],
"events": {"atk":162,"deploy":14,"die":25,"dmg":298,"engage":4,"fx":92,"heal":23,"skill":31,"spawn":29,"status":31},
"fx": {"dodge":9,"featherArrow":1,"flame":3,"garrisonGrant":1,"healField":1,"pulse":43,"riposte":9,"summon":4,"swordRain":17,"swordStorm":4},
"snaps": "8108fcf3 523bf24b 4a55bea2 c6042705 c8db73ef 3aeb96c2 7a035fd1 8b9f2773"
"snaps": "f2695385 772aaaa3 406ef0c7 3308eca7 65753010 432ff52b 07e6e68b 2aca82b1"
},
"bond-yanShip-low": {
"about": "炎 low: tier 1 layers 1; chess_char_3_16_a chess_char_3_03_a chess_char_3_18_a chess_char_1_03_a chess_char_2_04_a chess_char_3_14_a chess_char_3_17_a chess_char_3_19_a token_10028_vigil_wolf",
+32 -29
View File
@@ -81,7 +81,7 @@
"hooks": [1,70,6000,1208,1208,2984,3373,4986,32,41,94,88,9,39706,2798,15,29,0,1,32,0,21,0],
"events": {"atk":1512,"deploy":35,"die":41,"dmg":3089,"engage":17,"fx":630,"heal":489,"skill":182,"spawn":68,"status":684},
"fx": {"beam":54,"blink":1,"bombard":9,"bombardShell":9,"burst":21,"copy":3,"dp":4,"drone":335,"droneLock":17,"drones":3,"firewall":1,"garrisonGrant":1,"lightning":108,"link":1,"lock":9,"radiantSword":4,"shell":8,"snow":5,"split":2,"storm":10,"summon":8,"talent":4,"tide":5,"wanted":8},
"snaps": "d9836110 12e5b436 35c19c21 8b602c4b 63a29594 e92667ad 11995740 f731d336 2bcec2e1 d8cbc3bd 9017d64b 51d18505 e6ef48bf 94c31471 ab8f2380 1b98c997 13c68f78 a6b5eaa0 e318ae23 fd4a1367"
"snaps": "72dc81cb e1b819af f6f48b33 12da5c05 71ab4302 b8094e94 48f11686 168adccb 8baeac29 a827fcb5 f2c51503 f6ac9a0e 26f4f8c7 1e115b43 3689b856 f9c504d5 6a28e830 a1160c66 1e744db9 78024c8a"
},
"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",
@@ -325,7 +325,7 @@
"tide": 5,
"wanted": 3
},
"snaps": "b5e578c7 08019f4b 2414497e e8ccf404 0a757964 27f19ffa dc979221 9a9cddad 37887448 9597c3ad 3331f98b 22dca952 01026ee5 afe32a6d 65ca8fec ed617f42 3ed37aad 2975ad94 80313e62 c870870c"
"snaps": "87a11c35 cc0e1f70 2de88ef7 a5a4efc1 36878065 d2f20d65 a5481200 93371ab3 fa830c40 8d59e885 222cca53 040b3f01 dc7f3982 cca5f067 8a116542 f1f56dac c68f3562 6833a03c 12743320 0f665121"
},
"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",
@@ -606,7 +606,7 @@
"tide": 3,
"wanted": 8
},
"snaps": "726b45e6 655ee79a 912e8d78 fe351355 d02de871 2de23192 90fc2124 37da201e 332c960d a6a1a21f 4df5e422 d780f15c"
"snaps": "eca813c6 f271e40a c26e5584 d73b51c9 a7c760e5 818a39be 850539df a8a1d78c 1c2b94fb 61f626e7 15b5d45a 89f18974"
},
"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",
@@ -865,7 +865,7 @@
"hooks": [1,52,6000,1475,1475,3513,3867,3107,22,29,95,90,263,39065,1638,4,17,0,1,22,0,13,0],
"events": {"atk":1666,"deploy":35,"die":29,"dmg":3557,"engage":19,"fx":565,"heal":514,"skill":185,"spawn":50,"status":588},
"fx": {"airstrike":92,"beam":4,"blink":2,"burst":13,"column":47,"copy":4,"dp":5,"drone":334,"droneLock":16,"drones":3,"explode":6,"garrisonGrant":1,"link":1,"phase":1,"shieldBreak":4,"snow":5,"split":5,"steal":7,"sunBurst":4,"talent":4,"tide":5,"wanted":2},
"snaps": "aac21795 826f3a92 c07605ac 63ba2617 10d034dd f637231c 679eaf63 4484256d 932a1025 95a8f717 9f641051 49ed0ece cc247786 d907a3ce 4899d3c0 a603253f 2e8677a7 fc4cd6c6 ce276a0b 7cb6d808"
"snaps": "7ec7bc8a 80b84ac8 9ef4ab5e 2af0def9 18e8a357 b2a774b5 3da4fec2 83128f9e baca593e da6a4488 535145ce d7c8184c 6695103d 37c5ae18 9014d962 41b8cdf9 a33c7228 5e09f2b0 bd896428 bffa666e"
},
"boss-boss_7-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",
@@ -1077,36 +1077,36 @@
"killed": 8,
"total": 8,
"unspawned": 0,
"bossHpLeft": 4049210,
"bossHpLeft": 4046346,
"rngDraws": 1680,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1426827,"bossDmg":597682,"heal":13214,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2554978,"bossDmg":913911,"heal":130784,"deaths":8,"layerGains":{}}
"p1": {"killed":8,"total":8,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":1435418,"bossDmg":600545,"heal":13215,"deaths":5,"layerGains":{}},
"p2": {"killed":0,"total":0,"perfect":true,"leaks":0,"leaksUncounted":0,"coins":0,"dmg":2553890,"bossDmg":913911,"heal":130783,"deaths":8,"layerGains":{}}
},
"units": [
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",0,0,58,0,0,10883,4690,0,2],
["p1",1,"op","chess_char_6_03_b",2,"uniequip_002_yu",0,0,60,0,0,10883,4690,0,2],
["p1",2,"op","chess_char_6_06_b",2,"uniequip_002_pepe",2939,1,11,0,23455,2258,0,35,1],
["p1",3,"op","chess_char_6_07_b",2,"uniequip_002_siege2",2990,1,17,0,52147,2547,0,50,2],
["p1",4,"op","chess_char_6_01_b",2,"uniequip_002_lemuen",99.06,1,7,1,1043569,3226,0,57,8],
["p1",5,"op","chess_char_6_02_b",2,"uniequip_002_sbell2",931.61,1,9,0,31824,10406,0,46,2],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",745.66,1,52,0,8749,9134,562,0,4],
["p1",6,"op","chess_char_6_04_b",2,"uniequip_002_skadi2",745.66,1,52,0,17203,9134,563,0,4],
["p1",7,"op","chess_char_6_05_b",2,"uniequip_002_pasngr",2451.4,1,13,1,81581,0,0,0,11],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,1,32005,0,7161,70,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,1,131535,12922,0,27,27],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,56,0,14353,9097,349,78,2],
["p1",11,"token","token_10017_skadi2_dedant",null,null,0,0,28,0,0,130,0,0,null],
["p1",8,"op","chess_char_6_08_b",2,"uniequip_002_reed2",1668,1,19,0,31409,0,7161,70,2],
["p1",9,"op","chess_char_6_09_b",0,"uniequip_002_cello",0,0,21,2,130635,12922,0,27,27],
["p1",10,"op","chess_char_6_11_b",2,"uniequip_002_mlyss",1893,1,57,0,15985,9097,349,78,2],
["p1",11,"token","token_10017_skadi2_dedant",null,null,130.4,0,28,0,0,0,0,0,null],
["p1",12,"token","token_10030_mlyss_wtrman",null,null,0,0,24,0,0,1614,292,4,1],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",0,0,60,3,471039,50514,0,332,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,61,0,247360,13406,0,68,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,59,0,13275,9642,2640,45,2],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",2458.52,1,62,1,509240,22200,0,58,2],
["p2",13,"op","chess_char_6_15_b",2,"uniequip_002_qiubai",0,0,62,3,469951,50514,0,332,3],
["p2",14,"op","chess_char_6_19_b",2,"uniequip_002_blkkgt",0,0,63,0,247360,13406,0,68,4],
["p2",15,"op","chess_char_5_01_b",1,"uniequip_002_excu2",0,0,61,0,13275,9642,2640,45,2],
["p2",16,"op","chess_char_6_17_b",2,"uniequip_002_nearl2",2458.52,1,64,1,509240,22200,0,58,2],
["p2",17,"op","chess_char_6_12_b",1,"uniequip_002_rosmon",2886.8,1,8,0,264522,6236,0,75,3],
["p2",18,"op","chess_char_6_13_b",1,"uniequip_002_angel2",2215,1,14,0,662969,6421,4283,285,6],
["p2",19,"op","chess_char_6_14_b",2,"uniequip_002_lumen",2222.83,1,20,0,0,2781,19999,79,1],
["p2",20,"op","chess_char_6_16_b",2,"uniequip_002_halo2",0,0,10,0,80392,3176,881,101,3],
["p2",21,"op","chess_char_6_18_b",2,"uniequip_002_whitw2",1331,1,27,0,255595,5370,0,32,3],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",0,0,25,0,0,17059,102981,70,2],
["p2",22,"op","chess_char_6_20_b",2,"uniequip_002_agoat2",0,0,25,0,0,17059,102980,70,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],
@@ -1118,20 +1118,23 @@
["p1","~2","token","token_10030_mlyss_wtrman",null,null,131.08,0,48,0,1401,2025,0,19,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,49,0,120,1322,0,2,null],
["p1","~1","token","token_10040_siege2_vlion",null,null,3722.5,0,50,0,4750,277,0,10,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,3093.17,0,53,0,44047,4069,0,25,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,3933.26,0,55,0,0,81,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,57,0,0,180,160,0,1]
["p1","~1","token","token_10017_skadi2_dedant",null,null,130.4,0,53,0,0,0,0,0,null],
["p2","~2","token","token_10019_nearl2_sword",null,null,3093.17,0,54,0,44047,4069,0,25,null],
["p1","~2","token","token_10040_siege2_vlion",null,null,3933.26,0,56,0,0,81,0,0,null],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,1322,1,58,0,0,180,160,0,1],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,59,0,0,0,0,0,null],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,65,0,0,0,0,0,null]
],
"enemies": {
"enemy_1267_nhpbr_2": [6,6,0,2895,141894],
"enemy_9007_acelem": [2,2,0,23398,309587],
"enemy_9017_achunt_2": [2,0,0,59262,1550790],
"enemy_9018_actrpa": [1,0,0,43264,577004],
"enemy_9019_actrpb": [1,0,0,21943,535849],
"enemy_9020_actrpc": [1,0,0,24229,938579]
"enemy_9017_achunt_2": [2,0,0,59131,1553654],
"enemy_9018_actrpa": [1,0,0,43264,579324],
"enemy_9019_actrpb": [1,0,0,21943,537009],
"enemy_9020_actrpc": [1,0,0,24229,939739]
},
"hooks": [1,60,6000,1767,1767,7110,13865,2612,25,35,93,88,317,45858,2401,14,13,0,1,29,0,17,0],
"events": {"atk":2141,"deploy":41,"die":35,"dmg":13390,"engage":22,"fx":2180,"heal":905,"skill":181,"spawn":49,"status":750},
"hooks": [1,63,6000,1767,1767,7114,13887,2610,24,37,93,88,317,45858,2398,14,13,0,1,28,0,17,0],
"events": {"atk":2140,"deploy":44,"die":37,"dmg":13412,"engage":22,"fx":2180,"heal":906,"skill":181,"spawn":52,"status":751},
"fx": {
"airstrike": 99,
"beam": 24,
@@ -1173,7 +1176,7 @@
"wanted": 6,
"zone": 6
},
"snaps": "9797dd57 d3ba8d58 32d06887 bf42877c 1994874a e5491557 66d383f3 1312fd65 b74b9f4a 2f28845d ead771e6 c391a1a4 f923753b 23bee15f caa10968 93e7fa5f 8c6cfd06 4d79d333 2710458e 1a607645"
"snaps": "72f6af28 6f72c7d4 04c65e53 d68ea41c 34b0f4bf 38a30bbc b2d8c8dd 8d93cc3f 748b9cb6 4a9137e0 a01dbf1e ebdbc3fd a7ffe178 2c92480a 1e31807b 97b40094 e5feed1a 763383f6 f063bb7e d211d016"
},
"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",
@@ -1397,7 +1400,7 @@
"hooks": [1,25,5100,20,20,57,129,2358,12,15,6,5,7,9981,13,1,11,0,1,12,0,2,0],
"events": {"atk":28,"bounty":1,"deploy":14,"die":15,"dmg":78,"engage":3,"fx":57,"skill":11,"spawn":22,"status":43},
"fx": {"bombard":7,"bombardShell":7,"burst":2,"camouflage":1,"lightning":14,"lock":7,"lure":1,"snow":12,"storm":1,"talent":1,"tide":3,"wanted":1},
"snaps": "9845e2f6 5498943b a2c8865c 4947e8f1 7b75af29 70cf729f e3aec65c 1ab76abd 919d0c1a 8f2a907c 676cd0dc 4c514cee d56b2743 decf1034 44bec2d5 8b318871 593802f3"
"snaps": "c558a4df 5e12c668 02dad932 00fbc7af bc41cf43 ad41bea1 6e07fbe6 27dde52f 53b9a18c a640dd14 452ad2b3 46658458 8c81ee61 4c70f7fe c0fba143 3b8d81c6 aa0fc004"
},
"unite-2": {
"about": "联防 2 helper(s), 11 leaked: chess_char_6_15_b chess_char_6_17_b chess_char_6_09_b chess_char_6_11_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b token_10030_mlyss_wtrman | chess_char_6_19_b chess_char_5_01_b chess_char_5_05_b chess_char_5_06_b chess_char_6_18_b chess_char_6_20_b chess_char_5_02_b chess_char_5_03_b",
+56 -54
View File
@@ -184,7 +184,7 @@
"hooks": [1,105,3300,580,580,689,804,233,86,90,24,23,0,16480,226,29,85,0,1,90,1,0,0],
"events": {"atk":681,"bounty":6,"deploy":17,"die":90,"dmg":774,"engage":9,"fx":109,"heal":203,"layer":1,"skill":47,"spawn":101,"status":221},
"fx": {"aoe":1,"blink":1,"displace":8,"ember":1,"explode":10,"garrison":1,"heal":5,"phase":1,"pulse":42,"reveal":1,"revive":1,"sleep":8,"summon":25,"undying":1,"zone":3},
"snaps": "c29e3835 28b841b8 6caebbf9 58f57d98 6a790fcb 928cecdc 15ff1081 552544af 00dc4f61 848f1a18 310f6b34"
"snaps": "e30955f8 499cf905 25671f69 6dfb7b8d de2f1106 a052f01c c47c18ad 9c42fbaa 3cf7b1f7 86643fea 37f1f719"
},
"roster-004": {
"about": "pass 0 band 2: chess_char_2_08_a chess_char_2_17_a chess_char_2_18_a chess_char_2_07_a chess_char_2_09_a chess_char_2_11_a chess_char_2_13_a chess_char_2_16_a chess_char_2_12_a chess_char_2_06_a chess_char_2_10_a chess_char_2_14_a",
@@ -263,24 +263,24 @@
"bossHpLeft": null,
"rngDraws": 334,
"errors": {"count":0,"kinds":[]},
"players": {"p1":{"killed":83,"total":84,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":10,"dmg":306697,"bossDmg":0,"heal":37320,"deaths":0,"layerGains":{}}},
"players": {"p1":{"killed":83,"total":84,"perfect":false,"leaks":1,"leaksUncounted":0,"coins":10,"dmg":306697,"bossDmg":0,"heal":37410,"deaths":0,"layerGains":{}}},
"units": [
["p1",1,"op","chess_char_2_07_b",1,"uniequip_002_ghost",2664.2,1,2,0,0,1707,1318,0,0],
["p1",2,"op","chess_char_2_08_b",1,"uniequip_002_bubble",3373,1,4,0,49,1818,0,0,1],
["p1",3,"op","chess_char_2_09_b",1,"uniequip_002_humus",2133,1,6,28,84020,0,0,59,2],
["p1",4,"op","chess_char_2_11_b",1,"uniequip_002_kazema",2214,1,8,12,91244,15658,0,92,3],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7180,9327,0,129,1],
["p1",5,"op","chess_char_2_12_b",1,"uniequip_002_gravel",1452,1,10,2,7180,9417,0,129,1],
["p1",6,"op","chess_char_2_19_a",1,null,1021.2,1,11,1,14957,1433,314,26,3],
["p1",7,"op","chess_char_2_01_b",1,"uniequip_002_excu",2146,1,12,20,36079,4363,0,22,1],
["p1",8,"op","chess_char_2_02_b",1,"uniequip_002_silent",1614,1,1,0,0,0,9366,28,6],
["p1",9,"op","chess_char_2_04_b",1,"uniequip_002_grabds",1270,1,3,10,38233,2115,0,53,3],
["p1",10,"op","chess_char_2_05_b",1,"uniequip_002_harold",1971.2,1,5,0,0,0,11081,40,2],
["p1",11,"op","chess_char_2_06_b",0,"uniequip_002_papyrs",1639,1,7,0,0,0,12106,27,13],
["p1",11,"op","chess_char_2_06_b",0,"uniequip_002_papyrs",1639,1,7,0,0,0,12196,27,13],
["p1",12,"op","chess_char_2_10_b",1,"uniequip_002_rockr",1406,1,9,5,34936,900,0,113,2],
["p1",13,"token","token_10000_silent_healrb",null,null,1000,1,104,0,0,0,3137,30,null]
],
"enemies": {
"enemy_10027_vtsk": [1,0,0,10462,23138],
"enemy_10027_vtsk": [1,0,0,10552,23138],
"enemy_1006_shield_2": [1,1,0,0,8000],
"enemy_10122_uacann_2": [1,1,0,3476,8000],
"enemy_1042_frostd": [1,1,0,0,5200],
@@ -295,10 +295,10 @@
"enemy_1509_mousek": [1,1,0,7192,17600],
"enemy_2010_csdcr": [1,1,0,1986,9600]
},
"hooks": [1,103,4200,714,714,1650,1643,1085,83,89,37,36,0,29028,143,53,84,0,1,83,0,0,0],
"events": {"atk":928,"bounty":5,"deploy":19,"die":89,"dmg":1603,"engage":7,"fx":66,"heal":134,"skill":73,"spawn":97,"status":439},
"hooks": [1,103,4200,714,714,1650,1643,1083,83,89,37,36,0,29028,143,53,84,0,1,83,0,0,0],
"events": {"atk":926,"bounty":5,"deploy":19,"die":89,"dmg":1603,"engage":7,"fx":66,"heal":134,"skill":73,"spawn":97,"status":433},
"fx": {"counter":2,"explode":3,"expose":9,"overload":2,"phase":1,"shield":8,"shieldBreak":16,"sleep":7,"summon":6,"telegraph":6,"zone":6},
"snaps": "de0d6fce aa9dbd18 71199952 51ab68f4 a5c1cf26 39c299cd 389a7224 beb950e8 500c1fa6 c0a3a103 d99c8758 679ff285 e0d2bbaf 675fa07e"
"snaps": "84edc036 a46838b0 1d9bc714 1b570575 aab0920b 4bc6df97 dd7f1064 918ab11b 31267425 a4452600 f9a6edeb 9a33bdd8 c9a35f1c 7d3f9999"
},
"roster-006": {
"about": "pass 0 band 3: chess_char_2_17_b chess_char_2_18_b chess_char_3_06_a chess_char_2_13_b chess_char_2_16_b chess_char_3_02_a chess_char_3_03_a chess_char_3_04_a chess_char_3_05_a chess_char_2_14_b chess_char_2_19_b chess_char_3_01_a",
@@ -1072,7 +1072,7 @@
"hooks": [1,154,6900,951,1017,7281,7430,78804,91,140,40,36,17,35845,1032,57,126,45,1,102,1,7,6],
"events": {"atk":2283,"bounty":3,"deploy":28,"die":95,"dmg":7385,"engage":10,"fx":552,"heal":121,"layer":1,"leak":45,"skill":76,"spawn":142,"status":2410},
"fx": {"aoe":8,"bombard":17,"bombardShell":17,"burst":7,"crit":50,"dodge":6,"explode":40,"firewall":2,"garrison":1,"iceSpike":90,"lightning":148,"lock":17,"lure":3,"revive":1,"snow":82,"soul":1,"sp":1,"storm":5,"talent":2,"telegraph":8,"tide":3,"wanted":38,"weightless":5},
"snaps": "ee17d257 d832b8ac 2ca210aa 3cd3fc22 022376cf a362140e 0ca09d66 1f446040 29245222 70bfb33a 2b4ce12f 51a65ad5 1216d2fa f2d3b899 86e068de 5a0bbda6 1bb53622 0ef45b3c a98167d6 5245239e eac46280 b7e2f8ef 905be587"
"snaps": "76864815 62849bad 979bf882 25b6b06a 5180c227 3ecd4ada 973ab3dc 34c4af0f c0357d8d 21dd059d b2e1cc4c 8a773c5e 69aa5545 f41c0b6e 34d38c11 fd5b4e85 3a8cd96d 541d94f7 b7150f71 e0eeba75 f3b17a4b 864eec43 64dedb33"
},
"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",
@@ -1227,7 +1227,7 @@
"hooks": [1,146,5863,543,543,1347,1516,3220,125,132,42,38,32,26310,394,7,122,0,1,125,11,5,0],
"events": {"atk":634,"bounty":6,"deploy":22,"die":132,"dmg":1435,"engage":13,"fx":264,"heal":169,"layer":11,"skill":80,"spawn":146,"status":444},
"fx": {"beam":5,"bombard":32,"bombardShell":32,"burst":5,"camouflage":2,"copy":1,"dp":5,"garrison":8,"lightning":99,"link":1,"lock":32,"lure":3,"scorchBurst":9,"snow":7,"split":5,"storm":7,"summon":2,"talent":1,"tide":5,"wanted":3},
"snaps": "d98f113c d0331366 be0cebae 241fe6f7 134c6492 39edc9da 96df2aaa b5e300fe 865fba99 4cc163f0 3748edd7 acc90429 29160015 97e57a26 cabd46be 14cde004 c11ac045 97b93efb 317c26e6 835a5563"
"snaps": "9cdb845f 1a3da81a b8f8fa18 18adb6a1 045ac790 92daeb67 e9b25891 6153ba1f 318ab774 dbcd5d11 bd86ccae 2804e1a9 0b939c36 d69ed2f9 d52ba02d c74b0ce7 bf1bc7e3 b8b19432 7a71afd1 478ab631"
},
"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",
@@ -2195,7 +2195,7 @@
"hooks": [1,143,5589,311,311,508,779,843,126,129,71,69,0,13806,388,3,125,0,1,126,2,11,0],
"events": {"atk":502,"bounty":4,"deploy":18,"die":129,"dmg":779,"engage":6,"fx":218,"heal":9,"layer":2,"skill":140,"spawn":142,"status":553},
"fx": {"bombard":3,"bombardShell":3,"burst":11,"copy":1,"expose":117,"frostNova":1,"garrison":2,"heal":3,"phase":1,"shieldBreak":1,"snow":14,"sp":58,"talent":1,"wanted":2},
"snaps": "378db087 c01e4bfb 656d09bb 8ad40d0d 980adb80 0fbc6dfa 0affdeb4 5f9cb80b 96e5686c ad4f2604 0b08bcd0 e88e1b52 18910b94 829e4b19 4a00d92e 630d2588 f81f3544 df9493eb ce728565"
"snaps": "7d4b3aca f6affb06 724b3085 0ddf76eb d68a8dda a623e364 9c9d2e86 9998c540 c8bc041f 5e9130db 6dfbcc7b c6ec5c11 f21a16a2 e1fcd736 9bf63db1 696fbd1b 2c7e2d3c e2ef6c77 9b42d582"
},
"roster-036": {
"about": "pass 1 band 7: chess_char_6_03_b chess_char_6_15_a chess_char_6_19_a chess_char_6_17_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 chess_char_6_20_a chess_char_6_01_b chess_char_6_02_b",
@@ -2264,68 +2264,70 @@
"time": 217.2,
"ticks": 6516,
"reason": "cleared",
"killed": 38,
"killed": 45,
"total": 131,
"unspawned": 0,
"bossHpLeft": null,
"rngDraws": 100,
"rngDraws": 90,
"errors": {"count":0,"kinds":[]},
"players": {
"p1": {
"killed": 38,
"killed": 45,
"total": 131,
"perfect": false,
"leaks": 93,
"leaks": 86,
"leaksUncounted": 0,
"coins": 4,
"dmg": 994699,
"coins": 6,
"dmg": 1011569,
"bossDmg": 0,
"heal": 30633,
"deaths": 25,
"layerGains": {"sargonShip":14}
"heal": 42323,
"deaths": 24,
"layerGains": {"sargonShip":16}
}
},
"units": [
["p1",1,"op","chess_char_6_06_b",0,"uniequip_003_pepe",0,0,89,1,39371,6491,513,13,3],
["p1",2,"op","chess_char_6_15_b",0,"uniequip_003_qiubai",0,0,6,2,124903,6154,3570,27,6],
["p1",3,"op","chess_char_6_07_b",0,"uniequip_003_siege2",0,0,9,2,110073,2661,0,15,5],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,169,0,0,10506,3936,0,3],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,4,302318,1502,0,33,16],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",0,0,96,0,72197,8332,5006,52,1],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,170,0,57079,4352,296,20,2],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,171,3,23389,8393,901,58,2],
["p1",9,"op","chess_char_6_12_b",0,"uniequip_003_rosmon",0,0,60,1,123922,5390,87,22,6],
["p1",10,"op","chess_char_6_13_b",0,"none",1684.2,1,161,0,76484,11597,1724,50,5],
["p1",11,"op","chess_char_6_14_b",0,"uniequip_003_lumen",0,0,15,0,0,12802,14600,13,5],
["p1",12,"op","chess_char_6_16_b",0,"none",0,0,76,2,61875,2648,0,15,1],
["p1",13,"token","token_10017_skadi2_dedant",null,null,0,0,17,0,0,100,0,0,null],
["p1",14,"token","token_10030_mlyss_wtrman",null,null,0,0,11,0,201,1608,0,6,1],
["p1",1,"op","chess_char_6_06_b",0,"uniequip_003_pepe",0,0,90,0,29221,6236,258,7,3],
["p1",2,"op","chess_char_6_15_b",0,"uniequip_003_qiubai",0,0,6,1,115832,4600,2016,23,5],
["p1",3,"op","chess_char_6_07_b",0,"uniequip_003_siege2",0,0,9,0,76591,2661,0,11,4],
["p1",4,"op","chess_char_6_04_b",0,"uniequip_003_skadi2",3523.2,1,172,0,0,20406,11891,0,4],
["p1",5,"op","chess_char_6_05_b",0,"uniequip_003_pasngr",0,0,5,10,297865,1502,0,34,17],
["p1",6,"op","chess_char_6_08_b",0,"uniequip_003_reed2",0,0,98,1,68469,7787,4461,49,1],
["p1",7,"op","chess_char_6_09_b",1,"uniequip_003_cello",1352,1,171,1,72879,4204,148,19,2],
["p1",8,"op","chess_char_6_11_b",0,"uniequip_003_mlyss",1873,1,160,1,48208,7012,1393,94,2],
["p1",9,"op","chess_char_6_12_b",0,"uniequip_003_rosmon",0,0,67,2,165357,7444,2140,26,8],
["p1",10,"op","chess_char_6_13_b",0,"none",2005,1,169,0,71982,10725,2630,45,4],
["p1",11,"op","chess_char_6_14_b",0,"uniequip_003_lumen",0,0,15,0,0,12802,13143,13,5],
["p1",12,"op","chess_char_6_16_b",0,"none",0,0,81,0,62580,3399,751,17,1],
["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,550,4736,3127,14,1],
["-","~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],
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,43,0,522,1497,0,1,1],
["p1","~2","token","token_10030_mlyss_wtrman",null,null,0,0,78,0,497,1192,0,1,1],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,125,0,1778,2145,0,4,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,153,0,90,1805,0,3,1],
["p1","~5","token","token_10030_mlyss_wtrman",null,null,1804.5,1,172,0,0,0,0,0,1]
["p1","~1","token","token_10030_mlyss_wtrman",null,null,0,0,32,0,104,1608,0,3,null],
["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,80,0,0,1865,0,0,0],
["p1","~2","token","token_10017_skadi2_dedant",null,null,100,0,109,0,0,0,0,0,null],
["p1","~3","token","token_10030_mlyss_wtrman",null,null,0,0,125,0,1696,1684,363,5,1],
["p1","~4","token","token_10030_mlyss_wtrman",null,null,0,0,161,0,233,1805,0,6,1],
["p1","~3","token","token_10017_skadi2_dedant",null,null,100,1,173,0,0,0,0,0,null]
],
"enemies": {
"enemy_10042_prtrop": [1,1,0,775,17199],
"enemy_10118_ymgprc": [1,0,1,12755,112019],
"enemy_1045_hammer_2": [18,3,15,3234,313913],
"enemy_1061_zomshd": [60,10,50,34663,543507],
"enemy_10042_prtrop": [1,1,0,2278,17199],
"enemy_10118_ymgprc": [1,0,1,4232,122751],
"enemy_1045_hammer_2": [18,3,15,15972,315355],
"enemy_1061_zomshd": [60,11,49,36815,558519],
"enemy_1071_dftman": [1,1,0,0,8026],
"enemy_1172_dugago": [1,0,1,1428,38609],
"enemy_1210_msfden_2": [1,0,1,0,42],
"enemy_1269_nhfly": [45,23,22,19110,3747],
"enemy_1172_dugago": [1,0,1,2855,46471],
"enemy_1210_msfden_2": [1,1,0,0,45],
"enemy_1269_nhfly": [45,28,17,22220,0],
"enemy_1321_wdarft": [1,0,1,0,0],
"enemy_1501_demonk": [1,0,1,13122,15956],
"enemy_2052_smgia": [1,0,1,0,43424]
"enemy_1501_demonk": [1,0,1,11941,20299],
"enemy_2052_smgia": [1,0,1,0,22008]
},
"hooks": [1,170,6516,408,408,2395,2585,24079,69,163,61,57,27,9225,1280,27,131,93,1,48,2,5,0],
"events": {"atk":518,"bounty":2,"deploy":36,"die":70,"dmg":2504,"engage":15,"fx":80,"heal":48,"layer":2,"leak":93,"skill":118,"spawn":153,"status":679},
"fx": {"airstrike":6,"aoe":11,"burst":5,"copy":6,"explode":23,"garrison":2,"heal":3,"healField":5,"link":2,"revive":1,"stone":1,"summon":15},
"snaps": "79552059 e5891b97 05581a54 4cf8b8b4 4c68e31e 6d0f79f9 326b9fac c54d2228 53ac3aee 4caa7e6f 63d4e63f 466be41c 29f5fd64 c6cb8659 fe3e5972 f93d955b 3456f391 0ca7b782 058d8c95 51f8fe78 675ec88f 7ffc0c56"
"hooks": [1,171,6516,450,450,2452,2627,24832,74,164,59,56,25,9642,1397,32,131,86,1,49,2,6,0],
"events": {"atk":560,"bounty":3,"deploy":37,"die":78,"dmg":2552,"engage":14,"fx":83,"heal":40,"layer":2,"leak":86,"skill":115,"spawn":155,"status":670},
"fx": {"airstrike":5,"aoe":9,"burst":6,"cleanse":1,"copy":3,"explode":28,"garrison":2,"heal":4,"healField":5,"link":2,"revive":1,"split":1,"stone":1,"summon":15},
"snaps": "aad8bee8 801856dd 3399a410 b92f8209 23973744 57bbfa1c 9ffd3055 be8deeb1 0bdabeab bda375ae 4afc7560 c160fb12 28f439d9 6584b7fd d1fd5246 16ea2222 4f142c9c dd788125 251f8a15 260c47ec 822d6d6e 1f8efd14"
},
"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",
@@ -2807,7 +2809,7 @@
"hooks": [1,91,5571,428,415,892,1381,11450,69,74,12,9,10,22992,758,17,68,0,1,69,2,51,0],
"events": {"atk":524,"bounty":4,"deploy":20,"die":74,"dmg":1196,"engage":8,"fx":125,"layer":2,"skill":21,"spawn":90,"status":1451},
"fx": {"airstrike":5,"anchor":1,"burst":51,"dp":3,"expose":6,"garrison":2,"link":2,"snow":5,"summon":2,"summonStun":2,"talent":1,"wanted":45},
"snaps": "cf9f9e00 4757b940 5f298e20 410a2d5d d84d51b4 7fc5ffdd 6924e4f0 afd930c6 a7d4ff63 35d5e664 a27fdae2 8dc50ed9 19f8fcbd 461d841e 4ffb9bc5 2cc4d1a8 729b0562 d4ce4f77 c940e5b3"
"snaps": "10fd15da 83cddf7f c846b231 d53dbd5c 231b2365 73a4cfd5 dbf3e959 b191323a 564195d7 3b4974b0 9b85bd56 82fc4a77 d312a559 0c646576 98456a8d a4735a50 8734e3fa 41a0eaf2 8280a082"
},
"roster-046": {
"about": "pass 2 band 7: chess_char_6_03_b chess_char_6_15_a chess_char_6_19_a chess_char_6_17_a chess_char_6_14_a chess_char_6_16_a chess_char_6_18_a chess_char_6_20_a chess_char_6_01_b chess_char_6_02_b chess_char_6_04_b chess_char_6_05_b token_10017_skadi2_dedant",
@@ -2862,7 +2864,7 @@
"hooks": [1,165,6669,226,226,503,516,740,25,162,9,9,5,4004,335,15,134,133,1,26,1,1,0],
"events": {"atk":332,"bounty":1,"deploy":31,"die":29,"dmg":507,"engage":8,"fx":167,"heal":18,"layer":1,"leak":133,"skill":18,"spawn":149,"status":425},
"fx": {"blessing":1,"blink":2,"burst":1,"camouflage":2,"displace":1,"explode":71,"garrison":1,"garrisonGrant":2,"link":1,"snow":7,"sunBurst":3,"talent":2,"telegraph":2,"zone":71},
"snaps": "fd8ace00 3735d1a0 ac4c99f8 c5d4ca9a 8fded089 880882c7 e721adb9 0ae8c4ec e7907ddb 600d4aaf 8ba41e7f 29e57375 d2a53406 bc57a24b 0f0f5742 e95912b1 d765e411 ac479235 f1961017 bebbcde2 d00f5670 5d45684c ab83875e"
"snaps": "7d1bb456 b462eb12 665997c4 d3b0731e 733c887d 81c9ff7b d61bcd86 d87fe69c 579299cc 8f123aac dd0783cc 48859e06 0d12afe5 4eadb4fc b655b87d aa32fec6 c40fe17e e5480f12 c8fc10e0 5c4a7375 b8961ef3 38a3e52e 2342edcb"
},
"roster-047": {
"about": "pass 2 band 7: chess_char_6_06_b chess_char_6_15_b chess_char_6_07_b chess_char_6_17_b chess_char_6_08_b chess_char_6_09_b chess_char_6_11_b chess_char_6_12_b chess_char_6_13_b chess_char_6_14_b chess_char_6_16_b chess_char_6_18_b token_10030_mlyss_wtrman",
@@ -0,0 +1,72 @@
// test/sim/feedback5-countdown-summons.test.js — the countdown summons (community report of 2026-10-06 「赫默的无人机、浊心
// 斯卡蒂的海嗣等这种召唤物不会受到外来伤害(包括源石地板),也不会被干员治疗,血量随时间减少(血量就类似倒计时条,减少完召唤物就
// 消失)」): the summons that "不会受到攻击" and leave after a fixed time — 医疗探机, 诅咒娃娃, 斯卡蒂的海嗣 and the 自选 ones
// (工程蓄水炮, 沙地兽, “打字机”, 本能的召唤) — hold 无敌 + 禁疗 (PRTS 海嗣 / 沙地兽 「持有禁疗、无敌」, 工程蓄水炮 禁疗; the others
// [ASSUMED] from the report) and their bar (the snapshot's hp) shows the share of their life left (content/tokens.js
// COUNTDOWN_SUMMONS / startCountdown). Until 0.2.0 a 海嗣 on 活性源石 lost 70 of its 100 HP every second and a hurt drone
// drew the medics' heals. The 吟游者 trait (item 32) cannot refill such a bar either: their HP never moves.
// Run: node --test test/sim/feedback5-countdown-summons.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, checkInvariants } from '../helpers/battleHarness.js';
import { unitTuple } from '../../server/sim/snapshot.js';
import { COUNTDOWN_SUMMONS } from '../../server/sim/content/tokens.js';
/** [label, owner entry (uid 1), token id, life in s] — every placed countdown summon of the pool and of the 自选 pool. */
const CASES = [
['赫默 医疗探机', { chessId: 'chess_char_2_02_a' }, 'token_10000_silent_healrb', 10],
['巫恋 诅咒娃娃', { chessId: 'chess_char_3_15_a' }, 'token_10006_vodfox_doll', null],
['浊心斯卡蒂 海嗣', { chessId: 'chess_char_6_04_a' }, 'token_10017_skadi2_dedant', 25],
['温蒂 工程蓄水炮', { diy: { slot: 5, charId: 'char_400_weedy', skillIndex: 0, uniEquipId: null } }, 'token_10009_weedy_cannon', 20],
['莱伊 沙地兽', { diy: { slot: 5, charId: 'char_4117_ray', skillIndex: 0, uniEquipId: null } }, 'token_10034_ray_sndbst', 25],
['鸿雪 打字机', { diy: { slot: 5, charId: 'char_4055_bgsnow', skillIndex: 0, uniEquipId: null } }, 'token_10026_bgsnow_subbow', 25],
];
function field(owner, tokenId) {
const h = makeBattle({
units: [{ uid: 1, row: 10, col: 3, ...owner }, { uid: 2, kind: 'token', tokenId, ownerUid: 1, row: 10, col: 4 }],
autoFinish: false, timeLimit: 120, seed: 3, flags: { dpInit: 60, dpMax: 99 },
});
h.step();
return { h, t: h.b.allyUnits.find((u) => u.kind === 'token' && u.defId === tokenId) };
}
const bar = (h, t) => unitTuple(t, h.b.time)[3] / unitTuple(t, h.b.time)[4];
for (const [label, owner, tokenId, lifeWant] of CASES) {
test(`${label}: 无敌 + 禁疗, no outside damage (活性源石 tick), no heal, a bar that runs down with its life`, () => {
assert.ok(COUNTDOWN_SUMMONS.has(tokenId));
const { h, t } = field(owner, tokenId);
assert.ok(t && t.alive, 'deployed from its piece');
assert.ok(t.countdown, 'its life is counting down');
const life = t.countdown.until - t.countdown.from;
if (lifeWant != null) assert.ok(Math.abs(life - lifeWant) < 1e-6, `life ${life} s`);
const f = t.s.flags;
assert.ok(f.invulnerable && f.noHeal && f.healFree, '无敌 + 禁疗');
// a 活性源石 tick (true, 无来源, 环境) and an enemy-style hit change nothing
const hp0 = t.hp;
h.b.dealDamage(null, t, { amount: 70, type: 'true', canDodge: false, tags: ['terrain'] });
h.b.dealDamage(null, t, { amount: t.s.maxHp * 3, type: 'phys' });
assert.equal(t.hp, hp0, 'no damage reaches it');
assert.equal(h.b.heal(h.unit(1), t, 500), 0, 'no heal reaches it');
// the bar: full at the start, ~3/4 then ~1/4 of the way through, gone at the end
const from = t.countdown.from;
h.run(from + life * 0.25 - h.b.time);
assert.ok(Math.abs(bar(h, t) - 0.75) < 0.05, `a quarter in: ${bar(h, t).toFixed(3)}`);
h.run(life * 0.5);
assert.ok(Math.abs(bar(h, t) - 0.25) < 0.05, `three quarters in: ${bar(h, t).toFixed(3)}`);
assert.equal(t.hp, t.s.maxHp, 'its HP itself never moves');
h.run(life * 0.25 + 0.2);
assert.ok(!t.alive, 'it leaves when the bar is empty');
checkInvariants(h.b);
assert.equal(h.b.errors.length, 0, JSON.stringify(h.b.errors[0]));
});
}
test('a summon that is attacked keeps its HP as HP: 伺夜\'s 狼群 takes damage, is no countdown summon', () => {
const { h, t } = field({ chessId: 'chess_char_3_19_a' }, 'token_10028_vigil_wolf');
assert.ok(!COUNTDOWN_SUMMONS.has(t.defId) && t.countdown == null && !t.s.flags.invulnerable);
h.b.dealDamage(null, t, { amount: 100, type: 'true', canDodge: false, tags: ['terrain'] });
assert.ok(t.hp < t.s.maxHp);
assert.equal(unitTuple(t, h.b.time)[3], Math.ceil(t.hp), 'its bar is its HP');
});