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