137 KiB
DESIGN §23 — 0.1.3 — community reports after 0.1.2
Part of DESIGN.md (the index; section numbers are global).
23. Community reports after 0.1.2 (v0.1.3)
Reports after the 0.1.2 release, fixed on feedback3 in workstreams W3A–W3J. Each was checked against the official data and PRTS; anything with no source is marked [ASSUMED]. Where each is handled: 联防 carry (#82) → §23.1; 联防 devour of operators who enter down → §23.2; 不屈 on a 撤退 and a 替身 switch → §23.3; SP during stun → §23.4; 碎骨's grenade → §23.5; the enemy numbers left open in §22.12 → §23.6; 圣聆初雪 S2 and the blue gate → §23.7; 琳琅诗怀雅 S1 heals without an attack → §23.8; 芬's 信标 (#86) → §23.9; 远见 to 0 → §23.10; ready with funds left → §23.11; the host kicks a human → §23.12; 折桠's skill idle → §23.13; the ammo bar → §23.14; unit-sound mix → §23.15; a stuck model load → §23.16; shot heights → §23.17; the boss leader in prep → §23.18; spectator seats → §23.19; 阿戈尔 revives by position → §23.20; a 傀儡师 switch ends at full HP → §23.21; 联防 fights with the layers the combat reached → §23.22; deployment by column → §23.23; nest spawners and 节日爵士乐手 → §23.24; a split child's bounty → §23.25; a 隐匿 blocker and areas (#97) → §23.26; stacked 寒冷 → §23.27; 「地面干员」 → §23.28; 叙拉古 on every normal hit (#79) → §23.29; mode-off bonds on the strip → §23.30; an item gained as prep ends → §23.31; 乌尔比安 S3 as a free redeploy → §23.32; 波登可's spore radius → §23.33; 琳琅诗怀雅 S3 shoots at 10 coins → §23.34; 高台 only for elite 歌蕾蒂娅 with HOK-Y → §23.35; watching a board in prep (#87) → §23.36; 折射 while silenced → §23.37; a toast when a bond grants a chess → §23.38; an emote manifest timeout (#99) → §23.39.
Decisions of the owner, 2026-10-04: a 联防 devour counts members who enter already down (§23.2); 不屈 rolls on a 撤退 and on a 替身 switch (§23.3); the host may kick a human before the match (§23.12); a co-op room has two spectator seats (§23.19); the boss leader is shown in prep, on the red tiles it can be hit from (§23.18); 远见 can bring a price to 0 (§23.10); 琳琅诗怀雅 S1 heals an injured neighbour without attacking (§23.8); an item that arrives as 休整期结束 merges at the next prep (§23.31); a splitting enemy's bounty stays on the main body (§23.25); only elite 歌蕾蒂娅 carrying HOK-Y may use a 高台, and the bot places her on one that covers the road (§23.35); 琳琅诗怀雅 S3 shoots when the purse reaches 10 and a ground enemy is in the skill attack range (§23.34).
23.1 联防 carries the HP ratio and the SP only — no running skill (community report #34, GitHub #82) — match/unite.js uniteBattleOpts, sim/skills.js (reset, spTotal, setSpTotal), sim/Battle.js (_buildResult unitsEnd, _deploy, _startDeploying), match/fields.js (client-reported unitsEnd), match/PlayerState.js battleInput
- Seen (community report #34 「圣聆初雪2技能联防被吞技能」; GitHub #82): every skill that was running when a helper's own combat ended was already on at the 联防 start, with an empty bar — 圣聆初雪's S2 (a toggle: SP 0 once cast) looked "eaten" although it ran.
- Official: PRTS 卫戍协议/帮助 §联防阶段 "部署完成后,将对应单位的生命比例、技力修改至与上一阶段结束时相同(召唤物仅修改技力,上一阶段为退场状态的干员强制退场)"; the official setup message carries per unit only
charBattleStatusList: [{ instId, hp, tech }](research 09 §3HelpBattleInfo). The 技力 of a skill that is running: PRTS 技能 "触发技能后,触发技能的单位将消耗相应的技力,随后在技能持续期间进入阻回" — spent at the activation, nothing recovered while it runs. PRTS 技能 "可充能X次…当前技力上限等于该技能技力需求的X倍": a charge skill's 技力 includes its stored charges. - Cause:
unite.jscopiedunitsEnd.skillActiveintocarryState, andSkill.resetre-activated a carried timed skill for free (a fresh duration / ammo, no charge spent). Two more gaps in the carried SP:unitsEnd.spwas the SP towards the next charge only, so stored charges were lost (a full 2-charge skill came back with one); summons carried nothing (DESIGN §19.3 had left 召唤物仅修改技力 out). - Now:
carryStateis{ hpPct, sp }for an operator,{ sp }for a board summon piece,{ down: true }as before.unitsEnd.spisskill.spTotal(stored charges × cost + the SP towards the next charge);resetrebuilds the charges from it. A skill running at the end enters 联防 switched off with the SP it had left — 0 for a one-charge skill — and recharges from there (圣聆初雪 S2: off, SP 0, 60 s to recast — 52 elite; S3 likewise; her S1 with one charge and 5 SP stored comes back with exactly that).unitsEndnow also lists the board's summon pieces (a board uid); the server accepts them from a client-reported result (fields.js:own.all),unite.jsgives a summon on the field at the end its SP,battleInputhands it over, and_deployapplies a carried HP ratio to operators only. A board piece that content deploys during the start deployment (a tactician's 流形 / 狼群 comes with its owner's deploy,initial: false) takes its carry on that first deployment (_startDeploying). The carried SP is set "部署完成后", as PRTS says:_deployapplies it inresetand sets it again after thedeployhook (skill.setSpTotal: SP and charges rebuilt, nothing fired, a passive or an already running timed skill left alone), so a deploy-time SP gift does not come on top — a bar that ended at 30 / 40 used to come back ready with 独行 ("初始技力+15") or 黄沙罗盘 ("初始技力+30"); later redeploys in the same battle keep those gifts (initial SP 10 + 15 / + 30). In practice only 缪尔赛思's 流形 (cost 100, initial SP 95) has an SP skill among the placeable summons: it now enters with its end SP.unitsEnd.skillActiveis still reported, never carried. - [ASSUMED]: a summon off the field at the end of the own combat enters fresh (its 技力 at the end does not exist). Not changed: the HP ratio is still applied at the deployment (PRTS sets it "部署完成后" too; no deploy-time effect is known to change it).
- §22.4 needs a word changed: its "联防 carries no effects (HP, SP and a running skill only)" now reads "(the HP ratio and the SP only)".
- Docs: DESIGN §5.1
carryState/ unitsEnd comments (pinned in test/docs-consistency.test.js), §19.3's summon sentence, docs/SIM.md §1.1 and the result shape, docs/META.md (联防), docs/PLAYING.md (联防). - Tests:
test/match/feedback3-unitecarry.test.js(unite.js carry: HP + SP for an operator, neverskillActive,{ sp }for a summon on the field, nothing for one off it,downas before;battleInputhands a summon its carry; a real own combat whose 30 s skill still runs at the end → unitsEnd sp 0, skillActive true → the 联防 battle: skill off, no activation, SP from 0, no skill ATK, a 2-charge skill's stored charges back; 缪尔赛思's 流形 with{ sp: 40, hpPct: 0.3 }: SP 40 instead of 95, HP full; 独行 and 黄沙罗盘 with a carried 0 / 30 of 40: the bar comes back as it ended, not ready — 15 / 30 more and ready before the fix —, a later redeploy 25 / 40),test/sim/fidelity.test.jsandtest/sim/skills.test.js(rewritten: a carriedskillActivestarts nothing),test/match/{combat,clientCombat,unitedown}.test.js(the carry shape).
23.2 联防: 阿戈尔's devour counts the operators down since their own combat (community report #3, GitHub #33 item 3) — content/bonds/core.js devour
- Seen (community report #3): "阿戈尔在进协防的时候,即使已经处于撤退状态的干员也应该会在协防阶段时重新参与进阿戈尔的吞噬攻击力计算,不是像现在这样数值被重置" — a member's devour gains vanished in 联防 when an operator of its chain had been knocked out at the end of the own combat.
- Official / decision: no text gives the order of the 联防 forced exit and the battle-start devour (§19.3, §22.3: [ASSUMED] forced exit first). Per players' reports (two players), owner's decision 2026-10-04: in 联防 the operators that were down at the end of their own phase still take part in the battle-start devour — marking order, the unit in front, base ATK / block gains — and are then forced out.
- Cause:
Battle.startforces those operators out right beforebattleStart;devour()took only units on the field, so the chain broke at the down operator's tile: 乌尔比安 → 幽灵鲨 (down) → 歌蕾蒂娅 → 隐现 gave 乌尔比安 nothing (an empty tile in front of him). - Now (the smallest change; the forced exit keeps its place, so no other
battleStarthandler — devices, 征召's row check, 炎, the add-on refresh, kits, garrisons, bands — sees anything new):devour()treats an operator forced out at this start (carryState.down, removal reasonFORCED_EXIT, lying on its tile) as standing for the whole devour but what would land on it. It marks in its turn (left first, then top), it is found as "the unit in front" (battle.downOn; the chain goes on through it when it is a member), its base ATK / block count for its marker, and its own marks resolve: their 物理流失 lands on its targets, a kill is credited to it as usual, a member it knocks out spends a 5-tier revive like any first knock-out (whose pending marks are then cancelled). Only the marks on it resolve nothing (knocked: it is off the field) — it is never knocked out again, never revived, never devoured (no layers, nodeaths). Its own marking gains are a persistent buff, as for every marker since 0.1.0: it has them when it redeploys. Measured (real data): the chain above (3 members) gives 乌尔比安 +1569 ATK (幽灵鲨 530 + 歌蕾蒂娅 640 + 隐现 399) and +5 block — exactly the gains with 幽灵鲨 standing; 幽灵鲨 lies forced out with no knock-out, no revive, no 物理流失 on her; 歌蕾蒂娅 and 隐现 fall to 乌尔比安's marks as with her standing (her own marks find them knocked out already). With 幽灵鲨 (down) at the head of 幽灵鲨 → 歌蕾蒂娅 → 隐现, her two marks knock both out, exactly as standing (3 members: same knock-outs; 5 members: 歌蕾蒂娅's revive is spent and she stands again at full HP). - [ASSUMED]: nothing lands on a down operator (it is forced out right after; a 物理流失 on it could only knock it out a second time or spend a revive), so — unlike a standing one that an earlier mark knocks out — a down marker always resolves its own marks. Review fix (Grok, round 4): the first version skipped the down marker's own marks too, so the unit in front of it took no 物理流失 and a 5-tier revive stayed unspent while it still passed its base ATK / block on.
- §22.3 needs a word changed: its "The order of the 联防 forced exit and the battle-start devour stays [ASSUMED] (§19.3: forced exit first); no source gives it" now points here. §19.3's [ASSUMED] sentence names the exception (edited in place).
- Tests:
test/content/feedback3-egir-down.test.js(the chain 乌尔比安 → 幽灵鲨 (down) → 歌蕾蒂娅 → 隐现 in a 联防 battle: 乌尔比安's devour buff equals the chain with 幽灵鲨 standing; she is forced out — no'killed'death, no redeploy, no 物理流失 on her, one knock-out fewer indeaths; her marking gains kept; 幽灵鲨 (down) at the head of the chain with 3 and with 5 members: the resolved marks, kills, knock-outs and — at 5 — 歌蕾蒂娅's spent revive equal the chain with her standing, and she stays forced out);test/content/bonds_core.test.jsunchanged and passing.
23.3 不屈 rolls on a 撤退 and on a 傀儡师's 替身 switch (owner's decision 2026-10-04) — content/bonds/addon/battle.js (不屈), sim/professions.js installDollkeeper (hook dollSwap)
- Seen: the triage of community report #24 (GitHub #33): 不屈 rolled on knock-outs only; a ground operator withdrawn by its own effect stayed down even at 210 layers (p = 1).
- Official: PRTS 卫戍协议:盟约 下半/PRTS盟约记录, 不屈 — its 修正 (原因 6) replaces the text's "被击倒" by "被击倒、撤退、切换<替身>与<本体>": "地面干员…时,有(18+0.4×层数)%概率立刻重新部署"; "※“立刻重新部署”的实现方式为:令受益者下次部署的再部署时间和费用归零"; "※持有“复活”效果时,即使触发了不屈的“重新部署”效果,也还是会消耗一次“复活”次数". The tier-2 line "<在场3名不同【不屈】干员>地面干员被击倒时使场上所有干员技力+5" was not corrected. research 02 §3.19 already had the corrected trigger. Owner's decision 2026-10-04: follow PRTS.
- Cause: the
deathhook returned unless the reason was'killed'; nothing reported a 替身 switch (the garrisons pollunit.trait.doll). - Now: a knock-out (
'killed') or a 撤退 rolls: the reasons'retreat'(an operator withdrawn by its own effect — 史尔特尔's 余烬, 耀骑士临光 S2, 伊内丝 S3, the 骑士戒律 combo) and'merchant'(行商 that cannot pay). A hit redeploys it at once, free, where it lies (the engine rest tile, §22.15) — once per unit and instant, as before. No roll on the 突袭 retreat ('raid': it redeploys at once and free on its landing tile anyway) or on the 联防 forced exit (FORCED_EXIT: the battle's setup, not a 撤退 — §19.3; the operator keeps its full timer). A 傀儡师 switch rolls too — to the 替身 and back (new hookdollSwap { unit, form }, fired when a switch starts, not when she is knocked out as the 替身): she stays on the field, so a hit only zeroes her next deployment (unit.mem.indomFreeDeploy): the next time she leaves the field (knocked out as the 替身, withdrawn) she is back at once and free, whatever that exit's own roll gives; any deployment spends it (this redeploy, the 阿戈尔 5-tier revive, a 突袭 jump). Tier 2's +5 SP stays a knock-out effect. The 不屈 roll still needs a ground operator (unit.ground, §22.6) and the player's 不屈 bond; membership is not needed (unchanged). - Players notice: with 不屈 lit, 耀骑士临光 coming back from her S2 withdrawal, 史尔特尔 after her 余烬, a 行商 short of DP and 伊内丝 after placing her 影哨 may redeploy at once (at 205+ layers always — 耀骑士临光 then re-runs S2 every 22 s, a 行商 that cannot pay keeps coming back); 归溟幽灵鲨 / 风丸 roll on each switch and a hit brings them straight back after their next knock-out.
- [ASSUMED]: no roll on the 突袭 retreat (it would change nothing — its redeploy is already immediate and free); one zeroed deployment at a time (a second hit while one is banked adds nothing), spent by the next deployment of any kind; the switch roll is silent (no fx — nothing is redeployed then). Open: the 阿戈尔 5-tier revive's implementation note ("受益者因移动之外的原因退场时…归零") may cover retreats too; it still fires on first knock-outs only.
- Docs: docs/SIM.md §1 (the 退场 paragraph) and the hook table (
dollSwap). - Tests:
test/content/feedback3-indom-exits.test.js(p = 1: a'retreat'and a'merchant'withdrawal redeploy at once, free, on the tile; tier 2's +5 SP comes with a knock-out, not with a retreat; the 联防 forced exit and the 突袭 retreat roll nothing; p = 0.18: a missed roll leaves the operator down on its tile; 归溟幽灵鲨: the switch to the 替身 banks a free deployment — the knock-out of the 替身 brings her back at once and free, the bank spent; the switch back rolls on its own; with a bank, a knock-out brings any ground operator back at once whatever the roll),test/content/bonds_addon.test.js,test/sim/feedback2-doll.test.js,test/sim/professions.test.jsunchanged and passing.
23.4 A stunned operator keeps its natural SP recovery (community report #18, 洛洛 S2) — sim/skills.js tick
- Seen (community report #18): "洛洛二技能结束眩晕好像不会自然恢复技力,而且好像持续时间也不太对,洛洛二技能是有过载的".
- Official: PRTS 技能 — natural recovery is a 1 s SP cooldown slot, and "在阻回状态或技力条已满时,保留剩余冷却时间,计时暂停": only 阻回 (SP_RECOVER_STOPPED, "停止技力自然回复…※通常情况下,处于技能期间的单位皆持有阻回") or a full bar pause it. PRTS 异常效果 STUNNED "状态机尝试切换至STUN(无法攻击、释放技能、阻挡敌人类单位;敌人类单位无法移动)" and gamedata_const ba.stun "无法移动、阻挡、攻击及使用技能" name no SP effect; ba.frozen likewise. So a stun does not stop natural recovery. 洛洛 S2 自负此轭 (skill_table
skchr_rockr_2; the chess status skillLevel 4 / elite 7): duration 40, SP cost 45 / 40, initial SP 15 / 20, ATK +40 % / +50 %, ASPD +45 / +60, funnel cap ×1.6 / ×1.7; ba.overdrive "技能进行到一半时触发额外效果"; "技能结束后晕眩过载持续时间的等长时间". - Duration — correct, unchanged: the overload starts half-way (20 s) and the stun lasts as long as the overload ran (20 s after a full skill; an earlier end stuns for the part already overloaded — the triage measured 5.03 s for a stop 5 s past half-way, 0 before it); the data match skill_table at both levels.
- Cause:
SkillRuntime.tickreturned when the unit could not act (!canAct: stunned, frozen, levitated) before the natural recovery, so a stunned operator's time SP stood still — written as a rule in skills.js and docs/SIM.md ("no SP gain … while stunned"). - Now: the time SP recovers first (unless 阻回
noSpor a running timed skill, both unchanged), then a unit that cannot act skips its trigger checks and casts. A general rule — every stunned / frozen / levitated operator with time SP. 洛洛 after a full S2: +20 SP during the 20 s stun (was 0), recast 45 s after the skill end (elite 40) instead of 65 (60). Others that change: self-inflicted — 幽灵鲨 S2 肉斩骨断 (10 s stun after the skill: +10 SP; recast 45 / 43 s after the end instead of 55 / 53), 圣聆初雪's 圣山的祝福 (4 s self-freeze after the saved knock-out: +4 SP unless a skill runs — S2 holds 阻回 anyway); 雷蛇 S2 反击电弧 is hurt-SP (受击回复, which already counted hits while stunned) — no change. Enemy stuns / freezes on operators: 粉碎攻坚手 / 组长 and 码头水手 / 水手长 (every Nth attack), 流泪小子 (鸭爵, 3.5 s), “巨大的丑东西” (its knock-out blast), 杰斯顿·威廉姆斯, 重弩突袭者 (直击), 乌顶巨角卢鲁 (角力对决) and the leaders' stuns (胄's 剑 / 锤 blasts, tentacles 5 s), the 神经损伤 burst on operators (10 s), cold-on-cold freezes (3 s) and 浮空 — the stunned operator's SP keeps filling, its casts wait until it can act. Attack SP (no attacks while stunned) and hurt SP are unchanged; units that are not deployed recover nothing. - [ASSUMED]: none (the rule is PRTS's; frozen / levitated follow the same SP-cooldown text).
- Docs: skills.js header, docs/SIM.md §7.1.
- Tests:
test/sim/feedback3-stunsp.test.js(洛洛 S2's data against skill_table for both pieces; a full S2: a 20 s stun, +10 SP after 10 s of it, 20.5 after the stun, recast 45 s after the skill end; frozen: +1/s; 阻回: nothing; an operator that fills its bar while stunned casts only after the stun),test/sim/combat.test.js(the stunned operator neither attacks nor casts, SP +1.9 over 1.9 s — the old assertion was "no SP").
23.5 碎骨's grenade: 26 % ATK on the target and its 8 surrounding tiles, DEF −50 % for 5 s (community report #14) — content/enemies.js (splashAttack, noAirTargets, kitLeaderMisc enemy_1500_skulsr, SKULSR_GRENADE_SCALE / SKULSR_DEFDOWN_DUR)
- Seen (community report #14): 「敌人碎骨的伤害有人反馈过高,复核有没有异常」. Real: its combat stats are right (level 0 in data/enemies.json = PRTS: HP 10500, ATK 1000, DEF 150, RES 30, range 2.6, interval 3.0; the 50 % ATK-up below half HP is in the blackboard and was already modelled) — the unblocked grenade was not. §22.12 had listed it under "Found, not changed".
- Official: PRTS 碎骨 天赋 "未被阻挡时发射榴弹对目标及其周围八格的我方单位造成相当于攻击力26%的物理伤害,并令其在5秒内防御力下降50%(直接乘算)", "※榴弹对主目标造成物理普通伤害,对溅射目标造成物理溅射伤害,可溅射飞行单位", and "不会攻击飞行单位". The enemy_database blackboard has only
atkup.atk0.5,atkup.hp_ratio0.5 anddefdown.def−0.5: the 26 %, the 5 s and the 3×3 are PRTS's numbers (no table carries them); the −50 % is the blackboard's. - Cause: the kit let the engine deal a full attack on the target, then hit everything within radius 1 (the 4 orthogonal neighbours, no diagonal) for another 100 % ATK, with a 3 s DEF-down [ASSUMED "短时间"].
- Now: a shared helper
splashAttackfixes the attack's mode when it starts (before: unblocked ⇒ grenade). The target takes the engine's normal attack at 26 % ATK (profile.atkScale, so on-hit effects still apply); when it lands, every unit on the target's tile and the 8 around it (areaAlliesInTiles'box' 1, 格子判定) takes 26 % ATK physical as a splash (tagsplash); everyone hit gets DEF −50 % for 5 s. A blocked attack is its plain melee hit (100 %, no splash, no DEF-down). The splash keeps 0.1.2's area rule (§22.12): a 隐匿 operator next to the target that does not block 碎骨 is spared, a 迷彩 one is not; a flying ally (the 炎佑 dragon) is splashed ("可溅射飞行单位") but never targeted (noAirTargets); an airborne 起飞 operator stays out (对地规避, as every ground enemy's area). fxexplodekindgrenade(box) on each landing. - [ASSUMED]: the splash goes off on the grenade's landing even when the target dodges it (the main hit's
hitevent, before its dodge); a target that died or turned invulnerable mid-flight gives no splash. - Measured (real bot rounds — coop NORMAL, 4 bots, matches seed 3 and 7, the first bot's own field at rounds 4 / 6 / 8, one 碎骨 bounty added as
bountySpawnswould, 3 sim seeds each, 18 runs; before = v0.1.2 code): leaks 73 → 5, 碎骨 itself leaked 9 → 0 times (killed 9 → 18), operator knock-outs 81 → 55, damage taken by operators 383 k → 337 k. - Tests:
test/content/enemies_bosses.test.js(碎骨: 26 % on the target as its attack, 26 % splash on a diagonal neighbour, nothing two tiles away, DEF-down 5 s / 0.5 on both; blocked: one 100 % hit, no splash, no DEF-down; ATK up below half),test/sim/feedback2-stealth-splash.test.js(the 隐匿 / 迷彩 / 伪装服 neighbours, unchanged).
23.6 Enemy numbers from the 0.1.2 review checked against PRTS and this mode's data (§22.12 "Found, not changed") — content/enemies.js (splashAttack, noAirTargets, pathKeysAhead, kitWolfLord, kitUglyThing, kitTeapot, kitInvisShield, kitCrossbow, kitLeaderMisc “庞贝” / “复仇者” / “墓碑”, 集团军重型火炮)
- Seen: §22.12 listed enemy numbers that differ from their PRTS pages; each was checked again on PRTS (the enemy pages, fetched 2026-10-04) and against this mode's enemy data (data/enemies.json = enemy_database level 0, the level every act2autochess enemy uses — activity_table act2autochess
enemyKey … level 0; the act2autochess level files carry none of these enemies). Only what both confirm, or what PRTS states where the data has no number, changed. - Official → now (until 0.1.3 in brackets):
- 扎罗,“狼之主” — 天赋 "【远古威慑】:自身1.5半径范围内的我方单位攻击速度-50(指定状况下生效)", "重生期间【远古威慑】生效", second form "【远古威慑】生效 … 进行远程攻击,普通攻击为2连击,攻击范围半径1.25": aura radius 1.5, ASPD −50, during the 40 s 重生 and in the second form; second-form attack radius 1.25 [2.5 / −30 only while reborning / 2.5, all ASSUMED]. 【溶血骇惧】 (技能 "逐渐流失生命(从0/秒开始线性递增,在40秒后达到最大流失速度30%最大生命值/秒),持续时间无限 … 场上存在被此技能影响的单位时狼之主获得静默,每结束1个技能效果狼之主回复5SP,场上技能效果全部结束时狼之主获得7s静默 … 累计损失20%生命值后解除") = the FearCage blackboard: the loss rate rises to
hp_ratio30 % max HP/s overduration_bleed40 s and stays there, no end; cured once 扎罗's HP ratio ishp_ratio_offset20 % below its ratio at the cast (an offset from that ratio, so healing 扎罗 delays the cure); an effect whose 扎罗 left the field without a 重生 (a leak, a removal) ends at its next tick with no loss [ASSUMED — no official text; the 重生's onKo cure covers its knock-out]; its SP (the spCost 55 s cooldown) stands still while a unit is caught, each ended effect (cure, knock-out) gives backsp5, and the last one startsduration_wait7 s more of 静默 [5 % peak ASSUMED, 40 s buff,hp_ratio30 % read as the cure, a recast every 55 s whatever was caught]. - “复仇者” — 冲锋 "仅自身未被阻挡且存在符合条件的可选目标时可触发:选择3.0半径内位于自身下个检查点前的后续路径上(包含自身当前所在地块)的距离自身最近的我方单位…": a target it may select (
targetsNear: no 隐匿 / 迷彩 / 起飞 / untargetable operator) within 3.0 on its own path to its next checkpoint (pathKeysAhead: the tiles its current move leg crosses, its own tile included) [any unit within the blackboard'srange_radius1.5, 隐匿 / 迷彩 ones included, off its path too]. The blackboard keeps 1.5; PRTS's 3.0 is used. It walks to that unit's tile along that path anyway, so PRTS's re-aimed checkpoint is not a separate move. - “庞贝” — "被阻挡时,每10秒(受晕眩/无法行动/沉睡/冻结/浮空影响时暂停计时,解除阻挡时重置计时)对<固定半径>半径1.4范围内的所有我方单位造成1000预计算的无途径法术溅射伤害(不可对空)": radius 1.4; the clock runs only while it is blocked and can act and restarts at every new block; flying allies skipped [radius 1, a fixed 10 s clock from its spawn].
- “巨大的丑东西” — "未被阻挡时,可对半径2.5范围内的1名非飞行的我方单位进行远程攻击,对目标及其周围8格内的所有我方单位造成攻击力100%的物理伤害(※…可溅射飞行单位)", "被阻挡时进行近战攻击,造成攻击力300%的物理伤害", "重生开始的2.17s后进行自爆,对半径3.0范围内所有我方单位造成攻击力150%的物理伤害和16s晕眩": the data's attack radius is 0, so it never attacked unblocked — now a 2.5-radius ranged attack on a ground unit with the 3×3 splash (
splashAttack), the blocked hit atmcmstr_rage_attack.atk_scale; the self-destruct 2.17 s into the 重生 (a telegraph first), radius 3.0 [no ranged attack; the blast at once, radius 2.5 ASSUMED]. - “墓碑” — "自身造成的远程途径伤害的攻击倍率降低至40%" (the blackboard's
atkdown.atk_scale0.4, unused until now), "未被阻挡时会进行远程攻击,对目标及其周围八格内的所有我方单位造成物理伤害,不会攻击飞行单位": unblocked, 40 % ATK on the target and its 3×3; blocked, its melee hit at 100 % [one target at 100 % either way]. - 烹泉 / 沏虹 — "普通攻击对目标对及目标周围半径1.0范围内的所有我方单位造成法术普通伤害(无视迷彩,不可对空)": every attack also hits the units within 1.0 of its target; "死亡爆炸(爆炸半径1.25,造成攻击力100%法术溅射伤害并施加15s【烹泉减益】…不可对空)", 【烹泉减益】 "攻击速度-40(沏虹 −50),…可叠加,每层持续时间和效果独立计算": the blast reaches 1.25 and gives each unit hit one 15 s layer of its own [no attack splash; the blast took the attack radius 2 and left a 15 s steam zone re-applying the cut to whoever stood in it, ASSUMED]. The attack splash, a 法术普通伤害, can be dodged like the attack (
splashAttackdodge); the blast (法术溅射伤害) and every other enemy splash here cannot. - 集团军重型火炮 — "不会攻击飞行单位", "普通攻击向目标发射一枚炮弹,对目标半径1.0范围内的所有我方单位造成攻击力100%的物理伤害(此弹道会强制击中主目标,碰撞无视迷彩,不可对空)", 【燃烧区域】 "<1.5倍可变半径>碰撞半径1.5": the shell splashes 1.0 around its target; the zone's radius is
projectile_range× 1.5 and it skips a flying ally (dmgZonenoAir: the 炎佑 dragon) [no splash, zone radius 1, the dragon burned]. - 清明 / 堂皇 — 辉光照耀 "使【范围隐匿】生效3s" = the talent's
InvisibleShield.duration3 (both) [清明 took its skill blackboard'sduration5]. - 重弩突袭者 — 直击 "蓄力1.4s后向目标方向发射1支弩箭,对击中的首个目标造成攻击力100%的法术伤害与5s晕眩 ※技能持续2.5s", 天赋 "隐匿(被阻挡,主动攻击期间均可解除)": revealed for the skill's 2.5 s, the bolt 1.4 s in at the nearest unit still in line in the aimed direction [revealed for the blackboard's
duration2 s, the bolt at once]. - Every area added or changed picks its victims with
areaAllies/areaAlliesInTiles(§22.12): no 隐匿 operator that does not block the enemy, no airborne 起飞 one, 迷彩 not checked; "不可对空" sites also skip a flying ally (the 炎佑 dragon), "可溅射飞行单位" ones take it.
- 扎罗,“狼之主” — 天赋 "【远古威慑】:自身1.5半径范围内的我方单位攻击速度-50(指定状况下生效)", "重生期间【远古威慑】生效", second form "【远古威慑】生效 … 进行远程攻击,普通攻击为2连击,攻击范围半径1.25": aura radius 1.5, ASPD −50, during the 40 s 重生 and in the second form; second-form attack radius 1.25 [2.5 / −30 only while reborning / 2.5, all ASSUMED]. 【溶血骇惧】 (技能 "逐渐流失生命(从0/秒开始线性递增,在40秒后达到最大流失速度30%最大生命值/秒),持续时间无限 … 场上存在被此技能影响的单位时狼之主获得静默,每结束1个技能效果狼之主回复5SP,场上技能效果全部结束时狼之主获得7s静默 … 累计损失20%生命值后解除") = the FearCage blackboard: the loss rate rises to
- Cause: the kits predate these PRTS readings (the numbers were [ASSUMED] or read off the wrong blackboard key); 巨大的丑东西's and 墓碑's ranged attacks and the splash of 烹泉 / the 火炮 were never written.
- [ASSUMED]: a 溶血骇惧 ends when its 扎罗 leaks or is removed; 重弩突袭者 stands still and cannot attack for the 2.5 s, and a stun / silence or its death before the release cancels the bolt; 扎罗's SP is +1/s (its cast every spCost s, as before); a splash goes off on landing even if the target dodges (the main hit's
hitevent). - Found, not changed: 【烹泉减益】 "可被抵抗" (a plain buff here, no 抵抗); 清明's 【范围隐匿】 is an aura during its 3 s (units entering the 2.0 radius are veiled, leaving ones not) — here the units inside at the cast get 3 s; “复仇者” "※重生后:此技能释放的动画动作期间免疫晕眩" (no cast animation in the sim); 扎罗's 无法撤退, 血债账款 / 【狂暴怒嗥】; enemy areas measure centre distances, so “庞贝”'s 1.4 misses a diagonal neighbour (√2); the rest of §22.12's list (碎铳之簧, 锏's 速杀, W's C4, 管, “萨科塔昂首”) is outside this item.
- Measured (real bot rounds — coop NORMAL, 4 bots, matches seed 3 and 7, the first bot's own field, one bounty enemy added as
bountySpawnswould, 3 sim seeds each; before = v0.1.2 code; leaks / operator knock-outs): 巨大的丑东西 R9/11/13 (18 runs) 190 → 251 / 78 → 113; “庞贝” R8/10/12 (18) 164 → 164 / 110 → 113; “复仇者” R9/11/13 (18) 193 → 218 / 88 → 92; 扎罗 R11/13 (12) 147 → 158 / 74 → 86; “墓碑” R11/13 (12) 353 → 358 / 96 → 96; 烹泉 R5/7 (12) 43 → 31 / 21 → 20; 清明 R5/7 (12) 39 → 36 / 21 → 23; 重弩突袭者 R6/8 (12) 0 → 0 / 31 → 35; 集团军重型火炮 R9/11 (12) 27 → 25 / 40 → 43. 扎罗, “墓碑” and 重弩突袭者 bounties are not drafted (draftExcluded'unseen'); 集团军重型火炮 is a DOT 特训 enemy of the hard / abyss modes (inactiveEnemyKeysof the funny and normal ones) — added here to normal rounds all the same. - Tests:
test/content/enemies_bosses.test.js— 扎罗 (loss rate, no end, no recast while caught, cure at 18 % no / 20 % yes, the 7 s 静默 then the recast, every effect ends at the 重生, the aura −50 within 1.5 only, in the second form too, range 1.25), “复仇者” (a unit on its path 3 tiles ahead; none off its path, 4 tiles ahead, 隐匿 or 迷彩), “庞贝” (radius 1.4, the paused and restarted clock), “巨大的丑东西” (the blocked ×3 hit, the bomb at 2.17 s; the unblocked 3×3 at 100 %, the 3.0 blast), “墓碑” (40 % 3×3 unblocked, one 100 % hit blocked), 烹泉 / 沏虹 (splash r 1.0, blast r 1.25, one 15 s layer), 集团军重型火炮 (splash r 1.0, zone r 1.5; the 炎佑 dragon inside the zone is neither burned nor hit), 清明 / 堂皇 (3 s), 重弩突袭者 (no bolt before 1.4 s, revealed and standing for 2.5 s, a stun cancels it); 扎罗 leaking in its first form (every 溶血骇惧 ends, no loss after the leak); 烹泉 (an arts-dodge neighbour dodges the splash, the blast still hits a dodger). The three last fail on the branch's first version (Grok review).
23.7 圣聆初雪 S2 freezes the protection point, and only it (community report #32) — content/kits/tier6.js sbell2 (addSnow, freezeTile), render/units.js (ICE_TOKENS), render/textures.js diamondTexture (ice), docs/ASSETS.md
- Seen (community report #32): 「圣聆初雪天赋不能冻住蓝门(积雪能不能对怪物正常生效也查一下)」. Real, for her S2 霜涛覆岭: 5 layers of snow on the blue gate never froze it, and nothing showed — the kit gave up because no operator can stand on a gate. Her talent 无垠的雪景 freezes nothing officially (snow, arts on passing, slow per layer); its snow works on ground enemies (checked on a walker: the mode's talent_magic_scale 0.75 × ATK arts on stepping onto a snowy tile — 539 at ATK 718 —, move ×0.64 at 3 layers = 1 − 0.12 × 3).
- Official: skill text 「积雪在目标点积累至5层时,使目标点变为冻结状态」 (skill_table skchr_sbell2_2; blackboard
talent@token_keytoken_10058_sbell2_icetgt, 保护目标(冻结状态), 「能够阻挡三个敌人」). PRTS 圣聆初雪 S2 备注: 「目标点冻结的实际效果为令圣聆初雪在该地块上召唤一个保护目标(冻结状态)(无视部署属性),并去除相应地块上的积雪(且存在自身的该召唤物的地块不会积雪)」, 「可以被'变为冻结状态'的目标点包括常规的保护目标点与促融共竞的保护目标点」; PRTS 保护目标(冻结状态): 使用条件 「技能发动后于保护目标叠加5层积雪」, 备注 「持有禁疗、无法撤退」, 「圣聆初雪退场时强制撤退」. So 目标点 is the protection point — the gate — not any tile. - Cause:
freezeTilefroze any tile that reached max snow but only where a melee operator could stand (grid.canStand), which a gate (build NONE) never is; the reverse of the official rule. The client has no model or avatar for the token (data/assets.json: owner only; PRTS 无头像), so a token showed the token fallback — 圣聆初雪's own face in a diamond (assets.jstokenAvatarUrl). - Now: a layer bringing a goal tile (glyph E,
special'end') to max snow while S2 runs summons her 保护目标(冻结状态) there, deploy attributes ignored (spawnTokendoes not check them); its snow is used up and no snow gathers under her token. Any other tile at max snow just stays at max (S2's spread goes on); a gate that reached max while her one token stood elsewhere freezes on its next layer. The token blocks 3 (enemies walking into the gate are held there instead of leaking until it falls) and keeps its 禁疗 (PRTS 备注 「持有禁疗」, already modelled). On screen the frozen gate is an ice diamond: frame 0x9fe6ff and a snowflake, no picture looked up (ICE_TOKENS,diamondTexture({ ice })) — there is no ice model to load; docs/ASSETS.md (Tokens) says so. - [ASSUMED]: the token's 70 s 再部署时间 does not gate the next freeze (as before); one token at a time per 圣聆初雪 (maxDeployCount 1).
- Found, not changed: PRTS 无垠的雪景 备注 「积雪的基本效果无视可选性,但无法影响具有无敌/隐匿/不可选中的单位」 — the sim's snow also slows / hurts a 隐匿 or untargetable ground enemy; the slow's 「叠加时减速数值翻一倍(具体可参考显示算法)」 note is not modelled (−12 % per layer, linear).
- Tests:
test/content/kits_alt_t6.test.js(S2: a free tile of her range reaching 5 layers does not freeze; the gate at 4 + one layer freezes into the token on (9,2), blocks 3, its snow used up, none gathers under it; a walker is held at the gate, no leak),test/render/unitview.test.js(the token's ice diamond and frame).
23.8 琳琅诗怀雅 S1 heals an injured neighbour without an attack — a deliberate deviation (community report #5, owner's decision 2026-10-04) — content/kits/tier3.js chess_char_3_04_a installS1
- Seen (community report #5): 「琳琅诗怀雅1技能不会主动奶身边受伤的干员」 — with no enemy to attack, an injured operator next to her was never healed.
- Official: S1 仗义疏财 (skill_table skchr_swire2_1; this mode's data/chess.json) 「消耗一枚金币,下一次攻击会为周围八格内血量不足70%的一名友方单位恢复相当于攻击力40%的生命」 (
attack@heal_scale; 携带此技能时金币上限为2): the heal rides on her next attack, so without an enemy to attack there is none — the sim did exactly that. Not a bug against the official text. - Decision: the owner wants S1 to heal whenever an ally in her 8 surrounding tiles is injured, attack or no attack (owner's decision 2026-10-04) — a deliberate deviation from 「下一次攻击会为…恢复」, like §21.29's six 重装 skills that cast with an enemy in range instead of the official TAKE_DAMAGE row.
- Now: the kit's rules stay — the target is the lowest HP ratio below 70 % (the kit's reading of 「一名」) among the units of the 8 surrounding tiles (no 禁疗 or 孤立 unit, no device), a coin per heal (none without a coin), heal_scale × ATK, fx
coinwith the healed id as before. What changes is when: while she attacks, the heal still comes with the attack; when her last attack attempt found no target (trait.hadTargetfalse — no enemy in her range), she heals on her own timer. Either way at most one heal per attack cycle (her attack interval, ASPD included:nextAt= the heal +s.interval), so an attack right after a timer heal does not heal again; thetickhook runs after the attacks, so an attack due in the same tick takes the heal. A stunned (cannot act) or undeployed 琳琅诗怀雅 does not heal. - [ASSUMED]: the cadence — one heal per attack cycle, the first one as soon as a coin and an injured neighbour are there.
- Tests:
test/content/kits_alt_t3.test.js— no enemy on the field: an ally at 80 % is left alone and keeps the coin, one at 50 % is healed at once (heal_scale × ATK, one coin spent, no attack); kept injured with coins to spare, one heal per attack interval; with an enemy in her range, one heal per attack, never a second in the cycle; the existing S1 test (attack-driven heal, 周围八格 only, cap 2, no bombs) unchanged.
23.9 芬's 信标 sends the original operator, and the gift survives its sender (community report #6, GitHub #86) — match/builtinMeta.js (use_equip_recruit_new_char_and_give_char_to_player_most_bond, mostBondMate, effect:builtin_gift afterElimination), match/effectsMeta.js EffectDispatcher.dispatchEliminated, match/Match.js startRound
- Seen: community report #6 「精锐干员信标送出去变成一阶的了」 — a 精锐 operator sent with the beacon arrived at the teammate as the normal card of the same tier. GitHub #86: when the sender was eliminated before the next round start, the gift never arrived.
- Official [DATA]: act2autochess eff_acarm109 / eff_acgarm109 「装备时,目标干员和本装备销毁并进行一次特殊刷新,出现两名与携带者同等阶的干员,免费获取其中一名 / 若在同盟模拟中且存在其他队友,下个休整期向相应盟约人数最多的队友发送1个原干员 / 相应盟约人数相同则随机发送」; 芬 band_fang 「原干员在下回合传递给对应盟约人数最多的队友」. The gift is the original operator; nothing makes it depend on the sender still being in the match.
- Cause: (a) the gift stored
baseIdOf(target)— the normal id — so an elite came back as its normal card holding 1 pool copy. (b) the gift was an EffectRef on the sender resolved by the sender's ownonRoundStart;Match.startRounddispatches only to living players, andPlayerState.eliminatekeepseffects, so an eliminated sender's gift waited forever. It was also removed before the grant, so a grant that failed (no copy left in the shared pool) lost it. - Now: the gift carries the carrier's own id (
chess_char_*_bfor an elite) and its bonds (as counted at equip time, 变形同构体 pairings included). Destroying the carrier still returns its pool copies at once (an elite's 3); the grant takes them again (grantChess→acquireChess: an elite takes up togoldenCopies, like every effect grant), so the teammate gets the elite with 3 copies. The handler is flaggedafterElimination: after the living players'onRoundStart,Match.startRoundcallsEffectDispatcher.dispatchEliminatedfor each eliminated player, which runs only such EffectRefs (nothing else of an eliminated player dispatches). The receiver is still the one picked at equip time; the effect is removed only after the operator was granted, then the CHAR_GIFT ticker names the sender. - Players notice: a beaconed 精锐 arrives as that 精锐 next round; a gift sent by a player who drops out (LP 0 at the settle, or quits) still arrives; the sender's own pick of two is unchanged (two operators of the carrier's tier).
- [ASSUMED]: a receiver eliminated before the round start is replaced by the living teammate with the most members of the operator's bonds (ties at random, the text's rule applied again); with no teammate left alive the gift is dropped; a grant that fails (no copy of the operator left in the pool — the carrier's copies may be bought between the beacon and the next round start —, or the receiver's 整备区 and temp full) keeps the gift and is retried at every following round start (the official text names no refund); an elite whose copies are partly taken meanwhile arrives holding fewer than 3 (the existing rule for effect grants, as 升华 / 博士投影).
- Tests:
test/match/feedback3-gift.test.js(an elite arrives as the elite with 3 copies and the pool count is restored; a normal carrier still sends the normal card; the sender eliminated at the R1 settle — the gift and its CHAR_GIFT ticker arrive; an empty pool keeps the gift until copies return; a receiver eliminated meanwhile → the next teammate),test/content/items.test.js(信标, unchanged).
23.10 远见's price −1 has no floor but 0 (owner's decision 2026-10-04) — content/bonds/addon/meta.js (visiShip.onPrice), research 02 §3.15
- Seen: the owner: with 远见 at 150 layers 至简 still cost 1; the text promises −1 on every operator, so 至简 should be free.
- Official [DATA]: bond 远见 (visiShip) 「<首次达到80层>所有【远见】干员购买价格永久-1资金 / <首次达到150层>以上效果改为调度中心内所有干员的购买价格永久-1资金」 (buff
bond_multi_layer_char_goods_price_bond_discountdiscount1). No floor is named. - Cause:
visiShip.onPricelowered a price only while it was above 1 (Math.min(disc, price − 1)), research 02 §3.15's [ASSUMED] "price never below 1"; DESIGN §19.9 then kept 至简 (购买价格为1, a 特质 run first ononPrice) at 1. - Now: both tiers take the full
discountoff whatever price reaches the bond —ctx.modifyPrice(−1), which never goes below 0. Dispatch order unchanged (§19.9): the priced chess's own 特质 first (至简 3 → 1), then globals, the strategy, the bonds (远见 1 → 0).PlayerState.priceOfalready clamps at 0; buying at 0 needs no funds (buy:funds < priceis false), the shop card draws a price of 0 as FREE (shopBar.js,priceTone'discount'), the client's 资金不足 check (shopBlockReason) and the bot's affordability (price > funds) both accept 0. The bot's full-board "worth more than the refreshes its price would pay for" comparison keeps itsmax(1, price)(a value heuristic, not a price floor). - Players notice: at 150 layers 至简 is free; at 80 layers a 远见 operator brought to 1 by another discount becomes free; nothing else changes (every other operator was ≥ 2 before the −1).
- Open: 休露丝 雪域礼赠 (「每回合购买的首名<谢拉格>干员消耗资金为1」) runs as the strategy step, before the bonds, so with 远见 150 that first 谢拉格 operator now costs 0 too; whether the official game applies 远见 after the strategy's fixed price is not known.
- Docs: DESIGN §19.9 (至简 now reaches 0), research 02 §3.15 / 02-bonds.json
assumed(→ data/bonds.jsonvisiShip.spec.assumed, regenerated offline; the only data change), code comments ingarrisons/meta.js. - Tests:
test/content/bonds_addon.test.js(150: 3 → 2, 2 → 1, 1 → 0, 0 → 0; 80: a 远见 operator at 1 → 0, others unchanged),test/content/garrisons_meta.test.js(至简 sees 1 in a global modifier, then 0 with 远见 150;m.privateslot price 0; bought with 0 funds).
23.11 准备 with funds left asks first (community report #4) — ui/gameLogic.js readyFundsPrompt, screens/game.js toggleReady
- Seen: community report #4 「点准备继续时候如果商店里还有钱的话不会像原版那样弹提示框」 — the 准备 button (and Space) readied at once with funds left; the official client warns.
- Official: act2autochess
autoChessData.constData.noMoneyTipsBand= ["band_cannot"] [DATA] — the strategies that get no "funds left" tip (坎诺特 利滚利 「每回合剩余的资金可以继承」), so every other strategy gets one; data/config.json carries it aseconomy.leftoverFundsKeptByBands(research 01 rules: noMoneyTipsBand). PRTS 卫戍协议/帮助 (休整期): 「本回合的剩余资金将清零,未被冻结的干员/装备会随之清空。进入作战阶段。」 The dialog's own text is in neither the tables nor PRTS. - Cause: the client sent
g.readystraight away; only the temp overflow row could block it (shopBlockReason('ready')). - Now:
toggleReady(the 准备 button and Space) asksreadyFundsPrompt(m.private, { keptBands, autoplay })first; with a prompt it opens the usualconfirmDialog(title 剩余资金, 「还有 N 资金未使用。休整期结束时,本回合的剩余资金将清零。确定准备就绪吗?」, buttons 准备就绪 / 继续整备) and readies only on 准备就绪, if the prep is still on and the player not ready yet; a prep that ends while the question is open closes it. No prompt when un-readying, with 0 funds, already ready or eliminated, for a strategy in the list (坎诺特), or under AI 托管 (m.public players[].autoplay); bots never use the client. The server rule is unchanged:g.readyis accepted with funds andPlayerState.endPrepwipes them (坎诺特 keeps them). - Players notice: pressing 准备 with money left asks once; 继续整备 keeps shopping, 准备就绪 (or Enter) readies; 坎诺特 and an empty purse ready at once as before.
- [ASSUMED]: the dialog wording (the rule sentence follows PRTS 帮助).
- Docs: PLAYING.md (剩余资金 row).
- Tests:
test/ui/feedback3-ready.test.js(the prompt and its text; none at 0 / negative funds, for 坎诺特 — also without config —, when un-readying, already ready, eliminated, under AI 托管, without state; data/config.json lists band_cannot; the server still accepts g.ready and endPrep wipes the funds, 坎诺特 keeps them; game.js asks beforeactions.readyfor the button and Space).
23.12 The host removes a human before the match (community report #17, owner approved) — server/lobby.js kick, shared/protocol.js (room.kick {seat, playerId}), screens/room.js SeatCard / kick, main.js CLOSE_REASON.kicked
- Seen: community report #17 「多人在房间准备阶段时给真人添加跟人机一样的踢出按钮,不然有的人离线了但是不会退房」 — only AI seats had the host's remove button; a human who stopped answering kept the seat.
- Official: none — the official room (research 06 §3.2: 4 seats, creator, ready) has no kick for players, PRTS neither; a lobby convenience the owner approved (owner's decision 2026-10-04). Before this, a human who dropped in the room lost the seat only after
lobbyGraceMs(60 s), and one still connected but idle kept it. - Now:
room.kick {seat, playerId}(C→S, host only;playerId= the player the host confirmed): refused when the seat changed hands while the confirm was open — that player left and someone else sat down (BAD_TARGET'seat changed hands', the newcomer stays; integration review); refused outside the LOBBY (ROOM_STARTED— during a match the seat is the player's to reconnect to), for the host's own seat, an AI seat (room.removeBotstays its message), an empty seat (BAD_TARGET), a non-host (NOT_HOST). The seat is freed at once (removeMember: grace timer cleared, result replay dropped, room state broadcast), so an offline seat no longer blocksroom.start. The player getsroom.closed {reason:'kicked'}— at once when connected, else on the next resume (session.notice, plus the result replay as after a grace timeout); the client goes back to the lobby with 「你已被移出同盟」 (CLOSE_REASON.kicked, already there). The room screen shows the host the same ✕ button on every other human's seat (tooltip 将该博士移出同盟) behind a confirm (移出同盟 「确定将「名字」移出同盟吗?对方可以凭同盟密钥重新加入。」). - Reconnect: net.js reconnect tokens belong to the player's session, not to the seat; after the kick the seat binding is gone, so resuming with the old token keeps the identity (same playerId, its loadout) but lands outside the room (
room.closed {kicked}, noroom.state). - [ASSUMED]: no ban — a kicked player may join the same room again with its code; no kick during a match; the result replay of the last match still follows
room.closedfor an offline kicked player (as the grace timeout). - Docs: DESIGN §8.1 (
room.kick,room.closed.reason'kicked'), README (和朋友一起玩 step 1), the server/lobby.js header. - Tests:
test/lobby-kick.test.js(the schema; a connected guest: room.closed {kicked}, the seat freed, no more room.state,NOT_IN_ROOM, joins again; an offline guest: freed at once, the host starts alone, the old token resumes the identity with room.closed {kicked} and no room.state; refusals: non-host, self, AI seat, empty seat, no playerId (BAD_MSG), outsider, during a match; a seat that changed hands — Alice confirmed, Alice leaves, Bob takes seat 1 — is refused and Bob keeps it, naming Bob works),test/lobby.test.js/test/lobby-loadout.test.jsunchanged and passing.
23.13 A skill with its own idle clip stands in it between attacks (community report #23) — render/spine.js SpineActor.setSkill / _skillIdle / update
- Seen: community report #23 「折娅开了技能就会不停做跳起来打人的动作,哪怕没有接敌」 — 折桠 (chess_char_2_17_a, char_4207_branch) kept jumping and striking during her S2 生存决心 with no enemy engaged.
- Official [DATA]: her battle model has
Skill_2_Begin,Skill_2_Loop(the jump strike, OnAttack at 0.8 s),Skill_2_EndandSkill_2_Idle(4 s).Skill_N_Idlecarries no OnAttack event — it is the skill's resting pose,Skill_N_Loopits attack (research 07 §5.4 roles; no source spells out the official state machine, the clip names and events are the evidence). S2 starts when she takes damage (rule TAKE_DAMAGE) and her range is her own tile, so she can be in her skill with nobody to hit — the sim makes no attack then. - Cause:
setSkill(true)queued the skill's loop after the begin clip and nothing left it while the skill ran, so the attack clip looped with no attack. The skill idle (anims.skill.idle) was only reached after the first spell of attacks ended, and then through the skill's End clip, which belongs to the end of the skill. - Now: when the equipped skill has an idle clip of its own (
skill.idlepresent and not the loop) and its loop is the attack clip, the begin clip is followed by that idle; an attack plays the loop (wind-up and strike frame as before); when the attacks stop the model goes straight back to the idle; the End clip plays only when the skill ends. The manifest has 92 such skills on 62 operators' Front models (e.g. 史尔特尔 S3, 耀骑士临光 S1, 荒芜拉普兰德 S1–S3), all handled the same way; the 6 without a begin clip (耀骑士临光 S3, 缄默德克萨斯 S2 / S3, 引星棘刺 S3, 灰毫) already started in the idle and are unchanged at the start. Unchanged: a skill whose loop is its idle (蕾缪安 S2 / S3, 缇缇 S2, 信仰搅拌机 S3 — a stance), skills without an idle clip (begin → loop held as the stance; their spell of attacks still ends through the End clip), attacks borrowed from the normal attack (via: 'attack'), enemies. - Players notice: 折桠 (and every operator whose skill has its own idle pose) stands in that pose while the skill runs and nothing is in range, and only swings when the sim actually attacks.
- Tests:
test/render/feedback3-skill-idle.test.js(折桠 S2: begin → Skill_2_Idle, the loop only on attacks, back to the idle without Skill_2_End, End on stop; 史尔特尔 S3 begin → Skill_3_Idle; 耀骑士临光 S3 unchanged; 蕾缪安 S3 and 宴 unchanged).
23.14 The ammo bar counts the extra bullets (community report #35) — sim/skills.js Skill.ammoMax / activate / addAmmo / end / reset, sim/snapshot.js unitTuple
- Seen: community report #35 「拉特兰盟约叠层之后蕾缪安3技能锁定敌人消耗的子弹不会实时让技力条一格一格减少,而是最后突然消耗(最终效果好像是正确的)」.
- Official [DATA]: 拉特兰 「拉特兰干员开启技能获得的弹药量提升」 (
base_ammo_percent0.05 +ammo_percent_per_stack0.015 × layers); 蕾缪安's talent 逃犯引渡手续 adds bullets at the skill start too. The bullets themselves were right (the lock / bombard count followedammoLeft); only the bar was wrong. - Cause:
installLateranoraisesammoLeftin skillStart whileskill.ammostays the base 5, and the snapshot drewammoLeft / skill.ammo; the client clamps the bar at 1 (render/units.js). With 30 layers 蕾缪安 starts S3 with 9 bullets: the bar stayed full for the first four locks, then shrank by fifths — "consumed at the end". - Now: the skill keeps
ammoMax, the most bullets this activation has held: set at activation, raised after the skillStart handlers (any bullets they added — 拉特兰, 逃犯引渡手续, talents writingammoLeftdirectly as SIM §7.5's example does) and byaddAmmoabove it (refills under it leave it), 0 when the skill ends. The snapshot's draining bar isammoLeft / ammoMax, so it starts full and every bullet takes one share (9 bullets: 8/9, 7/9 …).skill.ammo(the base the next activation starts from) is unchanged — raising it would compound the bonus on the next cast. No sim number changes; only the wirespof a running ammo skill. - Players notice: with 拉特兰 layers the skill bar drops one segment per bullet from the first shot.
- Tests:
test/sim/feedback3-ammo-bar.test.js(蕾缪安 S3 with 30 拉特兰 layers: 9 bullets, the full mark 9, the bar k/9 after each lock; a refill under the mark keeps it, above it raises it; reset at the end),test/content/bonds_core.test.js(拉特兰 counts, unchanged).
23.15 Unit sounds keep their official play chance and volume (community report #30) — tools/assets/audio.mjs bankMix / indexAudio().mixOf, tools/assets/plan.mjs unitSounds, data/assets.json audio.sfx.units[id].mix, public/js/audio.js unit / unitGain / unitSoundPlays
- Seen: community report #30 「狗、飞机敌人音效声音过大」.
- Official [DATA] (excel/audio_data.json):
battle.ON_ABILITY_ON.enemy_1000_gopro(_2/_3).combat= an empty asset (weight 80) +e_atk_gopro_1(10) +e_atk_gopro_2(10), volume 1;enemy_1165_duhond(_2).combat=e_atk_gopro_1(20) + empty (80) — the dogs bark on about one attack in five.enemy_1005_yokai_2 / _3.attack(妖怪 / 威龙) = one vehicle clip at volume 0.7. Across the manifest's unit sounds 29 units have a bank that is not "always, volume 1": the three dogs (chance 0.2) and 26 with another volume (0.45–0.9 quieter, e.g. 妖怪, 盾卫's impact 0.65, 大鲍勃 0.45 / 0.6; 1.5–2 louder, e.g. 缄默德克萨斯's impact). - Cause:
indexAudiokept only the files of a bank (empty assets, weights and volumes dropped), so 猎狗pro / 深池侦察犬 playede_atk_gopro_1on every attack, andaudio.js unit()played every attack / impact at a flat 0.55. - Now: the asset tool records each bank's mix —
p= the weight of its sounds that have a file over all the weights,vol= the played file's volume (mean of min / max) — andplan.mjswrites it, when either differs from 1, assfx.units[id].mix[role]for attack / hit / die / born. data/assets.json was patched offline from the cached audio_data.json with the tool's own functions (each role's URL checked against the bank's first file; the JSON compare shows only the 29mixentries and the contenthash); the nextfetch-assetsrun writes the same.audio.jsplays a unit's own sound with chancep(a silent roll still counts as the unit's own sound: no genericenemyHitinstead) at its base gain (0.55 attack / hit, 0.8 die / born) × min(1,vol): 妖怪 / 威龙 0.385, the dogs 0.55 on 20 % of their attacks; a bank louder than 1 stays at the old gain (the report asked for quieter, never louder). The roll isMath.random— presentation only, no determinism needed. Skill-start banks (4 official ones are quieter, 0.6–0.85) keep the old gain. - [ASSUMED]: 猎狗pro's bank names two barks (10 + 10); the manifest has only
e_atk_gopro_1, so it barks that one on its full 20 %. The volume is the first file's (the one the manifest downloads); a fallback file, if ever used, keeps it. - Players notice: dogs bark now and then instead of on every bite; 妖怪 / 威龙 and the other officially quieter units are softer; nothing is louder.
- Tests:
test/ui/feedback3-audio-mix.test.js(bankMix of the official dog / 妖怪 banks andmixOfon a picked list; data/assets.json mix vs audio_data.json; a dog barks only on rolls < 0.2 and never falls back toenemyHit; 妖怪 0.385, a volume-2 bank stays 0.55, 余's born 0.6),test/ui/audio.test.js,test/assets.test.js(unchanged).
23.16 A model load left hanging by a hidden tab is started again (GitHub #68) — render/units.js SPINE_STUCK_MS / retryAssets / update / _acquireSpine, assets.js RefLru.restart / _attempt / _withTimeout, forgetPendingSpine, loadSpineData(entry, { fresh }), store spine.restart
- Seen: GitHub #68 — after switching back to the game's tab some units stayed the placeholder (the diamond with the glyph) instead of their model.
- Cause:
UnitView.retryAssets(run when the tab is shown again, §21.27) only reloads a view with no model and no load in flight. A load begun while the tab was hidden can stay pending — a request the background tab left hanging —, and every later try joined it:PIXI.Assets.loadof the same URL hands out the same pending promise, and the spine cache shares an entry that is still loading. Its 20 s timeout and the bounded retries (§21.27) then ran into the same pending request until they gave up. - Now: a view remembers whether its load began while the tab was hidden. When the tab is shown and that load is still in flight,
retryAssetsgives itSPINE_STUCK_MS= 5 s [ASSUMED] (a load about to succeed is never doubled); if it has not settled by then,updatecallsassets.spine.restart(entry).RefLru.restartstarts a new attempt of the same cache entry: its references and the promise every holder waits on stay (no acquire / release changes), the hung attempt is abandoned at once (its concurrency slot freed — a hung load no longer blocks the queue — and a late result ignored: PIXI shares resources by URL, so nothing is unloaded), the first attempt to succeed settles the entry, only the newest one's failure fails it. A restart — and any load after a remembered failure (a timeout may have left PIXI's request pending) — isfresh:loadSpineDatafirst drops PIXI's still-pending requests of that skeleton (forgetPendingSpine: the .skel's entry in the loader's promise cache and, unless the atlas finished, the atlas's and its pages'; pages another cached skeleton uses stay), so the new load asks again instead of joining them. A load begun while the tab was visible keeps the old path (20 s timeout, bounded retries). - [ASSUMED]: the 5 s grace; if the abandoned request completes after the restart, its PIXI resources are left to the garbage collector (a one-model leak on that rare path, preferred to unloading textures a shown model may share).
- Players notice: units whose models were loading while the game sat in a background tab show their models a few seconds after coming back, instead of staying placeholders.
- Tests:
test/render/feedback3-stuck-load.test.js(RefLru.restart: the waiting promise settles with the new fresh attempt, refs kept, a late old result ignored; the abandoned attempt frees its slot; only the newest failure counts; forgetPendingSpine drops only unfinished requests and keeps shared pages; a view whose load began hidden is restarted SPINE_STUCK_MS after the tab is shown and shows its model with one reference; a load finishing within the grace is not doubled; a load begun while visible is not armed),test/render/issue8-placeholders.test.js,test/render/assets.test.js(unchanged).
23.17 Shots leave the hands and meet the chest; no attack jolt on a shown model (GitHub #61) — render/projection.js Camera.liftFor, render/fx.js SHOT_HEIGHT / bodyZ / _bodyPt (shots, boomerang, mortar, beams, lock marks), render/units.js update (the jolt)
- Seen: GitHub #61 — shots looked as if they came out of the operator's crotch and hit the enemy's hips; on every attack the whole model jumped a little toward its target (a ranged operator shooting up the screen hopped upward).
- Official: no data gives the official muzzle points (weapon tips are in the prefabs, not in the tables we read); the official models do not move toward their target when they attack — the attack is the Spine clip.
- Cause: (a)
fx.js chestZ= feet + 0.45 × the unit's head height, used as a WORLD height; the battle models are upright screen-space billboards and the camera is pitched 30° from straight down, so a vertical world height draws at about half its length: shots started and aimed at ≈ 18–23 % of the model. (b)units.jsshifted the body 0.12 tiles toward the target on every attack (lunge) — the avatar diamond's stand-in for an attack clip — also while the Spine model played its own clip. - Now:
SHOT_HEIGHT = { launch: 0.45, aim: 0.5 }[ASSUMED: ≈ a battle chibi's hands / chest; enemies' torsos] are shares of the drawn model's height above its feet;bodyZ(cam, view, share)turns one into a world height withCamera.liftFor(x, y, z, px)(the exact lift whose projection ispxhigher on screen). Shots and boomerangs leave atlaunch(the boomerang returns to it) and fly toaim, shells still fall to the feet, the mortar starts atlaunch, lock reticles sit ataim; beams go from the shooter'slaunch(a chain bounce from the previous target'saim) to the target'saim. Other unit-anchored effects (_chest: buff sparkles, auras) are unchanged. The jolt runs only while no Spine model is shown (the avatar diamond, DESIGN §9's "bob / lunge" fallback). - Height used: the unit's head height (
_headTiles, the HP bar's): the uniform chibi headroom for operators and tokens, the setup-pose bounds for enemies (clamped as for the bar). Operators' setup-pose bounds are not their drawn body — they include weapons and effect sprites (median 1.34 tiles, 90th percentile 2.35, one skeleton 7.62) —, so shots from them would leave above the visible model; the review's tall-operator case (秋白, 史尔特尔 …) is such bounds, not a taller chibi. A tiny enemy whose bounds sit under the bar's 0.55-tile floor (青瓷 / 彩瓷茶器) fires from near its top. - Players notice: arrows, bolts and beams come from about the hands and strike the upper body; a model no longer hops toward its target on each attack.
- Tests:
test/render/feedback3-shot-height.test.js(liftFor exact on the normal / prep / boss / unite cameras; an arrow starts at 45 % and aims at 50 % of the drawn models — the old rule drew < 25 %; a raised / hovering unit measured from where it is drawn; the diamond still lunges, a shown Spine model stays put and plays its attack clip),test/render/fxproj.test.js,test/render/projection.test.js(unchanged).
23.18 The leader stands on the boss field in the prep; its hit tiles light red beside a range preview (community report #12) — match/waves.js previewOf (start), render/prepfield.js leaderStand, render/pick.js hitTiles, render/app.js setLeader / leaderShown / syncLeaderHits / leaderAt / LEADER_HIT_STYLE
- Seen: community report #12 「最终boss应该在准备时间就放到关卡中,点击干员拖动选面朝方向并且展示干员攻击范围时候也展示那样大体型boss实际受击地块,干员攻击范围目前用的橙色,boss受击范围可以像官方原版那样用红色」 — in the Final Assault / Hidden Core prep the camera shows the boss field, but the leader waited in the enemy pen off screen, and choosing a direction showed only the operator's orange range.
- Official: research 09 §2.2 [DATA + WIKI]: during a boss round's prep the leader previews on the boss field, standing by its spawn with its HP bar (the PRTS 最终攻势 intro image). Research 08 §4.1 decoded the client's preview row offset (+13 for leader rounds), which lands in the pen — the two disagree; the owner's decision 2026-10-04 (approving report #12) puts the leader on the field. The hit area is the sim's: data/enemies.json
hitArea(PRTS 巨型单位 "长4.95、宽2.95的长方形,向上偏移1.0", DESIGN §19.4) — 胄 / 管 / 昆图斯 / 阿利斯泰尔 / 萨米的意志 occupy 5 × 3 tiles (rows 3–5, cols 8–12 from (3, 10)); 铳 and 卢西恩 are point units (their tile). No source shows the colour of such tiles: red is the players' request. - Cause:
previewOfdropped the leader's route start, so the client could only lay it out in the pen (gateOf+ 13 → the pen's upper zone,layoutPen), which only the pen camera shows; the range preview (highlightTilesgroupsfacing/selRange) had no notion of the leader's body. - Now: the leader's preview entry carries its spawn tile
start([row, col] on the boss field; escorts and bounties do not). In the prep the field view takes the first such entry out of the pen layout (leaderStand) and stands its model there (setLeader: idle, its full HP bar, facing the player's half — left half → left [ASSUMED look] —, a tap opens its details like a pen enemy), shown only by the boss-field prep camera (leaderShown, also during a flight to / from it); a real battle clears it. Whenever a range preview is shown there — the direction wheel's (facing) or a selected piece's (selRange, any RANGE_GROUPS highlight) — its hit tiles (hitTiles= the sim'sbody.js bodyKeys: every tile its rectangle overlaps) are lit in red (LEADER_HIT_STYLE0xff3b30, fill 0.3 [ASSUMED]) beside the orange range, and go with it. Display only: no sim or rule change. The escorts stay in the pen (research 09 says they preview on the boss field too — not part of the report; left open). - Players notice: in the boss rounds' prep the leader stands where it will fight, with its HP bar; while choosing an operator's direction (or looking at a selected operator's range) the tiles where the leader can be hit show in red next to the orange range, so one can see at once whether the range reaches it.
- Tests:
test/render/feedback3-leader-prep.test.js(previewOf carriesstartfor the leader only; every boss template's leader preview = its route start;leaderStand+hitTiles= the sim's bodyKeys for every leader, 15 tiles for the huge ones, 1 for 铳; the leader view shows a full HP bar; app.js wiring checks),test/render/fieldview.browser.test.js"Final Assault prep (L / R): the leader stands on its spawn tile …" (RENDER_E2E: on (3, 10), facing the player's half, not in the pen, no red tiles until afacingpreview, then the 15 hit tiles, cleared with it, hidden by the own-board camera; screenshots fa-leader-L / -R),test/match/waves.test.js,test/render/pen.test.js(unchanged).
23.19 Spectator seats: up to two per co-op room, watching like an eliminated player (community report #26, owner approved — a remake feature) — server/lobby.js (Room.spectators / spectatorOf, spectate, removeSpectator, freeSpectatorSeat, roomOf, onHello / onDisconnect / runResync, routeGame, memberSessions / sendToPlayer, buildReplay, disposeRoom), server/match/Match.js (opts.spectators, addSpectator / removeSpectator / _spectator / _viewers / _resync / _spectatorSpec, handle, sendTo / _sendStart / _humansShowing, finish), server/net.js HEAVY_TYPES, shared/constants.js MAX_SPECTATORS / ERR.SPECTATOR, shared/protocol.js (room.spectate, room.removeSpectator), store.js isSpectating, main.js onRoomState, screens/lobby.js (观战), screens/room.js SpectatorBar / roomFacts, screens/game.js, ui/hud.js TopBar, ui/combatHud.js, ui/matchChrome.js ExitModal, screens/briefing.js, screens/bandDraft.js
- Seen: community report #26 「添加一到两个观战席」.
- Official: none — not in the official game. The official room has the players' 4 seat cards and nothing else (research 06 §3.2); watching exists only for the players themselves: PRTS 卫戍协议/帮助 「阵亡的玩家可在模拟中观战」, in 联防 「所有玩家于该阵地观战」, and a teammate's field after the own battle (research 09 §3.1, §6.3; §14 Spectating). A remake feature the owner approved (owner's decision 2026-10-04); no rule of the match changes.
- Now (the seats):
room.spectate {code}(C→S) takes one of a co-op room'sMAX_SPECTATORS= 2 spectator seats — in its lobby or while its match runs (a player seat can only be taken in the lobby). Refused: unknown code (ROOM_NOT_FOUND), a solo room or both seats taken (ROOM_FULL), a player of that room (ALREADY— nobody switches to spectating in place), a session whose own room is in a match (ROOM_STARTED, asroom.join); repeated by the spectator it is idempotent, and from another lobby room it leaves that room first (asroom.join).room.statecarriesspectators: [{ playerId, name, connected }](always present,[]without). A spectator is no player: never inseats, never counted for the 1–4 players, the ready count or theroom.startgate, never host (migrateHostreadsseatsonly), and never keeps a room alive — when the last human leaves, the room closes and its spectators getroom.closed {empty}(players who left themselves get nothing, as before). The host frees a spectator seat withroom.removeSpectator {playerId}(any time;NOT_HOST/BAD_TARGETotherwise): the spectator getsroom.closed {reason:'kicked'}at once, or on its next resume when offline (with the result replay, asroom.kick§23.12 and the grace timeout).room.leave/g.leaveleave a spectator seat; in the lobby a spectator takes a free player seat withroom.joinof the same code (UI 入座). - Now (what a spectator may do):
g.watch(the per-socket heavy bucket, 2/s burst 6, like every watcher),g.leave/room.leave, androom.loadout(checked and stored on its session for a later match, never handed to the match). Everything else → the newERR.SPECTATOR(「观战中无法进行该操作」): the lobby refuses every otherg.*/b.*before the match sees it (routeGame),room.readytoo; host-only messages stayNOT_HOST;Match.handlerefuses anything butg.watchfrom a spectator as well. Emotes are off for spectators [ASSUMED] — they see the players' bubbles but cannot send any, so they cannot spam the match. - Now (what a spectator sees) — what an eliminated player watching sees, never more: every broadcast of the room (
room.state,m.public,m.ticker,m.emote,b.pool: the public data every player gets); the match registers the spectator (opts.spectatorsat the start,addSpectatorwhen it joins mid-match and on every resume / resync) and shows it fields through_viewers()— the loop that already served eliminated humans: each 各自行动 the first field (b.start watch: true, a display replica; server-run modem.field+b.snap), the 联防 spec as an observer, the first boss field of 最终攻势 / 隐秘核心, any field ong.watch(alive false: none of the fighting player's limits — own battle running, the other pair's boss field), a player's board in prep ong.watch 'n:<pid>'(the one-shot scoutingm.field {prep: true}),b.endof the field it watches, andm.resultat the end (the rows every player gets;playerId= the spectator, no row of its own). Never anm.private(shop, hand, 临时整备区, funds, effects, loadout), anm.toastor anm.unitStats; never a field's player or authority. - Private-data check: the watch view's
b.startspec carriesplayers[].contentInfo, battle-start counters written byglobal:contentb_info(sim/content/support/meta.js):funds,handUnits,handChess,roundStats… — every watcher (a teammate's 前往查看, an eliminated player) receives them, no screen shows them. A spectator's copy leaves outcontentInfo.funds(Match._spectatorSpec, cached per field): no battle effect reads it (only the prep-side meta readsctx.funds()), so the replica still plays the same battle. The other counters stay: battle effects read them (handUnits— the prep-gated 机变 cards' fallback;roundStats.gainedChess— an item), and a replica with other inputs could show another fight.m.public players[](LP, strategy, shop level, board count, bonds, status) is what every teammate already sees. - Reconnect: a spectator seat is kept like a player's — in the lobby
connected: false, freed afterlobbyGraceMs(60 s; thenroom.closed {timeout}on resume); during a match kept for the reconnect window (10 min), its disconnect not told to the match (it plays no field). A resume (or a repeated hello, coalesced perresyncMinGapMsas for players) answersroom.state+Match.addSpectator:m.publicand the watched battle (first field by default), or the result once ended. - Client: the lobby has 观战 beside 加入同盟 (tooltip: 不占博士席位,只能观看,每个同盟最多 2 名,模拟进行中也可进入). The room shows a 观战席 N/2 strip under the seats while it has spectators — names, offline marks, the host's ✕ (no confirm [ASSUMED]: a spectator can come back with the code), and 入座 for a spectator while a player seat is free; a spectator's ready button reads 观战中 (disabled) and its status line 「观战中 · 不占博士席位,模拟开始后可切换观看各位博士」.
main.jskeeps the room for a spectator (onRoomStatechecksspectatorstoo). In the match a spectator takes the eliminated player's path (alivefalse): no shop, no ready toggle (TopBar spectator), no 交流 wheel, no 返回自己 / 返回战场; the briefing button reads 观战中, the draft sends nog.bandFocus; the pill reads 「观战中 · 点击左侧成员头像切换查看」 in the watching pill's amber and the combat HUD 「观战中…」 — never 你已被淘汰; in 休整期 / 机变 / round start it is shown the first player still in by itself (once per phase; a team row switches); the exit dialog offers only 离开观战 (no 暂离). - Protocol version:
PROTOCOL_VERSIONstays 1, as for every change since the first import (room.loadout,g.pause,g.unitStats,m.unitStats, theb.*frames): the change is additive — two new C→S types, a newroom.statefield, a new error code, an optional match option and two optional match methods. The server serves its own client, so only a stale tab could meet them, and the build guard reloads such a tab (ui/buildGuard.js). - Server load: at most 2 per room; a spectator's frames are the room's broadcasts (encoded once,
broadcastRoom) plus theb.start/m.fieldof what it watches;g.watchandroom.spectate(a mid-match join resends the state) draw from the heavy bucket; the per-network socket cap is unchanged./healthzcountsspectators. - [ASSUMED]: solo rooms have no spectator seat; spectators cannot emote; no ban after a removal (like
room.kick); no confirm on the ✕; the host may also remove a spectator during a match (server; the UI offers it in the room only); the spec withoutfunds; the auto-shown board in prep; no spectator count in the match HUD. - Docs: DESIGN §8.1 (spectator-seat paragraph), META §1.4, PLAYING §8, README (观战 row), the headers of
server/lobby.js,server/match/Match.js(interface),screens/room.js,screens/lobby.js,screens/game.js. - Tests:
test/lobby.test.js(+3, StubMatch / RecordingMatch over sockets: join / idempotent /ALREADY/ cap / never a player seat /SPECTATORandNOT_HOSTrefusals / 入座 / leave / host removal online and offline (room.closed {kicked}on resume) / solo and unknown codes / a room's last human leaving →room.closed {empty}; during a match: broadcasts but nom.private, a mid-match join, refusals,room.loadoutkept off the match, drop and resume give the seat back, staying through the result; lobby grace like a player,opts.spectators, onlyg.watchrouted,addSpectatoron join / resume,removeSpectatorong.leave, neveronDisconnect / onReconnect / onLeave; the protocol fuzz now also takes / frees spectator seats and checks the cap, no spectator in solo rooms, never seated twice, never host, the session pointing at its seat),test/match/spectator.test.js(3: a whole client-combat match — 各自行动, 联防, 最终攻势, settlement — everyb.startwatched and never authoritative, the first field like an eliminated player,b.endof the watched boss field, the samem.resultrows, no frame (unicast or broadcast) with a private key, every player intent refused; mid-battleg.watchof any field, the spec withoutfunds, a spectator added mid-battle gets the state, a removed one gets nothing; server-run modem.field+b.snap),test/match/lobby-integration.test.js(+1: the real Match over sockets — briefing / draft / prep refusals, the prep board, a watchedb.startwithout funds, drop and resume, no private frame, the room closing for it when the last player leaves),test/client-static.test.js(+1:isSpectating,roomFacts),test/ui/spectator.e2e.test.js(opt-in browser,SP_E2E=1: lobby 观战 → room strip and 观战中 → the host's ✕ → briefing / prep / battle views, nom.private, a reload, 离开观战; screenshotstest/e2e/out/spectator-*.png).
23.20 阿戈尔 5: the 3 revives belong to the first 3 members by position (community report #24) — withdrawn in 0.1.4, see §24.3 — content/bonds/core.js installEgir (egirOrder, shared with devour)
- Seen (community report #24, many players): 「阿戈尔盟约复活有bug,只能复活一个干员」「阿戈尔5人羁绊不生效」. The usual food chain puts the carries back-left and the food front-right; the battle-start devour knocks the food out first, and our rule ("the first 3 members knocked out", since 0.1.0) gave it the 2–3 revives at t ≈ 0 — seen only as a fall and a redeploy in the deploy frame. In the fight the carries fell and nobody came back. Measured (real co-op Match 险境, seeds 3 / 11, R1–R14 with 联防 and the boss, the 46 battles of the player holding 5 阿戈尔): 126 instant revives at t = 0; 4 of the 16 later knock-outs were revived.
- Official: PRTS 卫戍协议:盟约 下半/PRTS盟约记录, 阿戈尔: "<在场5名不同【阿戈尔】干员>前3名【阿戈尔】干员首次被击倒时立刻复活" (bonds.json
max_free_respawn_cnt3); the revived unit is a 受益者 ("“复活”的实现方式为:受益者因移动之外的原因退场时…"); the devour note starts "从最先部署(更靠左和靠上的)的【阿戈尔】干员开始". The 2026-03-14 notice (ak.hypergryph.com news 5114) raises the 阿戈尔 revivers, a 特定数量, to 前3名 — worded unlike 埃芒加德's first N knocked out. Players' videos read it as by position: BV146QXB4EUK (2026-03-22, danmaku 207–284 s: by position, left to right and top to bottom, not by who is eaten first), BV11XwCzXEH9 (2026-03-19, 66 s: column by column from the left until the count runs out), BV17jSsBpEER (act 1: the uploader's pinned reply — by position, bottom / left in act 1, unrelated to the eating order). So the 3 are the first 3 阿戈尔 in the deployment / devour order (left column first, top to bottom; mirrored on the right-hand boss side), and each revives once on its own first knock-out, whatever the order of knock-outs. - Cause:
installEgir's death hook revived any member on its first knock-out until 3 revives were spent. - Now: at
battleStart, beforedevour(), the beneficiaries are fixed: the firstmax_free_respawn_cntmember operators on the field in the devour's order (egirOrder, now shared by both: board column — a mirrored player counts from the field's right —, then top first, then id). The death hook (priority 11, still ahead of 不屈's 10) revives only a beneficiary, once, on its first'killed'; the other members' knock-outs are left to 不屈 and the rest. A member the devour knocks out comes back only when it is a beneficiary; its pending marks are cancelled as in 0.1.2 (§22.3). §22.3's chain 浊心斯卡蒂 → 乌尔比安 → 幽灵鲨 → 海霓 → 深巡 → 隐现: 幽灵鲨 (third by position) falls and is back at t = 0, 海霓 and 深巡 stay down until their redeploy, 浊心斯卡蒂 and 乌尔比安 come back on their first knock-out in the fight. Every battle (normal, 联防, boss) fixes its own beneficiaries. Measured again (the same 46 battles): 47 instant revives at t = 0 (only food among the first 3 by position); 10 of 25 later knock-outs revived (the carries; the rest were second knock-outs or other members). - [ASSUMED]: (1) A member entering 联防 down (forced out before
battleStart, §19.3) is no beneficiary; the next members on the field are. A 调和 member counts like any other member. (2) A beneficiary that 埃芒加德 or M3茧甲 saves in place (the remake's stand-in for PRTS's 0-time redeploy, §21.21) has used its 阿戈尔 revive, so its first knock-out after that is final — by analogy with PRTS's 不屈 note "持有“复活”效果时,即使触发了不屈的“重新部署”效果,也还是会消耗一次“复活”次数" (a held 复活 is consumed even when another effect brings the unit back at once). Read when the beneficiary is knocked out: its in-place revive count (mem.revives, itemsrevivedInPlace) is compared with the one at battle start. - Not changed: the devour (§22.3) and its order; 埃芒加德's own rule (the battle's first 3 knock-downs, as the notice words it — the devour's food still takes its charges at t = 0, community report #37: not a bug since 0.1.2). No icon marks the 3 holders yet.
- §22.3 edited: the one sentence that said the 3 revives are spent at t = 0 "as officially" now says what 0.1.2 did and points here [integrator: renumber "§23.20" there]. research 02 §3.6 and its JSON spec say "by position" (data/bonds.json
egirShip.specregenerated withbuild-data --offline; a JSON compare shows only that string changed). - Tests:
test/content/bonds_core.test.js— '阿戈尔 5: the first 3 members by position …' (rewritten: the bottom of the first column and the second column, knocked out first, stay down; the three top-left revive in any order, at full HP on the same tile with SP reset; a second knock-out is final; the relocation case; 3 members: none), '阿戈尔 5 + the devour chain (GitHub #33) …' (rewritten for the new outcome, normal and 联防), new '阿戈尔 5 beneficiaries: fixed at battle start …' (a 联防 member entering down keeps the slot (the forced exit is not a 击倒; reviewed 2026-10-04), a 调和 member included, the mirrored boss side from its own left), new '阿戈尔 5: a beneficiary 埃芒加德 or M3茧甲 save in place …'.test/content/feedback3-egir-down.test.jspasses unchanged;test/content/feedback1f-hammer.test.js's devour case has 3 members (no 5-tier) and passes unchanged.
23.21 傀儡师: the switch ends at full HP (community report #2) — sim/professions.js installDollkeeper (startSwitch)
- Seen (community report #2): 「傀儡师归溟幽灵鲨死后(阿戈尔盟约吞噬)之后无法正常切换形态复活」. 0.1.2 (§22.11) gave her the 替身 on a lethal hit, and a single devour mark works (full-HP 替身, back to the 本体 at 21 s). With two or more marks on her — e.g. 乌尔比安 → 斯卡蒂 → 归溟幽灵鲨 → 深巡 → 幽灵鲨 → 隐现 — the first mark switches her and the next lands inside the 1-s switch: the switch is no knock-out, so the mark is not cancelled; 无敌 does not stop a 流失 and 不死 holds it at 1 HP. She then fought the 替身's 20 s at 1 HP (1/3875), fell to the first hit and was down for 70 s.
- Official: PRTS 分支特性信息 §傀儡师: "切换动画开始时会清除自身一切Buff;切换途中重设自身生命至最大值,随后再次清除自身一切Buff", and during the switch animation she holds 不死 and 无敌 (with 阻回, 禁疗, 孤立, 强制缴械 and stun / freeze / sleep immunity). The HP reset comes after the start of the switch and 不死 holds throughout, so she always leaves the switch at full HP.
- Cause:
enter()/leave()set the HP to the max only at the switch's start; thefatalhook's 不死 then kept a later lethal 流失 at 1 HP, and nothing set the HP again. - Now: the 1-s
trait:dollSwitchingbuff also sets the HP to the form's max when it expires (both directions: to the 替身 — its own max, 风丸's 纸偶 HP — and back to the 本体), if she is still on the field. Its early removal (knocked out, withdrawn) only clears the flag, as before. The 2-mark case: 1 HP at t ≈ 0, full HP (3875) when the switch ends at 1 s; a lethal 流失 inside the switch back is held at 1 HP and the 本体 comes out at full HP. - [ASSUMED] (kept from §22.11): the switch lasts
DOLL_SWITCH(1 s); the reset is placed at its end — PRTS says "途中" without a time, and the end is the latest point that still makes every lethal 流失 inside the switch harmless. The second "清除自身一切Buff" after the reset is not modelled (statuses landing during the switch stay; she is 无敌 and immune to stun / freeze / sleep then). - docs/SIM.md (Professions, dollkeeper) says it.
- Tests:
test/sim/feedback2-doll.test.jsnew 'E: a lethal 流失 inside the switch (a second 阿戈尔 devour mark) …' (the real chain with 5 阿戈尔: two devour marks on her, 1 HP inside the switch, full HP when it ends; then a lethal 流失 inside the switch back, held at 1 HP, the 本体 at full HP; never knocked out — fails on 0.1.2's code); cases A–D, the hammer and 风丸 cases,test/sim/professions.test.js,test/render/feedback2-doll.test.jsandtest/content/feedback3-indom-exits.test.jspass unchanged.
23.22 联防 fights with the layers the helpers' own combat reached (community report #24) — match/PlayerState.js battleInput (reached), bondsView, match/unite.js uniteBattleOpts
- Seen (community report #24): 「两百多层不屈盟约复活有时候依然不触发」. The 不屈 chance itself matches the official (0.18 + 0.004 × layers: 196 → 287/300 hits, 200 → 292/300, 204 → 299/300, ≥ 205 → 300/300). But the 联防 battle ran on the round-start layers while the strip showed the layers reached in the own combat: with +20 in the own combat, 联防 used 230 while the strip showed 250 (a real co-op Match, real sim). Near 200 layers that is the gap between a sure redeploy and a miss.
- Official: PRTS 卫戍协议/帮助 §联防阶段: "所有防卫成功的玩家(最多2名)将以其阵地当前的状态,迎接所有防卫失败玩家未击倒敌人的集合。该阶段不能叠加层数。"
- Cause:
uniteBattleOpts→PlayerState.battleInput→bonds: bondSnapshot(this.bonds)— the persistent state; the round's in-battle gains sit inpendingLayerGainsuntil SETTLE, and only the views (bondsView, §20.15) added them. - Now: the 联防 input takes its bonds from
bondsView()(battleInput({ reached: true })): the persistent layers plus the own combat's pending gains, floored and capped at 999 exactly assettle()adds them (bondsWithGains/layerGainRoom) — the count the strip shows. Nothing else changes: the 联防 still gains no layers (layerGainsEnabledfalse), its result's gains are never read, and SETTLE adds the own combat's gains tops.layersonce, as before (no double count: the next round starts from 250, not 270). The normal and boss inputs keep the persistent layers (a boss round has no own combat before it). - [ASSUMED]: "当前的状态" includes the layers the own combat reached that round (PRTS lists no field of that state; the strip shows them and the official mode carries layers within the round). The helpers' order between two (unite.js
helperOrder, "盟约层数最高(存疑)") uses those same reached layers — persistent plus the pending gains, capped like the battle — so the right-hand helper is the one who actually fights with more layers. [ASSUMED] the 存疑 tie-break follows the layers the 联防 battle uses; the official line does not say "persistent only". - Exit paths for 不屈 re-checked on this head (no change here): knocked out on the ground, 撤退 / 商人 (0.1.3, workstream W3A), 流失 to 0: rolls; the 联防 forced exit: no (official); an operator on a 高台 tile: no (by tile, community report #33). Pith: no 不屈 effect of her own (调和 +1 is for core bonds; 不屈 is not core).
- docs: DESIGN §5.1 (
bonds), §6.1 UNITE and §20.15, META (bond layers in the views), PLAYING (联防), research 01 §4.4 say it. - Tests:
test/match/watch-bonds.test.jsnew 'the 联防 field fights with the layers the helper's own combat reached …' (fake battles: p_0 perfect with +20 不屈 / +9 炎, p_1 leaks; the own combat's input 230 / 995; the 联防 input 250 and 999 (capped), equal to the strip; nothing persistent before SETTLE; after it 250 / 999, and the round-2 combat starts from 250 — fails on 0.1.2's code); the otherwatch-bondscases andfeedback3-unitecarry,unitedown,combat,clientCombat,observe,results,hardening,teammate-loadoutmatch tests pass unchanged.
23.23 The initial deployment goes by column: the left column first, top to bottom (the report's side finding) — sim/Battle.js start, match/board.js mergeTile (+ boardOrder's comment), ui/gameLogic.js mergeTarget
- Seen (re-verification of the 阿戈尔 reports):
Battle.startdeployed row-major — the top row first, left to right within a row (the mirrored right boss side: right to left in field columns) — while the 阿戈尔 devour, written from PRTS's own wording, already ran by column. Act-2 videos (BV1rVwjziEQt, BV1pGAPzrEpZ) show the operators coming in column by column from the left, and the aggro that follows the same order. - Official: PRTS 卫戍协议/帮助 §作战阶段 "按从上到下>从左到右的顺序部署。优先部署干员,随后为召唤物(如果有)", with the note "自卫戍协议:盟约 下半(2026/3/14)起,部署顺序由“从左到右>从下到上”更改为“从上到下>从左到右”"; §多人 "分属于右侧场地的玩家,其与方向相关的盟约效果、部署顺序等都将左右镜像变化". Read as a scanning order — down each column, the columns from left to right (act 1: along each row, the rows from the bottom) — it agrees with PRTS's 阿戈尔 note "从最先部署(更靠左和靠上的)的【阿戈尔】干员开始", the bond text's devour priority ("优先更靠左和靠上"), act 1's (bottom, then left) and the videos. Read as sort keys (the top row first) it contradicts all of them.
- Cause: §5.5 / §19.1 took "从上到下>从左到右" as row-major ([ASSUMED] in §19.1).
- Now: per player the operators deploy by column — the left board column first, top to bottom within a column — then the summon pieces the same way; the right-hand boss player starts from its own left (the highest field column), as the devour does (
egirOrder). The shared-field interleave (the i-th operators of every player together, then the summons) and every other rule are unchanged; unit ids keep the battle input's reading order (board.js boardOrder, now described as what it is: the input / dispatch / view order, not the deployment order). What follows from the order: the 仇恨 tie-break "最后部署的目标" (aggroSeq) — an unblocked ranged enemy at equal taunt now picks the bottom of the rightmost column —, 耀骑士临光's "上一名部署干员", the order ofdeployeffects. A merge's elite among several deployed copies takes the tile of the copy that deploys first [ASSUMED, §20.11]:mergeTileand the client'smergeTarget(the gold tile in prep) now sort col asc, then row desc (in board coordinates: the mirrored boss half deploys in the same board order). - [ASSUMED]: the scanning reading of the PRTS sentence (the integrator's reading of the report; PRTS gives no example). Kept [ASSUMED] from §19.1: the shared-field interleave.
- Measured — every test that moved (the full sim, content, match, ui, render and root folders were run; details in the workstream notes):
test/sim/battle.test.js(the rule itself: new expected order, two cases added to tell column-first from row-first, also on the mirrored boss side),test/sim/playtest5-targeting.test.js#5 (the latest deployed is now the bottom of the rightmost column: new expected order and target),test/content/enemies_bosses.test.js烹泉 / 沏虹 and 碎骨 andtest/content/kits_t6.test.js余 S3 (their enemy's target was "the latest deployed", which moved to another unit; the layouts — or, for 余, a taunt on the operator behind the wall — now keep the same target, the assertions are unchanged),test/ui/playtest6-elite.test.js(mergeTarget: the left column wins over a higher row; a same-column case added),test/match/merge.test.js(the expected first deployed tile of the 突变细胞 SETTLE case is computed by column),test/docs-consistency.test.js(META's merge sentence). Bots, client-combat parity, 突袭, replays and every other match test pass unchanged. - docs: DESIGN §5.5, §19.1 (#5) and §20.11, SIM (start), META (board.js, garrison dispatch order, merges), PLAYING (作战, 晋升精锐, 仇恨), research 00 / 01 (§4.3, A1 rows 4.3 and 7, the promotion addendum) / 03 / 09 say it.
- Tests: as listed under Measured; plus the comment fixes in
test/sim/combat.test.js,test/match/hardening.test.js,test/match/playtest6-elite-to-board.test.js.
23.24 深溟巢涌者 do not attack; a revealed 节日爵士乐手 only hits what it blocks — content/enemies.js
- Seen: 深溟巢涌者 and 富营养的巢涌者 swung a normal attack. A 节日爵士乐手 that had been revealed and was not blocking still picked a target and attacked.
- Official: the nest-spawner pages walk and pulse an aura; they are not attackers. The jazz page's revealed unblocked form only attacks what it blocks.
- Now: both nest spawners have
noAttack. While they walk, a 1 s aura pulse hits allies in range, and the pulse pauses while the enemy is hidden. A revealed unblocked 节日爵士乐手 is melee, so it only swings at its blocker. - [ASSUMED]: the pulse radius and the 100 % 无途径 arts plus the 5 % 神经 on the enriched spawner follow the pages read for 0.1.3; the pause-while-hidden is the same hidden-enemy rule as the rest of the sim.
- Tests:
test/content/enemies_bosses.test.js,test/sim/feedback2-stealth-splash.test.js(the nest pulse still hits 迷彩).
23.25 A split or summoned child does not carry the kill bounty (GitHub #67, #89 item 2) — content/enemies.js spawnChildren, match/unite.js planUnite
- Seen: a child spawned when an enemy split or summoned paid the parent's kill bounty again. In 联防 a leaked child could also pay the card bounty of a different enemy.
- Now:
spawnChildrendropsbountyIdandbountyCoinson the child and leaves them on the parent. Killing the body pays once; killing a child pays nothing.planUniteattaches a card bounty only when the leaked enemy's key is that card's enemy key. - [ASSUMED]: none. The parent's bounty is unchanged.
- Tests:
test/sim/feedback3-bounty-children.test.js.
23.26 A 隐匿 blocker is hit only by the blocked enemy's attacks — not its areas, zones or auras (GitHub #97) — sim/targeting.js areaSelectable / auraSelectable, content/enemies.js areaAllies / auraAllies, content/bosses.js
- Seen: GitHub #97. A 隐匿 operator that is blocking an enemy was still selected by that enemy's splash, blast, zone and buff aura. 0.1.2 (
targeting.js, DESIGN §22.12) had made that the rule on purpose. - Official: PRTS 作战机制 §隐匿 "隐匿效果使得获得该效果的单位无法被任何敌方的能力索敌选中", and "因为“阻挡优先级最高”的效果,敌人会无视一切可选性对该干员进行攻击" — the attack, not the area. §AOE伤害判定 "AOE的判定是对攻击范围内的每个可以被选中的敌人进行判定". §隐匿与Buff的关系 "隐匿状态下的单位一般无法被敌方的索敌机制和Buff选择器选中为目标". PRTS 异常效果 迷彩: splash, 中点判定 / 格子判定 and auras are not stopped by 迷彩 (ba.camou "无法躲避溅射类攻击"). Direct attacks still land.
- Cause:
auraSelectablereturned true for a 隐匿 ally whensrc.alive && src.blockedBy === a.areaSelectabledelegates to it, so splash, blasts, zones and auras all kept the blocker. That reading of 阻挡优先级 for area abilities was marked [ASSUMED] in §22.12. - Now:
auraSelectablerejects every 隐匿 ally, including the one blockingsrc. Areas follow.canTargetAllyis unchanged: the blocked enemy's own attack still hits (ranged attacks from other enemies still do not). 迷彩 is still not checked. A deadsrc(a death blast; the body is unblocked before thedeathhook) never gets a blocker exception — there is no exception left to apply. Abilities marked 无视无法选择 / 无视目标可选性 still do not call this selector. Owner's decision 2026-10-04: the 0.1.2 [ASSUMED] is withdrawn. - Players notice: a 隐匿 blocker (伪装服, 叙拉古, 伊内丝 S2, an 排气格栅) no longer takes the enemy it blocks' self-blast, grenade splash, zone or aura. It still takes that enemy's attacks. A 迷彩 neighbour still takes the splash.
- Docs: SIM.md (the area-selector paragraph, the zone line, the
stealthrow). DESIGN §22.12 stays as the 0.1.2 text; this draft replaces the blocker exception. - Tests:
test/sim/feedback2-stealth-splash.test.js(9 — the selector skips a 隐匿 blocker for areas and auras and still takes 迷彩; “庞贝”'s blast misses its 隐匿 blocker and a 隐匿 bystander, hits a 迷彩 bystander, and its attacks still land on the blocker; a dead source does not select its former 隐匿 blocker). - Interaction (script, not a separate file): a 高能源石虫 death blast (
enemy_1021_bslime, radius 1.25) damaged the 迷彩 neighbour and not the 隐匿 blocker, including whenblockedBywas forced back onto the dead body; 深池伙友卫队's force field did not stick to the 隐匿 blocker and did stick to the 迷彩 neighbour. No new [ASSUMED].
23.27 A 冻结 from a second 寒冷 lasts the longer of the two colds, not a fixed 3 s — sim/Battle.js applyStatus, sim/constants.js COLD_FREEZE_DURATION
- Seen: stacking 寒冷 always froze the target for 3 s (
COLD_FREEZE_DURATION), whether the colds were 1 s or 10 s. A longer cold still on the target was cut short; a longer incoming cold did not lengthen the freeze. - Official: gamedata_const ba.cold "攻击速度下降30,如果在持续时间内再次受到寒冷效果则会变为冻结" (no length). PRTS 术语释义 寒冷 (id ba.cold): two kinds of cold that do not mix. 敌方 cold applied to a unit that already has 敌方 cold or 敌方 冻结 becomes 敌方 冻结. 友方 cold (operators and our summons, unless a page says otherwise) "始终需要两两一对产生友方冻结,持续时间取双方之中最高". Frozen-immune units never turn a cold into a freeze.
- Cause:
applyStatus, oncoldwhen the target already has cold and is not immune tofrozen, calledapplyStatus(target, 'freeze', { duration: COLD_FREEZE_DURATION })with that constant 3. The cold buff itself already keeps the longer time (addBuffrefresh'extend'→timeLeft = max(old.timeLeft, new duration)), and the incoming duration at that point is already after 抵抗. - Now: the freeze uses that same max(remaining cold
timeLeft, incoming duration).resistAppliedstops 抵抗 from halving it a second time. Immune-to-frozen still skips the freeze; the colds still take the longer duration.COLD_FREEZE_DURATION(3) remains only when neither side has a duration (a non-positive, non-infinite time). A shorter second cold does not shorten a longer remaining cold or its freeze. - [ASSUMED]: the engine has one catalogue cold, so an enemy-applied second cold uses the 友方 max as well. PRTS states 「持续时间取双方之中最高」 on the 友方 line only. The 敌方 trigger "already frozen, even with no cold left" is unchanged (still only when cold is still on).
- Players notice: 灵知's charged cast, 谢拉格's second gust, 不融冰 on an already-cold enemy freeze for the longer cold. A 1 s cold on top of a 10 s cold no longer freezes for 3 s, and does not wipe the remaining cold.
- Docs: SIM.md
coldrow. DESIGN §5's "freeze 3 s" sentence is the 0.1.2 text; this draft replaces it. - Tests:
test/sim/feedback3-cold-freeze.test.js(6 — 10 then 1 → freeze ~10 s; 1 then 8 → ~8 s; a 1 s cold after 6 s of a 10 s cold freezes for the ~4 s left; 抵抗 0.5 turns an incoming 10 s into 5 s before the max, and that 5 s is not halved again; frozen-immune gets no freeze; 吉兆飞鳞),test/sim/combat.test.js(the old "freeze lasts 3 s" case on two 5 s colds now expects 5 s). - Interaction: 吉兆飞鳞 【失温坠落】 still fires. Cold 1 s then 1 s: the 0.05 s stun lands when that ~1 s freeze ends, not at 3 s. Cold 10 s then 1 s: no stun at 3.2 s; the 0.05 s stun lands when the 10 s freeze ends. That 0.05 s stun still drops it into the existing 8 s grounded mode.
23.28 「地面干员」 is the melee position, not the tile — 不屈, 战栗维式重锤, 休谟斯 回收利用 (community report 「不屈盟约效果高台干员也错误的吃到了」) — content/support/index.js isGroundOp, content/bonds/addon/battle.js (不屈), content/items/battle.js (战栗), content/bands/battle.js (act1autochess_band18_buff)
- Seen: a community report: the 不屈 bond's effect reaches 高台干员 — a ranged operator standing on a melee tile came back at once when knocked out. Measured (act2 m01, 不屈 tier 2 at 210 layers, p = 1): 普罗旺斯 (狙击) and 阿罗玛 (术师) on the melee tile (9,4) were back at once and gave every operator +5 SP; 歌蕾蒂娅 (钩索师, melee) on the 高台 (10,4) stayed down.
- Official: 地面干员 / 高台干员 name the deploy class (position MELEE / RANGED), on any tile. Game data: this mode's map card 模拟战场演变·模式五 「场地右侧的射击台变为可以部署高台干员和地面干员的地面」 (a tile both classes may use), the 山脊 device 「可将高台干员部署于其上」, a module 「所有地面干员阻挡数+2」 (only the melee class blocks), 「地面位干员阻挡数之和」; training CH-2 「即使在地面作战时,高台干员也…」 (a ranged operator on a ground tile is still a 高台干员); PRTS corrected 琴柳's 地面干员 to 地面位干员. The three effects worded 地面干员 in this mode: 不屈 (both lines: 「地面干员被击倒时,有…概率立刻重新部署」 and 「<在场3名不同【不屈】干员>地面干员被击倒时使场上所有干员技力+5」), 战栗维式重锤 (「若携带者是地面干员则攻击时有10%概率使目标战栗2秒」), 休谟斯 回收利用 (「地面干员技能结束时使周围四格随机1名干员获得3点技力」).
- Cause:
isGroundOp(support/index.js) readunit.ground, the engine's tile flag set at deployment (low tile, not elevated) — §22.6 marked that reading [ASSUMED]. - Now:
isGroundOp(u)= an operator whosedef.positionis MELEE, whatever tile it stands on. Its only callers are the three effects above, so all of them follow: 歌蕾蒂娅 on a 高台 is a 地面干员 (不屈 brings her back, the 战栗 proc and 回收利用 work), a ranged operator on a melee tile (this mode lets any ranged operator stand there: PRTS 帮助 「所有行动内远程干员可部署在近战位」) never is.unit.groundkeeps every tile reading: blocking, the enemies' 地面单位 targeting (the 只攻击地面单位 enemy, ground-only boss picks, 污染秽蚀's 高地 rate), 赫默's module 「治疗地面单位」 and 隐德来希's 血镰 「1名其他地面单位」 (worded 地面单位, left on the tile), 焰影苇草 S2 「部署在地面的干员」 and the 推击手 远程位 refund (tile wording). Other 地面干员 texts already read the position: 新约能天使 S3 「将一名再部署时间最长的地面干员部署至该处」 (def.position !== 'RANGED'); 陈's 以己之长 says 所有干员 since PRTS's 70→100-layer patch (it once said 高台干员). - Players notice: a 狙击 / 术师 / 医疗 / 辅助 knocked out on a melee tile no longer comes back through 不屈 nor gives its +5 SP; 歌蕾蒂娅 on a 高台 does both, and her 战栗维式重锤 procs there.
- Docs: §22.6's last bullet (the tile reading) now points here; research 02 §3.19 (不屈: melee position, any tile) in .md and .json (→ data/bonds.json
indomShip.spec, rebuilt offline; the only data change); code comments (support, 不屈, 战栗, 回收利用, 蒂比's 起飞 in kits/tier2.js). - Tests:
test/content/feedback3-ground-ops.test.js(real chess on act2 m01: 歌蕾蒂娅 on the 高台 back at once with +5 SP, 普罗旺斯 on the melee tile stays down with no SP, 歌蕾蒂娅 on the ground as before; 战栗 from a melee carrier on a 高台, none from a ranged carrier on a melee tile; 回收利用 likewise — all three fail on the old tile reading),test/content/bonds_addon.test.js(不屈: the old "elevated → no" case now redeploys; a ranged operator on a melee tile does not, no tier-2 SP).
23.29 叙拉古 6 rolls on every 普通伤害 hit, not only on normal attacks (community report 「叙拉古盟约真伤概率数值没有正确递增成长」, GitHub #79) — content/bonds/core.js siracusaRolls / installSiracusa, kits/tier6.js whitw2 (S3 pulse tag), kits/tier2.js (拉普兰德 module), kits/tier3.js (忍冬 追凶)
- Seen: a community report: 叙拉古's true-damage chance does not grow as it should; GitHub #79 (closed, still true): 荒芜拉普兰德 S3 「咬了一整个技能也没出」. Measured on 0.1.3 before the fix: 966 of 2324 member hits never rolled (619 S3 pulses, 255 S3 drone attacks, 92 德克萨斯 剑雨 hits); a whole S3 rolled nothing, since she makes no attack of her own then.
- Official: PRTS 盟约记录 叙拉古: 「※仅在造成普通伤害时尝试造成来源为干员自身的真实附加伤害和恐惧」, 「每次造成普通伤害时尝试触发,基础概率0.139%…至多720次」 (the PRD rule, a counter shared by the members, unchanged). PRTS 伤害分类: 普通伤害 is the attack type NORMAL — 「站内省略伤害的攻击类型时,默认为此伤害类型」, and group damage stays 普通伤害 unless it is 溅射 (SPLASH); the other types are 持续伤害 (BUFF, 「(不依赖攻击行为的)每秒造成……」) and 附加伤害 (ADDITION). Per member: 荒芜拉普兰德 S3 备注 「该技能释放的浮游单元造成的伤害不属于普通攻击/技能直接伤害」 (no attack, no skill hit — still 普通伤害) and 「持续法术伤害…不可叠加」 for the per-second area hit; 缄默德克萨斯 S1 「伤害分类为法术持续伤害」; 拉普兰德's module 「造成预计算的法术附加伤害」; 忍冬 追凶 「伤害类型为法术附加伤害」; 德克萨斯 剑雨, 阿罗玛 S1 / S2, 忍冬 S1 / S2, 伺夜 S3 and 缄默德克萨斯 S3 carry no class note (→ 普通伤害).
- Cause:
installSiracusarolled only ondmg.isAttack && !dmg.isSplash— the main target of a normal attack. - Now:
siracusaRolls(dmg)— every damage instance a member deals to an enemy rolls unless it is 溅射 (isSplash), 持续 (tagsdot/periodic), 附加 (tagaddition), an item proc (tagitem), a bond's own damage (tagsbond:*: this proc, 卡西米尔's rider), element damage, 无来源 (sourceless: 坚守's thorns) or a 流失. So 荒芜拉普兰德's S3 drone attacks, 德克萨斯 剑雨 (both hits on every enemy), 缄默德克萨斯's deploy bursts and sword rain, 阿罗玛's and 忍冬's skill hits and 伺夜 S3's bonus roll too. Kits: the S3 area hit gets the tagdot(it is 持续伤害, so it does not roll — and 锡人's 凋敝魂灵, 「受到的持续伤害提高」, now raises it, as PRTS's wording implies); 拉普兰德's module rider and 忍冬's 追凶 getaddition. The 隐匿 window, the PRD counter (shared, 0.139 % steps, sure by 720), 5000 + 50 × L and the 3 s fear are unchanged. - [ASSUMED]: item procs (天马之枪, 铳骑之威, 海沟实验体, 耶拉冈德之泪, 家族徽章's strike) and the bonds' riders do not roll — PRTS gives them no class; we read them as 附加. The S3 area hit is not 普通伤害 by PRTS's own wording 「持续法术伤害」 (the 伤害分类 list of BUFF damage does not name it, but it says 「包括但不限于」). Other operators' 附加 / 持续 damage is only classified where a 叙拉古 member deals it (a 调和 operator at 叙拉古 6 rolls on everything not tagged).
- Players notice: with 6 叙拉古, procs come on skills too: 荒芜拉普兰德 S3 procs on her drone bites (one roll per bite; about 1 proc per 33 rolls on average), 德克萨斯 剑雨 can proc on each of its hits. A fight's proc count rises by the share of hits that used to be skipped.
- Open: PRTS 「※脱离隐匿10秒内若自身的异常效果/异常效果免疫发生变动将刷新加成倒计时」 (the 10 s window refreshes) is still not modelled. 卡西米尔 6's rider (「仅在造成普通伤害时附带此真实伤害」) has the same old filter (
isAttack) — a separate fix. - Docs: §22.9 (the drone damage now rolls; the area hit's
dot), research 02 §3.7 in .md and .json (→ data/bonds.jsonsiracusaShip.spec, rebuilt offline; the only data change), code comments (core.js header, whitw2). - Tests:
test/content/feedback3-siracusa-rolls.test.js(rng forced to 0 so every roll hits: the classification case by case — attack, skill hit, drone attack, unclassed talent hit roll; 溅射, 持续, 附加, item proc, another bond's rider, element, 无来源 do not; 荒芜拉普兰德 S3 over 10 s: procs = drone attacks, pulses taggeddotand never rolled; 德克萨斯 剑雨: 4 hits on 2 enemies → 4 procs — all three fail on the old filter);bonds_core.test.js叙拉古 tests unchanged and passing.
23.30 Mode-off bonds the player has members of show as grey 本局禁用 discs; the 成员 header counts the hand (community reports 「投资人等在休整区就能生效的盟约不生效」 / 「…不会触发斯卡蒂与异德的突袭」) — match/bondsMeta.js offBondCounts / bondList, PlayerState.privateView, Match public view, ui/bondStrip.js, ui/gameLogic.js memberHeadCount
- Seen: in 标准模拟 the bond strip hid 投资人, 奇迹, 突袭, 独行 and the other bonds the mode switches off, even when the player had their members. A 投资人 count of 3 (the members sitting in the 整备区) read 在场 3/3(含整备区) in the popup while the 成员 header said 0/n, because that header counted only operators on the board.
- Official: activity_table
modeDataDict.mode_single_funny(标准模拟, and the same list onmode_multi_funny)inactiveBondIdList: 拉特兰 阿戈尔 卡西米尔 灵巧 奥术 奇迹 投资人 突袭 独行 绝技. Those bonds never activate. 投资人 / 远见 / 奇迹 countBOARD_AND_DECK(the board and the hand, not the 5 temporary slots); 突袭 counts the board. The grey disc is this remake's mark for a bond the mode never activates (the briefing already drew one with ✕); the official mode simply leaves the bond off. - Cause:
computeBondsskipsmodeInactiveBonds, and the views listed only whatcomputeBondsreturned, so a bond with members and no effect was invisible.BondPopup's 成员 numerator wasmembers.filter(onBoard). - Now:
computeBondsstill omits them — the battle input (bondSnapshotofps.bonds) never sees them, so they still cannot activate.offBondCountscounts the inactive bonds the player has members of the same way (rawCount: 投资人 / 奇迹 include the hand) andbondList(..., { off })appends{ bondId, count, active: false, tier: 0, layers, off: true }after the live bonds, onm.privateandm.public. The strip draws that entry last as a grey disc with ✕ and 本局禁用 under it (no stack count). An inactive bond with no members is left out. A mode whoseinactiveBondIdsis empty (offBondCounts→ null; 险境 and the rest) is unchanged. The 成员 header usesmemberHeadCount: for a bond withcountsHand(投资人 远见 奇迹) a row in the hand counts, a row only in the temporary slots does not, and one operator on the board and in the hand counts once. - [ASSUMED]: 调和's +1 is not added to a 本局禁用 bond's count. The bond never activates, so the +1 would only inflate a count that does nothing; the tooltip's 「N 名成员」 is the raw member count.
- Players notice: in 标准模拟, holding a 投资人 (even only in the 整备区) or fielding 斯卡蒂 / 异德 shows that bond at the end of the strip, grey, marked 本局禁用, instead of it vanishing. Opening it, the 成员 number matches 在场, hand included. The fight is unchanged: those bonds still do not fire.
- Docs: DESIGN §8.3 (the
offentries come last; the pinned bonds-array sentence is unchanged), DESIGN §20.15 (m.public passesoffBondCounts), META.md (the same list), PLAYING.md (本局禁用 on the strip, and the 成员 header counts the 整备区). - Tests:
test/match/feedback3-off-bonds.test.js(标准: 投资人 in hand and 突袭 on the board areoffentries and absent fromcomputeBonds/bondSnapshot; a hand member counts for 投资人, not for 突袭, not from the temporary slots, and not twice; a mode with an empty inactive list returns null and those bonds stay incomputeBonds),test/ui/gameLogic.test.js(memberHeadCount,bondMembersinHand,sortBondsputsofflast),test/ui/feedback1b-bonds.test.js(three 奥术 on the board: still absent fromps.bonds, present onm.privateasoff: true, the chip still says 本局禁用).
23.31 An item gained as 休整期结束 waits in hand and merges at the next prep start (维多利亚's 战栗维式重锤; owner's decision 2026-10-04) — match/PlayerState.js acquireItem, match/effectsMeta.js (_deferItemMerge), match/Match.js enterPrep, content/bonds/core.js payHammers
- Seen: 维多利亚 pays a 战栗维式重锤 when a 25-layer milestone is crossed, including at 休整期结束. A player who already had one equipped went into the fight without it: the new copy had merged at once, the equipped copy was detached, and the golden hammer sat in the hand through the combat.
- Official: PRTS 卫戍协议/帮助 (the in-game tip, also
autoChessData.gameTipsList): 「两件同名装备可以合成一件更强力的装备」 and 「已拥有2件同一初始装备时…自动合并」. 维多利亚 (bonds.jsonlayer25,count1, poolpool_equip_vict): 「每叠加25层获得1件带有随机效果的维式重锤」. Nothing there says the merge has to finish in the same moment the prep ends, or that an equipped copy comes off for a fight that has not started. The item ischess_item_2_03_e_a战栗维式重锤, goldenchess_item_2_03_e_b,upgradeNum2. - Cause:
payHammersruns ononPrepEnd(HAMMER_HOOKS).grantItem→acquireItemcallscompletesItemMerge/_mergeItemimmediately, and_mergeItemdetaches an equipped copy of the same item.Match.endPrepdispatchesonPrepEndand only then starts the fight, so the detach landed before combat. - Now: while
onPrepEndis on the dispatch stack (the hook itself, and a grant nested under it — the next owed milestone is paid from the grant'sonGain),acquireItemstows the item (hand, else temp, the existing stow rules) and does not merge, even if an identical normal copy is already in the hand, the temp slots or equipped.enterPreprunscheckItemMergesonce beforeonPrepStart, so the pair merges before the player acts in the next prep, not inendPrepof the prep that granted it. A buy, anonPrepStartgrant, anonLayersgrant during combat and every other path still merge at once. Hand and temp both full keeps 「整备区已满,获得的装备已销毁」: the new item is destroyed and the equipped copy is not replaced by a golden one in its slot. At the next prep there is then nothing waiting to merge, so the equipped normal hammer is still there. - [ASSUMED]: the deferral is every item granted during that
onPrepEnddispatch, not only 维多利亚's hammers. The owner's sentence is 「an item that arrives at 休整期结束」. Two milestones caught up at that moment both wait: the nestedonGainpayment is inside the same stack, so it does not merge the first copy early. - Players notice: the hammer already on an operator stays on for the fight that is about to start; the new one sits in the 整备区 (or the temporary slots, if the hand was full). The golden hammer appears when the next prep starts. If the hand and the temporary slots were both full, the new hammer is destroyed and the equipped one is untouched, including at the next prep. Two hammers paid at the same prep end both wait; the next prep merges those two with each other and leaves the equipped one on the operator.
- Docs: META.md §2.5 (the auto-merge sentence now names this exception), PLAYING.md (同名装备: 休整期结束 waits, other gains still merge at once).
- Tests:
test/match/feedback3-prep-end-item.test.js(one equipped 战栗维式重锤, 维多利亚 pays another at prep end: still equipped in the battle input, second copy in hand, no golden; the next prep has the golden and no normal. Two milestones: three normals through the fight, then one golden plus the equipped copy. Hand and temp full: two 「整备区已满,获得的装备已销毁」, equipped copy unchanged after the next prep. A buy during prep merges at once.onLayersduring combat merges at once).test/content/bonds_core.test.js(the catch-up payment ononPrepStartstill merges) andtest/match/feedback1b-items.test.js(every other gain path) still pass.
23.32 乌尔比安 S3 【移动】 and 【返回】 are free redeploys (community report 「乌尔比安三技能结束后不属于再部署」) — Battle.moveRedeploy, kits/tier5.js, tokens.js 从不混淆的方向, devices.js, render/interp.js, render/spine.js
- Seen: his S3 only changed his tile (
Battle.relocate). Deploy effects never saw the 【移动】 or the 【返回】 — 迅捷作战粮, 突袭手雷, 卡西米尔 / 叙拉古 / a garrison<部署时>— and the return did not empty his SP. A knock-out, 不屈 and 阿戈尔's revive must not see either move. - Official: PRTS 乌尔比安 S3 备注 — both steps are 【移动】; "【移动】后仅继承下列效果:技能进度、第二天赋叠加层数"; the marker is the 【返回】 flag; "【返回】时将清空技力,但仍可以享受后续由其他效果提供的技力". PRTS 术语释义 【移动】 (ba.addash): "不退场,以当前血量在目标位置部署", "本质上为一次特殊的撤退-再部署行为", "因移动撤退时不会积累再部署惩罚", "可以通过移动行为多次触发部署时触发的效果", "部署时将清空技力". His skill is the special case: the outbound move keeps 技能进度, and only the 【返回】 names the clear. While he blocks, the anchor stays on his own tile (§22.3, unchanged): no marker, no new deployment.
- Cause:
relocatekeeps the samedeploySeq/deployedAtand emits nothing. - Now:
moveRedeploydoesrelocate's checks, then a new deployment (deploySeq/aggroSeq/deployedAt) anddeploy { initial:false, move:true }. Nodie/death, so no knock-out, no redeploy timer, no cost.clearSpempties SP before the deploy handlers (the 【返回】, and the marker's fallback). The outbound move does not. HP ratio and buffs stay, including the 阿戈尔 devour gains (owner's deviation, DESIGN §22.3 — officially only 技能进度 and the second talent's stacks carry). A 【移动】 does not zero terrain memory (ctx.move), so the old tile's 气流 / 烟尘 is not stranded. The client snaps a unit whose deploy animation starts between snapshots, and a skill that ends on that deploy lets the Start clip finish. - [ASSUMED]: a refused tile changes nothing (no 强制撤退, and a 【返回】 that cannot land does not 击倒 him). His own S3 never takes that branch — the note says the check can always fall back to his tile — so only a direct
moveRedeployonto an illegal tile hits it. The glossary's "部署完成后立刻流失一次生命值以匹配移动前的生命比例" and the return's 0.333 s 无敌 / 不可阻挡 are not separate events: the HP ratio is kept on the same body. The return runs inonEnd, while the skill's mods are still applied ("技能结束后" would be the other reading); nothing that reaches him on that deploy reads those stats. 大帝 加急调派's permanent redeploy stack grows once per deployment, so a move adds one stack ("每次部署后"); that was already [ASSUMED] cumulative. - Players notice: after the skill he is back on his tile with empty SP plus whatever a deploy effect just gave (迅捷作战粮), the 突袭手雷 window counting from the landing, and his devour / 血脉 stacks still on him. 不屈 and an 阿戈尔 revive are not spent. A 傀儡师's 替身 standing nearby is not knocked out.
- Docs:
docs/SIM.md(deployctxmove,moveRedeploy, the client'sdeployevent). Not DESIGN §23. - Tests:
test/sim/feedback3-ulpia-move.test.js(both moves bumpdeploySeqand emitdeploy { move:true }; no death / die / FORCED_EXIT; knock-out count stays 0; SP cleared on the return only, then the ration's gift; devour buffs stay; 突袭手雷 re-arms; blocking still does not move; illegal tiles change nothing; out and back leaves 风丸's 替身 up and does not spend 不屈 or the 阿戈尔 charge; 卡西米尔 +20 % once per move, 叙拉古 refreshes without a second stack, 大帝 one stack per deployment).test/render/feedback3-move-deploy.test.js(headless, no browser).test/docs-consistency.test.js.
23.33 波登可's spore cloud radius is 0.9 (PRTS 备注) — kits/tier1.js SPORE_RADIUS
- Seen: the cloud used radius 1.2, commented as "no blackboard key; ≈ the 3×3". An enemy on the next tile (centre distance 1) was inside it.
- Official: PRTS 波登可 技能2 孢子扩散 备注 "※孢子群范围半径为0.9,可对空". No blackboard key carries the radius.
- Cause: the constant was a guess at the 3×3 around the vial.
- Now:
SPORE_RADIUS = 0.9. The zone fx and the pulse both use it. Air units stay in range (可对空;foesInRadiusdoes not drop them). An enemy whose centre is 1 tile from the vial is outside. - [ASSUMED]: none. The centre is still the current target's position (the skill text "在周围", no landing note).
- Players notice: the cloud no longer reaches the tiles beside the enemy it landed on. Damage, 停顿 and silence on the enemy under the vial are unchanged.
- Docs: the kit comment cites the PRTS note. No SIM / DESIGN sentence named 1.2 for this cloud (the 1.2s elsewhere are 孽罪奇美拉,
batPctand 沸血燎原). - Tests:
test/content/kits_t1t2.test.js「1_13 波登可」 — the zone event's radius is 0.9; one pulse per second and the silence / 停顿 only on the enemy the vial landed on; the enemy one tile away and the far one take none.
23.34 琳琅诗怀雅 S3 shoots her coins when the purse reaches 10 (owner 2026-10-04) — kits/tier3.js 千金一掷
- Seen: with the purse full and a ground enemy in the coin range,
onTickcalledskill.end('manual'). GitHub #89-1 reported that auto-close. Removing it left no way to shoot the coins, because a fight never calls a manual close. - Official: the skill text 「携带此技能时金币上限为10」 and 「主动关闭技能时消耗所有金币」. PRTS 帮助 「通常不会自动关闭技能」 is the general line #89-1 cited.
- Owner 2026-10-04: the written cap is the close. At 10 the coins are shot. No separate 关闭技能 button.
- Now: while S3 is active and
coins >=the S3 blackboardsp(10),onTickends it withmanualonce a ground enemy is in the skill attack range (the record's range, one tile ahead — the same range as her attack).onEndis unchanged: that reason spends every coin on the ground enemies of range 2-4 and the ones she blocks, then pushes. Below the cap, or with nobody in the attack range, it stays open and the coins stay.skill.stop()(reason: 'stopped') still does not spend. - Owner 2026-10-04 (second call): do not shoot into an empty attack range. An enemy only in the coin-mark 2-4 grid does not trigger it.
- Players notice: she double-attacks and collects coins until the tenth, then waits until an enemy walks into the tile in front of her. That is when the burst fires.
- Docs: the kit comment. No pinned SIM / DESIGN sentence described the auto-close.
- Tests:
test/sim/feedback3-swire-s3.test.js(9 coins and an enemy, 6 s, still open; 10 coins, the enemy is hit and the purse is empty; 10 coins and no enemy, ends, spends, no hit, no error; a manual close at 4 still spends).test/sim/feedback1d-push.test.js「琳琅诗怀雅 S3」 (the radial push, one coin).
23.35 高台 only for elite 歌蕾蒂娅 carrying HOK-Y (owner's decision 2026-10-04) — withdrawn in 0.1.4, see §24.2 — shared/highGround.js, match/board.js placeClass, PlayerState._placementOf, ui/gameLogic.js piecePosition, match/bot.js planLayoutSteps, tools/build-data.mjs
- Seen: 0.1.2 §22.6 set
chess.json placement: 'all'on every melee record whose branch trait contains 「可以放置于远程位」 — 歌蕾蒂娅, 崖心 and 见行者, normal and elite — sopositionClasslet all of them stand on a 高台. The owner (2026-10-04) says that reading is wrong. - Official: character_table still writes that line on the 钩索师 (歌蕾蒂娅
char_474_glady, 崖心char_173_slchan) and the 推击手 (见行者char_4036_forcer). PRTS 新人入门 says both branches deploy on 高台 and ground. PRTS 卫戍协议/帮助 §战斗部署 switches off 部署效果 ("携带Y模组的教官仍无法部署至高台位"). No table row names HOK-Y as the prep exception. The module id is the data:chess_char_4_12_b.moduleslistsuniequip_003_gladytypeNameHOK-Yname 淡金坠饰 (not the default; the default isuniequip_002_gladyHOK-X 执政官手镜). 崖心's only module is HOK-Xuniequip_002_slchan. The golden record is the elite (chess_char_4_12_b,isGolden); there is no further copy. - Cause: §22.6 trusted the trait line. It is copied onto every variant, including ones the owner refuses.
- Now: build-data does not emit
placement. At place time,meleeOnHighGround(rec, moduleId)is true only when the record is golden,charIdischar_474_glady, and the equipped module (the player's loadout, defaults included) isuniequip_003_glady. That widensplaceClass/ the client'spiecePositionto every deployable tile. A normal record, 崖心, 见行者, HOK-X,none, and a missing module stay ground-only. The same check is whatg.move, swaps,_evictIllegal, the merge-tile legality, the audit and the invariants use. The client lights the same tiles. The bot plans withplaceClass. Owner 2026-10-04: when a 高台 tile's range covers an enemy-road tile, HOK-Y 歌蕾蒂娅 is planned there even if the ground is free; otherwise she stays on the ground. Everyone else may not use a 高台. She is still a melee blocker in the lineup (basePositionClass). The battle is unchanged: position stays MELEE, so on a 高台 she attacks and blocks nothing. The loadout is locked after 干员调配, so the module does not change under a piece already on a tile. - [ASSUMED]: the sim's automatic searches (突袭 landing, summon / device tiles,
grid.canStandwithoutranged) still treat every MELEE unit, including HOK-Y 歌蕾蒂娅, as ground-only. A prep placement on a 高台 is the board tile the battle deploys and redeploys on; those searches do not hunt a 高台 for her. There is no ground-only tile that refuses a ranged operator (ranged chess may use melee tiles); a non-deployable tile still refuses them. - Tests:
test/match/board.test.js(the class table),test/match/feedback1-placement.test.js(the operator × module × tile table, client highlights, the swap, the battle input on (10,4), the bot plan),test/ui/gameLogic.test.js(the drag mirror).node tools/build-data.mjs --offlinedropsplacementand nothing else indata/. - docs: DESIGN §3, DATA (
placement), PLAYING §4, META (board.js), research 03 C2. §22.6 itself is left for the integrator to replace with this section.
23.36 Watching another board during prep follows its moves (GitHub #87) — Match.watch, Match._notifyPrepScouts, ui/screens/game.js
- Seen: GitHub #87 — in the deploy phase, the board you opened with 前往查看 does not follow the other player's moves. Leaving and entering again shows the new layout.
- Official: research 09 §3.1 — a teammate's board in prep is read-only, including their pen. PRTS was not re-fetched for this report; the live update is what the report asks of the client, and the owner filed it as a bug.
- Cause:
g.watch 'n:<pid>'while no battle field is up deleted the viewer fromwatchersand sent oneprepFieldMeta(m.field,prep: true). Laterg.moveonly refreshedm.privatefor the player who moved. The client places that onem.fieldand had nothing further to enter. - Now: the scout stays in
watchersundern:<pid>. When that player's board changes (a move, a facing, equipment on a deployed piece),flushsendsprepFieldMetaagain to everyone scouting it, including a spectator seat. A shop or funds change does not. Opening the watch still sends the current board at once. Combat start still clearswatchersand assigns live fields, so a spectator's battle watch is the same path as before. A prepm.fieldwith no units still places an empty board, so a withdrawn lineup does not leave the old pieces up. [ASSUMED] the signature is the deployed pieces only (uid, id, tile, facing, equipped items); a hand-only change is not pushed, which matches a board view. - Tests:
test/match/prep-watch.test.js(seat B moves in prep, seat A'sm.fieldand a spectator's gain the piece before combat; freezing the shop does not).test/match/spectator.test.jskept.
23.37 折射 reads as off while the enemy is silenced — ui/abilityLines.js, ui/detailPanel.js EnemyDetail, render/style.js statusIconKey, render/units.js, shared/protocol.js unitStatsEntry
- Seen: silencing a 深池 enemy turns the 折射 RES bonus off (the sim already removes
ab:refraction). The enemy card still prints the 折射 line in the active colour, and the status icon does not show that the ability is off. - Official: the handbook line is
<$ba.refraction>折射</>withformat: "SILENCE"(data/enemies.json, 深池侦察兵enemy_1166_dusbrand the other 折射 enemies). PRTS 异常效果 沉默: the unit cannot use abilities flagged that way. The RES rule is unchanged. - Cause:
EnemyDetailalways drewabilities[].statusIconKeyhad no entry forab:refraction, and nothing looked at silence when choosing icons. - Now:
unitStatsEntrycarriessilencedfrom the aggregated flags. A SILENCE-format line (折射, and any other line with that format) getsis-off— grey text, the term underline dropped — whilelive.silencedis set. The 折射 buff maps to a status icon andstatusIconSuppressedleaves it out of the row while asilencestatus is up. The sim's RES rule is untouched. A card with no live stats (the prep preview, a handbook line) stays fully lit: nobody is silenced yet. - [ASSUMED]: every
format: "SILENCE"line greys the same way, not only the word 折射. The icon is dropped rather than drawn grey. - Tests:
test/ui/refraction-view.test.js(the line and the icon rule; no browser).test/match/unitStats.test.js(the newsilencedfield).
23.38 A granted chess is named, and the new card flashes — effectsMeta.js grantChess, render/app.js setPrep
- Seen: 歌蕾蒂娅's row trait grants 斯卡蒂 / 幽灵鲨 / 深巡 with no notice. 余's SERVER_MOST_BOND is the same. The card appears in the hand with no flash.
- Official: garrison text 「获得1个斯卡蒂、幽灵鲨或深巡」 / 「随机获得1名当前人数最多盟约的干员」. No official toast wording was fetched; the line is the owner's.
- Cause:
grantChessacquired the piece and did not toast.setPrepflashedfx.deployonly for a new board piece. - Now: a successful
grantChesstoasts 「名字:获得X」 — the garrison's operator (歌蕾蒂娅, 余), the item's name, the band's effect name, or the choice card's name.opts.toast === falseskips it, so a caller that already says the same thing is not doubled (nothing in the tree did). A full hand still only gets the existing 整备区已满 warning. A new hand or temp card, after the first prep layout, uses the existingfx.deployflash — the same one a new board piece already gets. Not a new animation. - [ASSUMED]: the toast names the piece actually owned, so a grant that immediately promotes names the elite. A shop buy is not a
grantChessand does not toast; a bought card is still a new hand uid, so it gets the same flash. - Tests:
test/content/garrisons_meta.test.js(歌蕾蒂娅 and 余, the toast text equals a chess now in hand).
23.39 An emote manifest that times out shows the glyph — data.js local / assets, ui/emotes.js EmoteArt
- Seen (GitHub #99): the 交流 panel's cells are empty, including the stand-in icon. The screenshot is the size of the emote plate.
- Official: none. The 36 pictures and the glyph are already the §22.5 art rule. The timeout length is not from a table.
- Cause:
EmoteArtdraws an empty box whilelocalorassetsisidleorloading, and the glyph only once both have settled toreadyormissing.createDataStorehad no clock, so a fetch that never returned left every cell blank for the session. This is the data-store fetch (useData('local','assets')), not the separate image cache inassets.js. - Now: one attempt at
localorassetsis abandoned afterART_MANIFEST_TIMEOUT_MS. A timeout or any other failure sets that filemissingand notifies immediately, which is the existing glyph path. The status staysmissingthrough the retry wait (going back toloadingwould blank the button again). A later attempt that succeeds notifiesreadyand the picture replaces the glyph. A 404 or bad JSON is still not retried. Chess and the other text files are not on this clock and still stayloadingacross retries. - [ASSUMED]: 8 seconds. A response that arrives after that attempt's clock is ignored; the retry is a new request, and the in-flight fetch is not aborted. After the two existing retries the glyph stays until the next
invalidateor page load. - Tests:
test/ui/emotes.test.js(stub timer, no network: hunglocalismissingbefore the retry wait, thenready; a thrown fetch is the same; a timeout with no retry and a 404 staymissing;chessarms no timer).test/ui/playtest3.test.jsdata-store retry andtest/client-static.test.jsdata.js still pass. No browser render:EmoteArtalready picks the glyph frommissing.