Support Revival answers with Richese Semuta
Verify and publish checkpoint container / container (push) Successful in 10m31s

This commit is contained in:
Quintine committed 2026-10-29 01:12:37 +11:00
1 parent 2eaaab876e
commit 4dfdd500e0
14 files changed
+540 -15

No files matched your search

+75
View File
@@ -1614,6 +1614,8 @@ export type Game = {
purpose?: KullPurpose;
/** New Basic Kull attempts may bind an existing structured bribe ledger. */
bribeReceipt?: true;
/** New Kull/Semuta attempts retain the original Revival answer ledger. */
revivalReceipt?: true;
stage: 'offer' | 'counter';
selection?: { source: 'printed' | 'nexus'; card: string; nexusEvent?: string };
intent: { kind: 'ordinary'; use: KaramaUse } | { kind: 'special'; use: SpecialKaramaIntent };
@@ -1927,6 +1929,11 @@ export type Game = {
| { kind: 'bribe'; index: number; value: BribePromise }
| { kind: 'bribeExpression'; index: number; value: BribeExpressionPromise }
| { kind: 'mixedBribe'; index: number; value: NonNullable<Game['mixedBribeQuestions']>[number] };
revivalBinding?:
| { kind: 'revival'; index: number; value: RevivalPromise }
| { kind: 'leaderRevival'; index: number; value: LeaderRevivalPromise }
| { kind: 'revivalExpression'; index: number; value: RevivalExpressionPromise }
| { kind: 'mixedRevival'; index: number; value: NonNullable<Game['mixedRevivalQuestions']>[number] };
parentSignature: string;
}
| {
@@ -7576,6 +7583,10 @@ function truthtranceDiscardSignature(g: Game, next: TruthDiscardContinuation) {
...(next.bribeBinding ? { bribeBinding: next.bribeBinding,
bribePromises: g.bribePromises, bribeExpressionPromises: g.bribeExpressionPromises,
mixedBribeQuestions: g.mixedBribeQuestions, bribePromiseSequence: g.bribePromiseSequence } : {}),
...(next.revivalBinding ? { revivalBinding: next.revivalBinding,
revivalPromises: g.revivalPromises, leaderRevivalPromises: g.leaderRevivalPromises,
revivalExpressionPromises: g.revivalExpressionPromises, mixedRevivalQuestions: g.mixedRevivalQuestions,
revivalPromiseSequence: g.revivalPromiseSequence } : {}),
controls: nullentropyParentSignature(
g,
next.resume,
@@ -7698,6 +7709,18 @@ function finishTruthtranceAnswer(g: Game) {
else if (g.semutaPreview && record.question.kind === 'mixedBribe') continuation.bribeBinding = {
kind: 'mixedBribe', index: g.mixedBribeQuestions!.length - 1, value: structuredClone(g.mixedBribeQuestions!.at(-1)!),
};
if (g.semutaPreview && record.question.kind === 'revival') continuation.revivalBinding = {
kind: 'revival', index: g.revivalPromises!.length - 1, value: structuredClone(g.revivalPromises!.at(-1)!),
};
else if (g.semutaPreview && record.question.kind === 'leaderRevival') continuation.revivalBinding = {
kind: 'leaderRevival', index: g.leaderRevivalPromises!.length - 1, value: structuredClone(g.leaderRevivalPromises!.at(-1)!),
};
else if (g.semutaPreview && record.question.kind === 'revivalExpression') continuation.revivalBinding = {
kind: 'revivalExpression', index: g.revivalExpressionPromises!.length - 1, value: structuredClone(g.revivalExpressionPromises!.at(-1)!),
};
else if (g.semutaPreview && record.question.kind === 'mixedRevival') continuation.revivalBinding = {
kind: 'mixedRevival', index: g.mixedRevivalQuestions!.length - 1, value: structuredClone(g.mixedRevivalQuestions!.at(-1)!),
};
continuation.parentSignature = truthtranceDiscardSignature(g, continuation);
stageTreacheryDiscard(
g,
@@ -8720,6 +8743,45 @@ function treacheryDiscardIntegrity(g: Game) {
'The completed mixed bribe answer no longer matches its payment binding.');
}
} else requireRule(!c.bribeBinding, 'This Truthtrance answer has no bribe binding.');
const expectedRevival = g.semutaPreview &&
(q.kind === 'revival' || q.kind === 'leaderRevival' || q.kind === 'revivalExpression' || q.kind === 'mixedRevival');
if (expectedRevival) {
const binding = c.revivalBinding;
const actual = binding?.kind === 'revival' ? g.revivalPromises :
binding?.kind === 'leaderRevival' ? g.leaderRevivalPromises :
binding?.kind === 'revivalExpression' ? g.revivalExpressionPromises : g.mixedRevivalQuestions;
requireRule(binding && binding.kind === q.kind && Array.isArray(actual) &&
Number.isSafeInteger(binding.index) && binding.index >= 0 && binding.index === actual.length - 1 &&
JSON.stringify(binding.value) === JSON.stringify(actual[binding.index]) &&
binding.value.player === q.target && binding.value.asker === record.asker &&
binding.value.answer === (record.answer === 'yes') && binding.value.turn === g.turn,
'The completed Truthtrance lost its original Revival answer.');
const emptyProgress = {total:0, normal:0, elite:0, leaders:[]};
if (binding.kind === 'revival' && q.kind === 'revival')
requireRule(binding.value.counter === q.counter && binding.value.minimum === q.minimum &&
binding.value.progress === 0 && !binding.value.fulfilled && !binding.value.released &&
g.revivalPromiseSequence?.at(-1) === binding.value.event,
'The completed Truthtrance Revival differs from its original question.');
else if (binding.kind === 'leaderRevival' && q.kind === 'leaderRevival')
requireRule(binding.value.leader === q.leader && !binding.value.fulfilled && !binding.value.released &&
g.revivalPromiseSequence?.at(-1) === binding.value.event,
'The completed Truthtrance leader Revival differs from its original question.');
else if (binding.kind === 'revivalExpression' && q.kind === 'revivalExpression')
requireRule(JSON.stringify(binding.value.claim) === JSON.stringify(q.claim) &&
JSON.stringify(binding.value.progress) === JSON.stringify(emptyProgress) &&
!binding.value.fulfilled && !binding.value.released && g.revivalPromiseSequence?.at(-1) === binding.value.event,
'The completed Truthtrance Revival expression differs from its original question.');
else if (binding.kind === 'mixedRevival' && q.kind === 'mixedRevival') {
const residual = g.revivalExpressionPromises?.at(-1);
requireRule(JSON.stringify(binding.value.mixed) === JSON.stringify(q.mixed) &&
(binding.value.binding ? !!residual && residual.event === binding.value.event &&
residual.player === q.target && residual.asker === record.asker && residual.answer === binding.value.answer &&
residual.turn === g.turn && JSON.stringify(residual.progress) === JSON.stringify(emptyProgress) &&
!residual.fulfilled && !residual.released && g.revivalPromiseSequence?.at(-1) === binding.value.event :
!g.revivalExpressionPromises?.some(item => item.event === binding.value.event)),
'The completed mixed Revival answer no longer matches its return binding.');
}
} else requireRule(!c.revivalBinding, 'This Truthtrance answer has no Revival binding.');
if (expectedShipment || expectedBattle || expectedMovement || expectedTraitor) {
const actual = expectedTraitor ? g.battle?.traitorDecisionPromises : expectedMovement ? g.movementPromises : expectedShipment
? g.shipmentPromises
@@ -9350,6 +9412,9 @@ function semutaOfferSupported(
if (continuation.kind === 'truthtranceDiscard' &&
['bribe', 'bribeExpression', 'mixedBribe'].includes(continuation.record.question.kind) &&
(!continuation.bribeBinding || !bareBasicSemutaProfile(g) || !bribePromiseModeSupported(g))) return false;
if (continuation.kind === 'truthtranceDiscard' &&
['revival', 'leaderRevival', 'revivalExpression', 'mixedRevival'].includes(continuation.record.question.kind) &&
(!continuation.revivalBinding || !bareBasicSemutaProfile(g) || !revivalPromiseModeSupported(g))) return false;
const richeseGiftDiscard = continuation.kind === 'karamaRicheseGiftDiscard';
const richeseDiscard = richeseGiftDiscard || continuation.kind === 'karamaRicheseAuctionDiscard';
const auctionAdvantageDiscard = continuation.kind === 'karamaAuctionAdvantageDiscard';
@@ -19158,6 +19223,9 @@ function kullSignature(g: Game, frame: KullFrame): string {
...(frame.bribeReceipt ? {bribeReceipt:frame.bribeReceipt,bribePromises:source.bribePromises,
bribeExpressionPromises:source.bribeExpressionPromises,bribePromiseSequence:source.bribePromiseSequence,
mixedBribeQuestions:source.mixedBribeQuestions} : {}),
...(frame.revivalReceipt ? {revivalReceipt:frame.revivalReceipt,revivalPromises:source.revivalPromises,
leaderRevivalPromises:source.leaderRevivalPromises,revivalExpressionPromises:source.revivalExpressionPromises,
mixedRevivalQuestions:source.mixedRevivalQuestions,revivalPromiseSequence:source.revivalPromiseSequence} : {}),
});
}
/** Only the verified pre-source printed profile may bind its old held cost. */
@@ -19205,6 +19273,10 @@ function kullIntegrity(g: Game): void {
requireRule(frame.bribeReceipt === undefined || frame.bribeReceipt === true && !g.advanced &&
bribePromiseModeSupported(g) && !!(g.bribePromises?.length || g.bribeExpressionPromises?.length || g.mixedBribeQuestions?.length),
'The Kull attempt has lost its original Basic bribe-answer ledger.');
requireRule(frame.revivalReceipt === undefined || frame.revivalReceipt === true && g.semutaPreview === true &&
revivalPromiseModeSupported(g) && !!(g.revivalPromises?.length || g.leaderRevivalPromises?.length ||
g.revivalExpressionPromises?.length || g.mixedRevivalQuestions?.length),
'The Kull attempt has lost its original Basic Revival-answer ledger.');
validateKullAttemptStamp(g, frame, physicalTreacheryCards(g));
requireRule(Number.isSafeInteger(g.kullSequence) && g.kullSequence! > 0 &&
frame.event === JSON.stringify(['kull', g.turn, g.phase, g.kullSequence, frame.player, frame.owner]),
@@ -19289,6 +19361,9 @@ function beginKullAttempt(g: Game, p: Player, card: Card, intent: KullFrame['int
...(g.advanced ? { purpose: kullIntentPurpose(g, intent) } : {}),
...(!g.advanced && (g.bribePromises?.length || g.bribeExpressionPromises?.length || g.mixedBribeQuestions?.length)
? { bribeReceipt: true as const } : {}),
...(g.semutaPreview && revivalPromiseModeSupported(g) && (g.revivalPromises?.length ||
g.leaderRevivalPromises?.length || g.revivalExpressionPromises?.length || g.mixedRevivalQuestions?.length)
? { revivalReceipt: true as const } : {}),
worthless: structuredClone(g.pendingChoamWorthless ?? null), signature: '',
};
// Payment dispatch removed this control before entering its native branch.