Commit Graph
68 Commits
Author SHA1 Message Date
sganggs 25b2a87b26 feedback5 WAD: language packs are the first type of content packs — a language is a file in a language folder (public/i18n/<code>.json, optional data/i18n/<code>.json) or a pack folder (packs/<id>/pack.json); the server lists them live at /packs/index.json (server/packs.js), the menu shows every pack, strings fall back pack → base → declared fallback (English) → Chinese, broken placeholders are skipped, plurals follow Intl.PluralRules (English unchanged), English carries a _meta manifest like any pack; tools: i18n.mjs template / check <code>, packs.mjs list / index, build-i18n --lang ja|ko|zh-TW (the official JP / KR / TW clients); the release zip ships packs/ and a generated packs/index.json; golden unchanged 2026-10-07 02:38:22 +08:00
sganggs f2bec76176 feedback5 WAC: players rebind the in-match shortcuts in 设置 → 快捷键 (community request 「快捷键可不可以自己设置」, the owner's decision of 2026-10-07)
- The settings (sp.pref.settings) hold a key map, action → KeyboardEvent.code, for refresh / freeze / level-up /
  retreat / sell / ready (the ready key also pauses a solo battle); the defaults are the 0.1.4 keys R F D Q X Space,
  so nothing changes for a player who never opens the section. sanitizeSettings keeps each saved key that may be a
  shortcut and gives any other its default; not a map, or two actions on one key, gives the defaults.
- One lookup, actionForKey (the character typed first, then the physical key: the 0.1.4 code-or-key match, identical
  for the defaults), serves shortcutFor(e, keys) in game.js and the facing wheel; the modifier, text-field, repeat and
  overlay guards are unchanged. The shop bar, ready / pause and underframe key hints read the map.
- 设置 → 快捷键 (the one SettingsModal, in a match and on the title screen): each action with its key; click or Enter
  waits, the next key binds; Esc cancels (the dialog stays open), Tab leaves; Ctrl / Alt / ⌘ combinations and the
  interface keys (Esc, Tab, Enter, arrows, F1–F12) are refused; a key in use is swapped and the line under the list
  names both; 恢复默认. Esc stays fixed. Touch-only devices: a keyboard note and a folded list; one column under 768 px.
- Tests: test/ui/feedback5-hotkeys.test.js (map, sanitising, conflicts, lookup, capture, handlers and hints with a
  rebound key), test/ui/gameLogic.test.js (sanitizeSettings now returns keys), test/ui/hotkeys.e2e.test.js (SP_E2E=1:
  撤退 rebound to W with the keyboard only in the mock match; phone width).

Golden unchanged (client UI only).
2026-10-07 01:56:19 +08:00
sganggs 6b08971203 feedback5: the public dev branch is marked as a development build (the owner's decision of 2026-10-06): version 0.2.0-dev (title footer, boot banner, /healthz), a README warning under the title (unstable, not for public servers, how to report, shared browser storage, no release zips), a visible 开发版 tag on the title screen (zh / en), a boot-banner warning for hosts, and tools/package.mjs refuses a dev version without --allow-dev; test/version.test.js checks all of it 2026-10-06 19:05:40 +08:00
sganggs 250ad254a6 feedback5 W5S: a language switch in the middle of a screen re-reads the game texts — useGameData(), the 干员调配 / 干员持有 rosters, the strategy list and the open detail card list data.locale() among their memo dependencies (they kept the records of the language they were built in); docs/I18N.md 2026-10-06 18:35:33 +08:00
sganggs 0025ceac16 feedback5 W5S: i18n phase 2, match texts the server sends — a 机变 card's and an effect entry's name and description, the draft's name, a reward offer's label and the result title are shown from the localized data when the server's Chinese text is the data record's own (data.lookupRaw + ui/lang.js sentText; names through tName), a text the server reworded (a multi-round 悬赏) stays as sent; Chinese output unchanged by construction (in Chinese the localized record is the raw one); test/i18n-server.test.js covers it 2026-10-06 18:27:13 +08:00
sganggs 647bd23ef2 feedback5 W5S: i18n phase 2, ui — HUD, shop bar, detail card, bond strip and popup, 机变 overlay, enemy drawer, team panel, under-frame, guide, emotes, match chrome, connection banner, components, the loadout / roster models, the store, net and observe texts through t() / tParts() (whole sentences where a number or a name sat inside), msgid tables marked N_() and translated where shown, the shop's refusal reasons compared through t(); tools/i18n.mjs reads aliased imports (t as tr); English for the 59 new msgids; Chinese output unchanged (zh render proof; five rewrites checked by hand, listed in the notes) 2026-10-06 18:09:50 +08:00
sganggs 166c4fbbfb feedback5 W5S: i18n phase 2, screens — band draft, briefing, game screen and its overlays, 干员调配 / 干员持有, result through t() / tParts() (sentences with a number or a <strong> inside stay whole), English for the 58 new msgids; Chinese output unchanged (zh render proof: only ownership's 阶 heading is a text node instead of a <span>, same text and layout) 2026-10-06 17:52:19 +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 d31de31e31 feedback5 WE2: the briefing and the 本局信息 views name a 自选 piece the match leaves out of the shop (#11)
m.private.diyBanned (every bond of the operator off this match) is shown under 本局禁用干员
in the INFO_CHECK briefing, the strategy draft's 本局信息 dialog and the in-game 本局信息
tab: ui/gameLogic/diy.js diyBannedPieces, ui/matchInfo.js matchInfoModel diyBanned +
DiyBannedLine (t() with English in public/i18n/en.json). Client only; golden unchanged.
2026-10-06 15:48:32 +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 3af5b847bd feedback4: merge master — PR #183 (a settings button on the title / login screen); golden unchanged (client only) 2026-10-06 14:35:46 +08:00
MayAsteroidea 19a89085a4 The title / login screen has a settings button: it opens the in-match settings dialog (local preferences only) (#183)
One gear button in the title connection row opens the existing SettingsModal; it reads and writes the same local preferences and touches no server state. Contributed by @zasx77771.
2026-10-06 14:35:23 +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 f6f5ed50cd A special terrain tile explains itself on a tap, and 活性源石 is one material on both boards (GitHub #184) (#185)
Tapping a 活性源石 / 沼泽 / 深水区 / 排气格栅 / gate / teleport tile opens a read-only card with that stage's own terrain numbers (the boss-prep board picks the board tile once, through prepXf.toBoard); the 2D atlas cell and the 3D shader now share one 活性源石 palette and recipe and draw to the tile edge. Contributed by @Convey123.
2026-10-06 14:08:18 +08:00
sganggs af466c6cfe feedback5: merge feedback4 (9d563424) — the settlement line (结算) is spoken again; in 0.2.0 the speaker is the record the own piece shows (screens/game.js ownShown: a 补位 piece's stand-in with its own voice, a filled 自选 slot's operator, else the chess record's charId); golden unchanged (client only) 2026-10-06 04:39:17 +08:00
sganggs 9d56342494 feedback4: the settlement line (结算) is spoken again — a battle result's unitsEnd names chess ids (chess_char_*), so the game screen maps them to the operator through the chess record's charId (resultSpeaker charOf); as merged in #73 the speaker took char_* ids only and no battle ever said its line (found while porting #73 to 0.2.0); test runs a real battle result through it; golden unchanged (client only) 2026-10-06 04:37:26 +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 0e8004a382 feedback5 WN: 补位 display follows the official game (the owner's recall of 2026-10-06) — a chess the player does not own is shown as its stand-in wherever the player's copy appears: shop and reward cards (the stand-in's portrait, name, class and backup skill; the chess's bonds, tier and price; a small 「替补」 mark), the hand and 临时整备区 pieces (the stand-in's model, a 「替补」 tag; the board already was), the detail card (the stand-in's portrait, name, skill, talents and module with the chess's bonds and 特质, 「银灰的替补」 under the name [ASSUMED]), bond popups (own and a teammate's by UnitInfo standInFor through ownerBoard / ownerOps), the result lineup (m.result standInFor), prep scouting of the bench, the equip-replace dialog, the elite and gift tickers; rules unchanged (bonds, price, tier, merges, pools and 特质 stay the chess's); new strings through t() with English; tests updated (standin-ui 15, standin-ownership 7, watch-bonds wiring) and the e2e now ends on the result screen (fastServer SP_FINISH_AFTER); golden unchanged 2026-10-06 02:40: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
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 0def7babfd feedback5 WK: 自选编队 in the browser — the 干员调配 overlay's third tab 自选编队 (5阶 ×2, 6阶 ×2 slots; the slot picker lists the kitted picks of the tier with avatar, name, class, derived bonds and a 原型 / 已持有 tag; an owned pick chooses its skill and module — 集成战略 modules left out —, a prototype shows its locked skill; sp.pref.diy, export / import, 全部清空, synced as room.diy through the shared engine, welcome.diyKitted kept), the operator on shop / reward cards, own pieces (「自选」 tag, its model on bench and board), the detail card (pick's skill and module), placement and range previews, bond popups (own and a teammate's 自选 member), the result screen; new UI strings through t() with English; the e2e fast server's SP_START_LEVEL hook
Bonds of 自选 pieces checked everywhere membership is decided (the owner's reminder of 2026-10-05): the match's counts through the player's data view (煌 in 炎 and 维多利亚, one member per slot, 绝技 elites), the sim's bond effects through def.bonds (叙拉古 on deployment, 协防 ×1.2 / elite ×1.4), the client's strip, popups, cards and detail card. Golden unchanged.
Tests: test/ui/diy-ui.test.js (15), test/match/diy-bonds.test.js (4), test/i18n.test.js pins screens/diy.js; test/ui/diy.e2e.test.js (SP_E2E=1, run once: pass).
2026-10-06 00:13:31 +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 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
sganggs 02b76989fa feedback4: merge master — PRs #150 #66 #146 #74 #72 #103 #149 #109 #160
Golden regenerated on the merged code and attributed by reverting each gameplay PR in a scratch tree: #66 alone moves the fights of roster-008/010/012/022/027/028/031/040, the boss pairs (boss_2/4/6, hidden boss_10) and the matches coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, solo-HARD-9-boosted (灵巧's aura no longer comes from merchant exits and retreats; hidden-layer bonds keep their internal layers); #109 changes the skill state of its deploy-timed skills (units / hooks / status events / snapshots); #160 adds the 0.5 s skill events of deploy passives (events.skill only in roster-001/006/025/049). The remaining scenarios listed by the full regenerate are those three effects together.
2026-10-05 22:41:02 +08:00
Alsan Jayson ede8e7e773 The bond bar can be folded, so the board stays tappable on phones (#149)
The fold state stays for the match; Space does not ready or pause at the same time. Closes #142. Contributed by @CELLOPOI.
2026-10-05 22:36:15 +08:00
龙千尘 94b6f6ac14 The copied invite link carries an invitation line: 「{name}邀请你加入卫戍协议:盟约【{difficulty}】」 (#103)
Only the clipboard text changes. Contributed by @LongQianChen.
2026-10-05 22:36:01 +08:00
sganggs a12a539e4d feedback5 WI: UI strings through t() with an English switch on the title screen and in 设置; pilot screens converted
public/i18n/en.json: Chinese msgid -> English, seeded from PR #70 (@YuriRestia, credited) for every Chinese
literal of the current code (≈830 entries; 966 of the 1 036 literals left for phase 2 already translated),
completed by hand in the official EN terms. public/js/ui/lang.js: boot language (?lang=en|zh, then
localStorage sp.pref.lang, else Chinese; the browser language is not consulted), switchLang, useLang,
LangToggle; main.js renders after initLang and re-renders in place on a switch, and translates server
texts (m.toast / m.ticker frames, room.closed reasons, error codes via ui/toasts.js describeError).
Converted: title screen (English title, wordmark hidden), lobby, room, settings (语言 row, On/Off,
quality, hotkeys), the shell (crash screen, toasts) and DifficultyTag.

tools/i18n.mjs: extract (msgids + untranslated literals per file), check (msgids missing from en.json),
codemod (html`` text / attributes / strings / template literals -> t(), nested strings, N_ for module
level, i18n-ignore lines), seed (fill en.json from a msgid map).
2026-10-05 20:19:58 +08:00
sganggs d5a4e61fbb feedback5 WG: 补位 detail cards only for the player's own cards (a teammate's bond popup member and the banned list show the chess as it is); the DOM fallback field draws a board stand-in with its avatar 2026-10-05 19:52:09 +08:00
sganggs 4b57f8120c feedback5 WG: 补位 in the browser — the 干员持有 tab next to 干员调配 (the 53 NORMAL chess by tier, operator → stand-in, a switch each, 全部持有, export / import, localStorage + room.ownership through the shared sync engine), 「替补:X」 on shop / reward cards and own prep pieces, the stand-in's model on the board, its body on the detail card (own pieces, cards, any unit with standInFor), placement and ranges by the stand-in 2026-10-05 19:34:52 +08:00
sganggs 7d20695adf feedback5: merge R5 — client split: ui/gameLogic/*, render/fx/*, render/app/* (prelude), screens/game/* (same exports)
# Conflicts:
#	.git-blame-ignore-revs
#	public/js/ui/gameLogic.js
2026-10-05 18:38:43 +08:00
sganggs 8a43624935 feedback5 WE: 联防 is played on its own map, the escaped template level (GitHub #41, item 3) — act2autochess constData escapedBattleTemplateMapSinglePlayer / MultiPlayer = level_act1autochess_escaped_single / _multi name the level of the 联防 battle, and its map is the field: two road halves (cols 3–9 and 11–17, rows 9–12) joined at col 10, no water, crates or devices (until 0.1.3 the round's stage, opened to both halves). tools/build-data.mjs builds both into data/stages.json after the 11 battle stages (kind 'unite', helpers 1 / 2, weight 0, inactive, no modes, the name 联防阵地(N名玩家) the remake's own label [ASSUMED]; validation ties config.unite.templates to them); regenerated offline, a JSON compare shows only the two added records. unite.js uniteStageId picks the map of the helper count (escaped_single's routes enter at col 10, escaped_multi's at col 18 through (9,10)); match/unitePhase.js fields the 联防 battle on it (the round's stage only with data that lacks it); each helper on its prep tiles, the first of two shifted 8 columns onto the right half as before (the official halves; player_map_lr_offset 8). Client: screens/game.js draws the shown field's own map and restores the board's on leaving it (a 2-line change in a file of the R5 split; render/app.js enterBattle already loads m.field stageId); the dev mock's 联防 follows. The 联防 BGM (PR #110) is unchanged. Tests: test/match/feedback5-unite-map.test.js (both combat modes, 1 and 2 helpers, degraded data), test/data.test.js, test/render/tiles.test.js. Docs: DATA §12, META §4, DESIGN §3, PLAYING §5. Golden: fields unite-1 / unite-2 (the battle's stage → the 联防 map) and the 6 co-op match scenarios with a 联防 round (coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun); roster / bonds unchanged (tools/golden.mjs leaves the 联防 maps out of its stage list) 2026-10-05 18:17:08 +08:00
sganggs dd07dc1ec1 feedback5 R5: split the match screen's standalone pieces out
The match-ended plate, the solo pause plate, the highlight styles and
keepEarly now live in public/js/screens/game/*.js. game.js stays the screen
entry. MatchScreen stays one component. Imports only those pieces used moved
with them. Two source pins follow the lines that moved.
2026-10-05 17:24:20 +08:00
sganggs d9750d8aba feedback5 R2: docs and comments follow the split — DESIGN §2 and META list server/match/match/ and server/match/player/ (the classes: constructor + method install), research 09 / 11 name the new homes (Match.watch in match/watch.js, the settle in match/settle.js, the facing changes in player/placement.js / items.js), code comments that pointed at moved code say where it lives now (shared constants, client bandDraft / observe / result / hud / teamPanel / underframe / game-mock, test comments); .git-blame-ignore-revs lists the two pure-move commits. DESIGN §17–§23 history citations, the "before" column and the proposal of research 11, tools/build-data.mjs (Match.js still exports BAND_TURN_SECONDS) and public/js/ui/gameLogic.js (in the client stream R5) are left as they are 2026-10-05 17:18:22 +08:00
sganggs f2440d4fd7 feedback4: merge master — PR #129 (watching a teammate shows hand, temp, equipment, strategy, effects) 2026-10-05 16:47:54 +08:00
Luo Xingyu bce182703b Watching a teammate shows their hand, 临时整备区, equipment, strategy and effects (#129)
The prep scout of another player's board carries their 整备区 hand and 临时整备区 as units on the bench rows (like the own prep bench), the equipped items of their operators, their strategy in the drawer and their effects column (the official mode shows a teammate's hand too). The scout signature includes the hand / temp slots, so a moved card refreshes the watcher. Closes #128. Contributed by @2321Robin.
2026-10-05 16:47:33 +08:00
sganggs e1de092b74 feedback4: the lobby imports ERR, so a failed 观战 join shows its toast instead of throwing (PR #119's error path; found by the 0.2.0 lint) 2026-10-05 16:32:44 +08:00
saltfish 7bea774f01 The 观战 and 加入 buttons send the typed key, not the click event (#119)
Preact hands the click event to onClick handlers, so the 0.1.3 spectate button normalised an element into the key "OBJE" and every spectate request failed. codeArg() takes only a string key and otherwise falls back to the input field; spectate errors now say what went wrong; a recent-key chip fills the field instead of joining at once. Contributed by @salt-fishes.
2026-10-05 14:20:08 +08:00
Chun'an Shi 3f05c65dec Q / X hotkeys retreat and sell the selected operator in prep (#114)
Q retreats and X sells the selected operator during prep, through the same buttons (never an item, never while typing in a field); the buttons show [Q] / [X]. Contributed by @SpiritedAwayCN.
2026-10-05 14:18:44 +08:00
sganggs 6e59b5e4dd feedback3 WJ: prep watch of another board updates when it changes
A scout stays subscribed to n:<pid> and gets a new prep field when that board moves, including a spectator. A shop change does not push one.
2026-10-04 20:34:32 +08:00
sganggs 8707c161b9 feedback3: merge W3E — two spectator seats per co-op room (GitHub-relayed community request #26; a remake feature): watch like an eliminated player, never act, no private data 2026-10-04 17:57:09 +08:00
sganggs 976475efff feedback3 WE: spectator seats in the browser — 观战 in the lobby, the 观战席 strip in the room (host removes, 入座), watch-only match screen; docs (community report #26) 2026-10-04 17:39:30 +08:00
sganggs 782f918684 feedback3 W3C: room.kick names the confirmed player; a seat that changed hands while the host confirmed is refused (BAD_TARGET) 2026-10-04 17:38:29 +08:00
sganggs a308317041 feedback3 WC: the room's host can remove a human before the match (room.kick), the player is told and may join again 2026-10-04 17:15:21 +08:00
sganggs db57c39c7a feedback3 WC: readying with funds left asks first (the prep end wipes them); never for 坎诺特, at 0 funds or under AI 托管 2026-10-04 17:12:24 +08:00
shanzhaikabi 502f183e43 新增:导出 / 导入干员调配 (#14) 2026-10-04 14:20:02 +08:00
sganggs 4c78ab94e6 feedback2 WL: 干员调配 shows 局内数值 — the chosen skill / module's stats, 攻击范围, 特性 and 天赋, 精锐 first with a 普通 toggle, drawn by the card's own block (GitHub #64) 2026-10-04 02:51:21 +08:00
sganggs ff7ce30907 feedback1 final: merge the strategy draft's 查看禁用盟约与干员 dialog (GitHub issue #8 item 1; one matchInfo component for the briefing, the draft and the in-game 本局信息 tab)
Conflicts resolved keeping both sides:
- bandDraft.js header: the 本局禁用 mark of a strategy tied to a switched-off bond, then the 本局信息 dialog; the code
  merged cleanly (the card's 本局禁用 tag and the detail note sit beside the dialog's state and button).
- draft.css: the 本局禁用 tag / note rules, then the 本局信息 entry and dialog block (its header cites DESIGN §21.28).
- CHANGELOG 0.1.1 界面: the gaps and issue #5 / #8 lines, then the dialog line.
- PLAYING §6: the disabled-bond line says where to look again (draft dialog, in-game 🔍); the 变形同构体 line keeps its
  pairing explanation.
- DESIGN: the branch's "§21.26 Match info in the strategy draft" becomes §21.28; its two §10 references follow.
2026-10-03 18:51:37 +08:00
sganggs c61167e3aa feedback1 final: merge the client fixes for GitHub issues #5 (prep camera follows the folded shop) and #8 item 5 (operators no longer stay placeholders)
Conflicts resolved keeping both sides:
- CHANGELOG 0.1.1 界面: the three gaps lines, then the issue #5 and #8 lines.
- DESIGN: the branch's "§21.26 Client fixes for GitHub issues #5 and #8" follows the gaps' §21.26 as §21.27; its
  references in §9 (setCamera `shop`, the model fallback), §17.1 (the quiet budget) and §19.8 (the collapsed shop)
  now point at §21.27.
2026-10-03 18:49:39 +08:00
sganggs 82c54897c7 feedback1 ui: folding the shop in prep moves the own board to the official shop-collapsed camera (GitHub #5)
While the own prep board is shown and the shop bar is folded, the game asks for the prep camera with shop: false
(left_prepare_camera_param / the boss prepare params) and the view keeps the bench above the folded shop's HUD band:
the higher of the folded tab and the corner buttons' hit areas (hudBands(kind, size, { shop: false }), cornerBand).
Unfolding returns to the shop camera. Never over the pen, a teammate's board or a battle; deferred while a piece is
dragged or its direction is chosen (gameLogic prepCameraFor / foldCamera). Below 768 px of width the corner stays on
one row while the bar is folded. Tile px at row 10: 1920x1080 109.8 -> 130.7, 844x390 35.9 -> 44.3,
756x366 31.4 -> 40.1, every bench pad fully pressable.
2026-10-03 16:14:43 +08:00