Files
Dune/tests/truth-future-battle-leader-runtime.test.ts
Quintine 02695aea7c
Verify and publish checkpoint container / container (push) Failing after 11m11s
Prototype conditional future battle Truthtrance
2026-10-28 21:17:44 +11:00

111 lines
5.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import {
actFutureBattleLeader as act, advanceFutureBattleLeader as advance,
answerFutureBattleLeader as answer, createFutureBattleLeaderFixture as fixture,
declareFutureBattleLeader as declare, futureBattleLeaderClean as clean,
futureBattleLeaderInventory as inventory, futureBattleLeaderRecords as records,
reachFutureBattle as reachBattle,
} from './fixture-truth-future-battle-leader';
function rejected(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => act(game, actor, action));
assert.deepEqual(game, before);
}
for (const expected of ['yes', 'no'] as const) {
void test(`Basic next battle leader ${expected} binds the first real battle and constrains its native plan`, () => {
const f = fixture();
const asked = declare(f);
assert.equal(asked.truthtrance?.stage, 'answer');
assert.deepEqual(asked.truthtrance?.question, f.question);
let game = answer(asked, f, expected);
assert.equal(game.truthtrance, null);
assert.equal(game.discard.filter(card => card.id === f.truth).length, 1);
assert.equal(records(game).length, 1);
assert.equal(records(game)[0].status, 'waiting');
assert.equal(records(game)[0].player, f.target);
assert.equal(records(game)[0].leader, f.leader);
assert.equal(records(game)[0].answer, expected === 'yes');
rejected(game, f.target, { type: 'truthAnswer', answer: expected });
assert.equal(records(game).length, 1);
game = reachBattle(game, f);
assert.equal(records(game)[0].status, 'bound');
assert.equal(records(game)[0].battle, game.battle!.event);
const yesPlan = { ...f.targetPlan, leader: f.leader } as Action;
const noPlan = { ...f.targetPlan, leader: f.otherLeader } as Action;
rejected(game, f.target, expected === 'yes' ? noPlan : yesPlan);
game = act(game, f.target, expected === 'yes' ? yesPlan : noPlan);
assert.equal(records(game)[0].status, 'bound');
game = act(game, f.asker, f.ownPlan);
assert.ok(game.battle?.revealed);
assert.equal(records(game)[0].status, 'settled');
assert.equal(records(game).length, 1);
assert.equal(game.discard.filter(card => card.id === f.truth).length, 1);
assert.deepEqual(inventory(game), f.originalCards);
});
}
void test('Basic next battle leader question expires without creating a battle or forcing a shipment', () => {
const f = fixture();
for (const expected of ['yes', 'no'] as const) {
let game = answer(declare(f), f, expected);
const before = structuredClone(game.players.find(player => player.id === f.target));
game = act(game, f.target, { type: 'endMovement' });
game = advance(game, state => state.turn !== f.game.turn || state.phase > 6 || state.status === 'finished');
assert.equal(game.battle, null);
assert.equal(records(game).length, 1);
assert.equal(records(game)[0].status, 'expired');
const after = game.players.find(player => player.id === f.target); assert.ok(before && after);
assert.equal(after.reserves, before.reserves);
assert.deepEqual(after.forces, before.forces);
assert.equal(game.discard.filter(card => card.id === f.truth).length, 1);
assert.deepEqual(inventory(game), f.originalCards);
assert.ok(clean(game));
}
});
void test('future battle questions use a current turn, another seat and that faction’s native public leader', () => {
const f = fixture();
let game = act(f.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 invalid = [
{ ...f.question, turn: f.game.turn + 1 },
{ ...f.question, target: f.asker },
{ ...f.question, leader: f.base.leaders.atreides },
{ ...f.question, leader: 'unknown-leader' },
];
for (const question of invalid)
rejected(game, f.asker, { type: 'truthAsk', question } as unknown as Action);
const asked = act(game, f.asker, f.ask);
rejected(asked, f.asker, { type: 'truthAnswer', answer: 'yes' });
});
void test('each AI profile chooses a legal conditional answer and honors it in the real battle', () => {
for (const difficulty of DIFFICULTIES) {
const f = fixture();
const asked = declare(f);
const respondent = asked.players.find(player => player.id === f.target)!;
respondent.bot = difficulty;
const reply = botActions(viewGame(asked, f.target)).find(action => action.type === 'truthAnswer');
assert.ok(reply, difficulty);
let game = act(asked, f.target, reply);
assert.equal(records(game)[0].status, 'waiting');
game = reachBattle(game, f);
const planned = botActions(viewGame(game, f.target)).find(action => action.type === 'battlePlan');
assert.ok(planned, difficulty);
game = act(game, f.target, planned);
game = act(game, f.asker, f.ownPlan);
assert.equal(records(game)[0].status, 'settled');
}
});