Files
Dune/tests/fixture-no-field-allied-transit.ts
Quintine 8083598cef
Verify and publish checkpoint container / container (push) Canceled after 0s
Support Basic allied No-Field transit
2026-10-28 22:44:26 +11:00

82 lines
4.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { type Action, type Game } from '../game/engine';
import type { NoFieldValue } from '../game/richese-no-field';
import {
actNoFieldAlliedSeparation as act, advanceNoFieldAlliedSeparation as advance,
createNoFieldAlliedSeparationFixture, noFieldAlliedSeparationClean as clean,
noFieldAlliedSeparationInventory as inventory, noFieldAlliedSeparationPlayer as player,
nextNoFieldAlliedSeparation,
type NoFieldAlliedSeparationStep,
} from './fixture-no-field-allied-separation';
export { act, advance, clean, inventory, player };
/** Start with the existing genuine Basic setup and first Storm. Replace only
* the source's prospective marker shipment with a paid two-force shipment;
* Emperor's paid two forces are already across the actual Storm. The next
* real worm opens Nexus and both factions consent before Richese's next turn.
* Only unopened Spice-card order is selected; no played force, purse,
* alliance, phase, marker or receipt is installed. */
export function createNoFieldAlliedTransitFixture(value: NoFieldValue = 0) {
const base = createNoFieldAlliedSeparationFixture({advanced: false, value});
let game = structuredClone(base.markerShipment.before);
const actor = base.actor, ally = base.ally, other = base.other;
const actions: NoFieldAlliedSeparationStep[] = [];
const perform = (id: string, action: Action) => { game = act(game, id, action, actions); };
const reach = (predicate: (state: Game) => boolean) => {
for (let count = 0; count < 1800 && !predicate(game); count++) {
const own = player(game, actor);
// The two-force first shipment costs four, so native turn-two Charity
// must actually bring Richese from one spice to two for the marker.
if (game.turn === 2 && game.phase === 2 && clean(game) && own.spice < 2 && own.charityTurn !== game.turn) {
perform(actor, {type: 'charity'}); continue;
}
const step = nextNoFieldAlliedSeparation(game); assert.ok(step);
perform(step.actor, step.action);
}
assert.ok(predicate(game), 'The real transit source must reach its requested boundary.');
};
assert.equal(game.active, actor); assert.equal(game.turn, 1); assert.equal(game.storm, 10);
assert.equal(player(game, ally).forces['imperial_basin:11'], 2);
assert.equal(player(game, actor).noField!.deployed, null);
const beforePhysical = structuredClone(game);
perform(actor, {type: 'ship', territory: 'imperial_basin', sector: 9, amount: 2, elite: 0, allyPayment: 0});
reach(clean);
assert.equal(player(game, actor).forces['imperial_basin:9'], 2);
assert.equal(player(beforePhysical, actor).spice - player(game, actor).spice, 4);
perform(actor, {type: 'endMovement'});
reach(state => state.turn === 2 && state.phase === 0 && clean(state));
const originalSpice = game.spiceDeck.map(card => JSON.stringify(card)).sort();
for (let position = 0; position < 2; position++) {
const from = game.spiceDeck.findIndex((card, index) => index >= position && (position === 0
? 'worm' in card && !card.greatMaker && !card.suppressed
: 'territory' in card && !card.discovery && card.territory !== 'imperial_basin' &&
game.players.every(seat => !Object.entries(seat.forces).some(([key, count]) =>
count > 0 && key.startsWith(`${card.territory}:`)))));
assert.ok(from >= position);
game.spiceDeck.splice(position, 0, game.spiceDeck.splice(from, 1)[0]);
}
assert.deepEqual(game.spiceDeck.map(card => JSON.stringify(card)).sort(), originalSpice);
reach(state => state.turn === 2 && state.phase === 1 && state.nexus &&
!state.spiceWindow && !state.spiceResolution && clean(state));
perform(actor, {type: 'alliance', target: ally});
perform(ally, {type: 'alliance', target: actor});
assert.equal(player(game, actor).ally, ally); assert.equal(player(game, ally).ally, actor);
reach(state => state.turn === 2 && state.phase === 5 && state.active === actor && clean(state));
const own = player(game, actor), marker = own.noField?.tokens.find(item => item.value === value);
assert.ok(marker && own.noFieldEvent);
assert.equal(own.forces['imperial_basin:9'], 2);
assert.equal(player(game, ally).forces['imperial_basin:11'], 2);
assert.equal(own.allySinceTurn, game.turn);
assert.ok(own.spice >= 2);
const ship: Action = {type: 'ship', noField: marker.id, event: own.noFieldEvent,
territory: 'imperial_basin', sector: 9, allyPayment: 0};
return {game, actor, ally, other, value, token: marker.id, ship, actions,
baseline: 2, original: inventory(game), base};
}
/** Resolve the original No-Field response and any native arrival controls. */
export function finishAlliedMarkerShipment(game: Game): Game {
return advance(game, state => state.phase === 5 && clean(state) && !state.pendingShipment);
}