feedback1 WB: bounty drafts and the 机密商店 from 66 official screenshots (22 matches)

Bounty drafts: every 悬赏决策 event is a fixed card list and the draft shows
6 different cards of it, drawn with weight 1 + the official drafts of its
group the card showed in.
- R3: 9 of the 10 fixed sets seen (two new: 20_1 + 20_5, and one with 17_6);
  3 x I + 2 x II + 1 x III in 22 of 22. The unseen 10th set is built by a
  rule whose III is 10_6 or 20_6, the only two-battle cards no draft showed.
- R9: 6 groups of 9 cards, one per bossInitial event. The 鼠王 group came in
  14 of 22 matches (every grey-board match among them), so groups are picked
  by the matches they came in. Named bosses no longer always come together.
- R11: lists of 7 cards, the draft leaves out one (matches 1 / 6 / 21 / 22
  leave out four different cards of one list); 7 of 15 lists seen, the rest
  and the 4 partial lists' 7th cards built by rule. Families 14 / 4 / 4.

机密商店 (the user: "机密商店按官方改成可以重复吧"): choices.json shopDraft,
six slots drawn with replacement - VI, VI, V, 盟约之币 and twice
V / IV / III / 盟约之币, items weighted by the official cards they showed on.
A repeated item now comes in about 61 % of shops (official 3 of 4). Two
identical cards stay two cards (picks by index): E2E, overlay and a
headless-Chrome test (game-mock ?variant=shop, the official match 8 shop).
This commit is contained in:
sganggs committed 2026-10-03 07:42:20 +08:00
1 parent 7c13e38422
commit d1f8a49829
12 files changed
+799 -261

No files matched your search

+43 -3
View File
@@ -350,7 +350,7 @@ test('道具补给 E2E (co-op): 6 free normal items within the round window; the
m.dispose();
});
test('机密商店 E2E (solo HARD R11): 3 free normal items of any tier I–VI; a second copy merges into the 进阶 item', () => {
test('机密商店 E2E (solo HARD R11): 3 free normal items of the official composition; a second copy merges into the 进阶 item', () => {
const data = dataWith('mode_single_hard', 11, 'shop');
const h = makeMatch({ mode: 'solo', difficulty: 'HARD', humans: 1, seed: 42, data, registry: REG, fake: true });
const m = h.m;
@@ -372,22 +372,62 @@ test('机密商店 E2E (solo HARD R11): 3 free normal items of any tier I–VI;
m.dispose();
});
test('机密商店 draws every tier I–VI with duplicates allowed (generation, 30 drafts)', () => {
test('机密商店 draws the official composition with duplicates allowed (generation, 30 drafts; the evidence is test/match/feedback1-secret-shop.test.js)', () => {
const h = makeMatch({ mode: 'coop', difficulty: 'HARD', humans: 1, seed: 5, data: dataWith('mode_multi_hard', 11, 'shop'), registry: REG, fake: true });
const coin = DATA.choices.shopDraft.coin;
const tiers = new Set();
let dupes = 0;
for (let i = 0; i < 30; i++) {
const d = generateDraft(h.m.gd, h.m.rngDraft, 11, { stageId: h.m.stageId });
assert.equal(d.family, 'shop');
assert.equal(d.cards.length, 6);
assert.equal(d.cards.filter((c) => DATA.items[c.id].tier === 6).length, 2, 'two tier-VI items');
assert.ok(d.cards.some((c) => c.id === coin), 'a 盟约之币');
for (const c of d.cards) tiers.add(DATA.items[c.id].tier);
if (new Set(d.cards.map((c) => c.id)).size < d.cards.length) dupes++;
}
h.m.dispose();
assert.deepEqual([...tiers].sort(), [1, 2, 3, 4, 5, 6]);
assert.deepEqual([...tiers].sort(), [1, 3, 4, 5, 6], 'tiers III–VI and the tier-I 盟约之币');
assert.ok(dupes > 0, 'duplicates allowed');
});
test('机密商店 E2E (co-op): two identical cards are two cards — both can be taken, by index, and each picker gets the item', () => {
// six 变形同构体: every slot draws the one item `coin` names
const one = Object.values(DATA.items).find((i) => i.name === '变形同构体' && !i.isGolden).id;
const data = dataWith('mode_multi_hard', 3, 'shop');
data.choices = { ...data.choices, shopDraft: { ...data.choices.shopDraft, slots: Array.from({ length: 6 }, () => ({ coin: 1 })), coin: one } };
const h = makeMatch({ mode: 'coop', difficulty: 'HARD', humans: 2, bots: 2, seed: 43, data, registry: REG, fake: true });
const m = h.m;
h.start();
const sp = toDraft(h, 3);
assert.equal(sp.family, 'shop');
const pub = m.publicView().sp;
assert.deepEqual(pub.cards.map((c) => c.id), Array(6).fill(one), 'six identical offers');
assert.deepEqual(pub.cards.map((c) => c.idx), [0, 1, 2, 3, 4, 5], 'each its own index');
const funds = { p_0: h.ps('p_0').funds, p_1: h.ps('p_1').funds };
const picked = {};
for (let guard = 0; guard < 1000 && m.phase === 'SP_DRAFT'; guard++) {
const pid = m.spTurn();
if (pid !== 'p_0' && pid !== 'p_1') { h.sched.runNext(); continue; }
// a taken card is refused even though an identical one is still free
const taken = Object.keys(m.sp.taken).map(Number);
if (taken.length) assert.deepEqual(m.handle(pid, { t: 'g.choice', idx: taken[0] }), { error: 'SOLD_OUT' }, 'a taken index stays taken');
const idx = m.sp.cards.find((c) => m.sp.taken[c.idx] == null).idx;
assert.deepEqual(m.handle(pid, { t: 'g.choice', idx }), { ok: true });
picked[pid] = idx;
}
assert.equal(m.phase, 'PREP');
assert.notEqual(picked.p_0, picked.p_1, 'two different cards of the same item');
assert.equal(Object.keys(sp.taken).length, 4, 'every player took one of the identical cards');
for (const pid of ['p_0', 'p_1']) {
const ps = h.ps(pid);
assert.ok([...ps.hand, ...ps.temp].some((p) => p && (p.id === one || p.id === DATA.items[one].goldenId)), `${pid} holds the item`);
assert.equal(ps.funds, funds[pid], 'free');
}
matchInvariants(m);
m.dispose();
});
// =====================================================================================================================
// 战术决策 — prep-side cards