Commit Graph
116 Commits
Author SHA1 Message Date
sganggs 76a2db7dd0 feedback6 R21F: enemy animation states, the client part of PR #275 by @xcdoge re-implemented — the resolver gives an enemy every numbered skill clip (anims.skills; the caller's first index stays the primary, operators unchanged, 宴's PREFAB_SPINE_ROLES fix kept), the stun spellings Stun_1 and Dizzy_Begin/Loop/End, a separate run role (Run_Begin/Loop/End); the renderer walks an enemy faster than moveSpeed 1 on its Run cycle (猎狗pro, 深池侦察犬 ×2; [ASSUMED] threshold), swaps an enemy's skill clip on a cast event (UnitView.setSkillSlot; a form in force is kept), and plays the 重生 of 巨大的丑东西 (Revive, then the fleeing 大祭司 Idle_2 / Move_2 / Stun_2) and 自在 (Revive_01); data/assets.json regenerated offline: 18 enemies gain anims.skills, 6 anims.run, 3 a stun clip (巨大的丑东西, 斩胄之剑, 破胄之锤), nothing else; the sim's cast slot (a new event) is not part of it; golden: unchanged 2026-10-07 17:59:11 +08:00
sganggs caee1dbed9 feedback5: 宴 stands during 落地斩·破门 — her one unnumbered skill set (Skill_Start / Loop / End) is S1 分神's sit-down rest, and the by-name resolver gave it to S2 too, so she knelt through the opening seconds of every battle (the owner's playtest of 2026-10-07); tools/assets/spine.mjs PREFAB_SPINE_ROLES now also fixes operator models by character id (front and back): 宴's S2 plays her ordinary clips (via 'attack'), S1 keeps the rest; data/assets.json rebuilt offline (only her entries changed); test/feedback5-utage-anims.test.js; CHANGELOG 界面, §25.22.9; golden unchanged (client art only) 2026-10-07 13:28:12 +08:00
sganggs d014b9ff0b feedback5: merge R19D — 5-阿戈尔 revives: the members the battle-start devour knocks out revive by board position once every devour pass is over (community report; replaces §24.3's knock-out order for that batch; scan 「左下到右上」 = columns from the left, bottom first [ASSUMED]); bond HP before the devour and one 5000 物理 (DEF) per mark confirmed official; golden unchanged 2026-10-07 12:46:03 +08:00
sganggs 2b0dfceabd feedback5 R19D: 5-阿戈尔 revives — the members the battle-start devour knocks out revive once every devour pass is over, by board position (columns from the left, bottom first: the [ASSUMED] reading of the community report's 「左下到右上」), not in knock-out order; later knock-outs take the slots left in time order, uneaten members none (community report relayed 2026-10-07); golden: unchanged
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.
2026-10-07 12:32:33 +08:00
sganggs f3a88427d5 feedback5: merge UI-KO — the Korean interface (every msgid translated with the official KR terms, 합니다체, marked machineTranslated); data/i18n/ko.json rebuilt (the UI pack's emote names); golden unchanged 2026-10-07 12:29:32 +08:00
sganggs 818d070668 feedback5: the Japanese game texts keep what the JP client writes like the Chinese — build-i18n's ja test is 'native' (a text identical to the Chinese is the client's own when every Han character of it appears in the client's translated texts: 炎 / 不屈 / 助力 / 速射手 stay, 甄选干员 is still untranslated), so the bonds 炎, 不屈 and 助力 no longer show their English names in a Japanese interface; data/i18n/ja.json rebuilt (97.5 %, bonds 100 %) and zh-TW.json (the UI pack's emote names); the CJK font stack follows <html lang> (Japanese kanji, Traditional Chinese, a Korean face); the lobby's difficulty lines drop the JP bullet ・ too; golden unchanged 2026-10-07 12:11:19 +08:00
sganggs 2e83a6b977 feedback5: Japanese, Korean and Traditional Chinese ship — the official game texts of the JP / KR / TW clients (data/i18n/ja.json 96.5 %, ko.json 97.4 %, zh-TW.json 91.1 % of 22 114 texts, built with build-i18n --lang from ArknightsAssets/ArknightsGamedata) and minimal UI packs public/i18n/ja.json, ko.json, zh-TW.json (a _meta manifest, no UI strings yet): ja / ko fall back to the English UI, zh-TW to the Simplified Chinese UI (no fallback; numbers in 萬 / 億); the owner's decision of 2026-10-07; UI translations welcome from the community; golden: unchanged (client text only)
- The packs: _meta = type lang, the code, its own and English name (日本語 / Japanese, 한국어 / Korean, 繁體中文 /
  Traditional Chinese), version 0.2.0, app >=0.2.0, authors, credits (the official client data, © Hypergryph (/ Yostar
  for JP and KR), via ArknightsAssets/ArknightsGamedata), fallback ["en"] for ja / ko and [] for zh-TW; formatted as
  `node tools/i18n.mjs template` writes them, so a translator runs it on the shipped file and keeps the manifest.
- Built offline from the tables W5AD downloaded and checked today (jp / kr 51.10.0, tw 51.2.0; copied into
  .cache/gamedata-<code>): coverage as W5AD measured; `build-i18n --lang <code> --check` up to date for en, ja, ko,
  zh-TW. `node tools/i18n.mjs check --all --strict`: 0 errors (en complete; ja / ko / zh-TW 0 of 1022 msgids, fallback).
  `node tools/packs.mjs list` lists the four packs with their game texts; `tools/package.mjs --dry-run --list` ships
  data/i18n and public/i18n of all four and the generated packs/index.json.
- With five languages the menu is a list (ui/lang.js SEGMENTED_MAX 4, W5AD's rule): test/ui/i18n.e2e.test.js reads and
  picks either form, expects the shipped languages, and checks each new pack in the browser (its official game text,
  the fallback UI, <html lang>) — SP_E2E=1: 3 / 3; the title page of the five devices (devices.e2e "title page") still
  fits: 5 / 5.
- Docs: docs/I18N.md (intro, §2, §5 "Shipped packs" with the call for UI translations), docs/PLAYING.md (语言),
  DESIGN §25.20 and the §25 intro's decisions of 2026-10-07.

Gate: non-browser suites 5024 / 5024 (test/i18n*, test/package, client-static 358 / 358), npm run golden ok,
test/golden.test.js 9 / 9, lint 0 errors, check:imports ok, typecheck ok.
2026-10-07 04:16:59 +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 d841d6ac22 feedback5: merge CB3 — community reports (operator kits): 砾's normal attack hits twice at 50 % 伤害倍率 (a 频次 enemy loses 2), 乌尔比安's S3 moves only onto his own board (never behind the one-helper 联防 map's red gate nor onto a boss field's other half), the 「受到攻击时」 counters answer every enemy damage instance (菲莱 S2 on 深溟巢涌者's pulse, 星熊, 泡泡, 刺玫, 余, 玛恩纳, 年, 斩业星熊), an in-place 复活 restarts a deploy-timed skill so a 突袭 member jumps at once, 银灰's normal attack plays its own impact (and 雷蛇 / 初雪 / 圣约送葬人 drop a skill mode's sound); five reports pinned; §25.17 between CB2's §25.16 and CB4's §25.18; data/assets.json lists 银灰's impact (p_imp_spear_n.mp3 fetched); golden: vs feedback5 CB3's own rules — roster-002/004/005/008/010/012/016/024/033/044, bond-indomShip-low/high, bond-kazimierzShip-low/high, bond-lateranoShip-low, boss-boss_2-solo, boss-boss_4-pair/-solo, hidden-boss_8-pair/-solo, hidden-boss_9-solo, diy-051, matches solo-FUNNY-2, coop2-NORMAL-3, coop4-ABYSS-7, coop2-ABYSS-10-boosted; vs CB3 exactly what feedback5 changed since its base (CB2, CB4, CB5)
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.
2026-10-07 03:41:48 +08:00
sganggs fe2a0cbc29 feedback5 CB3: 银灰's normal attack no longer plays his S3 swing (and S2 / S3 impact); 雷蛇, 初雪 and 圣约送葬人 lose a skill mode's impact the same way (community report of 2026-10-06, item 54)
Official (excel/audio_data.json, local client charpack char_172_svrash): his normal attack is the Default mode's Combat —
ON_ABILITY_HIT.char_172_svrash.combat p_imp_spear_n, no swing bank; .attack.2 (p_atk_silver_n swing, p_imp_sword_n impact) is
his S3 mode, .attack.1 his S2 mode. Cause: tools/assets/audio.mjs pickUnitSfx took the numbered variants because their files
end in _n (its skill-mode test is by file name). Now an operator whose default mode is the unsuffixed ability (a plain
attack / combat bank of any event) never takes a numbered variant; operators that number their default mode (attack.0 …)
keep the earlier order. Over the 209 operators of the manifest it changes 4: 银灰 (charpack modes Default / S2 / S3), 雷蛇
(attack.1 = S2, lightning impact), 初雪 (attack.1 = her S1-2 mode) and 圣约送葬人 (attack.4 = S3_End).

data/assets.json regenerated offline (fetch-assets --offline --add-only --allow-shrink; JSON compare: exactly the 4 operators'
entries, hash and stats): 雷蛇 / 初雪 / 圣约送葬人 drop their wrong `hit`; 银灰's entry goes too, because his official impact
p_imp_spear_n.mp3 was never downloaded on this machine — one online `node tools/fetch-assets.mjs --add-only` adds it (needs
the owner's OK for that download). Golden: unchanged (the manifest is client-side). Tests: test/feedback5-operator-sfx.test.js
(3; 2 fail on the parent). Docs: docs/ASSETS.md.
2026-10-07 02:38:07 +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 714af994fc feedback5: merge W5AA — community reports of 2026-10-06: the 孤岛风云 prisoners leave the gate confined (grey) and change model as their prefabs do, the 4th attack hits freed; a bounty enemy that dies with no operator behind it pays in 联防 too (the helper whose half it fell on); 忍冬's trigger and 枯朽之种's bounty were already fixed (pinned by tests); DESIGN §25.14 after round 18's §25.13; data/enemies.json regenerated offline on the merged code (round 18's model quirks + the prisoners' clips); golden: vs feedback5 exactly W5AA's moves (26 battles, solo-ABYSS-2, coop2-NORMAL-3, coop3-ABYSS-5, solo-HARD-9-boosted), vs W5AA exactly round 18's boss-pool moves 2026-10-06 22:35:10 +08:00
sganggs 6372f2892b feedback5 WAA: the 孤岛风云 prisoners leave the gate confined and change model as their official prefabs do — grey while 【禁锢】, the blinking orange warning before the last confined attack, red once freed; the 4th attack already hits freed (community report of 2026-10-06 「囚徒类敌人出门就是已解放的模型」)
- 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.
2026-10-06 21:57:51 +08:00
sganggs 66ae86b969 feedback5 WY: flying units hover 1.3 tiles (the client's 0.35 ÷ the standard prefab scale 0.27); the 帝国炮火先兆者 pair is drawn 1.263× taller and 木制瑞印 mirrored, as their official prefabs (from PR #211 by @xcdoge; the owner's decision of 2026-10-06)
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).
2026-10-06 21:41:49 +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 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
sganggs 575333137c feedback5 WX: the 39 summon models no web dump carries come from the local client (extract.py TOKEN_SPINES) and are drawn instead of the avatar diamond when data/local-assets.json lists them
- 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.
2026-10-06 20:16:20 +08:00
sganggs 6499f4b32d feedback5: data/assets.json gains the battle voices of the 71 自选 operators (fetch-assets --add-only: 994 lines, 25.5 MB; voiceChars 120 → 191); nothing else in the manifest changed 2026-10-06 18:41:28 +08:00
sganggs b8a86b88f1 feedback5 WU: module type icons get one lower-case file per type (dec-X and dec-x were two names for one file)
The official data spells one DEC X module 'dec-X' (uniequip_003_aglina) and the others 'dec-x' (WAH-Y is the other
mixed-case type id), and the asset plan stored each under its own case: module/dec-X.png and module/dec-x.png. On
Windows / macOS those are one file, so a release zip built there could hold only one of them and a case-sensitive
server (a Linux VPS) would miss the other — the release packager found it ("planned but not staged"). The local file
is now module/{typeIcon in lower case}.png; the upstream names keep their order (the id as spelled, then lower case).

data/assets.json regenerated offline (node tools/fetch-assets.mjs --offline --add-only). JSON diff: modules.dec-X
/assets/module/dec-X.png -> /assets/module/dec-x.png, modules.WAH-Y /assets/module/WAH-Y.png -> /assets/module/wah-y.png,
hash, stats.files 6979 -> 6978, stats.bytes 458334505 -> 458333444; nothing else.

fetch-assets compares its orphans (what --prune deletes) without case: on Windows / macOS module/WAH-Y.png on disk is
the listed module/wah-y.png and must not be deleted (orphanFiles, exported).

Tests: test/assets-diy.test.js (one lower-case file per type, no two manifest URLs differing only in case, orphans).
docs/ASSETS.md: the module icon file name.
2026-10-06 17:59:13 +08:00
sganggs 9bfac54c8f feedback5 WU: 焰狐龙梓兰 (MH05) leaves data/assets.json; a voice slot with no line on disk is dropped, not []
She left the 自选 pool in 0.2.0 (data/backups.json diy.excluded, a collab operator) but her art stayed in the
manifest because fetch-assets refuses to shrink it. Regenerated offline in this worktree, whose public/assets and
public/fonts are the shared ones: node tools/fetch-assets.mjs --offline --add-only --allow-shrink (--add-only so no
file on disk is rewritten and the fonts entry is kept). The run dropped exactly her 163 entries.

JSON diff against the previous manifest: removed chars.char_1048_orchd2, skills.skchr_orchd2_1..3,
skillsById.skchr_orchd2_1..3, audio.sfx.units.char_1048_orchd2; changed hash and stats (files 6994 -> 6979,
bytes 462620448 -> 458334505, chars 210 -> 209, charsWithBack 207 -> 206, spineModels 673 -> 671,
skills 525 -> 522, sfxUnits 484 -> 483); nothing added, nothing else changed.

The first regeneration also added 71 entries like audio.voice.char_134_ifrit = {select: [], place: []}: the 自选
operators' voice lines are planned but not downloaded on this machine, and resolveTemplate kept an array whose lines
were all missing as []. tools/assets/manifest.mjs now drops such an array like a missing leaf (and the entry it
empties); an array that is empty in the template stays.

Tests: test/assets.test.js (the array rule), test/assets-diy.test.js (no diy.excluded operator keeps art in the
manifest). docs/ASSETS.md: 71 自选 picks, 209 operators (206 with Back), 522 skill icons, the empty-slot rule.
2026-10-06 17:51:41 +08:00
sganggs 723ee72d38 feedback5: merge WE2 — fidelity (data / match / UI): summon hand counts from the tokens' hidden talents (麦哲伦 / 令 3, 白铁 2), 望's 棋子 on any tile, the 一会儿见! marker no hand card, TRY_SEARCH_ALLY_SKILL → SKILL_RANGE + allies, 斩业星熊's normal attack phys, IS / RA-only modules refused (SO-A / SO-B / RA-A), 黑键 S2 at full SP with a free tile, 重构体 / 战术锚点 placement, 塑心's cap per trait, random grants draw 自选 stock, diyBanned shown, AI 托管 buys 自选, 谬因 S2 / 薇薇安娜 S3 area triggers, #148 regression test, tier*.js / diyRecord shims removed; golden: coop2-NORMAL-70-diy, diy-045 / 078 / 114 / 115 / 116 moved (WE2's own), the W4B moves kept 2026-10-06 16:15:52 +08:00
sganggs 53ee0a1c6f feedback5 WE2: 重构体 only on the ground inside Mon3tr's range, 战术锚点 only on a 高台 outside 凯尔希·思衡托's (#26)
Mon3tr T1 "可以在攻击范围内的地面使用…重构体"; PRTS 战术锚点 特性 "仅可以部署在凯尔希·思衡托攻击
范围外的远程位". tools/build-data.mjs summonRecord: ownerRange also from an owner talent
naming the token, ownerRangeOutside / rangedTilesOnly from the token text. Regenerated
offline: 3 paths (重构体 ownerRange true; 战术锚点 ownerRangeOutside, rangedTilesOnly).
Match: board.js placement class 'high'; PlayerState.summonExcluded in _legal, the
re-orientation lift (+ toast), _roomForOutOfRange, the invariants and the bot; the
client mirror (piecePosition HIGH, summonExcluded, reasons; English strings added).
Golden: no scenario moved.
2026-10-06 15:34:20 +08:00
sganggs 75c98f1d93 feedback5 WE2: 驭法铁卫 (斩业星熊) — the data's normal attack is physical, arts only with a skill (#15)
tools/build-data.mjs classifyAttack: a trait whose 法术伤害 comes only "技能开启时" keeps
the normal attack's phys (display / AI only — her kit already attacks physically
off-skill). Regenerated offline: 3 paths, the three forms of char_1044_hsgma2 dmgType
arts -> phys. Golden: no scenario moved.
2026-10-06 15:15:15 +08:00
sganggs cd547b5178 feedback5 WE2: 黍 S3's TRY_SEARCH_ALLY_SKILL becomes SKILL_RANGE + allies in the data, her kit override goes (#17)
tools/build-data.mjs TRIGGER_ALLY_RULES (PRTS 特殊策略 "技能范围内存在可治疗的我方单位时释放技能")
writes the row as the engine's SKILL_RANGE on the skill range with allies: true; simdata
normalizeSkill passes allies into the def's trigger (skills.js reads trigger.allies).
Regenerated offline: 9 paths, the S3 trigger of 黍's three forms. op-shu.js no longer sets
the trigger. Golden: no scenario moved.
2026-10-06 15:13:33 +08:00
sganggs f50fd50273 feedback5 WE2: no hand card for 予愿安洁莉娜's “一会儿见!” — a summon no owner shows is not placeable (#24)
tools/build-data.mjs summonRecord: placeable also needs an owner that shows the summon
(the variants' display source, the owner's displayTokenDict). The S3 marker is the skill's
own object (PRTS 予愿安洁莉娜 S3 备注: the skill deploys it at her initial tile); every other
undisplayed summon is already HIDDEN. Regenerated offline: one path changed,
backups.tokens.token_10071_aglna2_agairp.placeable true -> false. The kit drops its
placed-marker branch (the skill always summons it). Golden: no scenario moved.
2026-10-06 15:10:59 +08:00
sganggs ad23320411 feedback5 WE2: 望's 棋子 may be placed on any tile, as PRTS records (#22)
tools/build-data.mjs TOKEN_POSITION_CORRECTIONS (PRTS 棋子 部署位置 "全部位", 备注
"游戏内召唤物信息与实际不符(显示为仅部署在近战位)") sets the 棋子 record's position to ALL;
validateAll checks every entry. Regenerated offline: one path changed,
backups.tokens.token_10064_wang_stone1.position MELEE -> ALL. Her stock / 阻回 unchanged
(an owner question). Golden: no scenario moved (none places 棋子).
2026-10-06 15:06:07 +08:00
sganggs 99d8807328 feedback5 WE2: 自选 summon hand counts include the tokens' own deploy talents (#21)
tools/build-data.mjs tokenTalentDeckBonus adds a summon's talent max_deploy_count /
max_deck_stack_cnt (the hidden "TOKEN数" talent; a module token part of the same talent
replaces it, matched by prefabKey) to its variant deployLimit / deckStack. Regenerated
offline: only data/backups.json tokens of 麦哲伦 (drones), 令 (souls), 白铁 (devices) and
夜莺 (幻影) changed — deployLimit 3 / 4 with SUM-Y stage 3, 2, 3; deckStack 5 (8), 3 (4).
The hand count (player/diy.js placeableTokens) now matches the official one for every
自选 summoner (PRTS 卫戍协议/帮助 "根据召唤物部署数量上限", the owners' talents, PRTS 幻影 备注).
The summoner kits read deckStack / deployLimit as they are (same decks as before).
Golden: no scenario moved (the diy family places no summon pieces).
2026-10-06 15:01:46 +08:00
sganggs c6827a0eaf feedback5: merge feedback4 (583899d) — W4B ported: 阿戈尔's devoured base ATK as a 最终加算 (atkFinal), a fallen marker's marks still resolve, 缇缇 S2 pulses re-enter sleep for her trait (applyStatus reenter in server/sim/battle/status.js; the 缇缇 kit in kits/ops/chess_char_5_02-titi.js; Battle.js / tier5.js stay feedback5's façade and shim); golden: roster-042, bond-egirShip-high, coop2-ABYSS-10-boosted moved, the same three as on feedback4 (bond-egirShip-high identical to it) 2026-10-06 14:47:13 +08:00
sganggs 7059528c47 feedback5: merge feedback4 (3af5b84) — PRs #186 / #134 / #185 / #24 / #183 ported to the split layout (terrainInfo / TERRAIN_TIPS into a new ui/gameLogic/terrain.js re-exported by the gameLogic.js barrel, closesOnFieldPress into ui/gameLogic/shortcuts.js, the rest in place; 42 strings through t(), #183's 设置 too); golden unchanged
Ported by hand:
- public/js/ui/gameLogic.js is a re-export shim on feedback5: #185's TERRAIN_TIPS / terrainInfo went into the new
  public/js/ui/gameLogic/terrain.js (the barrel gains one re-export line); closesOnFieldPress 'terrain' into
  ui/gameLogic/shortcuts.js.
- i18n (docs/I18N.md): every new user-visible string goes through t() with an English entry in public/i18n/en.json
  (42, after the 补位 strings): the terrain names / tags / fact table are N_() and translated where terrainInfo hands
  them out, the mechanism lines are msgids with their numbers as params (a line with an optional half is two whole
  msgids, the list joins use an array param), the card's 地形机制 / 这一格 titles in ui/detailPanel.js. Official EN
  terms where the EN client has them (stage_table tileInfo: Active Originium, Mire, Exhaust Grille, Deep Water Zone,
  Incursion Point, Protection Objective, Tunnel Entry / Exit, High Ground; Camouflage). The Chinese output is
  byte-identical to feedback4's terrainInfo on all 5187 tiles of the 13 stages and on synthetic stages.
- public/js/screens/game.js (MatchScreen still lives there): the import keeps feedback5's names plus terrainInfo /
  fieldTile; the terrain stage is the one view.setStage gets, so it includes feedback5's fieldStage — a 联防 field is
  played on its own map (GitHub #41), and a tap there explains that map's tile, not the round stage's. The tileClick
  handler and render/app.js emitTileClick / ui/fallbackField.js auto-merged in place. terrainInfo reads the stage only,
  so a 补位 / 自选 board explains its tiles the same way (their pieces only change a piece's art and name).
- public/js/ui/detailPanel.js: the terrain branch of resolveDetail before feedback5's stand-in / 自选 helpers.
- public/js/screens/title.js (#183): feedback5's LangToggle / t() imports plus GIcon / SettingsModal; the gear
  button's aria-label / title are t('设置') (en.json already has "Settings").
- tools/fetch-assets.mjs / tools/assets/downloader.mjs: --add-only (keepExisting, the font skip) beside #24's
  --asset-source / SP_ASSET_SOURCE and the shared MirrorPolicy; feedback5's extraOperators / moduleTypes and the
  voice plan unchanged.
- data/assets.json: feedback5's manifest plus #134's audio.sfx.battle.leak (after heal, the plan order); stats files
  6993 → 6994 and bytes +23 240 as measured on feedback4; the hash recomputed with the generator's contentHash
  (c01aa739a2d3 → 6a547fc5c937). A field-wise JSON diff against feedback5 shows only those four fields.
- docs: DESIGN §24 intro and §24.5 / §24.6 as on feedback4 (the rest of DESIGN.md is feedback5's); ASSETS.md,
  DEPLOY.md, PLAYING.md auto-merged.
- Auto-merged and checked: audio.js (the sim still emits ['leak', id], server/sim/battle/deploy.js), render/style.js,
  textures.js, tiles.js, board3d/materials.js, boardArt.js, tools/assets/*, tools/setup.mjs, extract.py, the tests.

Checks: the PRs' unit tests (audio 22, gameLogic 44, terrain-detail 1, originium-art 4, boardart 15,
board3d-extract 9, asset-network 21, fetch-assets-shrink 4), i18n 9 / i18n-data 8 / i18n-server 4, client-static 313,
docs-consistency 24, kits_layout 6 pass; lint 0 errors (no new warnings), check:imports, typecheck; golden: all 283
scenarios match, nothing regenerated.
2026-10-06 14:41:40 +08:00
sganggs 5bc2669407 feedback4: 阿戈尔's devoured base ATK is a 最终加算, and a marker knocked out during the devour still resolves its own marks (GitHub #165 points 2 and 3, PR #176 by @SpiritedAwayCN; the owner's decision of 2026-10-06)
- PRTS 卫戍协议:盟约 下半/PRTS盟约记录, 阿戈尔 ※: 「该付与来源获得所有标记单位的基础攻击力(最终加算)和阻挡数」. New
  additive mod key atkFinal (PRTS 游戏数据基础 A_f = F_t[(A + D_p)(1 + D_t) + F_p]):
  ATK = ((base + ΣatkFlat) × (1 + ΣatkPct) + ΣatkFinal) × ΠatkMul. The devour buff carries atkFinal instead of atkFlat:
  1000 base ATK, +100 % skill, +2000 devoured → 4000 (was 6000). Every other mod key keeps its meaning.
- 「标记按付与顺序触发【吞噬】效果,目标首次被击倒后解除自身被付与但还未触发的【吞噬】效果」 cancels only the knocked-out
  target's marks: a marker knocked out earlier in the pass now resolves its own marks, the kill credited to it
  (「击杀来源始终为对应标记的付与来源」). A → B → C with B devoured first: C takes B's 5000 and the kill is B's. The 3
  revive slots, the 联防 front-tile devour and the down-at-start rules (DESIGN §24.3 / §24.4) are unchanged.
- Point 5 (the 流失's own source is the target itself) stays open: the hooks still see the marker (DESIGN §24.7).
- Tests: bonds_core — new 最终加算 test (4000; a 直接加算 still scaled; ×0.5 scales the whole), new A → B → C test,
  the dead-marker test rewritten (its mark now lands); feedback3-egir-down, feedback4-egir-unite, feedback3-ulpia-move
  read atkFinal; match/bosshp recomputes ATK with the 最终加算.
- Docs: DESIGN §24.7 (and the §24 intro, §5.2 / §5.3, the §24.4 atkFlat lines marked), SIM.md §3, research 02 §3.6
  and its JSON spec; data/bonds.json regenerated with build-data --offline — only egirShip.spec.algorithm[2] / [3]
  changed.
- Golden: bond-egirShip-high (the devourers 深巡 / 幽灵鲨 / 斯卡蒂 no longer scale the devoured ATK by their ATK +%; the
  fight ends at 89.03 s instead of 74.03 s), coop2-ABYSS-10-boosted (from round 8 ai_0 has 3 more 阿戈尔 and 奥术
  layers — 海霓's first-kill trait — and both bots' boss damage moves). The marker rule moves no scenario.
2026-10-06 14:39:32 +08:00
sganggs 4f870013c7 feedback4: merge master — PR #186 (board textures as WebP), PR #134 (the leak alarm), PR #185 (terrain tap-to-explain, one 活性源石 material); DESIGN §24 intro lists §24.5 / §24.6 after the 0.1.4 sections; golden unchanged (client and tools only) 2026-10-06 14:10:29 +08:00
Cartethyia d582925816 The leak alarm: an enemy entering the blue gate plays the official stage sound (ON_ENEMY_REACHED_EXIT), one alarm per burst (#134)
Battle.leak emits its own 'leak' event, which no sound handled, so every leak was silent; the client now plays battle.ON_ENEMY_REACHED_EXIT (b_ui_alarmenter) with the bank's own one-at-a-time rule, leaks closer than the sound's length sharing one alarm. The manifest gains only audio.sfx.battle.leak. Contributed by @Convey123.
2026-10-06 14:06:43 +08:00
sganggs c5e8d13636 feedback5: merge feedback4 (beb0296) — PR #73 (operator battle voices) ported to the split layout (the voice setting into ui/gameLogic/settings.js, the gameLogic.js shim unchanged; onResult and DetailPanel voice stay in screens/game.js, which needed its store import back; the sim's 'engage' in server/sim/ai.js performAttack, still the one battle/combat.js and devices.js call; plan.mjs / fetch-assets.mjs merged with --add-only, extraOperators and moduleTypes; the 干员语音 slider through t() with English); golden: roster / bonds / fields / standins / diy differ only in events.engage, matches unchanged
Ported by hand:
- public/js/ui/gameLogic.js is a re-export shim on feedback5: #73's DEFAULT_SETTINGS / sanitizeSettings `voice`
  hunk went into public/js/ui/gameLogic/settings.js.
- public/js/screens/game.js: MatchScreen still lives there, so #73's onResult (battleRunner 'result' → resultSpeaker /
  resultVoiceSlot) and `voice=${combat}` on the DetailPanel auto-merged in place. feedback5's game.js no longer
  imported `store` (the split moved its other users), so onResult's store.get() would have thrown into its
  try/catch and the 结算 line would never have played: `store` is imported again. `localAsset` stays dropped.
- public/js/ui/settings.js (i18n phase 1): the slider is `label=${t('干员语音')}`, public/i18n/en.json
  "干员语音": "Operator Voices" (next to Music / Sound Effects).
- tools/assets/plan.mjs: buildPlan takes #73's charword / voiceLang / voiceSlots and feedback5's extraOperators /
  moduleTypes; the template has `modules` and audio { bgm, bossBgm, voice, sfx }. `chars` now includes the 自选 picks,
  so the voice loop's comment says so (the next `npm run assets` plans their lines too).
- tools/fetch-assets.mjs: parseArgs keeps --add-only beside --voice-lang / --voice-all.
- data/assets.json: feedback5's manifest plus #73's audio.voice (120 operators, after bossBgm, the plan order);
  stats files +1680 and bytes +42 041 118 as measured on feedback4, voiceChars 120; the hash is recomputed with
  the generator's contentHash. No other entry changed.
- docs/ASSETS.md: the stats line lists modules and voiceChars. DESIGN §21.30 stays where feedback4 put it; SIM.md's
  'engage' line and docs-consistency's §21.30 test auto-merged (game.js / detailPanel.js still hold the pinned code).
- Auto-merged and checked: audio.js, battle/runner.js emit('result'), components.js mic icon, detailPanel.js voice
  prop, shared/protocol.js EV.ENGAGE, server/sim/ai.js, tools/assets/audio.mjs / cache.mjs, #73's tests.

Where #73 met feedback5 work:
- 补位: a chess fielded as its stand-in shows the stand-in in the detail card, and audio.js keys battle lines by
  UnitInfo spine (the stand-in's model). The 选中干员 line now takes detail.standIn.charId first, so the stand-in
  speaks, not the operator it replaces (test/ui/standin-ui.test.js pins it). A 自选 record is already the pick's.
- The sim's 'engage': stand-in and 自选 units attack through the same performAttack, so it fires for them as well.

Golden regenerated: every moved scenario differs only in events.engage (the new event's count; no gameplay change),
proven by a field-path diff against feedback5:
- roster: all 49; bonds: all 46; fields: all 22; standins: all 10 (the stand-ins engage).
- diy: 101 of 109. Unmoved: diy-010/011/012/092/093 (闪灵, 夜莺, 凯尔希 squads: they only heal, and a heal never
  engages) and diy-077 (薇薇安娜), diy-080/081 (赫德雷), whose picks make no attack in those battles (already so on
  feedback5).
- matches: unchanged.
2026-10-06 04:31:38 +08:00
sganggs beb0296a52 feedback4: merge master — PR #73 (operator battle voices); golden regenerated: every moved roster / bonds / fields scenario differs only in events.engage (the sim's new 'engage' event for the voice slot; no gameplay change), matches unchanged 2026-10-06 01:55:26 +08:00
sganggs 78251f499c feedback5: trigger fixes from the wave-2 kit reports — 「攻击范围向前延伸」 and 「攻击范围朝…扩大」 are attack-range changes (帕拉斯 S2 DEFAULT → ACTIVE_RANGE on her range + 1, 司霆惊蛰 S1 SEARCH → ACTIVE_RANGE on the 3-19: the owner's larger-range rule); UNIT_TRIGGER_CORRECTIONS for a live-client change the text misses — 菲亚梅塔 S1 is DEFAULT since the 2026-08-01 client (PRTS 备注: no +1 range, no flyers); data regenerated offline (only these three skills' triggers in backups.json, chess.json unchanged); golden: only diy moved (diy-060/061/068/069/075/076: those three operators) 2026-10-06 01:53:09 +08:00
Cartethyia 54cdad2602 Operator battle voices: the official combat lines play by voice slot during battles, one at a time (#73)
Deploy, skill and result lines come from the operators' official voice slots; a newer line takes the channel over (stale callbacks of the replaced line no longer release it), and the result line is spoken by an operator of the player's own battle. Contributed by @Convey123.
2026-10-06 01:44:46 +08:00
sganggs 9ce5435373 feedback5: merge W5M — the nine 0.1.4 PRs (#150 #66 #146 #74 #72 #103 #149 #109 #160) and W4A (高台 per the trait, 阿戈尔 revive slots, 联防 devour) ported into the split layout
Conflicts: server/match/bot.js (W4A's comment + W5K's player data view pgd), test/golden/diy.json (regenerated). data/ equals an offline build of the merged tools/build-data.mjs. Golden regenerated on the merged code: against feedback5 the moved roster / bonds / fields / matches / standins scenarios are exactly W5M's per-PR attribution (#160 skill events, #109 deploy-timed lifecycle, #66 灵巧 sources and hidden layers, 83fc408 歌蕾蒂娅 placement), plus coop2-NORMAL-70-diy (fields operators those PRs touch) and diy-051 / diy-052 (events.skill only: Misery's deploy passives, #160); against fb5-merge4 only feedback5's own additions (the diy kits, coop2-NORMAL-70-diy).
2026-10-06 00:33:15 +08:00
sganggs 8287422d6d feedback5: 焰狐龙梓兰 leaves the 自选 pool as a collab operator (MH05, a Monster Hunter 联动寻访 whose team reserve6 names no collab: DIY_EXCLUDED_NUMBER_PREFIXES MH / RS / AM / PS / DD next to the team list — the owner's decision of 2026-10-05; reported by O6) — 71 owned picks; 「攻击范围改为」 is an attack-range change (黑 S3 was SKILL_RANGE, now DEFAULT; 远牙 S3 keeps its own CUSTOM_RANGE row); data and the EN overlay regenerated offline, chess.json unchanged; golden unchanged 2026-10-06 00:16:23 +08:00
sganggs c5c52eed34 feedback5 WM: merge feedback4 at 13808e3 (W4A) — 高台 for every 「可以放置于远程位」 trait holder, the 5-阿戈尔 revive slots, the 联防 devour of whoever stands in front
Ported by hand into the split layout:
- 83fc408 board.js placeClass: the player's loadout no longer counts, as in 83fc408. feedback5's 补位 rule stays: a
  chess the player fields as its stand-in is placed by the stand-in's record (ps.fieldRecord). positionClass(rec)
  takes no module.
- 83fc408 client piecePosition → public/js/ui/gameLogic/placement.js: meleeOnHighGround(rec) on the stand-in or
  chess record, no module. The gameLogic.js shim header and deployedModuleId's comment follow; deployedModuleId is
  kept, still exported and tested.
- 83fc408 PlayerState._placementOf comment → server/match/player/placement.js.
- 83fc408 tools/golden.mjs layout: positionClass without the module, on feedback5's stand-in body (recOf) and in
  diyLayout.
- bonds/core.js (6ab9a06, 8a2e0c7) applied cleanly; it equals 13808e3's plus feedback5's i18n toast. shared/highGround.js
  equals 13808e3's.
- data/: only bonds.json (the 阿戈尔 spec text). node tools/build-data.mjs --offline on the merged tree rewrites all
  15 files byte-identical.

Golden regenerated: 16 scenarios moved, all by 83fc408. Each fields 歌蕾蒂娅 (normal, or elite with HOK-X / no
module), whom tools/golden.mjs now lays out beside the enemy path like a ranged operator instead of on the busiest
melee tile: roster-010, roster-012, roster-029, roster-040, roster-041; bond-deputShip-high, bond-indomShip-low,
bond-preciShip-low, bond-preciShip-high, bond-suntShip-low, bond-swiftShip-high; boss-boss_2-pair, boss-boss_4-pair,
boss-boss_6-pair, hidden-boss_8-pair, hidden-boss_10-pair. This is the list in 83fc408's own message. With the
阿戈尔 commits reverted the digests are the same, so those two move nothing in this corpus. The standins, diy and
matches families are unchanged: no stand-in or 自选 piece there is 歌蕾蒂娅, 崖心 or 见行者.
2026-10-06 00:06:41 +08:00
sganggs b2a438804d feedback5: 「攻击范围增大」 is an attack-range change too (ATTACK_RANGE_CHANGE) — 艾雅法拉 S3 was read as a 技能范围 skill (SKILL_RANGE) and now casts by the owner's ACTIVE_RANGE rule on her enlarged range (reported by O2); data regenerated offline, only her three forms' trigger changed; golden unchanged 2026-10-06 00:05:47 +08:00
sganggs a0a7660287 feedback5 WM: merge feedback4 (896dd5d, the coming 0.1.4) — PRs #150 #66 #146 #74 #72 #103 #149 #109 #160 ported into the split layout
Ported by hand:
- #109 kit hunks → kits/ops (the tier files stay re-export shims): 宴 chess_char_1_18-utage.js, 野鬃
  chess_char_1_19-wildmn.js, 砾 chess_char_2_12-gravel.js, 斯卡蒂 chess_char_3_05-skadi.js, 伊内丝
  chess_char_4_04-ines.js, 缄默德克萨斯 chess_char_4_16-texas2.js, 耀骑士临光 chess_char_6_17-nearl2.js. The +/- lines
  are identical to #109's tier1/2/3/4/6 diffs.
- #109 generic.js against W5D's own-duration passives (一击即退): the deployment window is computed where #109 computes
  it, with W5D's case included. Both forms run #109's duration lifecycle (activateOnDeploy, spType 'none', the
  skill's own mods), and W5D's 冲锋号令 DP start effect is kept.
- #103 room.js through i18n phase 1: t('{name}邀请你加入卫戍协议:盟约【{difficulty}】') with the translated difficulty
  name (the Chinese text is the PR's), plus its English line in public/i18n/en.json.
- data/assets.json: feedback5's manifest plus #72's audio.bgm.combatAlts (after boss, the plan order). files +3 and
  bytes +5 037 595 as measured on feedback4; the hash is recomputed with the generator's contentHash. No other data/
  file changed.
- docs: DESIGN §5.6 (#109's timed deployment paragraph before feedback5's trigger rules; "Permanent passive skills are
  always on"), SIM §7.4 (both generic passive cases).
- Auto-merged and checked: #149 bond-bar fold (still in screens/game.js), #160 skills.js / spine.js, #109 skills.js /
  snapshot.js, #66 addon/battle.js (restTile / isDown live in sim/battle/tiles.js), #150 units.js, #72 audio.js /
  plan.mjs, #74 plan.mjs / docs.

Where the PRs met feedback5 work:
- 突袭 (W5L, GitHub #49): raidPoll counts a skill that needs no SP while it is on. That is a passive, as before, or a
  #109 deploy-timed skill while its window runs. Once the window has ended only the idle trigger is left. Without this
  change 缄默德克萨斯 / 宴 lost #49. research 02 §3.18 says so.
- Tests changed to #109's representation: bonds_addon #49 test (kind 'duration', activateOnDeploy) plus a new test
  (window over → idle trigger only), and standin.test.js's 预备干员 PASSIVE branch (the skill's mods instead of the
  generic:passive buff).

Golden regenerated on the merged code. Attribution: each PR's gameplay change was reverted alone in a scratch copy, and
all three together. With all three reverted the digests equal feedback5's exactly. Every moved scenario was replayed
and fields the unit that explains it.
- #160 only (0.5 s skill events of deploy passives; events.skill only): roster-001/006/025/049, bond-arcaneShip-low,
  bond-emptyShip-high, bond-investShip-high, bond-kjeragShip-low, bond-siracusaShip-low/high, bond-victoriaShip-low,
  bond-yanShip-high, boss-boss_2/4/6-solo, hidden-boss_8/10-solo, standin-03/05/08/10, diy-005/006 (普罗旺斯 S1,
  琳琅诗怀雅 S1/S2, 星熊 S2, 凯瑟琳 S1, Misery S3 as stand-in / 自选 prototype).
- #109 only (deploy-timed skill state and lifecycle; 突袭 技能就绪 limited to the window; generic 一击即退):
  roster-003/004/005/021/023/024/039/041/042/046/047, bond-egirShip-high, bond-indomShip-high,
  bond-kazimierzShip-low/high, hidden-boss_8-pair, standin-01, coop4-FUNNY-6, coop2-NORMAL-8-serverrun.
- #66 only (灵巧's aura stays only for a killed member, from its body tile): roster-028, coop2-NORMAL-3,
  coop3-ABYSS-5, coop4-ABYSS-7, solo-HARD-9-boosted.
- #109 + #160: roster-002, roster-029, bond-investShip-low, bond-raidShip-low/high, bond-suntShip-low,
  bond-visiShip-high.
- #66 + #160: roster-008, roster-027.
- #66 + #109: roster-022, roster-040, bond-indomShip-low, boss-boss_2/4/6-pair, hidden-boss_10-pair, standin-06,
  coop2-HARD-4, coop2-NORMAL-14-standins.
- all three: roster-010, roster-012, roster-031.
69 scenarios. Every other digest is unchanged.
2026-10-05 23:59:05 +08:00
sganggs 8a2e0c7e7f feedback4: 联防 阿戈尔 devours the operator in front whoever owns it, a teammate's standing or down operator too
The owner's decision of 2026-10-05 (GitHub #140 comment 4, #105): PRTS 「战斗开始时依次吞噬身前一格干员」 has no
own-side limit. The devour's front-tile lookup took only the player's own operators.

- devour opAt: the operator on the front tile whoever owns it — a living one, else one lying there since the 联防
  start (carry.down, forced out). The marker gains its base ATK and block count exactly as in a solo battle; a mark
  on a standing teammate's operator resolves (5000 物理流失 less DEF, credited to the marker, the knock-out is its
  owner's: their revive slots), a mark on a down one resolves nothing (it stays forced out, §19.3 kept).
- The chain goes on through a marked 阿戈尔 by S.isMember: for own operators exactly the members, as before; a
  teammate's by its own bonds ([ASSUMED]).
- 联防 still adds no layers (layerGainsEnabled false, unchanged).
- On the current maps the two 联防 helpers stand on board cols 3-9 and 11-17 with the road column 10 between them,
  so a teammate's operator is never in front at the battle start; the tests place one on col 10. The own-board case
  (food down since the own combat) is verified through unite.js's real carry: 联防 gives exactly the own combat's
  atkFlat / blockCnt.
- research 02 §3.6 and its JSON spec; data/bonds.json regenerated offline (only egirShip.spec.assumed[1] changed).
- Tests: test/content/feedback4-egir-unite.test.js (teammate's living and down operator, solo parity of ATK / block /
  物理流失, no layers, the chain through a teammate's 阿戈尔 and her owner's 5-tier slot, the own-board case through
  uniteBattleOpts). The teammate cases fail on the previous code.

Golden: no scenario moved (no corpus field puts a teammate's operator in front of an 阿戈尔).
2026-10-05 23:34:33 +08:00
sganggs 6ab9a0648c feedback4: the 5-阿戈尔 revives go to the first 3 members knocked out, in knock-out order
The owner's decision of 2026-10-05, following players' reports (GitHub #105 comment 2, #140 comment 3 and a report
the owner quoted): 0.1.3 fixed the 3 holders at battle start by position, so uneaten front members held slots they
never used and whatever the food chain ate only 3 阿戈尔 stood. Now:

- A member operator's first knock-out ('killed') of the battle takes a slot while fewer than 3 are spent, the
  battle-start devour included; it redeploys at once (free) where it lies. A member never knocked out takes none, so
  survivors = uneaten + 3. Later knock-outs never take one; a first knock-out after the 3 are gone stays down.
- One slot at most per member however many 阿戈尔 mark it: the devour still cancels a target's pending marks once
  it is knocked out. The report that each devour of the same operator wastes a revive was checked and not
  reproduced on 0.1.3 (the extra marks were already cancelled); the fixed holders were what players saw.
- An operator's own save uses no slot: every kit / item / band saver is a fatal hook (斯卡蒂's DRE-Y, M3茧甲,
  坚固维式重锤, 埃芒加德) and runs before the death hook, so the unit is not knocked out; the 0.1.3 rule that an
  in-place save spends the holder's revive is gone ([ASSUMED] for 埃芒加德, a band, treated like M3茧甲).
- 联防: a member entering down is not knocked out (no slot) until it stands up and falls again. 埃芒加德's own
  "first 3 knock-downs" rule is a separate hook and is unchanged.
- research 02 §3.6 and its JSON spec; data/bonds.json regenerated offline (only egirShip.spec.tiers[2].effect
  changed).
- Tests (bonds_core): knock-out order wherever members stand; the devour chain with 6 members (survivors = 2 + 3,
  normal and 联防); a member marked three times spends one slot; 斯卡蒂's DRE-Y, M3茧甲 and 埃芒加德 saves use none;
  联防 / 调和 / mirrored boss player. All five fail on 0.1.3's code.

Golden: no scenario moved (no corpus battle has a 5-阿戈尔 knock-out whose slot differs; bond-egirShip-high runs at
999 layers and nobody falls to the devour).
2026-10-05 23:28:43 +08:00
sganggs 742f478526 feedback5: every 自选 重装 casts with an enemy in range like the 重装 stand-ins — a backups.json TANK unit's MANUAL skill on the class row's TAKE_DAMAGE becomes DEFAULT, ACTIVE_RANGE when its running range is larger (森蚺, 涤火杰西卡, 斩业星熊, 年, 黍, 斥罪, 机械师, 珊比; 48 skill forms; rawRule keeps TAKE_DAMAGE), the owner's decision of 2026-10-05; data regenerated offline, only backups.json changed; golden unchanged (no 自选 重装 has a kit yet) 2026-10-05 23:00:17 +08:00
sganggs a611cb013c feedback5: merge W5I — real i18n phase 1: shared/i18n.js (Chinese msgids, params, data names), English game texts from the official EN client data (data/i18n/en.json), the 中文 / English switch on the title screen and in 设置, title / lobby / room / settings converted, server toasts and ticker lines with msgid + params, tools/i18n.mjs, docs/I18N.md (PR #70's translations by @YuriRestia reused, credited)
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.
2026-10-05 22:57:53 +08:00
Convey123 777aee7e24 Battle BGM follows the round: rounds 1–7 无畏者 (bat_kazimierz2_2), rounds 8–13 骑士之日 (bat_kazimierz2_1), as in the official level data (#72)
联防 keeps the escaped stage's corrosion track (#110). Contributed by @Convey123.
2026-10-05 22:35:19 +08:00
sganggs e596478bef feedback5 WI: English game texts from the official EN client data (data/i18n/en.json) and the i18n core
shared/i18n.js: gettext-style t() / tc() with the Chinese text as msgid (untranslated -> Chinese), {name},
{0} and {n|one|other} params, lists, game-data names (dn / tName), setLang / getLang / onLangChange, and the
server message wire format (msg -> wireMessage -> translateWire). Chinese stays the default, English is a
switch (the owner's decision of 2026-10-05).

tools/build-i18n.mjs (build-data.mjs untouched) walks the zh_CN and EN tables side by side and writes
data/i18n/en.json: the official English of every data/*.json text by record id and field. Blackboard texts
are matched by template (the remake's numbers read back and filled into the English template); stage names
are composed; what the EN client lacks falls back to tools/i18n/fallback-remake.json and fallback-pr70.json
(PR #70 by @YuriRestia). Default EN source ArknightsAssets/ArknightsGamedata (has act2autochess);
Kengxxiao/ArknightsGameData_YoStar is archived (2025-11) and has no 盟约 season (--source yostar).
Coverage: 14 922 / 14 931 texts (99.9 %), 99.3 % official; only 9 CN-only 甄选 operator names stay Chinese.

shared/i18nData.js: the overlay format (index-aligned leaves + a 2-char hash of each Chinese source, so a
stale leaf is never applied). public/js/data.js: setLocale('en') downloads the overlay once; get / lookup /
list return localized copies; getRaw keeps the Chinese file; localeName maps names.
2026-10-05 20:19:43 +08:00
sganggs 9db99aad00 feedback5 WH: 自选 art — fetch-assets plans every data/backups.json unit research 07 lacks (the 72 owned 6★: avatar / portrait with E2, battle Spine Front / Back, every skill icon and skill sound, sub-profession icon, from 07's URL patterns), the 自选 summons as tokens, and a type icon per module of chess.json / backups.json (manifest modules, the loadout tiles' fallback); --add-only downloads only missing files and never rewrites, re-downloads or deletes an existing one (worktrees sharing public/assets)
Downloaded 1,295 files, 127.2 MB (133,426,089 bytes) into the shared public/assets; no existing file changed or removed. data/assets.json only gains entries: 72 chars (all with avatar, portrait, Front and Back Spine), 38 tokens (avatars; no Spine — upstream has skin variants only), 208 skill icons, 10 sub-profession icons, 102 SFX units, 131 module type icons.
2026-10-05 20:13:40 +08:00
sganggs c278eafa52 feedback5 WH: 自选 data — data/backups.json gets the 72 owned 6★ picks as units (a form at every DIY slot status: E2 Lv1 rank 4 without a module, E2 Lv60 rank 7 with every module at stage 1 and at stage 3; all three skills, talents, trait, ranges), their summons (backups.json tokens, variants keyed by owner form <charId>@<statusKey>) and each prototype's locked skill / module per tier (diy.locked: its 补位 rows' selection, 预备干员-医疗 S3 assumed); the 6 collab operators (teams rainbow / action4 / mujica / sees / laios) leave the data and the pool, the owner's decision of 2026-10-05
backups.json 171,616 → 1,884,868 bytes (gzip ≈ 142 KB); every other data file is byte-identical (buildTokens' record head moved into summonRecord, shared with the DIY summons). No golden scenario moved (none fields a 自选 piece).
2026-10-05 19:37:49 +08:00
sganggs 17d9960fb4 feedback5: merge W5F — ACTIVE_RANGE also replaces SEARCH rows and covers 深巡 S2, 纯烬艾雅法拉 S1 at full SP, 伺夜 S1 only with his pack on the field
Golden regenerated on the merged code (only test/golden/matches.json conflicted): against feedback5 it moves exactly W5F's scenarios (roster-001/002/016/026/027/028/038/041/042/044, bond-egirShip-low, bond-suntShip-high, boss-boss_2-pair, boss-boss_6-pair, solo-NORMAL-1, coop2-NORMAL-3, coop4-FUNNY-6, coop2-NORMAL-8-serverrun); against fb5-bugs3 exactly W5E's (unite-1/2 and the 8 match scenarios of the 联防 map and the full-hand rule). data/ equals an offline build of the merged tools/build-data.mjs.
2026-10-05 19:05:06 +08:00