Continue Kull across paid sale responses
Verify and publish checkpoint container / container (push) Successful in 10m57s
Verify and publish checkpoint container / container (push) Successful in 10m57s
This commit is contained in:
1 parent
3d2470b7f8
commit
e7520e8679
9 files changed
+487
-18
No files matched your search
+35
-15
@@ -1620,7 +1620,7 @@ export type Game = {
|
||||
allianceReceipt?: true;
|
||||
/** New Basic Kull attempts retain whole-answer order and progress. */
|
||||
turnActionReceipt?: true;
|
||||
/** A Basic bonus cancellation retains its already-paid auction parent. */
|
||||
/** A Basic sale cancellation retains its already-paid auction parent. */
|
||||
auctionReceipt?: true;
|
||||
stage: 'offer' | 'counter';
|
||||
selection?: { source: 'printed' | 'nexus'; card: string; nexusEvent?: string };
|
||||
@@ -6940,7 +6940,7 @@ function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKul
|
||||
!frame.resume.decision && !frame.resume.pendingKarama && !frame.resume.phaseOpening;
|
||||
let revivalSupported = false;
|
||||
let guildSupported = false;
|
||||
let bonusSupported = false;
|
||||
let saleSupported = false;
|
||||
if (nativeResponse && frame && use?.kind === 'cancel') {
|
||||
// Success has just installed this very Kull restriction. Validate the
|
||||
// suspended source as it stood before that restriction, without changing
|
||||
@@ -6952,12 +6952,19 @@ function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKul
|
||||
revivalSupported = !!g.pendingRevival && semutaRevivalKaramaResponse(source, use.response, frame.owner);
|
||||
guildSupported = (!!g.pendingShipment || !!g.pendingGuildTransport) &&
|
||||
semutaGuildShipmentKaramaResponse(source, use.response);
|
||||
bonusSupported = frame.auctionReceipt === true && use.response.kind === 'harkonnenBonus' &&
|
||||
!!g.currentAuctionSale && (g.currentAuctionSale.origin === 'normal'
|
||||
const paidSale = frame.auctionReceipt === true && !!g.currentAuctionSale && (g.currentAuctionSale.origin === 'normal'
|
||||
? !!g.auction && !g.richeseAuction
|
||||
: g.currentAuctionSale.origin === 'cache' && !!g.richeseAuction &&
|
||||
g.richeseAuction.source === 'cache' && !g.auction) &&
|
||||
semutaBonusNoFieldTraitorResponse(source, use.response, frame.owner);
|
||||
g.richeseAuction.source === 'cache' && !g.auction);
|
||||
if (paidSale && use.response.kind === 'harkonnenBonus')
|
||||
saleSupported = semutaBonusNoFieldTraitorResponse(source, use.response, frame.owner);
|
||||
else if (paidSale && use.response.kind === 'ixAllyCard')
|
||||
saleSupported = semutaMarketGrantKaramaResponse(source, use.response, frame.owner);
|
||||
else if (paidSale && use.response.kind === 'emperorIncome' && !use.response.source &&
|
||||
semutaIncomeKaramaResponse(source, use.response)) {
|
||||
validateKaramaUse(source, getPlayer(source, frame.owner), use);
|
||||
saleSupported = true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (!(error instanceof RuleError)) throw error;
|
||||
}
|
||||
@@ -6971,13 +6978,14 @@ function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKul
|
||||
JSON.stringify(use.response) === JSON.stringify(frame.resume.response) &&
|
||||
!frame.resume.decision && !frame.resume.pendingKarama && !frame.resume.phaseOpening;
|
||||
return battleSupported && !g.truthtrance && !g.pendingRicheseGift && !g.pendingNullentropy &&
|
||||
!g.pendingAmbassador && !g.pendingIxAlly && (!g.pendingRevival || revivalSupported) &&
|
||||
!g.pendingAmbassador && (!g.pendingIxAlly || saleSupported && use?.kind === 'cancel' &&
|
||||
use.response.kind === 'ixAllyCard') && (!g.pendingRevival || revivalSupported) &&
|
||||
(!g.pendingShipment || guildSupported) && (!g.pendingGuildTransport || guildSupported) &&
|
||||
!g.pendingHomeworldShipment && !g.pendingTerrorEntry &&
|
||||
!g.pendingEcazPlacement && !g.pendingMoritaniPlacement &&
|
||||
!g.pendingArrivalOverlap && !g.pendingArrivalReaction?.length && !g.pendingExchange &&
|
||||
!g.pendingRichesePurchaseIncome && !g.pendingChoamMarketGhola && (!g.currentAuctionSale || bonusSupported) &&
|
||||
!g.choamMarket && (!g.richeseAuction || bonusSupported) && !g.pendingFremenMove && !g.pendingIxMove &&
|
||||
!g.pendingRichesePurchaseIncome && !g.pendingChoamMarketGhola && (!g.currentAuctionSale || saleSupported) &&
|
||||
!g.choamMarket && (!g.richeseAuction || saleSupported) && !g.pendingFremenMove && !g.pendingIxMove &&
|
||||
!g.pendingMobileMove && !g.pendingChoamMove && !g.ornithopter && !g.ecologicalStorm &&
|
||||
!g.summonedWorm && !g.wormRides.length;
|
||||
}
|
||||
@@ -7951,7 +7959,10 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
(!g.pendingNullentropy || continuation?.kind === 'karamaRicheseGiftDiscard') &&
|
||||
(!g.pendingAmbassador || box || truth || ambassadorTerror || ambassadorCost || continuation?.kind === 'karamaRicheseGiftDiscard') &&
|
||||
(!g.pendingIxAlly || box || truth || continuation?.kind === 'karamaRicheseGiftDiscard' ||
|
||||
continuation?.kind === 'karamaMarketDiscard' && continuation.use.response.kind === 'ixAllyCard'),
|
||||
continuation?.kind === 'karamaMarketDiscard' && continuation.use.response.kind === 'ixAllyCard' ||
|
||||
(continuation?.kind === 'kullSuccessDiscard' || continuation?.kind === 'kullCounterDiscard') &&
|
||||
continuation.frame.auctionReceipt === true && continuation.frame.intent.kind === 'ordinary' &&
|
||||
continuation.frame.intent.use.kind === 'cancel' && continuation.frame.intent.use.response.kind === 'ixAllyCard'),
|
||||
'The saved discard does not match its current continuation.',
|
||||
);
|
||||
const ids = new Set<string>();
|
||||
@@ -19231,8 +19242,17 @@ function canonicalKullCard(card: Card): boolean {
|
||||
return !!expected && card.name === expected.name && card.kind === expected.kind &&
|
||||
card.effect === expected.effect;
|
||||
}
|
||||
/** A settled normal sale is not an outstanding bid. Validate its native
|
||||
* response and physical auction receipt before exempting it from funding. */
|
||||
function kullPaidAuctionSale(g: Game, player: Player): boolean {
|
||||
const sale = g.currentAuctionSale, auction = g.auction, response = g.response;
|
||||
if (!sale || sale.origin !== 'normal' || !auction || auction.bidder !== player.id ||
|
||||
sale.winner !== player.id || sale.amount !== auction.bid || !response) return false;
|
||||
try { return !!validateAuctionContinuationCancellation(g, response); }
|
||||
catch (error) { if (error instanceof RuleError) return false; throw error; }
|
||||
}
|
||||
function kullCostRetainsAuctionPayment(g: Game, p: Player, card: Card, physical: readonly Card[]): boolean {
|
||||
return !g.auction || g.auction.bidder !== p.id || auctionSpicePaymentFunded(g, p) ||
|
||||
return !g.auction || g.auction.bidder !== p.id || kullPaidAuctionSale(g, p) || auctionSpicePaymentFunded(g, p) ||
|
||||
p.hand.some(held => held.id !== card.id && canonicalKullCard(held) &&
|
||||
physical.reduce((count, candidate) => count + Number(candidate.id === held.id), 0) === 1 &&
|
||||
!karamaSpendingBlock(g, p, held, 'auction-free-purchase'));
|
||||
@@ -19429,8 +19449,8 @@ function kullIntegrity(g: Game): void {
|
||||
requireRule(frame.auctionReceipt === undefined || frame.auctionReceipt === true &&
|
||||
!g.advanced && g.semutaPreview === true && !!g.currentAuctionSale &&
|
||||
frame.intent.kind === 'ordinary' && frame.intent.use.kind === 'cancel' &&
|
||||
frame.intent.use.response.kind === 'harkonnenBonus',
|
||||
'The Kull attempt has lost its original paid auction bonus.');
|
||||
['harkonnenBonus','emperorIncome','ixAllyCard'].includes(frame.intent.use.response.kind),
|
||||
'The Kull attempt has lost its original paid auction response.');
|
||||
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]),
|
||||
@@ -19498,7 +19518,7 @@ function beginKullAttempt(g: Game, p: Player, card: Card, intent: KullFrame['int
|
||||
'Kull preview cannot interrupt this unproven Truthtrance cancellation continuation.');
|
||||
// This composition is user-deferred. The guard is private and independent
|
||||
// of CHOAM's hidden hand; never accept a transaction needing a made-up remedy.
|
||||
requireRule(!g.auction || g.auction.bidder !== p.id ||
|
||||
requireRule(!g.auction || g.auction.bidder !== p.id || kullPaidAuctionSale(g, p) ||
|
||||
g.auction.bid <= p.spice + (g.auction.allyPayment ?? 0),
|
||||
'Kull preview defers Karama activation while your winning overbid cannot be paid in spice.');
|
||||
const resume: KullFrame['resume'] = {
|
||||
@@ -19524,7 +19544,7 @@ function beginKullAttempt(g: Game, p: Player, card: Card, intent: KullFrame['int
|
||||
...(turnActionPromiseModeSupported(g) && g.turnActionPromises?.length
|
||||
? { turnActionReceipt: true as const } : {}),
|
||||
...(g.semutaPreview && !g.advanced && !!g.currentAuctionSale && intent.kind === 'ordinary' &&
|
||||
intent.use.kind === 'cancel' && intent.use.response.kind === 'harkonnenBonus'
|
||||
intent.use.kind === 'cancel' && ['harkonnenBonus','emperorIncome','ixAllyCard'].includes(intent.use.response.kind)
|
||||
? { auctionReceipt: true as const } : {}),
|
||||
worthless: structuredClone(g.pendingChoamWorthless ?? null), signature: '',
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user