- CHANGELOG 0.2.1 (2026-10-07): the streams' lines collected (新功能: the update package and MANIFEST.json; 规则(按官方): 联防 on the round's battlefield, 突袭 onto a teammate's half, full potential, the replace dialog for consumables, the 外勤医疗 Touch, 凋亡, 贾维's 拉普兰德, the 频次 器物 / leader summons; 界面: the collapse arrow, 自选 picks in the bond lists, flyers, enemy animations, the Touch card, Esc in the 自选 picker; 联机与大厅: battle messages while a battle loads; 工具与开发: the update build, .gitignore), one intro paragraph. - README: the dev warning under the title removed, the release badge, 「当前版本 0.2.1」; DESIGN's version line names 0.2.1 (§26) as the state described; §26's intro says when the dev line shipped. - Tests: version, docs-consistency, docs-paths, package, i18n-packs, update-package, update-boot 80 / 80.
56 KiB
DESIGN §26 — 0.2.1
Part of DESIGN.md (the index; section numbers are global).
26. 0.2.1 — after the 0.2.0 release
0.2.1 is built on feedback6, branched from the 0.2.0 release (tag v0.2.0) and marked 0.2.1-dev until it shipped (2026-10-07). Its
workstreams branch off it as fb6-*, each merged after review with its golden scenarios named. Each rule was checked
against the official data and PRTS; anything no source backs is marked ASSUMED. Where each is handled: the 突袭
landing on a teammate's half of a shared field → §26.1; 联防 back on the round's battlefield → §26.2; the 自选 picks in
the bond lists → §26.3; a consumable on a full carrier → §26.4; the 外勤医疗 Touch → §26.5; 凋亡 and stored charges →
§26.6; 贾维's 拉普兰德 → §26.7; .gitignore → §26.8; enemy flyers over raised tiles → §26.9; enemy animation states →
§26.10; battle messages while the browser prepares a battle → §26.11; the 频次 器物 and leader summons → §26.12; the
bond strip's collapse arrow → §26.13; every operator at full potential → §26.14; Esc in the 自选 picker → §26.15.
Decisions of the owner: 2026-10-07 — a 突袭 member may land on a teammate's half when both halves of the field are taken (the two-helper 联防 field, the Final Assault / Hidden Core pair field), replacing part of §25.18.1; 乌尔比安's S3 【移动】 keeps his own board (§26.1); 联防 plays on the round's battlefield again, from the owner's knowledge of the official mode and a tournament video (§26.2, withdrawing §25.6.4); 0.2.1 within hours of the report; every operator at full potential, no host setting (§26.14); the 自持有 bonus, multi-language voices and the title particles are not in 0.2.1.
26.1 突袭 may land on a teammate's half when both halves are taken (the owner's decision of 2026-10-07) — sim/battle/players.js onFieldBoard, content/bonds/addon/battle.js raidTile
- Seen: 0.2.0 (§25.18.1) let a member land only on its own board, on every field. Where both halves belong to players — the two-helper 联防 field and a Final Assault / Hidden Core pair field — a member whose target stood on the teammate's half stayed home or took a farther tile of its own half (reproduced: the right player's member with an enemy on the left telin (1,3) of 战场#01 stayed home; on the two-helper 联防 map the left helper's member with an enemy at (10,15) stayed home). After the release the owner decided that such a member may land on the teammate's half.
- Official: the bond text 「保留技力立即再部署至一名地面敌人周围」 names no board, and where the official landing falls among the legal tiles is not in the data (§22.2). PRTS 卫戍协议/帮助 §联防阶段: with two helpers 「两处阵地将前后拼接为一处 阵地,玩家在同一阵地作战」; a pair field is one boss map holding both players' boards (research 09 §1.2, ConvertChessPositionInfoToBossMap). A lone helper's other half and a solo boss field's other half hold no player.
- Now:
Battle.onFieldBoard(r, c)— the tile lies on the board of a player of this field (onOwnBoardof any ofplayers, the players whose units the battle holds; the 联防 right-hand helper's +8 columns and the mirrored right half of a pair are mapped as before) — andraidTiletakes only those tiles: both halves on the two-helper 联防 field and on a pair field; the own half for a lone 联防 helper (escaped_single) and on a solo boss field (nobody stands on the other half; a teammate eliminated before the battle or playing another field opens nothing); never a boss field's hand row 0 or 临时整备区 row 1 (no board covers them). Every other landing rule is kept: ground / ranged tiles (grid.canStand), free tiles (isReservedTile), a blockable tile first for a melee member (§25.11.3), a target only reachable from a forbidden tile skipped (the next candidate is tried, else the member stays); the candidates are still the player's own enemies first (raidTargets). 乌尔比安's S3 【移动】 keepsonOwnBoard(§25.17.2, a separate community report): his landing never takes the teammate's half. This replaces the own-half rule of §25.18.1 on those two fields. - ASSUMED: none beyond §22.2's landing rule (which legal tile the official landing takes); with both halves open the nearest covering tile wins, on whichever half it lies.
- Golden: 8 scenarios, each parting from the parent at the first landing on a teammate's half (checked tick by tick with
the parent instrumented: identical up to that landing): boss-boss_2 / 3 / 4 / 5 / 6-pair, hidden-boss_8 / 10-pair (the
right player's 缄默德克萨斯, 伊内丝, 仇白 or 耀骑士临光 landing on the left player's half, 1–7 times per battle, the first
0.3–39.8 s in) and the bot match coop4-ABYSS-7 (its round-6 two-helper 联防: at 52 s ai_2's 斯卡蒂 lands on ai_1's
half; of the match digest only the two players' damage totals move, 8263 from ai_1 to ai_2). unite-2 is unchanged (its
two landings stay on their own half). Tests:
test/sim/feedback5-raid-own-board.test.js(4 cases; the pair and the two-helper cases fail on the parent; the solo boss field's hand rows, the lone helper and 乌尔比安 beside a teammate are unchanged). Docs: docs/SIM.md (onFieldBoard;onOwnBoardis now 乌尔比安's only).
26.2 「联防阶段地形全消失了」 (GitHub #244, reported by alien-zzy, official footage pointed out by RushFTK): 联防 plays on the round's battlefield again — §25.6.4 withdrawn — match/unitePhase.js _uniteOpts, unite.js, screens/game.js, public/dev/game-mock.js, tools/golden.mjs
- Seen: from 0.2.0 on, every 联防 was an empty two-part road — no walls, fences, water, crates, 活性源石, blowers or band medic. Walkers went straight down row 9 where the round's battlefield walls them off (战场#01 cols 5–7), and the helpers' 机变 terrain cards and the stage's devices did nothing there. In 0.1.x the 联防 was fought on the round's map.
- Official: the owner's first-hand knowledge of the official mode — 「官服保留地形,改回去」 (2026-10-07) — matching the
tournament footage RushFTK linked in #244 (舟戍杯#2: a 联防 on the round's map). Why the level files misled us:
act2autochess constData
escapedBattleTemplateMapSinglePlayer/MultiPlayernamelevel_act1autochess_escaped_single/_multi, and §25.6.4 read that level's map as the 联防 map. But that map is the placeholder grid every wave template level carries: the 39 template levels of the season (the round templates01…07andh01…h08with their_ssolo variants, the trainingtr01/02/04, both escaped levels) hold the identical 19 × 21 road grid, tile for tile, while only the 11 stage levels (m01…m07, act2m01…m04) have terrain and devices. Nobody plays a normal round on its template's road: a template gives a battle its waves, routes and BGM (the 联防 BGM, PR #110, stays), the stage gives the map. - Now:
_uniteOptsfields the 联防 battle onthis.stageIdin both combat modes, as 0.1.4 did: the match stage opened to both halves (GEO.UNITE_RECT, cols 0–20) with its terrain, crates, water, devices, runes and band map characters; the escaped templates' routes (one helper:escaped_singlefrom the middle gate (9,10); two:escaped_multifrom col 18 through (9,10)); each helper's pieces on their prep tiles, the first of two shifted 8 columns onto the right half.unite.js uniteStageIdis removed. The client draws the 联防 on the board's stage again (screens/game.js: 0.1.4'sownView ? ownStage : baseStage— no map switch, nothing to restore on leaving; the helpers see their own 机变 terrain changes, as in 0.1.4); the dev mock's 联防 likewise. Kept from 0.2.0: 突袭 and 乌尔比安's S3 land only on their own board (Battle.onOwnBoard; the raidTile change of another stream is independent of this one), the payee of a bounty death no operator caused (the helper of that half — 战场#04's 活性源石 burns leaked bounty enemies in 联防 again, §25.14.3), the 联防 BGM, the official result box at SETTLE, and everything else since 0.1.4.data/stages.jsonkeeps the twokind: 'unite'records, which no field uses: they are the official level data, the build checks them againstconfig.unite.templates, and the engine tests of the own-board rules use them as a plain two-halves road (乌尔比安 S3, 突袭 landing, the CB4 pins); dropping them changes no game behaviour and would rewrite those tests (one of them belongs to the raidTile stream).tools/build-data.mjs: comments only —node tools/build-data.mjs --offlinereproduces all 15 data/*.json files byte for byte. - Checked against 0.1.4: the 联防 battle input a match builds — stage id, rect, wave id, routes, spawns, helpers' offsets, units and carried state, and the battle made from it (rows, ground / crate grid, device units, special terrain, runes, map characters, ally tiles) — is byte-identical to v0.1.4's in 20 cases (战场#01, #04, #05(下半), #07(下半), #08(下半) × 1 / 2 helpers × both combat modes).
- GitHub #41 item 3 (an enemy pushed back across the spawn onto the other half): back exactly as in 0.1.4 — 野鬃 S2
beside the middle gate pushes 3–4 of
escaped_single's 20 walkers onto the right half (x ≤ 13.2), none off the map, the same numbers on v0.1.4; they walk back through the left half and leak or die. The reporter accepted it 「不影响正常 运行就可以了」. The 0.2.0 road showed the same push (its right half is road too): §25.6.4 never removed it. - ASSUMED: none new (the official 联防 field is the owner's knowledge, not a data reading).
- Golden: fields
unite-1(escaped_single → 战场#02, act1autochess_m02) andunite-2(escaped_multi → 战场#07(上半), act1autochess_m07) — the stages they had in 0.1.4; the matches whose 联防 rounds came out differently on the round's map: coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, coop2-NORMAL-14-standins (now a victory: walls and crates send the leaked walkers round through the helpers' range). coop4-FUNNY-6, coop2-ABYSS-10-boosted and coop2-NORMAL-70-diy play 联防 rounds too, with the same digests on both fields. roster, bonds, standins, diy unchanged. - Tests:
test/match/feedback5-unite-map.test.jsrewritten — 6 tests: one / two helpers × both combat modes (the match stage, its rows and walls, 战场#01's six crates on both halves, the helpers on their 围墙 prep tiles with the +8 columns, walkers going round the walls, the stage the leaker's spec / a watcher's m.field names), 战场#04's 活性源石 on the field, and the same field with or without the escaped records — all 6 fail on the parent, and with soft asserts every new assertion does.test/e2e/coop.e2e.mjs: the 联防 round's field meta and drawn stage are the match stage.test/data.test.js(the records' test retitled: never the 联防 field). Docs: DATA §12, META §4, PLAYING §5, design/geometry-time.md, I18N, the §25.6.4 pointer.
26.3 The 自选 picks in the bond popup's member list (the owner's report of 2026-10-07) — ui/gameLogic/bonds.js bondMembers, ui/bondStrip.js BondPopup, ui/detailPanel.js resolveDetail
- Seen: the owner's report of 2026-10-07 「自选编队的干员在局内对应盟约展开的名单里没有显示出来」. Reproduced with a
unit test of
bondMembers(m.private.diy holding 推进之王 and 煌) and the popup in headless Chrome: the popup listed every regular member — dimmed while not owned — but a 自选 pick only once a copy was owned (on the board, in the hand or the 临时整备区), so a pick not bought yet, or not offered yet (the tier-5 slots from 调度中心 level 5, the tier-6 from 6), never appeared. Owned pieces did show in every case checked (board, hand, 临时整备区; normal and elite; 煌 in both 炎 and 维多利亚; beside a 补位 stand-in of the same bond), with two smaller faults: an elite on the board drew its row with the elite record (gold frame and golden avatar, unlike a member row, which always draws the base chess), and in a watched teammate's popup a tap on their 自选 row opened the empty 甄选干员 slot's card (resolveDetail read no pick for another player's member card). - Official: neither the excel nor PRTS describes the popup's 自选 rows (PRTS 卫戍协议:盟约 only notes 「禁用盟约有可能 影响自选的支援干员」). The rule follows the owner's report: a 自选 pick belongs to the bonds of its operator like any member.
- Now:
bondMembersadds each pick of m.private.diy (the V and VI slots) to every bond its operator carries (the composed record through the own getter, gameLogic/diy.jsdiyGetter):owned: falseuntil a copy is on the board, in the hand or in the 临时整备区 (then on board / in hand as before),bannedwhen its slot is in m.private.diyBanned (every bond of the operator off this match: it never shows in the shop). One row per slot, drawn as its normal form, with the operator's name and portrait and the 「自选」 tag; a banned pick's tag sits on the portrait's bottom corner, clear of the ✕ and the tier chip. The order rule is kept (on board, owned, tier, id: an unbought tier-5 pick comes after the unowned tier-5 members). The 成员 header (memberHeadCount: the board, plus the hand for a bond that counts it) and 在场 (the server's count) mean what they did — an unowned pick counts for nothing; the header's total includes the picks, as it includes the unowned members. A tap opens the operator's card (the own pick, from m.private.diy). A watched teammate: m.public sends no picks, so their popup still lists only their 自选 pieces on the field on screen (UnitInfodiy, ui/watchBonds.jsownerBoard, as in 0.2.0); such a row now passes the unit's pick on (BondPopuponMember's fourth argument → the detail target'sdiy→resolveDetailcomposes their operator). The briefing and the 本局信息 bond lists show no members (bond discs, banned-member counts, the 本局禁用干员 grid and the diyBanned line), so nothing changes there. - ASSUMED: how an unbought pick looks (dimmed, like an unowned member), how a pick whose bonds are all off looks (the banned member's ✕), and the normal-form portrait for an elite copy (one row per member).
- Golden: unchanged (client only). Tests:
test/ui/feedback6-diy-bond-members.test.js(5 cases, each failing on the parent: the unowned pick listed and dimmed, header 0 / n + 2, its card; owned → on through the hand, the 临时整备区, the board and the elite, one normal-form row; 煌 in 炎 and 维多利亚 beside 风笛's stand-in; the diyBanned pick; the teammate's row opening their 煌, not the slot);test/ui/watch-bonds.test.js(the game screen's onMember wiring, now with the pick).
Optional PLAYING.md sentence for the integrator (the 自选编队 paragraph, after 「和普通棋子一样计入盟约人数」 — not edited here to avoid a conflict on that one-line paragraph): 盟约详情的「成员」一栏也列出你的自选干员(带「自选」标记;还没买到、商店 里还刷不出来时和没有的成员一样显示为灰色)。
26.4 A consume-on-equip item on a full carrier replaces first (GitHub #263, PR #264) — match/player/items.js equip / _takeReplaced, ui/gameLogic/placement.js equipReplaces, match/bot.js itemTarget
- Seen: with both equipment slots of an operator used, a normal 博士投影 dropped on it opened no replace dialog and
silently destroyed the OLDEST equipped item: the client's
itemAttachestreated everyconsume_on_equip*item as slot-free (dropIntent→confirmReplace: false), and the server ran the effect, whoseev.keepre-attached the item through_attach(holder, item)with noreplaceUid— the full row pushed out index 0 (areplaceUidsent anyway was ignored). 盟约之币 and the other immediate consumables (随身身份牌, 紧急调度券, 精打细算玩偶, 简易通讯机, 见钱眼开玩偶, 骑士储蓄罐, 寻呼模块, 信标, 拟态物质, 人事部文档, the golden 博士投影) opened no dialog either and kept both equipped items (reproduced on feedback6783bd30:scratchpad/w6-r21d-notes/repro.mjs; reported by @SpiritedAwayCN with PR #264). - Official: PRTS 卫戍协议/帮助 §装备 (fetched 2026-10-07): 「每名干员最多可佩戴2件装备」, 「达到上限强行佩戴会改为替换装备:
指定一件已佩戴装备替换为将要佩戴的装备,并销毁被指定的装备(可取消以中断替换操作)」 — no exception for the items the data
marks 装备时销毁; research 04's "never take a slot" is a [DATA] reading of
kind, not a waiver of the dialog. The reporter's first-hand account of the official mode: the picked item is destroyed first, then the item resolves, and the operator is left with a free slot. - Now: server —
PlayerState.equip's consume path takes the itemreplaceUidnames (else the oldest, as for every third item) off a full carrier beforeonEquipruns (_takeReplaced), so the effect sees the carrier without it (its bonds: 随身身份牌 / 简易通讯机 / 寻呼模块 / 拟态物质); once the effect went through the item is destroyed (onDestroyreasonreplace) and the slot stays free — the normal 博士投影 takes it (ev.keep) and no longer pushes another item out. A refusal (博士投影 of either quality on an elite:BAD_TARGET 'already elite') puts the item back at its index: nothing is destroyed, the 博士投影 stays in the hand. 信标 destroys its carrier as before: the remaining item returns to the hand, the picked one is gone. Client —equipReplaces(ctx, uid, target)(gameLogic) opens the replace dialog for every item on a full carrier, a consumable too, except where nothing would be replaced: an attaching item that completes an item merge (equipMerges; the server's consume path never merges) and a 博士投影 (its promoting effect keys) on an elite.itemAttachesis gone. Bots — a consumable whose effect is the same on anyone (funds, 紧急调度券, 人事部文档 …) goes to a carrier with a free slot first (itemTarget, PR #264); the targeted ones (博士投影, 随身身份牌, 简易通讯机, 寻呼模块, 拟态物质, 信标) keep their carrier and, when it is full, replace its oldest item (bots send noreplaceUid). - ASSUMED: the order inside the server is "take off → effect → destroy" rather than "destroy → effect": the effect sees
the carrier without the item (as officially), but the replaced item's
onDestroyruns after the effect so a refusal can restore it — no equipment item has anonDestroyeffect forreplace, so nothing observable differs. - Golden:
matchesonly, 10 of 18 move — in each the first point it parts from the parent is a bot's (or the AI 托管 human seat's) targeted consumable resolving on a full carrier, which now destroys the carrier's oldest item (traced equip by equip against the parent:scratchpad/w6-r21d-notes/trace-equips.mjs): solo-FUNNY-1 (r9 ai_0 随身身份牌 → 突袭手雷 destroyed; bossPoolLeft 88535 → 91555), solo-FUNNY-2 (r9 ai_0 寻呼模块; bossPoolLeft 87617 → 107027), solo-HARD-2 (r9 ai_0 随身身份牌, r12 简易通讯机; teamLp 17 → 13), coop2-NORMAL-3 (r9 ai_1 随身身份牌; teamLp 46 → 39), coop3-ABYSS-5 (r11 ai_1 随身身份牌; ai_1's numbers only), coop4-FUNNY-6 (r4 ai_1 随身身份牌), solo-HARD-9-boosted (r14 ai_0 随身身份牌), coop2-ABYSS-10-boosted (r13 ai_0 随身身份牌, r14 ai_1 简易通讯机; teamLp 398 → 529), coop2-NORMAL-14-standins (r9 ai_0 随身身份牌; the match now won: 13 → 14 rounds, teamLp 0 → 12), coop2-NORMAL-70-diy (r14 h_0 随身身份牌; numbers only). The bots' free-slot choice for target-agnostic consumables moves no scenario by itself (six unmoved matches carry such a changed carrier: the effect is the same on anyone). - Tests:
test/match/merge.test.js(a full carrier with the picked item: normal 博士投影 takes the pick's slot, 盟约之币 pays and leaves a free slot, 随身身份牌 gives the carrier's bonds +3, the golden 博士投影 promotes and the other item stays; 博士投影 of both qualities on an elite is refused with every pick and both items kept),test/ui/leftovers.test.js(all 24 consumables open the dialog on a full operator and not on a free one, 博士投影 on an elite does not, an operator in the hand the same; the real engine end to end: the dialog asks for 博士投影 and 盟约之币 and the picked item is the one destroyed),test/ui/gameLogic.test.js(the consumable intent flipped),test/match/bot-smarter.test.js(盟约之币 to a carrier with a free slot) — each new assertion fails on the parent; the dialog e2etest/ui/leftovers.e2e.test.js.
26.5 The 外勤医疗 Touch casts on its 5-2, and the map characters' card is an operator card (GitHub #260, PR #278) — content/tokens.js touchKit, ui/detailPanel.js MapCharDetail / SKILL_TEXT_NOTES / skillTags
- Seen (reported by @CurlyZhu on 0.1.4; PR #278 by @IceCodeNew): the 外勤医疗 strategy's Touch and 预备干员-医疗 showed as
「召唤物」 with base stats only — no skill, no talents. Touch waited for an injured ally inside her own 3-3 before casting
恳切福音 although the skill heals on its 5-2, so a 雷蛇 losing HP to 源石溶剂 five tiles ahead was never healed.
Cause: tools/build-data.mjs builds the map characters as tokens (kind
mapChar,variants: {}; stats, skill, talents and 特性 on the record) andresolveTriggerwidens operators' skills only, so their records keep DEFAULT; TokenDetail read skill and talents from an owner variant only (none) and always printed 召唤物. - Official: PRTS Touch(卫戍协议) — an operator of the mode (医疗 · 医师, 远程位), talents 攫升 / 超脱, S3 恳切福音 手动
触发, 技能范围 5-2, 「攻击距离+2」 corrected by PRTS to 攻击范围扩大, 「不高于一半」 corrected to 低于 (原因 6);
aceffect_band_61: a 预备干员-医疗 appears, replaced by Touch with two elites on the field. The larger-range cast is the
owner's rule of 2026-10-05 (§25.5), which the Touch 补位 already runs by its data (backups.json S3
ACTIVE_RANGEon the 5-2). - Now:
touchKitsets the skill's trigger toACTIVE_RANGEon the skill's own range (the running range, which strictly contains her 3-3): an injured, healable ally inside it casts; a full-HP or 禁疗 one does not (the reserve medic's 治疗强化·β型 has no range of its own and stays DEFAULT). The card (MapCharDetail, taken forkind: 'mapChar'): chip 干员, the class line (医疗 icon, 远程位), the 特性, the four stats (live in battle), the skill row of the operator card (icon, SP type, trigger, initial · cost, duration —skillTags, shared with ChessDetail) with its text, and the talents — all from the record, translated by the data overlays. Checked against the kit: 恳切福音's stored sentence matches what the sim does (ATK +40 %, ×150 %, 2 targets, the 30 % extra heal; the 5-2 = her 3-3 two tiles longer) except 「不高于一半」 — touchGospel boosts strictly below half (PRTS 修正) — so the card keeps the official sentence and adds a line under it (SKILL_TEXT_NOTES, by skill id; on the Touch 补位's S3 card too, whose text reads the same). The triage's "+40 %" doubt does not hold: bb.atk of the map character's Lv10 skill is 0.4, which the kit applies. Enemy targeting is unchanged (the units staykind: 'token'in the sim; no enemy that skips them was named). - ASSUMED: none new (the trigger is the standing owner rule).
- Golden: roster-041 (its strategy Touch: heal 8438 → 17072, heal actions 19 → 27, casts 2 — the same numbers as PR #278; p1 deaths 7 → 4) and matches coop2-NORMAL-14-standins (h_0 plays 外勤医疗: defeat in round 13 → victory, team LP 16) — the two battles with that strategy's Touch; nothing else moved.
- Tests:
test/content/tokens_devices.test.js(Touch on her stage slot, an ally only in the 5-2: no cast at full HP, none while 禁疗, then a cast and a heal — fails on the parent),test/ui/playtest6_summons.test.js(both cards: 干员, 医疗, skill / talents / 特性 text, the 低于 line only under 恳切福音, a summon keeps 召唤物 — fails on the parent). i18n: one new string in en / ja / ko / zh-TW. Docs: docs/SIM.md (ACTIVE_RANGE also set by touchKit).
26.6 An operator's 凋亡 drains the stored charges too (PR #262; #261 its closed duplicate, both by @siruimei07) — sim/damage.js resolveBurst (apoptosis, ally side)
- Seen: the burst's −1 SP/s took only
sk.sp, the SP towards the next charge. A full skill keepssp = costwith every charge stored, so it kept all of them; a one-charge skill stayed ready through the whole 15 s (and cast the moment 静默 ended). Reproduced with 精锐 雪猎 (S2 可充能2次, 14 SP): two charges stored before the burst, two after it. - Official: PRTS 元素 — 凋亡损伤 on a 我方单位: 「立即获得等时长的阻回、静默,该阻回、静默持续期间每秒损失1点技力并受到100点无来源 法术持续伤害」 (15 s); PRTS 技能 §特殊属性 可充能: 「当前技力上限等于该技能技力需求的X倍」 and 「当持有者的技力流失时,充能次数也 会实时降低(但因达到上限产生的额外效果会依然生效)」.
- Now: each second
sk.setSpTotal(max(0, spTotal − 1))— the official 技力 (charges × cost + the partial bar), charges rebuilt silently (no spGain hook, 阻回 untouched); a passive, a skill-less unit, a free skill (spTotal 0) and a running duration / ammo / toggle skill are left alone as before (setSpTotal refuses those too). An effect a full charge produced is not undone (none is modelled on this path). - Golden: 5 moved, each by a burst on an operator with SP stored — 假想敌:管's 凋亡 strikes and the 萨卡兹王庭军's 凋亡 on hit: fields hidden-boss_10-solo (泥岩 loses its stored charge, casts 1 → 0 — the move PR #262 reported; team heals 73194 → 72505), boss-boss_3-solo, hidden-boss_10-pair and diy-058 (later snapshots and event counts only, same results), diy-059 (黍 forms: casts 11 → 5, 6 → 2, 9 → 5, 6 → 1; kills 39 → 47, leaks 57 → 49).
- Tests:
test/content/feedback6-apoptosis-sp.test.js(2: 雪猎's two charges → one after 1 s, none after 15 s; a full one-charge skill not ready after 1 s, −15 技力 in all, no spGain, a running timed skill unchanged — both fail on the parent);test/sim/combat.test.jsoperator bursts unchanged. Docs: docs/SIM.md element table.
26.7 A 拉普兰德 贾维 grants during a refresh waits for the next one (PR #196 by @SpiritedAwayCN, the part 0.2.0 lacks) — match/effectsMeta.js dispatch / _garrisons / refreshPieces
- Seen: 贾维 【团伙行动】 grants a 叙拉古 operator from the dispatch of every 6th manual refresh (band step 2), before the garrisons (step 4), which walked the board and hand live — so a 拉普兰德 granted (or the elite her copy completes) ran her 刷新时 特质 in the refresh that granted her and spent her 「本回合首次主动刷新」 on it (+4 / +8 at once, nothing on the next refresh).
- Official: garrison_123 「<刷新时>若为本回合首次主动刷新…此干员在整备区时也有效」; the players' reading (§21.1, after 0.1.0) 「获得该干员后该回合的首次刷新」 — a refresh she was gained in is not one she was there for. 贾维: 「主动刷新6次调度中 心后获得一名…<叙拉古>干员」.
- Now:
dispatchtakes the board (reading order) and hand chess before the first handler of an onRefresh; the garrison step runs each of them that is still on the board or in the hand, where it stands now; a chess gained during the dispatch does not run, nor one that left (sold, consumed by a merge, sent to the 临时整备区). Other hooks keep the live walk. Not taken from PR #196: copying round counters on a merge and keying the counter by garrison id — 0.2.0 counts a merged elite as a new 拉普兰德 and keeps an in-place promotion's count (the owner's decision, §25.13.3). - ASSUMED: none new.
- Golden: unchanged.
- Tests:
test/match/feedback1-meta.test.js(2: a gifted copy and a gift that completes the elite — nothing in the 6th refresh, +4 / +8 in the 7th, nothing in the 8th; both fail on the parent). Docs: docs/META.md (SERVER_REFRESH_SHOP row), the effectsMeta.js header, garrisons/meta.js.
26.8 .gitignore lists .npm-cache/ (GitHub #258, reported by @qfmc7040) — .gitignore
- Now: a local npm cache (
npm_config_cache=./.npm-cache,npm ci --cache .npm-cache) is ignored like node_modules/ and .cache/; the packages ship tracked files only, so it stays out of them too. Golden: unchanged. Test:test/gitignore.test.js(the entry, and git's own check-ignore; fails on the parent).
26.9 An enemy flyer hovers from the road whatever tile it crosses (GitHub #277 by @FrogThai) — render/units.js UnitView.sync / shadowZ
- Seen (GitHub #277, 飞机经过一格方块时会跟走楼梯一样,有高低差): a flying enemy whose route crosses one high-ground or
forbidden block rose onto it and dropped back, like a step. Cause:
UnitView.syncput every unit except a ground enemy on the tile top under it (groundZ: TILE_H.wall 0.42, forbid 0.3) and addedFLY_HOVERon top, so an enemy flyer followed the tiles (reproduced headless,w6-r21f-notes/flyer-277.mjs: 妖怪 along row 9 of act2autochess_m01 over the '#' at col 6 — body height 1.293 … 1.600 tiles on both boards before, 1.293 … 1.300 after). - Official (docs/research/12, §25.13.5): the fly offset is one constant,
Vector3(0, 0.35, 0)added while flying, ≈ 1.3 tiles over the route; nothing per tile. High ground is where units stand, not a step under a flyer. - Now: every enemy keeps z 0 (ground enemies already did; a flyer hovers
FLY_HOVERabove the road); operators and summons keep their tile top (an operator on high ground stands on it). The flyer's shadow lies on the tile under it (shadowZ, eased like the body), so over a block it is drawn on the block top. The 2D and the 3D board share the camera and the Pixi unit layer, so both are fixed. - ASSUMED: the shadow on the block top under a flyer (no official reference for a shadow over a block).
- Golden: unchanged (client only). Tests:
test/render/unitview.test.jsan enemy flyer crossing a raised tile keeps its height (GitHub #277)(2 cases; the body-height case fails on the parent: it rose 0.415 tiles). Headless screenshots before / after, 2D and 3D:w6-r21f-notes/{before,after}-{2d,3d}-{before,over,past}.png. Docs: ASSETS (flying units), design/client.md, research 12 §6.
26.10 Enemy animation states: per-slot skill clips, two stun spellings, the Run cycle, two 重生 (PR #275 by @xcdoge, client part) — tools/assets/anim-roles.mjs, tools/assets/spine.mjs, render/spine.js _applyRoles, render/units.js setSkillSlot / moveFast / FORMS, render/app.js 'cast'
- Seen (PR #275, a scan of every enemy skeleton against its manifest roles): (1) an enemy's manifest only had skill
index 0 (tools/assets/plan.mjs passes [0]), so a multi-skill boss could show one cast clip — 盐风主教昆图斯 has
Skill_01..04; (2) the stun resolver knew Stun / Stun_Begin / Stun_End only, so 巨大的丑东西 (Stun_1 / Stun_2) and 斩胄之剑 /
破胄之锤 (Dizzy_*) had
stun: nulland froze their clip; (3) 猎狗pro walked on Move_Loop (0.80 s) though its skeleton has Run_Loop (0.53 s) and it is fast (moveSpeed 1.9, 行动速度很快); (4) 巨大的丑东西 and 自在 have a Revive clip and a 重生 in the sim but no FORMS entry, so their 重生 played nothing. - Official: clip names and lengths are the skeletons' own (data/assets.json
animations): 巨大的丑东西 Revive 8.67 s for its 10 sreborn.duration(its self-destruct 2.17 s in), 自在 Revive_01 5.33 s for 5 s. The sim reports a 重生 as a stun (archetypes.jsreborn: flags.stun), so the 重生 form must say what the rest of it holds. - Now: the resolver resolves every numbered skill clip of an enemy after the caller's indices (
numberedSkills, passed by spine.mjs for enemy models; the first index stays the primaryskill; operators keep the pool's indices, 宴's PREFAB_SPINE_ROLES fix of §25.22.9 unchanged); the stun loop is Stun → Stun_1 → Dizzy_Loop (with *_Begin / *_End); a model's own Run cycle is a separaterunrole. The renderer:SpineActor._applyRolescomposes the skill slot's clip, the Run cycle and the form in force (a slot change keeps a form; a form ending keeps the slot); a UnitView of an enemy faster than moveSpeed 1 moves onrun(猎狗pro 1.9, 深池侦察犬 / pro 1.7; 宿主士兵 0.9 and 鸭爵 0.4 keep Move);UnitView.setSkillSlot(render/app.jscase 'cast') swaps an enemy's skill clip. FORMS: 巨大的丑东西reborn= Revive as the change clip, then the 大祭司's Idle_2 held (also as the 重生's stun) →form2Idle_2 / Move_2 / Die / Stun_2, no attack (不进行攻击); 自在reborn= Revive_01 →form2its own clips. data/assets.json regenerated withnode tools/fetch-assets.mjs --offline: 18 enemies gainanims.skills, 6anims.run, 3 a stun clip instead of null (巨大的丑东西, 斩胄之剑, 破胄之锤 — the two 胄 parts are stun-immune, so only 巨大的丑东西's is ever seen); no operator or token entry changed (w6-r21f-notes/manifest-diff.txt). - Not ported: the sim side of (1) —
content/enemies.js castSkillSlotemitting['cast', id, slot]when an ability casts, 昆图斯's ability ids,UnitInfo.skillIndexfor enemies — adds an event, so golden's event counts move (+cast in the two 昆图斯 fields); it is left for the owner (patch:w6-r21f-notes/sim-cast-slot.patch, not applied). Until then the per-slot clips are unused and every boss keeps its primary clip. Note: 昆图斯 has 10 data skills (Tidewater … Doom) and 4 clips, so mapping the G1 / G2 / Doom slots to Skill_04 or the primary needs a check first. The PR's research note (docs/research/13-animation-states.md) is not ported. - ASSUMED: the Run threshold (moveSpeed > 1; the client's own trigger is unknown) — only a model with a Run cycle changes;
巨大的丑东西's
_1/_2clips as its first form / the 大祭司 (the 大祭司 set has no attack clip, matching 不进行攻击); 自在's second form on its first-form clips. - Golden: unchanged (client and asset tools only). Tests:
test/assets.test.js(resolver: numbered enemy skills, Stun_N / Dizzy_*, run role; the manifest: every stun / Run clip has its role, every enemy with numbered skill clips has a clip per slot, 宴 unchanged — 6 cases, all fail on the parent),test/render/forms.test.js(the two 重生; every clip FORMS names exists in its manifest entry),test/render/loadout-skill.test.js(an enemy cast slot swaps the clip, a form kept),test/render/unitview.test.js(moveSpeed 1.9 on Run, 1 on Move, composing with the slot);RENDER_E2E=1 node --test --test-concurrency=1 test/render/models.browser.test.js7/7. Docs: ASSETS (Roles: skills of an enemy, run, stun spellings).
26.11 A b.end, a second b.start, b.pool and the pause reach a battle the browser is still preparing (PR #266 by @siruimei07) — public/js/battle/runner.js (pending, prepare, onEnd / endBattle, onPool, setPaused, clear)
- Seen (PR #266; reproduced on feedback6
783bd30bytest/match/runner-pending.test.js— 11 of its 15 cases fail on the parent): the client runner added ab.start's battle to its map only after the sim module had loaded (ensureSim) and the new Battle had been caught up silently to the field clock (one animation frame per 600-tick slice), and everything else looked only at that map. Cause (runner.js at783bd30):onEnd(line 688,entries.get) dropped ab.endthat came during the load or the catch-up — after a takeover the browser went on reporting as the authority (the server ignores a non-authority's reports, server/match/match/reports.js), and a forced end (the Final Assault'scleared/forced) never ended the battle, so the server waited out its 6 s grace and filled the field with the stats-only stand-in;onStart(line 595) built a second Battle for a secondb.startof the same battle during the catch-up; line 624 (seq !== startSeq) never built an authority that another field'sb.startsuperseded during the load (the deadline then took the field over); lines 663–666 re-registered an authoritative battle whose catch-up the next prep'sclear()had interrupted, and it went on running and reporting into the next round;setPaused(line 506) andonPool(line 680) skipped a battle in catch-up (the paused time was caught up; the boss pool waited for the nextb.pool). No wrong settlement came of it: the server re-simulates or stands in. - Official: not a game rule — the remake's own client / server protocol (§14); nothing to check in the game data.
- Now (§14 Client runner,
b.end): ab.startis registered as pending the moment it arrives (pending,battlenull until built;prepare()loads, builds, catches up and registers it), so what comes for that battle meanwhile is applied to it in arrival order:b.end takeoverclears its authority; a forced / timeoutb.endkeeps the first end reason and ends the Battle as soon as it exists — before any catch-up, or at once mid-catch-up — and an authority reports it then; a secondb.startof the same battle updates the same entry (authority, watch, the field clock while it is not built yet) and never builds a second Battle;b.poolsyncs a built pending boss Battle (one not built yet takes the last pool when built, as before); the solo pause shifts a built pending battle's clock. Ab.startof another field supersedes it: an authority, or a battle the server already ended, is still built and registered (a superseded authority catches up in the background with the bounded fast-forward, as before), a display replica is dropped.clear()(the next prep, the match over, leaving) drops the pending entries; their preparation finds itself gone at its next step and builds, steps, registers and reports nothing — a freshb.startof the same battleId afterwards is a new preparation the old one cannot touch. - ASSUMED A forced / timeout end that arrives before the Battle exists ends it where it stands (tick 0, or wherever the catch-up was) instead of catching up to the field clock first (as the PR does): the server sends those ends only when the Final Assault / Hidden Core ends, and after a forced end it credits no further report (§20.5), so catching up first would only cost time. A superseded display replica the server already ended is kept (registered, finished) so switching back shows it ended (as the PR does).
- Not taken from the PR: its
cancelledguards on a result request already in flight when the round moves on (a timeout may still retry it once; the server ignores a stale battleId — not a lost control message) and its cancellable frame wait (the preparation notices a clear or an end at its next frame). Itsdocs/DESIGN.mdparagraph became the §14 Client runner sentences (DESIGN.md is the index now). - Golden: unchanged (client only). Tests:
test/match/runner-pending.test.js(new, 15: a b.end takeover / forced / takeover-then-forced, a second b.start, the next prep and another field's b.start, each during the load and during the catch-up; a superseded replica, the pause and b.pool during the catch-up); the runner suitestest/match/runner.test.js,clientCombat-review,feedback1-runner-forms,diy-shop,standin-ownership,watch-bonds,test/ui/playtest6-unite.test.js,test/ui/spectate-effects.test.jsunchanged and green.
26.12 The round's enemy effects reach the 频次 器物 and the leaders' summons (PR #272, 感谢 @CXUtk) — content/enemies/archetypes.js times, content/bosses.js summonMods, config enemyScale[r].supplyHp
- Seen: a 器物 (the 14 hit-count keys: 瑞印, 卷轴, 铜镜, 镇纸, 茶器, 矛头, 灯盘 and their _2) needed the data's number
of hits at every difficulty: spawnEnemy multiplied its max HP by the round's hpMul, then the 频次 archetype's
setHits(e, e.def.maxHp)overwrote it with the raw count (reproduced: 青铜镜 30 hits in solo 标准 R1 — data × 0.75 — and in co-op 终极 R15 — data × 4.30 — alike, as a 悬赏 card and as 身观's death spawn). A leader's mid-fight summons — 死亡集群's 妖怪, 刺胄之弹, “裂管之音”/“断弦之音”, 不祥幻影 — spawned with no round mods at all (800 HP / 8 hits / 10 hits / 10000 HP at every difficulty). Reported by @CXUtk in PR #272. - Official: activity_table act2autochess (data/effects.json): the round effects are ENEMY effects on 所有敌人 —
攻坚装备 / II / III
enemy_attribute_mul(ATK ×1.1 / 0.8 / 0.7, HP ×1.2 / 0.8 / 0.75) and 急行军 (speed +15 %) withenemy_exclude= 炎佑 only; 补给线 / 补给线II (HP ×1.08 / ×1.2, no ATK) withenemy_exclude= 炎佑 + exactly the 14 器物 keys. The exclusion only makes sense if a max-HP multiplier changes a hit-count unit's count, so the 器物 take 攻坚装备's HP and not 补给线's. The per-round table (PRTS 卫戍协议:盟约/下半 §开始模拟, user-provided; research 01 A3):hp = hpBase · 1.2^kHp · extra,atk = atkBase · 1.1^kAtk; 攻坚装备 always brings ATK, so kAtk counts its stacks and the HP-only steps 1.2^(kHp − kAtk) are 补给线II,extra1.08 is 补给线 — only co-op 终极 has them (R5–R15). PRTS: "领袖单位于 服务器的生命值加成不受上述加成影响" — only the leader's HP (the server pool) is exempt. 炎佑 is a bond ally in this remake (spawnYanyou), never an enemy (waves.js skips it in templates): its exclusion needed no change. - Now: build-data writes
enemyScale[r].supplyHp= 1.2^(kHp − kAtk) · extra when ≠ 1 (co-op 终极 R5 1.2, R6 1.08, R7 1.44, R8–R11 1.728, R12 2.0736, R13 1.86624, R14–R15 1.5552; only config.json changed in the rebuild, checked by a JSON compare); gamedata.baseEnemyScale returns it assupplyHpMuland the wave / 悬赏 mods carry it (waves.jsroundMods). The 频次 archetype sets hits = the spawn's max HP (data × hpMul) ÷ supplyHpMul, rounded (setHits); a 频次 enemy's death spawn inherits the parent's mods, so 身观's 青铜镜 counts the same. A boss / hidden field gets the round's scale as the BattleSpec flagflags.enemyScale(bossRounds.js; golden fields and tools/balance.mjs likewise), and the leaders' summons spawn with it (summonMods): HP / ATK / speed, 死亡集群'ssummon.hp_ratiomultiplied on top; 刺胄之弹 and 余音 set their hit count from that HP like the 器物 (they are in no exclusion list). 王权号令's equipment (invulnerable, untargetable, never fights) is left as it was. Examples: 青铜镜 solo 标准 23 hits, co-op 险境 R13 86, co-op 终极 R6 62 (1.2⁴, not 1.2⁴ × 1.08), R14 129; co-op 终极 R14 刺胄之弹 53 hits, 余音 67, 妖怪 5350 HP. - What the PR did and we did not take: it back-calculated 补给线 from the composed hpMul (recognising only a 1.08 / 1.2 ratio against 1.2^kHp), which counts the 补给线II steps as 攻坚装备 (co-op 终极 R5 / R7–R12 器物 too many hits), and kept fractional HP (a ceiling); it also scaled the husk counts.
- ASSUMED: a fractional count rounds to the nearest whole hit (setHits' Math.round, kept; a ceiling — float HP — would
differ for 3 × 0.75 and 35 × 0.75); the summons' hit counts follow their HP (inferred from the 器物 exclusion, not
measured). Open: the 逐火 ember / 再生 傀儡 counts (talent
Revive[Trigger].prop_max_hp) stay unscaled — whether the effect multiplies a talent-set max HP is not in the data. - Golden: 74 scenarios. 器物 counts (every one holds a 器物 — extra spawn, 频次 wave or a 频次 enemy's death spawn): roster 37 (002 003 004 005 007–020 022 023 025 026 029–034 036–041 043 045 049), bond-soloShip-high, diy 9 (036 040 056 062 068 073 091 114 115), boss-boss_6-solo (俗心's 小说卷轴), matches 16 (all but solo-ABYSS-2 and coop2-NORMAL-70-diy: solo-FUNNY-1/2, solo-NORMAL-1/2, solo-HARD-1/2, solo-ABYSS-1, coop2-NORMAL-3, coop2-HARD-4, coop3-ABYSS-5, coop4-FUNNY-6, coop4-ABYSS-7, coop2-NORMAL-8-serverrun, solo-HARD-9-boosted, coop2-ABYSS-10-boosted, coop2-NORMAL-14-standins — 频次 rounds / 悬赏 and the Final Assault summons). Summons: boss-boss_1-pair/solo and hidden-boss_8-pair/solo (妖怪 + 刺胄之弹), boss-boss_3-pair/solo and hidden-boss_10-pair/solo (余音), boss-boss_5-pair/solo (不祥幻影). standins unchanged.
- Tests: enemies_bosses — the 14 per-key 频次 tests (hits × the round's HP, was "must not change"), the data pin (the 14
kitTimes keys = 补给线 / 补给线II
enemy_excludeminus 炎佑; 攻坚装备 / II / III / 急行军 only 炎佑), hpMul ÷ supplyHpMul with a death spawn and the solo 标准 rounding, leader summons with / withoutflags.enemyScale; waves — supplyHpMul per co-op 终极 round (hp ÷ it = 1.2^kAtk), wave and 悬赏 mods; finalAssault — the boss fields carryflags.enemyScale.
26.13 The bond strip's collapse button is a bare arrow (the owner's request of 2026-10-07) — screens/game.js, public/css/screens/game.css .bonds-toggle
- Seen: the boxed 「收起」 / 「盟约」 tab in front of the bond discs looked heavy (0.1.4's fold button, PR #149, GitHub #142).
- Now: a bare chevron (‹ open, › folded), transparent, at 55 % opacity (80 % when folded), full opacity and mint on
hover / keyboard focus; the 44 × 44 touch target stays as an invisible hit area and the name is the aria-label
(展开盟约 / 收起盟约). Tests:
test/ui/bond-collapse.e2e.test.js(SP_E2E, 2).
26.14 「当前全体干员为无潜能,与官方实现不符」 (GitHub #252 by SpiritedAwayCN, PR #255 by SpiritedAwayCN — a host slider, not taken): every operator fights at FULL potential (潜能 6) — the owner's decision of 2026-10-07 — tools/build-data.mjs OPERATOR_POTENTIAL / withPotential, data/chess.json / backups.json / tokens.json, data/i18n/*.json
- Seen: every operator fought at potential 1 (no potential): 刺玫 (tier 1, E1 Lv55) ATK 413 / cost 17 where the
official panel reads 435 / 15; 砾's redeploy 18 s instead of 16, the gift 5★ (至简 …) 80 s instead of 70; every
「天赋效果增强」 step missing (刺玫's 土壤基肥改良 108 % instead of 111 %, …). The data build picked every candidate at
potential rank 0 and added no
potentialRanksattribute step. - Official: the season's chess records (activity_table act2autochess
charChessDataDictstatus: evolvePhase, charLevel, skillLevel, favorPoint 0, equipLevel) carry no potential field; a tournament video (bilibili BV1FTowB2EzP, 02:36, linked in #252) shows 刺玫 at ATK 435 / cost 15 — exactly her E1 Lv55 413 / 17 plus her potentials 攻击力+22 and two 部署费用-1. In the official game a player's own potential applies (RushFTK in #252: 0 信赖, potential from the account; an operator not owned — potential 1; the shop rows'backupCharPotRankis 0 except 宴 / 野鬃 1). The owner's decision of 2026-10-07: everyone at full potential, no host setting (PR #255's 1–6 slider is not taken). Summons: a token's own character has nopotentialRanks, but its talent candidates carryrequiredPotentialRankmirroring its owner's 「天赋效果增强」 (夕 潜能 5 → “小自在” 化境 15 → 18 层; 凯尔希 潜能 5 → Mon3tr 不毁重构 3.5 s / 1400 — PRTS 凯尔希 天赋 "潜能增强=5 … 晕眩3.5(+0.5)秒…1400(+200)点真实伤害", X 模组 3 级 4 s / 1700; 望 潜能 3 → 棋子 +1 持有 / 部署; 麦哲伦 → 龙腾 22 s; 鸿雪 → 打字机 −20 % / −25 % 5 s; 伺夜 → 狼群 200 DEF ignore; 风丸 → 纸偶 ×2.75; …). So a summon picks its candidates at its owner's potential and takes none of the owner's attribute steps (ATK / cost / redeploy belong to the owner entity). - Now:
OPERATOR_POTENTIAL = 5. Every chess (normal and elite), every 补位 / 自选 unit form (buildUnitForm) adds everypotentialRanksattribute modifier to the interpolated attributes before rounding (withPotential: all ADDITION — COST, RESPAWN_TIME, ATK, MAX_HP, DEF, MAGIC_RESISTANCE, ATTACK_SPEED; another kind is reported, none exists) and picks talent / trait / module-talent candidates at rank 5 (bestCandidate(…, potRank)). Summons (tokenVariant, tokens.json and backups.jsontokens,tokenTalentDeckBonus) pick theirs at the owner's rank; the band's map characters and stage devices stay at 0. The 17 原型干员 stand-ins (预备干员 char_600–607, char_608–615, 领主·Sharp) have no potential ranks: unchanged. Nothing else reads potential: no skill or trait candidate of the season's operators carries arequiredPotentialRank> 0, skill levels never depend on it. JSON compare (old vs new build): only potential-driven fields moved — chess.json 256 of 266 records (all but the four DIY slots and 盟约·辅助干员, which has no potential ranks; stats cost 256, respawnTime 200, atk 176, def 16, maxHp 14, res 12, aspd 6 —statsBasethe same; 275 talents and 140 talentsBase entries, 27 module talent changes: bb + desc), backups.json (the 71 owned picks' three forms — none of the 17 stand-ins: cost, atk, respawnTime, hp / def / res / aspd, talents, module talent changes; 8 of 38 summons: Mon3tr, 龙腾.F / L / A, 打字机, 结构性原理 talents, 望's 棋子 deploy limit 6 → 7 / holding 7 → 8 / count 7, 多萝西's 共振装置 count 8 → 10), tokens.json (5 summons' talents: “小自在”, 海嗣, 纸偶, 狼群, 防护单元); every other data file byte-identical. The data translations (data/i18n/en / ja / ko / zh-TW) are rebuilt (tools/build-i18n.mjs --offline): only the talent texts that carry the new numbers. Spot checks: 刺玫 435 / 15 / 66 s and 111 %; 格雷伊 ASPD 100 → 106 (攻击速度+6), 崖心 / 伊内丝 +8; 砾 16 s, 至简 70 s, 野鬃 60 s (two 再部署时间-10秒: #252 said 70 for the gift 5★ — true for 至简; 野鬃 has two such steps). The 干员调配 局内数值 and the detail card read the records (statsBase + module attr, shared/loadoutRecord.js), so they show the new numbers unchanged in code. One kit read a number from a talent text that now carries the potential step: 望's stock cap, 「最多拥有7枚」 at potential 0, reads 「最多拥有8(+1)枚」 —op-wang.jsmatched/最多拥有(\d+)枚/and fell back to 7, so with 7 棋子 to start she sat at the cap (阻回, no S1 / S2); the pattern now skips the step (8 = the 棋子's holding, deckStack 8). The other kits that parse talent texts (多萝西 「最多拥有10个」, 娜仁图亚 「偷取触发2次」, 傀影 「攻击力各+10%」, 伺夜 「至多3只」) read numbers that carry no step; skill texts have no potential steps. - Consequences worth knowing (the data's, nothing assumed): 多萝西's 共振装置 stock at a deployment is 10 at full
potential (潜能 5 「第一天赋效果增强」) — her trap limit without TRP-X is 10, so the kit's 阻回 (no SP while the stock is
full) holds from her deployment until a trap is spent (TRP-X: limit 13, no change); 望 sends 7 棋子 (TRP-X 8); 史尔特尔's
余烬 lasts 9 s; 琳琅诗怀雅's 破财消灾 revives to 80 %. One official-data quirk surfaces: 重岳's FGT-Y stage 3 potential
candidate (battle_equip_table uniequip_003_chyue) keeps
prob0.23 while its text reads 「25%(+2%)」 (damage_scale 1.75 → 1.8 does move) — the sim follows the blackboard, as for 嵯峨's SOL-Y before (op-saga.js), so that one form rolls 23 % and every other form 25 %. - ASSUMED none beyond the owner's decision (full potential for every player and every operator, including the 自选 picks, whose official potential would be the account's).
- Golden: moves almost everywhere, as a data change of every operator must — roster 49 / 49, bonds 46 / 46, fields 22 / 22, matches 18 / 18, diy 133 / 138, standins 0 / 10. Unchanged: the 10 stand-in battles and the four prototype 自选 battles diy-135…138 (the 原型干员 have no potential ranks), and diy-012 (twelve 夜莺 pieces that are never hit, never redeploy and deal no damage, so her cost / RES / redeploy steps never show; her talents do not move). Spot checks, the parent data against the new one tick by tick through the golden scenario path (diy-110 / diy-111, 望's battles, also move with her cap fix): roster-001 and roster-020 part at tick 1 on 角峰's HP (生命上限+250: 2770 → 3083 and 5961 → 6634 with the board's HP bonuses), bond-arcaneShip-high at 4.73 s and diy-001 at 3.70 s on an enemy's HP (a hit of a higher ATK). Every moved battle's first differing snapshot hash is in its first 10 game seconds except one bond battle (20–30 s) and ten 自选 battles (10–100 s): a later hit or redeploy; the matches part at rounds 1–8 through those battles (battle results, then the bots' boards).
- Tests: test/full-potential.test.js (new, 6 tests — every one fails on the parent data): 刺玫 435 / 15 / 66 s and her
talent; the other potential kinds and #252's redeploy times; every chess, 自选 form and stand-in form re-derived from
the raw keyframes plus the potential steps; every chess talent = the last candidate unlocked with requiredPotentialRank
≤ 5; summons at the owner's potential (PRTS Mon3tr numbers) without the owner's attribute steps; the loadout record
(局内数值 / detail card), 自选 / 补位 compositions and a battle unit (刺玫 base ATK 435, cost 15, 66 s). data.test.js's
independent re-derivation now adds the potential steps (and re-derives respawnTime too); the spot checks and the kit
tests that hard-coded potential-0 numbers carry the full-potential numbers (each from the data and the test's own
formula; no check loosened): 205 tests in the 71 自选 kit test files (op_*), 21 elsewhere (data / backups / i18n-data,
chess kits, summons, 史尔特尔 余烬 9 s, 望's hand of 7, …); op_wang's S1 / stock / S3 tests fail on the parent kit (the
stock cap). Full
node --test: 5114 / 5114 pass (the first run on the new data: 233 failures in 87 files, two of them timing tests that failed under load only and pass alone).
26.15 Esc in the 自选 picker closes only the picker (GitHub #284 by Xinglan233, idea from PR #286) — screens/diy.js DiyPicker, screens/loadout.js
- Seen: in 干员调配 → 自选编队, one Esc with the operator picker open closed the whole overlay and went back to the
lobby (5阶 and 6阶 slots, also from the picker's search field), while the picker's 取消 and × close only the picker and
the 导入 dialog's Esc closes only itself. The overlay's capture-phase key handler gave way only to a
.modal; the picker is a.diy-picksection without key handling of its own. - Now: the picker registers its own capture-phase Esc handler that acts as 取消 (the draft is dropped, the saved
picks are untouched) and gives way to a
.modalover it (the 导入 dialog); the overlay's handler skips Esc while a.diy-pickis open. The next Esc closes the overlay as before. - Golden: unchanged (interface only). Tests: test/ui/diy.e2e.test.js — a real-server browser test that needs no art: Esc closes the picker of a 5阶 slot, then of a 6阶 slot from its search field, the overlay and its four slots stay, sp.pref.diy is unchanged, and a second Esc closes the overlay; it fails on the parent.