Files
Dune/tests/advanced-sneak-direct-runtime.test.ts
T
Quintine b79e448deb
Verify and publish checkpoint container / container (push) Canceled after 0s
Resolve direct advanced Sneak arrivals
2026-10-29 19:48:35 +11:00

109 lines
5.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Game } from '../game/engine';
import { guildAmbassadorShipments } from '../game/guild-ambassador';
import { reserveShipmentCost } from '../game/shipment-price';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { nextBasicSemutaAmbassadorStep } from './fixture-basic-semuta-ambassador';
import { act, player } from './fixture-advanced-overlap-child';
import { advancedDirectSneakFixture } from './fixture-advanced-sneak-direct';
function advanceTo(game: Game, until: (state: Game) => boolean): Game {
for (let i = 0; i < 60 && !until(game); i++) {
if (game.decision?.kind === 'advisor') {
game = act(game, game.decision.player, {type:'decision', accept:false});
continue;
}
if (game.decision?.kind === 'guildShipment') {
game = act(game, game.decision.player, {type:'decision', allow:true});
continue;
}
const next = nextBasicSemutaAmbassadorStep(game);
assert.ok(next, `The original arrival must continue: ${game.decision?.kind ?? game.response?.kind}`);
game = act(game, next.actor, next.action);
}
assert.ok(until(game), 'The requested native arrival reaction is reached');
return game;
}
function revealSneak(withBG: boolean) {
const f = advancedDirectSneakFixture(withBG);
const originalSpice = player(f.game, f.entrant).spice;
const price = reserveShipmentCost({faction:'guild', halfRate:true}, 'stronghold', 1);
let game = act(f.game, f.entrant, f.shipment);
game = advanceTo(game, state => !!state.pendingTerrorEntry);
assert.equal(game.pendingArrivalOverlap ?? null, null, 'Guild cannot trigger its own Ambassador');
assert.equal(game.pendingTerrorEntry?.token, f.sneak);
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
assert.equal(player(game, f.entrant).spice, originalSpice - price);
game = act(game, f.moritani, {type:'decision', reveal:true});
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
return {f, game, originalSpice, price};
}
for (const withBG of [false, true]) void test(
`Advanced direct Sneak opens the Moritani Ambassador entry ${withBG ? 'after BG accompaniment' : 'without BG'}`, () => {
const {f, game: before, originalSpice, price} = revealSneak(withBG);
const reserves = player(before, f.moritani).reserves;
let game = act(before, f.moritani, {type:'decision', amount:1});
if (withBG) {
assert.equal(game.decision?.kind, 'advisor');
assert.equal(game.decision?.player, 'beneGesserit');
game = act(game, 'beneGesserit', {type:'decision', accept:false});
}
game = advanceTo(game, state => !!state.pendingAmbassador);
const entry = game.pendingAmbassador; assert.ok(entry);
assert.equal(entry.token, f.ambassador);
assert.equal(entry.entrant, f.moritani);
assert.equal(player(game, f.moritani).reserves, reserves - 1);
assert.equal(player(game, f.moritani).forces[`${f.destination}:${f.sector}`], 1);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
assert.equal(player(game, f.entrant).spice, originalSpice - price);
for (const difficulty of DIFFICULTIES) {
const view = viewGame(game, 'ecaz');
view.players.find(p => p.id === 'ecaz')!.bot = difficulty;
const action = botActions(view)[0]; assert.ok(action, difficulty);
assert.doesNotThrow(() => act(game, 'ecaz', action), difficulty);
}
game = act(game, 'ecaz', {type:'decision', event:entry.event, decline:true});
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(game.pendingArrivalOverlap ?? null, null);
assert.equal(player(game, f.moritani).reserves, reserves - 1);
assert.equal(player(game, f.entrant).spice, originalSpice - price);
});
void test('Advanced direct Sneak sending zero opens no separate entry or accompaniment', () => {
const {f, game: before, originalSpice, price} = revealSneak(true);
const reserves = player(before, f.moritani).reserves;
const game = act(before, f.moritani, {type:'decision', amount:0});
assert.equal(player(game, f.moritani).reserves, reserves);
assert.equal(game.pendingAmbassador ?? null, null);
assert.equal(game.pendingTerrorEntry, null);
assert.notEqual(game.decision?.kind, 'advisor');
assert.equal(player(game, f.entrant).spice, originalSpice - price);
});
void test('Advanced direct Sneak can trigger the retained Guild Ambassador exactly once', () => {
const {f, game: before, originalSpice, price} = revealSneak(false);
const reserves = player(before, f.owner).reserves, spice = player(before, f.owner).spice;
let game = act(before, f.moritani, {type:'decision', amount:1});
game = advanceTo(game, state => !!state.pendingAmbassador);
const entry = game.pendingAmbassador; assert.ok(entry);
game = act(game, f.owner, {type:'decision', event:entry.event, trigger:true, beneficiary:f.owner});
assert.equal(game.pendingAmbassador?.stage, 'ship');
const target = guildAmbassadorShipments(game, f.owner).destinations.find(row => row.territory === 'polar_sink');
assert.ok(target);
game = act(game, f.owner, {type:'decision', event:entry.event, amount:1, elite:0,
territory:target.territory, sector:target.sector});
game = advanceTo(game, state => !state.pendingAmbassador);
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(player(game, f.owner).reserves, reserves - 1);
assert.equal(player(game, f.owner).spice, spice);
assert.equal(player(game, f.entrant).spice, originalSpice - price);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
assert.equal(game.ecazAmbassadors?.tokens.find(token => token.id === f.ambassador)?.zone, 'used');
});