Files
Dune/tests/fixture-nexus-advisor-fresh.ts
Quintine 178251ad35 Restore native Ecaz simulation continuation and current rule fixtures
Match original Ecaz sample snapshots to their native Occupy and Advanced assassination state. Preserve older absent-power samples and resume the original scenario instead of rejecting fresh saved output. Update stale fixture reactions and module expectations while retaining meaningful Banker guidance and positive legal commitments. Correct the unsupported rules-completion claim.

Validation: typecheck/lint, 173 affected tests across 16 files, bounded independent source review, and one direct Ecaz continuation completed through 178 accepted actions. Runtime fingerprint 95d009d3957650b08bb24826b70aa5f00a74cdd9992b6a6c222f0665d3e3759c; documentation-only proof update a91ab248109bee74a74373fb544027a2e7cc8f178e26d2c9dad475a9618eb64d. Full rules, comprehensive assurance and deployed acceptance remain open. No saved-room or provider changes.
2026-10-18 10:08:55 +11:00

195 lines
10 KiB
TypeScript

import assert from 'node:assert/strict';
import {
applyAction, createGame, initializeNexusGameForAudit, joinGame, newPlayer,
viewGame, type Action, type Game,
} from '../game/engine';
import { type SpiceCard } from '../game/cards';
import { validateNexusCards } from '../game/nexus-cards';
import { nextStrongholdFactionsNativeStep, type StrongholdFactionsNativeStep } from './fixture-stronghold-factions';
export type NexusAdvisorFreshFixture = {
initial: Game;
afterSetup: Game;
game: Game;
owner: string;
foreign: string;
canceler: string;
card: string;
territory: string;
location: string;
event: string;
declaration: Action;
batchDeclaration: Action;
ordinaryAction: Action;
actions: StrongholdFactionsNativeStep[];
beforeForeignShipment: Game;
afterForeignShipment: Game;
beforeAccompaniment: Game;
afterAccompaniment: Game;
beforeReinforcement: Game;
scenario: {
turn: number;
storm: number;
lockedTurn: number;
advisors: number;
opposingFighters: number;
alliance: [string, string];
ordinaryTarget: string;
undealtOrder: string[];
};
};
const clean = (game: Game) => !game.phaseOpening && !game.response && !game.decision &&
!game.pendingGuildBetrayal;
export function nexusAdvisorFreshPlayer(game: Game, actor: string) {
const player = game.players.find(p => p.id === actor);
assert.ok(player, `Missing original advisor seat ${actor}`);
return player;
}
function entropy<T>(producer: () => T): T {
const descriptor = Object.getOwnPropertyDescriptor(crypto, 'getRandomValues');
crypto.getRandomValues = <V extends ArrayBufferView | null>(array: V): V => {
assert.ok(array);
new Uint8Array(array.buffer, array.byteOffset, array.byteLength).fill(255);
return array;
};
try { return producer(); }
finally {
if (descriptor) Object.defineProperty(crypto, 'getRandomValues', descriptor);
else Reflect.deleteProperty(crypto, 'getRandomValues');
}
}
function nativeStep(game: Game): StrongholdFactionsNativeStep {
// Native reaction states now initialize at fresh setup for classic core games:
// finish the real Guild Betrayal acknowledgement and purchased-card windows.
const betrayal = game.pendingGuildBetrayal;
if (betrayal) {
const seat = betrayal.required.find(id => !betrayal.passed.includes(id));
assert.ok(seat, 'The pending Guild Betrayal window must retain an unfinished responder');
return { actor: seat, action: { type: 'guildBetrayalPass', event: betrayal.invoice.event } };
}
if (!game.phaseOpening && !game.response) {
const decision = game.decision;
if (decision?.kind === 'nexusIxianReplacement')
return { actor: decision.player, action: { type: 'nexusIxianReplacementPass', event: decision.event } };
if (decision?.kind === 'guildTiming')
return { actor: decision.player, action: { type: 'decision', take: game.turn === 2 } };
// Preserve original advisors through the earlier mandatory declaration.
if (decision?.kind === 'advisorBattle' || decision?.kind === 'advisor' || decision?.kind === 'intrusion')
return { actor: decision.player, action: { type: 'decision', accept: false } };
if (game.status === 'setup' && game.setupStage === 'prediction')
return { actor: 'bg', action: { type: 'predict', faction: 'emperor', turn: 10 } };
if (game.status === 'setup' && game.setupStage === 'forces' && !nexusAdvisorFreshPlayer(game, 'bg').advisorSetup)
return { actor: 'bg', action: { type: 'advisorSetup', territory: 'arrakeen', sector: 10 } };
}
const step = nextStrongholdFactionsNativeStep(game);
assert.ok(step, 'The genuine advisor fixture reached an unexpected private battle plan');
return step;
}
function run(game: Game, step: StrongholdFactionsNativeStep, actions: StrongholdFactionsNativeStep[]): Game {
actions.push(structuredClone(step));
return entropy(() => applyAction(game, step.actor, step.action));
}
function advance(game: Game, until: (state: Game) => boolean, actions: StrongholdFactionsNativeStep[]): Game {
for (let n = 0; n < 1800; n++) {
if (until(game)) return game;
assert.notEqual(game.status, 'finished');
game = run(game, nativeStep(game), actions);
}
throw Error('Native advisor progression did not reach its requested boundary');
}
/** Finish only original acknowledgement/decision windows, never another movement turn. */
export function settleNexusAdvisorFresh(game: Game): Game {
return advance(game, clean, []);
}
/** Reorder undealt Spice only. Previously blown cards retain their discard custody. */
function orderSpice(game: Game, kinds: ('land' | 'worm')[], order: string[]): void {
const front: SpiceCard[] = [];
for (const kind of kinds) {
const index = game.spiceDeck.findIndex(card => kind === 'worm' ? 'worm' in card :
'territory' in card && !['imperial_basin', 'arrakeen', 'tueks_sietch'].includes(card.territory));
assert.ok(index >= 0, `Missing original undealt ${kind}`);
const card = game.spiceDeck.splice(index, 1)[0];
front.push(card);
order.push('territory' in card ? card.territory : 'worm');
}
game.spiceDeck.unshift(...front);
}
/** Every force, payment, lock, alliance, card deal and continuation is native.
* The only staged values are physical undealt deck order and producer entropy. */
export function createNexusAdvisorFreshFixture(): NexusAdvisorFreshFixture {
const owner = 'bg', foreign = 'guild', canceler = foreign;
const territory = 'shield_wall', location = 'shield_wall:9';
const actions: StrongholdFactionsNativeStep[] = [], undealtOrder: string[] = [];
let game = createGame('NEXUSADVISORFRESH', newPlayer(owner, 'Bene Gesserit', 'beneGesserit'), true, []);
joinGame(game, newPlayer(foreign, 'Guild', 'guild'));
joinGame(game, newPlayer('emperor', 'Emperor', 'emperor'));
joinGame(game, newPlayer('atreides', 'Atreides', 'atreides'));
game.nexusCards = { cards: null, phase: null };
for (const player of game.players) game = run(game, { actor: player.id, action: { type: 'ready' } }, actions);
game = entropy(() => initializeNexusGameForAudit(game));
assert.equal(game.status, 'setup');
assert.ok(game.players.every(p => p.hand.length === 0));
// The original starting deal consumes one card per seat, in roster order.
const karamaIndex = game.deck.findIndex(card => card.effect === 'karama');
assert.ok(karamaIndex >= 0);
const card = game.deck[karamaIndex].id;
[game.deck[1], game.deck[karamaIndex]] = [game.deck[karamaIndex], game.deck[1]];
undealtOrder.push(`Starting deal ${card} to ${canceler}`);
const initial = structuredClone(game);
game = advance(game, state => state.status === 'playing', actions);
const afterSetup = structuredClone(game);
assert.ok(nexusAdvisorFreshPlayer(game, canceler).hand.some(held => held.id === card));
game = advance(game, state => state.phase === 1 && clean(state), actions);
orderSpice(game, ['land', 'land'], undealtOrder);
game = advance(game, state => state.phase === 5 && state.active === 'atreides' && clean(state), actions);
// A genuine nearby occupied destination makes the later ordinary flip action
// geometrically legal; only its fresh advisor stance disallows fighter entry.
game = run(game, { actor: 'atreides', action: { type: 'ship', territory: 'imperial_basin', sector: 10, amount: 1 } }, actions);
game = advance(game, clean, actions);
game = advance(game, state => state.turn === 2 && state.phase === 1 && clean(state), actions);
orderSpice(game, ['worm', 'land', 'land'], undealtOrder);
game = advance(game, state => state.nexus === true && !state.spiceWindow, actions);
game = run(game, { actor: 'emperor', action: { type: 'alliance', target: 'atreides' } }, actions);
game = run(game, { actor: 'atreides', action: { type: 'alliance', target: 'emperor' } }, actions);
game = advance(game, state => state.nexusCards?.phase?.stage === 'drawing', actions);
for (const [actor, face] of [[owner, 'beneGesserit'], [foreign, 'guild']] as const) {
const cards = game.nexusCards!.cards!;
const index = cards.deck.indexOf(face);
assert.ok(index >= 0);
cards.deck.unshift(cards.deck.splice(index, 1)[0]);
undealtOrder.push(`Closing Nexus ${face} to ${actor}`);
game = run(game, { actor, action: { type: 'nexusCardChoice', turn: game.turn, card: null, choice: 'draw', ownRedraws: 0 } }, actions);
}
game = advance(game, state => state.phase === 5 && state.active === foreign && clean(state), actions);
const beforeForeignShipment = structuredClone(game);
game = run(game, { actor: foreign, action: { type: 'ship', territory, sector: 9, amount: 1 } }, actions);
game = advance(game, state => state.decision?.kind === 'advisor' && !state.response && !state.phaseOpening, actions);
const afterForeignShipment = structuredClone(game), beforeAccompaniment = structuredClone(game);
game = run(game, { actor: owner, action: { type: 'decision', accept: true, accompany: true, amount: 1 } }, actions);
game = advance(game, clean, actions);
const afterAccompaniment = structuredClone(game);
game = run(game, { actor: foreign, action: { type: 'endMovement' } }, actions);
game = advance(game, state => state.phase === 5 && state.active === owner && clean(state), actions);
const beforeReinforcement = structuredClone(game);
game = run(game, { actor: owner, action: { type: 'ship', territory, sector: 9, amount: 1 } }, actions);
game = advance(game, clean, actions);
const player = nexusAdvisorFreshPlayer(game, owner);
assert.equal(player.advisors?.[territory]?.lockedTurn, game.turn);
assert.equal(player.forces[location], 2);
assert.equal(game.nexusCards!.cards!.hands[owner], 'beneGesserit');
assert.equal(player.ally, null);
validateNexusCards(game.nexusCards!.cards!, game.players);
const event = viewGame(game, owner).nexusAdvisors!.offer!.event;
return {
initial, afterSetup, game, owner, foreign, canceler, card, territory, location, event,
declaration: { type: 'nexusAdvisors', event, territories: [territory] },
batchDeclaration: { type: 'nexusAdvisors', event, territories: ['arrakeen', territory] },
ordinaryAction: { type: 'move', from: location, territory: 'imperial_basin', sector: 10, amount: 1, fighters: true },
actions, beforeForeignShipment, afterForeignShipment, beforeAccompaniment, afterAccompaniment, beforeReinforcement,
scenario: { turn: game.turn, storm: game.storm, lockedTurn: player.advisors![territory].lockedTurn!,
advisors: player.forces[location], opposingFighters: nexusAdvisorFreshPlayer(game, foreign).forces[location],
alliance: ['emperor', 'atreides'], ordinaryTarget: 'imperial_basin:10', undealtOrder },
};
}