Files
Dune/tests/truthtrance-occupied-auction-inspection.test.ts
T
Quintine 4ec4f843a7
Verify and publish checkpoint container / container (push) Canceled after 0s
Honor Caladan auction knowledge in Truthtrance
2026-10-29 19:03:34 +11:00

99 lines
5.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { TRUTH_CARD_NAMES } from '../game/truthtrance-card-names';
import { knowledgeFactAnswer, truthKnowledgeOf } from '../game/truthtrance-knowledge';
import { freshPrivateLotGame, passPrivateLotWindow, privateLotPhysicalIds, privateLotPlayer,
recyclePrivatePurchaseIntoOriginalNormalLot, sellPrivateBlackMarketLot } from './fixture-homeworld-occupied-private-lots';
/** Stage the questioner's card by exchanging existing physical cards. The
* inspected/seller card remains untouched and no hand size is changed. */
function stageTruth(game: Game, protectedId: string) {
const original = privateLotPhysicalIds(game), asker = privateLotPlayer(game, 'ally');
const held = asker.hand.find(card => card.effect === 'truthtrance');
if (held) return held.id;
const stocks = [game.deck, game.discard, ...game.players.filter(p => p.id !== asker.id).map(p => p.hand)];
const source = stocks.find(cards => cards.some(card => card.effect === 'truthtrance' && card.id !== protectedId));
assert.ok(source, 'A distinct original Truthtrance must be available');
const index = source.findIndex(card => card.effect === 'truthtrance' && card.id !== protectedId);
const truth = source[index];
if (asker.hand.length) [asker.hand[0], source[index]] = [source[index], asker.hand[0]];
else asker.hand.push(...source.splice(index, 1));
assert.deepEqual(privateLotPhysicalIds(game), original);
return truth.id;
}
function ask(game: Game, card: string, name: string) {
let next = applyAction(game, 'ally', {type:'card',card});
while (next.truthtrance?.stage === 'priority') {
const p = next.players.find(player => !next.truthtrance!.passed.includes(player.id)); assert.ok(p);
next = applyAction(next, p.id, {type:'truthPass'});
}
return applyAction(next, 'ally', {type:'truthAsk', question:{kind:'fact',target:'occupier',fact:{kind:'auctionPeek',name}}});
}
function verify(state: Game, truth: string, name: string, expected: 'yes' | 'no', lot: 'ordinary' | 'blackMarket') {
const before = privateLotPhysicalIds(state), game = ask(structuredClone(state), truth, name);
assert.equal(viewGame(game, 'occupier').truthAnswer, expected);
assert.equal(viewGame(game, 'ally').truthAnswer, null);
assert.equal(viewGame(game, 'rival').truthAnswer, null);
if (lot === 'ordinary') {
assert.ok(viewGame(game, 'occupier').auction?.card);
assert.equal(viewGame(game, 'ally').auction?.card, null);
} else {
assert.ok(viewGame(game, 'occupier').richeseAuction?.card);
assert.equal(viewGame(game, 'ally').richeseAuction?.card, null);
}
for (const difficulty of DIFFICULTIES) {
const view = viewGame(game, 'occupier');
view.players.find(player => player.id === 'occupier')!.bot = difficulty;
assert.deepEqual(botActions(view), [{type:'truthAnswer',answer:expected}]);
const done = applyAction(game, 'occupier', {type:'truthAnswer',answer:expected});
assert.equal(done.truthHistory?.at(-1)?.answer, expected);
assert.deepEqual(privateLotPhysicalIds(done), before);
}
}
for (const denied of [false, true]) {
void test(`Caladan occupier answers actual Advanced Black Market inspection with native Atreides ${denied ? 'canceled' : 'allowed'}`, () => {
const fixture = freshPrivateLotGame();
let game = passPrivateLotWindow(fixture.game, 'richeseBlackMarket');
game = denied ? applyAction(game, 'rival', {type:'card',mode:'cancel',card:fixture.karama.id})
: passPrivateLotWindow(game, 'atreidesAuction');
assert.equal(game.homeworldAuctionInspection?.occupiedKnown, true);
assert.equal(game.richesePeekKnown, !denied);
const truth = stageTruth(game, fixture.card.id);
const wrong = TRUTH_CARD_NAMES.find(name => name !== fixture.card.name); assert.ok(wrong);
verify(game, truth, fixture.card.name, 'yes', 'blackMarket');
verify(game, truth, wrong, 'no', 'blackMarket');
});
}
void test('Caladan occupier answers after the original ordinary lot inspection', () => {
const fixture = freshPrivateLotGame();
let game = passPrivateLotWindow(fixture.game, 'richeseBlackMarket');
game = passPrivateLotWindow(game, 'atreidesAuction');
game = sellPrivateBlackMarketLot(game);
game = recyclePrivatePurchaseIntoOriginalNormalLot(game, fixture.card.id);
game = passPrivateLotWindow(game, 'atreidesAuction');
const truth = stageTruth(game, fixture.card.id);
const wrong = TRUTH_CARD_NAMES.find(name => name !== fixture.card.name); assert.ok(wrong);
verify(game, truth, fixture.card.name, 'yes', 'ordinary');
verify(game, truth, wrong, 'no', 'ordinary');
});
void test('absent or stale occupied receipt does not expose an auction observation', () => {
const fixture = freshPrivateLotGame();
let game = passPrivateLotWindow(fixture.game, 'richeseBlackMarket');
game = passPrivateLotWindow(game, 'atreidesAuction');
const receipt = structuredClone(game.homeworldAuctionInspection!);
game = sellPrivateBlackMarketLot(game);
game = recyclePrivatePurchaseIntoOriginalNormalLot(game, fixture.card.id);
const fact = {kind:'auctionPeek',name:fixture.card.name} as const;
const stale = structuredClone(game); stale.homeworldAuctionInspection = receipt;
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf(stale, privateLotPlayer(stale, 'occupier'))), 'unknown');
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf(game, privateLotPlayer(game, 'occupier'))), 'unknown');
});