feedback5 WE: the 整备区 rules of PRTS 卫戍协议/帮助 §手牌区 (GitHub #82, second part) — a full regular hand refuses every purchase and reward pick, also one whose copy would complete a merge at once ("当常规手牌区全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入等通常情况下会增加手牌的操作"; BWIKI 盟约 says the same with no exception; the reporter checked it in the official game), and a free regular slot pulls the 临时整备区 in at once ("常规手牌区出现空位时自动移入"): PlayerState._fillHandFromTemp runs in every recompute, the temp row right to left (the order it fills, [ASSUMED]) into the hand's free slots right to left — after a sale, a deployment from the hand, a merge that consumed hand copies, an item equipped / destroyed / used, a summon stack removed with its owner. invariants.js: temp holds pieces only while the hand is full. The client's refusal path follows (gameLogic.shopBlockReason: 整备区已满 on every card — a one-line change in a file the R5 split moves). Bots: a full hand never tries a purchase, an affordable third copy of a held pair sheds the weakest hand single first (never one of this prep), resolveTemp decides temp right to left. Tests: test/match/feedback5-hand.test.js; merge / hardening / playtest6-elite-to-board / economy / prep-bench / uiFixes updated to the rule. Docs: META §1 Hand, DESIGN §6, PLAYING §3, research 00-INDEX / 01 (the old "a merge-completing purchase is allowed" corrected). Golden: 3 match scenarios moved (coop2-NORMAL-3, solo-HARD-9-boosted, coop2-ABYSS-10-boosted — bot seats that reached a full hand: the merge-completing purchase refused or a single shed first, temp pieces moving in by themselves); every battle digest unchanged

This commit is contained in:
sganggs committed 2026-10-05 18:17:08 +08:00
1 parent b1ec04b58c
commit db1b44bb71
22 files changed
+356 -101

No files matched your search

+22 -10
View File
@@ -67,7 +67,7 @@ test('3 copies (board + hand + bought) merge into 1 elite on the board copy\'s t
m.dispose();
});
test('elites never merge; 风丸 merges with 2 copies; a full hand still buys the merge-completing copy', () => {
test('elites never merge; 风丸 merges with 2 copies; a full hand refuses the merge-completing copy too (GitHub #82)', () => {
const { m, ps } = prep(22);
const id = chessOfTier(2).find((x) => m.pool.has(x) && x !== 'chess_char_2_11_a');
const golden = DATA.chess[id].goldenId;
@@ -83,7 +83,9 @@ test('elites never merge; 风丸 merges with 2 copies; a full hand still buys th
m.handle('p_0', { t: 'g.buy', slot: 0 });
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess.chess_char_2_11_a.goldenId), '风丸 elite after 2 copies');
}
// full hand + 2 copies in the hand: buying the 3rd completes the merge (net −1)
// full hand + 2 copies in the hand: the 3rd copy is refused like any purchase (PRTS 卫戍协议/帮助 §手牌区 "当常规手牌区
// 全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入"; GitHub #82); a sale frees a slot, then the purchase
// completes the merge (the elite in the hand)
for (const p of ps.hand) if (p) ps.returnCopies(p);
ps.hand.fill(null);
const x = chessOfTier(1).filter((c) => m.pool.has(c));
@@ -95,15 +97,19 @@ test('elites never merge; 风丸 merges with 2 copies; a full hand still buys th
stock(m, ps, x[20] || x[x.length - 1], 1);
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 1 }), { error: ERR.HAND_FULL });
stock(m, ps, target, 0);
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL }, 'the merge-completing copy too');
assert.ok(!ps.hand.some((p) => p && p.id === DATA.chess[target].goldenId));
const filler = ps.hand.find((p) => p && p.id !== target);
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: filler.uid }), { ok: true });
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
assert.ok(ps.hand.some((p) => p && p.id === DATA.chess[target].goldenId));
assert.equal(ps.hand.filter(Boolean).length, 9);
assert.equal(ps.hand.filter(Boolean).length, 8, '10 − the sold filler − 2 copies + the elite');
assert.ok(ps.tempEmpty);
checkInvariants(m);
m.dispose();
});
test('merge from two board copies with a full hand: the elite takes the copy that deploys first (no overflow, one tile freed)', () => {
test('merge from two board copies with a full hand (a gained copy; a purchase is refused): the elite takes the copy that deploys first (no overflow, one tile freed)', () => {
const { m, ps } = prep(23);
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
const id = ids[0];
@@ -115,7 +121,8 @@ test('merge from two board copies with a full hand: the elite takes the copy tha
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
stock(m, ps, id);
assert.equal(ps.deployCount, 2);
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL }, 'a full hand refuses the purchase (GitHub #82)');
assert.ok(ps.acquireChess(id, { source: 'grant' }), 'a gain (an effect\'s grant) completes the merge');
// deploy order = by column from the left (col asc, then row desc; Battle.start): (9,3) before (9,4) — [ASSUMED] the copy
// the battle deploys first
const elite = ps.board.get('9,3');
@@ -134,12 +141,14 @@ test('merge whose copies are not deployed (temp) with a full hand overflows the
const { m, ps } = prep(23);
const ids = chessOfTier(1, (c) => c.position === 'MELEE').filter((c) => m.pool.has(c));
const id = ids[0];
give(m, ps, id, 'temp', 0);
give(m, ps, id, 'temp', 1);
const others = chessOfTier(2).filter((c) => m.pool.has(c));
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
stock(m, ps, id);
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
// with the hand full they wait in temp (a free hand slot would pull them in, PRTS 卫戍协议/帮助 §手牌区)
give(m, ps, id, 'temp', 0);
give(m, ps, id, 'temp', 1);
assert.equal(ps.temp.filter(Boolean).length, 2);
// a gain completes the merge (a purchase is refused with a full hand)
assert.ok(ps.acquireChess(id, { source: 'grant' }));
assert.ok(ps.temp.some((p) => p && p.id === DATA.chess[id].goldenId), 'elite overflowed into temp');
assert.equal(ps.board.size, 0, 'nothing was deployed');
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { error: ERR.TEMP_NOT_EMPTY });
@@ -187,11 +196,14 @@ test('equipment: max 2 (a third replaces the oldest), identical normal items mer
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_1_02_e_b'), 'golden item in the hand');
assert.ok(!a.items.some((x) => x.id === 'chess_item_1_02_e_a'));
assert.equal(ps.stats.itemMerges, 1);
// buying an item whose twin is in the hand merges immediately (and works with a full hand)
// buying an item whose twin is in the hand merges immediately — but a full hand refuses the purchase all the same
// (PRTS 卫戍协议/帮助 §手牌区, GitHub #82)
const i5 = giveItem(m, ps, 'chess_item_2_03_e_a');
const others = chessOfTier(1).filter((c) => m.pool.has(c));
for (let i = 0; ps.hand.some((p) => p == null); i++) give(m, ps, others[i]);
ps.shop.slots[0] = { kind: 'item', id: 'chess_item_2_03_e_a', basePrice: 2, frozen: false, sold: false };
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { error: ERR.HAND_FULL });
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: ps.hand.find((p) => p && p.kind === 'chess' && p !== a).uid }), { ok: true });
assert.deepEqual(m.handle('p_0', { t: 'g.buy', slot: 0 }), { ok: true });
assert.ok(ps.hand.some((p) => p && p.id === 'chess_item_2_03_e_b'));
assert.ok(!ps.find(i5.uid));