Applied the host-only room option 「AI 队友最后选择」, default off. Off, the draft order and the random streams stay as they are. On, humans draft before AI seats after the existing shuffle, and a skip stays ahead of the AI seats.
Dropped docs/history/0.2.2.md and every §27 citation. The player-facing sentence is in docs/PLAYING.md. The room-option rule is in docs/design/match.md, docs/design/client.md and docs/design/network.md. The #338 per-bot faction and ban presets are not included.
Tests: node --test test/lobby-ai-last.test.js test/match/ai-picks-last.test.js test/ui/ai-picks-last.test.js — 18 pass. node --test test/lobby.test.js test/client-static.test.js — 378 pass. node tools/i18n.mjs check --all --strict — 1032/1032 in en, ja, ko and zh-TW, 0 errors.
Applied as-is. All-mode verification runs the existing HeadlessJob in headlessSliceMs slices (8 ms on a real scheduler; synchronous when that value is not finite, including the virtual scheduler). The field still settles with the server digest. Sample mode still accepts the client result immediately and logs afterwards. A comparison error in all mode falls back to the server result. Dispose cancels the pending slice timer.
Tests: node --test test/match/verification-slices.test.js — 11 pass, 0 fail; node --test test/match/clientCombat.test.js test/match/clientCombat-review.test.js — 39 pass, 0 fail.
Applied as-is. Prep-scout refresh now signs the public prepFieldMeta payload, so a 机变 or bounty change that only updates effects or nextEnemies is pushed to teammates and spectators. Shop, funds, and private views stay off that payload. An unchanged public view is still not sent twice, and an explicit g.watch still resends the current view.
Tests: node --test test/match/prep-watch.test.js test/match/prep-bench.test.js test/match/spectator.test.js test/match/feedback5-boss-prep-scout.test.js test/match/teammate-loadout.test.js test/ui/spectate-effects.test.js test/match/feedback5-boss-pair-prep.test.js — 25 pass, 0 fail. eslint on the touched files: 0 errors.
器物 (the 14 hit-count keys): hits = data × the spawn's hpMul ÷ supplyHpMul, rounded as before (setHits) — 攻坚装备 / II / III
leave out only 炎佑, 补给线 / 补给线II also exactly these 14 keys (activity_table aceffect_enemy_2 / 2_2 enemy_exclude,
data/effects.json); until now setHits(def.maxHp) overwrote the round's HP (青铜镜 30 hits at every difficulty). build-data
writes enemyScale[r].supplyHp = 1.2^(kHp − kAtk) · extra (the HP-only 补给线II steps and 补给线's 1.08; co-op 终极 R5–R15
only — the rebuild changed config.json alone), gamedata returns it as supplyHpMul and the wave / 悬赏 mods carry it
(waves.js roundMods; a 频次 enemy's death spawn inherits it). Not taken from the PR: its 1.08 / 1.2 back-calculation (it
counted the 补给线II steps as 攻坚装备) and its fractional-HP ceiling. Leader summons (死亡集群's 妖怪, 刺胄之弹,
“裂管之音”/“断弦之音”, 不祥幻影): the boss / hidden field carries the round's scale as flags.enemyScale (bossRounds.js,
golden fields, tools/balance.mjs) and bosses.js summonMods gives them HP / ATK / speed (死亡集群's summon.hp_ratio on
top; 刺胄之弹 / 余音 hit counts follow their HP); the leader's pool stays exempt; 王权号令's invulnerable equipment
unchanged; 炎佑 is an ally token, never scaled (no change). Unchanged and open: the 逐火 / 再生 husk counts (talent
prop_max_hp).
Golden — 器物 counts: roster 37 (002 003 004 005 007–020 022 023 025 026 029–034 036–041 043 045 049: each holds a 器物
extra spawn / 频次 wave / death spawn), bond-soloShip-high, diy 9 (036 040 056 062 068 073 091 114 115), boss-boss_6-solo
(俗心's 小说卷轴 escorts); matches 16 (all but solo-ABYSS-2 and coop2-NORMAL-70-diy: 频次 rounds / 悬赏 and the Final
Assault summons). Summons: boss-boss_1-pair/solo, hidden-boss_8-pair/solo (妖怪, 刺胄之弹), boss-boss_3-pair/solo,
hidden-boss_10-pair/solo (余音), boss-boss_5-pair/solo (不祥幻影). standins unchanged.
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.
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.
「自选干员放入后模拟中的补给池随机范围也将被相应扩大」: player/diy.js diyStockEntries (every
slotted piece with copies left; the roll's own tier rules, no 调度中心 gate) joins
effectsMeta ctx.rollChess / ctx.rollPool (spDraft rollPool extra + chessOf: bonds through
the player's view) and the 驰援 fallback in choices.js. Granted from the player's own
stock (poolOf). Docs: META, PLAYING. Golden: no scenario moved.
The owner's decisions of 2026-10-05 (picks, slots, shop per player, bans); research 0.2.0 §2. Golden unchanged (no scenario slots a DIY pick).
Tests: test/match/diy-shop.test.js (9), test/lobby-diy.test.js (4).
Conflict: server/match/player/basics.js imports (W5G's checkNotOwned + W5I's msg / dn). data/i18n/en.json rebuilt on the merged data (tools/build-i18n.mjs --offline): the 自选 data adds texts of CN-only operators and modules the EN client lacks, so coverage is 97.6 % (skills 95.9, talents 95.1); the coverage floors in test/i18n-data.test.js and docs/I18N.md say so. Golden unchanged; i18n / lobby / match / ui suites 1144/1144 after the rebuild, i18n e2e 1/1.
messaging.js: a toast / CUSTOM ticker line is a string (its own msgid) or a shared/i18n.js msg(); frames keep
the Chinese `text` (older clients) and add `msgid` / `params`; config.broadcasts lines carry their `args`
(the client fills the localized broadcast template; the player name {0} is never translated). Templated call
sites send msg(): the grant toast 「X:获得Y」, the summon-range and terrain withdrawals, the 联防 line,
eliminated / left lines, the 远见 / 奇迹 stack payouts and the 维多利亚 hammers; game-data names go as dn().
The protocol stays backward compatible (new fields optional). Tests that captured m.toast's argument render
it with renderMessage. No gameplay change: golden unchanged.