From a80b163aed557581eaf41540daebe51bac859fd4 Mon Sep 17 00:00:00 2001 From: sganggs Date: Sat, 3 Oct 2026 11:52:52 +0800 Subject: [PATCH] feedback1 integrate: the bot's bench shed never sells a piece of this prep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/BALANCE.md | 9 +++++++++ server/match/bot.js | 10 +++++++--- test/match/bot-smarter.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/BALANCE.md b/docs/BALANCE.md index bb8e2b95..a1ac914f 100644 --- a/docs/BALANCE.md +++ b/docs/BALANCE.md @@ -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 diff --git a/server/match/bot.js b/server/match/bot.js index 99abf007..0109b4f0 100644 --- a/server/match/bot.js +++ b/server/match/bot.js @@ -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; diff --git a/test/match/bot-smarter.test.js b/test/match/bot-smarter.test.js index a019a4f9..5cb2771e 100644 --- a/test/match/bot-smarter.test.js +++ b/test/match/bot-smarter.test.js @@ -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'); +});