Enable Kull during Basic battle preparation
Verify and publish checkpoint container / container (push) Failing after 7m45s

This commit is contained in:
Quintine committed 2026-10-28 20:47:15 +11:00
1 parent 6bf165a6aa
commit 91ce096818
8 files changed
+371 -8

No files matched your search

+21 -5
View File
@@ -1773,6 +1773,8 @@ export type Game = {
resume: NonNullable<Game['pendingNullentropy']>['resume'];
declaration?: NonNullable<Game['pendingChoamWorthless']>;
use?: Extract<KaramaUse, { kind: 'cancel' }>;
/** New pre-plan battle reactions bind the original live battle. */
battleBinding?: true;
stateSignature: string;
}
| {
@@ -6873,8 +6875,18 @@ type KullSemutaDiscardContinuation = Extract<
>;
/** These native parents can be suspended without replaying another physical
* transaction. More deeply nested response families retain their own gates. */
function kullSemutaParentSupported(g: Game): boolean {
return !g.truthtrance && !g.battle && !g.pendingRicheseGift && !g.pendingNullentropy &&
function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKull']>): boolean {
const battle = g.battle;
const use = frame?.intent.kind === 'ordinary' ? frame.intent.use : undefined;
const battleSupported = !battle || !!frame && !g.advanced && g.phase === 6 && !battle.revealed &&
!!battle.event && (!battle.preLeader || battle.preLeader.event === battle.event &&
Array.isArray(battle.preLeader.ready) && new Set(battle.preLeader.ready).size === battle.preLeader.ready.length &&
battle.preLeader.ready.every(id => id === battle.attacker || id === battle.defender)) &&
Object.keys(battle.plans).length === 0 && !battle.truthPromises?.some(promise => !promise.released) &&
use?.kind === 'cancel' && ['voice', 'prescience'].includes(use.response.kind) &&
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 && !g.pendingShipment &&
!g.pendingGuildTransport && !g.pendingHomeworldShipment && !g.pendingTerrorEntry &&
!g.pendingEcazPlacement && !g.pendingMoritaniPlacement &&
@@ -6890,6 +6902,7 @@ function kullSemutaDiscardSignature(g: Game, c: KullSemutaDiscardContinuation):
parent: nullentropyParentSignature(g, c.resume, c.frame.event),
active: g.active, order: g.order, auction: g.auction, richeseAuction: g.richeseAuction,
restrictions: g.kullRestrictions, discard: g.discard.map(card => card.id),
...(c.battleBinding ? {battle:g.battle} : {}),
players: g.players.map(player => ({id:player.id, hand:player.hand.map(card => card.id),
spice:player.spice, reserves:player.reserves, forces:player.forces, tanks:player.tanks})) });
}
@@ -9234,7 +9247,8 @@ function semutaOfferSupported(
) {
if (continuation.kind === 'kullSuccessDiscard' || continuation.kind === 'kullCounterDiscard')
return g.semutaPreview === true && g.advanced === false && bareBasicSemutaProfile(g) &&
!!byFaction(g, 'richese') && kullSemutaParentSupported(g) && entries.length === 1 &&
!!byFaction(g, 'richese') && kullSemutaParentSupported(kullSource(g, continuation.frame), continuation.frame) &&
(!!g.battle === !!continuation.battleBinding) && entries.length === 1 &&
entries[0].publicFace && entries[0].discardedBy === continuation.player &&
entries[0].card.id === continuation.card;
// These completed public costs retain the original ordinary lot through the
@@ -18743,7 +18757,7 @@ function kullCostPromisesAllow(g: Game, p: Player, card: Card, fuelSource: 'prin
}
function kullCostCards(g: Game, p: Player, source: 'printed' | 'nexus' = 'printed'): Card[] {
const original = g.pendingKull ? kullSource(g, g.pendingKull) : g;
if (g.semutaPreview && (!kullSemutaParentSupported(original) || source !== 'printed')) return [];
if (g.semutaPreview && (!kullSemutaParentSupported(original, g.pendingKull ?? undefined) || source !== 'printed')) return [];
if (source === 'nexus' && (!g.nexusKullPreview || p.faction !== 'choam' || p.ally ||
(g.pendingKull?.selection?.source !== 'nexus' && g.nexusCards?.cards?.hands[p.id] !== 'choam')))
return [];
@@ -19025,13 +19039,14 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
if (g.semutaPreview && !g.advanced && g.pendingKull?.stage === 'counter' &&
use.kind === 'cancel' && use.response.kind === 'choamWorthless' && use.response.intent === 'Kull Wahad') {
const declaration = g.pendingChoamWorthless;
requireRule(declaration && kullSemutaParentSupported(kullSource(g, g.pendingKull)),
requireRule(declaration && kullSemutaParentSupported(kullSource(g, g.pendingKull), g.pendingKull),
'This Kull counter cannot suspend its current parent for Semuta.');
const continuation: KullSemutaDiscardContinuation = {
kind: 'kullCounterDiscard', player: p.id, card: used.id,
frame: structuredClone(g.pendingKull), declaration: structuredClone(declaration), use,
resume: { response: g.response, decision: g.decision,
pendingKarama: g.pendingKarama, phaseOpening: g.phaseOpening },
...(g.battle ? {battleBinding:true as const} : {}),
stateSignature: '',
};
g.pendingKull = null; g.pendingChoamWorthless = null;
@@ -36116,6 +36131,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
kind: 'kullSuccessDiscard', player: choam.id, card: used.id, frame,
resume: { response: g.response, decision: g.decision,
pendingKarama: g.pendingKarama, phaseOpening: g.phaseOpening },
...(g.battle ? {battleBinding:true as const} : {}),
stateSignature: '',
};
g.response = null; g.decision = null; g.pendingKarama = null; g.phaseOpening = null;