import assert from 'node:assert/strict'; import test from 'node:test'; import { applyAction, createGame, newPlayer, type Game } from '../game/engine'; import { baseDeck } from '../game/cards'; import { acquireDuke, createDukeVidal, DUKE_VIDAL_ID } from '../game/duke-vidal'; import { richeseCards } from '../game/richese-cards'; import { moritaniTerrorAssassinationPool } from '../game/moritani-terror-assassination'; import { createMoritaniDukeCaptureFixture, revealMoritaniTerrorVictim } from './fixture-moritani-duke-capture'; function mark(game: Game, marker: 'uncertain' | 'legacy') { if (marker === 'uncertain') game.dukeCaptureUncertain = true; else delete game.dukeCaptureUncertain; } /** Staged ordinary pre-battle source based on the Residual Poison review * fixture. One real card and the one shared Duke are placed in their physical * zones; native leader deaths are controlled so the random pool is singleton. */ function residual(victim: 'duke' | 'native'): Game { let game = createGame('DUKERESIDUAL', newPlayer('r', 'Richese', 'richese'), false, ['choam']); game.players.push(newPlayer('g', 'Guild', 'guild'), newPlayer('e', 'Emperor', 'emperor')); Object.assign(game, {status:'playing',phase:6,turn:2,active:'r',order:['r','g','e'],storm:18, deck:baseDeck(),richeseCache:richeseCards()}); for (const player of game.players) { player.hand = []; player.spice = 10; player.forces = {}; player.reserves = 20; } for (const player of game.players.slice(0, 2)) { player.forces = {'arrakeen:10':2}; player.reserves = 18; } const cache = game.richeseCache!, index = cache.findIndex(card => card.effect === 'residualPoison'); assert.ok(index >= 0); game.players[0].hand.push(cache.splice(index, 1)[0]); const guild = game.players[1]; for (const leader of guild.leaders) { leader.dead = true; leader.deaths = 1; } if (victim === 'native') { guild.leaders[0].dead = false; guild.leaders[0].deaths = 0; } game.dukeVidal = acquireDuke(createDukeVidal(), 'g', 2, 'moritani'); if (victim === 'native') game.dukeVidal.leader.usedAt = 'carthag'; game = applyAction(game, 'r', {type:'chooseBattle', target:'g', territory:'arrakeen'}); return game; } for (const state of ['uncertain', 'legacy'] as const) { void test(`public Moritani Terror Duke death resolves ${state} uncertainty`, () => { const f = createMoritaniDukeCaptureFixture({terror:true}); mark(f.game, state); const {after} = revealMoritaniTerrorVictim(f, DUKE_VIDAL_ID); assert.equal(after.dukeVidal!.leader.dead, true); assert.equal(after.dukeCaptureUncertain, false); assert.ok(after.log.some(entry => entry.text.includes('Duke Prad Vidal was assassinated'))); }); void test(`Moritani Terror killing another leader preserves ${state} uncertainty`, () => { const f = createMoritaniDukeCaptureFixture({terror:true}); mark(f.game, state); const pool = moritaniTerrorAssassinationPool(f.game, f.captor); const other = pool.find(leader => leader.id !== DUKE_VIDAL_ID); assert.ok(other); const {after} = revealMoritaniTerrorVictim(f, other.id); assert.equal(after.dukeCaptureUncertain, state === 'uncertain' ? true : undefined); assert.equal(after.dukeVidal!.leader.dead, false); }); for (const victim of ['duke', 'native'] as const) { void test(`public Residual Poison ${victim} death ${victim === 'duke' ? 'resolves' : 'preserves'} ${state} uncertainty`, () => { const game = residual(victim); mark(game, state); const after = applyAction(game, 'r', {type:'card',card:'richese-residual-poison',target:'g',event:game.battle!.event}); assert.equal(after.dukeCaptureUncertain, victim === 'duke' ? false : state === 'uncertain' ? true : undefined); assert.equal(after.dukeVidal!.leader.dead, victim === 'duke'); assert.equal(after.log.some(entry => entry.text.includes('Duke Prad Vidal was randomly selected')), victim === 'duke'); }); } }