Files
Dune/tests/atreides-full-plan-window.test.ts
Quintine 61828150b9 Keep Atreides special inspection available until plan reveal
Allow native Advanced use after declining the first offer, including a sealed
target. Reuse projected card/target permission in persistent controls and
minimal bots; retain sealed plans and finish target-last public reveal once.

Source: authorized supplied physical27 F. Ordinary preparation priority and
target-first ordering remain provisional; no new reading acknowledgement.

Verified: 11 affected offline files126/126, types/lint, production build, five
actual control/gameplay/GameTable SSR journeys and eight legal profile paths.
No saved-room, D1, reset, restart or deployment operation.
2026-10-24 18:49:22 +11:00

128 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import {
createAtreidesFullPlanWindowFixture as fixture,
actAtreidesFullPlanWindow as act,
atreidesFullPlanWindowPlayer as player,
atreidesFullPlanWindowInventory as inventory,
finishAtreidesFullPlanWindow as finish,
} from './fixture-atreides-full-plan-window';
function rejected(game: Game, actor: string, action: Action, reason?: RegExp) {
const before = structuredClone(game);
if (reason) assert.throws(() => applyAction(game, actor, action), reason);
else assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
void test('actual paid setup and battle retain the special window after declining its first offer', () => {
const f = fixture();
assert.equal(f.offer.decision?.kind, 'fullPlanOffer');
assert.equal(f.game.decision, null);
assert.equal(f.game.battle?.revealed, false);
assert.deepEqual(f.game.battle?.plans, {});
assert.equal(viewGame(f.game, f.owner).atreidesFullPlan?.blocked, null);
assert.deepEqual(viewGame(f.game, f.owner).atreidesFullPlan?.cards, [f.karama]);
assert.equal(viewGame(f.game, f.guild).atreidesFullPlan, null);
assert.deepEqual(inventory(f.game), f.originalInventory);
const inspecting = act(f.game, f.owner, f.inspect);
assert.equal(inspecting.decision, null);
rejected(inspecting, f.owner, f.ownPlan, /requested by special prescience/);
const read = act(inspecting, f.opponent, f.opponentPlan);
assert.equal(read.decision, null);
assert.equal(read.battle?.revealed, false);
assert.ok(viewGame(read, f.owner).battle?.fullPlanInsight);
assert.equal(finish(read, f).phase, 7);
});
void test('a target sealed before activation is immediately readable without changing that plan or spending support early', () => {
const f = fixture();
const sealed = act(f.game, f.opponent, f.opponentPlan);
const before = structuredClone(player(sealed, f.opponent));
assert.equal(viewGame(sealed, f.owner).battle?.plans[f.opponent], undefined);
const read = act(sealed, f.owner, f.inspect);
assert.deepEqual(read.battle?.plans, sealed.battle?.plans);
assert.deepEqual(player(read, f.opponent), before);
assert.equal(read.response, null);
assert.equal(read.decision, null);
const insight = viewGame(read, f.owner).battle?.fullPlanInsight;
assert.deepEqual(insight?.plan, sealed.battle!.plans[f.opponent]);
assert.deepEqual(insight?.cards.map(card => card.id), [f.weapon]);
assert.equal(player(read, f.owner).specialKaramaUsed, true);
assert.equal(read.discard.filter(card => card.id === f.karama).length, 1);
assert.deepEqual(inventory(read), f.originalInventory);
for (const actor of [f.opponent, f.guild]) {
assert.equal(viewGame(read, actor).battle?.fullPlanInsight, null);
assert.equal(viewGame(read, actor).players.find(p => p.id === f.owner)?.hand, undefined);
}
rejected(read, f.opponent, { ...f.opponentPlan, dial: 0 }, /not available/);
rejected(read, f.owner, f.inspect, /already been used/);
const revealed = act(read, f.owner, f.ownPlan);
assert.equal(revealed.battle?.revealed, true);
assert.equal(viewGame(revealed, f.owner).battle?.fullPlanInsight, null);
assert.equal(finish(revealed, f).phase, 7);
});
void test('own seal then special activation and target-last commitment reveals both once without a read deadlock', () => {
const f = fixture();
const sealed = act(f.game, f.owner, f.ownPlan);
const inspecting = act(sealed, f.owner, f.inspect);
assert.deepEqual(inspecting.battle?.plans, sealed.battle?.plans);
assert.equal(inspecting.battle?.revealed, false);
assert.equal(inspecting.decision, null);
const revealed = act(inspecting, f.opponent, f.opponentPlan);
assert.equal(revealed.battle?.revealed, true);
assert.deepEqual(revealed.battle?.plans[f.owner], sealed.battle?.plans[f.owner]);
assert.equal(revealed.log.filter(row => row.text === 'Both battle plans are revealed.').length, 1);
assert.ok(revealed.log.some(row => row.text.includes('Both plans reveal immediately')));
assert.notEqual(revealed.decision?.kind, 'fullPlanRead');
rejected(revealed, f.owner, { ...f.ownPlan, dial: 0 }, /not available/);
const done = finish(revealed, f);
assert.equal(done.phase, 7);
assert.equal(done.battle, null);
assert.deepEqual(inventory(done), f.originalInventory);
});
void test('any one player includes the already sealed own plan without reopening it', () => {
const f = fixture();
const sealed = act(f.game, f.owner, f.ownPlan);
const read = act(sealed, f.owner, { ...f.inspect, target: f.owner });
assert.deepEqual(viewGame(read, f.owner).battle?.fullPlanInsight?.plan, sealed.battle?.plans[f.owner]);
assert.deepEqual(read.battle?.plans, sealed.battle?.plans);
assert.equal(viewGame(read, f.guild).battle?.fullPlanInsight, null);
assert.equal(finish(read, f).phase, 7);
});
void test('actual preparation, foreign target and public reveal retain their timing and card boundaries', () => {
const f = fixture();
const before = viewGame(f.beforePreparation, f.owner).atreidesFullPlan;
assert.ok(before?.blocked);
rejected(f.beforePreparation, f.owner, f.inspect);
rejected(f.game, f.owner, { ...f.inspect, target: f.guild }, /in this battle/);
rejected(f.game, f.owner, { ...f.inspect, card: f.defense }, /Karama card/);
let revealed = act(f.game, f.opponent, f.opponentPlan);
revealed = act(revealed, f.owner, f.ownPlan);
assert.equal(revealed.battle?.revealed, true);
assert.match(viewGame(revealed, f.owner).atreidesFullPlan!.blocked!, /already been revealed/);
rejected(revealed, f.owner, f.inspect, /already been revealed/);
assert.equal(player(revealed, f.owner).specialKaramaUsed, undefined);
assert.ok(player(revealed, f.owner).hand.some(card => card.id === f.karama));
});
for (const profile of DIFFICULTIES) {
void test(`${profile} uses the same legal private late-inspection offer and continues to Collection`, () => {
const f = fixture();
const sealed = act(f.game, f.opponent, f.opponentPlan);
const view = viewGame(sealed, f.owner);
view.players.find(p => p.id === f.owner)!.bot = profile;
const action = botActions(view)[0];
assert.deepEqual(action, f.inspect);
const read = act(sealed, f.owner, action);
assert.ok(viewGame(read, f.owner).battle?.fullPlanInsight);
assert.equal(finish(read, f).phase, 7);
});
}