Compose Semuta with original native Nexus discard receipts

This commit is contained in:
Quintine committed 2026-10-12 09:26:49 +11:00
1 parent 5b3729e2db
commit 9afc03b347
17 files changed
+959 -23

No files matched your search

+25 -7
View File
@@ -5525,13 +5525,21 @@ function suspendedControlsIntegrity(
strongholdIntegrity(context);
auditorIntegrity(context);
}
/** Original native Nexus roster/modules with no extra overlay-specific setup. */
function semutaNexusProfile(g: Game): boolean {
return !!g.nexusCards && (g.leaderSkills
? pairedNexusLeaderSkillsProfile(g) || singleE1E2NexusLeaderSkillsProfile(g) ||
mixedE1E2NexusLeaderSkillsProfile(g)
: pairedNexusModulesProfile(g) || singleE1E2NexusProfile(g) || mixedE1E2NexusProfile(g));
}
function treacheryDiscardIntegrity(g: Game) {
requireRule(g.semutaPreview === undefined || g.semutaPreview === true &&
g.expansions.includes('choam') && !!byFaction(g, 'richese') &&
!g.homeworlds && !g.nexusCards &&
!g.homeworlds && (!g.nexusCards || semutaNexusProfile(g)) &&
(!g.leaderSkills || ordinaryLeaderSkillModeSupported(g)) && !g.discoveries &&
!g.discoveryEnabled && (!g.techTokens || (g.leaderSkills
? nativeTechLeaderSkillsProfile(g) : nativeFactionTechProfile(g, true))) &&
!g.discoveryEnabled && (!g.techTokens || (g.nexusCards
? semutaNexusProfile(g) : g.leaderSkills
? nativeTechLeaderSkillsProfile(g) : nativeFactionTechProfile(g, true))) &&
(!g.strongholdCards || nativeStrongholdFactionRoster(g)),
'This Semuta preview no longer matches its original Richese profile.');
const pending = g.pendingTreacheryDiscard;
@@ -6700,6 +6708,8 @@ function semutaOfferSupported(
continuation.kind !== 'nullentropyDiscard' &&
continuation.kind !== 'distransDiscard' &&
continuation.kind !== 'terrorDiscard' &&
continuation.kind !== 'nexusChoamTrade' &&
continuation.kind !== 'nexusChoamBetrayal' &&
continuation.kind !== 'saphoMovementDiscard' &&
continuation.kind !== 'saphoAggressorDiscard' &&
continuation.kind !== 'saphoAuctionDiscard' &&
@@ -6777,7 +6787,8 @@ function semutaOfferSupported(
return g.semutaPreview === true && g.status === 'playing' &&
g.players.some(p => p.faction === 'richese') &&
entries.length > 0 &&
(continuation.kind === 'ixAllyCard' || continuation.kind === 'terrorDiscard'
(continuation.kind === 'ixAllyCard' || continuation.kind === 'terrorDiscard' ||
continuation.kind === 'nexusChoamBetrayal'
? entries.length === 1 && !entries[0].publicFace
: continuation.kind === 'battleCleanup' ||
continuation.kind === 'winnerMandatoryDiscard' ||
@@ -6800,6 +6811,8 @@ function semutaOfferSupported(
continuation.kind === 'battleResolved' ||
continuation.kind === 'ixAllyCard' ||
continuation.kind === 'terrorDiscard' ||
continuation.kind === 'nexusChoamTrade' ||
continuation.kind === 'nexusChoamBetrayal' ||
continuation.kind === 'choamSaleDiscard' ||
continuation.kind === 'choamKulonDiscard' ||
continuation.kind === 'choamLaLaLaDiscard' ||
@@ -9604,8 +9617,8 @@ export function initializeMoritaniAssassinateGameForAudit(state: Game): Game {
initializeMoritaniAssassinateState(g);
return g;
}
/** Development-only Semuta with original native Tech/Advanced Strongholds and
* explicit native skills, or bounded Basic Moritani/E3. Public starts stay gated. */
/** Development-only Semuta with original native Tech/Advanced Strongholds/Nexus
* and explicit native skills, or bounded Basic Moritani/E3. Public starts stay gated. */
export function initializeSemutaGameForAudit(state: Game, skills = false): Game {
const nativeTerror = !state.advanced && state.expansions.length === 2 &&
state.expansions.includes('choam') && state.expansions.includes('ecaz') &&
@@ -9616,8 +9629,13 @@ export function initializeSemutaGameForAudit(state: Game, skills = false): Game
state.expansions.includes('choam') &&
(state.expansions.every(id => id === 'choam' || id === 'ix') || nativeTerror) &&
state.players.some(p => p.faction === 'richese'),
'Semuta needs a fresh Richese faction game: CHOAM with optional Ix, original Tech/Advanced Strongholds, and explicit native Basic Richese or Advanced E1/E2 Leader Skills; separate Basic Moritani uses CHOAM/Ecaz and classics without modules. Other overlays remain excluded.');
'Semuta needs a fresh Richese faction game: CHOAM with optional Ix, original Tech/Advanced Strongholds/Nexus, and explicit native Basic Richese or Advanced E1/E2 Leader Skills; separate Basic Moritani uses CHOAM/Ecaz and classics without modules. Other overlays remain excluded.');
if (state.nexusCards) requireRule(!nativeTerror && !state.homeworlds &&
!state.discoveryEnabled && !state.discoveries,
'Semuta Nexus retains original E1/E2 rosters without Homeworlds, Discovery or E3.');
const g = skills ? initializeLeaderSkillsGameForAudit(state)
: state.nexusCards ? state.expansions.length === 1 && !!byFaction(state, 'choam')
? initializePairedNexusGameForAudit(state) : initializeNexusGameForAudit(state)
: state.strongholdCards ? initializeStrongholdFactionsGameForAudit(state)
: initializeFactionExpansionsGameForAudit(state);
g.semutaPreview = true;
+3 -1
View File
@@ -41,7 +41,9 @@ function nexusLeaderSkillModulesSupported(game: LeaderSkillProfile, assassinatio
(!game.discoveries && !game.discoveryEnabled && !game.discoveryStash && !game.greatMaker)) &&
(!game.techTokens || game.players.length >= 3) &&
(!game.strongholdCards || game.advanced === true) &&
!game.ecazTreachery && !game.semutaPreview &&
!game.ecazTreachery && (!game.semutaPreview ||
(semutaLeaderSkillContext(game) && !game.homeworlds &&
!game.discoveryEnabled && !game.discoveries)) &&
(!game.moritaniAssassinatePreview || assassination) &&
!game.advancedPreview && !game.kullPreview && !game.nexusKullPreview &&
!game.guildBetrayalPreview && !game.richeseBetrayalPreview &&
+11 -4
View File
@@ -2,6 +2,13 @@ import { FACTIONS } from './catalog';
import { mixedE3NexusRosterProfile, type FactionModuleProfile } from './faction-module-profile';
import { classicDiscoveryNexusProfile, mixedE1E2DiscoveryNexusProfile, mixedE3DiscoveryNexusProfile, pairedDiscoveryNexusProfile, pairedE3DiscoveryNexusProfile, singleE1E2DiscoveryNexusProfile, standaloneE3DiscoveryNexusProfile } from './discovery-module-profile';
/** Explicit Richese composition only; original predicates still own roster and module legality. */
function semutaNexusContext(game: FactionModuleProfile): boolean {
return game.semutaPreview === true && !game.homeworlds &&
!game.discoveryEnabled && !game.discoveries &&
game.expansions.includes('choam') && game.players.some(p => p.faction === 'richese');
}
/** Original classic Nexus modules, optionally Homeworlds; initializer checks undealt components. */
export function classicNexusModulesProfile(game: FactionModuleProfile): boolean {
if (game.discoveryEnabled || game.discoveries) return classicDiscoveryNexusProfile(game);
@@ -30,12 +37,12 @@ export function classicNexusModulesProfile(game: FactionModuleProfile): boolean
export function pairedNexusModulesProfile(game: FactionModuleProfile): boolean {
if (game.discoveryEnabled || game.discoveries) return pairedDiscoveryNexusProfile(game);
if (typeof game.advanced !== 'boolean' || !game.nexusCards ||
(!game.techTokens && !game.strongholdCards) || game.expansions.length !== 1 ||
(!game.techTokens && !game.strongholdCards && !semutaNexusContext(game)) || game.expansions.length !== 1 ||
game.players.length < 2 || game.players.length > 6 ||
(game.techTokens && game.players.length < 3) ||
(game.strongholdCards && !game.advanced) || game.leaderSkills ||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.ecazTreachery || game.semutaPreview || game.advancedPreview ||
game.ecazTreachery || (game.semutaPreview && !semutaNexusContext(game)) || game.advancedPreview ||
game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview ||
game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview) return false;
@@ -97,7 +104,7 @@ export function singleE1E2NexusProfile(game: FactionModuleProfile): boolean {
(game.techTokens && game.players.length < 3) ||
(game.strongholdCards && !game.advanced) || game.leaderSkills ||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.ecazTreachery || game.semutaPreview || game.advancedPreview ||
game.ecazTreachery || (game.semutaPreview && !semutaNexusContext(game)) || game.advancedPreview ||
game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview ||
game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview) return false;
@@ -125,7 +132,7 @@ export function mixedE1E2NexusProfile(game: FactionModuleProfile): boolean {
(game.techTokens && game.players.length < 3) ||
(game.strongholdCards && !game.advanced) || game.leaderSkills ||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.ecazTreachery || game.semutaPreview || game.advancedPreview ||
game.ecazTreachery || (game.semutaPreview && !semutaNexusContext(game)) || game.advancedPreview ||
game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview ||
game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview) return false;
+5
View File
@@ -1181,6 +1181,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'In the Basic native Moritani entry, winner compulsory/optional card discards may pause before the losing ally’s original retention choice. After that ally chooses one eligible played card or declines, native Karama finishes first; the completed unkept-card batch then offers Semuta. A kept or unrelated hand card is never a target. Multiple fresh cards require commitment and one private selection; taking a winner’s card does not add it to the original retention pool. Advanced Moritani, Nexus retention and unresolved casualty parents remain separate.',
'A printed Karama canceling native Harkonnen capture, CHOAM Auditor or quoted battle income, Ixian casualty substitution, or Basic Moritani allied retention now offers that exact public cost before the original cancellation finishes. Claim or Continue restores the saved response and cancels once. Acquiring the cost card cannot restore a captive, inspect a hand, pay the canceled income, exchange casualties or keep the proposed allied card. Later native card cleanup and markets still finish; Worthless conversion, other cancellations and module combinations remain separate.',
'The explicit fresh Leader Skills entry composes Semuta with original all-fourteen-card native Basic Richese/CHOAM or Advanced E1/E2 setup, retaining original Tech and Advanced Stronghold prerequisites. Actual offers train original living discs; the native Auditor remains ineligible. A completed Hajr discard can resume the same Movement owner before an original private Mentat question. Mandatory loser disposal can resume trained Suk casualty rescue or Rihani’s normal private inspection and optional skilled exchange before winner cleanup. Winning compulsory Artillery stays held through the original skill, then becomes its own fresh Treachery discard. Traitor/Face Dancer movement is never a Semuta target; no rescue, question, inspection or exchange repeats. Basic Ix mixtures, E3, other module/Banker overlays and public starts stay separate.',
'The fresh semuta --nexus-cards entry retains original native E1/E2 Nexus setup, optionally explicit Skills and original Tech/Advanced Strongholds. There is no starting Nexus hand: an actual qualifying closing draw follows a Nexus and settled alliance. CHOAM Secret Ally with CHOAM absent spends a Worthless and its Nexus for two bank spice before the public discard offer; CHOAM Betrayal with CHOAM seated commits one random actual hand-card discard without compensation before its private offer. Claim or Continue retires the original receipt once, without another payment or sample. Separate Nexus cards never become Treachery targets. Homeworlds, Discovery, E3, played-save conversion and public starts stay gated.',
'An ordinary Ghola played during battle also offers its consumed card after revival. Claim or Continue preserves the revived leader or forces and the opposing sealed plan, then normal planning resumes. It cannot revive again by resuming the receipt.',
'A completed paid Nullentropy Box search during battle offers only the used Box. Claim or Continue preserves the two-spice payment, private recovered card, already-shuffled pile and sealed opposing plan. The recovered card may be used in normal legal planning; the search is not repeated.',
'A clean Distrans transfer during battle also offers only the used Distrans. The private card has already reached its recipient; claim or Continue cannot repeat the gift or change a sealed plan. A recipient holding Semuta may claim the used Distrans and keep the already-transferred card.',
@@ -1243,6 +1244,8 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/truthtrance-discard-recovery.test.ts',
'tests/ordinary-card-discard-continuations.test.ts',
'tests/ordinary-card-discard-recovery.test.ts',
'tests/semuta-nexus-trade.test.ts',
'tests/semuta-nexus-betrayal.test.ts',
],
},
],
@@ -4253,6 +4256,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'The Collection trade and post-victory inspection are available only while CHOAM is absent and you are unallied. This works in Basic and Advanced play.',
'During Spice Collection, choose one Worthless card from your own hand. The chosen card and your CHOAM Nexus card are discarded, and you receive two spice from the bank.',
'Finish any current choice, Truthtrance or automatic continuation first. Continuing the phase without trading keeps your cards.',
'In the explicit native Richese Semuta composition, the already-paid public Worthless discard offers claim or Continue before its original trade receipt retires. Taking that card never pays again or makes the spent Nexus a Treachery target.',
'After winning a battle, the private inspection choice may instead spend the Nexus card to reveal one random opposing hand card that was not used in that battle. Only you see the sampled face; if none is eligible, continue without spending the card.',
],
related: ['nexus-cards', 'collection', 'battle'],
@@ -4275,6 +4279,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'Hold the CHOAM Nexus card while CHOAM is seated and holds at least one Treachery Card. Finish other choices or automatic continuations before using Betrayal; the printed panel has no phase restriction.',
'The server chooses uniformly from CHOAM’s held cards, not from a client choice. Discard both the Nexus card and the selected Treachery Card; CHOAM receives no spice.',
'Only the holder sees the Betrayal control. CHOAM’s hand count is shown only during Bidding; no rival card identity is disclosed before the ordinary discard reveal. Outside Bidding an empty-hand attempt rejects without spending the card.',
'In the explicit native Richese Semuta composition, the committed random CHOAM discard offers a neutral reaction with its face hidden until commitment. Claim or Continue completes that original receipt without sampling or compensating again; the CHOAM victim remains the discarder.',
'Basic and Advanced share this bounded effect. Other Nexus effects and complete expansion combinations retain their separate gates.',
],
related: ['nexus-cards', 'choam-modules'],