Files
Dune/tests/truthtrance-guild-shipment-engine.test.ts
Quintine 2939ffa900 Support Truthtrance shipment promises with Advanced Guild
Admit base Advanced Guild through the existing active unused-shipment predicate. Preserve native interception, typed transport permission, rate cancellation and actual payment/arrival. Opposing stops evaluate the complete promise against absent shipment; self-interception cannot voluntarily evade a required shipment. Share the authoritative self-stop reason with controls, and hide executable completion guidance during pending native windows. No other profile opens or strategic AI tuning is added.

Validation and exact frozen-source evidence are in the canonical Truthtrance shipment contract. Public classic Advanced starts earn real physical Truthtrance/Karamas via the unopened original starting deal, native Guild TAKE/WAIT, typed Emperor delivery or opposing stop, and Guild half/full-price routes. Four minimal profiles use legal native continuations. Separate controlled probes cover No/mixed claims, self-stop, unaffordable repricing/owned alternatives and Fremen route denial. Update existing scope expectations; no new recovery campaign.

Broader timings/routes/modules, complete modes, final assurance and browser/deployed acceptance remain open. No saved-room/database/reset/restart/deployment operation.
2026-10-18 14:29:18 +11:00

152 lines
11 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { viewGame } from '../game/engine';
import { botActions } from '../game/bots';
import {
actTruthtranceGuildShipment, advanceTruthtranceGuildShipment, askTruthtranceGuildShipment,
completeTruthtranceGuildShipment, createTruthtranceGuildShipmentFixture,
truthtranceGuildShipmentClean, truthtranceGuildShipmentPlayer as player,
type TruthtranceGuildShipmentFixture,
} from './fixture-truthtrance-guild-shipment';
function source(f: TruthtranceGuildShipmentFixture) {
assert.equal(f.game.advanced, true);
assert.deepEqual(f.game.expansions, []);
assert.ok(!f.game.techTokens && !f.game.strongholdCards && !f.game.homeworlds && !f.game.nexusCards);
assert.ok(!f.game.leaderSkills && !f.game.discoveryEnabled);
assert.ok(f.actions.some(step => step.actor === f.game.host && step.action.type === 'start' && step.action.advancedPreview === true));
assert.ok(f.beforeStartingDeal.players.every(player => player.hand.length === 0));
assert.deepEqual(f.beforeStartingDeal.deck.slice(0, 3).map(card => card.id), [f.card, f.rateKarama, f.guildKarama]);
assert.ok(player(f.afterSetup, f.asker).hand.some(card => card.id === f.card && card.effect === 'truthtrance'));
assert.ok(player(f.afterSetup, f.rateCanceler).hand.some(card => card.id === f.rateKarama));
assert.ok(player(f.afterSetup, f.guild).hand.some(card => card.id === f.guildKarama));
assert.notEqual(f.guildKarama, f.rateKarama);
assert.equal(f.game.phase, 5);
assert.equal(f.game.active, f.target);
assert.equal(player(f.game, f.target).shipped, false);
assert.equal(player(f.game, f.target).moved, 0);
assert.equal(f.game.guildTimingLocked, false);
assert.equal(viewGame(f.game, f.asker).truthShipmentSupported, true);
assert.equal(f.sourceActionCount, f.actions.length);
}
function bound(f: TruthtranceGuildShipmentFixture, result: ReturnType<typeof completeTruthtranceGuildShipment>) {
assert.ok(viewGame(result.binding.asked, f.target).truthShipmentAnswers!.includes('yes'));
assert.equal(result.binding.game.truthtrance, null);
assert.equal(result.binding.game.discard.filter(card => card.id === f.card).length, 1);
assert.deepEqual(result.binding.game.shipmentPromises, [{
turn: f.game.turn, player: f.target, asker: f.asker,
territory: 'carthag', minimum: 3, answer: true,
}]);
assert.equal(result.declaration.game.decision?.kind, 'guildShipment');
assert.equal(result.declaration.game.pendingShipment!.elite, f.target === 'emperor' ? 1 : 0);
assert.deepEqual(result.declaration.game.players.map(player => ({ spice: player.spice, reserves: player.reserves,
forces: player.forces, elites: player.elites })), result.binding.game.players.map(player => ({ spice: player.spice,
reserves: player.reserves, forces: player.forces, elites: player.elites })));
assert.equal(result.declaration.game.shipmentPromises![0].fulfilled, undefined);
assert.equal(result.declaration.game.shipmentPromises![0].released, undefined);
assert.equal(viewGame(result.declaration.game, f.target).shipmentCompletion, null,
'The permission decision has no concurrently executable completion guide');
assert.equal(result.settled.discard.filter(card => card.id === f.card).length, 1);
assert.equal(result.game.discard.filter(card => card.id === f.card).length, 1);
assert.equal(player(result.settled, f.target).shipped, true);
assert.equal(player(result.settled, f.target).moved, 0);
assert.equal(result.settled.pendingShipment, null);
assert.equal(result.game.movementRemaining!.includes(f.target), false);
assert.equal(result.actions.filter(step => step.actor === f.target && step.action.type === 'ship').length, 1);
assert.equal(result.actions.filter(step => step.actor === f.target && step.action.type === 'endMovement').length, 1);
}
void test('genuine Advanced Guild WAIT and allowance deliver the promised typed reserve shipment with one native payment and fulfillment', () => {
const f = createTruthtranceGuildShipmentFixture(); source(f);
assert.ok(f.completedPrefix);
assert.equal(f.timing.length, 2);
assert.ok(f.timing.every(choice => choice.step.action.take === false));
const result = completeTruthtranceGuildShipment(f, 'allow'); bound(f, result);
assert.equal(player(result.binding.game, f.target).spice - player(result.settled, f.target).spice, 3);
assert.equal(player(result.settled, f.guild).spice - player(result.binding.game, f.guild).spice, 3);
assert.equal(player(result.binding.game, f.target).reserves - player(result.settled, f.target).reserves, 3);
assert.equal(player(result.binding.game, f.target).elites!.reserves - player(result.settled, f.target).elites!.reserves, 1);
assert.equal(player(result.settled, f.target).forces[f.location], 3);
assert.equal(player(result.settled, f.target).elites!.forces[f.location], 1);
assert.equal(result.settled.shipmentPromises![0].fulfilled, true);
assert.equal(result.settled.shipmentPromises![0].released, undefined);
assert.equal(player(result.settled, f.guild).specialKaramaUsed, undefined);
assert.equal(result.settled.discard.some(card => card.id === f.guildKarama), false);
});
void test('genuine Guild special Karama stop consumes the promised opportunity without payment or arrival and publicly releases the impossible positive', () => {
const f = createTruthtranceGuildShipmentFixture(); source(f);
const result = completeTruthtranceGuildShipment(f, 'stop'); bound(f, result);
assert.deepEqual(result.settled.players.map(player => ({ spice: player.spice, reserves: player.reserves,
forces: player.forces, elites: player.elites })), result.binding.game.players.map(player => ({ spice: player.spice,
reserves: player.reserves, forces: player.forces, elites: player.elites })));
assert.equal(player(result.settled, f.target).forces[f.location], undefined);
assert.equal(player(result.settled, f.guild).specialKaramaUsed, true);
assert.equal(result.settled.discard.filter(card => card.id === f.guildKarama).length, 1);
assert.equal(result.settled.shipmentPromises![0].released, true);
assert.equal(result.settled.shipmentPromises![0].fulfilled, undefined);
assert.equal(viewGame(result.settled, f.asker).shipmentPromises[0].released, true);
assert.ok(result.settled.log.slice(result.binding.game.log.length)
.some(entry => /Truthtrance.*no longer binding|Truthtrance.*released/i.test(entry.text)));
assert.throws(() => actTruthtranceGuildShipment(result.settled, f.target, result.declaration.step.action));
assert.throws(() => actTruthtranceGuildShipment(result.settled, f.guild, result.choice.action));
});
void test('genuine Guild TAKE reserve promise survives an actual opposing Karama rate cancellation and pays the funded full tariff once', () => {
const f = createTruthtranceGuildShipmentFixture({ target: 'guild' }); source(f);
assert.equal(f.timing.length, 1);
assert.equal(f.timing[0].step.action.take, true);
assert.equal(f.timing[0].declared.response?.kind, 'guildTiming');
assert.ok(f.actions.some(step => step.actor === f.rateCanceler && step.action.type === 'passResponse'));
assert.equal(f.game.guildTimingGranted, true);
const halfRate = completeTruthtranceGuildShipment(f, 'allow'); bound(f, halfRate);
assert.equal(player(halfRate.binding.game, f.target).spice - player(halfRate.settled, f.target).spice, 2);
assert.equal(player(halfRate.settled, f.target).forces[f.location], 3);
assert.equal(halfRate.settled.shipmentPromises![0].fulfilled, true);
assert.ok(player(halfRate.settled, f.rateCanceler).hand.some(card => card.id === f.rateKarama));
const result = completeTruthtranceGuildShipment(f, 'cancelRate'); bound(f, result);
assert.ok(result.rate);
assert.equal(result.rate.before.response?.kind, 'guildRate');
assert.equal(result.rate.before.pendingShipment!.cost, 2, 'The actual original declaration earns the native half price');
assert.equal(viewGame(result.rate.before, f.target).shipmentCompletion, null);
assert.ok(viewGame(result.rate.before, f.rateCanceler).responseControls!.cancelCards.includes(f.rateKarama));
assert.equal(player(result.binding.game, f.target).spice - player(result.settled, f.target).spice, 3);
assert.equal(player(result.binding.game, f.target).reserves - player(result.settled, f.target).reserves, 3);
assert.equal(player(result.settled, f.target).forces[f.location], 3);
assert.equal(result.settled.shipmentPromises![0].fulfilled, true);
assert.equal(result.settled.shipmentPromises![0].released, undefined);
assert.equal(result.settled.discard.filter(card => card.id === f.rateKarama).length, 1);
assert.equal(player(result.settled, f.guild).specialKaramaUsed, undefined);
assert.ok(player(result.settled, f.guild).hand.some(card => card.id === f.guildKarama));
});
void test('all four minimal profiles answer the real shipment question and use authoritative legal completion through native Guild permission', () => {
const f = createTruthtranceGuildShipmentFixture(), asked = askTruthtranceGuildShipment(f);
for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) {
const targetView = viewGame(asked.game, f.target);
targetView.players.find(player => player.id === f.target)!.bot = difficulty;
const answer = botActions(targetView).find(action => action.type === 'truthAnswer'); assert.ok(answer);
assert.ok(answer.answer === 'yes' || answer.answer === 'no' || answer.answer === 'unknown');
assert.ok(targetView.truthShipmentAnswers!.includes(answer.answer));
assert.equal(answer.answer, 'yes', 'The original funded stronghold predicate remains an ordinary positive policy choice');
let game = actTruthtranceGuildShipment(structuredClone(asked.game), f.target, answer);
const promised = viewGame(game, f.target);
promised.players.find(player => player.id === f.target)!.bot = difficulty;
const shipment = botActions(promised).find(action => action.type === 'ship'); assert.ok(shipment);
assert.equal(shipment.territory, 'carthag');
assert.ok(Number(shipment.amount) >= 3);
game = actTruthtranceGuildShipment(game, f.target, shipment);
assert.equal(game.decision?.kind, 'guildShipment');
const guildView = viewGame(game, f.guild);
guildView.players.find(player => player.id === f.guild)!.bot = difficulty;
const permission = botActions(guildView).find(action => action.type === 'decision' && action.allow === true ||
action.type === 'card' && action.mode === 'special' && action.card === f.guildKarama); assert.ok(permission);
game = actTruthtranceGuildShipment(game, f.guild, permission);
game = advanceTruthtranceGuildShipment(game,
game => truthtranceGuildShipmentClean(game) && !game.pendingShipment);
assert.equal(game.discard.filter(card => card.id === f.card).length, 1);
assert.equal(player(game, f.target).shipped, true);
assert.ok(game.shipmentPromises![0].fulfilled || game.shipmentPromises![0].released);
}
});