Files
Dune/tests/sneak-overlap-child-ambassador.test.ts

103 lines
6.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { act, player, createFremenOverlapFixture } from './fixture-overlap-fremen-native';
void test('Terror-first Sneak waits for its own BG choice and then earns a retained Ambassador', () => {
const f = createFremenOverlapFixture('terror', false, true);
const source = structuredClone(f.game);
// Controlled physical-token selection in the original public fixture: return
// its staged Robbery token to supply and put the existing Sneak token in the
// same stronghold. No force, money, pending event or turn is installed.
const robbery = source.moritaniTerror!.tokens.find(token => token.id === f.terror);
const sneak = source.moritaniTerror!.tokens.find(token => token.kind === 'sneakAttack');
assert.ok(robbery && sneak && sneak.status === 'available');
robbery.status = 'available'; robbery.location = null;
sneak.status = 'placed'; sneak.location = f.destination;
const originalSpice = player(source, f.entrant).spice;
const reserves = player(source, 'moritani').reserves;
let game = act(source, f.entrant, f.shipment);
// The first accompaniment belongs to the real original shipment.
if (game.decision?.kind === 'advisor')
game = act(game, 'beneGesserit', { type: 'decision', accept: false });
assert.equal(game.pendingArrivalOverlap?.first, 'terror');
assert.equal(game.pendingTerrorEntry?.token, sneak.id);
game = act(game, 'moritani', { type: 'decision', reveal: true });
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
game = act(game, 'moritani', { type: 'decision', amount: 1 });
assert.equal(player(game, 'moritani').reserves, reserves - 1);
assert.equal(player(game, 'moritani').forces[`${f.destination}:${f.sector}`], 1);
assert.equal(game.pendingArrivalOverlap?.stage, 'second');
const original = game.pendingAmbassador; assert.ok(original);
assert.equal(original.entrant, f.entrant);
game = act(game, 'ecaz', { type: 'decision', event: original.event, decline: true });
assert.equal(game.pendingArrivalOverlap, null);
// Sneak is its own off-planet arrival: its BG opportunity precedes any
// additional Ambassador created by that same committed group.
assert.equal(game.decision?.kind, 'advisor');
assert.equal(game.decision?.player, 'beneGesserit');
game = act(game, 'beneGesserit', { type: 'decision', accept: false });
const later = game.pendingAmbassador; assert.ok(later, 'The same retained Ambassador also responds to the Sneak group');
assert.equal(later.token, f.fremen);
assert.equal(later.entrant, 'moritani');
assert.notEqual(later.event, original.event);
game = act(game, 'ecaz', { type: 'decision', event: later.event, decline: true });
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(game.pendingArrivalOverlap, null);
assert.equal(game.pendingArrivalReaction ?? null, null);
assert.equal(game.ecazAmbassadors?.tokens.find(token => token.id === f.fremen)?.zone, 'placed');
assert.equal(player(game, f.entrant).spice, originalSpice - 1);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
assert.equal(player(game, 'moritani').forces[`${f.destination}:${f.sector}`], 1);
});
void test('a direct Sneak arrival receives BG timing and triggers an eligible Ambassador', () => {
const f = createFremenOverlapFixture('terror', false, true);
const source = structuredClone(f.game);
const inventory = source.ecazAmbassadors!;
const old = inventory.tokens.find(token => token.id === f.fremen);
const guild = inventory.tokens.find(token => token.effect === 'guild');
assert.ok(old && guild && old.zone === 'placed');
// Controlled custody-preserving token selection: use the original Guild
// Ambassador in the same cohort and location. The original Guild entrant
// cannot trigger its own faction's token, while the later Moritani entrant can.
old.zone = 'supply'; old.location = null;
if (guild.zone === 'pool') {
const index = inventory.cohort.indexOf(old.id); assert.ok(index >= 0);
inventory.cohort[index] = guild.id;
old.zone = 'pool'; guild.zone = 'supply';
}
assert.equal(guild.zone, 'supply');
guild.zone = 'placed'; guild.location = f.destination;
const robbery = source.moritaniTerror!.tokens.find(token => token.id === f.terror);
const sneak = source.moritaniTerror!.tokens.find(token => token.kind === 'sneakAttack');
assert.ok(robbery && sneak && sneak.status === 'available');
robbery.status = 'available'; robbery.location = null;
sneak.status = 'placed'; sneak.location = f.destination;
const originalSpice = player(source, f.entrant).spice;
const reserves = player(source, 'moritani').reserves;
let game = act(source, f.entrant, f.shipment);
if (game.decision?.kind === 'advisor')
game = act(game, 'beneGesserit', { type: 'decision', accept: false });
assert.ok(!game.pendingArrivalOverlap, 'The Guild entrant does not trigger the Guild Ambassador');
assert.equal(game.pendingTerrorEntry?.token, sneak.id);
game = act(game, 'moritani', { type: 'decision', reveal: true });
assert.equal(game.pendingTerrorEntry?.stage, 'sneakAttack');
game = act(game, 'moritani', { type: 'decision', amount: 1 });
assert.equal(player(game, 'moritani').reserves, reserves - 1);
assert.equal(game.decision?.kind, 'advisor');
assert.equal(game.decision?.player, 'beneGesserit');
game = act(game, 'beneGesserit', { type: 'decision', accept: false });
const later = game.pendingAmbassador; assert.ok(later);
assert.equal(later.token, guild.id);
assert.equal(later.entrant, 'moritani');
game = act(game, 'ecaz', { type: 'decision', event: later.event, decline: true });
assert.equal(game.pendingAmbassador, null);
assert.equal(game.pendingTerrorEntry, null);
assert.equal(game.pendingArrivalOverlap ?? null, null);
assert.equal(game.pendingArrivalReaction ?? null, null);
assert.equal(player(game, f.entrant).spice, originalSpice - 1);
assert.equal(player(game, f.entrant).forces[`${f.destination}:${f.sector}`], 1);
assert.equal(player(game, 'moritani').forces[`${f.destination}:${f.sector}`], 1);
});