Files
Dune/tests/kull-whole-guild-arrival-guard.test.ts
T
Quintine 6e1b95d2fc
Verify and publish checkpoint container / container (push) Successful in 10m22s
Allow bounded Kull cancellation continuations
2026-10-29 01:46:28 +11:00

69 lines
4.0 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, joinGame, newPlayer, viewGame, type Game } from '../game/engine';
import { act, advance, clean, inventory, player } from './fixture-kull-native-ix';
/** Genuine public Basic setup and movement; only unopened card and Spice
* order is selected. The live whole answer and both overlapping response
* windows arise through original actions, not installed pending state. */
function source() {
let game=createGame('KULLBGGUILD',newPlayer('choam','CHOAM','choam'),false,['choam','ix']);
for (const id of ['emperor','beneGesserit','guild','harkonnen'] as const)
joinGame(game,newPlayer(id,id,id));
game=act(game,'choam',{type:'moduleOption',module:'kullPreview',enabled:true});
for (const seat of game.players) game=act(game,seat.id,{type:'ready'});
game=act(game,'choam',{type:'start'});
assert.equal(game.status,'setup'); assert.ok(game.kullPreview);
const originalCards=inventory(game), selected:Game['deck']=[];
for (const kind of ['kull','karama','filler','truth','karama','filler'] as const) {
const index=game.deck.findIndex(card=>kind==='kull'?card.id==='ix-kull-wahad':
kind==='truth'?card.effect==='truthtrance':kind==='karama'?card.effect==='karama':
card.kind==='worthless' && card.id!=='ix-kull-wahad');
assert.ok(index>=0); selected.push(game.deck.splice(index,1)[0]);
}
game.deck.unshift(...selected);
game=advance(game,state=>state.status==='playing');
const [,karama,,truth]=selected.map(card=>card.id);
assert.ok(player(game,'emperor').hand.some(card=>card.id===karama));
assert.ok(player(game,'guild').hand.some(card=>card.id===truth));
const safe=game.spiceDeck.findIndex(card=>'territory' in card && !card.discovery); assert.ok(safe>=0);
game.spiceDeck.unshift(game.spiceDeck.splice(safe,1)[0]);
game=advance(game,state=>state.phase===2 && clean(state) && state.choamCharity?.turn===state.turn);
game=act(game,'guild',{type:'card',card:truth});
while (game.truthtrance?.stage==='priority') {
const seat=game.players.find(row=>!game.truthtrance!.passed.includes(row.id)); assert.ok(seat);
game=act(game,seat.id,{type:'truthPass'});
}
game=act(game,'guild',{type:'truthAsk',question:{kind:'turnActions',target:'emperor',turn:game.turn,
mixed:{kind:'action',action:{kind:'payment',recipient:'harkonnen',minimum:1}}}});
assert.ok(viewGame(game,'emperor').truthTurnActionAnswers?.includes('yes'));
game=act(game,'emperor',{type:'truthAnswer',answer:'yes'});
const promise=game.turnActionPromises?.at(-1); assert.ok(promise);
game=advance(game,state=>state.phase===5 && state.active==='emperor' && clean(state));
game=act(game,'emperor',{type:'ship',territory:'polar_sink',sector:0,amount:1,elite:0,allyPayment:0});
assert.equal(game.response?.kind,'guildIncome');
assert.equal(game.decision?.kind,'advisor');
assert.ok(!promise.released);
return {game,karama,event:promise.event,originalCards};
}
void test('live whole-answer preflight fences Guild income cancellation without consuming a simultaneous BG arrival decision',()=>{
const f=source(); let game=f.game;
const before=structuredClone(game);
assert.ok(!viewGame(game,'emperor').responseControls?.cancelCards.includes(f.karama));
assert.throws(()=>act(game,'emperor',{type:'card',mode:'cancel',card:f.karama}));
assert.deepEqual(game,before);
assert.equal(game.pendingKull ?? null,null);
assert.deepEqual(game.decision,before.decision);
assert.ok(game.turnActionPromises?.some(row=>row.event===f.event && !row.released));
for (let count=0;game.response?.kind==='guildIncome' && count<game.players.length;count++) {
const seat=game.players.find(row=>!game.response!.passed.includes(row.id)); assert.ok(seat);
game=act(game,seat.id,{type:'passResponse'});
}
assert.equal(game.response,null);
assert.deepEqual(game.decision,before.decision);
game=act(game,'beneGesserit',{type:'decision',accept:false});
assert.equal(game.decision,null);
assert.deepEqual(inventory(game),f.originalCards);
});