feedback5 WK: 自选编队 on the server — room.diy { picks } (lenient: illegal picks dropped, only malformed input is BAD_MSG; stored per session / seat; a match takes the picks its seat had at the start, a change during a match is for the next one; welcome lists diyKitted), PlayerState.diy with the player's data view (a slotted DIY slot is its operator for every rule: bonds from its factions, no 特质, the pick's skill and module, the slot's tier / price / merge), a per-player stock per slotted piece (8 at tier 5, 5 at tier 6 [ASSUMED]; never in the shared pool; none when every bond of it is banned), DIY pieces in that player's shop rolls and reward offers once the 调度中心 reaches the slot's level, battleInput diy, m.private.diy / diyBanned, prep scouting and results name the operator, 信标 never sends a 自选 piece to a teammate [ASSUMED]

The owner's decisions of 2026-10-05 (picks, slots, shop per player, bans); research 0.2.0 §2. Golden unchanged (no scenario slots a DIY pick).
Tests: test/match/diy-shop.test.js (9), test/lobby-diy.test.js (4).
This commit is contained in:
sganggs committed 2026-10-05 23:35:01 +08:00
1 parent 742f478526
commit 4bee542e61
26 files changed
+1026 -105

No files matched your search

+8 -5
View File
@@ -1,6 +1,6 @@
// server/match/player/economy.js — PlayerState methods: the economy — funds, spending (onSpend), prep-side bond layer
// gains (clamped by layerGainRoom), the shop (onPrice prices, copy-weighted rolls from the shared pool, freeze) and its
// intents: g.buy, g.refresh, g.freeze, g.levelUp, g.sell.
// gains (clamped by layerGainRoom), the shop (onPrice prices, copy-weighted rolls from the shared pool plus the player's
// own 自选 stock — player/diy.js —, freeze) and its intents: g.buy, g.refresh, g.freeze, g.levelUp, g.sell.
// Installed on PlayerState.prototype by server/match/PlayerState.js (a method container: never instantiated; `this` is
// the player state).
@@ -61,7 +61,8 @@ export class PlayerEconomy {
}
_rollChessSlot() {
const id = this.m.pool.roll(this.m.rngShop, { maxTier: this.shop.level });
// the slotted 自选 pieces join the draw once the 调度中心 reaches their slot's level (player/diy.js diyRollEntries)
const id = this.m.pool.roll(this.m.rngShop, { maxTier: this.shop.level, extra: this.diyRollEntries() });
return id ? { kind: 'chess', id, basePrice: this.gd.chessPrice(id), frozen: false, sold: false } : null;
}
@@ -115,10 +116,12 @@ export class PlayerEconomy {
let piece;
if (slot.kind === 'chess') {
const rec = this.gd.chess(slot.id);
if (!rec) return fail(ERR.BAD_TARGET);
// a DIY slot sells only as this player's own 自选 piece (a slotted slot's record carries `diyFor`)
if (!rec || (rec.isDiy && !rec.diyFor)) return fail(ERR.BAD_TARGET);
const base = this.gd.baseIdOf(slot.id);
const need = rec.isGolden ? this.gd.goldenCopies : 1;
if (this.m.pool.has(base) && this.m.pool.left(base) < need) return fail(ERR.SOLD_OUT);
const pool = this.poolOf(base);
if (pool.has(base) && pool.left(base) < need) return fail(ERR.SOLD_OUT);
if (handFull) return fail(ERR.HAND_FULL);
this.spend(price);
slot.sold = true;