feedback7 UIBACK: upgrading the 调度中心 opens the new shop slot EMPTY — the next refresh or round start fills it; 0.2.0's fill-at-once (464a823, item 19) reverted (GitHub #332 and PR #333 by @2321Robin, official recording BV1AXwuzdEys 1:39 — authored by them: the economy.js change, the rewritten level-slots test and the docs hunks applied as reviewed, without the PR's UPGRADE_FILLS_NEW_SLOTS flag and LOCAL RULING comment: the empty slot is the rule): the shop bar draws a null operator slot as a bare dashed frame (EmptyCard, inert scard--sold) instead of the SOLD OUT 「已招募」 frame, one new UI string in the four packs; META.md §3 and §7 and PLAYING.md say so; the dev mock opens the slot empty and survives null slots; test/ui/shop-empty-slot.e2e.test.js; golden: only matches moves, all 18 (the bots' shop RNG stream) — end results changed in solo-FUNNY-1 defeat→victory, solo-NORMAL-1 and solo-HARD-1 victory→defeat, solo-HARD-2 and solo-ABYSS-1 defeat→eliminated, coop2-NORMAL-14-standins defeat→victory (2 distinct stand-ins fielded: golden-standins.test.js asks ≥ 2); roster, bonds, fields, standins, diy unchanged
This commit is contained in:
1 parent
b4bc89ad4e
commit
4bb0c11e42
15 files changed
+691
-542
No files matched your search
@@ -1,8 +1,9 @@
|
||||
// Community report of 2026-10-06, item 19: 「升级商店获得新的商店位时用新卡补上,而不是空着」. The official tutorial's 休整期
|
||||
// page says 「升级:消耗资金升级调度中心。升级后将出现更多的商品栏位、可调度干员以及新装备」: the upgrade itself opens the
|
||||
// new level's extra slot. Until 0.2.0 the shop kept its old slot count until the next roll (a refresh or the round start),
|
||||
// so a level-up from 1 to 2 (险境: 3 → 4 operator slots) or 3 to 4 (4 → 5) showed no new card. Now the upgrade adds the
|
||||
// extra slots at once, each with a new card drawn at the new level, and leaves the cards already shown where they are.
|
||||
// GitHub #332 / PR #333 by @2321Robin (the footage of an official 1→2 upgrade: bilibili BV1AXwuzdEys 1:39): a 调度中心
|
||||
// upgrade opens the new level's extra slots at once but EMPTY — the official shop shows the new slot, draws no card into
|
||||
// it, and fills it with the next roll (a manual refresh or the round start). The official texts only name the slots: the
|
||||
// tutorial's 休整期 page 「升级后将出现更多的商品栏位」, PRTS 帮助 「增加刷新栏位」. 0.2.0 drew a card into each new slot instead
|
||||
// (item 19 of the community report of 2026-10-06, 「升级商店获得新的商店位时用新卡补上,而不是空着」, one uncorroborated remark);
|
||||
// before that the shop kept its old slot count until the next roll. Either way the upgrade leaves the cards shown where they are.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeMatch, DATA } from './harness.js';
|
||||
@@ -11,7 +12,7 @@ import { GameData } from '../../server/match/gamedata.js';
|
||||
const ids = (slots) => slots.map((s) => (s ? `${s.kind}:${s.id}${s.sold ? '(sold)' : ''}` : null));
|
||||
const tierOf = (id) => DATA.chess[id]?.tier ?? 0;
|
||||
|
||||
test('a level-up opens the new level\'s operator slot at once with a new card; the cards shown stay in place', () => {
|
||||
test('a level-up opens the new level\'s operator slot EMPTY; the cards shown stay in place; a refresh fills it', () => {
|
||||
const h = makeMatch({ mode: 'solo', difficulty: 'NORMAL', seed: 7, fake: true }).start();
|
||||
h.toPrep(1);
|
||||
const ps = h.ps('p_0');
|
||||
@@ -20,35 +21,37 @@ test('a level-up opens the new level\'s operator slot at once with a new card; t
|
||||
const s1 = ps.shop.slots.slice();
|
||||
assert.equal(s1.length, 4);
|
||||
assert.deepEqual(h.m.handle('p_0', { t: 'g.levelUp' }), { ok: true });
|
||||
// level 2: 4 operator slots + the item slot — the three cards and the item kept (the same objects), one new card
|
||||
// level 2: 4 operator slots + the item slot — the three cards and the item kept (the same objects), the new slot empty
|
||||
assert.equal(ps.shop.level, 2);
|
||||
assert.deepEqual(ps.shop.layout, { chess: 4, item: 1 });
|
||||
assert.equal(ps.shop.slots.length, 5);
|
||||
for (let i = 0; i < 3; i++) assert.equal(ps.shop.slots[i], s1[i], `operator card ${i} kept`);
|
||||
assert.equal(ps.shop.slots[4], s1[3], 'the item card stays after the operator cards');
|
||||
const added = ps.shop.slots[3];
|
||||
assert.ok(added && added.kind === 'chess' && !added.sold, `a new operator card: ${JSON.stringify(added)}`);
|
||||
assert.ok(tierOf(added.id) >= 1 && tierOf(added.id) <= 2, `drawn at the new level (tier ${tierOf(added.id)})`);
|
||||
assert.equal(added.frozen, false);
|
||||
assert.equal(ps.shop.slots[3], null, 'the new slot is open and empty');
|
||||
// the player sees it right away (m.private shop)
|
||||
assert.deepEqual(ids(ps.privateView().shop.slots), ids(ps.shop.slots));
|
||||
// 2 → 3 keeps 4 operator slots: nothing rerolled
|
||||
// a manual refresh fills every slot at the new level (the empty one included)
|
||||
assert.deepEqual(h.m.handle('p_0', { t: 'g.refresh' }), { ok: true });
|
||||
assert.equal(ps.shop.slots.length, 5);
|
||||
for (const s of ps.shop.slots) assert.ok(s && !s.sold, 'every slot carries a fresh card after the refresh');
|
||||
for (const s of ps.shop.slots) if (s.kind === 'chess') assert.ok(tierOf(s.id) >= 1 && tierOf(s.id) <= 2, `drawn at level 2 (tier ${tierOf(s.id)})`);
|
||||
// 2 → 3 keeps 4 operator slots: nothing rerolled, nothing added
|
||||
const s2 = ps.shop.slots.slice();
|
||||
h.m.handle('p_0', { t: 'g.levelUp' });
|
||||
assert.equal(ps.shop.level, 3);
|
||||
assert.deepEqual(ps.shop.slots, s2, 'no extra slot at level 3, no reroll');
|
||||
// 3 → 4: the fifth operator slot
|
||||
// 3 → 4: the fifth operator slot, empty again
|
||||
h.m.handle('p_0', { t: 'g.levelUp' });
|
||||
assert.equal(ps.shop.level, 4);
|
||||
assert.deepEqual(ps.shop.layout, { chess: 5, item: 1 });
|
||||
for (let i = 0; i < 4; i++) assert.equal(ps.shop.slots[i], s2[i]);
|
||||
assert.equal(ps.shop.slots[5], s2[4]);
|
||||
assert.ok(ps.shop.slots[4]?.kind === 'chess' && tierOf(ps.shop.slots[4].id) <= 4);
|
||||
assert.equal(ps.shop.slots[4], null, 'the new slot is open and empty');
|
||||
h.invariants();
|
||||
h.m.dispose();
|
||||
});
|
||||
|
||||
test('a bought card stays sold; a frozen shop freezes the new card too [ASSUMED], and the round start keeps it', () => {
|
||||
test('a bought card stays sold; the empty new slot has nothing to freeze; the round start fills the shop to the new level', () => {
|
||||
const h = makeMatch({ mode: 'solo', difficulty: 'NORMAL', seed: 11, fake: true }).start();
|
||||
h.toPrep(1);
|
||||
const ps = h.ps('p_0');
|
||||
@@ -58,35 +61,43 @@ test('a bought card stays sold; a frozen shop freezes the new card too [ASSUMED]
|
||||
assert.deepEqual(h.m.handle('p_0', { t: 'g.freeze' }), { ok: true });
|
||||
h.m.handle('p_0', { t: 'g.levelUp' });
|
||||
assert.equal(ps.shop.slots[0].sold, true, 'the bought slot is not refilled');
|
||||
const added = ps.shop.slots[3];
|
||||
assert.ok(added && !added.sold && added.frozen === true, 'the new card follows the freeze toggle');
|
||||
assert.equal(ps.shop.slots[3], null, 'the new slot is open and empty — nothing to freeze');
|
||||
const keep = ps.shop.slots.filter((s) => s && !s.sold && s.frozen).map((s) => s.id);
|
||||
assert.equal(keep.length, 4, 'two operator cards, the new one and the item');
|
||||
// the next round start keeps every frozen card (in place) and rolls only the sold slot
|
||||
assert.equal(keep.length, 3, 'two operator cards and the item carry the freeze toggle');
|
||||
// the round start fills every slot of the new level: frozen cards kept in place, the sold and the empty slot rolled
|
||||
h.drive(() => h.m.phase === 'PREP' && h.m.round === 2);
|
||||
assert.deepEqual(ps.shop.slots.slice(1).map((s) => s.id), keep);
|
||||
assert.equal(ps.shop.slots.length, 5);
|
||||
for (const s of ps.shop.slots) assert.ok(s, 'the round start leaves no empty slot');
|
||||
assert.deepEqual(ps.shop.slots.slice(1).map((s) => s.id).slice(0, 2), keep.slice(0, 2), 'the frozen operator cards kept');
|
||||
assert.equal(ps.shop.slots[4].id, keep[2], 'the frozen item kept after the operator slots');
|
||||
assert.notEqual(ps.shop.slots[0].id, undefined, 'the sold slot rerolled');
|
||||
h.invariants();
|
||||
h.m.dispose();
|
||||
});
|
||||
|
||||
test('every mode\'s slot table: a level-up adds exactly the slots the new level has more of (标准: 3,4,4,4,4,5)', () => {
|
||||
test('every mode\'s slot table: a level-up opens exactly the new positions, empty (标准: 3,4,4,4,4,5)', () => {
|
||||
for (const [mode, difficulty] of [['solo', 'FUNNY'], ['solo', 'NORMAL'], ['coop', 'FUNNY'], ['coop', 'ABYSS']]) {
|
||||
const h = makeMatch({ mode, difficulty, seed: 3, fake: true, humans: 1, bots: mode === 'coop' ? 1 : 0 }).start();
|
||||
h.toPrep(1);
|
||||
const ps = h.ps('p_0');
|
||||
const gd = h.m.gd;
|
||||
ps.funds = 500;
|
||||
let empty = 0;
|
||||
for (let lv = 2; lv <= gd.maxShopLevel; lv++) {
|
||||
const before = ps.shop.slots.slice();
|
||||
assert.deepEqual(h.m.handle('p_0', { t: 'g.levelUp' }), { ok: true });
|
||||
const { chess, item } = gd.shopSlots(lv);
|
||||
assert.equal(ps.shop.slots.length, chess + item, `${mode} ${difficulty} L${lv}`);
|
||||
const grew = chess - gd.shopSlots(lv - 1).chess;
|
||||
const kept = ps.shop.slots.filter((s) => before.includes(s)).length;
|
||||
assert.equal(kept, before.length, `${mode} ${difficulty} L${lv}: every shown card kept`);
|
||||
assert.equal(ps.shop.slots.length - before.length, grew + (item - gd.shopSlots(lv - 1).item));
|
||||
const grew = (chess - gd.shopSlots(lv - 1).chess) + (item - gd.shopSlots(lv - 1).item);
|
||||
const kept = ps.shop.slots.filter((s) => s && before.includes(s)).length;
|
||||
assert.equal(kept, before.filter((s) => s).length, `${mode} ${difficulty} L${lv}: every shown card kept`);
|
||||
// the empty positions accumulate: nothing fills them between the upgrades (no refresh / round start here)
|
||||
empty += grew;
|
||||
assert.equal(ps.shop.slots.filter((s) => !s).length, empty, `${mode} ${difficulty} L${lv}: the new positions are open and empty`);
|
||||
}
|
||||
// one refresh fills everything at the final level
|
||||
assert.deepEqual(h.m.handle('p_0', { t: 'g.refresh' }), { ok: true });
|
||||
for (const s of ps.shop.slots) assert.ok(s, `${mode} ${difficulty}: the refresh leaves no empty slot`);
|
||||
h.invariants();
|
||||
h.m.dispose();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user