Files
Dune/tests/fremen-revival-grant-bot-priority.test.ts
T
Quintine ac7db3cb35
Verify and publish checkpoint container / container (push) Successful in 10m54s
Preserve Fremen revival grant opportunity for bot allies
2026-10-29 07:18:48 +11:00

141 lines
7.0 KiB
TypeScript
Raw 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 Game } from '../game/engine';
import { botActions, runBots } from '../game/bots';
import { DIFFICULTIES, type Difficulty } from '../game/bot-profiles';
import { actFremenRevivalGrant as act, advanceFremenRevivalGrant as advance,
createFremenRevivalGrantFixture, fremenRevivalGrantClean as clean,
fremenRevivalGrantPlayer as player, passFremenRevivalGrantResponses as pass } from './fixture-fremen-revival-grant';
let fixture: ReturnType<typeof createFremenRevivalGrantFixture> | undefined;
const source = () => fixture ??= createFremenRevivalGrantFixture();
/** The source uses real public Basic setup, an actual battle with six allied
* casualties and reciprocal Nexus consent. Handover uses the real seat action. */
function botAlly(difficulty: Difficulty) {
const f = source();
const game = act(structuredClone(f.game), f.ally, {type:'setAutopilot', difficulty});
assert.equal(game.advanced, false);
assert.equal(game.phase, 4);
assert.equal(player(game, f.fremen).ally, f.ally);
assert.equal(player(game, f.ally).ally, f.fremen);
assert.ok(player(game, f.ally).tanks >= 3);
assert.equal(viewGame(game, f.ally).revival.freeRemaining, 2);
assert.equal(viewGame(game, f.fremen).fremenRevivalGrant?.blocked, null);
return {f, game};
}
const ownChoices = (game: Game, id: string) => botActions(viewGame(game, id));
for (const difficulty of DIFFICULTIES) void test(
`Basic ${difficulty} ally waits for human Fremen, then uses the granted free revival`, () => {
const {f, game} = botAlly(difficulty);
const spice = player(game, f.ally).spice, tanks = player(game, f.ally).tanks;
const afterBot = runBots(game, 1);
assert.equal(player(afterBot, f.ally).spice, spice);
assert.equal(player(afterBot, f.ally).tanks, tanks);
assert.equal(player(afterBot, f.ally).revived, 0);
assert.ok(!afterBot.ready.includes(f.ally));
let next = act(afterBot, f.fremen, f.grant);
assert.equal(next.response?.kind, 'fremenRevivalGrant');
assert.equal(next.freeRevival.includes(f.ally), false);
next = pass(next);
assert.equal(viewGame(next, f.ally).revival.freeRemaining, 3);
const revival = ownChoices(next, f.ally).find(action => action.type === 'revive' && action.amount === 3);
assert.ok(revival, `${difficulty} should offer its legal three-force revival`);
next = advance(runBots(next, 1), clean);
assert.equal(player(next, f.ally).spice, spice);
assert.equal(player(next, f.ally).tanks, tanks - 3);
assert.equal(player(next, f.ally).freeForcesRevived, 3);
},
);
void test('Fremen’s normal Ready releases the waiting bot without granting the benefit', () => {
const {f, game} = botAlly('Easy');
const waiting = runBots(game, 1);
assert.equal(player(waiting, f.ally).revived, 0);
assert.ok(!waiting.ready.includes(f.ally));
const next = act(waiting, f.fremen, {type:'ready'});
assert.ok(next.ready.includes(f.fremen));
assert.equal(next.freeRevival.includes(f.ally), false);
const resumed = runBots(next, 1);
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
});
void test('a real Karama prevention releases the waiting bot with its intrinsic free rate', () => {
const {f, game} = botAlly('Medium');
let next = act(game, f.fremen, f.grant);
assert.equal(next.response?.kind, 'fremenRevivalGrant');
next = act(next, f.observer, {type:'card', card:f.karama, mode:'cancel'});
assert.equal(next.response, null);
assert.ok(next.revivalRules?.fremenGrantBlocked?.includes(f.ally));
assert.equal(viewGame(next, f.ally).revival.freeRemaining, 2);
const resumed = runBots(next, 1);
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
});
void test('the waiting ally can still pass its actual Karama response to the Fremen grant', () => {
const {f, game} = botAlly('Easy');
// Stage one conserved physical card exchange solely to make the allied
// bot, rather than the original Emperor, own the response card.
const ally = player(game, f.ally), observer = player(game, f.observer);
const karama = observer.hand.findIndex(card => card.id === f.karama);
assert.ok(karama >= 0 && ally.hand.length > 0);
[ally.hand[0], observer.hand[karama]] = [observer.hand[karama], ally.hand[0]];
let next = act(game, f.fremen, f.grant);
assert.equal(next.response?.kind, 'fremenRevivalGrant');
next = runBots(next, 1);
assert.equal(next.response, null);
assert.equal(next.freeRevival.includes(f.ally), true);
});
void test('a bot Fremen does not make its bot ally wait for a human grant', () => {
const {f, game} = botAlly('Easy');
const next = act(game, f.fremen, {type:'setAutopilot', difficulty:'Easy'});
// Staged actor iteration order puts the allied bot first; public storm order
// and the genuine prior game state are otherwise untouched.
next.players = [player(next, f.ally), player(next, f.fremen), player(next, f.observer)];
const resumed = runBots(next, 1);
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
});
void test('a completed normal revival does not hold the bot’s Ready for an unusable grant', () => {
// This fixture actually completes its paid/free three-force return before
// handover; the remaining ordinary revival allowance is genuinely zero.
const f = createFremenRevivalGrantFixture({priorRevival:true});
const game = act(f.game, f.ally, {type:'setAutopilot', difficulty:'Easy'});
assert.equal(viewGame(game, f.ally).revival.forcesRemaining, 0);
const resumed = runBots(game, 1);
assert.ok(resumed.ready.includes(f.ally));
});
void test('two remaining Tanks forces do not hold a bot that already has two free revivals', () => {
const {f, game} = botAlly('Easy');
const ally = player(game, f.ally);
// Labelled physical-custody staging: return the surplus ordinary Tanks
// counters to reserves, preserving the player's total force inventory.
const surplus = ally.tanks - 2;
assert.ok(surplus > 0);
ally.tanks -= surplus;
ally.reserves += surplus;
const reserves = ally.reserves, spice = ally.spice;
assert.equal(viewGame(game, f.ally).revival.freeRemaining, 2);
assert.equal(viewGame(game, f.fremen).fremenRevivalGrant?.blocked, null);
const resumed = runBots(game, 1);
assert.equal(player(resumed, f.ally).revived, 2);
assert.equal(player(resumed, f.ally).tanks, 0);
assert.equal(player(resumed, f.ally).reserves, reserves + 2);
assert.equal(player(resumed, f.ally).spice, spice);
});
void test('a bot barred from normal revival can Ready without waiting for a futile grant', () => {
const {f, game} = botAlly('Easy');
// Stage the existing current-turn native prevention receipt, without
// changing the genuine alliance, Tanks, wallet or Fremen grant state.
game.revivalPrevention = {player:f.ally, turn:game.turn};
assert.equal(viewGame(game, f.ally).revival.prevented, true);
const resumed = runBots(game, 1);
assert.ok(resumed.ready.includes(f.ally));
assert.equal(player(resumed, f.ally).revived, 0);
assert.equal(player(resumed, f.ally).tanks, player(game, f.ally).tanks);
});