feedback7 R22-LOADOUT: the server takes each player's 潜能 / 练度 per operator and every owned operator fights at them (default 潜能 6, 精英2 Lv.60); golden: roster 49/49, bonds 46/46, fields 22/22, matches 18/18, diy 134/138 moved (+10 % 练度), standins unchanged

The owner's decision of 2026-10-08: 「调配干员里自己设置吧,默认满潜满加成」. Official: the account's potential (调度手册);
the 自持有 bonus to owned operators of the same name, initial and elite alike (activity_table cultivateEffectList).

- shared/protocol.js: room.loadout { entries, ops? } — `ops` = { [charId]: { potential?: 1–6, cultivate?: 0–3 } }
  (isLoadoutOps, ≤ 256), checkLoadoutOps (strict like checkLoadout: an operator of the 干员调配 roster or the 自选 owned
  pool — cultivationCharIds; defaults dropped, entries stored complete); absent = none set.
- server/lobby.js / net.js: checked with the entries (either refused ⇒ nothing stored), session.ops / seat.ops /
  seats[].ops, match.setLoadout(playerId, loadout, ops) in INFO_CHECK only.
- PlayerState: ops re-checked and frozen (bots none); cultivationFor (shared/potential.js cultivationOf: null for a
  补位 stand-in or a prototype 自选 pick); loadoutFor carries potential / cultivate; battleInput states `potential` /
  `cultivate` for every owned operator (the chess as itself, an owned 自选 pick) — an old seat without ops and every bot
  at 6 / 3; the 自选 summons' hand count follows the owner's potential (望's 棋子 6 below 潜能3); m.private.ops; the
  scouting UnitInfo carries potential (below 6) / cultivate.
- tools/golden.mjs: the direct-spec scenarios field the default player (owned operators at 潜能 6 / 练度 3; stand-ins
  and prototype picks neither), as PlayerState does.
- Golden: every battle with an owned operator moves — ATK / DEF / max HP ×1.1 from the first second (roster-001:
  角峰 ends at 3527 HP instead of 3115; the first differing snapshot window is 0–10 s in every moved battle); the
  matches part at round 1–8 (battle results, then the bots' boards). Unchanged: the 10 stand-in battles (a stand-in has
  no 练度) and diy-135…138 (the four prototype 自选 battles). No engine error.
- Docs: design/match.md §16 (潜能 / 练度), design/network.md (room.loadout ops).
- Tests: test/match/potential.test.js (new, 4): the ops wire / data check, battle input of a human (settings, defaults,
  stand-in none, elite shares the charId), an old seat and a bot at the defaults, spec → battle, scouting units; 自选
  owned pick vs prototype and 望's hand count; Match.setLoadout in INFO_CHECK only, a refusal changes nothing.
  test/lobby-loadout.test.js (+2): strict validation, storage beside the loadout, an older client's message, the real
  match's briefing (m.private.ops) and lock. test/match/runner.test.js / diy-shop.test.js expect the new fields.
This commit is contained in:
sganggs committed 2026-10-08 20:52:30 +08:00
1 parent ec0a2fc669
commit 96d9441ff1
23 files changed
+6889 -6528

No files matched your search

+8 -3
View File
@@ -14,12 +14,16 @@
// opts.modeId string modeIdFor(mode, difficulty), e.g. 'mode_multi_hard'
// opts.seats Array<{ seat: 0..3, playerId: string, name: string, isBot: boolean, connected: boolean,
// loadout?: { [baseChessId]: { skill: index, module: uniEquipId|'none'|null } } | null,
// ops?: { [charId]: { potential: 1–6, cultivate: 0–3 } } | null,
// notOwned?: string[] | null,
// diy?: { [slotBaseId]: { charId, skillIndex, uniEquipId } } | null }>
// sorted by seat, 1–4 entries, ≥ 1 human; solo ⇒ exactly 1 human and no bots.
// Bot playerIds start with 'ai_'. Seat indexes may have gaps (e.g. seats 0 and 2).
// `loadout` (DESIGN §16, optional): the human's operator loadout, already checked by the lobby
// (shared/protocol.js checkLoadout); PlayerState re-checks it against opts.data and ignores it for bots.
// `ops` (0.2.2, optional): the human's per-operator 潜能 / 练度 (checkLoadoutOps; missing = 潜能 6, 精英2
// Lv.60 — the owner's decision of 2026-10-08), re-checked and changed like the loadout (INFO_CHECK);
// bots fight with the defaults.
// `notOwned` (0.2.0 补位, optional): the base chess ids the human marked as not owned (干员持有) — fixed
// for the match; those chess fight as their stand-ins (PlayerState setNotOwned; bots own everything).
// `diy` (0.2.0 自选编队, optional): the human's 自选 picks (shared/protocol.js checkDiyPicks) — fixed for
@@ -53,9 +57,10 @@
// (a resync request). May be called without a preceding onDisconnect. Resend full
// state: m.public, m.private and, if a battle is on (client-side combat), the
// b.start of the field the player is on / watching (server-run mode: m.field + b.snap).
// setLoadout(playerId, loadout) → { ok } | { error, detail? } (DESIGN §16; optional for the platform) a new checked
// operator loadout from room.loadout. Accepted only during INFO_CHECK (the briefing's
// 干员调配 entry); afterwards the match's loadout is locked (WRONG_PHASE).
// setLoadout(playerId, loadout, ops?) → { ok } | { error, detail? } (DESIGN §16; optional for the platform) a new
// checked operator loadout (and its 潜能 / 练度 `ops`, 0.2.2) from room.loadout. Accepted only
// during INFO_CHECK (the briefing's 干员调配 entry); afterwards the match's loadout is locked
// (WRONG_PHASE).
// onLeave(playerId) The human quit permanently (g.leave, room.leave, or the 10-minute reconnect
// window expired). They will never return under this playerId in this match;
// treat as quit (AI takes over / eliminated per DESIGN). No onDisconnect follows.