Allow Basic Emperor gifts before auction payment
Verify and publish checkpoint container / container (push) Successful in 10m39s

This commit is contained in:
Quintine committed 2026-10-30 18:58:02 +11:00
1 parent a1b09c5b02
commit b258c6867f
8 files changed
+342 -13

No files matched your search

+37 -13
View File
@@ -6715,6 +6715,14 @@ function semutaEmperorShareKaramaResponse(g: Game, response: ResponseWindow): bo
response.amount! > 0 && (response.elite === undefined || response.elite === 0 || response.elite === 1) &&
!g.battle && !g.pendingShipment && !g.pendingGuildTransport;
}
/** The auction gift may park only its exact original buyer/payment decision. */
function emperorAuctionGiftResumeMatches(g: Game, response: ResponseWindow, decision: Game['decision']): boolean {
const pending = g.pendingEmperorAuctionGift;
return !!pending && response.kind === 'emperorGift' && response.emperorAuctionGiftEvent === pending.event &&
response.owner === pending.owner && response.recipient === pending.recipient &&
decision?.kind === 'auctionPayment' && decision.player === pending.recipient &&
g.phase === 3 && !!g.auction && g.auction.bidder === pending.recipient && !g.currentAuctionSale && !g.richeseAuction;
}
/** Preserve physical identity: a Shrine authorizes the role, not a manufactured card. */
function semutaKaramaRoleCost(g: Game, owner: string, card: Card): boolean {
return card.effect === 'karama' || (card.effect === 'truthtrance' &&
@@ -8723,7 +8731,8 @@ function treacheryDiscardIntegrity(g: Game) {
c.resume.response?.kind === c.use.response.kind &&
JSON.stringify(c.resume.response) === JSON.stringify(c.use.response) &&
(c.kind === 'karamaBgConversionCancelDiscard' ||
((c.kind === 'karamaTerminalDiscard' || c.kind === 'karamaCombatDiscard' || !c.resume.decision) &&
((c.kind === 'karamaTerminalDiscard' || c.kind === 'karamaCombatDiscard' || !c.resume.decision ||
c.kind === 'karamaEmperorShareDiscard' && emperorAuctionGiftResumeMatches(g, c.use.response, c.resume.decision)) &&
!c.resume.pendingKarama && !c.resume.phaseOpening)) &&
batch.cause === `karama:${c.use.response.kind}` &&
batch.entries.length === 1 &&
@@ -10171,7 +10180,9 @@ function semutaOfferSupported(
: continuation.kind === 'karamaShipmentDiscard'
? semutaGuildShipmentKaramaResponse(g, continuation.use.response)
: ['choamCharity', 'choamInflation', 'bgCharity'].includes(continuation.resume.response?.kind ?? '')) &&
(terminalDiscard || combatDiscard || !continuation.resume.decision) && !continuation.resume.pendingKarama &&
(terminalDiscard || combatDiscard || !continuation.resume.decision ||
continuation.kind === 'karamaEmperorShareDiscard' &&
emperorAuctionGiftResumeMatches(g, continuation.use.response, continuation.resume.decision)) && !continuation.resume.pendingKarama &&
!continuation.resume.phaseOpening && !g.pendingKarama &&
(!g.response || JSON.stringify(g.response) === JSON.stringify(continuation.resume.response))
: true) &&
@@ -10243,7 +10254,9 @@ function semutaOfferSupported(
continuation.use.response.kind === 'emperorIncome'
? !!g.currentAuctionSale && (g.currentAuctionSale.origin === 'normal'
? !!g.auction && !g.richeseAuction : !!g.richeseAuction && !g.auction)
: ordinaryAuctionDiscard || (continuation.kind === 'saphoAuctionDiscard' && continuation.cyclic === 'normal') || !g.auction)) &&
: ordinaryAuctionDiscard || (continuation.kind === 'saphoAuctionDiscard' && continuation.cyclic === 'normal') ||
continuation.kind === 'karamaEmperorShareDiscard' &&
emperorAuctionGiftResumeMatches(g, continuation.use.response, continuation.resume.decision) || !g.auction)) &&
(advisorDiscard || auctionCostDiscard || bonusDiscard || auctionAdvantageDiscard || richeseGiftDiscard || bgDiscard || continuation.kind === 'specialKaramaDiscard' || continuation.kind === 'saphoAuctionDiscard' ||
continuation.kind === 'ixAllyCard' ||
(continuation.kind === 'karamaIncomeDiscard' &&
@@ -18911,14 +18924,21 @@ function auctionNext(g: Game) {
a.active = next.player;
g.active = a.active;
}
function emperorAuctionGiftParked(g: Game) {
const c = g.pendingTreacheryDiscard?.continuation;
if (c?.kind === 'kullCounterDiscard') return c.frame.resume;
if (c?.kind === 'kullSuccessDiscard' || c?.kind === 'karamaEmperorShareDiscard') return c.resume;
return g.pendingKull?.resume ?? null;
}
function emperorAuctionGiftSignature(g: Game): string {
return JSON.stringify([g.turn, g.phase, g.active, g.decision, g.order, g.auction, g.aid]);
return JSON.stringify([g.turn, g.phase, g.active, g.decision ?? emperorAuctionGiftParked(g)?.decision, g.order, g.auction, g.aid]);
}
/** The public alliance and winning seat alone determine this first refusal.
* Only auctionNext creates it: a restored payment never reopens the offer. */
function beginEmperorAuctionGift(g: Game) {
if (!g.advanced || g.status !== 'playing' || g.phase !== 3 ||
g.expansions.length !== 0 || g.players.some(seat => !['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'].includes(seat.faction)) ||
if (g.status !== 'playing' || g.phase !== 3 ||
(g.advanced ? g.expansions.length !== 0 || g.players.some(seat => !['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'].includes(seat.faction))
: g.expansions.length > 0 && !selectedPhaseModuleRoster(g)) ||
g.techTokens || g.strongholdCards || g.leaderSkills || g.homeworlds || g.nexusCards ||
g.discoveryEnabled || g.sandtrout || g.coalitionState || g.pendingEmperorAuctionGift ||
g.response || g.phaseOpening || g.truthtrance || g.pendingKarama ||
@@ -18943,14 +18963,16 @@ function emperorAuctionGiftIntegrity(g: Game) {
}
const owner = g.players.find(player => player.id === pending.owner);
const recipient = g.players.find(player => player.id === pending.recipient);
requireRule(g.status === 'playing' && g.advanced && g.phase === 3 && g.turn === pending.turn &&
const parked = emperorAuctionGiftParked(g);
const decision = g.decision ?? parked?.decision;
requireRule(g.status === 'playing' && g.phase === 3 && g.turn === pending.turn &&
typeof pending.event === 'string' && !!pending.event && owner?.faction === 'emperor' && recipient &&
owner.ally === recipient.id && recipient.ally === owner.id &&
g.decision?.kind === 'auctionPayment' && g.decision.player === recipient.id &&
decision?.kind === 'auctionPayment' && decision.player === recipient.id &&
g.auction?.bidder === recipient.id && pending.signature === emperorAuctionGiftSignature(g),
'The Emperor auction gift lost its original payment opportunity.');
const response = g.pendingKarama?.use.kind === 'cancel'
? g.pendingKarama.use.response : g.response;
const response = parked?.response ?? (g.pendingKarama?.use.kind === 'cancel'
? g.pendingKarama.use.response : g.response);
if (response)
requireRule(response.kind === 'emperorGift' && response.owner === owner.id &&
response.recipient === recipient.id && response.emperorAuctionGiftEvent === pending.event &&
@@ -20801,7 +20823,9 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
g.response = null;
if (bgConversionCancelDiscard) {
g.decision = null; g.pendingKarama = null; g.phaseOpening = null;
} else if (continuation.kind === 'karamaTerminalDiscard' || continuation.kind === 'karamaCombatDiscard') g.decision = null;
} else if (continuation.kind === 'karamaTerminalDiscard' || continuation.kind === 'karamaCombatDiscard' ||
continuation.kind === 'karamaEmperorShareDiscard' &&
emperorAuctionGiftResumeMatches(g, continuation.use.response, continuation.resume.decision)) g.decision = null;
continuation.stateSignature = karamaResponseDiscardSignature(g, continuation);
stageTreacheryDiscard(g, `karama:${use.response.kind}`, entries, continuation);
return;
@@ -44604,7 +44628,7 @@ function applyActionInner(
revealPlayerNoField(g, p, 'voluntary');
return g;
}
if (g.pendingEmperorAuctionGift && !g.response && !g.pendingKarama) {
if (g.pendingEmperorAuctionGift && !g.response && !g.pendingKarama && !g.pendingKull) {
const pending = g.pendingEmperorAuctionGift;
if (t === 'advanceBots') return g;
requireRule(id === pending.owner, 'Wait for the Emperor to finish the allied gift opportunity.');
@@ -44623,7 +44647,7 @@ function applyActionInner(
return g;
}
if (g.pendingEmperorAuctionGift && (g.response || g.pendingKarama) && t !== 'advanceBots')
requireRule(t === 'passResponse' || t === 'card' && action.mode === 'cancel',
requireRule(t === 'passResponse' || t === 'card' && action.mode === 'cancel' || t === 'kullDecision' && !!g.pendingKull,
'Resolve the Emperor gift response before another action.');
if (g.nexusFremenRideBetrayal?.stage === 'choose') {
if (t === 'advanceBots') return g;