Connect Semuta with Nexus Kull in Basic games
Verify and publish checkpoint container / container (push) Successful in 10m47s
Verify and publish checkpoint container / container (push) Successful in 10m47s
This commit is contained in:
1 parent
d4cd03afff
commit
e697d7f558
13 files changed
+558
-24
No files matched your search
+53
-15
@@ -7131,7 +7131,8 @@ function kullSemutaParentSupported(g: Game, frame?: NonNullable<Game['pendingKul
|
||||
const battle = g.battle;
|
||||
const use = frame?.intent.kind === 'ordinary' ? frame.intent.use : undefined;
|
||||
const nativeResponse = !!frame && !g.advanced &&
|
||||
(bareBasicSemutaProfile(g) || g.semutaRecruitsPreview === true && basicIndependentRecruitsSemutaProfile(g)) &&
|
||||
(bareBasicSemutaProfile(g) || basicNexusKullSemutaProfile(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;
|
||||
@@ -7200,10 +7201,29 @@ function kullSemutaDiscardSignature(g: Game, c: KullSemutaDiscardContinuation):
|
||||
...(c.frame.auctionReceipt ? {auctionReceipt:c.frame.auctionReceipt, sale:g.currentAuctionSale,
|
||||
round:g.richeseBidding, cache:g.richeseCache?.map(card => card.id), funding:g.richeseFunding, aid:g.aid} : {}),
|
||||
restrictions: g.kullRestrictions, discard: g.discard.map(card => card.id),
|
||||
...(c.frame.selection?.source === 'nexus' ? {nexusChoamHistory:g.nexusChoamHistory, nexusChoamLast:g.nexusChoamLast} : {}),
|
||||
...(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})) });
|
||||
}
|
||||
/** A Nexus-fueled success is bound to its original physical Cunning receipt. */
|
||||
function kullSemutaNexusReceiptMatches(g: Game, c: KullSemutaDiscardContinuation): boolean {
|
||||
const selection = c.frame.selection;
|
||||
if (c.kind !== 'kullSuccessDiscard' || selection?.source !== 'nexus' || !selection.nexusEvent) return false;
|
||||
const record = g.nexusChoamHistory?.find(row => row.receipt.event === selection.nexusEvent);
|
||||
if (!record || record.stage !== 'complete' || record.signature !== nexusChoamSignature(record) ||
|
||||
record.receipt.owner !== c.player || record.receipt.card !== c.card || record.receipt.effect !== 'kull' ||
|
||||
record.receipt.turn !== g.turn || record.receipt.phase !== g.phase ||
|
||||
!g.nexusCards?.cards?.discard.includes('choam')) return false;
|
||||
try {
|
||||
nexusRule(() => validateNexusChoam(g, record.receipt));
|
||||
const declared = JSON.parse(record.frame) as {owner?:string;card?:string;effect?:string;nexusEvent?:string};
|
||||
const parent = JSON.parse(record.parent) as {kull?: {event?:string;signature?:string;selection?:unknown}};
|
||||
return declared.owner === c.player && declared.card === c.card && declared.effect === 'kull' &&
|
||||
declared.nexusEvent === selection.nexusEvent && parent.kull?.event === c.frame.event &&
|
||||
parent.kull.signature === c.frame.signature && JSON.stringify(parent.kull.selection) === JSON.stringify(selection);
|
||||
} catch { return false; }
|
||||
}
|
||||
function karamaResponseDiscardSignature(g: Game, c: KaramaResponseDiscardContinuation) {
|
||||
const battle = c.kind === 'karamaBattleDiscard';
|
||||
const worthless = c.kind === 'karamaChoamWorthlessDiscard';
|
||||
@@ -8072,6 +8092,10 @@ function semutaNexusProfile(g: Game): boolean {
|
||||
mixedE1E2NexusLeaderSkillsProfile(g)
|
||||
: pairedNexusModulesProfile(g) || singleE1E2NexusProfile(g) || mixedE1E2NexusProfile(g)));
|
||||
}
|
||||
function basicNexusKullSemutaProfile(g: Game): boolean {
|
||||
return g.advanced === false && g.semutaPreview === true && g.kullPreview === true &&
|
||||
g.nexusKullPreview === true && nexusCoreProfile(g);
|
||||
}
|
||||
/** Original classic occupation or a focused Advanced Tleilax SEND profile.
|
||||
* Other optional envelopes remain separate; no recorded game is redealt. */
|
||||
function semutaHomeworldRoster(g: Game): boolean {
|
||||
@@ -8256,11 +8280,12 @@ function treacheryDiscardIntegrity(g: Game) {
|
||||
frame.event === JSON.stringify(['kull', g.turn, g.phase, g.kullSequence, frame.player, frame.owner]) &&
|
||||
frame.signature === kullSignature(g, frame) &&
|
||||
getPlayer(g, frame.owner).hand.some(card => card.id === frame.card) &&
|
||||
batch.entries.length === 1 && entry.publicFace && entry.discardedBy === c.player &&
|
||||
batch.entries.length === 1 && entry.publicFace === !(c.kind === 'kullSuccessDiscard' && frame.selection?.source === 'nexus') && entry.discardedBy === c.player &&
|
||||
entry.card.id === c.card && canonicalKullCard(entry.card) &&
|
||||
(c.kind === 'kullSuccessDiscard'
|
||||
? c.player === frame.player && frame.selection?.source === 'printed' &&
|
||||
frame.selection.card === c.card && !c.declaration && !c.use &&
|
||||
? c.player === frame.player && (frame.selection?.source === 'printed' ||
|
||||
basicNexusKullSemutaProfile(g) && kullSemutaNexusReceiptMatches(g, c)) &&
|
||||
frame.selection?.card === c.card && !c.declaration && !c.use &&
|
||||
!!g.kullRestrictions?.some(row => row.player === frame.owner && row.turn === g.turn && row.phase === g.phase)
|
||||
: c.card !== frame.card && entry.card.effect === 'karama' && !!c.declaration &&
|
||||
c.declaration.effect === 'kull' && c.declaration.owner === frame.player &&
|
||||
@@ -9739,12 +9764,13 @@ 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 && bareBasicSemutaProfile(g) ||
|
||||
return (g.semutaPreview === true && (bareBasicSemutaProfile(g) || basicNexusKullSemutaProfile(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 &&
|
||||
entries[0].publicFace === !(continuation.kind === 'kullSuccessDiscard' &&
|
||||
continuation.frame.selection?.source === 'nexus') && entries[0].discardedBy === continuation.player &&
|
||||
entries[0].card.id === continuation.card;
|
||||
// These completed public costs retain the original ordinary lot through the
|
||||
// existing signed continuation. Semuta itself has no during-bid exception;
|
||||
@@ -11196,12 +11222,14 @@ function publicSemutaDiscoveryProfile(g: Game): boolean {
|
||||
!g.homeworlds && !g.nexusCards && g.discoveryEnabled === true && !g.ecazTreachery &&
|
||||
!g.leaderSkills && !g.kullPreview && !g.nexusKullPreview && discoveryModuleProfile(g);
|
||||
}
|
||||
/** Fresh Basic CHOAM-deck Nexus starts retain the existing native Semuta path. */
|
||||
/** Fresh Basic CHOAM/optional Ix Nexus starts retain the native Semuta path. */
|
||||
function publicSemutaNexusProfile(g: Game): boolean {
|
||||
return g.status === 'lobby' && g.advanced === false &&
|
||||
g.players.length >= 2 && g.players.length <= 6 &&
|
||||
g.expansions.length === 1 && g.expansions[0] === 'choam' && !!byFaction(g, 'richese') &&
|
||||
g.players.every(p => p.faction === 'richese' || p.faction === 'choam' || CLASSIC_FACTIONS[p.faction] === true) &&
|
||||
g.expansions.includes('choam') && g.expansions.length <= 2 &&
|
||||
g.expansions.every(id => id === 'choam' || id === 'ix') && !!byFaction(g, 'richese') &&
|
||||
g.players.every(p => p.faction === 'richese' || p.faction === 'choam' ||
|
||||
g.expansions.includes('ix') && (p.faction === 'ixians' || p.faction === 'tleilaxu') || CLASSIC_FACTIONS[p.faction] === true) &&
|
||||
!!g.nexusCards && !g.homeworlds && !g.discoveryEnabled && !g.ecazTreachery && !g.leaderSkills &&
|
||||
!g.techTokens && !g.strongholdCards && !g.twoPlayerControllers && !g.shortAllianceVictory &&
|
||||
!g.coalitionPreview && !g.kullPreview && !g.nexusKullPreview;
|
||||
@@ -12826,6 +12854,7 @@ function ixianNexusBetrayalIntegrity(g: Game): void {
|
||||
const contexts = [g, g.pendingExchange, g.pendingNullentropy?.resume,
|
||||
g.pendingRicheseGift?.resume, g.pendingRichesePurchaseIncome?.resume,
|
||||
g.summonedWorm?.resume, g.pendingKull?.resume,
|
||||
continuation?.kind === 'kullCounterDiscard' ? continuation.frame.resume : null,
|
||||
continuation && 'resume' in continuation ? continuation.resume : null];
|
||||
const provider = byFaction(g, 'ixians')!.id;
|
||||
const ownsWindow = (kind: 'ixAuction' | 'ixTechnology') => contexts.some(context => {
|
||||
@@ -13726,12 +13755,13 @@ export function initializeNexusKullGameForAudit(state: Game): Game {
|
||||
state.expansions.includes('choam') && state.expansions.includes('ix') &&
|
||||
state.players.some(p => p.faction === 'choam') &&
|
||||
state.players.every(p => p.faction === 'choam' || faction(p.faction).expansion === 'base' ||
|
||||
!state.advanced && (p.faction === 'ixians' || p.faction === 'tleilaxu')),
|
||||
'Nexus Kull requires a fresh CHOAM lobby with classic or Basic Ixian/Tleilaxu factions, CHOAM and Ix decks, and only Nexus Cards.');
|
||||
!state.advanced && (p.faction === 'richese' || p.faction === 'ixians' || p.faction === 'tleilaxu')),
|
||||
'Nexus Kull requires a fresh CHOAM lobby with classic or Basic Richese/Ixian/Tleilaxu factions, CHOAM and Ix decks, and only Nexus Cards.');
|
||||
requireFreshBaseRuntime(state);
|
||||
const g = initializeSetupGameForAudit(state, false, true, false, false, false, false, true);
|
||||
g.kullPreview = true;
|
||||
g.nexusKullPreview = true;
|
||||
if (!g.advanced && byFaction(g, 'richese')) g.semutaPreview = true;
|
||||
return g;
|
||||
}
|
||||
/** Prototype-only Discovery setup. Public starts stay gated while remaining effects are connected. */
|
||||
@@ -20193,7 +20223,8 @@ 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 (semutaCardProducerEnabled(g) && (!kullSemutaParentSupported(original, g.pendingKull ?? undefined) || source !== 'printed')) return [];
|
||||
if (semutaCardProducerEnabled(g) && (!kullSemutaParentSupported(original, g.pendingKull ?? undefined) ||
|
||||
source !== 'printed' && !basicNexusKullSemutaProfile(g))) return [];
|
||||
if (source === 'nexus' && (!g.nexusKullPreview || p.faction !== 'choam' || p.ally ||
|
||||
(g.pendingKull?.selection?.source !== 'nexus' && g.nexusCards?.cards?.hands[p.id] !== 'choam')))
|
||||
return [];
|
||||
@@ -20273,10 +20304,11 @@ function kullIntegrity(g: Game): void {
|
||||
g.expansions.length === 2 && g.expansions.includes('choam') && g.expansions.includes('ix') &&
|
||||
g.players.some(p => p.faction === 'choam') &&
|
||||
g.players.every(p => p.faction === 'choam' || faction(p.faction).expansion === 'base' ||
|
||||
!g.advanced && (p.faction === 'ixians' || p.faction === 'tleilaxu')) &&
|
||||
!g.advanced && (p.faction === 'richese' || p.faction === 'ixians' || p.faction === 'tleilaxu')) &&
|
||||
!g.homeworlds && !g.leaderSkills && !g.discoveryEnabled && !g.discoveries &&
|
||||
!g.discoveryStash && !g.greatMaker &&
|
||||
!g.techTokens && !g.strongholdCards && !g.ecazTreachery && !g.semutaPreview &&
|
||||
!g.techTokens && !g.strongholdCards && !g.ecazTreachery &&
|
||||
(!g.semutaPreview || !g.advanced && !!byFaction(g, 'richese')) &&
|
||||
!g.richeseBetrayalPreview && !g.richeseBetrayal && !g.pendingRicheseBetrayal,
|
||||
'The Nexus Kull profile has lost its supported native inventory and modules.');
|
||||
if (g.kullRestrictions !== undefined) validateKullPhaseRestrictions(g, g.kullRestrictions);
|
||||
@@ -38824,7 +38856,7 @@ function finishResponse(g: Game, canceled: boolean, settlement?: BureaucratSettl
|
||||
};
|
||||
g.response = null; g.decision = null; g.pendingKarama = null; g.phaseOpening = null;
|
||||
continuation.stateSignature = kullSemutaDiscardSignature(g, continuation);
|
||||
stageTreacheryDiscard(g, 'choam:kull', [{card:used, discardedBy:choam.id, publicFace:true}], continuation);
|
||||
stageTreacheryDiscard(g, 'choam:kull', [{card:used, discardedBy:choam.id, publicFace:pending.nexusEvent === undefined}], continuation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -42008,10 +42040,16 @@ function nexusChoamIntegrity(g: Game) {
|
||||
const karama = context && 'pendingKarama' in context ? context.pendingKarama as Game['pendingKarama'] : null;
|
||||
return [context?.response, karama?.use.kind === 'cancel' ? karama.use.response : null];
|
||||
}).filter((response): response is ResponseWindow => response?.kind === 'choamWorthless');
|
||||
const parkedCounter = continuation?.kind === 'kullCounterDiscard' && continuation.declaration?.nexusEvent
|
||||
? {...g, pendingKull:continuation.frame, pendingChoamWorthless:continuation.declaration,
|
||||
response:continuation.resume.response, decision:continuation.resume.decision,
|
||||
pendingKarama:continuation.resume.pendingKarama, phaseOpening:continuation.resume.phaseOpening} : null;
|
||||
const transactions = [
|
||||
{ state: g, pending: g.pendingChoamWorthless, responses: responseList(controls) },
|
||||
...(g.pendingKull ? [{ state: kullSource(g, g.pendingKull), pending: g.pendingKull.worthless,
|
||||
responses: responseList([g.pendingKull.resume]) }] : []),
|
||||
...(parkedCounter && continuation?.kind === 'kullCounterDiscard' ? [{ state: parkedCounter,
|
||||
pending: continuation.declaration, responses: responseList([continuation.resume]) }] : []),
|
||||
];
|
||||
const declared = transactions.filter(transaction => transaction.pending?.nexusEvent);
|
||||
const history = g.nexusChoamHistory;
|
||||
|
||||
Reference in new issue
Block a user