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 | 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); });