Files
Dune/tests/richese-reveal-interruption-quote.test.ts
T
Quintine 0d55333d5d
Verify and publish checkpoint container / container (push) Canceled after 0s
Allow Richese No-Field reveal during Basic openings and markets
2026-10-29 07:27:08 +11:00

106 lines
6.2 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, newPlayer, type Decision, type Game } from '../game/engine';
import { richeseRevealInterruptSupported as supported } from '../game/richese-reveal-interruption';
/** Staged parent records test only the admission quote; native parent and
* physical token transitions are exercised by the separate runtime suite. */
function source(): Game {
const g = createGame('REVEALQUOTE', newPlayer('r', 'Richese', 'richese'), false, ['choam', 'ix']);
g.players.push(newPlayer('c', 'CHOAM', 'choam'), newPlayer('e', 'Emperor', 'emperor'));
g.status = 'playing'; g.phase = 3; g.turn = 2;
return g;
}
function parent(kind: Decision['kind']): Game {
const g = source();
if (kind === 'auctionPayment') {
g.decision = {kind, player:'e'};
g.auction = {cards:[], index:0, bid:3, bidder:'e', active:'e', passed:[], opener:0};
} else if (kind === 'richeseDeclaration' || kind === 'richeseCacheTerms' || kind === 'richeseCache' || kind === 'richeseUnbid') {
g.decision = {kind, player:'r'};
g.richeseBidding = {owner:'r', event:'round', turn:g.turn,
stage:kind === 'richeseDeclaration' ? 'declaration' : kind === 'richeseUnbid' ? 'lot' : 'cacheOffer',
position:'first', normalCount:1, blackMarketSold:false, cacheCanceled:false};
if (kind === 'richeseUnbid') g.richeseAuction = {
event:'lot', cardId:'card', source:'cache', owner:'r', method:'onceAround', eligible:['r','e'],
order:['e','r'], tieOrder:['r','e'], active:null, bid:0, bidder:null, passed:['r','e'], acted:['r','e'], sealed:{}, outcome:{kind:'unbid'},
};
} else if (kind === 'choamMarket' || kind === 'choamTradeReply' || kind === 'choamTradeConfirm') {
g.decision = {kind, player:kind === 'choamTradeReply' ? 'r' : 'c'};
g.choamMarket = {owner:'c', resume:'phase', blocked:[],
...(kind === 'choamMarket' ? {} : {trade:{ally:'r', offered:'offered',
...(kind === 'choamTradeConfirm' ? {returned:'returned'} : {})}})};
} else if (kind === 'richeseAllyOpportunity') {
g.phase = 5;
g.decision = {kind, player:'r', recipient:'e'};
g.richeseAllyOpportunity = {turn:g.turn, recipient:'e'};
} else throw Error(`Unrecognized staged parent ${kind}`);
return g;
}
void test('each supported economic parent is admitted without modifying its frame or inspecting marker custody', () => {
for (const kind of ['auctionPayment','richeseDeclaration','richeseCacheTerms','richeseCache','richeseUnbid',
'choamMarket','choamTradeReply','choamTradeConfirm','richeseAllyOpportunity'] as const) {
const game = parent(kind), before = structuredClone(game);
assert.equal(supported(game), true, kind);
assert.deepEqual(game, before);
assert.equal(game.players[0].noField?.deployed ?? null, null, 'Seat/marker authorization belongs to the caller');
}
});
void test('a clean Ix phase opening remains admitted before or after its owner passes', () => {
const game = source(); game.phase = 0; game.phaseOpening = {passed:[], initialize:true}; game.amalWindowOpen = true;
assert.equal(supported(game), true);
game.phaseOpening.passed.push('r');
assert.equal(supported(game), true);
const before = structuredClone(game); assert.equal(supported(game), true); assert.deepEqual(game, before);
game.decision = {kind:'auctionPayment', player:'e'}; assert.equal(supported(game), false);
game.decision = null; game.amalWindowOpen = false; assert.equal(supported(game), false);
});
void test('a legacy table without public Truthtrance history keeps its earlier interruption behavior', () => {
const game = parent('auctionPayment');
delete game.truthHistory;
assert.equal(supported(game), false);
});
void test('profile and nested-interaction boundaries do not admit another parent or module', () => {
const patches: Array<Partial<Game>> = [
{advanced:true}, {status:'setup'}, {phase:6}, {phase:-1}, {turn:0},
{expansions:['choam','ecaz']}, {expansions:['ix']},
{response:{kind:'richeseAuction', owner:'r', passed:[]}},
{pendingNullentropy:{} as NonNullable<Game['pendingNullentropy']>},
{pendingTreacheryDiscard:{} as NonNullable<Game['pendingTreacheryDiscard']>},
{pendingShipment:{} as NonNullable<Game['pendingShipment']>},
{pendingKull:{} as NonNullable<Game['pendingKull']>},
{biddingEnd:{} as NonNullable<Game['biddingEnd']>},
{stormResolution:{} as NonNullable<Game['stormResolution']>},
{techTokens:{} as NonNullable<Game['techTokens']>},
{leaderSkills:{} as NonNullable<Game['leaderSkills']>},
{discoveryEnabled:true}, {ecazTreachery:true},
];
for (const patch of patches) {
const game = Object.assign(parent('auctionPayment'), patch), before = structuredClone(game);
assert.equal(supported(game), false, JSON.stringify(patch)); assert.deepEqual(game, before);
}
const wrong = parent('auctionPayment'); wrong.decision = {kind:'revivalStop', player:'e', recipient:'r', revival:'forces'};
assert.equal(supported(wrong), false);
const missing = parent('auctionPayment'); missing.auction = null; assert.equal(supported(missing), false);
const wrongSeat = parent('auctionPayment'); wrongSeat.auction!.bidder = 'r'; assert.equal(supported(wrongSeat), false);
const native = parent('auctionPayment'); native.players.push(newPlayer('i','Ixians','ixians'));
native.expansions = ['choam']; assert.equal(supported(native), false);
});
void test('live current-turn commitments block the interruption; settled and previous-turn records do not', () => {
const game = parent('auctionPayment');
game.shipmentPromises = [{turn:game.turn, player:'e', asker:'r', answer:true, territory:'carthag', minimum:3}];
assert.equal(supported(game), false);
game.shipmentPromises[0].fulfilled = true; assert.equal(supported(game), true);
game.shipmentPromises[0].fulfilled = false; game.shipmentPromises[0].turn--; assert.equal(supported(game), true);
game.shipmentPromises = [];
game.futureBattleLeaderPromises = [{turn:game.turn, asker:'r', player:'e', leader:'leader', answer:true, status:'waiting'}];
assert.equal(supported(game), false);
game.futureBattleLeaderPromises[0].status = 'settled'; assert.equal(supported(game), true);
game.deals = [{status:'active'}] as unknown as Game['deals']; assert.equal(supported(game), false);
});