Order simultaneous Truthtrances during battle
Verify and publish checkpoint container / container (push) Successful in 11m19s

This commit is contained in:
Quintine committed 2026-10-29 02:27:09 +11:00
1 parent 50703664cf
commit cdc47ed2e1
7 files changed
+201 -9

No files matched your search

+17 -3
View File
@@ -1712,6 +1712,8 @@ export type Game = {
truth: TruthWindow;
/** Present only on new supported Richese cache continuations. */
richeseLotEvent?: string;
/** New battle continuations bind their original battle event and state. */
battleEvent?: string;
resume: NonNullable<Game['pendingNullentropy']>['resume'];
stateSignature: string;
}
@@ -6351,6 +6353,7 @@ function saphoTruthOrderDiscardSignature(g: Game, c: SaphoTruthOrderDiscardConti
// Older clean-parent receipts omit these fields; only an actual ordinary
// auction adds a binding to its original lot and pledged funding.
...(g.auction ? {auction:g.auction,aid:g.aid} : {}),
...(c.battleEvent ? {battleEvent:c.battleEvent,battle:g.battle} : {}),
...(c.richeseLotEvent ? {richeseLotEvent:c.richeseLotEvent,richeseAuction:g.richeseAuction,
richeseBidding:g.richeseBidding,richeseFunding:g.richeseFunding,richeseCache:g.richeseCache,aid:g.aid} : {})});
}
@@ -8182,6 +8185,8 @@ function treacheryDiscardIntegrity(g: Game) {
richeseCardDefinition(entry.card)?.card.effect === 'juiceOfSapho' &&
!c.resume.response && !c.resume.decision && !c.resume.pendingKarama && !c.resume.phaseOpening &&
(!c.richeseLotEvent || supportedRicheseTruthLot(g) && g.richeseAuction?.event === c.richeseLotEvent) &&
(!c.battleEvent || g.phase === 6 && g.battle?.event === c.battleEvent &&
!g.battle.revealed && !g.battle.preparation) &&
truth && truth.question === null && (truth.stage === 'order' || truth.stage === 'ask') &&
truth.queue.length > 0 && new Set(truth.queue.map(row => row.card)).size === truth.queue.length &&
truth.queue.every(row => queuedTruthCardMatches(g.players.find(player => player.id === row.player)?.hand ?? [],row)) &&
@@ -9848,6 +9853,9 @@ function semutaOfferSupported(
(continuation.kind === 'karamaIncomeDiscard' &&
continuation.use.response.kind === 'emperorIncome') || richeseTruthDiscard || richeseBoxDiscard || !g.richeseAuction) &&
(richeseGiftDiscard || combatDiscard || alliedTraitorDiscard || occupyDiscard || continuation.kind === 'saphoAggressorDiscard' ||
(continuation.kind === 'saphoTruthOrderDiscard' && !!continuation.battleEvent &&
g.phase === 6 && g.battle?.event === continuation.battleEvent && !g.battle.revealed && !g.battle.preparation &&
bareBasicSemutaProfile(g)) ||
bgDiscard ||
continuation.kind === 'specialKaramaDiscard' ||
continuation.kind === 'residualPoisonDiscard' ||
@@ -28284,6 +28292,11 @@ function saphoExtortionDecision(g: Game) {
decision?.kind === 'moritaniExtortion' && decision.player === state.queue[state.cursor] &&
decision.event === extortionEvent(state);
}
function saphoBattleCardReserved(g: Game, player: string, card: string): boolean {
const battle = g.battle, plan = battle?.plans[player];
return !!battle && (!!plan && [plan.weapon, plan.defense, plan.leader].includes(card) ||
committedPlanElements(battle, player).some(element => element.value === card));
}
/** Quote the exact public parent/discard continuation on a detached copy.
* This does not inspect whether anyone holds Semuta; it prevents offering a
* Sapho action whose mandatory supported reaction cannot be staged. */
@@ -28302,6 +28315,7 @@ function saphoTruthOrderSemutaSupported(g: Game,p: Player,card: string,mode: 'fi
const c: SaphoTruthOrderDiscardContinuation = {kind:'saphoTruthOrderDiscard',player:p.id,card:used.id,
event,revision,mode,truth:structuredClone(w),
...(supportedRicheseTruthLot(trial) ? {richeseLotEvent:trial.richeseAuction!.event} : {}),
...(trial.battle?.event ? {battleEvent:trial.battle.event} : {}),
resume:{response:trial.response,decision:trial.decision,
pendingKarama:trial.pendingKarama,phaseOpening:trial.phaseOpening},stateSignature:''};
trial.truthtrance = null; trial.response = null; trial.decision = null;
@@ -28315,7 +28329,7 @@ function saphoTruthOrderChoices(g: Game,p: Player): {card:string;mode:'first'|'l
printedRolePlayBlock(g,p.id,'juiceOfSapho')) return [];
const card = p.hand.find(row => row.id === 'richese-juice-of-sapho' && row.effect === 'juiceOfSapho');
if (!card || !richeseCardDefinition(card) || p.hand.filter(row => row.id === card.id).length !== 1 ||
transferCardBlock(g,p,card) || giftReserved(g,p.id,card.id)) return [];
transferCardBlock(g,p,card) || giftReserved(g,p.id,card.id) || saphoBattleCardReserved(g,p.id,card.id)) return [];
try { return quoteSaphoTruthOrder(w.order,w.queue,p.id).modes
.filter(mode => saphoTruthOrderSemutaSupported(g,p,card.id,mode)).map(mode => ({card:card.id,mode})); }
catch (error) { if (error instanceof SaphoTruthOrderError) return []; throw error; }
@@ -28341,6 +28355,7 @@ function playSaphoTruthOrder(g: Game,p: Player,action: Action) {
event:action.event as string,revision:action.revision as number,mode:action.mode as 'first'|'last',
truth:structuredClone(w),
...(supportedRicheseTruthLot(g) ? {richeseLotEvent:g.richeseAuction!.event} : {}),
...(g.battle?.event ? {battleEvent:g.battle.event} : {}),
resume:{response:g.response,decision:g.decision,pendingKarama:g.pendingKarama,phaseOpening:g.phaseOpening},
stateSignature:''};
g.truthtrance = null; g.response = null; g.decision = null; g.pendingKarama = null; g.phaseOpening = null;
@@ -28448,8 +28463,7 @@ function saphoOptions(g: Game, p: Player): SaphoOption[] {
p.hand.filter((c) => c.id === card.id).length !== 1 ||
transferCardBlock(g, p, card) ||
giftReserved(g, p.id, card.id) ||
(g.battle?.plans[p.id] && [g.battle.plans[p.id].weapon, g.battle.plans[p.id].defense, g.battle.plans[p.id].leader].includes(card.id)) ||
(g.battle && committedPlanElements(g.battle, p.id).some(element => element.value === card.id))
saphoBattleCardReserved(g,p.id,card.id)
)
return [];
const options: SaphoOption[] = [];