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

This commit is contained in:
Quintine committed 2026-10-29 01:17:58 +11:00
1 parent 4dfdd500e0
commit d6ddb7cb5c
15 files changed
+476 -19

No files matched your search

+69 -1
View File
@@ -1616,6 +1616,8 @@ export type Game = {
bribeReceipt?: true;
/** New Kull/Semuta attempts retain the original Revival answer ledger. */
revivalReceipt?: true;
/** New Kull/Semuta attempts retain the original alliance answer ledger. */
allianceReceipt?: true;
stage: 'offer' | 'counter';
selection?: { source: 'printed' | 'nexus'; card: string; nexusEvent?: string };
intent: { kind: 'ordinary'; use: KaramaUse } | { kind: 'special'; use: SpecialKaramaIntent };
@@ -1934,6 +1936,10 @@ export type Game = {
| { kind: 'leaderRevival'; index: number; value: LeaderRevivalPromise }
| { kind: 'revivalExpression'; index: number; value: RevivalExpressionPromise }
| { kind: 'mixedRevival'; index: number; value: NonNullable<Game['mixedRevivalQuestions']>[number] };
allianceBinding?:
| { kind: 'alliance'; index: number; value: AlliancePromise }
| { kind: 'allianceExpression'; index: number; value: AllianceExpressionPromise }
| { kind: 'mixedAlliance'; index: number; value: NonNullable<Game['mixedAllianceQuestions']>[number] };
parentSignature: string;
}
| {
@@ -7587,6 +7593,10 @@ function truthtranceDiscardSignature(g: Game, next: TruthDiscardContinuation) {
revivalPromises: g.revivalPromises, leaderRevivalPromises: g.leaderRevivalPromises,
revivalExpressionPromises: g.revivalExpressionPromises, mixedRevivalQuestions: g.mixedRevivalQuestions,
revivalPromiseSequence: g.revivalPromiseSequence } : {}),
...(next.allianceBinding ? { allianceBinding: next.allianceBinding,
alliancePromises: g.alliancePromises, allianceExpressionPromises: g.allianceExpressionPromises,
mixedAllianceQuestions: g.mixedAllianceQuestions, alliancePromiseSequence: g.alliancePromiseSequence,
nexus: g.nexus, spiceSequence: g.spiceSequence, spiceDiscard: g.spiceDiscard } : {}),
controls: nullentropyParentSignature(
g,
next.resume,
@@ -7721,6 +7731,15 @@ function finishTruthtranceAnswer(g: Game) {
else if (g.semutaPreview && record.question.kind === 'mixedRevival') continuation.revivalBinding = {
kind: 'mixedRevival', index: g.mixedRevivalQuestions!.length - 1, value: structuredClone(g.mixedRevivalQuestions!.at(-1)!),
};
if (g.semutaPreview && record.question.kind === 'alliance') continuation.allianceBinding = {
kind: 'alliance', index: g.alliancePromises!.length - 1, value: structuredClone(g.alliancePromises!.at(-1)!),
};
else if (g.semutaPreview && record.question.kind === 'allianceExpression') continuation.allianceBinding = {
kind: 'allianceExpression', index: g.allianceExpressionPromises!.length - 1, value: structuredClone(g.allianceExpressionPromises!.at(-1)!),
};
else if (g.semutaPreview && record.question.kind === 'mixedAlliance') continuation.allianceBinding = {
kind: 'mixedAlliance', index: g.mixedAllianceQuestions!.length - 1, value: structuredClone(g.mixedAllianceQuestions!.at(-1)!),
};
continuation.parentSignature = truthtranceDiscardSignature(g, continuation);
stageTreacheryDiscard(
g,
@@ -8782,6 +8801,40 @@ function treacheryDiscardIntegrity(g: Game) {
'The completed mixed Revival answer no longer matches its return binding.');
}
} else requireRule(!c.revivalBinding, 'This Truthtrance answer has no Revival binding.');
const expectedAlliance = g.semutaPreview &&
(q.kind === 'alliance' || q.kind === 'allianceExpression' || q.kind === 'mixedAlliance');
if (expectedAlliance) {
const binding = c.allianceBinding;
const actual = binding?.kind === 'alliance' ? g.alliancePromises :
binding?.kind === 'allianceExpression' ? g.allianceExpressionPromises : g.mixedAllianceQuestions;
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 alliance answer.');
const opportunity = allianceOpportunity(context);
requireRule(opportunity, 'The completed Truthtrance lost its original Nexus.');
if (binding.kind === 'alliance' && q.kind === 'alliance')
requireRule(binding.value.partner === q.partner && binding.value.source === opportunity.source &&
!binding.value.fulfilled && !binding.value.released && g.alliancePromiseSequence?.at(-1) === binding.value.event,
'The completed Truthtrance alliance differs from its original question.');
else if (binding.kind === 'allianceExpression' && q.kind === 'allianceExpression')
requireRule(JSON.stringify(binding.value.claim) === JSON.stringify(q.claim) &&
binding.value.source === opportunity.source && !binding.value.fulfilled && !binding.value.released &&
g.alliancePromiseSequence?.at(-1) === binding.value.event,
'The completed Truthtrance alliance expression differs from its original question.');
else if (binding.kind === 'mixedAlliance' && q.kind === 'mixedAlliance') {
const residual = g.allianceExpressionPromises?.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 && residual.source === opportunity.source && !residual.fulfilled && !residual.released &&
g.alliancePromiseSequence?.at(-1) === binding.value.event :
!g.allianceExpressionPromises?.some(item => item.event === binding.value.event)),
'The completed mixed alliance answer no longer matches its Nexus binding.');
}
} else requireRule(!c.allianceBinding, 'This Truthtrance answer has no alliance binding.');
if (expectedShipment || expectedBattle || expectedMovement || expectedTraitor) {
const actual = expectedTraitor ? g.battle?.traitorDecisionPromises : expectedMovement ? g.movementPromises : expectedShipment
? g.shipmentPromises
@@ -9415,6 +9468,9 @@ function semutaOfferSupported(
if (continuation.kind === 'truthtranceDiscard' &&
['revival', 'leaderRevival', 'revivalExpression', 'mixedRevival'].includes(continuation.record.question.kind) &&
(!continuation.revivalBinding || !bareBasicSemutaProfile(g) || !revivalPromiseModeSupported(g))) return false;
if (continuation.kind === 'truthtranceDiscard' &&
['alliance', 'allianceExpression', 'mixedAlliance'].includes(continuation.record.question.kind) &&
(!continuation.allianceBinding || !bareBasicSemutaProfile(g) || !alliancePromiseModeSupported(g))) return false;
const richeseGiftDiscard = continuation.kind === 'karamaRicheseGiftDiscard';
const richeseDiscard = richeseGiftDiscard || continuation.kind === 'karamaRicheseAuctionDiscard';
const auctionAdvantageDiscard = continuation.kind === 'karamaAuctionAdvantageDiscard';
@@ -9590,12 +9646,14 @@ function semutaOfferSupported(
g.pendingChoamMove || g.pendingIxMove ||
g.pendingFremenMove || g.ornithopter || g.summonedWorm ||
g.wormRides.length > 0)) return false;
const allianceNexusDiscard = continuation.kind === 'truthtranceDiscard' && !!continuation.allianceBinding &&
g.phase === 1 && g.nexus && bareBasicSemutaProfile(g) && alliancePromiseModeSupported(g);
if ((continuation.kind === 'distransDiscard' ||
continuation.kind === 'truthtranceDiscard' ||
continuation.kind === 'saphoMovementDiscard' ||
(continuation.kind === 'ornithopterDiscard' && continuation.source === 'move')) &&
(g.pendingChoamMove || g.pendingIxMove || g.pendingFremenMove ||
g.ornithopter || g.summonedWorm || g.wormRides.length > 0)) return false;
g.ornithopter || g.summonedWorm || !allianceNexusDiscard && g.wormRides.length > 0)) return false;
if (continuation.kind === 'saphoAggressorDiscard' &&
(!g.battle || g.pendingAuditor || g.pendingCapture || g.pendingTech ||
g.pendingFaceDance || g.pendingChoamBattleIncome ||
@@ -19226,6 +19284,9 @@ function kullSignature(g: Game, frame: KullFrame): string {
...(frame.revivalReceipt ? {revivalReceipt:frame.revivalReceipt,revivalPromises:source.revivalPromises,
leaderRevivalPromises:source.leaderRevivalPromises,revivalExpressionPromises:source.revivalExpressionPromises,
mixedRevivalQuestions:source.mixedRevivalQuestions,revivalPromiseSequence:source.revivalPromiseSequence} : {}),
...(frame.allianceReceipt ? {allianceReceipt:frame.allianceReceipt,alliancePromises:source.alliancePromises,
allianceExpressionPromises:source.allianceExpressionPromises,mixedAllianceQuestions:source.mixedAllianceQuestions,
alliancePromiseSequence:source.alliancePromiseSequence} : {}),
});
}
/** Only the verified pre-source printed profile may bind its old held cost. */
@@ -19277,6 +19338,10 @@ function kullIntegrity(g: Game): void {
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.');
requireRule(frame.allianceReceipt === undefined || frame.allianceReceipt === true && g.semutaPreview === true &&
alliancePromiseModeSupported(g) && !!(g.alliancePromises?.length || g.allianceExpressionPromises?.length ||
g.mixedAllianceQuestions?.length),
'The Kull attempt has lost its original Basic alliance-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]),
@@ -19364,6 +19429,9 @@ function beginKullAttempt(g: Game, p: Player, card: Card, intent: KullFrame['int
...(g.semutaPreview && revivalPromiseModeSupported(g) && (g.revivalPromises?.length ||
g.leaderRevivalPromises?.length || g.revivalExpressionPromises?.length || g.mixedRevivalQuestions?.length)
? { revivalReceipt: true as const } : {}),
...(g.semutaPreview && alliancePromiseModeSupported(g) && (g.alliancePromises?.length ||
g.allianceExpressionPromises?.length || g.mixedAllianceQuestions?.length)
? { allianceReceipt: true as const } : {}),
worthless: structuredClone(g.pendingChoamWorthless ?? null), signature: '',
};
// Payment dispatch removed this control before entering its native branch.