feedback7 dober: 教鞭 is the official personal choice of three 战术特训 cards again, not a random one (PR #353 by @Sukvii, the owner's decision of 2026-10-08); golden: coop2-NORMAL-8-serverrun and coop2-ABYSS-10-boosted move
Seen: the Art 教鞭 (杜宾's strategy) drew three 战术特训 cards and added a random one — a documented simplification (docs/META.md §2.5: a personal choice would need its own phase, protocol message, timer and AI takeover). Official: act2autochess choice_event hunter_band_1 is a PERSONAL_CHOOSE; PRTS 下半 记录 §法术 教鞭 "使用后销毁,于3个战术特训的悬赏任务中选择一项". The owner decided on 2026-10-08 to follow it. Taken from PR #353 (code and tests by @Sukvii), ported to the split 0.2.2 modules. Now: ctx.offerBountyChoice -> Match.offerBountyChoice keeps up to three different mode-eligible cards (15 of 20 in 标准 / 险境, 20 in 绝境 / 终极; one meta-rng shuffle) in PlayerState.personalChoice, m.private only (same cards after a reconnect); g.choice { idx, choiceId } confirms it (Match.pickPersonalChoice: owner, id, round, index; a request without a choiceId is still the public draft, a stale id never falls through to it). No new phase or timer: Ready is refused while it is open, the prep's deadline picks at random before the temp pieces and Ready, a bot or a 托管 seat picks with the bounty scorer (botPickCard, the bots' seeded rng; at the prep's start, after the cast, before Ready; the scheduler's fallback picks at random if a step fails). The client reuses ChoiceOverlay (personal: no order, no turns, the prep's countdown) over a locked prep, the Ready reasons, the mock harness; six UI strings in en / ja / ko / zh-TW. An eliminated player's offer goes with it; invariants.js flags an offer outside its owner's own open prep. Not taken from the PR: its §25.24 in the released docs/history/0.2.0.md (the 0.2.2 section is the integrator's), its edits of the Ready toggle beyond the personal-choice case, its tail placement of the i18n keys. Golden: coop2-NORMAL-8-serverrun (ai_0 杜宾 casts 教鞭 from R2 on and now scores the three cards: teamLp 39 -> 34, virtualMs 121400 -> 118400, rng shop 511 -> 483 / bots 18063 -> 18070 / meta 112 -> 103) and coop2-ABYSS-10-boosted (ai_1 杜宾: virtualMs 121650 -> 124650, shop 590 -> 589 / bots 22075 -> 22096 / meta 141 -> 154); both first differ in round 2, every other scenario and family is unchanged; node tools/golden.mjs --twice is identical. Tests: items.test.js (three distinct cards, the tapped one applied once, nothing before the confirm, a refused cast keeps the Art / count / rng / uid, an eliminated player's offer), economy.test.js (Ready refused, the deadline picks before endPrep, solo prep untimed), connection.test.js (two players' offers, reconnect, foreign / duplicate / old ids, an offline player's pick at the deadline), bot.test.js (托管 takeover incl. the failing-step fallback), bot-smarter.test.js (the scorer's pick, deterministic per seed, no rng draw when nothing is pending), followups.test.js (R14 / R15), draft.test.js, unitStats.test.js (protocol), hardening.test.js (invariant), gameLogic / playtest3 / playtest4 (view model, Ready reason, ChoiceView, actions), mock.e2e.test.js and playtest2.real.e2e.test.js (browser, SP_E2E=1).
This commit is contained in:
1 parent
05eb2b807f
commit
56a7b918a9
48 files changed
+901
-159
No files matched your search
@@ -222,6 +222,18 @@ describe('3: the temp overflow row (临时整备区)', () => {
|
||||
assert.deepEqual(tempInfo(null), { count: 0, items: 0 });
|
||||
});
|
||||
|
||||
test('Ready shows the personal-choice reason and describes it even with no temp pieces', () => {
|
||||
for (const temp of [[], [{ uid: 1, kind: 'item' }]]) {
|
||||
const v = ReadyToggle({ priv: { ...priv(temp), canReady: false, personalChoice: { id: 'choice.1' } }, onToggle() {} });
|
||||
const nodes = [...walk(v)];
|
||||
const btn = nodes.find((n) => n.type === 'button');
|
||||
assert.equal(btn.props.disabled, true);
|
||||
assert.equal(btn.props['aria-describedby'], 'readywrap-why');
|
||||
assert.equal(textOf(nodes.find((n) => hasClass(n, 'readywrap__why'))), '请先完成教鞭选择');
|
||||
assert.equal(nodes.find((n) => n.type?.name === 'Tooltip').props.text, '请先完成教鞭选择');
|
||||
}
|
||||
});
|
||||
|
||||
test('the row frame: the outer corners of both end tiles, the label on the side with room (mirrored Final Assault prep too)', () => {
|
||||
// tileScreen polys: [back-left, back-right, front-right, front-left] (render/app.js tileScreen corner order)
|
||||
const tile = (cx, y, w = 100, d = 60) => ({ x: cx, y, s: w, poly: [[cx - w / 2 + 6, y - d / 2], [cx + w / 2 - 6, y - d / 2], [cx + w / 2, y + d / 2], [cx - w / 2, y + d / 2]] });
|
||||
@@ -258,7 +270,7 @@ describe('3: the temp overflow row (临时整备区)', () => {
|
||||
test('while the ready button shows its reason the effects column moves down a line (on phones the ready count ran into it)', () => {
|
||||
const game = read('public/js/screens/game.js');
|
||||
// the same condition as ReadyToggle's reason: PREP, alive, not ready, pieces in the row
|
||||
assert.match(game, /const readyWhy = phase === PHASE\.PREP && alive && !priv\?\.ready && temp\.count > 0;/);
|
||||
assert.match(game, /const readyWhy = phase === PHASE\.PREP && alive && !priv\?\.ready && \(hasPersonalChoice \|\| temp\.count > 0\);/);
|
||||
assert.match(game, /readyWhy && 'has-readywhy'/);
|
||||
const css = read('public/css/screens/game.css');
|
||||
assert.match(css, /\.gm__effects \{ position: absolute; right: \.3rem; top: 2\.4rem; \}/);
|
||||
@@ -389,6 +401,18 @@ describe('9: busy indicators and data loading', () => {
|
||||
assert.ok(![...walk(landed)].some((n) => hasClass(n, 'spcard__busy')));
|
||||
});
|
||||
|
||||
test('choice requests carry the personal ID while global requests keep their existing shape', async () => {
|
||||
const { net } = await import('../../public/js/net.js');
|
||||
const { actions } = await import('../../public/js/ui/gameActions.js');
|
||||
const request = net.request, sent = [];
|
||||
net.request = async (t, fields) => { sent.push([t, fields]); };
|
||||
try {
|
||||
assert.equal(await actions.choice(0), true);
|
||||
assert.equal(await actions.choice(2, 'seed.choice.3'), true);
|
||||
assert.deepEqual(sent, [['g.choice', { idx: 0 }], ['g.choice', { idx: 2, choiceId: 'seed.choice.3' }]]);
|
||||
} finally { net.request = request; }
|
||||
});
|
||||
|
||||
test('the "作战结束,等待队友完成作战" hourglass stands still', () => {
|
||||
const css = read('public/css/screens/game.css');
|
||||
assert.doesNotMatch(css, /hg-flip/);
|
||||
|
||||
Reference in new issue
Block a user