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.
This commit is contained in:
Quintine committed 2026-10-18 14:29:18 +11:00
1 parent 377e9c5160
commit 2939ffa900
16 files changed
+678 -53

No files matched your search

+29 -2
View File
@@ -22920,6 +22920,27 @@ function finishShipmentPromises(
))
promise.fulfilled = true;
}
function ownGuildStopPromiseBlock(g: Game, p: Player): string | null {
const decision = g.decision;
if (p.faction !== 'guild' ||
(decision?.kind !== 'guildShipment' && decision?.kind !== 'homeworldShipmentGuild') ||
decision.shipper !== p.id || g.pendingShipment?.source === 'ambassador') return null;
try { checkShipmentPromises(g, p, null); return null; }
catch (error) { if (error instanceof RuleError) return error.message; throw error; }
}
/** An opposing Guild stop consumes the opportunity without a shipment. It
* fulfills statements matching that absence and releases impossible answers;
* the respondent's promise cannot veto the opponent's printed power. */
function settleStoppedShipmentPromises(g: Game, p: Player, actor: string) {
if (actor === p.id) checkShipmentPromises(g, p, null);
for (const promise of liveShipmentPromises(g.shipmentPromises ?? [], p.id, g.turn)) {
if (matchesShipment(promise, null) === promise.answer) promise.fulfilled = true;
else {
promise.released = true;
log(g, `The Guild stopped ${p.name}’s shipment; that Truthtrance shipment answer is no longer binding.`);
}
}
}
function reconcileShipmentPromises(
g: Game,
voluntary?: { actor: string; action: Action },
@@ -29875,6 +29896,8 @@ export function prepareSpecialKaramaIntent(
},
};
} else if (p.faction === 'guild') {
const promiseBlock = ownGuildStopPromiseBlock(g, p);
requireRule(!promiseBlock, promiseBlock ?? 'Honor your Truthtrance shipment answer.');
if (g.decision?.kind === 'homeworldShipmentGuild') {
requireRule(!g.response && g.decision.player === p.id,
'Wait for your Homeworld shipment interception decision.');
@@ -30236,7 +30259,7 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
refundStoppedShipmentKarama(g, shipper);
g.pendingHomeworldShipment = null;
g.decision = null;
finishShipmentPromises(g, shipper, null);
settleStoppedShipmentPromises(g, shipper, p.id);
log(g, `${p.name} used special Karama to stop ${shipper.name}’s interplanetary shipment. No forces arrive or spice is paid; their shipment is used and movement remains available.`);
} else if (intent.kind === 'guild') {
const shipper = getPlayer(g, intent.target);
@@ -30263,6 +30286,7 @@ function resolveSpecialKaramaEffect(g: Game, p: Player, card: Card, intent: Spec
g,
`${p.name} used special Karama to stop ${shipper.name}’s shipment. No forces arrive or spice is paid; movement remains available.`,
);
settleStoppedShipmentPromises(g, shipper, p.id);
} else if (
intent.kind === 'emperorLeader' ||
intent.kind === 'emperorForces'
@@ -35127,6 +35151,7 @@ export function viewGame(state: Game, id: string) {
battleOrder: projectedBattleOrder(g),
guildAmbassadorAdvisorChoices: guildAdvisorChoices(g, id),
truthShipmentSupported: shipmentPromiseModeSupported(g),
guildShipmentStopPromiseBlock: ownGuildStopPromiseBlock(g, me),
truthShipmentAnswers:
g.truthtrance?.stage === 'answer' &&
g.truthtrance.question?.target === id &&
@@ -35138,7 +35163,9 @@ export function viewGame(state: Game, id: string) {
shipmentPromises: (g.shipmentPromises ?? []).filter(p => !p.mixed || p.player === id),
shipmentCompletion:
!g.pendingTreacheryDiscard &&
!g.response && !g.pendingKarama &&
!g.response && !g.pendingKarama && !g.truthtrance &&
!g.decision && !g.phaseOpening && !g.pendingShipment &&
!g.pendingGuildTransport && !g.pendingHomeworldShipment &&
liveShipmentPromises(g.shipmentPromises ?? [], id, g.turn).some(
(p) => matchesShipment(p,null) !== p.answer,
)