Prototype conditional future battle Truthtrance
Verify and publish checkpoint container / container (push) Failing after 11m11s

This commit is contained in:
Quintine committed 2026-10-28 21:17:44 +11:00
1 parent 71009d73e3
commit 02695aea7c
15 files changed
+721 -8

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
/** A conditional commitment for the respondent's first later native battle.
* Waiting does not require a battle to occur. The engine owns plan legality,
* answer feasibility and the decision that an obligation became impossible. */
export type FutureBattleLeaderReceipt = {
turn: number;
asker: string;
player: string;
leader: string;
answer: boolean;
battle?: string;
status: 'waiting' | 'bound' | 'settled' | 'expired' | 'released';
};
export type FutureBattleLeaderSource = {
turn: number;
event: string;
attacker: string;
defender: string;
native: boolean;
plansOpen: boolean;
};
export class FutureBattleLeaderError extends Error {}
function check(condition: unknown, message: string): asserts condition {
if (!condition) throw new FutureBattleLeaderError(message);
}
function validId(value: unknown): value is string {
return typeof value === 'string' && value.length <= 256 && value.trim() === value && value.length > 0;
}
function validTurn(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) > 0;
}
function validateReceipt(receipt: FutureBattleLeaderReceipt): void {
check(!!receipt && typeof receipt === 'object' && !Array.isArray(receipt), 'Invalid future battle receipt.');
check(validTurn(receipt.turn) && validId(receipt.asker) && validId(receipt.player) &&
receipt.asker !== receipt.player && validId(receipt.leader) && typeof receipt.answer === 'boolean',
'Invalid future battle question.');
check(['waiting', 'bound', 'settled', 'expired', 'released'].includes(receipt.status),
'Invalid future battle status.');
check(receipt.battle === undefined || validId(receipt.battle), 'Invalid future battle identity.');
check(receipt.status !== 'waiting' && receipt.status !== 'expired' || receipt.battle === undefined,
'A waiting or expired question cannot name a battle.');
check(receipt.status !== 'bound' && receipt.status !== 'settled' || receipt.battle !== undefined,
'A bound or settled question must name its battle.');
}
/** Caller supplies the respondent's public, native leader identities; no hand is inspected. */
export function createFutureBattleLeader(
question: Pick<FutureBattleLeaderReceipt, 'turn' | 'asker' | 'player' | 'leader' | 'answer'>,
nativeLeaders: readonly string[],
): FutureBattleLeaderReceipt {
check(!!question && typeof question === 'object', 'Invalid future battle question.');
const receipt: FutureBattleLeaderReceipt = { turn: question.turn, asker: question.asker,
player: question.player, leader: question.leader, answer: question.answer, status: 'waiting' };
validateReceipt(receipt);
check(Array.isArray(nativeLeaders) && nativeLeaders.every(validId) &&
new Set(nativeLeaders).size === nativeLeaders.length && nativeLeaders.includes(receipt.leader),
'Choose one of the respondent’s native leader identities.');
return receipt;
}
/** Observe a real battle; unrelated battles and nonmatching turns leave it waiting. */
export function bindFutureBattleLeader(
receipt: FutureBattleLeaderReceipt,
source: FutureBattleLeaderSource,
): FutureBattleLeaderReceipt {
validateReceipt(receipt);
check(!!source && typeof source === 'object' && validTurn(source.turn) && validId(source.event) &&
validId(source.attacker) && validId(source.defender) && source.attacker !== source.defender &&
typeof source.native === 'boolean' && typeof source.plansOpen === 'boolean',
'Invalid observed battle.');
if (receipt.status !== 'waiting' || receipt.turn !== source.turn || !source.native ||
(receipt.player !== source.attacker && receipt.player !== source.defender))
return receipt;
check(source.plansOpen, 'The respondent’s first qualifying battle is already past plan preparation.');
return { ...receipt, battle: source.event, status: 'bound' };
}
/** Only the actual revealed respondent plan can settle the linked receipt. */
export function settleFutureBattleLeader(
receipt: FutureBattleLeaderReceipt,
observed: { turn: number; battle: string; player: string; revealed: boolean; leader: string | null },
): FutureBattleLeaderReceipt {
validateReceipt(receipt);
check(!!observed && typeof observed === 'object' && validTurn(observed.turn) && validId(observed.battle) &&
validId(observed.player) && typeof observed.revealed === 'boolean' &&
(observed.leader === null || validId(observed.leader)), 'Invalid revealed battle plan.');
check(receipt.turn === observed.turn && receipt.battle === observed.battle && receipt.player === observed.player,
'The revealed plan does not belong to this question.');
check((receipt.status === 'bound' || receipt.status === 'settled') && observed.revealed,
'Only an actual revealed bound plan can settle this question.');
check((observed.leader === receipt.leader) === receipt.answer,
'The revealed plan contradicts the active future battle answer.');
if (receipt.status === 'settled') return receipt;
return { ...receipt, status: 'settled' };
}
/** The caller must independently establish impossibility before invoking this. */
export function releaseFutureBattleLeader(
receipt: FutureBattleLeaderReceipt,
source: { turn: number; battle?: string },
): FutureBattleLeaderReceipt {
validateReceipt(receipt);
check(!!source && typeof source === 'object' && validTurn(source.turn) &&
(source.battle === undefined || validId(source.battle)) && source.turn === receipt.turn &&
source.battle === receipt.battle, 'The release does not belong to this question.');
if (receipt.status === 'released') return receipt;
check(receipt.status === 'waiting' || receipt.status === 'bound', 'This question cannot be released.');
return { ...receipt, status: 'released' };
}
/** Only unbound questions expire when their original Battle phase has ended. */
export function expireFutureBattleLeader(
receipt: FutureBattleLeaderReceipt,
source: { turn: number; battlePhaseEnded: boolean },
): FutureBattleLeaderReceipt {
validateReceipt(receipt);
check(!!source && typeof source === 'object' && validTurn(source.turn) &&
typeof source.battlePhaseEnded === 'boolean' && source.turn === receipt.turn,
'The phase end does not belong to this question.');
if (!source.battlePhaseEnded || receipt.status !== 'waiting') return receipt;
return { ...receipt, status: 'expired' };
}