Prototype conditional future battle Truthtrance
Verify and publish checkpoint container / container (push) Failing after 11m11s

This commit is contained in:
Quintine committed 2026-10-28 21:17:44 +11:00
1 parent 71009d73e3
commit 02695aea7c
15 files changed
+721 -8

No files matched your search

@@ -0,0 +1,110 @@
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');
}
});