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

+5 -3
View File
@@ -58,18 +58,20 @@ export class MatchPlatform {
/**
* room.loadout during the match (DESIGN §16): only while INFO_CHECK runs (the briefing's 干员调配 entry); the lobby
* already checked it against the data (PlayerState.setLoadout re-checks it).
* already checked it against the data (PlayerState.setLoadout re-checks it). `ops` (0.2.2): the per-operator 潜能 /
* 练度 that come with it (undefined = unchanged).
* @param {string} playerId
* @param {Record<string, { skill: number, module: string|null }> | null} loadout
* @param {Record<string, { potential: number, cultivate: number }> | null} [ops]
* @returns {{ ok: true } | { error: string, detail?: string }}
*/
setLoadout(playerId, loadout) {
setLoadout(playerId, loadout, ops = undefined) {
const ps = this.players.get(playerId);
if (!ps || ps.isBot || ps.left) return fail(ERR.NOT_IN_ROOM);
if (this.disposed || this.ended || this.phase !== PHASE.INFO_CHECK) return fail(ERR.WRONG_PHASE, 'loadout locked for this match');
let res = OK;
this.guard(() => {
if (!ps.setLoadout(loadout)) { res = fail(ERR.BAD_TARGET, 'loadout does not match the game data'); return; }
if (!ps.setLoadout(loadout, ops)) { res = fail(ERR.BAD_TARGET, 'loadout does not match the game data'); return; }
this.markPrivate(ps);
});
return res;
+14 -1
View File
@@ -13,6 +13,17 @@ import { timelineAt } from '../fields.js';
import { bossFieldPlacement } from '../finalAssault.js';
import { battleProgress } from '../../sim/spec.js';
/**
* A scouted operator's potential (below 6) and 练度 (0.2.2), like the sim's UnitInfo: PlayerState.loadoutFor's
* `potential` / `cultivate` (null for a stand-in or a prototype 自选 pick: neither field).
*/
function cultivationInfo(lo) {
const out = {};
if (lo && Number.isInteger(lo.potential) && lo.potential < 6) out.potential = lo.potential;
if (lo && Number.isInteger(lo.cultivate)) out.cultivate = lo.cultivate;
return out;
}
export class MatchViews {
statusOf(ps) {
if (ps.left) return 'left';
@@ -209,7 +220,7 @@ export class MatchViews {
const rec = piece.kind === 'token' ? gd.token(piece.id) : ps.fieldRecord(chess);
const assets = (rec && rec.assets) || {};
// DESIGN §16: the skill / module THIS player's operator fights with (the scout's detail card shows it, like the
// sim's UnitInfo in a shared field); moduleId only for an elite
// sim's UnitInfo in a shared field); moduleId only for an elite; 0.2.2 its potential (below 6) and 练度
const lo = piece.kind === 'chess' && chess ? ps.loadoutFor(chess) : null;
units.push({
id: piece.uid, uid: piece.uid, kind: piece.kind === 'token' ? 'token' : 'op', side: 'ally', ownerId: ps.playerId, defId: piece.id,
@@ -222,6 +233,7 @@ export class MatchViews {
items: piece.kind === 'chess' && Array.isArray(piece.items) && piece.items.length ? piece.items.map((it) => it.id) : undefined,
standInFor: rec && rec.standInFor ? rec.standInFor : undefined,
diy: this._diyInfo(ps, piece),
...cultivationInfo(lo),
});
}
return units;
@@ -281,6 +293,7 @@ export class MatchViews {
items: piece.kind === 'chess' && Array.isArray(piece.items) && piece.items.length ? piece.items.map((it) => it.id) : undefined,
standInFor: standIn && standIn.standInFor ? standIn.standInFor : undefined,
diy: this._diyInfo(ps, piece),
...cultivationInfo(lo),
});
};
for (let i = 0; i < ps.hand.length; i++) {