feedback1 integrate: the bot's bench shed never sells a piece of this prep

The buy loop's pre-emptive shed sold the single it had just bought back for 1 (the buy picks
by lineup gain, the shed by piece value). Same-prep buy then sell in co-op 绝境 seeds 21-26:
29 -> 3. Seeds 1-40: solo 绝境 22 -> 24 wins, co-op 9 -> 10 on the 22-match bounty lists;
decision time unchanged.
This commit is contained in:
sganggs committed 2026-10-03 11:52:52 +08:00
1 parent 14e292f491
commit a80b163aed
3 files changed
+42 -3

No files matched your search

+9
View File
@@ -374,6 +374,15 @@ rehearsal 3, before → after: solo 绝境 27 → 27 wins, LP left 17.9 → 18.4
→ 24 / 57 ms solo and 16 / 36 → 16 / 36 ms co-op; the whole prep with its rehearsal wall clock 80 / 148 → 78 / 153 ms and
74 / 152 → 73 / 151 ms.
**0.1.1 after the QA (DESIGN §21.6, §21.19).** Seeds 1–40, rehearsal 3 (wins solo 绝境 / co-op 绝境, 4 AI): the merged
build `dbd45c8` 27 / 11; with WB's 22-match bounty lists (a rule change: R11 is a 悬赏决策 in 14 of 22 and every R11 bounty
list holds a 特异III giant) the same bot 22 / 9 (LP left 16.3 / 10.9); the residual sim fixes change no outcome (22 / 9);
the bench-shed fix (the buy loop's shed never sells a piece that came this prep) 24 / 10 (LP left 17.4 / 13.1, co-op
leaks per match 217 → 210). 0.1.0 on the same seeds: 19 / 5. The 29 same-prep buy → sell of co-op 绝境 (2 AI 托管 + 2
bots, seeds 21–26) drop to 3 (a reward pick's room, `arrange`, `sellJunk`). Decision time unchanged (one thread, seeds
1–3, both builds side by side): the whole prep p50 / p95 81 / 187 → 81 / 184 ms solo and 71 / 153 → 71 / 129 ms co-op,
heuristics CPU 36 / 88 → 35 / 86 ms and 16 / 40 → 16 / 40 ms.
---
## 6. Match follow-ups shipped with this pass
+7 -3
View File
@@ -1166,8 +1166,9 @@ function refreshValue(m, ps, ctx) {
/**
* Sell the weakest bench chess (temp and hand; the hand only with `handOnly`) that is not part of a merge pair (or
* anything when keepPairs is false) and does not carry a 突变细胞 (its transformation after the battle is the point).
* `keepFresh`: never a piece that came this prep (`boughtRound`: bought for the lineup, an elite just merged, a reward).
*/
function sellWeakestHand(m, ps, { keepPairs = true, below = Infinity, handOnly = false } = {}) {
function sellWeakestHand(m, ps, { keepPairs = true, below = Infinity, handOnly = false, keepFresh = false } = {}) {
const ctx = context(m, ps);
let worst = null;
let worstV = Infinity;
@@ -1175,6 +1176,7 @@ function sellWeakestHand(m, ps, { keepPairs = true, below = Infinity, handOnly =
if (!p || p.kind !== 'chess' || (p.items || []).some((it) => isMutationCell(m.gd, it.id))) continue;
const base = m.gd.baseIdOf(p.id);
if (keepPairs && !m.gd.isGolden(p.id) && (ctx.copies.get(base) || 0) >= 2) continue;
if (keepFresh && p.boughtRound === m.round) continue;
const v = pieceValue(m, ps, p, ctx);
if (v < worstV) { worstV = v; worst = p; }
}
@@ -1375,9 +1377,11 @@ function* buyLoopSteps(m, ps, { fillOnly = false, maxRefreshes = 0 } = {}) {
const boardFull = ownedN >= ps.deployCap;
// the first pass fills the board, then completes the merges it can afford — before any level-up spends the funds
const mergesOnly = fillOnly && boardFull;
// keep room for merges: a crowded bench sheds its weakest single
// keep room for merges: a crowded bench sheds its weakest single — never one that came this prep (the buy picks by
// lineup gain, the shed by piece value: it used to sell the single just bought for 3–5 back for 1; QA of the 0.1.1
// bots). The shed is pre-emptive (one slot is still free), so with only fresh singles it waits for the next prep.
const used = ps.hand.filter(Boolean).length;
if (used >= gd.benchSize - 1) sellWeakestHand(m, ps);
if (used >= gd.benchSize - 1) sellWeakestHand(m, ps, { keepFresh: true });
let best = -1;
let bestS = 0;
let bestMerges = false;
+26
View File
@@ -316,3 +316,29 @@ test('cost guard: the prep heuristics of a late-round 4-bot match stay cheap (re
assert.equal(m.errorCount, 0);
m.dispose();
});
test('bench shed: the buy loop never sells a piece that came this prep (it used to sell the single it had just bought back for 1)', async () => {
// QA of the 0.1.1 bots: co-op 绝境, 2 humans on AI 托管 + 2 bots, seeds 21–23 — 29 same-prep buy → sell in 6 matches,
// every one the buy loop's pre-emptive bench shed (the buy picks by lineup gain, the shed by piece value)
const { PlayerState } = await import('../../server/match/PlayerState.js');
const oSell = PlayerState.prototype.sell;
const shed = [];
PlayerState.prototype.sell = function (uid) {
const loc = this.find(uid);
const p = loc && loc.piece;
if (p && p.boughtRound === this.m.round && /buyLoopSteps/.test(new Error().stack)) shed.push(`${p.id} R${this.m.round}`);
return oSell.call(this, uid);
};
try {
for (const seed of [21, 22, 23]) {
const h = makeMatch({ mode: 'coop', difficulty: 'HARD', humans: 2, bots: 2, seed, botRehearsal: 0 });
h.start();
h.autoHumans();
const m = h.m;
h.run(() => h.ended != null, { maxSteps: 3e7 });
assert.equal(m.errorCount, 0);
m.dispose();
}
} finally { PlayerState.prototype.sell = oSell; }
assert.deepEqual(shed, [], 'no piece of this prep sold by the shed');
});