Commit Graph
158 Commits
Author SHA1 Message Date
sganggs 6bfc08270c 0.2.4-dev: round-29 community fixes and the official shared card pool
- Shared card pool and item stock as in the official mode (the owner's request, from the 路标档案馆 video and public notes): solo low-tier exceptions, co-op operators x2, shared item stock with special-item exceptions, normal / advanced occupancy 1 / 2, re-check on buy, 机变 exhaustion, release on consume / destroy / elimination; 整备 never exceeds stock; invariant held <= cap.
- 突袭: one taunt-first reselect right after a revive (the owner's rule); ordinary polls and redeploys keep the 0.2.3 rule; a revive on an enemy's tile stays. #475: online boss / hidden-boss Raid may land on the empty half (owner-limited scope).
- 浊心斯卡蒂 S1 x 坚守 no longer bounce redirected damage, and the two shares conserve the hit (idea of PR #464 by @HappyTanuki).
- Operator fixes adapted from PRs by @Xinglan233 (#459 哈洛德 S2 elemental trigger, #460 涤火杰西卡 shield on the four adjacent tiles, #461 家族徽章 first hit after stealth, #462 松果 S2 regrow per deploy); 克莱门莎 S2 splash 1.1 / 100% ATK and air auto-trigger (#467); 白面 copying 歌蕾蒂娅 / 余 skips the three-member condition (#473, the owner's video).
- 香槟炸弹 is not chosen by friendly auras / heals (PR #468 / #476 by @Siornya); 琳琅诗怀雅 coin balance (PR #450 by @IceCodeNew); Touch prep display (PR #451 by @YinqiMargatroid); lobby header (PR #471 by @YuriRestia); Japanese UI terms (PR #448 by @scutkane).
- AI-only boss countdown grace, render-failure cache / recovery without restarting a battle, device units kept on a same-field re-entry, DIY detail / skill previews, large-text result buttons.
- golden: battle families equal 0.2.3 except diy-135 / diy-136 (克莱门莎); all 18 release matches move through the shared pool, every flip named in docs/history/0.2.4.md §29.12; two coverage scenarios added.
2026-10-11 01:58:52 +08:00
sganggs 821ab827bd 修复赤刃明霄陈三技能对空自动开启并核实社区反馈
S3 的扩展范围含可选中的飞行目标时可以自动开启剑气;普攻保持仅对地。
[ASSUMED] 自动触发区域沿用扩展攻击格和永久射程延长,不扩张到未来剑气的整条路线。

补测部署技能与休整属性预览、后排协防蕾缪安真实锁定耗弹、诗怀雅落地金币、
莎草对炸弹的治疗链,以及予愿三种技能的普通地面溅射和无视可选性例外。
未复现的报告保留证据边界;诗怀雅被动落地投瓶符合 PRTS。

全 285 场 golden 重建并验证,仅 diy-074 变化:新增 4 次陈 S3,伤害 142376→163563,
时序/选敌改变后击杀 21→18、漏怪 96→99,223.83 秒和结束方式不变。
其余 284 场不变。

CI:5879 tests,5876 pass,0 fail,3 skip;setup/smoke/lint/imports/typecheck 均通过。
新增 16 项回归,相关 6 文件 63/63,补充 2 文件 20/20,文档 31/31。
2026-10-10 01:02:23 +08:00
sganggs 708caf7d06 fix: correct community-reported combat and strategy draft behavior
Guard lethal stuns against a replacement deployment; keep bard auras running
through stun; require an active Aegir bond for Clementia's strategy; allow
Compass SP to bypass recovery locks; and refresh Hidden Core gun mitigation
against current gun/spring companions. Set multiplayer strategy picks to 50s
and describe renderer fallback without diagnosing unsupported hardware.

Golden moves (full 285-scenario regeneration):
- roster-008: inactive Aegir no longer gains 18 strategy layers.
- boss-boss_4-solo: bard regeneration continues through stun.
- hidden-boss_9-pair: mitigation covers first hits/current companions.
- standin-07: hammer stun cannot follow an immediate revival deployment.
No ending-reason changes. Sources and report-by-report findings: DESIGN 28.25.
2026-10-10 00:45:41 +08:00
sganggs f3658a0dd4 0.2.3: prepare follow-up fixes and client features (#407, #408, #410, #413, #431, #435, #436, #440, #444)
Add special-target activation without bypassing attack cadence, cooperative
card preferences and emotes, skill sound banks, per-operator voice choices,
text sizing, install prompts, closed-tab recovery and unanimous setup rerolls.
Retain late deployment elapsed time and resolve echo/counter pulses without
recursive hook overflow. Independent final review remains pending.

Golden: roster-016, roster-035, roster-036, roster-045;
boss-boss_1-pair, boss-boss_3-pair, boss-boss_5-pair, boss-boss_7-pair,
hidden-boss_9-pair; solo-HARD-9-boosted: Lemuen attack cadence.
diy-132: Necras extra activation targets.
coop2-NORMAL-14-standins: cooperative purchase preference and simulation fixes.
hidden-boss_10-solo: echo pulse FIFO resolution.
See DESIGN 28.13-28.24 for assumptions, sources and contributor credit.
2026-10-09 22:55:24 +08:00
sganggs 0ced21d321 feedback7: merge fb7-loadout — per-operator 潜能 (1–6) and 练度 (未精英化 / 精英1 / 精英2 / 精英2 Lv.60) set in 干员调配, defaults 潜能 6 and 精英2 Lv.60 (the owner's decision of 2026-10-08); PR #301 ported as one aligned list; #398 the skill frame, #400 REA-Y's heal text
- Conflicts (the branch started at ae22335c): the four i18n packs (union of keys), game.js DetailPanel props (ops + the
  voices' voice=true), detailPanel.js resolveDetail (cultOpts + the voices' tap), match/views.js (both helpers),
  docs/PLAYING.md and design/network.md merged line by line against the base. test/match/bosshp.test.js's ATK formula
  now includes the unit's 练度 multiplier (cultMul).
- Grok review: MERGE AFTER FIXES — the note and the module card at the set potential fixed on the branch.
- Golden: every owned operator ×1.1 ATK / DEF / max HP by default — roster 49, bonds 46, fields 22, matches 18, diy 134
  (stand-in battles and prototype picks unchanged). Broad node --test 5666 (0 fail after the bosshp formula; 2 skipped);
  lint 0 errors; typecheck; i18n strict clean (1152 msgids).
2026-10-08 22:35:33 +08:00
sganggs 2de2feee3c feedback7: merge fb7-dobermann — 教鞭 is the official three-choice (PR #353 by @Sukvii, the owner's decision), the Final Assault / Hidden Core pay a 战术特训 card's perfect coins (guarded by b.progress leaksBy), the bots put zero-range heavy guards on the road first (PR #339 by @IceCodeNew, placement only)
- Conflicts (the branch started at ae22335c): spec.js / protocol.js b.progress (resolved from fb7-ui-backlog + leaksBy),
  gameLogic.js / game.js exports (device tips + normalizePersonalChoice), docs/PLAYING.md / design/network.md lines
  merged against the base, META.md (画卷 note + 教鞭), test/docs-consistency.test.js pin (both fields).
- Grok review: MERGE AFTER FIXES — the HIGH (a forged boss result paid the cards) fixed on the branch (66a228c6), the
  META wording (1ab12763).
- Golden: matches 10 (the 杜宾 bot picks and the guard placement); union: coop3-ABYSS-5 differs from HEAD only in ai_2's
  dmgDealt (1238011 → 1186269), no outcome change — HEAD's side had moved it, the new placement applies on that path.
- Broad node --test 5623 (5621 pass, 0 fail, 2 skipped); lint 0 errors; typecheck; i18n strict clean.
2026-10-08 22:11:49 +08:00
sganggs 66a228c64c feedback7 dober: a boss field's perfect-payout card is paid only where the client's result agrees with its own b.progress (b.progress.leaksBy splits the field's LP per player); golden: unchanged
Seen (review of this branch): the Final Assault / Hidden Core path trusts a client's b.result — validateClientResult skips
the leak multiset for boss fields and nothing re-simulates — while the team LP a field costs arrives through b.progress.leaks.
Commit 05eb2b80 paid the perfect-payout cards from the result's leaked list, so a modified authority could have the server
charge a leak and then report two empty lists (LP 58 -> 57 charged, both seats +2: FORGE_BOTH_EMPTY) or pin the leak on the
partner's list (the forger +2, the partner 0: FORGE_PAY).
Now: b.progress carries `leaksBy` (shared/protocol.js, optional; spec.js battleProgress for a boss / hidden field): per
player, the LP of the leak entries the result will show (compactResult's list, lpr clamped as on the wire); `leaks` minus its
sum is the leader's own "扣除目标生命" effects (boss "Doom" / 斥退 / 萨米 Doom), which are no leak and must not cost a perfect
seat its card. The browser runner and the test SimClient send it; the server keeps the highest value per player (f.leaksBy).
Match._bossLeaksAgree (called by _finishFinal) pays a seat of a client-reported field only when (a) the LP the authority
reported was split at all (leaksBy seen whenever lpReported > 0 — a report without the split explains nothing) and (b) the
seat's leaked list shows at least the leak LP reported for it. A server-run field (no client, a takeover) is the truth itself.
Not stopped, and said so in the docs: an authority that lies consistently (a split that blames the partner, a leak labelled as
the leader's effect) — the boss path has no second source; what is caught is a result that contradicts its own progress.
Tests (test first; the three forge tests failed on the parent with [2, 2] / [2, 0] paid, the runner test on the missing field):
clientCombat.test.js — charged leak + two empty lists -> 0 / 0; the leak moved to the partner -> 0 / 0 (with and without a
split); honest split -> the clean seat paid, the leaker not, a leader's effect LP costs nobody; two honest SimClients end to
end; the protocol field; runner.test.js — the browser runner's boss progress carries leaksBy; docs-consistency pins the
b.progress signature. Grok's two repro scripts now print +0 / +0.
Golden: unchanged (matches 18 / full 283 scenarios match; every golden boss field is server-run).
2026-10-08 22:01:26 +08:00
sganggs ac4231846f feedback7 R22-LATE: 画卷 never puts the copied items on the copy — they are gained unequipped into the hand / temp like any item; golden: unchanged
PRTS 卫戍协议:盟约 PRTS盟约记录, 法术编纂集, 画卷 备注: 「选择策略<夕>时可获得<br>使用后销毁,获得的装备为未装备状态」.
Without a promotion the 画卷 handler still equipped every copied item that did not merge (an advanced item, a
变形同构体) onto the copy in the hand (the promotion case was fixed for #389). Now each copied item is only gained
(ctx.grantItem → acquireItem): the hand, overflow temp, and with both full it is destroyed with the usual toast
「整备区已满,获得的装备已销毁」, as for any gained item. A copied normal item that completes a pair still merges at once
(the golden in the hand), and the copy itself still follows the chess gain rules (a full hand and temp refuse the
scroll: HAND_FULL, the scroll kept). Full-hand check: a full hand with 3 free temp slots holds the copy and both
copied advanced items; with 2 free slots the copy and one item fit and the other is destroyed with the toast (before,
both rode on the copy).

Docs: META.md (Arts list), research/04-items.md (画卷 copy). The content test that pinned the old behaviour
(test/content/items.test.js: 变形同构体 "the copy wears it too") now expects it in the hand.
Tests: test/match/scroll-copy-merge.test.js +3 (no promotion, normal and elite targets with advanced items; the full
hand at 3 / 2 free temp slots) — all 3 fail on the parent; every test mentioning 画卷: 177 pass. Golden unchanged.
2026-10-08 21:53:51 +08:00
sganggs 2df1cf15bf feedback7 R22-LATE: a watched teammate's bond popup counts their bench like their own popup — hand operators owned (and counted for 投资人 / 远见 / 奇迹), temp ones never, neither 在场; golden: unchanged
GitHub #385 / PR #387 by @zasx77771. The prep scout sends the hand and the 临时整备区 as units, and ownerBoard put every
op unit on the popup's synthetic board, so the popup marked bench operators 在场 and its 成员 header disagreed with the
server's ring (炎: ring 1, header 3; 远见: ring 2, header 3 with the temp copy).

The scout's units now carry `area` 'board' | 'hand' | 'temp' (the server tags from PR #387; the tag survives the
boss-half remap, the row does not). ownerBoard sorts them into board / hand / temp instead of dropping the bench as
the PR did: dropping the hand would break the bonds that count the 整备区 (投资人 / 远见 / 奇迹 — a hand-only 远见
member: ring 1, header 0). bondMembers / memberHeadCount then count exactly as the player's own popup: hand = owned
and in hand, temp = owned only. A unit without an area (battles, older servers) stays on the board.

Docs: PLAYING.md's 观看谁 paragraph (the bench is listed in prep); comments in bondStrip.js / game.js.
Tests: test/ui/watch-bonds.test.js (36; 2 fail on the parent), test/match/prep-bench.test.js +
feedback5-boss-pair-prep.test.js (area tags; 4 fail on the parent). Golden unchanged (views only).
2026-10-08 21:11:25 +08:00
Xinglan233 19c7b26850 feedback7 R22-LATE: a 画卷 copy that completes a three-copy merge copies the target's equipment again; golden: unchanged
GitHub #389 / PR #390 by @Xinglan233 (this commit is substantially the PR: the snapshot and its test file, ported).
onArt read target.items after grantChess; a gain that completes a merge runs _mergeChess first, which consumes the
target and empties its items, so the copy loop ran on an empty list (two base 维式重锤 never became the +25% one).
The item ids are now recorded before the gain.

Added on top of the PR: after a promotion the copied items are never equipped onto the elite (an already-advanced
item used to go straight back onto it via equipDirect). The promotion unequips the copies (PRTS 帮助 自动卸除 on
合并) and the 画卷 note says the obtained equipment arrives unequipped, so they wait in the hand; a copied normal item
merges with the returned original as before. The non-promotion path is unchanged.

Tests: test/match/scroll-copy-merge.test.js (8; 4 fail on the parent, the advanced-item case also fails with the
PR's snapshot alone). Golden unchanged (no scenario casts 画卷 into a promotion).
2026-10-08 21:04:58 +08:00
IceCodeNew e63214571d feedback7 dober: the bots plan a zero-range heavy guard (range 0-1: only its own tile) on a free enemy-road tile first (PR #339 by @IceCodeNew, that part only); golden: 9 of 18 matches move, no result flips
Seen: 蛇屠箱 and the seven other operators with the range 0-1 (角峰, 古米, 泡泡, 折桠, 菲莱, 塞雷娅, 余 — 16 chess records, all
melee blockers with block 3, the elite 角峰 4) block and hit only on the tile they stand on. The layout planner maximizes an exposure model whose
kill estimate saturates in a full layout; the tile then falls to a 1e-6 rng tie-break, so a guard stood off the road, where
it neither blocks nor hits (reproduced: planLayout with six of them on act2autochess_m04, seed 11 — 蛇屠箱 off the road; over
40 seeds of the eight stages 16 of 320 guards stood off the road: 11 because the eight guards outnumber the 7 free road tiles of
act1autochess_m03 / act2autochess_m02, 5 avoidably — after the change only the 11 are left).
Official: excel range_table "0-1" is the single grid (0,0).
Now: planLayoutSteps keeps the best road tile (model.ground) for a blocker whose deployed range grid (rangeRec: its
loadout's) is exactly one (0,0) grid, and takes it before the overall best; with no free road tile the best legal tile as
before (a high-ground 「可以放置于远程位」 blocker keeps its own preference first).
Not taken from the PR: the scarce-card penalty of buyScore, the item scoring (人事部文档 "only with nine 炎" contradicts the
official 部署上限变为 9), the 神秘 morph fit and the draft yield — AI heuristics with no official text, and the golden
swing they caused (a solo match that cleared the Hidden Core no longer did) came from them, not from the placement.
Golden (matches only; every other family unchanged, --twice identical): solo-FUNNY-1 (from R7: boss pool left 74740 -> 71550,
still a loss in R8), solo-NORMAL-1 (from R4: same win, Hidden Core cleared, team LP 26), solo-HARD-1 (from R4: boss pool left
3046117 -> 2940542), solo-ABYSS-1 (from R4: 2303334 -> 2281356, still a loss in R13), coop2-NORMAL-3 (from R4), coop2-HARD-4
(from R4), coop4-FUNNY-6 (from R3: virtualMs 128500 -> 127000), coop2-NORMAL-8-serverrun (from R3), coop2-NORMAL-70-diy (from
R6): rows, stats, lineups and rng draws differ, wins, losses and rounds passed do not.
botbench A/B on 12 seeds each (solo HARD / ABYSS, coop4 NORMAL / HARD): wins 1 / 0 / 11 / 3 both before and after, rounds
passed 13.00 / 11.92 / 14.00 / 13.00 -> 12.92 / 11.92 / 14.00 / 13.17, LP left 18.7 / 6.5 / 80.1 / 27.9 -> 18.8 / 7.4 / 83.7 / 28.0.
Tests: bot.test.js — the PR's saturated-layout case (蛇屠箱 blocks and hits only on a road tile, a real battle on both tiles;
the road full: a legal fallback) and every stage over 40 seeds (the eight guards fill min(8, free road tiles) road tiles);
both fail on the parent.
2026-10-08 20:58:20 +08:00
sganggs dc609ea13f feedback7: merge fb7-ui-backlog — #229 device tooltips, #238 settings button in lobby / room, #138 准备 folds the shop, #120 in-match spectator roster (cap stays 2), PR #308 capsule counts (+ the shared-field progress fix), PR #333 empty new shop slot on 调度中心 upgrade
- Conflicts: the stats entry (merged fb7-stats) and the settings button both sit in the lobby / room top bars — both kept;
  public/js/ui/settings.js (#321 diagnostics, voiceLang, SettingsButton); docs/PLAYING.md table rows resolved line by
  line against the merge base (#368, #354, #333 edits kept); docs/SIM.md / design/network.md snapshot field lists
  (resolved + ammo / wolves / neg / stand / standCut); design/client.md Screens line (#368's AI switch kept).
- Grok review: MERGE AFTER FIXES — the shared-field capsule finding fixed on the branch (1a880699).
- Golden: roster 23, bonds 2, fields 11, matches 18, diy 11 — #308 totals / snapshots and #333's shop RNG (union OK);
  broad node --test 5473 (5472 pass, 0 fail, 1 skipped); lint 0 errors; typecheck; i18n strict clean.
2026-10-08 20:56:20 +08:00
sganggs 96d9441ff1 feedback7 R22-LOADOUT: the server takes each player's 潜能 / 练度 per operator and every owned operator fights at them (default 潜能 6, 精英2 Lv.60); golden: roster 49/49, bonds 46/46, fields 22/22, matches 18/18, diy 134/138 moved (+10 % 练度), standins unchanged
The owner's decision of 2026-10-08: 「调配干员里自己设置吧,默认满潜满加成」. Official: the account's potential (调度手册);
the 自持有 bonus to owned operators of the same name, initial and elite alike (activity_table cultivateEffectList).

- shared/protocol.js: room.loadout { entries, ops? } — `ops` = { [charId]: { potential?: 1–6, cultivate?: 0–3 } }
  (isLoadoutOps, ≤ 256), checkLoadoutOps (strict like checkLoadout: an operator of the 干员调配 roster or the 自选 owned
  pool — cultivationCharIds; defaults dropped, entries stored complete); absent = none set.
- server/lobby.js / net.js: checked with the entries (either refused ⇒ nothing stored), session.ops / seat.ops /
  seats[].ops, match.setLoadout(playerId, loadout, ops) in INFO_CHECK only.
- PlayerState: ops re-checked and frozen (bots none); cultivationFor (shared/potential.js cultivationOf: null for a
  补位 stand-in or a prototype 自选 pick); loadoutFor carries potential / cultivate; battleInput states `potential` /
  `cultivate` for every owned operator (the chess as itself, an owned 自选 pick) — an old seat without ops and every bot
  at 6 / 3; the 自选 summons' hand count follows the owner's potential (望's 棋子 6 below 潜能3); m.private.ops; the
  scouting UnitInfo carries potential (below 6) / cultivate.
- tools/golden.mjs: the direct-spec scenarios field the default player (owned operators at 潜能 6 / 练度 3; stand-ins
  and prototype picks neither), as PlayerState does.
- Golden: every battle with an owned operator moves — ATK / DEF / max HP ×1.1 from the first second (roster-001:
  角峰 ends at 3527 HP instead of 3115; the first differing snapshot window is 0–10 s in every moved battle); the
  matches part at round 1–8 (battle results, then the bots' boards). Unchanged: the 10 stand-in battles (a stand-in has
  no 练度) and diy-135…138 (the four prototype 自选 battles). No engine error.
- Docs: design/match.md §16 (潜能 / 练度), design/network.md (room.loadout ops).
- Tests: test/match/potential.test.js (new, 4): the ops wire / data check, battle input of a human (settings, defaults,
  stand-in none, elite shares the charId), an old seat and a bot at the defaults, spec → battle, scouting units; 自选
  owned pick vs prototype and 望's hand count; Match.setLoadout in INFO_CHECK only, a refusal changes nothing.
  test/lobby-loadout.test.js (+2): strict validation, storage beside the loadout, an older client's message, the real
  match's briefing (m.private.ops) and lock. test/match/runner.test.js / diy-shop.test.js expect the new fields.
2026-10-08 20:52:30 +08:00
Sukvii 56a7b918a9 feedback7 dober: 教鞭 is the official personal choice of three 战术特训 cards again, not a random one (PR #353 by @Sukvii, the owner's decision of 2026-10-08); golden: coop2-NORMAL-8-serverrun and coop2-ABYSS-10-boosted move
Seen: the Art 教鞭 (杜宾's strategy) drew three 战术特训 cards and added a random one — a documented simplification (docs/META.md
§2.5: a personal choice would need its own phase, protocol message, timer and AI takeover). Official: act2autochess
choice_event hunter_band_1 is a PERSONAL_CHOOSE; PRTS 下半 记录 §法术 教鞭 "使用后销毁,于3个战术特训的悬赏任务中选择一项". The
owner decided on 2026-10-08 to follow it. Taken from PR #353 (code and tests by @Sukvii), ported to the split 0.2.2 modules.
Now: ctx.offerBountyChoice -> Match.offerBountyChoice keeps up to three different mode-eligible cards (15 of 20 in 标准 /
险境, 20 in 绝境 / 终极; one meta-rng shuffle) in PlayerState.personalChoice, m.private only (same cards after a
reconnect); g.choice { idx, choiceId } confirms it (Match.pickPersonalChoice: owner, id, round, index; a request without
a choiceId is still the public draft, a stale id never falls through to it). No new phase or timer: Ready is refused while
it is open, the prep's deadline picks at random before the temp pieces and Ready, a bot or a 托管 seat picks with the bounty
scorer (botPickCard, the bots' seeded rng; at the prep's start, after the cast, before Ready; the scheduler's fallback
picks at random if a step fails). The client reuses ChoiceOverlay (personal: no order, no turns, the prep's countdown) over
a locked prep, the Ready reasons, the mock harness; six UI strings in en / ja / ko / zh-TW. An eliminated player's offer
goes with it; invariants.js flags an offer outside its owner's own open prep.
Not taken from the PR: its §25.24 in the released docs/history/0.2.0.md (the 0.2.2 section is the integrator's), its edits
of the Ready toggle beyond the personal-choice case, its tail placement of the i18n keys.
Golden: coop2-NORMAL-8-serverrun (ai_0 杜宾 casts 教鞭 from R2 on and now scores the three cards: teamLp 39 -> 34, virtualMs
121400 -> 118400, rng shop 511 -> 483 / bots 18063 -> 18070 / meta 112 -> 103) and coop2-ABYSS-10-boosted (ai_1 杜宾: virtualMs
121650 -> 124650, shop 590 -> 589 / bots 22075 -> 22096 / meta 141 -> 154); both first differ in round 2, every other scenario
and family is unchanged; node tools/golden.mjs --twice is identical.
Tests: items.test.js (three distinct cards, the tapped one applied once, nothing before the confirm, a refused cast keeps
the Art / count / rng / uid, an eliminated player's offer), economy.test.js (Ready refused, the deadline picks before
endPrep, solo prep untimed), connection.test.js (two players' offers, reconnect, foreign / duplicate / old ids, an offline
player's pick at the deadline), bot.test.js (托管 takeover incl. the failing-step fallback), bot-smarter.test.js (the scorer's
pick, deterministic per seed, no rng draw when nothing is pending), followups.test.js (R14 / R15), draft.test.js,
unitStats.test.js (protocol), hardening.test.js (invariant), gameLogic / playtest3 / playtest4 (view model, Ready reason,
ChoiceView, actions), mock.e2e.test.js and playtest2.real.e2e.test.js (browser, SP_E2E=1).
2026-10-08 20:45:54 +08:00
sganggs 1a88069968 feedback7 UIBACK: a finished 联防 / boss-pair field publishes the capsule's own resolved, not the sum of the players' clamps — an enemy that enters one helper's half and leaks on the other's is billed to one player's total and to the other's leakedInTotal, so each player's min(total, killedInTotal + leakedInTotal) read 0 while the field read 1/1 and the teammates' m.public progress dropped to 0/1 once the field finished (found reviewing the capsule port of PR #308): validateClientResult keeps the result's field-level resolved (Battle.resolved, clamped to the field's total; junk counts as not reported), _fieldProgress publishes it for client-run and server-run fields alike, the players' own numbers are only the fallback of a result with none, and none at all stays null; SIM.md §1.3 and network.md (顶栏胶囊, result validation) say so; test/match/hud-capsule-resolved.test.js +5 (a 3-human match whose 联防 field runs the real sim with unarmed helpers: capsule 4/4 and m.public 4/4 not 0/4; the boss pair's crossing route; a server-run 联防 field and its timeline; validateClientResult's field-level rule; _fieldProgress's precedence) — all failing before; golden: unchanged (no sim change; compare: roster 49, bonds 46, fields 22, matches 18, standins 10, diy 138 match) 2026-10-08 20:40:42 +08:00
sganggs 7d6fefe8f5 feedback7: merge fb7-hud — HP-bar readouts (#303 / #378 / #383: ammo cells, 伺夜's wolf-shadow pips, 斩业星熊's 我执 bar), #302 renderer abort, #292 held skill cue, #334 atlas slots, #354 梓兰's per-card frost
- Grok review: MERGE (no defect). Golden unchanged (union OK); broad node --test 5327 (5326 pass, 0 fail, 1 skipped); lint 0 errors.
2026-10-08 20:29:27 +08:00
sganggs fc2e57ebff feedback7: merge fb7-g-small — PR #368 (AI teammates draft last, default off), #374 (double-click a teammate's avatar in prep), #321 (copy local diagnostics; whole-token / exact-value redaction) with the review's fixes
- Golden unchanged (union OK); broad node --test 5292 (5291 pass, 0 fail, 1 skipped); lint 0 errors.
2026-10-08 20:23:26 +08:00
sganggs f9969aeeef feedback7: merge fb7-g-ui — PRs #306 #309 #341 #349 #356 #358 #361 applied as-is (Grok coding run; static review p3: MERGE)
- Golden unchanged (union OK); broad node --test 5257 (5256 pass, 0 fail, 1 skipped); lint 0 errors.
2026-10-08 20:14:09 +08:00
Sukvii 05eb2b807f feedback7 dober: the Final Assault and the Hidden Core pay a 战术特训 card's perfect-battle coins, they paid kill coins only (PR #353 by @Sukvii); golden: unchanged
Seen: Match._settleBossBounties banked only the kill coins of the boss field (pp.coins). A perfect-payout bounty (a
战术特训 card, from the 杜宾 Art 教鞭) has its enemies spawned in the Final Assault / Hidden Core like any bounty with
battles left, and used a battle there, but its coins were never paid — SETTLE pays them after every normal battle.
Official: PRTS 杜宾 "<教鞭>:使用后为下场战斗添加额外敌人,若自身战斗完美作战可获得资金" and the cards' "若各自行动阶段就达成完美作战,获得N资金"
have no boss-battle exception. The condition is [ASSUMED]: the team won (the first end the server registered), the result
is a real one (not the synthetic stand-in of a field that never reported) and the player's own field let no counted enemy
through; paid once per player, spent in the Hidden Core's prep with the kill coins.
Now: _finishFinal reads the victory before the per-field loop and hands _settleBossBounties a `perfect` flag.
Tests: clientCombat.test.js (the held 'cleared' result pays once, a duplicate never twice; six eligibility cases:
team win / synthetic / own leak / uncounted leak / forced team defeat), followups.test.js (R14 -> R15 income and the Hidden
Core; a boss win with an own leak keeps the kill coins only). Both fail on the parent.
Golden: unchanged (matches: 18 scenarios match).
2026-10-08 20:12:14 +08:00
anAtheist987 fd791a66cb feedback7 G-SMALL: co-op rooms can make AI teammates draft last (PR #368 by @anAtheist987)
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.
2026-10-08 19:57:26 +08:00
2321Robin 4bb0c11e42 feedback7 UIBACK: upgrading the 调度中心 opens the new shop slot EMPTY — the next refresh or round start fills it; 0.2.0's fill-at-once (464a823, item 19) reverted (GitHub #332 and PR #333 by @2321Robin, official recording BV1AXwuzdEys 1:39 — authored by them: the economy.js change, the rewritten level-slots test and the docs hunks applied as reviewed, without the PR's UPGRADE_FILLS_NEW_SLOTS flag and LOCAL RULING comment: the empty slot is the rule): the shop bar draws a null operator slot as a bare dashed frame (EmptyCard, inert scard--sold) instead of the SOLD OUT 「已招募」 frame, one new UI string in the four packs; META.md §3 and §7 and PLAYING.md say so; the dev mock opens the slot empty and survives null slots; test/ui/shop-empty-slot.e2e.test.js; golden: only matches moves, all 18 (the bots' shop RNG stream) — end results changed in solo-FUNNY-1 defeat→victory, solo-NORMAL-1 and solo-HARD-1 victory→defeat, solo-HARD-2 and solo-ABYSS-1 defeat→eliminated, coop2-NORMAL-14-standins defeat→victory (2 distinct stand-ins fielded: golden-standins.test.js asks ≥ 2); roster, bonds, fields, standins, diy unchanged 2026-10-08 19:38:48 +08:00
xcdoge b4bc89ad4e feedback7 UIBACK: the top-bar capsule counts like the official — resolved = killed + leaked among the enemies the round scheduled (opening 0/3, a leak 1/3, a kill 2/3, the splitter 3/3); a split child, a summon and a boss summon are in neither number but still cost LP and break 完美作战 (PR #308 by @xcdoge, the capsule half of PR #157 — authored by them: the sim / protocol / HUD hunks applied as reviewed): inTotal / killedInTotal / leakedInTotal / resolved, b.snap / BattleResult / b.progress (optional field) / m.public progress carry resolved, killed may exceed total (validation bounds it by the content-spawn allowance); dropped the PR's stray design-doc line — it had replaced the S→C b.end bullet of docs/design/network.md, which is back; the 联防 live-counter change mark follows the enemy list now that a spawn no longer moves total (uniteLeftMark + test); golden: only total / players.*.total / snaps move, outcomes bit-identical — roster 23 (roster-002 003 007 008 009 010 011 012 013 015 017 018 020 023 024 025 026 027 037 045 047 048 049), bonds 2 (bond-maniShip-low, bond-soloShip-high), fields 11 (boss-boss_1 3 5 pair+solo, boss-boss_6-solo, hidden-boss_8 10 pair+solo), diy 11 (diy-036 040 056 062 068 073 083 091 095 114 115), matches 12 (solo-FUNNY-1 NORMAL-1 HARD-1 ABYSS-1 ABYSS-2, coop2-NORMAL-3 coop2-HARD-4 coop3-ABYSS-5 coop4-ABYSS-7 coop2-NORMAL-8-serverrun solo-HARD-9-boosted coop2-NORMAL-70-diy: the total column of 36 round rows), standins unchanged 2026-10-08 19:30:36 +08:00
LimitlessHPPK 3484178e2e feedback7 R22-HUD: a skill cue that reaches the client before its unit is held and sounds once when the unit is tracked — the first cast of a deploy-time skill was silent (PR #292 by @LimitlessHPPK, without handing the whole early buffer to the sound); golden: unchanged
Seen (Grok's review, reproduced here with the real sim): the sim's deploy emits a deployment-activated skill's
['skill', id, 1] before the unit's ['spawn', unitInfo] (宴: three events earlier) and audio.js dropped a skill event of an
unknown unit, so that one cast made no sound while every later one did.

- audio.js: pendingSkill — a 'skill' ON for an unknown unit is held (one per unit, 64 at most, the oldest go first);
  _track plays it once when the unit's spawn arrives; a skill OFF is no cast and is not held; setFieldUnits drops the holds.
  Changed from the PR: setFieldUnits clears the holds BEFORE it tracks the new field's units (the PR cleared after, so a hold
  left over from the previous field would have sounded for a unit of the new field that shares its id — ids are per battle).
- screens/game.js: NOT the PR's change. The pre-entry buffer (keepEarly: every spawn, die, deploy, status and skill since the
  field's m.field, up to 1500 of them) is handed to the sound as its 'spawn' tuples only (new early.js audioEarly): the whole
  list would play every old death, deploy line and cast at once on entering. The live path (onEv) already forwards the full
  list, so the hold alone fixes the live silent first cast; a deploy-tick cast that falls into the short pre-entry gap
  stays unheard (a deliberate gap — replaying "only the casts before their spawn" would still sound a minute-old cast when a
  field was buffered while another one was on screen).
- Tests: test/ui/audio.test.js (3: the held cast in one batch and across batches, once, no hold for a skill OFF; a hold does
  not outlive its field and is bounded; 宴's real deploy: the sim's cast comes before the spawn and sounds exactly once — all
  fail on the parent) and test/match/feedback1-runner-forms.test.js (the audio gets the spawn tuples only: silent replay that
  still teaches the units, the next live cast sounds; the whole list would have sounded old cues). Docs: design/client.md.
- Golden: unchanged (client audio only).
2026-10-08 19:22:08 +08:00
sganggs d7c665dbf6 feedback7 UIBACK: the match's top bar lists the spectator seats — a 观战席 capsule (eye + count) beside the latency opens the roster, the host removes a spectator with its ✕ while the match runs (PR #120 by @salt-fishes, the part the owner accepted: the cap stays 2, no per-room spectator option); the server already took room.removeSpectator at any time — test/match/lobby-integration.test.js now covers the host removing one mid-match (room.closed kicked, the other spectator and the host told, no frame after it, the seat refilled), test/ui/spectator-roster.e2e.test.js the capsule; one new UI string in the four packs; the dev mock seats spectators on ?variant=spectators; golden: unchanged (client only) 2026-10-08 19:19:09 +08:00
siruimei07 a2a3f52418 feedback7 G-TOOLS: SP_VERIFY re-runs slice work in the existing 8 ms budget instead of blocking the event loop (PR #352 by @siruimei07)
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.
2026-10-08 18:47:53 +08:00
siruimei07 246622beff feedback7 G-UI: the spectator prep view updates effects and the enemy preview after a 机变 pick (PR #349 by @siruimei07)
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.
2026-10-08 18:43:24 +08:00
siruimei07 d07d97dc4c feedback7 G-UI: selling a summoner counts the bench slots its summon stacks free (PR #309 by @siruimei07)
Applied as-is. The sale pre-check now treats each of the operator's own hand or temp summon stacks as one empty slot, independent of stack.count, before the equipment returns. Board summons and another player's tokens still occupy their slots. A sale that does not fit still returns HAND_FULL before anything is detached.

Tests: node --test test/match/sell-capacity.test.js test/match/economy.test.js test/match/playtest6_summons.test.js — 28 pass, 0 fail. eslint on the touched files: 0 errors (one pre-existing unused-arg warning in economy.js).
2026-10-08 18:42:00 +08:00
sganggs b05845f507 feedback6: merge R21P — every operator at full potential (潜能 6; GitHub #252, PR #255 by @SpiritedAwayCN, the owner's decision of 2026-10-07)
- tools/build-data.mjs applies every potentialRanks attribute step (COST, RESPAWN_TIME, ATK, MAX_HP, DEF,
  MAGIC_RESISTANCE, ATTACK_SPEED) to the chess, elites, 补位 and 自选 units and picks talent / trait / module-talent
  candidates up to requiredPotentialRank 5; summons take the owner's potential for their talents, not its stat steps.
  data/chess.json, backups.json, tokens.json and the en / ja / ko / zh-TW data translations rebuilt; op-wang.js reads
  her stock cap (8 at full potential). 刺玫 435 ATK / cost 15, 砾 16 s, 至简 70 s.
- No conflicts outside the golden files (regenerated). Golden vs HEAD: roster 49, bonds 46, fields 22, matches 18,
  diy 133 (standins and diy-012 / diy-135…138 unchanged: no potential ranks or never hit). Union check against the
  branch and the merge base: every scenario only the branch moved equals the branch, except roster-048 and
  solo-ABYSS-2: at full potential a 频次 器物 is reached there (roster-048: one more enemy dies instead of leaking, 86 → 87
  units), so R21H's hit count (data × hpMul ÷ supplyHpMul) applies where it did not before — with only that rule put back
  (archetypes.js times as at the merge base) both equal the branch exactly.
- Tests: broad node --test 5147 / 5147; lint 0 errors.
2026-10-07 18:48:38 +08:00
sganggs 806a047717 feedback6 R21P: every operator fights at full potential (潜能 6) — the owner's decision of 2026-10-07 (GitHub #252, PR #255; no host slider): tools/build-data.mjs OPERATOR_POTENTIAL adds every character_table potentialRanks attribute step (部署费用 / 再部署时间 / ATK / HP / DEF / RES / ASPD, all ADDITION, before rounding) to every chess (normal and elite), 补位 and 自选 unit form and picks talent / trait / module-talent candidates up to requiredPotentialRank 5 (「天赋效果增强」); a summon picks its talent candidates at its owner's potential (their requiredPotentialRank mirrors the owner's — PRTS 凯尔希: Mon3tr 3.5 s / 1400) and takes none of the owner's attribute steps; the 原型干员 stand-ins have no potential ranks (unchanged). 刺玫 413 / 17 → 435 / 15 as in the tournament video, 砾 18 → 16 s, 至简 80 → 70 s. data/chess.json (256 records), backups.json (the 71 owned picks, 8 summons), tokens.json (5 summons) and the data translations en / ja / ko / zh-TW rebuilt — only potential-driven fields (JSON-compared); the 干员调配 局内数值 and detail cards read the records. op-wang.js reads her stock cap 「最多拥有8(+1)枚」 through the potential step (the pattern fell back to 7: she sat at the cap from the start). Docs: DATA §2 / §18 / tokens, PLAYING (detail card), research 03, kits README, shared/diy.js / standIn.js, the 71 自选 kit headers and the kit comments that quoted potential-0 numbers. Tests: test/full-potential.test.js (new, fails on the parent); data.test.js re-derivation adds the potential steps (+ respawnTime); every test that hard-coded a potential-0 number carries the full-potential one (from the data and the test's own formula; no check loosened); golden: moves almost everywhere — roster 49/49, bonds 46/46, fields 22/22, matches 18/18, diy 133/138 (each battle parts on a stat: roster-001 / roster-020 at tick 1 on 角峰's HP (生命上限+250), bond-arcaneShip-high at 4.73 s and diy-001 at 3.70 s on an enemy's HP from a higher ATK; diy-110 / 111 also by 望's cap; the matches through those battles); unchanged: standins 10/10 and diy-135…138 (no potential ranks), diy-012 (twelve 夜莺 never hit, never redeployed) 2026-10-07 18:35:39 +08:00
sganggs a9fe794c3f feedback6: merge R21H — 器物 (频次) enemies take the 攻坚 HP share and not 补给线's (the data's exclusion list names exactly the 14 器物), leader summons take the round's enemy scaling (PR #272 by @CXUtk, re-implemented from the data); tools/golden.mjs keeps R21H's roundMods import and drops uniteStageId (removed by R21B); golden regenerated on the merge — vs the parent exactly R21H's 74 scenarios, vs the branch only the other streams' moves 2026-10-07 18:21:01 +08:00
sganggs 3f918c9f03 feedback6: merge fb6-small-fixes (see its commits) 2026-10-07 18:16:10 +08:00
sganggs 3c36cdff48 feedback6: merge R21D — with both slots full, every consumable (博士投影, 盟约之币 …) opens the replace dialog and destroys only the picked item once its effect succeeds (PRTS 卫戍协议/帮助 「达到上限强行佩戴会改为替换装备…并销毁被指定的装备」; GitHub #263, PR #264 by @SpiritedAwayCN, ported); golden as R21D's commit (10 matches: bots using a consumable on a full operator), the merged tree differs from the branch only in R21B's 联防 scenarios 2026-10-07 18:11:53 +08:00
sganggs e33ee3db8f feedback6 R21H: the round's enemy effects reach the 频次 器物 and the leaders' mid-fight summons (PR #272, 感谢 @CXUtk); golden: 74 moved
器物 (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.
2026-10-07 18:05:42 +08:00
sganggs 8cef750e14 feedback6 R21D: a consume-on-equip item on a full carrier opens the replace dialog and replaces the picked item first (GitHub #263, PR #264); golden: matches 10 of 18 moved (a targeted consumable on a full carrier now destroys the oldest item), the rest unchanged
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
2026-10-07 18:03:53 +08:00
sganggs ce2bd75b49 feedback6 R21E: a refresh runs the 刷新时 特质 of the chess that stood on the board / in the hand when it happened (EffectDispatcher.dispatch takes them before its first handler; each still runs where it stands now, one that left is skipped): the 拉普兰德 贾维 grants on every 6th refresh — or the elite that gift completes — fired in the very refresh that granted her and spent her 「本回合首次主动刷新」; now her first is the next manual refresh; round counters are still not copied on a merge (a merged elite is a new 拉普兰德, §25.13.3); the part of PR #196 0.2.0 lacked, re-implemented; golden: unchanged 2026-10-07 18:02:48 +08:00
sganggs 53ccf5d8fe feedback6: merge R21G — battle control messages that arrive while the sim loads or catches up (takeover, forced end, a duplicate start, pool, pause) are held and applied in order (PR #266 by @siruimei07, ported); golden unchanged 2026-10-07 17:57:18 +08:00
sganggs d84b961c80 feedback6 R21G: a b.start is registered as pending before the sim loads, so a b.end (takeover / forced, the first end counts), a second b.start of the same battle, b.pool and the solo pause that arrive during the load or the silent catch-up are applied to it in order once the Battle exists (they were dropped); a superseded authority is still built, the next prep's clear stops the preparation (PR #266 by @siruimei07, re-implemented on the current runner); docs/design/network.md §14 Client runner + b.end; golden: unchanged 2026-10-07 17:51:26 +08:00
sganggs 75202776cc feedback6 R21B: 联防 plays on the round's battlefield again — its terrain, crates, water, devices and runes, as in 0.1.4 (the owner's decision of 2026-10-07, 「官服保留地形,改回去」; GitHub #244). 0.2.0's escaped-level map (8a436249, §25.6.4) is withdrawn: that map is the placeholder grid all 39 wave template levels carry, tile for tile; only the 11 stage levels have terrain. match/unitePhase.js _uniteOpts fields this.stageId in both combat modes, unite.js uniteStageId is removed, the client draws the 联防 on the board's stage again (screens/game.js: 0.1.4's ownStage / baseStage, no map switch, nothing to restore on leaving), the dev mock likewise. Kept from 0.2.0: own-board 突袭 / 乌尔比安 S3 landings (onOwnBoard, raidTile untouched), the bounty payee, the 联防 BGM, the SETTLE result box. data/stages.json keeps the two kind 'unite' records (no field uses them; engine tests use them as a plain two-halves road); tools/build-data.mjs comments only, an offline rebuild reproduces data/ byte for byte. The 联防 battle input is byte-identical to v0.1.4's in 20 cases (5 stages × 1 / 2 helpers × both combat modes); GitHub #41 item 3 (野鬃 pushing walkers back across the gate onto the other half) is back with v0.1.4's numbers, none off the map. Tests: test/match/feedback5-unite-map.test.js rewritten (6, all fail on the parent), test/e2e/coop.e2e.mjs checks the 联防 field's drawn stage; docs: DATA §12, META §4, PLAYING §5, design/geometry-time, I18N, the §25.6.4 pointer; golden: fields unite-1 (escaped_single → act1autochess_m02) and unite-2 (escaped_multi → act1autochess_m07), the stages of 0.1.4, and the co-op matches whose 联防 rounds come out differently on the round's map — coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins (now a victory) 2026-10-07 17:40:03 +08:00
sganggs 522f6287bb feedback5 R19F: community report 「小拉和赫默有概率在备战区叠不上叙拉古和远见」 checked, not a bug — 拉普兰德 stacks 叙拉古 from the 整备区 whenever 叙拉古 is active (it counts deployed 叙拉古 only, her bench copy is not one of the three) at her first manual refresh of the round; 赫默's trait has no 「此干员在整备区时也有效」 and the official data marks it deployed-only (conditionkey character_target_inboard); the 临时整备区 is not the 整备区. No code change: pinned by test/match/feedback5-bench-traits.test.js (the official data rule and the report's cases through the match paths), PLAYING.md §6 says where a 特质 works, CHANGELOG 核实后不是问题, DESIGN §25.22.7; golden unchanged 2026-10-07 12:30:58 +08:00
sganggs f661a47773 feedback5 R19C: the official 「作战结束」 result box and the three battle-over sounds at settlement (GitHub #235; PR #112 by @Convey123, approved and never merged, ported by hand onto the 0.2.0 modules) — the box says only the viewer's own LP, 「全员无伤!」 or 「生命值减少 −N」; after a 联防 the number is the server's own per-player charge (m.public.uniteResult in the SETTLE view) and 「全员无伤!」 only when nothing got through; no box for spectator seats, eliminated players or the boss rounds; golden: unchanged 2026-10-07 11:21:24 +08:00
sganggs 9ca52ba798 feedback5: merge CB1 — community reports (bonds / strategies / items): a pair of colds on an enemy becomes the freeze and leaves no cold (the 谢拉格 wind no longer freezes for good), a 复活 (埃芒加德's 命结之秘, M3茧甲) is a knock-out and an immediate free redeploy where the operator lies (砾's and 瑕光's 被击倒时 / 部署时 layers fire), 佩佩's special refresh is paid, the result card hides the layers of 调和 / 协防干员 / 独行 / 绝技, the 炎佑 keeps off 外勤医疗's slot, 凯瑟琳's 定向投放 offers every shop item; 烛煌's 绝处重燃 before the hammer's lock and the 寻呼模块 / 简易通讯机 tier cap kept as they are (the owner's decisions of 2026-10-07); §25.15 → §25.21 (§25.15 is the shortcuts'); golden: vs feedback5 CB1's own rules — roster-005/009/010/012/014/015/016/018/026/029/031/033/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/3/4/6-pair, hidden-boss_8/9/10-pair, standin-07, matches solo-FUNNY-1, solo-NORMAL-1, solo-HARD-1, solo-ABYSS-1, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7; vs CB1 what feedback5 changed since its base (CB2, CB3, CB4, CB5) plus roster-014
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.
2026-10-07 03:54:35 +08:00
sganggs 3bfe17bd07 feedback5 CB1: item 20 follow-up — bot-smarter's 坎诺特 case no longer assumes the round-7 level-up costs 9
Seed 7's solo bot plays 佩佩 for rounds 1–6; with paid special refreshes (96794c5) it reaches level 3 a round later, so
the 3 → 4 upgrade at round 7 costs 10 instead of 9 and 14 − 10 leaves 4. The test now checks what it means — after the
level-up on the curve the bot spends nothing that takes it under the 5 capital — whatever that price is. No product
change; golden unchanged.
2026-10-07 02:46:59 +08:00
sganggs ef10164773 feedback5 CB5: the boss round's prep shows both players of a pair together — the partner's pieces on the other half, as the battle places them — and frames the pair's two avatars in the official green (community report of 2026-10-06, item 51)
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.
2026-10-07 02:29:49 +08:00
sganggs 1d3c76b11c feedback5 CB1: 凯瑟琳's 定向投放 offers every shop item, not only those up to the shop level (community report 「凯瑟琳装备池没有超出商店等级的装备,原版凯瑟琳1升2都能有6本装备」, item 43)
The pool (pool_equip_kathe) is server-side; tools/build-data.mjs had capped it at the shop level [ASSUMED], so the 1 → 2
upgrade only ever offered tiers I–II (measured over 40 seeds: 55 × I, 65 × II). The players' first-hand account: tier VI
shows at that upgrade. Now every shop-eligible item, uniform [ASSUMED] (40 seeds: I 9, II 15, III 25, IV 31, V 16, VI 24);
pool_equip_narant keeps its cap (no report). data/choices.json rebuilt offline (build-data --offline): the JSON compare
shows only pools.pool_equip_kathe.maxTier removed. Test: test/match/feedback5-cathy-pool.test.js (fails on the parent
data). Docs: research 04 (the pool).

Golden: coop4-ABYSS-7 — a bot on 凯瑟琳 is offered other items at its upgrades (the match runs differently from there).
2026-10-07 02:27:54 +08:00
sganggs f14f03a59f feedback5 CB5: in a boss round's prep a watcher sees the boss field — the scouted player's half (mirrored on the right) with the leader standing at its spawn — instead of the normal board without a boss (community report of 2026-10-06, item 55)
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).
2026-10-07 02:11:44 +08:00
sganggs e8d2b45ad4 feedback5 CB5: an eliminated player (and a spectator seat) follows a player through every phase reset — the one it last watched, else the first player still in — instead of being sent back to its own empty board each round (community report of 2026-10-06, item 56; the idea of PR #189)
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.
2026-10-07 02:06:10 +08:00
sganggs 464a823bc5 feedback5 CB5: a 调度中心 upgrade opens the new level's extra slot at once with a new card, the cards shown stay in place (community report of 2026-10-06, item 19; the official tutorial's 休整期 page: 「升级后将出现更多的商品栏位」)
Until 0.2.0 the shop kept its old slot count until the next roll (a refresh or the round start), so a
level-up 1→2 (险境: 3→4 operator slots) or 3→4 (4→5; 标准: 5→6) showed no new card.
PlayerState._openLevelSlots (player/economy.js): the extra slots of the new level are appended to the
chess slots (the item slot stays after them), each drawn at the new level; [ASSUMED] the new card follows
the freeze toggle as a manual refresh's cards do. audit.js checks the slot count and that every card
shown before stays in place. Docs: META.md §3 / §7, PLAYING.md.

Golden: matches — all 18 scenarios moved (solo-FUNNY-1/2, solo-NORMAL-1/2, solo-HARD-1/2, solo-ABYSS-1/2,
coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun,
solo-HARD-9-boosted, coop2-ABYSS-10-boosted, coop2-NORMAL-14-standins, coop2-NORMAL-70-diy): every bot
levels its shop and now sees (and draws the shop RNG for) the new card at once, so purchases and boards
part from there. roster / bonds / fields / standins / diy unchanged; no engine error stored.
Tests: test/match/feedback5-shop-levelslots.test.js (new).
2026-10-07 01:44:35 +08:00
sganggs 4e58481706 feedback5 WY: the Final Assault / Hidden Core leader pool is bloodPoint × the players alive when the fight starts (solo × 1), replacing 「保持固定血量」; 胄's drone share passes 限伤 (from PR #209 by @qingjingshenghuo; the owner's decision of 2026-10-06)
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.
2026-10-06 21:41:49 +08:00
sganggs 661f1c074e feedback5 WY: a newly merged elite 拉普兰德 is a new 拉普兰德 — its first manual refresh this round adds +8 even when its copies already fired (GitHub #169; the owner's decision of 2026-10-06)
garrison_123 「<刷新时>若为本回合首次主动刷新…此干员在整备区时也有效」 counts per piece (§21.1). No source says whether a
merge resets the count; since 0.1.1 _mergeChess carried the copies' highest per-piece round count onto the elite
([ASSUMED], conservative), so two white copies that had fired this round made an elite that did not fire again. The
owner decided on 2026-10-06 that a newly merged elite counts as a new 拉普兰德.

server/match/player/acquire.js _mergeChess no longer carries the per-piece round counters (拉普兰德's refresh count,
garrison:SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT:refreshes, is the only one): the elite starts at 0 like any new piece;
an in-place promotion keeps its count. test/match/feedback1-meta.test.js: the test that locked the old inherit is
updated on purpose (the elite adds +8 on its next refresh, once), and the case "two white copies refreshed this round,
merge, refresh → +8" is new; both fail without the change. Docs: META (counter API, edge cases), PLAYING, the comments
(PlayerState, pieces, effectsMeta, garrisons/meta), DESIGN §25.13.3 and the §25.10 table.
Golden unchanged (no corpus match merges 拉普兰德 after a refresh in the same round).
2026-10-06 21:41:49 +08:00
sganggs 269d7b0bcf feedback5 WY: 华法琳's granted trait stops at the data's 7 / 14 layers per battle, not 12 / 24 (GitHub #175; from PR #192 by @kukiC; the owner's decision of 2026-10-06)
PRTS 卫戍协议:盟约 下半, 3月27日更新#2调整 (revision 439189): 「[Ⅳ阶]华法琳:赋予的特质的叠层上限从 初始12/精锐24
降低至 初始7/精锐14」 — a nerf; the current 参战干员 record says 至多7层 / 14层 and the client blackboard is 7 / 14.
Research 02 Addendum 1 had read the line backwards, so the remake overrode garrison_95's cap to 12 / 24.

Ported from PR #192 by @kukiC: GRANTED_CAP_OVERRIDE is removed from server/sim/content/garrisons/battle.js and from
the client-result layer bound (server/match/fields.js layerAllowanceOf); a granted trait keeps its data cap like a
native one. Tests from the PR: garrisons_battle (updated on purpose: the 7th cast reaches 7 / 14, the 8th adds
nothing), feedback1-gaps (the bound with 华法琳 + 古米), playtest3 (both cards show 7 / 14); the first two fail without
the fix. Docs: research 00 / 01 / 02 corrected (02-bonds.json's note; data/bonds.json regenerated offline — only
miraShip.spec.layerGain changed), history 0.1.1's pointer, DESIGN §25.13.2 and the §25.10 table.
Golden unchanged (no corpus battle casts 8 times in front of 华法琳).
2026-10-06 21:03:56 +08:00