feedback5 R2: docs and comments follow the split — DESIGN §2 and META list server/match/match/ and server/match/player/ (the classes: constructor + method install), research 09 / 11 name the new homes (Match.watch in match/watch.js, the settle in match/settle.js, the facing changes in player/placement.js / items.js), code comments that pointed at moved code say where it lives now (shared constants, client bandDraft / observe / result / hud / teamPanel / underframe / game-mock, test comments); .git-blame-ignore-revs lists the two pure-move commits. DESIGN §17–§23 history citations, the "before" column and the proposal of research 11, tools/build-data.mjs (Match.js still exports BAND_TURN_SECONDS) and public/js/ui/gameLogic.js (in the client stream R5) are left as they are

This commit is contained in:
sganggs committed 2026-10-05 17:18:22 +08:00
1 parent 64443b4aab
commit d9750d8aba
19 files changed
+43 -25

No files matched your search

+4
View File
@@ -11,3 +11,7 @@ b48a67caec5ba0699097e21f3db4d4dbb35a2aed
560328cacc863c26d07780096527e255ebf62bc4 560328cacc863c26d07780096527e255ebf62bc4
# feedback5 R4: kits/tier1..6.js → kits/ops/ (one file per kit), kits/shared/, kits/index.js # feedback5 R4: kits/tier1..6.js → kits/ops/ (one file per kit), kits/shared/, kits/index.js
1169102d46742474bf0f0189d467c791984d9e05 1169102d46742474bf0f0189d467c791984d9e05
# feedback5 R2: server/match/Match.js → server/match/match/*.js (the Match methods, verbatim; shared constants in common.js)
1ca4936f0a541cabda9acae5fe6f9213df4b5e6a
# feedback5 R2: server/match/PlayerState.js → server/match/player/*.js (the PlayerState methods, verbatim)
64443b4aab3126d6703509b8c47e9cfa58b75157
+9 -2
View File
@@ -56,8 +56,15 @@ server/
lobby.js rooms (4-letter codes), seats, host, AI seats, ready/start, reconnect tokens, room→Match wiring lobby.js rooms (4-letter codes), seats, host, AI seats, ready/start, reconnect tokens, room→Match wiring
data.js loads data/*.json once, builds indexes (getChess, getBond, …); frozen objects data.js loads data/*.json once, builds indexes (getChess, getBond, …); frozen objects
match/ match/
Match.js match state machine, timers, round loop, co-op orchestration, broadcasting views Match.js match state machine, timers, round loop, co-op orchestration, broadcasting views — the class:
PlayerState.js per-player economy/shop/hand/board/items/bonds/LP state + all prep-intent handlers the lobby⇄match interface (header), constructor + method install
match/ Match's methods by concern (platform, infra, messaging, views, watch, intents, pause, phases,
spDraft, prep, combat, clientCombat, reports, unitePhase, bossRounds, settle; shared constants
in common.js), installed on Match.prototype
PlayerState.js per-player economy/shop/hand/board/items/bonds/LP state + all prep-intent handlers — the class:
constructor + method install
player/ PlayerState's methods by concern (basics, pieces, acquire, economy, placement, items, prep,
round, views; common.js), installed on PlayerState.prototype
pool.js SharedPool (copies per base chess), banned/disabled bonds, odds & rolls pool.js SharedPool (copies per base chess), banned/disabled bonds, odds & rolls
board.js placement legality, deploy cap, hand/temp management, merge detection & execution board.js placement legality, deploy cap, hand/temp management, merge detection & execution
bondsMeta.js bond member counting (BOARD / BOARD_AND_DECK / golden), activation tiers, persistent layers bondsMeta.js bond member counting (BOARD / BOARD_AND_DECK / golden), activation tiers, persistent layers
+8 -1
View File
@@ -7,7 +7,14 @@ Normative contracts stay in DESIGN.md §6 and §8; this file documents the imple
``` ```
server/match/ server/match/
Match.js state machine, timers, round loop, co-op orchestration, views (the lobby⇄match interface is at the top) Match.js state machine, timers, round loop, co-op orchestration, views (the lobby⇄match interface is at the top)
PlayerState.js per-player economy / shop / hand / board / items / bonds / LP + every prep intent handler — the class: constructor + method install
match/ Match's methods by concern: platform, infra, messaging, views, watch, intents, pause, phases,
spDraft (机变), prep, combat, clientCombat, reports, unitePhase (联防), bossRounds (Final Assault /
Hidden Core), settle; common.js (FLOW_TICKER_PRIORITY, DELAYS, BAND_TURN_SECONDS, re-exported by Match.js)
PlayerState.js per-player economy / shop / hand / board / items / bonds / LP + every prep intent handler — the class:
constructor + method install
player/ PlayerState's methods by concern: basics, pieces, acquire (gains, merges), economy (funds, layers,
the shop), placement (g.move), items, prep, round (lifecycle, battleInput), views; common.js
gamedata.js typed, defaulted view of data/*.json (config tunables with research defaults) + the balance layer gamedata.js typed, defaulted view of data/*.json (config tunables with research defaults) + the balance layer
(data/tuning.json, §3.1) (data/tuning.json, §3.1)
pool.js SharedPool (copies per base chess, across players), per-match bans, copy-weighted rolls pool.js SharedPool (copies per base chess, across players), per-match bans, copy-weighted rolls
+2 -2
View File
@@ -318,7 +318,7 @@ The public mirror (ArknightsAssets2 `cn`, `ui/emoticon/theme/[uc]<themeId>/icon/
- `g.move {uid, to:{area:'board',row,col}, dir}` with `dir ∈ 'UP'|'RIGHT'|'DOWN'|'LEFT'`; the server defaults to `'RIGHT'` when absent, for bots and old clients. - `g.move {uid, to:{area:'board',row,col}, dir}` with `dir ∈ 'UP'|'RIGHT'|'DOWN'|'LEFT'`; the server defaults to `'RIGHT'` when absent, for bots and old clients.
- `g.art {itemUid,row,col,dir}`. - `g.art {itemUid,row,col,dir}`.
- Broadcast `dir` in the board state (public and private views, snapshot `UnitInfo.dir`). - Broadcast `dir` in the board state (public and private views, snapshot `UnitInfo.dir`).
2. **`server/match/PlayerState.js`.** 2. **`server/match/PlayerState.js`** (the methods named here: `server/match/player/placement.js`, `items.js`).
- Store `piece.dir`, preserved across rounds. - Store `piece.dir`, preserved across rounds.
- In `_moveChessToBoard` and `_moveTokenToBoard`, `if (occ === piece) return OK;` must become "update `piece.dir`, recompute, OK". This is the in-place re-orient. - In `_moveChessToBoard` and `_moveTokenToBoard`, `if (occ === piece) return OK;` must become "update `piece.dir`, recompute, OK". This is the in-place re-orient.
- Swaps keep each piece's own dir. - Swaps keep each piece's own dir.
@@ -363,7 +363,7 @@ The public mirror (ArknightsAssets2 `cn`, `ui/emoticon/theme/[uc]<themeId>/icon/
4. Prep: the same avatar → 前往查看 flow (read-only board + pen). 4. Prep: the same avatar → 前往查看 flow (read-only board + pen).
5. 联防 and FA fields: the ‹ › pill cycles LEFT half / 全景 / RIGHT half, with captions "你自己" / "👁 name#" / "全景". Non-helpers are auto-moved to the 联防 field as observers. In FA the other pair's field is not selectable. 5. 联防 and FA fields: the ‹ › pill cycles LEFT half / 全景 / RIGHT half, with captions "你自己" / "👁 name#" / "全景". Non-helpers are auto-moved to the 联防 field as observers. In FA the other pair's field is not selectable.
6. Eliminated players: a dialog offers 继续观战 (auto-observe the first live field, free switching) or 退出. 6. Eliminated players: a dialog offers 继续观战 (auto-observe the first live field, free switching) or 退出.
7. `server/match/Match.js watch()` must enforce the same rules (prep: any teammate; combat: only if your own field finished; FA: own pair only; dead: anything). 7. `Match.watch()` (`server/match/match/watch.js`) must enforce the same rules (prep: any teammate; combat: only if your own field finished; FA: own pair only; dead: anything).
### 6.4 Combat computation model (user item 5, architecture) [proposal based on §3.2] ### 6.4 Combat computation model (user item 5, architecture) [proposal based on §3.2]
+1 -1
View File
@@ -220,7 +220,7 @@ line is lost rather than clamped.
- `shared/constants.js`: `BOND_LAYER_CAP = 999` and `BOSS_HIT_LIMIT = 300000` (0 / Infinity = off), plus - `shared/constants.js`: `BOND_LAYER_CAP = 999` and `BOSS_HIT_LIMIT = 300000` (0 / Infinity = off), plus
`layerGainRoom(before, n)` = min(n, cap − before), never negative. `layerGainRoom(before, n)` = min(n, cap − before), never negative.
- Layers: every writer clamps with it — `PlayerState.addLayers` (every prep-side gain: 特质, items, bands, 机变 cards, - Layers: every writer clamps with it — `PlayerState.addLayers` (every prep-side gain: 特质, items, bands, 机变 cards,
bonds), the settle of the in-battle gains (`Match.js`), `Battle.addLayers` (the live in-battle copy, after the bonds), the settle of the in-battle gains (`Match.settle`, `server/match/match/settle.js`), `Battle.addLayers` (the live in-battle copy, after the
`layerGain` hook, as the client's `AddBondCount`); a gain at the cap adds 0 (no onLayers, no 'layer' event, no hook), `layerGain` hook, as the client's `AddBondCount`); a gain at the cap adds 0 (no onLayers, no 'layer' event, no hook),
so the per-N milestones (远见, 奇迹, 维多利亚 …) stop with the count; the client-result check (`fields.js`) bounds a so the per-N milestones (远见, 奇迹, 维多利亚 …) stop with the count; the client-result check (`fields.js`) bounds a
reported gain by the room left from the bond's starting layers; `invariants.js` flags a bond above the cap. The dev reported gain by the room left from the bond's starting layers; `invariants.js` flags a bond above the cap. The dev
+1 -1
View File
@@ -681,7 +681,7 @@ async function mockRequest(t, f = {}) {
const who = f.fieldId.replace(/^n:/, ''); const who = f.fieldId.replace(/^n:/, '');
if (who !== ME) { if (who !== ME) {
const units = shuffle(S.pool).slice(0, 6).map((c, i) => ({ id: 900 + i, kind: 'op', side: 'ally', ownerId: who, defId: c.chessId, name: c.name, tier: c.tier, golden: i === 0, spine: c.charId, avatar: c.assets.avatar, x: [3, 4, 5, 7, 8, 5][i], y: [9, 9, 10, 11, 12, 12][i], facing: 1, maxHp: c.stats.maxHp })); const units = shuffle(S.pool).slice(0, 6).map((c, i) => ({ id: 900 + i, kind: 'op', side: 'ally', ownerId: who, defId: c.chessId, name: c.name, tier: c.tier, golden: i === 0, spine: c.charId, avatar: c.assets.avatar, x: [3, 4, 5, 7, 8, 5][i], y: [9, 9, 10, 11, 12, 12][i], facing: 1, maxHp: c.stats.maxHp }));
// like server/match/Match.js prepFieldMeta: a read-only prep board with THEIR coming enemies (the pen shows them) // like server/match/match/views.js prepFieldMeta: a read-only prep board with THEIR coming enemies (the pen shows them)
const theirs = S.priv.nextEnemies.filter((e) => e.source !== 'bounty').slice(0, 3).map((e) => ({ ...e, gate: 'upper', count: e.count + 1 })); const theirs = S.priv.nextEnemies.filter((e) => e.source !== 'bounty').slice(0, 3).map((e) => ({ ...e, gate: 'upper', count: e.count + 1 }));
store.patch('match', { field: { fieldId: f.fieldId, kind: 'normal', rect: { ...GEO.NORMAL_RECT }, stageId: pub.stageId, units, prep: true, nextEnemies: theirs } }); store.patch('match', { field: { fieldId: f.fieldId, kind: 'normal', rect: { ...GEO.NORMAL_RECT }, stageId: pub.stageId, units, prep: true, nextEnemies: theirs } });
} }
+1 -1
View File
@@ -1,5 +1,5 @@
// Observing rules and labels of client-side combat (research 09 §3.1 / §6.3, DESIGN §14 "Spectating") — pure helpers // Observing rules and labels of client-side combat (research 09 §3.1 / §6.3, DESIGN §14 "Spectating") — pure helpers
// for the game screen, the team panel and the combat HUD (mirror of server/match/Match.js _watchClient): // for the game screen, the team panel and the combat HUD (mirror of server/match/match/watch.js _watchClient):
// * prep (休整期): tap a teammate → 前往查看 → their board (read-only); // * prep (休整期): tap a teammate → 前往查看 → their board (read-only);
// * own normal battle running: no observing ("当前无法查看"); // * own normal battle running: no observing ("当前无法查看");
// * own battle over: "⌛ 作战结束,等待队友完成作战" + the teammates' progress; tap a teammate → 前往查看 → a local // * own battle over: "⌛ 作战结束,等待队友完成作战" + the teammates' progress; tap a teammate → 前往查看 → a local
+2 -2
View File
@@ -58,7 +58,7 @@ export function allowedBands(bands, modeType) {
export const DEFAULT_TIMEOUT_BAND = 'band_bldsk'; export const DEFAULT_TIMEOUT_BAND = 'band_bldsk';
/** /**
* The strategy the server assigns me when my turn times out (server/match/Match.js defaultBand): the official default * The strategy the server assigns me when my turn times out (server/match/match/phases.js defaultBand): the official default
* 「华法琳」 while no teammate holds it, else the first free strategy in draft order (sortId) — never one a teammate * 「华法琳」 while no teammate holds it, else the first free strategy in draft order (sortId) — never one a teammate
* already picked (队友已选). * already picked (队友已选).
* @param {any[]} bands allowedBands(...) (sortId order) * @param {any[]} bands allowedBands(...) (sortId order)
@@ -218,7 +218,7 @@ export function BandDraftScreen() {
act('g.bandFocus', { bandId: sel }, { sfx: false, quiet: true }); act('g.bandFocus', { bandId: sel }, { sfx: false, quiet: true });
}, [sel, timed, myPick]); }, [sel, timed, myPick]);
// what a timeout gives me: the highlighted band while free, else the default (never 队友已选 — Match.js timeoutBand) // what a timeout gives me: the highlighted band while free, else the default (never 队友已选 — match/phases.js timeoutBand)
const autoId = autoPickBand(sel, { bands, taken, myPick, defaultId }); const autoId = autoPickBand(sel, { bands, taken, myPick, defaultId });
const autoName = (autoId && gd.band(autoId)?.name) || gd.band(defaultId)?.name || '华法琳'; const autoName = (autoId && gd.band(autoId)?.name) || gd.band(defaultId)?.name || '华法琳';
+1 -1
View File
@@ -22,7 +22,7 @@ import { audio } from '../audio.js';
const cx = (...p) => p.flat().filter(Boolean).join(' '); const cx = (...p) => p.flat().filter(Boolean).join(' ');
// The first seven present are shown (+ remaining LP = a 4×2 grid); the title (评语) stats come first. // The first seven present are shown (+ remaining LP = a 4×2 grid); the title (评语) stats come first.
// `gold` is the funds a player SPENT (server/match/PlayerState.js spend(); the 挥金如土 title stat). // `gold` is the funds a player SPENT (server/match/player/economy.js spend(); the 挥金如土 title stat).
const STAT_ROWS = [ const STAT_ROWS = [
['dmgDealt', '造成伤害'], ['kills', '击倒敌人'], ['bossDamage', '领袖伤害'], ['activatedLayers', '盟约层数'], ['dmgDealt', '造成伤害'], ['kills', '击倒敌人'], ['bossDamage', '领袖伤害'], ['activatedLayers', '盟约层数'],
['merges', '晋升次数'], ['itemsEquipped', '配发装备'], ['gold', '消耗资金'], ['perfectRounds', '完美作战'], ['merges', '晋升次数'], ['itemsEquipped', '配发装备'], ['gold', '消耗资金'], ['perfectRounds', '完美作战'],
+1 -1
View File
@@ -102,7 +102,7 @@ export function MissTag({ n, name = null }) {
const LEAK_PHASES = new Set([PHASE.COMBAT, PHASE.UNITE]); const LEAK_PHASES = new Set([PHASE.COMBAT, PHASE.UNITE]);
/** /**
* LP a round's leaks cost at settlement — the server rule (server/match/Match.js settle): min(lpCapPerRound, counted * LP a round's leaks cost at settlement — the server rule (server/match/match/settle.js): min(lpCapPerRound, counted
* leaks), never negative. * leaks), never negative.
* @param {number} leaks counted leaks so far * @param {number} leaks counted leaks so far
* @param {number} [cap] data/config.json lpCapPerRound * @param {number} [cap] data/config.json lpCapPerRound
+1 -1
View File
@@ -7,7 +7,7 @@
// watches that player's field at once. // watches that player's field at once.
// Live LP (user playtest #3 item 2): during a normal round's battle each row's tower shows lp − the loss that player's // Live LP (user playtest #3 item 2): during a normal round's battle each row's tower shows lp − the loss that player's
// leaks so far will cost (red, −N): the own row the top bar's live value (`self`, ui/hud.js liveLp), a teammate's row // leaks so far will cost (red, −N): the own row the top bar's live value (`self`, ui/hud.js liveLp), a teammate's row
// m.public players[].pendingLp (server/match/Match.js, ~1 Hz). 联防 (user playtest #6 item 7): a leaker's row adds the // m.public players[].pendingLp (server/match/match/views.js, ~1 Hz). 联防 (user playtest #6 item 7): a leaker's row adds the
// runner tag ×N — its enemies still standing on the 联防 field, uncapped and live (falling as the helpers kill them, // runner tag ×N — its enemies still standing on the 联防 field, uncapped and live (falling as the helpers kill them,
// rising when one splits) — next to lp − min(lpCapPerRound, N): the own row the top bar's value, a teammate's row the // rising when one splits) — next to lp − min(lpCapPerRound, N): the own row the top bar's value, a teammate's row the
// local 联防 replica's count while it is on screen (`uniteLocal`: the battle runner's state().uniteLeft, the same battle // local 联防 replica's count while it is on screen (`uniteLocal`: the battle runner's state().uniteLeft, the same battle
+1 -1
View File
@@ -135,7 +135,7 @@ export function tempRowFrame(a, b, { labelW = 0, gap = 8, vw = Infinity } = {})
} }
/** /**
* What the temp row's label says will happen to its pieces (server/match/PlayerState.js tempDue): a piece is resolved * What the temp row's label says will happen to its pieces (server/match/player/basics.js tempDue): a piece is resolved
* at the end of the first prep in which the player can act on it. Not ready (or outside PREP): at the end of this / the * at the end of the first prep in which the player can act on it. Not ready (or outside PREP): at the end of this / the
* coming prep, and 准备就绪 waits for the row to be cleared. Ready in PREP: Ready is refused while the row holds pieces, * coming prep, and 准备就绪 waits for the row to be cleared. Ready in PREP: Ready is refused while the row holds pieces,
* so whatever lies there arrived after it — kept through the NEXT prep (cancelling Ready makes it due at this one). * so whatever lies there arrived after it — kept through the NEXT prep (cancelling Ready makes it due at this one).
+3 -3
View File
@@ -10,7 +10,7 @@ export const MAX_SEATS = 4;
* Spectator seats of a co-op room (community report #26, owner's decision 2026-10-04) — a remake feature: the official * Spectator seats of a co-op room (community report #26, owner's decision 2026-10-04) — a remake feature: the official
* room has 1–4 players and no spectator seat (there only eliminated players and 联防 bystanders watch, research 09 §3.1). * room has 1–4 players and no spectator seat (there only eliminated players and 联防 bystanders watch, research 09 §3.1).
* A spectator never counts as a player, may not act, and watches like an eliminated player (server/lobby.js spectate, * A spectator never counts as a player, may not act, and watches like an eliminated player (server/lobby.js spectate,
* server/match/Match.js addSpectator). * server/match/match/watch.js addSpectator).
*/ */
export const MAX_SPECTATORS = 2; export const MAX_SPECTATORS = 2;
export const ROOM_CODE_LEN = 4; export const ROOM_CODE_LEN = 4;
@@ -79,8 +79,8 @@ export const SKILL_SUMMON_START_DEPLOY = true;
* `Torappu.Battle.AutoChessBattleConst.MAX_GARRISON_STACK = 999`, and its bond counter (`AddBondCount`) stores * `Torappu.Battle.AutoChessBattleConst.MAX_GARRISON_STACK = 999`, and its bond counter (`AddBondCount`) stores
* `min(L + n, 999)` — each bond stops at 999 on its own; the community reports bonds sitting at 999 while fed (巴哈姆特 * `min(L + n, 999)` — each bond stops at 999 on its own; the community reports bonds sitting at 999 while fed (巴哈姆特
* 12534 "每把都能999层", 12316 "999謝"; research 02 §layers). The only implementation of the cap (DESIGN §20.12). Every * 12534 "每把都能999层", 12316 "999謝"; research 02 §layers). The only implementation of the cap (DESIGN §20.12). Every
* writer of a bond's layers goes through `layerGainRoom`: the prep-side gains (server/match/PlayerState.js addLayers — * writer of a bond's layers goes through `layerGainRoom`: the prep-side gains (server/match/player/economy.js addLayers —
* 特质, items, bands, 机变 cards, bonds), the settle of the in-battle gains (server/match/Match.js) and the live in-battle * 特质, items, bands, 机变 cards, bonds), the settle of the in-battle gains (server/match/match/settle.js) and the live in-battle
* copy (server/sim/battle/economy.js addLayers, as the client's AddBondCount) — and the dev tools' direct writes (tools/matchrun.mjs * copy (server/sim/battle/economy.js addLayers, as the client's AddBondCount) — and the dev tools' direct writes (tools/matchrun.mjs
* --layers, tools/balance.mjs applyBoard); a gain at the cap adds 0 (no onLayers, no 'layer' * --layers, tools/balance.mjs applyBoard); a gain at the cap adds 0 (no onLayers, no 'layer'
* event), and the client-result check (server/match/fields.js) bounds a reported gain by the room left. Milestones paid * event), and the client-result check (server/match/fields.js) bounds a reported gain by the room left. Milestones paid
+1 -1
View File
@@ -9,7 +9,7 @@
// the verdict then came from the pool after the final b.result, whose per-player damage is rounded: rounding up // the verdict then came from the pool after the final b.result, whose per-player damage is rounded: rounding up
// emptied the dust → 胜利 (what the user saw), rounding down → 失败. // emptied the dust → 胜利 (what the user saw), rounding down → 失败.
// //
// Now (sim/constants.js BOSS_POOL_MIN_HP, finalAssault.js SharedBossPool, sim/spec.js LocalBossPool, Match.js): a pool // Now (sim/constants.js BOSS_POOL_MIN_HP, finalAssault.js SharedBossPool, sim/spec.js LocalBossPool, match/reports.js): a pool
// never holds less than 1 HP (the hit that would leave less takes the rest; a browser pool reading below 1 is 0), so // never holds less than 1 HP (the hit that would leave less takes the rest; a browser pool reading below 1 is 0), so
// the leader dies on every field as soon as the pool shows 0 and the fight ends; the first end condition the server // the leader dies on every field as soon as the pool shows 0 and the fight ends; the first end condition the server
// registers decides the verdict (pool 0 → victory, team LP 0 → defeat: PRTS 卫戍协议:盟约 下半 "…使目标生命值扣除至0, // registers decides the verdict (pool 0 → victory, team LP 0 → defeat: PRTS 卫戍协议:盟约 下半 "…使目标生命值扣除至0,
+1 -1
View File
@@ -1,7 +1,7 @@
// User playtest #6 follow-up — a merge's elite goes to the board when it consumed a deployed copy. The user (first-hand, // User playtest #6 follow-up — a merge's elite goes to the board when it consumed a deployed copy. The user (first-hand,
// official game): "官方就是合成精锐时,如果消耗了场上的干员,精锐会出现在场上那个位置". PRTS 卫戍协议/帮助 §干员的获得与精锐化: // official game): "官方就是合成精锐时,如果消耗了场上的干员,精锐会出现在场上那个位置". PRTS 卫戍协议/帮助 §干员的获得与精锐化:
// "不获得第3名干员,销毁已有的2名初始干员,发送1名【精锐】状态的该干员至手牌区(若消耗已部署至作战区的干员,则发送至作战区 // "不获得第3名干员,销毁已有的2名初始干员,发送1名【精锐】状态的该干员至手牌区(若消耗已部署至作战区的干员,则发送至作战区
// 对应位置)". Remake (server/match/PlayerState.js _mergeChess, board.js mergeTile): the elite takes the tile and facing of the // 对应位置)". Remake (server/match/player/acquire.js _mergeChess, board.js mergeTile): the elite takes the tile and facing of the
// consumed copy that deploys first (row desc, col asc — [ASSUMED] when several stood on the board); a 突变细胞 carrier is // consumed copy that deploys first (row desc, col asc — [ASSUMED] when several stood on the board); a 突变细胞 carrier is
// destroyed before its gain, so its tile never counts; with no deployed copy it goes to the hand (overflow temp). // destroyed before its gain, so its tile never counts; with no deployed copy it goes to the hand (overflow temp).
// Equipment returns to the hand ("干员晋级后已配发装备会回收至整备区"), the copies' summons are removed and the elite on // Equipment returns to the hand ("干员晋级后已配发装备会回收至整备区"), the copies' summons are removed and the elite on
+1 -1
View File
@@ -1,7 +1,7 @@
// Live operator stats (user playtest #4 item 7 — the detail card showed the fixed record numbers): g.unitStats answers // Live operator stats (user playtest #4 item 7 — the detail card showed the fixed record numbers): g.unitStats answers
// m.unitStats { seq, round, units } with the stats every unit of the player's board starts its next battle with — the // m.unitStats { seq, round, units } with the stats every unit of the player's board starts its next battle with — the
// real battle input (equipment, bonds / layers, 特质, band and 机变 effects, the onBattleStart meta) built into a Battle // real battle input (equipment, bonds / layers, 特质, band and 机变 effects, the onBattleStart meta) built into a Battle
// that is started and read, never stepped (server/match/Match.js unitStats); the shape is shared/protocol.js // that is started and read, never stepped (server/match/match/intents.js unitStats); the shape is shared/protocol.js
// unitStatsEntry (also the browser runner's live battle stats, test/match/runner.test.js). The preview changes nothing // unitStatsEntry (also the browser runner's live battle stats, test/match/runner.test.js). The preview changes nothing
// of the match. // of the match.
import { test } from 'node:test'; import { test } from 'node:test';
+3 -3
View File
@@ -1,8 +1,8 @@
// Strategy draft 队友已选 (research 09 §5 / §7, DESIGN §14 corrections): a strategy a teammate already picked cannot be // Strategy draft 队友已选 (research 09 §5 / §7, DESIGN §14 corrections): a strategy a teammate already picked cannot be
// chosen again — the server refuses it (server/match/Match.js pickBand → BAD_TARGET '队友已选'), bots re-draw, and the // chosen again — the server refuses it (server/match/match/phases.js pickBand → BAD_TARGET '队友已选'), bots re-draw, and the
// UI marks it (screens/bandDraft.js teammateBands). Automatic assignments (a turn that runs out, a departing seat) give // UI marks it (screens/bandDraft.js teammateBands). Automatic assignments (a turn that runs out, a departing seat) give
// the official default 「华法琳」 only while no teammate holds it, else the first free strategy (Match.js defaultBand; the // the official default 「华法琳」 only while no teammate holds it, else the first free strategy (match/phases.js defaultBand; the
// UI names it: bandDraft.js timeoutBand) — a timed-out turn takes the highlighted band first (g.bandFocus, Match.js // UI names it: bandDraft.js timeoutBand) — a timed-out turn takes the highlighted band first (g.bandFocus, match/phases.js
// timeoutBand; user playtest #4 item 4: one countdown, BAND_TURN_SECONDS per turn, no separate step cap). // timeoutBand; user playtest #4 item 4: one countdown, BAND_TURN_SECONDS per turn, no separate step cap).
import { test, describe } from 'node:test'; import { test, describe } from 'node:test';
+1 -1
View File
@@ -1,5 +1,5 @@
// Player report after 0.1.0, client part: "隐藏boss还没打就出了造成50%伤害播报". Besides the server's share (fixed in // Player report after 0.1.0, client part: "隐藏boss还没打就出了造成50%伤害播报". Besides the server's share (fixed in
// server/match/Match.js, test/match/bosshit-ticker.test.js) the ticker strip plays its queue in order, TICKER_MS (5.2 s) // server/match/match/bossRounds.js, test/match/bosshit-ticker.test.js) the ticker strip plays its queue in order, TICKER_MS (5.2 s)
// per line and up to QUEUE_MAX (4) lines behind: in a headless-Chrome run the Final Assault's "超过20%" / "超过80%" // per line and up to QUEUE_MAX (4) lines behind: in a headless-Chrome run the Final Assault's "超过20%" / "超过80%"
// lines played 2–8 s into the Hidden Core with its leader at 96 % / 53 %. A leader-damage line (BOSS_HIT) now plays // lines played 2–8 s into the Hidden Core with its leader at 96 % / 53 %. A leader-damage line (BOSS_HIT) now plays
// only during the round it came in, and a player's newer BOSS_HIT line of the round supersedes their older one, queued // only during the round it came in, and a player's newer BOSS_HIT line of the round supersedes their older one, queued
+1 -1
View File
@@ -2,7 +2,7 @@
// 2 the own LP drops live while the own battle's enemies enter the blue gate — pendingLoss / liveLp (the settle // 2 the own LP drops live while the own battle's enemies enter the blue gate — pendingLoss / liveLp (the settle
// rule min(lpCapPerRound, counted leaks), reset when the settled m.private lands, 联防中 during 联防), the team // rule min(lpCapPerRound, counted leaks), reset when the settled m.private lands, 联防中 during 联防), the team
// panel rows (rowLp: own live value, teammates' m.public players[].pendingLp), the LP tower's −N tick, and the // panel rows (rowLp: own live value, teammates' m.public players[].pendingLp), the LP tower's −N tick, and the
// server's pendingLp (server/match/Match.js _pendingLpView) // server's pendingLp (server/match/match/views.js _pendingLpView)
// 3 the temp overflow row (临时整备区): the ready button's visible reason, the row's frame geometry (tempRowFrame) // 3 the temp overflow row (临时整备区): the ready button's visible reason, the row's frame geometry (tempRowFrame)
// 8 the operator's own effect (特质 / garrison) right under the detail card's header (CHESS_SECTIONS, rendered order) // 8 the operator's own effect (特质 / garrison) right under the detail card's header (CHESS_SECTIONS, rendered order)
// 9 no spinning busy indicators next to texts: no wait / progress cursors, the button's busy bar instead of a // 9 no spinning busy indicators next to texts: no wait / progress cursors, the button's busy bar instead of a