Resume Richese auctions after Distrans Semuta reaction
Verify and publish checkpoint container / container (push) Successful in 11m24s

This commit is contained in:
Quintine committed 2026-10-29 10:38:59 +11:00
1 parent ccc4c7bb40
commit f8078ed5e2
9 files changed
+245 -56

No files matched your search

+28 -4
View File
@@ -1734,6 +1734,8 @@ export type Game = {
}
| {
kind: 'distransDiscard';
/** Present on new special-auction reactions; older saves omit it. */
auctionBinding?: string;
owner: string;
recipient: string;
transferred: string;
@@ -5485,9 +5487,17 @@ function distransBlock(g: Game, owner: Player, card: Card): string | null {
special.acted.length > 0 || Object.keys(special.sealed).length > 0));
if (started && (g.response || g.decision || g.pendingKarama || g.phaseOpening || g.pendingTreacheryDiscard))
return 'Finish the pending auction interaction before playing Distrans.';
// A special-lot Semuta continuation still needs an auction-bound resume.
if (started && special && (g.semutaPreview || g.semutaAdvancedOrdinaryPreview || g.semutaRecruitsPreview))
return 'Distrans between special-auction bids is unavailable with the Semuta preview.';
if (started && special && (g.semutaPreview || g.semutaAdvancedOrdinaryPreview || g.semutaRecruitsPreview)) {
const auctionBinding = distransSpecialAuctionBinding(g);
const continuation: DistransDiscardContinuation = {
kind: 'distransDiscard', owner: owner.id, recipient: owner.id, transferred: '', card: card.id,
auctionBinding, resume: { response: g.response, decision: g.decision,
pendingKarama: g.pendingKarama, phaseOpening: g.phaseOpening }, stateSignature: '',
};
if (!auctionBinding || !semutaOfferSupported(g, continuation,
[{ card, discardedBy: owner.id, publicFace: true }]))
return 'Finish the special auction before playing Distrans in this Semuta interaction.';
}
return distransAuctionBlock(g);
}
function validateDistrans(
@@ -6443,6 +6453,14 @@ type DistransDiscardContinuation = Extract<
NonNullable<Game['pendingTreacheryDiscard']>['continuation'],
{ kind: 'distransDiscard' }
>;
/** Preserve the physical special lot and its committed funds through Semuta. */
function distransSpecialAuctionBinding(g: Game): string | undefined {
if (g.phase !== 3 || !g.richeseAuction || g.auction || g.ixAuction || g.currentAuctionSale ||
distransAuctionBlock(g)) return undefined;
return JSON.stringify({ turn: g.turn, phase: g.phase, active: g.active, order: g.order,
auction: g.richeseAuction, bidding: g.richeseBidding, funding: g.richeseFunding,
aid: g.aid, players: g.players.map(p => ({ id: p.id, spice: p.spice, ally: p.ally })) });
}
function distransDiscardSignature(g: Game, c: DistransDiscardContinuation) {
const owner = getPlayer(g, c.owner), recipient = getPlayer(g, c.recipient);
return JSON.stringify({
@@ -6451,6 +6469,7 @@ function distransDiscardSignature(g: Game, c: DistransDiscardContinuation) {
turn: g.turn, phase: g.phase, active: g.active,
ownerHand: owner.hand.map(card => card.id),
recipientHand: recipient.hand.map(card => card.id),
...(c.auctionBinding === undefined ? {} : { auctionBinding: c.auctionBinding }),
});
}
type SaphoTruthOrderDiscardContinuation = Extract<
@@ -8338,6 +8357,7 @@ function treacheryDiscardIntegrity(g: Game) {
!g.discard.some(card => card.id === c.transferred) &&
!c.resume.response && !c.resume.decision &&
!c.resume.pendingKarama && !c.resume.phaseOpening &&
(c.auctionBinding === undefined || c.auctionBinding === distransSpecialAuctionBinding(g)) &&
c.stateSignature === distransDiscardSignature(g, c),
'The completed private Distrans transfer no longer matches its fresh discard.',
);
@@ -9703,6 +9723,9 @@ function semutaOfferSupported(
!!continuation.richeseLotEvent && continuation.richeseLotEvent === g.richeseAuction?.event;
const richeseBoxDiscard = supportedRicheseTruthLot(g) && continuation.kind === 'nullentropyDiscard' &&
continuation.auctionBinding?.kind === 'richese';
const richeseDistransDiscard = continuation.kind === 'distransDiscard' &&
continuation.auctionBinding !== undefined &&
continuation.auctionBinding === distransSpecialAuctionBinding(g);
if (continuation.kind === 'truthtranceDiscard' &&
['bribe', 'bribeExpression', 'mixedBribe'].includes(continuation.record.question.kind) &&
(!continuation.bribeBinding || !bareBasicSemutaProfile(g) || !bribePromiseModeSupported(g))) return false;
@@ -10129,7 +10152,7 @@ function semutaOfferSupported(
(advisorDiscard || auctionCostDiscard || bonusDiscard || auctionAdvantageDiscard || richeseGiftDiscard || bgDiscard || continuation.kind === 'specialKaramaDiscard' || continuation.kind === 'saphoAuctionDiscard' ||
continuation.kind === 'ixAllyCard' ||
(continuation.kind === 'karamaIncomeDiscard' &&
continuation.use.response.kind === 'emperorIncome') || richeseTruthDiscard || richeseBoxDiscard || !g.richeseAuction) &&
continuation.use.response.kind === 'emperorIncome') || richeseTruthDiscard || richeseBoxDiscard || richeseDistransDiscard || !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 &&
@@ -44258,6 +44281,7 @@ function applyActionInner(
g.semutaRecruitsPreview && basicIndependentRecruitsSemutaProfile(g)) && !g.pendingTreacheryDiscard) {
const continuation: DistransDiscardContinuation = {
kind: 'distransDiscard', owner: p.id, recipient: recipient.id,
...(distransSpecialAuctionBinding(g) === undefined ? {} : { auctionBinding: distransSpecialAuctionBinding(g) }),
transferred: result.transferred.id, card: result.discarded.id,
resume: { response: g.response, decision: g.decision,
pendingKarama: g.pendingKarama, phaseOpening: g.phaseOpening },