PRTS 卫戍协议/帮助 §装备: 「达到上限强行佩戴会改为替换装备:指定一件已佩戴装备替换为将要佩戴的装备,并销毁被指定的装备」,
no exception for the 装备时销毁 items. Before: a normal 博士投影 opened no dialog and its kept item pushed the OLDEST
equipped item out (replaceUid ignored); 盟约之币 and the other consumables kept both items.
- server PlayerState.equip (player/items.js): on a full carrier the consume path takes the pick (replaceUid, else the
oldest) off before onEquip (_takeReplaced), destroys it (onDestroy reason replace) once the effect went through —
the normal 博士投影 takes the freed slot —, and puts it back at its index when the effect refuses (博士投影 on an elite:
nothing destroyed)
- client gameLogic equipReplaces: the replace dialog opens for every item on a full carrier, consumables included,
except an attaching item that completes an item merge and a 博士投影 on an elite; itemAttaches removed
- bots (itemTarget): a consumable whose effect is the same on anyone goes to a carrier with a free slot first (PR #264)
- docs: design/match.md equipment rules, META §2.5, PLAYING 装备 row, research 04
golden (matches only; each traced equip by equip against the parent — the first change of state is a bot's, or the
AI 托管 human seat's, targeted consumable on a full carrier destroying its oldest item): solo-FUNNY-1 (r9 ai_0 随身身份牌),
solo-FUNNY-2 (r9 ai_0 寻呼模块), solo-HARD-2 (r9 ai_0 随身身份牌, r12 简易通讯机), coop2-NORMAL-3 (r9 ai_1 随身身份牌),
coop3-ABYSS-5 (r11 ai_1 随身身份牌), coop4-FUNNY-6 (r4 ai_1 随身身份牌), solo-HARD-9-boosted (r14 ai_0 随身身份牌),
coop2-ABYSS-10-boosted (r13 ai_0 随身身份牌, r14 ai_1 简易通讯机), coop2-NORMAL-14-standins (r9 ai_0 随身身份牌; now won,
14 rounds), coop2-NORMAL-70-diy (r14 h_0 随身身份牌); the bots' free-slot carrier for target-agnostic consumables moves
no scenario by itself; roster / bonds / fields / standins / diy unchanged
content/enemies/archetypes.js stealth: an enemy spawned with the band's swap tag 'duck' (content/bands/meta.js
duckReplace) gets its 隐匿 back no sooner than DUCK_STEALTH_RESTORE (1 s, content/enemies/helpers.js) after a block ends
— in practice 流泪小子 (enemy_2034_sythef_2), the only one of the four swaps with 隐匿, officially 0 s (PRTS 流泪小子
「隐匿(解除阻挡0秒后恢复)」). The same enemy anywhere else keeps 0 s: the bounty card's 流泪小子 (enemy_2034_sythef), an
untagged spawn; a 3 s 隐匿 stays 3 s; a copy leaked into 联防 keeps its tag and the 1 s. After each strike (its stunned
blocker lets go) it is now revealed through its 0.8 s post-strike stand and 0.2 s of walking.
Measured, 8 seeds each — leaked / HP left / revealed s per battle; W = 3 elite melee in a lane + an elite sniper vs the
R6 wave's ground enemies with one swapped 流泪小子, B = 8 elites on 战场#01 vs the whole R6 wave with one swap: before
the clip fix W 5/8, 42 %, 2.3 / B 8/8, 47 %, 3.2; after it W 5/8, 31 %, 2.6 / B 8/8, 58 %, 2.5; with the 1 s W 4/8,
31 %, 4.0 / B 8/8, 56 %, 5.5. Options for the owner (measured on the fixed engine, not built): 2 s W 4/8, 31 %, 5.4 /
B 8/8, 46 %, 8.5; 3 s (the general 隐匿 rule) W 4/8, 31 %, 6.4 / B 8/8, 39 %, 11.5; a 1.5 s stun on the copy (below its
2 s interval, so its blocker takes it over again) W 4/8, 2 %, 4.0 / B 4/8, 6 %, 5.5; a 1 s stun: knocked out in all 16;
the 1 s delay + a 2 s stun W 4/8, 21 %, 5.1 / B 5/8, 26 %, 8.1. Worse: half speed while 隐匿; a stunned operator that
keeps blocking it (pins that operator for good).
Golden: matches solo-HARD-2 — its bot holds 鸭爵; the swapped 流泪小子 of round 13 is free and revealed for 49 ticks in
its 1 s windows (the round-12 one is never released by a block), so the match parts at round 13 (a victory instead of
a defeat); the other 282 scenarios unchanged.
Tests: test/content/feedback5-duck-restealth.test.js (3; the swap's 1 s and the band path fail on the parent, the 0 s
guard passes on both); enemies_bosses, feedback2-surtr-stealth, feedback1-ducklord, feedback5-attack-clip-rest pass.
Docs: CHANGELOG 0.2.0 有意与官方不同的调整 (and the 核实后不是问题 line points to it), docs/history/0.2.0.md §25.22.6,
docs/PLAYING.md, docs/SIM.md, design/engine.md.
ai.js enemyAttack set the post-strike stand only for an unblocked ranged enemy (`!e.blockedBy && radius > 0`), so a
melee enemy released by its own strike walked on that very tick. 流泪小子 (enemy_2034_sythef_2, the strategy's version;
「泪眼汪汪」 / 「哭狗」 in the reports) stuns its blocker with every hit, the stunned blocker lets it go and its 隐匿 is
back 0 s later: on 0255afd it walked off the tick of each strike (four non-attacking walls in a lane: 8 stuns, 19.8 s
隐匿 / 9.0 s revealed, leaked at 28.9 s). PRTS 状态机: an enemy's ATTACK state 「每帧检查异常状态…攻击结束后回退到MOVE状态」
and COMBAT 「逻辑同ATTACK」 — only a character's COMBAT 「检测到不存在阻挡对象时强行切回IDLE状态」 — so an enemy finishes its
attack when its blocker goes. Now every enemy stands for the rest of its clip after a strike (attackStand; a blocked one
never walks, so it shows only when the block ends meanwhile: its blocker stunned or knocked out by that strike, or
gone); a stun / freeze / sleep / 浮空, leaving the field and, new, a displacement (失衡, Battle.displace) end the stand.
流泪小子 now stands 0.8 s (its 1.567 s clip, strike at 0.767), 隐匿, and a blocker whose stun ended often takes it over
again: the same lane 13 stuns, 29.1 s 隐匿 / 12.8 s revealed, leaked at 42.0 s.
Checked and official, unchanged: a stunned operator blocks nothing (gamedata_const ba.stun 「无法移动、阻挡、攻击及使用技能」,
PRTS 异常效果 STUNNED 「无法攻击、释放技能、阻挡敌人类单位」 — the keeps-blocking hypothesis is wrong); 隐匿 back 0 s after a
block (PRTS 流泪小子); moveSpeed 0.8 × the level's moveMultiplier 0.5, interval 2.0 s, stun 3.5 s, MELEE (enemy_database;
PRTS 盟约记录 鸭爵 备注: 4500 HP, 3.5 s stun, 隐匿 kept); the cooldown runs on the walk, so a new blocker sees the swing at
once and the strike after the 0.767 s wind-up; a bot teammate's 鸭爵 swaps into a human's battles, the Final Assault and
the Hidden Core too (「你和队友遭遇的部分敌人」), on the replaced enemy's route.
Golden — traced in lockstep against a copy of the parent: in each of the 199 battle scenarios the first differing tick is
an enemy standing out its clip after a strike where the parent walked on; every moved match has such stands in an
earlier or the same round. roster 41 of 49 (all but -003, -005, -009, -018, -019, -025, -035, -049); bonds 21
(deputShip-low, emptyShip-low / -high, indomShip-low / -high, investShip-low, kazimierzShip-low / -high,
kjeragShip-high, lateranoShip-high, miraShip-high, raidShip-low, sargonShip-high, siracusaShip-low, soloShip-low /
-high, suntShip-low / -high, swiftShip-high, visiShip-low, yanShip-low); fields 13 (all but boss-boss_1-solo,
boss-boss_2-pair / -solo, boss-boss_6-solo, boss-boss_7-pair / -solo, hidden-boss_8-solo, hidden-boss_10-solo,
unite-1); matches 17 (all but solo-HARD-9-boosted); standins 10 of 10; diy 114 of 138 (all but -011, -012, -018, -020,
-025, -027, -029, -031, -033, -047, -053, -077, -080 … -082, -086 … -088, -091 … -093, -098, -108, -123). The
displacement rule alone moves one roster scenario.
Tests: test/sim/feedback5-attack-clip-rest.test.js (5; the four behaviour tests fail on the parent, the data pin passes
on both); test/sim (745), test/content + feedback1-ducklord (2030), test/match (593) pass. Docs: CHANGELOG 0.2.0 规则
(按官方) and 核实后不是问题, docs/history/0.2.0.md §25.22.6, docs/SIM.md, design/engine.md, DATA.md.
The report's steps 1-2 were verified and hold: the bond HP is on every member before the devour (an elite 深巡,
2904 HP / DEF 655, stands its 4345 from 15 layers on), and each mark is one 5000 物理流失 less DEF (PRTS), so a member
in the middle of a chain is marked by every 阿戈尔 behind it — that is what knocked out the reporter's 蓝蒂 / 小鲨.
Golden unchanged: the corpus's only 5-阿戈尔 battle (bond-egirShip-high, 999 layers) has no devour knock-out.
DESIGN §25.22.5.
ai.js updateEnemy returned on `stunned && !e.hidden` before the contact check (Battle._checkBlock), and 冻结 / 沉睡 / 浮空
set `stun` too, so a held enemy was never checked: an operator redeployed or deployed beside it, a slot freeing up or a
push / pull into contact with a stun all waited for the status to end (0.1.4 had it too). PRTS 异常效果: STUNNED / FROZEN
stop the enemy's moves and attacks and hold no 不可阻挡 (BLOCK_FREE), which SLEEPING / LEVITATE do; 术语释义 冻结 names no
block; 作战机制 §碰撞体积与位置识别: an operator deployed onto an enemy blocks it, a standing one included. The held branch now
runs the same check before it returns (no move, no attack, as before): _checkBlock still refuses 沉睡 / 浮空 / 恐惧 /
不可阻挡, an enemy held short of contact is blocked only once it walks in (the same point as without the status), and the
3 s 隐匿 window after a block's end is unchanged. A block the held enemy already holds is still not re-checked (a reborn()
重生 keeps its blocker, as in 0.1.4: noted as open in §25.22.1).
Golden — in each scenario the first divergence is a stunned or frozen enemy blocked during its status by an operator or
summon it touched with a free slot (traced one by one with an instrumented copy): roster-003, -014, -015, -016, -017,
-018, -019, -031, -036, -038, -040, -041, -045, -046, -047; bond-indomShip-high, bond-kazimierzShip-high,
bond-raidShip-low, bond-siracusaShip-low, bond-suntShip-low; boss-boss_3-solo, boss-boss_4-pair, boss-boss_7-solo,
hidden-boss_8-pair, hidden-boss_9-pair, hidden-boss_10-pair, unite-2; matches solo-FUNNY-1, solo-NORMAL-2, solo-HARD-2,
solo-ABYSS-1, coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop2-NORMAL-8-serverrun, coop2-NORMAL-70-diy; diy-003, -004,
-026, -034, -035, -056, -057, -065, -066, -070, -071, -094, -095, -101, -102, -103. standins unchanged.
Tests: test/sim/feedback5-frozen-reblock.test.js (9; the four #232 cases and the 晕眩 / 冻结 capacity cases fail on the
parent). Docs: CHANGELOG 0.2.0 规则(按官方), docs/history/0.2.0.md §25.22.1, docs/SIM.md (§1.2 Blocking, stun, freeze),
docs/design/engine.md, docs/PLAYING.md.
- server/sim/units.js `downAtHome` (false), cleared by every deployment (battle/deploy.js _deploy).
- kits/ops/chess_char_5_05-ulpia.js sets it once the S3 【移动】 has put him on the anchor tile; onEnd clears it when the
skill ends with him standing (the 【返回】); knocked out, it stays for the body.
- sim/battle/tiles.js _layBody: a unit holding it lies on its home tile (his board placement, where the 从不混淆的方向
stood) when that tile is in the rect and free — else where it fell [ASSUMED], the PRTS exception's one attempt; the
timed redeploy, 不屈's and CB1's 复活 (M3茧甲 / 埃芒加德: at once, free) all come back there (restTile), b.snap
`down` names it. Every other moved operator (a 突袭 jump, other kits' 【移动】) keeps "where it fell".
- Test: test/sim/feedback5-ulpia-revive-home.test.js (3) — knocked out during S3 he lies at home, the anchor tile free,
and redeploys there; revived at home by M3茧甲; control: after the 【返回】 and for a 突袭 member the PRTS rule holds. The
first two fail on the parent (he lay and stood up on (10,7)); the third fails there only on the missing field.
- Docs: DESIGN §25.17.3 (the decision, marked as the owner's deviation; the §25 intro's decisions of 2026-10-07),
docs/SIM.md §1, DESIGN §5 (design/engine.md DP / knocked-out line), docs/PLAYING.md 作战 row.
Gate: non-browser suites 5024 / 5024, npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok,
typecheck ok.
Conflicts resolved by hand:
- server/sim/content/items/battle.js: CB3 had extended `revivedInPlace` (an in-place 复活 restarts a deploy-timed
skill, item 44); CB1 replaced the in-place revive by PRTS's form — `reviveNow`, a `death` hook that redeploys the
operator at once, free, where it lies — and removed `revivedInPlace`. CB1's side is kept: its redeploy resets the
skill, so an `activateOnDeploy` skill starts anew by itself; CB3's tests (test/sim/feedback5-raid-revive.test.js:
the jump at once after M3茧甲 / 埃芒加德, the window back to full, 砾's fresh barrier) pass on it unchanged. Their
header, bonds_core's comment, DESIGN §25.17.5 say so.
- docs/SIM.md, docs/design/engine.md: CB1's `fatal` rows (the 复活 are `death` hooks now); the `death` rows also say a
deploy-timed skill starts anew (§25.17.5).
- data/choices.json: no conflict; `node tools/build-data.mjs --offline` on the merged tree rewrites all 15 data files
byte-identically, and the JSON compare with feedback5 shows only pools.pool_equip_kathe.maxTier removed (CB1).
- docs/history/0.2.0.md: CB1's section renumbered §25.21 (its §25.15.1–10 and two self-citations; no citation of it
elsewhere), placed last; the owner's decisions of 2026-10-07 on items 5 and 45 recorded there, in the intro and in the
two pin tests' headers (feedback5-blaze-hammer, feedback5-cb1-pins).
Golden: solo-FUNNY-1 (outside CB1's list) has 6 埃芒加德 revives and 85 freezes on feedback5's path (instrumented).
roster-014 differs from CB1's own digest by 玛恩纳's 无动于衷: CB3's rule (every enemy damage instance) answers the
萨卡兹枯朽战车's pollution that hits a 卡西米尔 operator in CB1's timeline — with that counter limited to attacks the merged
roster-014 equals CB1's digest exactly (checked).
Gate: non-browser suites 5021 / 5021, npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok,
typecheck ok.
The merge:
- server/sim/battle/players.js: CB3's onOwnBoard hunk is byte-identical to CB4's (git kept one); its comment and
docs/SIM.md's onOwnBoard row now name 乌尔比安's S3 【移动】 next to the 突袭 landing (DESIGN §25.17.2 / §25.18).
- test/sim/facing-invariance.test.js: CB3's scenario setup (onOwnBoard off on the open field) and the CB4 merge's
突袭 test (its enemy inside the board) merged cleanly, both kept.
- data/assets.json: CB3 had regenerated it offline without 银灰's entry (his official impact file was not on disk);
`node tools/fetch-assets.mjs --add-only` (shared public/assets, add-only) downloaded p_imp_spear_n.mp3 — 8.8 KB,
the only new file (ok 1, skipped 7968; the 117 misses are the known upstream gaps) — and rewrote the manifest.
Field-wise JSON compare with CB3's manifest: only audio.sfx.units.char_172_svrash = { hit: p_imp_spear_n } plus hash
and stats (files / bytes / sfxUnits +1); against feedback5: CB3's four operators (银灰 now the spear impact and no
S3 swing, 雷蛇 / 初雪 / 圣约送葬人 without the skill-mode impact) plus hash / stats. §25.17.6 says so.
- docs/history/0.2.0.md: §25.17 placed in order, the intro's per-stream list and workstream list name CB3.
Golden moves outside CB3's commit list: coop2-NORMAL-3 fields 砾 from round 3 (her double hit) and 刺玫 in rounds 4-7
(instrumented); solo-FUNNY-1 (in CB3's list through the counters) fields no counter operator on feedback5's path, only
砾, whose two half hits change no kill, leak, LP or end stat there, so its digest stays.
Gate: non-browser suites 5003 / 5003, npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok,
typecheck ok.
Merge fix: test/sim/facing-invariance.test.js — the 突袭 rotation check put its enemy on the board's top row (9, 9),
so the facing-RIGHT landing tile it expected, (8, 8), lies above the player's board, where CB4's onOwnBoard rule (the
official 再部署 goes where the player deploys; community reports 40 / 16.3) now refuses it; the member landed on
(10, 8). The enemy stands at (10, 6), inside rows 9–12 / cols 2–10, so all four diagonal tiles are candidates in every
direction and the test checks the facing tie-break as before. It failed on fb5-cb4 itself (the stream did not run the
file). The §25 intro's stream lines joined into one sentence again.
Golden moves outside CB4's commit list are its rules on feedback5's paths (instrumented): solo-HARD-1 and solo-ABYSS-1
now have silenced 枯朽战士 deaths (8 / 15) and 突袭 candidates the own-board filter refuses; coop2-ABYSS-10-boosted,
coop2-NORMAL-3 and coop2-NORMAL-14-standins (in CB4's list) have no silenced 枯朽战士 death on this path, and their
digests are identical with the own-board filter switched off (the refused tiles were never the chosen ones).
Gate: non-browser suites 4968 / 4968, npm run golden ok, test/golden.test.js 9 / 9, lint 0 errors, check:imports ok,
typecheck ok, docs-consistency + docs-paths 31 / 31.
Merge fixes (the three tests the trial merge failed):
- test/content/summon_loadout_conflicts.test.js (赫默's drone lifetime per module variant): it asserted "no cast yet"
after the drone's redeploy time and then cast S2 by hand — with nobody hurt the old DEFAULT rule never cast. S2 now
casts by itself the tick its SP is full (22 s), which is the official rule (client skill skchr_silent_2: no
`_trigger`, `_allowNoTarget` 1, `_checkHasTargetBeforeDoCast` 0; CB2 64f8bf9), so the test waits for that cast and
checks both drones' lifetimes from it. Failed on fb5-cb2 itself (the stream did not run the file).
- test/sim/feedback1f-downed-tile.test.js (F5 rule 3): its "killed" case killed 浊心斯卡蒂's 海嗣 with 1e9 true damage;
the 海嗣 is a countdown summon now — 无敌 + 禁疗, the official state (PRTS 海嗣 备注 「持有禁疗、无敌」) — so it only
expires. The killed case takes 缪尔赛思's 流形 (a placed board summon that is attacked and killed and also comes back on
its tile 25 s later); both cases now also check the summon's return. Failed on fb5-cb2 itself.
- test/golden-standins.test.js (the 补位 match fields at least 3 different stand-ins): already failing on feedback5
37558f0 — CB5's 调度中心 upgrade card changed the human seat's shop in coop2-NORMAL-14-standins, which then fielded
only 2 of its 7 not-owned chess (忍冬 → Sharp, 安洁莉娜 → Raidian). tools/golden.mjs MATCH_NOT_OWNED adds 缄默德克萨斯
and 铃兰 (chess its AI fields now): 4 stand-ins (Sharp from round 4, Misery from 7, 预备干员-辅助 from 9, Raidian in 12);
the test is unchanged, test/golden/README.md says 9 chess. This moves coop2-NORMAL-14-standins only.
- docs/history/0.2.0.md: the §25 intro's two stream lines joined into one sentence (the merge had cut it).
Golden moves outside CB2's commit list are CB2's rules on the paths CB5 gave the bots: coop2-HARD-4 fields 琳琅诗怀雅
from round 6, coop2-NORMAL-8-serverrun 刺玫 from round 7, coop2-ABYSS-10-boosted 刺玫 from round 2,
coop2-NORMAL-14-standins 刺玫 from round 3 and 琳琅诗怀雅 in rounds 6-7 (instrumented); coop2-NORMAL-3 and coop4-FUNNY-6
(in CB2's list) still field 刺玫 and her S2 heals now reach other allies (36 / 205 heals), but no kill, leak, LP or
end stat changes, so their digests stay.
Gate: non-browser suites 4949 / 4949, npm run golden ok (283 scenarios), test/golden.test.js 9 / 9, lint 0 errors,
check:imports ok, typecheck ok.
docs/history/0.2.0.md: §25.17 in §25's style (seen / official / cause / now / [ASSUMED] / golden / tests) and the intro list;
docs/design/engine.md cites §25.17.5 for the in-place revive; the header comments of 星熊, 斩业星熊, 年 and 余 say "every enemy
damage instance" instead of "every attack". No code change.
Official: PRTS 盟约记录 备注 — "复活" is 退场 + a 0-time / 0-cost redeploy, i.e. a new deployment, after which a deploy-timed
skill ("部署后…秒内") runs again; a 突袭 member counts such a running window as 技能就绪 (bonds/addon/battle.js raidPoll,
GitHub #49 / #109) and jumps when nothing is in its range. Cause: the remake revives in place (fatal hook) — her window, over
by then, stayed over, and only the idle trigger was left (measured: 缄默德克萨斯 S3 jumped 10.00 s after an M3茧甲 / 埃芒加德
revive, 0.23 s after 不屈's real redeploy). Now items/battle.js revivedInPlace also restarts an activateOnDeploy skill on the
next tick: activated anew when its window had ended, its window back to the full duration when it still ran. The SP reset
and the other 部署时 effects of PRTS's form stay unmodelled (§21.21).
Golden: 1 scenario — roster-012: the elite 缄默德克萨斯 is revived in place three times (101 s, 105 s, 113 s) and her S3 runs
again each time (checked instrumented). Tests: test/sim/feedback5-raid-revive.test.js (3; the M3茧甲 / 埃芒加德 jump and the
window cases fail on the parent, 不屈 is the unchanged control). Docs: docs/SIM.md and docs/design/engine.md (fatal hook).
The own half was already the official one (the right-hand player on the right half with the
right_boss_*_camera_param camera, its layout mirrored col c -> 20 - c with RIGHT <-> LEFT, so its operators
face the leader as before: research 09 §1.2, DESIGN §15); the partner's half stood empty and nothing framed
the pair.
Server: in a boss round's prep m.private.bossMate { playerId, side, units } = the partner's board as UnitInfo
on its half (Match.bossMateView, finalAssault.js bossFieldPlacement); markPrivate marks the partner's
m.private with the mover's, so it follows live; a scout of either player (Match.prepFieldMeta) carries both
halves (`mate`), and its signature covers the partner's board.
Client: render/app.js syncMates draws the partner's units read-only ('m:<uid>' views: never picked, dragged
or swept with the own pieces) and the whole boss field is lit; the team panel frames the pair's avatars
(gameLogic teamFrameIds: the players still in by seat, two by two, as prepCamera — the boss field in the
fight) from the round start through the Final Assault / Hidden Core, with the official ui/battle
bg_team_border tinted the autochess green (#4ed8af; a plain ring without the local art); none for a lone
player, an eliminated player, a spectator seat, or 联防.
Overlap with PR #191 (same-field frames, issue #190; not merged): it published m.public bossPairing and
framed the partner alone in gold, 联防 included, plus a banner naming the partner; here the pairing is read
as the prep camera already does, both avatars get the official green frame, boss rounds only.
[ASSUMED] the partner's bench is not shown; one frame per avatar.
Golden unchanged. Tests: test/match/feedback5-boss-pair-prep.test.js (new, 4), the item 55 test now checks
the scouted player's own pieces (the partner's stand on the other half); SP_E2E
test/ui/feedback5-boss-pair.e2e.test.js (new, 2: both halves and the frames in prep and fight; item 55's
eliminated guest on the boss field with the leader). Docs: design/client.md §10 / §15, design/network.md,
PLAYING.md.
Reproduced in the browser (notes i55-repro, an eliminated guest at the boss round): in the Final Assault fight
the watched boss field shows the leader (enemy_9013_acstmk at 10,3), but in the boss round's prep the
eliminated player saw its own empty normal board (camera 'prep', no leader), and a scout of a teammate was the
normal board too, while the players prepare on their half of the boss field with the leader standing there.
Match.prepFieldMeta: in a boss round's prep the scout is built on the boss field (kind 'boss', BOSS_RECT,
units through finalAssault.js bossFieldPlacement: rows − 7, the right half mirrored with RIGHT ↔ LEFT, the player's side;
nextEnemies keeps the leader's spawn tile); game.js frames it with the boss-field prep camera of that side,
which shows the leader (render/app.js leaderShown). With item 56 an eliminated viewer gets it at the round
start without a tap. After the fix the same run: guest field kind 'boss', camera bossPrep, leader shown.
Golden unchanged. Tests: test/match/feedback5-boss-prep-scout.test.js (new, 2). Docs: design/network.md,
design/client.md (§15).
Official: research 09 §3.1 — after elimination the client keeps observing (self_dead_dialog KEEP_WATCH,
DeadAutoObDn { obIndex, state, preparation }, FindFirstAvailObTarget). Until 0.2.0 every phase reset cleared
an eliminated player's watch: in 回合开始 / 机变 / 休整期 it saw its own empty board, in battle the first field.
Server (server/match/match/watch.js): Match.watchPref (viewer → the player of its last manual g.watch: a prep
scout, a single-player field, or the new optional g.watch playerId on a shared 联防 / boss pair field — PR
#189's review point); _watchTargetOf = that player while still in, else the first player still in (seat
order, AI seats included); startRound scouts its board (_followScout, kept through 机变 and 休整期), both
combat modes and the boss fields start on its field, a resync / a spectator joining in prep gets the board,
a followed player who quits hands over at once. Client: the screen adopts the pushed board like a 前往查看 tap
(battle/observe.js followedScout; the stale-field fix of PR #189), keeps it through the prep phases (no camera
flight home and back), a spectator no longer asks for the first player itself; g.watch sends the tapped row.
Overlap with PR #189 (not merged): the same preference chain; differences — the fallback includes AI seats
(a lone human with AI teammates had nobody to follow), shared fields record the tapped playerId.
Golden unchanged. Tests: test/match/feedback5-watch-follow.test.js (new, 9), spectator.test.js updated (the
spectator follows the player it scouted). Docs: design/network.md, PLAYING.md.
PRTS 卫戍协议:盟约 下半/PRTS盟约记录, 埃芒加德 and M3茧甲 备注: "“复活”的实现方式为:受益者因移动之外的原因退场时下次部署的
再部署时间和费用归零". Both revived in place as `fatal` savers, so the operator was never knocked out nor deployed again:
砾 (<部署时> 卡西米尔 +1, <被击倒时> 不屈 +2), 瑕光 (<部署时> +4, 12 per battle), the 卡西米尔 bond's per-deployment ATK,
克莱门莎 / 卡莱莎 and every other 被击倒时 / 部署时 effect skipped the first 3 knock-outs (measured: only the 4th, real
death stacked). Now items/battle.js reviveNow, a `death` hook (M3茧甲 PRIO_RESPAWN 13, 埃芒加德 PRIO_BAND_REVIVE 12,
ahead of 阿戈尔 5 at 11 and 不屈 at 10): the knock-out stands and the operator redeploys at once, free, where it lies
(full HP, SP reset, a new deployment for 坚固维式重锤's lock — revivedInPlace and the `revives` counter are gone). The
ctx carries `revivedBy`, so 阿戈尔 5 still does not count such a knock-out as the member's first (the owner's decision
of 2026-10-05: an operator's own revive uses no slot; 埃芒加德 [ASSUMED] alike, unchanged). 淬羽赫默 S3 keeps its place
right after the hammer's lock (PRIO_REVIVE − 0.5). Tests: test/content/feedback5-revive-redeploy.test.js (fails on the
parent); bands / bonds_core / feedback1f-hammer / op_lessng / op_zuole / docs-consistency updated to the redeploy. Docs:
SIM.md (fatal, death), design/engine.md (fatal, death), PLAYING.md (复活), research 04 (M3茧甲).
Golden: the battles with M3茧甲 or 埃芒加德 whose operators fell — a revive is now a knock-out (deaths count, kill events,
redeploy, SP reset, 被击倒时 / 部署时 effects): roster-012 / 026 / 033 / 040 / 047, boss-boss_6-pair, hidden-boss_8-pair /
boss_9-pair, standin-07; matches solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1.
PRTS 术语释义 寒冷 「友方寒冷始终需要两两一对产生友方冻结,持续时间取双方之中最高」, 异常效果 COLD 「在特定条件下转变为冻结」:
applyStatus kept the older cold on for as long as the freeze, so every later cold froze again; the wind (every 25 s,
20 + 0.1 x layers s of cold) froze an enemy anywhere for the rest of the battle once its cold outlasted 25 s, never
below. Now the pair turns into the freeze (max of the two, as before) and leaves no cold on an enemy; a cold on an
operator (敌方寒冷) keeps the old rule. The wind alone now freezes 20 + 0.1 x L s of every 50 s from 51 layers (all of
it from 300). Tests: test/sim/feedback5-kjerag-cold.test.js (fails on the parent); feedback3-cold-freeze and 灵知 S2
(kits_t4) updated to the pairing. Docs: SIM.md cold row, design/engine.md.
Golden: every scenario where a second cold met an enemy still cold (灵知, 初雪, 雪猎, 耶拉, 圣聆初雪, 凛御银灰, items,
the 谢拉格 wind) — the pair no longer leaves a cold: roster-005 / 009 / 010 / 012 / 014 / 015 / 016 / 018 / 026 / 029 /
031 / 036 / 040 / 042 / 046 / 047 / 049; bond-egirShip-high, bond-indomShip-low, bond-kjeragShip-high,
bond-maniShip-high, bond-raidShip-low, bond-steadShip-high, bond-suntShip-low; boss-boss_2-pair / boss_3-pair /
boss_4-pair / boss_6-pair, hidden-boss_8-pair / boss_10-pair; matches solo-HARD-1, solo-ABYSS-1, coop3-ABYSS-5,
coop4-FUNNY-6, coop4-ABYSS-7.
- Cause, client: the prisoner skeletons carry three clip sets whose collar light is grey, blinking orange
and red. The official battle prefabs (local client battle/enm_pfb_6.ab: the Graphic component's anim key
-> clip table, the modes' replace pairs, the clip the Spine starts on) draw mode Default (【禁锢】) on the
grey set, mode R (the warning) on the orange set and mode L (【解放】) on the red one. The name-based role
resolver gave 普通囚犯 / 老练囚犯 their unnumbered set — the red one — so they came out of the gate freed;
no FORMS entry existed, so no prisoner ever changed (强壮囚犯 / 拳师囚犯 / 重犯 / 传奇重犯 stayed grey).
- Cause, sim: buff_template_data enemy_confinement[atk_cnt] spends a charge ON_BEFORE_ATTACK (not while
麻痹) and switches to mode R when one is left; enemy_liberty_listener[warning] switches to mode L before
the next attack (PRTS "进行第4次攻击前,切换至解放状态"). archetypes.js prisoner() counted on the 'attack'
hook, after the damage, so the 4th attack still hit confined, and it sent no form.
- Now: prisoner() counts in `before` (an attack 麻痹 interrupts never gets there, PRTS 拳师囚犯), setForm
'warning' (fx 'phase', drawn silently: kinds.js SILENT_PHASES) before attack times-1 and 'liberty' (fx
'liberate') before attack times; 重犯's free-all and 杰斯顿's KO switch the freed ones too.
render/units.js FORMS maps 'warning' / 'liberty' for the six skeletons (普通 / 老练囚犯 *2 / unnumbered,
强壮囚犯 *2 / *3, 拳师囚犯 / 重犯 / 传奇重犯 *_orange / *_red). tools/assets/spine.mjs PREFAB_SPINE_ROLES gives
普通囚犯 / 老练囚犯 their grey *3 set; data/assets.json regenerated (fetch-assets --offline --add-only, JSON
compare: only those two anims and the hash), data/enemies.json too (build-data --offline: only their
attackAnim.clip Attack -> Attack3, same length and strike frame). The 3D board draws the same Pixi views.
- Docs: ASSETS.md (PREFAB_SPINE_ROLES, the prisoner forms), SIM.md (the form fx), design/client.md (FORMS).
- Tests: test/content/feedback5-prisoners.test.js (the six: warning before attack 3, attack 4 hits with
ATK +50 %, the forms reach the client and UnitInfo; free-all; 麻痹), test/render/feedback5-prisoners.test.js
(manifest = the prefab's grey set, FORMS sets, view clips, PREFAB_SPINE_ROLES = the committed manifest) —
both fail on the parent but the 麻痹 pin; test/render/feedback5-prisoners.browser.test.js (RENDER_E2E=1:
three prisoners on the 2D and the 3D board, each model's set follows its sim form).
- Golden: 28 scenarios. Only the warning's new 'phase' fx (the prisoner fell before its 4th attack):
roster-001, roster-003, roster-007, roster-024, roster-033, bond-miraShip-low. The 4th attack now hits
freed, one attack earlier than before, so the fight after it moves: roster-004, roster-022, roster-026,
bond-kazimierzShip-low, bond-raidShip-low, bond-soloShip-low, bond-victoriaShip-low, bond-visiShip-low,
standin-01, diy-024, diy-061, diy-065, diy-066, diy-094, diy-097, diy-104, diy-105, diy-127; only the
per-second snapshots besides the fx: roster-021, diy-017; and the bot matches whose battles field
prisoners: solo-ABYSS-2, solo-HARD-9-boosted.
Ported by hand from PR #211 by @xcdoge:
- render/units.js: FLY_HOVER 0.32 → 1.3, flat (the official fly offset is one model-independent Vector3(0, 0.35, 0) in
the client's character space, whose unit is the standard battle-prefab scale 0.27; an official screenshot measures
1.2–1.4 tiles); enemyModelScaleY multiplies the skeleton's Y scale and the head (bar) height, also in the impostor
path; a mirrorX model flips on top of the direction flip. The body, its HP bar, damage numbers, projectile hits and
skill rings ride `hover`, the shadow stays on the ground; one camera drives the units over the 2D and the 3D board.
- tools/build-data.mjs MODEL_STRETCH_Y / MIRRORED_PREFABS → enemies.json modelScaleY / mirrorX; data/enemies.json
regenerated offline here (the PR's one-line blob was not taken) — JSON compare: only enemy_1112_emppnt /
enemy_1112_emppnt_2 modelScaleY 1.263 and enemy_1196_msfyin mirrorX true.
- tools/local-extract/enemy_model_offsets.py (optional, host-side prefab reader) and
docs/research/12-flying-visuals-official.md (without the claim that the owner said 「都要」 on 2026-10-05 — the owner's
decision of 2026-10-06 is cited; where the quirk enemies appear corrected against 0.2.0's data: 帝国炮火先兆者 is a FLY
special enemy of the mode, 帝国炮火中枢先兆者 comes with the bounty 战术特训·飞行III, 木制瑞印 is in no wave). No
screenshot or extracted art committed.
Tests (from PR #211): test/render/unitview.test.js (FLY_HOVER ≈ 0.35 / 0.27; the same lift for every flyer; a flying
view lifts by FLY_HOVER — added here: its body and HP bar ride the lift, its shadow stays on the ground),
test/render/playtest6-enemies.test.js (the data keys; the 1.263 aspect and bar height; the mirrored model's x sign);
both fail without the renderer change; the 425 render unit tests pass. Docs: DATA §9, ASSETS, design client.md,
research 00 / 12, DESIGN §25.13.5 and the §25.10 table.
Golden unchanged (drawing only; the sim reads neither field).
Ported by hand from PR #209 by @qingjingshenghuo (its one-line data/config.json was not taken):
- server/match/gamedata.js bossPoolShareOf (used by GameData.bossPoolShare and by finalAssault.bossPoolHp for a plain
game-data object): solo × `solo` (1); co-op with `perPlayer` true × `coop` (1) × the players alive at the boss
round's start (bots and AI 托管 seats count, eliminated / departed seats do not; at most aliveFull 4; a count left
out = a full team). The fixed pool stays restorable through config: bossHpScale perPlayer false + solo 0.25 (and its
optional aliveScaling).
- tools/build-data.mjs: the global bossHpScale carries the rule (perPlayer true, solo 1); the modes' entries keep only
bloodPointKey / unaffectedByEnemyScale. data/config.json regenerated offline — JSON compare: only bossHpScale (global
and the 9 mode entries) changed.
- The problem #209 documented: a 胄 drone death is 2 % of the pool and 限伤 cancels a hit of ceil ≥ 300000, so the
hidden 胄 终极 pool at 3 / 4 players (21.6M / 28.8M) would cancel every drone (432000 / 576000). The drone link is
a share of the pool, no hit: Battle.loseHp takes noHitLimit and the link passes the limit [ASSUMED: the official
client checks every damage modifier and PRTS calls it 真实伤害, but which max HP it reads is undocumented]; a part's
传递 and every hit stay under the limit.
Golden (regenerated; why: the pool size):
- fields: boss-boss_1..7-pair/-solo and hidden-boss_8..10-pair/-solo (all 20 boss / hidden scenarios) — a pair
field's pool × 2 (two players), a solo template's × 4 (solo × 1, was × 0.25); boss-boss_1-pair, boss-boss_1-solo,
boss-boss_3-pair, boss-boss_3-solo, boss-boss_5-solo, boss-boss_7-pair and hidden-boss_9-solo now end at the
200 s cap instead of clearing; the two 联防 scenarios are unchanged.
- matches: solo-FUNNY-2, solo-NORMAL-1, solo-HARD-1, solo-HARD-2 now lose the Final Assault (solo leaders have 4× the
HP: team LP 0 with 93088–176395 of the pool left); solo-FUNNY-1, solo-NORMAL-2, solo-HARD-9-boosted (its Hidden Core
too), coop2-NORMAL-3, coop4-FUNNY-6, coop2-ABYSS-10-boosted, coop2-NORMAL-70-diy still win with more leader damage
(pool × players alive; solo-FUNNY-1 / solo-HARD-9-boosted end with 6 / 1 team LP less). Unchanged:
coop2-NORMAL-8-serverrun and coop2-NORMAL-14-standins (one player left at R14: × 1 as before) and the matches that
end before R14; roster / bonds / standins / diy unchanged.
Tests: test/match/boss-hp-scaling.test.js (from PR #209, adapted — every leader and difficulty × 1–4 players and solo;
R14 / R15 leave out two departed players; AI 托管 and bots count, an eliminated player does not; config restores the
fixed pool, the opposite of the PR's test; the plain-object fallback agrees; only the leader's HP scales);
test/content/enemies_bosses.test.js (hidden 胄 终极 at 3 / 4 players: the drone share lands, a hit of the same size is
cancelled — fails without the change). Updated on purpose: finalAssault, balance, bosshp, connection, docs-consistency
(the §20.9 record points to §25.13.4; the data carries perPlayer true), sim/playtest6_limits (the drone share lands
past the line), match/playtest6-bosspool (the 5 % knob halved to keep the 2-player pool's size: that test is about the
pool's arithmetic). Docs: PLAYING §7, DATA, META, BALANCE, SIM, design engine.md / network.md, the §20.9 / §20.10
records, research 00 / 01 / 08, the code comments, DESIGN §25.13.4 and the §25.10 table.
- tools/local-extract/extract.py TOKEN_SPINES (`--only spine/token`): each token's battle prefab in the
battle token packs (pkgrps/btl_pfb_tokens_*.ab; the Windows build has all 39, the iOS build lacks
btl_pfb_tokens_0), the skeleton of its Front (or only) Spine renderer, written to
public/assets/local/spine/token/<id>/ like the enemy models (sized pma atlas, premultiplied pages;
the newer RGBA pages keep their own alpha). The enemy writer is shared (write_spine; the two enemy
models re-extract byte for byte). The skinpack job of 乌尔比安's block is gone: it extracted
nothing, and that token draws no model in the game (nor do 圣聆初雪's, 酒神's, 贝洛内's and
予愿安洁莉娜's hidden tokens: no Spine renderer, no avatar in either install).
- tools/assets/local-token-spines.json (committed, `fetch-assets --local-spines`) → data/assets.json
tokens[id].spineLocal for 35 自选 summons, 凯瑟琳's 爬行号·防护单元 and 凛御银灰's three 风雪之眼; the
manifest is the same with or without the extraction (JSON diff: only the 39 spineLocal entries and
the hash). spine.mjs LOCAL_SPINE_ROLES maps clips the resolver cannot read (电弧's 戴乌 C_Skill1_*,
酒神's 本能的召唤 Loop / End, 白铁's platforms End, 战术锚点 on its Default pose) [ASSUMED where named].
- assets.js spineEntry: a token resolves its spineLocal like an enemy (no web model behind it: the
diamond stays when it is not listed or fails to load). Scale as the web tokens (UNIT.modelScale).
- setup reports an extraction without the summon models; docs: ASSETS.md "Token models from the local
client", DEPLOY.md §6, DESIGN §13 (design/client.md), README.
- Tests: test/local-token-models.test.js (new), test/local-extract.test.js (job table, prefab walk,
alpha merge), test/assets.test.js (the local token models load like the client does),
test/package.test.js (the full zip ships them), test/static-local-art.test.js. Golden unchanged.