Files
Dune/tests/truth-future-battle-leader-expansions.test.ts
T
Quintine 50703664cf
Verify and publish checkpoint container / container (push) Canceled after 0s
Extend conditional battle questions across previews
2026-10-29 02:21:12 +11:00

114 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { newPlayer, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { createTruthMovementChoamFixture } from './fixture-truth-movement-choam';
import {
actFutureBattleLeader as act, advanceFutureBattleLeader as advance,
futureBattleLeaderClean as clean, futureBattleLeaderRecords as records,
} from './fixture-truth-future-battle-leader';
import {
createTruthtranceRichesePromisesFixture, passTruthRicheseFreshDiscard,
finishTruthtranceRichesePromises, truthtranceRichesePromisesInventory,
} from './fixture-truthtrance-richese-promises';
function ask(game: Game, asker: string, card: string, target: string, leader: string): Game {
assert.ok(viewGame(game, asker).truthFutureBattleLeaderTargets.includes(target));
game = act(game, asker, { type: 'card', card });
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
const player = game.players.find(seat => !game.truthtrance!.passed.includes(seat.id));
assert.ok(player);
game = act(game, player.id, { type: 'truthPass' });
}
assert.equal(game.truthtrance?.stage, 'ask');
return act(game, asker, { type: 'truthAsk', question: {
kind: 'futureBattleLeader', target, turn: game.turn, leader,
} } as unknown as Action);
}
for (const answer of ['yes', 'no'] as const) {
void test(`Basic CHOAM ${answer} binds its first real battle and constrains its native leader`, () => {
const f = createTruthMovementChoamFixture();
const leader = f.beforeShipment.players.find(player => player.id === f.target)!.leaders[0].id;
const other = f.beforeShipment.players.find(player => player.id === f.target)!.leaders[1].id;
let game = ask(f.beforeShipment, f.asker, f.truth, f.target, leader);
assert.ok(viewGame(game, f.target).truthFutureBattleLeaderAnswers?.includes(answer));
game = act(game, f.target, { type: 'truthAnswer', answer });
assert.equal(records(game)[0].status, 'waiting');
game = act(game, f.target, { type: 'ship', amount: 1, territory: 'arrakeen', sector: 10 });
game = advance(game, clean);
game = act(game, f.target, { type: 'endMovement' });
game = advance(game, state => clean(state) && state.phase === 5 && state.active === f.asker);
game = act(game, f.asker, { type: 'ship', amount: 1, territory: 'arrakeen', sector: 10 });
game = advance(game, clean);
game = act(game, f.asker, { type: 'endMovement' });
game = advance(game, state => clean(state) && state.phase === 6 && !!state.active && !state.battle);
const choice = viewGame(game, game.active!).battleChoices.find(row => row.territory === 'arrakeen');
assert.ok(choice);
game = act(game, choice.chooser, { type: 'chooseBattle', territory: choice.territory,
target: choice.attacker === choice.chooser ? choice.defender : choice.attacker });
game = advance(game, state => !!state.battle && clean(state) && !state.battle.preparation && state.battle.preLeader?.closed !== false);
assert.equal(records(game)[0].status, 'bound');
assert.equal(records(game)[0].battle, game.battle!.event);
const plan = (id: string): Action => ({ type: 'battlePlan', leader: id, dial: 0, support: 0, weapon: null, defense: null });
const invalid = plan(answer === 'yes' ? other : leader);
const before = structuredClone(game);
assert.throws(() => act(game, f.target, invalid));
assert.deepEqual(game, before);
game = act(game, f.target, plan(answer === 'yes' ? leader : other));
const emperorLeader = game.players.find(player => player.id === f.asker)!.leaders[0].id;
game = act(game, f.asker, plan(emperorLeader));
assert.equal(records(game)[0].status, 'settled');
});
}
void test('Basic Richese answer survives physical Truthtrance discard and Semuta continuation, then expires without a battle', () => {
const f = createTruthtranceRichesePromisesFixture();
const leader = f.game.players.find(player => player.id === f.target)!.leaders[0].id;
let game = ask(f.game, f.asker, f.truth, f.target, leader);
assert.ok(viewGame(game, f.target).truthFutureBattleLeaderAnswers?.includes('yes'));
for (const difficulty of DIFFICULTIES) {
const copy = structuredClone(game);
copy.players.find(player => player.id === f.target)!.bot = difficulty;
const reply = botActions(viewGame(copy, f.target)).find(action => action.type === 'truthAnswer');
assert.ok(reply, difficulty);
const answered = act(copy, f.target, reply);
assert.equal(records(answered)[0].status, 'waiting');
}
game = act(game, f.target, { type: 'truthAnswer', answer: 'yes' });
assert.equal(records(game)[0].status, 'waiting');
assert.equal(game.pendingTreacheryDiscard?.reaction?.stage, 'offer');
game = passTruthRicheseFreshDiscard(game);
assert.equal(records(game)[0].status, 'waiting');
assert.equal(game.discard.filter(card => card.id === f.truth).length, 1);
game = finishTruthtranceRichesePromises(game);
assert.equal(game.battle, null);
assert.equal(records(game)[0].status, 'expired');
assert.deepEqual(truthtranceRichesePromisesInventory(game), f.originalCards);
});
void test('Advanced Ecaz participation guards the future-battle question in the view and parser', () => {
const f = createTruthMovementChoamFixture();
// Controlled guard probe: this extra seat is staged on a genuine quiet
// CHOAM source; it does not claim to be an Ecaz setup or played journey.
let game = structuredClone(f.beforeShipment);
game.advanced = true;
game.expansions.push('ecaz');
game.players.push(newPlayer('ecaz-guard', 'Ecaz', 'ecaz'));
assert.deepEqual(viewGame(game, f.asker).truthFutureBattleLeaderTargets, []);
const leader = game.players.find(player => player.id === f.target)!.leaders[0].id;
game = act(game, f.asker, { type: 'card', card: f.truth });
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
const player = game.players.find(seat => !game.truthtrance!.passed.includes(seat.id));
assert.ok(player);
game = act(game, player.id, { type: 'truthPass' });
}
assert.equal(game.truthtrance?.stage, 'ask');
const before = structuredClone(game);
assert.throws(() => act(game, f.asker, { type: 'truthAsk', question: {
kind: 'futureBattleLeader', target: f.target, turn: game.turn, leader,
} } as unknown as Action));
assert.deepEqual(game, before);
});