Prototype Truthtrance ally funding commitments
Verify and publish checkpoint container / container (push) Successful in 10m48s

This commit is contained in:
Quintine committed 2026-10-29 05:44:01 +11:00
1 parent 68643925e7
commit c414a03a19
14 files changed
+795 -4

No files matched your search

+29 -1
View File
@@ -23,6 +23,7 @@ import { parseRevivalExpressionClaim, revivalExpressionText, revivalExpressionPr
import { parseLeaderRevivalClaim, leaderRevivalClaimText, leaderRevivalPromiseModeSupported, type LeaderRevivalClaim } from './leader-revival-promises';
import { parseAllianceClaim, allianceClaimText, alliancePromiseModeSupported, type AllianceClaim } from './alliance-promises';
import { parseBribeClaim, bribeClaimText, bribePromiseModeSupported, type BribeClaim } from './bribe-promises';
import { parsePledgeClaim, type PledgeClaim } from './pledge-promises';
import { parseRevivalClaim, revivalClaimText, revivalPromiseModeSupported, type RevivalClaim } from './revival-promises';
import { parseMovementClaim, movementClaimText, movementPromiseTargetAvailable, MovementClaimError, type MovementClaim } from './movement-promises';
import { shipmentAvailable } from './shipment-opportunity';
@@ -112,6 +113,7 @@ export type TruthQuestion =
| ({ kind: 'leaderRevival'; target: string } & LeaderRevivalClaim)
| ({ kind: 'alliance'; target: string } & AllianceClaim)
| ({ kind: 'bribe'; target: string } & BribeClaim)
| ({ kind: 'pledgeAid'; target: string } & PledgeClaim)
| ({ kind: 'revival'; target: string } & RevivalClaim)
| ({ kind: 'bid'; target: string } & BidClaim)
| ({ kind: 'phaseBid'; target: string } & PhaseBidClaim)
@@ -279,6 +281,10 @@ function parseQuestion(g: Game, asker: string, value: unknown): TruthQuestion {
);
// A joint planner for Charity and other future-action questions is not yet
// available. Do not bind incompatible action obligations in the interim.
if (v.kind !== 'pledgeAid' && v.kind !== 'fact' && !(v.kind === 'freeform' && v.scope === 'fact') &&
g.truthHistory?.some(row => row.turn === g.turn && row.phase === g.phase && row.answer !== 'unknown' &&
row.question.kind === 'pledgeAid'))
throw new TruthError('Other future-action questions are unavailable in a phase with an ally-funding answer.');
if (v.kind !== 'charity' && v.kind !== 'fact' && v.kind !== 'freeform' &&
g.charityPromises?.length && liveCharityPromises(g.charityPromises,v.target as string,g.turn).length)
throw new TruthError('Finish this player’s current Charity answer before binding another action question.');
@@ -369,6 +375,16 @@ function parseQuestion(g: Game, asker: string, value: unknown): TruthQuestion {
'Choose current-turn ordinary bribe statements to non-allied participants in a supported table.');
return { kind: 'bribeExpression', target: v.target, ...claim };
}
if (v.kind === 'pledgeAid') {
check(Object.keys(v).sort().join(',') === 'ally,kind,minimum,phase,target,turn',
'Supply the current phase, respondent, ally and funding minimum.');
const claim = parsePledgeClaim(v), respondent = g.players.find(player => player.id === v.target)!;
check(g.status === 'playing' && !g.advanced && claim.turn === g.turn && claim.phase === g.phase &&
respondent.ally === claim.ally && g.players.some(player => player.id === claim.ally && player.ally === respondent.id) &&
!g.response && !g.decision && !g.phaseOpening && !g.pendingTreacheryDiscard && !g.pendingKarama,
'Choose a current mutual ally in a supported quiet Basic Bidding or Shipment and Movement window.');
return {kind: 'pledgeAid', target: v.target, ...claim};
}
if (v.kind === 'bribe') {
check(Object.keys(v).sort().join(',') === 'kind,minimum,recipient,target,turn',
'Supply the current turn, named bribe recipient, minimum and respondent.');
@@ -711,7 +727,8 @@ export function validateSavedTruthtrance(g: Game) {
}
if (window?.question?.kind === 'fact' || window?.question?.kind === 'movement' ||
window?.question?.kind === 'freeform' && window.question.interpreted ||
window?.question?.kind === 'traitorCall' || window?.question?.kind === 'futureBattleLeader') {
window?.question?.kind === 'traitorCall' || window?.question?.kind === 'futureBattleLeader' ||
window?.question?.kind === 'pledgeAid') {
check((window.stage === 'answer' || window.stage === 'unknown') && window.queue.length > 0,
'The saved fact question must retain its asking player and answer stage.');
check(JSON.stringify(parseQuestion(g, window.queue[0].player, window.question)) === JSON.stringify(window.question),
@@ -825,6 +842,7 @@ export function truthQuestionText(
if (q.kind === 'alliance') return `Will you ${allianceClaimText(q, playerName)}?`;
if (q.kind === 'bribeExpression') return `Will you ${bribeExpressionText(q.claim, playerName)} from now until this turn ends?`;
if (q.kind === 'bribe') return `Will you ${bribeClaimText(q, playerName)}?`;
if (q.kind === 'pledgeAid') return `Will you update funding available to ${playerName(q.ally)} to at least ${q.minimum} spice during this phase?`;
if (q.kind === 'revival') return `Will you ${revivalClaimText(q)}?`;
if (q.kind === 'bid') return `Will you ${bidClaimText(q)}?`;
if (q.kind === 'phaseBid') return `Will you ${phaseBidClaimText(q)}?`;
@@ -955,6 +973,8 @@ export function resolveTruthAction(
bindBribeExpression?: (g: Game, p: Player, claim: BribeExpressionClaim, answer: boolean, asker: string) => void;
bribeAnswers?: (g: Game, p: Player, claim: BribeClaim) => TruthAnswer[];
bindBribe?: (g: Game, p: Player, claim: BribeClaim, answer: boolean, asker: string) => void;
pledgeAnswers?: (g: Game, p: Player, claim: PledgeClaim) => TruthAnswer[];
bindPledge?: (g: Game, p: Player, claim: PledgeClaim, answer: boolean, asker: string) => void;
mixedRevivalAnswers?: (g: Game, p: Player, claim: MixedRevivalClaim) => TruthAnswer[];
bindMixedRevival?: (g: Game, p: Player, claim: MixedRevivalClaim, answer: boolean, asker: string) => void;
revivalExpressionAnswers?: (g: Game, p: Player, claim: RevivalExpressionClaim) => TruthAnswer[];
@@ -1161,6 +1181,9 @@ export function resolveTruthAction(
if (w.question.kind === 'bribeExpression')
check(effects.bribeExpressionAnswers?.(g, g.players.find(other => other.id === w.question!.target)!, w.question).length,
'Choose a whole bribe expression compatible with earlier answers.');
if (w.question.kind === 'pledgeAid')
check(effects.pledgeAnswers?.(g, g.players.find(other => other.id === w.question!.target)!, w.question).length,
'Choose a supported ally-funding question compatible with current commitments.');
if (w.question.kind === 'bribe')
check(effects.bribeAnswers?.(g, g.players.find(other => other.id === w.question!.target)!, w.question).length,
'Choose a current bribe question compatible with earlier answers.');
@@ -1295,6 +1318,11 @@ export function resolveTruthAction(
'Choose a whole bribe answer consistent with actual funding and earlier promises.');
if (a.answer !== 'unknown') effects.bindBribeExpression(g, p, q, a.answer === 'yes', current.player);
}
if (q.kind === 'pledgeAid') {
check(effects.pledgeAnswers && effects.bindPledge && effects.pledgeAnswers(g, p, q).includes(a.answer),
'Choose an answer consistent with current ally-funding legality.');
if (a.answer !== 'unknown') effects.bindPledge(g, p, q, a.answer === 'yes', current.player);
}
if (q.kind === 'bribe') {
check(effects.bribeAnswers && effects.bindBribe && effects.bribeAnswers(g, p, q).includes(a.answer),
'Choose an answer consistent with actual ordinary bribe funding and earlier promises.');