Files
Dune/tests/advanced-overlap-child-runtime.test.ts
T
Quintine 8a39f5a172
Verify and publish checkpoint container / container (push) Successful in 10m16s
Enable advanced ambassador child arrivals
2026-10-29 19:42:27 +11:00

116 lines
6.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Game } from '../game/engine';
import { reserveShipmentCost } from '../game/shipment-price';
import { guildAmbassadorShipments } from '../game/guild-ambassador';
import { nextBasicSemutaAmbassadorStep } from './fixture-basic-semuta-ambassador';
import { act, player, advancedOverlapChildFixture, type ChildEffect, type First } from './fixture-advanced-overlap-child';
function continueTo(game: Game, until: (state: Game) => boolean): Game {
for (let n = 0; n < 100 && !until(game); n++) {
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, `Unanticipated original continuation ${game.decision?.kind ?? game.response?.kind}`);
game = act(game, next.actor, next.action);
}
assert.ok(until(game), 'The native source reached the intended reaction');
return game;
}
function originalEntry(effect: ChildEffect, first: First, withBG = false) {
const f = advancedOverlapChildFixture(effect, first, withBG);
const originalSpice = player(f.game, f.entrant).spice;
const shipper = player(f.game, f.entrant);
const price = reserveShipmentCost({ faction:shipper.faction, halfRate:shipper.faction === 'guild' }, 'stronghold', 1);
let game = act(f.game, f.entrant, f.shipment);
game = continueTo(game, state => !!state.pendingArrivalOverlap);
assert.equal(player(game, f.entrant).spice, originalSpice - price);
assert.equal(game.pendingArrivalOverlap?.first, first);
if (first === 'terror') {
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
game = act(game, f.moritani, { type:'decision', decline:true });
}
const entry = game.pendingAmbassador; assert.ok(entry);
assert.equal(entry.token, f.token);
assert.equal(viewGame(game, f.owner).ambassadorEntry?.beneficiaries[0].blocked, null);
const parentEvent = game.pendingArrivalOverlap!.event;
game = act(game, f.owner, { type:'decision', event:entry.event, trigger:true, beneficiary:f.owner });
assert.equal(game.pendingAmbassador?.stage, effect === 'fremen' ? 'move' : 'ship');
return { f, game, parentEvent, entryEvent:entry.event, originalSpice, price };
}
for (const effect of ['fremen','guild'] as const)
for (const first of ['ambassador','terror'] as const)
for (const reveal of [false,true])
void test(`Advanced ${effect} child ${first} first and ${reveal ? 'revealed' : 'declined'} Terror`, () => {
const start = originalEntry(effect, first), f = start.f;
let game = start.game;
const before = player(game, f.owner);
const reserves = before.reserves, spice = before.spice;
if (effect === 'fremen') {
game = act(game, f.owner, { type:'decision', event:start.entryEvent,
forces:{ [f.moveFrom]:1 }, territory:f.destination, sector:f.sector });
} else {
assert.ok(guildAmbassadorShipments(game, f.owner).destinations.some(option =>
option.territory === f.destination && option.sector === f.sector));
game = act(game, f.owner, { type:'decision', event:start.entryEvent,
amount:1, elite:0, territory:f.destination, sector:f.sector });
}
game = continueTo(game, state => state.pendingTerrorEntry?.cause === 'ambassador');
assert.equal(game.pendingTerrorEntry?.entrant, f.owner);
assert.equal(game.pendingTerrorEntry?.token, f.terror);
assert.equal(game.pendingArrivalOverlap?.event, start.parentEvent);
assert.equal(player(game, f.owner).reserves, reserves - (effect === 'guild' ? 1 : 0));
assert.equal(player(game, f.owner).spice, spice);
if (reveal) {
game = act(game, f.moritani, { type:'decision', reveal:true });
assert.equal(game.pendingTerrorEntry?.stage, 'robbery');
game = act(game, f.moritani, { type:'decision', choice:'spice' });
assert.equal(game.moritaniTerror!.tokens.find(token => token.id === f.terror)?.status, 'removed');
} else game = act(game, f.moritani, { type:'decision', decline:true });
if (first === 'ambassador' && !reveal) {
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
assert.equal(game.pendingTerrorEntry?.token, f.terror);
game = act(game, f.moritani, { type:'decision', decline:true });
}
assert.equal(game.pendingArrivalOverlap, null);
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(player(game, f.entrant).spice, start.originalSpice - start.price);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
assert.ok((player(game, f.owner).forces[`${f.destination}:${f.sector}`] ?? 0) >= 1);
});
void test('Advanced child Sneak and Bene Gesserit accompaniment wait for the original Terror', () => {
const start = originalEntry('fremen', 'ambassador', true), f = start.f;
assert.ok(f.alternate && f.sneak);
const alternate = f.alternate;
let game = act(start.game, f.owner, { type:'decision', event:start.entryEvent,
forces:{ [alternate.sector.key]:1 }, territory:alternate.destination.territory, sector:alternate.destination.sector });
game = continueTo(game, state => state.pendingTerrorEntry?.token === f.sneak);
const reserves = player(game, f.moritani).reserves;
game = act(game, f.moritani, { type:'decision', reveal:true });
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
game = act(game, f.moritani, { type:'decision', amount:1 });
assert.equal(player(game, f.moritani).reserves, reserves - 1);
assert.equal(game.pendingTerrorEntry?.token, f.terror);
assert.equal(game.pendingTerrorEntry?.entrant, f.entrant);
assert.equal(game.decision?.kind, 'moritaniTerror');
game = act(game, f.moritani, { type:'decision', decline:true });
assert.equal(game.pendingArrivalOverlap, null);
assert.equal(game.decision?.kind, 'advisor');
assert.equal(game.decision?.player, 'beneGesserit');
game = act(game, 'beneGesserit', { type:'decision', accept:false });
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(player(game, f.moritani).forces[`${alternate.destination.territory}:${alternate.destination.sector}`], 1);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
});