Files
Dune/tests/fixture-ix-revealed-weapons.ts
Quintine 19e554666e Admit Mirror and Stone in Basic native E3 battles
Reuse original copied-weapon and typed coalition accounting for bare Basic
Ecaz/Moritani and selected Ix. Preserve optional-module guards and physical
card identity, actual force-owner commitments and native continuations.
2026-10-24 16:34:19 +11:00

68 lines
3.2 KiB
TypeScript

import assert from 'node:assert/strict';
import {applyAction, viewGame, type Action, type Game} from '../game/engine';
import {harassWithdrawGame} from './fixture-harass-withdraw';
import {stoneHarassCustody, takeStoneHarassCard, STONE_CARD, MIRROR_CARD} from './fixture-stone-harass';
import {finishOccupyCardsAftermath} from './fixture-ecaz-occupy-cards';
import {finishBattleOutcomeWindow} from './fixture-battle-outcome-window';
export const IX_TOOTH = 'ix-poison-tooth';
export type IxRevealedCase = {
game: Game;
native: 'ixians' | 'tleilaxu';
holder: 'a' | 'd';
other: 'a' | 'd';
kind: 'mirrorStone' | 'mirrorTooth' | 'stoneTooth';
plans: {actor: string; action: Action}[];
};
/** Original native Ix/Richese setup and exact deck, then the upstream fixture's
* explicit phase/board staging. Actual chooseBattle owns every battle control;
* this is a conserved rule fixture, not an unstaged complete-game claim. */
export function ixRevealedWeaponFixture({advanced = false, native = 'ixians', holder = 'a',
kind = 'mirrorStone'}: {advanced?: boolean; native?: 'ixians' | 'tleilaxu';
holder?: 'a' | 'd'; kind?: IxRevealedCase['kind']} = {}): IxRevealedCase {
const game = harassWithdrawGame({advanced, factions: [native, 'atreides', 'richese'],
normal: native === 'ixians' ? 2 : 4, elite: native === 'ixians' ? 2 : 0,
territory: 'shield_wall', sector: 8});
for (const player of game.players) {
game.deck.push(...player.hand);
player.hand = [];
}
const other = holder === 'a' ? 'd' : 'a';
const holderCard = kind === 'stoneTooth' ? STONE_CARD : MIRROR_CARD;
const sourceCard = kind === 'mirrorStone' ? STONE_CARD : IX_TOOTH;
takeStoneHarassCard(game, holder, holderCard);
takeStoneHarassCard(game, other, sourceCard);
const plans = (['a', 'd'] as const).map(actor => ({actor, action: {
// Full ordinary commitment remains preflight-safe against every opposing
// mixed Ix allocation; a hidden already-sealed zero dial is not a shortcut.
type: 'battlePlan', dial: actor === 'a' ? 0 : 5,
support: advanced && actor === 'd' ? 5 : 0,
leader: game.players.find(player => player.id === actor)!.leaders[0].id,
weapon: actor === holder ? holderCard : sourceCard,
} satisfies Action}));
stoneHarassCustody(game);
return {game, native, holder, other, kind, plans};
}
export function revealIxWeapons(source: IxRevealedCase): Game {
let game = source.game;
for (const {actor, action} of source.plans) game = applyAction(game, actor, action);
assert.equal(game.battle?.revealed, true);
return game;
}
export function finishIxWeaponBattle(state: Game): Game {
let game = state;
assert.equal(game.decision, null, 'All native reveal choices must precede traitor calls.');
for (const id of viewGame(game, 'a').battle!.traitorVoters)
game = applyAction(game, id, {type: 'traitorCall', call: false});
game = finishBattleOutcomeWindow(game);
game = finishOccupyCardsAftermath(game, {stopAtFaceDance: true});
if (game.decision?.kind === 'faceDance') {
game = applyAction(game, game.decision.player, {type: 'decision', reveal: false});
game = finishOccupyCardsAftermath(game);
}
assert.equal(game.battle, null);
assert.equal(game.decision, null);
stoneHarassCustody(game);
return game;
}