Order simultaneous Truthtrances during battle
Verify and publish checkpoint container / container (push) Successful in 11m19s
Verify and publish checkpoint container / container (push) Successful in 11m19s
This commit is contained in:
1 parent
50703664cf
commit
cdc47ed2e1
7 files changed
+201
-9
No files matched your search
+17
-3
@@ -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[] = [];
|
||||
|
||||
@@ -512,6 +512,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'During a clean native Extortion payer decision, use Sapho to take your optional choice first/last among unanswered non-Moritani players. Earlier declines, collected bank5 and the one-payment3 limit remain. The next payer event changes even when that player stays next. You may decline instead of paying.',
|
||||
'During ordered Advanced Charity, take your remaining turn first or last, then discard Sapho. An accepted current claim provisionally protects that actor while another holder reorders only later unstarted turns. At a clean boundary, the holder may pause its own claimed turn and resume last without repeating the claim or payment. Completed turns stay closed and physical First Player order is unchanged. Basic and older concurrent phases have no ordered scope.',
|
||||
'During ordered Advanced Revival, take your unfinished opportunity first or last, then discard Sapho. If another player has started, provisional queued timing lets that player finish unchanged. At a clean boundary, you may instead pause your own started turn and resume last, retaining used allowances. Pending requests must finish first. Completed turns and already-held positions cannot be repeated. Basic and older concurrent Revival phases have no ordered scope.',
|
||||
'When two or more players declare simultaneous Truthtrances in a quiet unrevealed battle, their public question-order window can use Sapho to move the holder’s group first or last. The current battle and committed plans remain in place. In the supported Basic Semuta preview, the discard and any exchange finish before the reordered questions resume.',
|
||||
'Finish pending shipment, movement and card preparations and decisions before changing order. The exact Extortion payer window is a supported exception for its own order only. Your hand panel lists available choices. Reserved cards and positions you already hold cannot be selected.',
|
||||
'Completed bids and turns stay completed. Storm order, committed funding and movement counters do not reset. Refreshing preserves the same remaining opportunities and any declared last position.',
|
||||
'In the explicitly opted-in Semuta preview, clean Once Around, movement and ordered Charity/Revival discards pause after their queues reorder; a clean between-battles chooser discard pauses after changing battle priority but before the next chooser acts; an aggressor discard pauses after changing ordinary tie priority. Charity/Revival retain their current participant, claims or requests and allowances through Semuta claim/pass. Fresh late continuations also bind revealed choices and the public final revision; old continuation bytes remain unchanged. Every seat receives the same neutral offer. Claim or decline moves only the used card or leaves it discarded; each committed queue or unresolved battle continues once. Direct combined late claim/capture proof remains open.',
|
||||
|
||||
+4
-1
@@ -1389,7 +1389,10 @@ export function resolveTruthAction(
|
||||
!g.richeseAuction.outcome && !g.currentAuctionSale && !g.auction && !g.ixAuction;
|
||||
const clean = g.status === 'playing' && g.expansions.includes('choam') && !g.response && !g.decision && !g.phaseOpening &&
|
||||
!g.pendingKarama && !g.pendingTreacheryDiscard && !g.pendingNullentropy && (!g.auction || ordinaryLot) &&
|
||||
(!g.richeseAuction || richeseCacheLot) && !g.battle && !g.pendingRevival && !g.pendingShipment &&
|
||||
(!g.richeseAuction || richeseCacheLot) &&
|
||||
(!g.battle || g.phase === 6 && !!g.battle.event && !g.battle.revealed && !g.battle.preparation &&
|
||||
!g.pendingWinnerDiscards && !g.pendingCapture && !g.pendingTech && !g.pendingFaceDance && !g.pendingIxSubstitution) &&
|
||||
!g.pendingRevival && !g.pendingShipment &&
|
||||
!g.pendingAmbassador && !g.pendingTerrorEntry && !g.pendingChoamMove &&
|
||||
!g.pendingIxMove && !g.pendingFremenMove && !g.pendingExchange && !g.pendingRicheseGift &&
|
||||
!g.karamaShipping && !g.ornithopter && !g.summonedWorm && g.wormRides.length === 0 &&
|
||||
|
||||
Reference in new issue
Block a user