Connect Kull and Semuta with independent cards
Verify and publish checkpoint container / container (push) Canceled after 0s

This commit is contained in:
Quintine committed 2026-10-29 03:28:30 +11:00
1 parent 87d1887139
commit bc0db31134
11 files changed
+412 -23

No files matched your search

+24 -12
View File
@@ -6941,7 +6941,8 @@ type KullSemutaDiscardContinuation = Extract<
function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKull']>): boolean {
const battle = g.battle;
const use = frame?.intent.kind === 'ordinary' ? frame.intent.use : undefined;
const nativeResponse = !!frame && !g.advanced && bareBasicSemutaProfile(g) &&
const nativeResponse = !!frame && !g.advanced &&
(bareBasicSemutaProfile(g) || g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g)) &&
use?.kind === 'cancel' && JSON.stringify(use.response) === JSON.stringify(frame.resume.response) &&
!frame.resume.decision && !frame.resume.pendingKarama && !frame.resume.phaseOpening;
let revivalSupported = false;
@@ -9477,7 +9478,9 @@ function semutaOfferSupported(
!continuation.resume.response && !continuation.resume.decision &&
!continuation.resume.pendingKarama && !continuation.resume.phaseOpening;
if (continuation.kind === 'kullSuccessDiscard' || continuation.kind === 'kullCounterDiscard')
return g.semutaPreview === true && g.advanced === false && bareBasicSemutaProfile(g) &&
return (g.semutaPreview === true && bareBasicSemutaProfile(g) ||
g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g) &&
(!g.pendingTreacheryDiscard || !!g.pendingTreacheryDiscard.reaction)) && g.advanced === false &&
!!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 &&
@@ -9709,7 +9712,8 @@ function semutaOfferSupported(
const independentNativeDiscard = ambassadorDiscard || continuation.kind === 'terrorDiscard' ||
continuation.kind === 'choamSaleDiscard' || continuation.kind === 'choamKulonDiscard' ||
continuation.kind === 'choamLaLaLaDiscard' || continuation.kind === 'choamBalisetDiscard' ||
continuation.kind === 'choamGamontDiscard' || continuation.kind === 'choamJubbaDiscard';
continuation.kind === 'choamGamontDiscard' || continuation.kind === 'choamJubbaDiscard' ||
continuation.kind === 'karamaShipmentPriceDiscard';
return (g.semutaPreview === true || g.semutaRecruitsPreview === true &&
(!g.pendingTreacheryDiscard || !!g.pendingTreacheryDiscard.reaction) &&
(independentNativeDiscard || continuation.kind === 'ordinaryCardDiscard' &&
@@ -9717,6 +9721,7 @@ function semutaOfferSupported(
continuation.kind === 'battleResolved' || continuation.kind === 'winnerMandatoryDiscard' ||
continuation.kind === 'battleCleanup' && (continuation.source === 'winner' ||
continuation.source === 'moritani' && !g.advanced && continuation.retention?.source === undefined) ||
continuation.kind === 'residualPoisonDiscard' ||
continuation.kind === 'nullentropyDiscard' || continuation.kind === 'distransDiscard' ||
continuation.kind === 'truthtranceDiscard' && continuation.promise === null &&
(continuation.record.question.kind === 'fact' || continuation.record.question.kind === 'freeform') ||
@@ -10845,10 +10850,16 @@ function startModules(state: Game): Game {
g.players.every(player => player.faction === 'choam' || player.faction === 'richese' || player.faction === 'ixians' ||
player.faction === 'tleilaxu' || g.expansions.includes('ecaz') &&
(player.faction === 'ecaz' || player.faction === 'moritani') || CLASSIC_FACTIONS[player.faction] === true) &&
!options.leaderSkills && !options.ecazTreachery && !g.nexusCards && !g.discoveryEnabled &&
!options.leaderSkills && !g.nexusCards && !g.discoveryEnabled &&
!g.homeworlds && !g.techTokens && !g.strongholdCards;
requireRule(supportedBasic || advancedKullLobbyEligible(state),
'Kull preview needs CHOAM and Ix decks with no optional modules. Advanced currently supports CHOAM and classic factions only; Basic also supports the selected native expansion factions.');
'Kull preview needs CHOAM and Ix decks. Advanced supports CHOAM and classic factions without optional modules; Basic also supports selected native expansion factions and independent Ecaz Treachery Cards.');
if (options.ecazTreachery) {
requireRule(supportedBasic, 'Kull with independent Ecaz cards currently needs the supported Basic native roster.');
const initialized = initializeEcazTreacheryGameForAudit(g);
initialized.kullPreview = true;
return initialized;
}
return initializeKullGameForAudit(g);
}
if (options.ecazTreachery && options.leaderSkills) g.ecazTreachery = true;
@@ -13081,7 +13092,8 @@ function ecazOccupyCompositionSupported(g: Game): boolean {
// Family/roster checks below remain authoritative; optional configurations
// retain their separate registered profiles rather than inheriting admission.
const basicNatives = g.advanced === false && !g.strongholdCards &&
((!g.ecazTreachery && (!g.techTokens || nativeFactionTechProfile(g))) || selectedIndependentCardsProfile(g));
((!g.ecazTreachery && (!g.techTokens || nativeFactionTechProfile(g))) || selectedIndependentCardsProfile(g) ||
g.kullPreview === true && selectedIndependentCardsProfile({...g,kullPreview:undefined}));
if ((independentNexusProfile(g) || independentDiscoveryProfile(g)) && byFaction(g, 'ecaz')) return true;
if (g.leaderSkills && nativeEcazLeaderSkillsProfile(g)) return true;
if (g.ecazOccupyPreview === true && (mixedE3NexusProfile(g) || standaloneE3NexusProfile(g) || pairedE3NexusProfile(g)) &&
@@ -13211,7 +13223,7 @@ function basicIndependentRecruitsSemutaProfile(g: Game): boolean {
g.expansions.includes(faction(p.faction).expansion)) &&
!g.homeworlds && !g.nexusCards && !g.leaderSkills && !g.techTokens && !g.strongholdCards &&
!g.discoveryEnabled && !g.discoveries && !g.discoveryStash && !g.greatMaker &&
!g.kullPreview && !g.nexusKullPreview && !g.moritaniAssassinatePreview;
!g.nexusKullPreview && !g.moritaniAssassinatePreview;
}
function semutaCardProducerEnabled(g: Game): boolean {
return g.semutaPreview === true || g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g);
@@ -19469,7 +19481,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, g.pendingKull ?? undefined) || source !== 'printed')) return [];
if (semutaCardProducerEnabled(g) && (!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 [];
@@ -19884,7 +19896,7 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
return stage && !stage.closed && windowCardOpportunity(g, effect, stage.source)?.source === stage.source;
});
})) guardTurnActionPrintedCardCost(g, p, used);
if (g.semutaPreview && !g.advanced && g.pendingKull?.stage === 'counter' &&
if (semutaCardProducerEnabled(g) && !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), g.pendingKull),
@@ -19903,7 +19915,7 @@ function spendKarama(g: Game, p: Player, card: Card, use: KaramaUse, kullOffered
stageTreacheryDiscard(g, 'karama:kullCounter', [{card:used, discardedBy:p.id, publicFace:true}], continuation);
return;
}
const semutaRoleCost = g.semutaPreview && semutaKaramaRoleCost(g, p.id, card);
const semutaRoleCost = semutaCardProducerEnabled(g) && semutaKaramaRoleCost(g, p.id, card);
const guildShipmentDiscard = semutaRoleCost &&
use.kind === 'cancel' && semutaGuildShipmentKaramaResponse(g, use.response);
const bgConversionCancelDiscard = semutaRoleCost &&
@@ -30821,7 +30833,7 @@ function playResidualPoison(
`${p.name} played Residual Poison against ${target.name}. ${victim.name} was randomly selected from the available leaders and sent to the Tanks. No spice is awarded, no forces are lost, and battle preparation continues.`,
{ faction: p.faction, name: 'Residual Poison' },
);
if (g.semutaPreview || g.semutaRecruitsPreview && basicIndependentSemutaProfile(g)) {
if (g.semutaPreview || g.semutaRecruitsPreview && basicIndependentRecruitsSemutaProfile(g)) {
const continuation: ResidualPoisonDiscardContinuation = {
kind: 'residualPoisonDiscard', player: p.id, target: target.id,
victim: victim.id, battleEvent: b.event!, card: used.id,
@@ -37121,7 +37133,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
log(g, `${getPlayer(g, g.pendingKull!.owner).name} cannot activate ${restriction.purpose
? kullPurposeLabel(restriction.purpose) : 'Karama'} for this phase. The attempted card stays held.`);
resumeKullAttempt(g, true);
if (g.semutaPreview && !g.advanced) {
if (semutaCardProducerEnabled(g) && !g.advanced) {
const continuation: KullSemutaDiscardContinuation = {
kind: 'kullSuccessDiscard', player: choam.id, card: used.id, frame,
resume: { response: g.response, decision: g.decision,
+2 -2
View File
@@ -48,11 +48,11 @@ export function kullDefaultProfile(game: KullDefaultProfile): KullDefaultEligibi
new Set(game.players.map(player => player.faction)).size !== game.players.length)
return blocked('Automatic Kull needs one to six distinct factions, including CHOAM, with their selected decks.');
const options = game.lobbyOptions, projected = game.lobbyModules;
if (options?.leaderSkills || options?.ecazTreachery || projected?.leaderSkills || projected?.ecazTreachery ||
if (options?.leaderSkills || projected?.leaderSkills ||
projected?.nexusCards || projected?.discoveries || game.leaderSkills || game.ecazTreachery || game.nexusCards ||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.homeworlds || game.techTokens || game.strongholdCards)
return blocked('Automatic Kull currently excludes optional modules.');
return blocked('Automatic Kull currently excludes optional modules other than the independent Ecaz Treachery Cards.');
return { eligible: true, reason: null };
}
+4 -3
View File
@@ -4840,7 +4840,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'Whenever another faction requests a normal revival with a free portion, CHOAM receives a response decision independently of its private hand. Allow the request, or declare La La La for that requester. If the effect succeeds, the whole pending request stops without payment, force movement or quota use; a faction permitted to purchase normal revivals can submit a paid request. If the effect is canceled, the original quote resumes through any Tleilaxu and other applicable responses.',
'La La La does not prevent otherwise legal paid revivals or separate Ghola card effects. With Tleilaxu in the game, Fremen may submit a paid request within their current limit after free revival is prevented. Without Tleilaxu, Fremen’s base purchase restriction leaves no normal force return available. Emperor-funded extras remain separate. The prohibition resets when the next Revival phase begins. Card custody is checked at settlement: if CHOAM cashed in the declared card during the response, no Worthless effect occurs and the interrupted revival resumes.',
'During Shipment and Movement, use Baliset to select another player and a territory CHOAM occupies. The current implementation prevents that player from entering that territory by movement for the phase while CHOAM remains there; shipment is allowed. A declared move into a CHOAM territory pauses for CHOAM regardless of its private hand. Allow it or declare Baliset. A successful effect leaves the moving forces in place and the movement unspent; a canceled or unavailable card resumes the original move after checking its legality again.',
'Kulon, La La La, Baliset, Jubba Cloak and Trip to Gamont have engine, private controls and AI support. Printed Kull Wahad has a selectable Basic preview, on by default in new eligible lobbies, for CHOAM, Richese, Ixians, Tleilaxu, Ecaz, Moritani and classic factions with the CHOAM and Ix decks, each seated faction’s deck and no other modules; the explicit Nexus Kull profile adds Cunning fuel choices at that same reaction, not in every Nexus game. See both Kull guides for distinct counters, pre-conversion Bene Gesserit custody and the deferred winning-overbid boundary. Exact cancellation scope, Hajr/ornithopter combinations, Baliset duration and destination scope, simultaneous prevention and later module exceptions remain under audit. Complete expansion play remains unfinished.',
'Kulon, La La La, Baliset, Jubba Cloak and Trip to Gamont have engine, private controls and AI support. Printed Kull Wahad has a selectable Basic preview, on by default in new eligible lobbies, for CHOAM, Richese, Ixians, Tleilaxu, Ecaz, Moritani and classic factions with the CHOAM and Ix decks and each seated faction’s deck. Independent Ecaz Treachery Cards may also be selected; other optional modules remain excluded. The explicit Nexus Kull profile adds Cunning fuel choices at that same reaction, not in every Nexus game. See both Kull guides for distinct counters, pre-conversion Bene Gesserit custody and the deferred winning-overbid boundary. Exact cancellation scope, Hajr/ornithopter combinations, Baliset duration and destination scope, simultaneous prevention and later module exceptions remain under audit. Complete expansion play remains unfinished.',
],
related: [
'faction-choam',
@@ -4863,7 +4863,8 @@ export const RULE_TOPICS: RuleTopic[] = [
developmentStage: 'Prototyped',
summary: 'React before a Karama is played; a distinct counter can stop Kull before its phase restriction begins.',
steps: [
'In a new supported Basic lobby with CHOAM and the CHOAM and Ix decks, printed Kull is enabled by default as a development preview. The host may turn it off to keep the broader non-Kull Truthtrance options. Basic supports Richese, Ixians, Tleilaxu, Ecaz, Moritani and classic factions with each seated faction’s deck and no other optional modules. In an Advanced preview lobby, the host may explicitly select printed Kull with CHOAM, classic factions and exactly the CHOAM and Ix decks, without optional modules. Advanced supports Bene Gesserit substitutions and legal special powers. Both previews withhold some structured questions and defer unfunded non-CHOAM Karama-dependent bids. Existing games are not retrofitted, and this warning does not certify complete rules.',
'In a new supported Basic lobby with CHOAM and the CHOAM and Ix decks, printed Kull is enabled by default as a development preview. The host may turn it off to keep the broader non-Kull Truthtrance options. Basic supports Richese, Ixians, Tleilaxu, Ecaz, Moritani and classic factions with each seated faction’s deck, optionally with independent Ecaz Treachery Cards; other optional modules are excluded. In an Advanced preview lobby, the host may explicitly select printed Kull with CHOAM, classic factions and exactly the CHOAM and Ix decks, without optional modules. Advanced supports Bene Gesserit substitutions and legal special powers. Both previews withhold some structured questions and defer unfunded non-CHOAM Karama-dependent bids. Existing games are not retrofitted, and this warning does not certify complete rules.',
'In supported Basic games with independent Ecaz Treachery Cards and Richese, Semuta may claim the discarded successful Kull, a distinct Karama counter, or the original shipment Karama when Kull declines. The original attempt continues under its existing rules; other nested reactions remain incomplete.',
'In the supported Basic printed Kull preview, simple and mixed current-lot ordinary bid, phase-wide bid, ordinary bribe, Revival and alliance questions are available, including with Richese and Semuta. Combined turn-action questions are also available in this profile. A pending Kull response holds their controls while the original transaction resolves; some Karama uses remain guarded. Kull and Semuta also retain a paid ordinary or cache sale when Karama targets Harkonnen’s bonus or Emperor income, and an ordinary sale when it targets an Ixian ally’s replacement. Wider interactions remain incomplete.',
'Another player’s legal Karama cancellation, shipment-rate use, immediate auction purchase, winning-bid payment or supported special activation pauses before its card and other costs. CHOAM sees the attempted player and purpose and may decline or choose a legal held Kull Wahad. Each Use names its printed source explicitly. The separate Nexus Kull guide covers additional Cunning costs in its new profile only. The same public opportunity appears without Kull in hand, so waiting does not disclose hidden ownership. Invalid attempts never open this window; CHOAM does not receive a self-targeted Kull offer.',
'Decline resumes the original attempt once. Declaring Kull opens the ordinary CHOAM-effect response. Kull is still pending: its target may counter with a different eligible physical Karama before any phase ban applies. The interrupted card stays reserved and cannot serve as its own counter. Other eligible responders use their existing response controls.',
@@ -6620,7 +6621,7 @@ export const RULE_TOPICS: RuleTopic[] = [
steps: [
'The separate three-card variant includes Recruits, Reinforcements and Harass & Withdraw. It does not require the Ecaz faction. A development preview includes all three physical cards before setup; Recruits, bounded Reinforcements and Harass & Withdraw controls are connected.',
'The holder may play Recruits from its Revival panel. Discard the card once, then all players use the increased rates while this Revival phase remains open, including players who had already marked themselves ready. Completed returns still count toward their turn totals.',
'In a fresh Basic game with Richese, the CHOAM deck and these independent cards, Semuta Drug may claim Recruits or a used Hajr, Ghola, Harvester, Weather Control or Family Atomics immediately after another player discards it, including when other selected expansion factions are seated. Supported battle cleanup, native Moritani allied retention, Ixian substitution, CHOAM sales and Worthless costs, clean Ambassador and Terror discards, completed Box searches, Distrans, simple Truthtrance and ordinary Ornithopter can also offer their discarded card. The original effect stays committed; other Semuta opportunities remain unfinished.',
'In a fresh Basic game with Richese, the CHOAM deck and these independent cards, Semuta Drug may claim Recruits or a used Hajr, Ghola, Harvester, Weather Control or Family Atomics immediately after another player discards it, including when other selected expansion factions are seated. Supported battle cleanup, clean pre-leader Residual Poison, native Moritani allied retention, Ixian substitution, CHOAM sales and Worthless costs, clean Ambassador and Terror discards, completed Box searches, Distrans, simple Truthtrance and ordinary Ornithopter can also offer their discarded card. The original effect stays committed; other Semuta opportunities remain unfinished.',
'Atreides rises from two free forces to four; Fremen from three to six; Guild from one to two. The ordinary force limit becomes seven. Native unlimited CHOAM and Tleilaxu allowances remain unlimited, and their free rates become zero and four. Paid prices and separate elite limits still apply.',
'Free prevention still prevents free returns. Prior paid ordinary returns now have a visibly provisional policy: preserve payment, deduct only recorded free returns from the doubled free allowance and all returns from the total cap. Two free plus one paid Atreides may take two more free without refund; this is not a numerical user ruling. Fremen may now grant after play under the same provisional current-rate policy, with its original Karama response settled before commitment. Missing/inconsistent ledgers, pending transactions and same-turn replay remain guarded.',
'Clean classic2–6 Basic/Advanced Homeworlds compose through the fresh independent36-card setup. Double current native free rates including low-population bonuses, within the seven-force total. Provisionally recheck population and recorded free use on each separate request: low Fremen may take7 together, but1 crossing high leaves5 further free and cannot buy a seventh without Tleilaxu. Original typed destinations and special-force limits stay. Other overlays/rosters, public activation and whole-mode acceptance remain unfinished.',