Files
Dune/tests/nexus-kull-public-basic-runtime.test.ts
T
Quintine 95ebd7f976
Verify and publish checkpoint container / container (push) Successful in 11m12s
Enable Basic Nexus Kull and native reactions
2026-10-30 18:26:05 +11:00

96 lines
6.1 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, createGame, joinGame, newPlayer, normalizeAutomaticGame, viewGame, type Game, type Action } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { takeKullCard } from './fixture-choam-kull';
import { advanceSemutaNexusTrade, swapSemutaNexusTradeIdentity } from './fixture-semuta-nexus-trade';
import { orderIxSpiceCards, beforeIxSpiceDraw } from './fixture-ix-spice-cards';
const act = (game: Game, actor: string, action: Action) => applyAction(game, actor, action);
const player = (game: Game, id: string) => { const p = game.players.find(row => row.id === id); assert.ok(p); return p; };
const clean = (game: Game) => !game.phaseOpening && !game.response && !game.decision && !game.truthtrance;
const physical = (game: Game) => [...game.deck, ...game.discard,
...(game.auction?.cards.slice(game.auction.index) ?? []), ...game.players.flatMap(p => p.hand)]
.map(card => card.id).sort();
/** Native public start and original drawn Nexus. Conserved unopened Spice order,
* earned Nexus identity and physical card placements are explicitly staged. */
function fixture() {
let game = createGame('PUBLICNXKULL', newPlayer('c','CHOAM','choam'), false, ['choam','ix']);
for (const [id,faction] of [['a','atreides'],['g','guild'],['h','harkonnen']] as const)
joinGame(game,newPlayer(id,faction,faction));
game = act(game,'c',{type:'moduleOption',module:'nexusCards',enabled:true});
for (const p of game.players) game = act(game,p.id,{type:'ready'});
game = act(game,'c',{type:'start'});
assert.equal(game.kullPreview,true); assert.equal(game.nexusKullPreview,true);
assert.equal(game.spiceDeck.filter(card => 'sandtrout' in card).length,1);
game = advanceSemutaNexusTrade(game, state => state.status === 'playing');
const inventory = physical(game);
// Prevent incidental battles while retaining every physical counter.
for (const p of game.players) {p.reserves += Object.values(p.forces).reduce((a,b)=>a+b,0); p.forces = {};}
game = advanceSemutaNexusTrade(game, beforeIxSpiceDraw);
orderIxSpiceCards(game,['land','land']);
game = advanceSemutaNexusTrade(game, state => state.turn === 2 && beforeIxSpiceDraw(state));
orderIxSpiceCards(game,['worm','land','land']);
game = advanceSemutaNexusTrade(game, state => state.nexus && !state.spiceWindow && clean(state));
game = act(game,'a',{type:'alliance',target:'h'}); game = act(game,'h',{type:'alliance',target:'a'});
game = advanceSemutaNexusTrade(game, state => state.nexusCards?.phase?.stage === 'drawing' && clean(state));
assert.ok(game.nexusCards!.phase!.eligible.includes('c'));
game = act(game,'c',{type:'nexusCardChoice',turn:game.turn,card:null,choice:'draw',ownRedraws:0});
swapSemutaNexusTradeIdentity(game,'c','choam');
game = advanceSemutaNexusTrade(game, state => state.turn === 3 && beforeIxSpiceDraw(state));
orderIxSpiceCards(game,['sandtrout','land','land']);
game = advanceSemutaNexusTrade(game, state => state.phase === 5 && state.active === 'g' && clean(state));
assert.equal(game.sandtrout,true); assert.equal(game.nexusCards!.cards!.hands.c,'choam');
// Put actual original cards into hands; no card is created or deleted.
for (const p of game.players) game.deck.push(...p.hand.splice(0));
const printed = takeKullCard(game,'ix-kull-wahad'); player(game,'c').hand.push(printed);
const fuel = takeKullCard(game,'ix-hunter-seeker'); player(game,'c').hand.push(fuel);
const original = takeKullCard(game,'karama'); player(game,'g').hand.push(original);
const counter = takeKullCard(game,'karama'); player(game,'h').hand.push(counter);
assert.deepEqual(physical(game),inventory);
game = act(game,'g',{type:'card',mode:'shipment',card:original.id,target:'g'});
assert.ok(game.pendingKull); assert.equal(game.sandtrout,true);
return {game,inventory,printed:printed.id,fuel:fuel.id,original:original.id,counter:counter.id,event:game.pendingKull.event};
}
function passCounter(state: Game): Game {
let game=state;
while(game.pendingKull) {
const seat=game.players.find(p=>{const controls=viewGame(game,p.id).responseControls;
return controls && !controls.hasPassed && controls.cancelCards.length>0;});
game=seat ? act(game,seat.id,{type:'passResponse'}) : normalizeAutomaticGame(game);
}
return game;
}
for (const source of ['printed','nexus'] as const) void test(`public Basic Nexus Kull ${source} succeeds after actual Sandtrout`,()=>{
const f=fixture();
for(const difficulty of DIFFICULTIES) {
const view=viewGame(f.game,'c');view.players.find(p=>p.id==='c')!.bot=difficulty;
const choice=botActions(view).find(a=>a.type==='kullDecision');assert.ok(choice);
assert.doesNotThrow(()=>act(f.game,'c',choice));
}
let game=act(f.game,'c',{type:'kullDecision',event:f.event,source,card:source==='printed'?f.printed:f.fuel});
game=passCounter(game);
assert.equal(game.pendingKull,null);assert.equal(game.karamaShipping,null);
assert.equal(player(game,'g').hand.some(card=>card.id===f.original),true);
assert.equal(game.discard.filter(card=>card.id===(source==='printed'?f.printed:f.fuel)).length,1);
assert.equal(game.nexusCards!.cards!.hands.c,source==='printed'?'choam':null);
assert.equal(game.sandtrout,true);assert.deepEqual(physical(game),f.inventory);
});
void test('public Basic Nexus Kull decline and distinct counter resume the original Karama',()=>{
const f=fixture();
const declined=act(f.game,'c',{type:'kullDecision',event:f.event,decline:true});
assert.equal(declined.pendingKull,null);assert.equal(declined.karamaShipping?.owner,'g');
assert.equal(declined.nexusCards!.cards!.hands.c,'choam');
let countered=act(f.game,'c',{type:'kullDecision',event:f.event,source:'nexus',card:f.fuel});
countered=act(countered,'h',{type:'card',mode:'cancel',card:f.counter});
assert.equal(countered.pendingKull,null);assert.equal(countered.karamaShipping?.owner,'g');
assert.equal(player(countered,'c').hand.some(card=>card.id===f.fuel),true);
assert.equal(countered.nexusCards!.cards!.hands.c,null);
assert.deepEqual(physical(declined),f.inventory);assert.deepEqual(physical(countered),f.inventory);
});