feedback6: DESIGN §26.2–§26.13 assembled from the streams' drafts (联防 battlefield, 自选 picks in bond lists, consumables on a full carrier, Touch, 凋亡, 贾维, .gitignore, flyer hover, enemy animation states, runner messages, 频次 / leader summons, the collapse arrow), the §26 intro and the owner's decisions
This commit is contained in:
1 parent
a9fe794c3f
commit
cfc149e277
1 file changed
+385
-2
+385
-2
@@ -7,11 +7,18 @@ Part of [DESIGN.md](../DESIGN.md) (the index; section numbers are global).
|
||||
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 ships. 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.
|
||||
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.
|
||||
|
||||
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).
|
||||
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 (pending, the R21P stream); 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`
|
||||
|
||||
@@ -44,3 +51,379 @@ S3 【移动】 keeps his own board (§26.1).
|
||||
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`; `onOwnBoard` is 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` / `MultiPlayer` name `level_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 templates `01…07` and `h01…h08` with their
|
||||
`_s` solo variants, the training `tr01/02/04`, both escaped levels) hold the identical 19 × 21 road grid, tile for
|
||||
tile, while only the 11 stage levels (`m01…m07`, act2 `m01…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**: `_uniteOpts` fields the 联防 battle on `this.stageId` in 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_single` from the middle gate (9,10); two:
|
||||
`escaped_multi` from 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 uniteStageId` is removed. The client draws the 联防 on the board's stage again
|
||||
(`screens/game.js`: 0.1.4's `ownView ? 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.json` keeps the two `kind: 'unite'` records, which no field uses: they are the official level
|
||||
data, the build checks them against `config.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 --offline` reproduces 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) and `unite-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.js` rewritten — 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**: `bondMembers` adds 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.js `diyGetter`): `owned: false` until a copy is on the board, in
|
||||
the hand or in the 临时整备区 (then on board / in hand as before), `banned` when 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
|
||||
(UnitInfo `diy`, ui/watchBonds.js `ownerBoard`, as in 0.2.0); such a row now passes the unit's pick on (BondPopup
|
||||
`onMember`'s fourth argument → the detail target's `diy` → `resolveDetail` composes 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 `itemAttaches` treated every `consume_on_equip*` item as
|
||||
slot-free (`dropIntent` → `confirmReplace: false`), and the server ran the effect, whose `ev.keep` re-attached the item
|
||||
through `_attach(holder, item)` with no `replaceUid` — the full row pushed out index 0 (a `replaceUid` sent anyway was
|
||||
ignored). 盟约之币 and the other immediate consumables (随身身份牌, 紧急调度券, 精打细算玩偶, 简易通讯机, 见钱眼开玩偶, 骑士储蓄罐,
|
||||
寻呼模块, 信标, 拟态物质, 人事部文档, the golden 博士投影) opened no dialog either and kept both equipped items
|
||||
(reproduced on feedback6 783bd30: `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 item `replaceUid` names (else the oldest, as for every
|
||||
third item) off a full carrier before `onEquip` runs (`_takeReplaced`), so the effect sees the carrier without it (its
|
||||
bonds: 随身身份牌 / 简易通讯机 / 寻呼模块 / 拟态物质); once the effect went through the item is destroyed (`onDestroy`
|
||||
reason `replace`) 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. `itemAttaches` is 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 no
|
||||
`replaceUid`).
|
||||
- [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 `onDestroy` runs after the effect so a refusal
|
||||
can restore it — no equipment item has an `onDestroy` effect for `replace`, so nothing observable differs.
|
||||
- Golden: `matches` only, 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 e2e `test/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) and `resolveTrigger` widens 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_RANGE` on the
|
||||
5-2).
|
||||
- **Now**: `touchKit` sets the skill's trigger to `ACTIVE_RANGE` on 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 for `kind: '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 stay `kind: '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 keeps `sp = cost` with
|
||||
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).
|
||||
- [ASSUMED]: none.
|
||||
- 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.js` operator 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**: `dispatch` takes 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.sync` put every unit except a *ground*
|
||||
enemy on the tile top under it (`groundZ`: TILE_H.wall 0.42, forbid 0.3) and added `FLY_HOVER` on 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_HOVER` above 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.js` `an 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: null` and 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 s `reborn.duration` (its self-destruct 2.17 s in), 自在 Revive_01 5.33 s for 5 s. The sim reports a 重生 as a stun
|
||||
(archetypes.js `reborn`: 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 primary `skill`; 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 separate `run` role. The renderer: `SpineActor._applyRoles` composes 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 on `run` (猎狗pro 1.9, 深池侦察犬 / pro 1.7; 宿主士兵 0.9 and 鸭爵 0.4 keep Move);
|
||||
`UnitView.setSkillSlot` (render/app.js `case '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) → `form2` Idle_2 / Move_2 / Die / Stun_2, no
|
||||
attack (不进行攻击); 自在 `reborn` = Revive_01 → `form2` its own clips. data/assets.json regenerated with
|
||||
`node tools/fetch-assets.mjs --offline`: 18 enemies gain `anims.skills`, 6 `anims.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 castSkillSlot` emitting `['cast', id, slot]` when an ability
|
||||
casts, 昆图斯's ability ids, `UnitInfo.skillIndex` for 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` / `_2` clips 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.js` 7/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 783bd30 by `test/match/runner-pending.test.js` — 11 of its 15 cases fail
|
||||
on the parent): the client runner added a `b.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 at 783bd30): `onEnd` (line 688,
|
||||
`entries.get`) dropped a `b.end` that 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's `cleared` / `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 second `b.start` of
|
||||
the same battle during the catch-up; line 624 (`seq !== startSeq`) never built an authority that another field's
|
||||
`b.start` superseded during the load (the deadline then took the field over); lines 663–666 re-registered an
|
||||
authoritative battle whose catch-up the next prep's `clear()` had interrupted, and it went on running and reporting
|
||||
into the next round; `setPaused` (line 506) and `onPool` (line 680) skipped a battle in catch-up (the paused time was
|
||||
caught up; the boss pool waited for the next `b.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`): a `b.start` is registered as pending the moment it arrives (`pending`,
|
||||
`battle` null 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 takeover` clears its authority; a forced / timeout `b.end` keeps
|
||||
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 second `b.start` of 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.pool` syncs 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. A
|
||||
`b.start` of 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 fresh `b.start` of 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 `cancelled` guards 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). Its `docs/DESIGN.md` paragraph
|
||||
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 suites
|
||||
`test/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.js` unchanged 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 %) with
|
||||
`enemy_exclude` = 炎佑 only; 补给线 / 补给线II (HP ×1.08 / ×1.2, no ATK) with `enemy_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, `extra` 1.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 as `supplyHpMul` and the wave / 悬赏 mods carry it (waves.js
|
||||
`roundMods`). 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 flag `flags.enemyScale` (bossRounds.js; golden fields and tools/balance.mjs likewise), and the
|
||||
leaders' summons spawn with it (`summonMods`): HP / ATK / speed, 死亡集群's `summon.hp_ratio` multiplied 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_exclude` minus 炎佑; 攻坚装备 / II / III / 急行军 only 炎佑), hpMul ÷ supplyHpMul with a
|
||||
death spawn and the solo 标准 rounding, leader summons with / without `flags.enemyScale`; waves — supplyHpMul per co-op
|
||||
终极 round (hp ÷ it = 1.2^kAtk), wave and 悬赏 mods; finalAssault — the boss fields carry `flags.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).
|
||||
Reference in new issue
Block a user