Files
Dune/tests/choam-market-auto-waiver.test.ts
T
Quintine 825f903cce
Verify and publish checkpoint container / container (push) Successful in 10m42s
Add opt-in CHOAM market skip for the current turn
2026-10-29 07:08:53 +11:00

114 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Game } from '../game/engine';
import { actChoamCharityFunding, advanceChoamCharityFunding, createChoamCharityFundingFixture,
nextChoamCharityFundingStep } from './fixture-choam-charity-funding';
let fixture: ReturnType<typeof createChoamCharityFundingFixture> | undefined;
const source = () => fixture ??= createChoamCharityFundingFixture();
const owner = 'choam', observer = 'atreides';
const seat = (g: Game, id: string) => {
const player = g.players.find(p => p.id === id); assert.ok(player); return player;
};
const step = (g: Game) => {
const next = nextChoamCharityFundingStep(g); assert.ok(next);
return actChoamCharityFunding(g, next.actor, next.action);
};
const rejected = (g: Game, id: string, action: {type:string; [key:string]:unknown}) => {
const before = structuredClone(g);
assert.throws(() => applyAction(g, id, action));
assert.deepEqual(g, before);
};
/** Public Basic CHOAM/Atreides/Guild game, with the fixture's original deal
* and genuine setup and Storm actions; stop at the actual first closing market. */
function firstMarket() {
const g = advanceChoamCharityFunding(structuredClone(source().afterSetup), state =>
state.turn === 1 && state.phase === 0 && state.decision?.kind === 'choamMarket');
assert.deepEqual(g.expansions, ['choam']);
assert.equal(g.advanced, false);
assert.equal(g.choamMarket?.owner, owner);
assert.equal(g.choamMarket.resume, 'storm');
return g;
}
function optIn(g: Game) {
return actChoamCharityFunding(g, owner, {type:'decision', done:true, skipMarketsThisTurn:true});
}
void test('a human CHOAM can waive remaining markets this turn and the native Storm continuation proceeds', () => {
const before = firstMarket();
assert.equal(viewGame(before, owner).choamMarketSkipAvailable, true);
assert.equal(viewGame(before, owner).choamMarketSkipActive, false);
assert.equal(viewGame(before, observer).choamMarketSkipAvailable, false);
assert.equal(viewGame(before, observer).choamMarketSkipActive, false);
const game = optIn(before);
assert.equal(game.turn, 1);
assert.equal(game.phase, 1);
assert.equal(game.choamMarket, null);
assert.equal(viewGame(game, owner).choamMarketSkipActive, true);
assert.equal(viewGame(game, observer).choamMarketSkipActive, false);
let next = JSON.parse(JSON.stringify(game)) as Game;
for (let count = 0; next.turn === 1 && next.phase < 7 && count < 800; count++) {
assert.notEqual(next.decision?.kind, 'choamMarket', `Unexpected market in phase ${next.phase}`);
next = step(next);
}
assert.equal(next.turn, 1);
assert.equal(next.phase, 7);
assert.equal(viewGame(next, owner).choamMarketSkipActive, true);
assert.notEqual(next.decision?.kind, 'choamMarket');
});
void test('waiver continuation does not inspect or depend on CHOAM’s hidden card faces', () => {
const original = optIn(firstMarket());
const changed = structuredClone(original);
const choam = seat(changed, owner), guild = seat(changed, 'guild');
assert.ok(choam.hand.length && guild.hand.length);
// Swap two actual held cards, preserving all public counts and custody.
[choam.hand[0], guild.hand[0]] = [guild.hand[0], choam.hand[0]];
assert.deepEqual(viewGame(original, observer), viewGame(changed, observer));
for (let game of [original, changed]) {
for (let count = 0; game.phase === 1 && count < 100; count++) {
assert.notEqual(game.decision?.kind, 'choamMarket');
game = step(game);
}
assert.equal(game.phase, 2);
assert.notEqual(game.decision?.kind, 'choamMarket');
}
});
void test('only CHOAM can opt in or revoke, and revocation restores the next real market', () => {
const market = firstMarket();
rejected(market, observer, {type:'decision', done:true, skipMarketsThisTurn:true});
rejected(market, owner, {type:'cancelChoamMarketSkip'});
let game = optIn(market);
rejected(game, observer, {type:'cancelChoamMarketSkip'});
game = actChoamCharityFunding(game, owner, {type:'cancelChoamMarketSkip'});
assert.equal(viewGame(game, owner).choamMarketSkipActive, false);
rejected(game, owner, {type:'cancelChoamMarketSkip'});
game = advanceChoamCharityFunding(game, state => state.phase === 1 && state.decision?.kind === 'choamMarket');
assert.equal(game.choamMarket?.owner, owner);
assert.equal(viewGame(game, owner).choamMarketSkipAvailable, true);
game = actChoamCharityFunding(game, owner, {type:'decision', done:true});
assert.equal(viewGame(game, owner).choamMarketSkipActive, false);
});
for (const mode of ['bot','autopilot'] as const) void test(
`the standing waiver does not skip a market when CHOAM is under ${mode} control`, () => {
let game = optIn(firstMarket());
// A labelled staged controller handover; all game actions before it were native.
seat(game, owner)[mode] = 'Easy';
game = advanceChoamCharityFunding(game, state => state.phase === 1 && state.decision?.kind === 'choamMarket');
assert.equal(game.choamMarket?.owner, owner);
},
);
void test('the waiver expires at the next turn and preserves CHOAM’s final Mentat decision', () => {
let game = optIn(firstMarket());
game = advanceChoamCharityFunding(game, state => state.turn === 1 && state.decision?.kind === 'choamMentat');
assert.equal(game.phase, 8);
assert.equal(game.decision?.player, owner);
game = advanceChoamCharityFunding(game, state => state.turn === 2 && state.phase === 0 && state.decision?.kind === 'choamMarket');
assert.equal(viewGame(game, owner).choamMarketSkipActive, false);
assert.equal(viewGame(game, owner).choamMarketSkipAvailable, true);
});