Compose standalone E3 Nexus Skills and original module mechanics
This commit is contained in:
1 parent
a8c02b4a85
commit
83f825513a
23 files changed
+1926
-43
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, noOtherLeaderSkillModules, strongholdLeaderSkillsProfile, type LeaderSkillProfile } from './leader-skill-profile';
|
||||
import { classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, noOtherLeaderSkillModules, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile, type LeaderSkillProfile } from './leader-skill-profile';
|
||||
export type BureaucratPaymentKind = 'auction' | 'shipment' | 'bribe' | 'revival';
|
||||
export type BureaucratPaymentSource = {
|
||||
event: string;
|
||||
@@ -38,7 +38,7 @@ export function bureaucratPaymentModeSupported(game: LeaderSkillProfile): boolea
|
||||
return (noOtherLeaderSkillModules(game) &&
|
||||
(!game.expansions.length ||
|
||||
(game.expansions.length === 1 && game.expansions[0] === 'choam') ||
|
||||
nativeExpansionLeaderSkillsProfile(game))) || classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) || strongholdLeaderSkillsProfile(game) || classicNexusLeaderSkillsProfile(game);
|
||||
nativeExpansionLeaderSkillsProfile(game))) || classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) || strongholdLeaderSkillsProfile(game) || classicNexusLeaderSkillsProfile(game) || standaloneE3NexusLeaderSkillsProfile(game);
|
||||
}
|
||||
export function bureaucratUsed(
|
||||
used: readonly BureaucratPaymentUse[],
|
||||
|
||||
+43
-9
@@ -1,6 +1,6 @@
|
||||
import { classicNexusModulesProfile, pairedNexusModulesProfile } from './nexus-module-profile';
|
||||
import { nativeFactionTechProfile } from './faction-module-profile';
|
||||
import { advancedMoritaniLeaderSkillsProfile, advancedNativeLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedNexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
|
||||
import { advancedMoritaniLeaderSkillsProfile, advancedNativeLeaderSkillsProfile, classicNexusLeaderSkillsProfile, classicTechLeaderSkillsProfile, nativeExpansionLeaderSkillsProfile, nativeTechLeaderSkillsProfile, ordinaryLeaderSkillModeSupported, pairedNexusLeaderSkillsProfile, standaloneE3NexusLeaderSkillsProfile, strongholdLeaderSkillsProfile } from './leader-skill-profile';
|
||||
import { bribeTimingBlock, maximumBribe, type BribeOptions } from './bribe-options';
|
||||
import { quoteSpicePlacement, stormExposesTerritory, stormSectorAfter, wormConsumesForces } from './disaster-rules';
|
||||
import { isStormCardDistance, type StormCardComponent } from './storm-cards';
|
||||
@@ -7539,8 +7539,9 @@ function e3StrongholdFactionProfile(g: Game): boolean {
|
||||
function moritaniAssassinateModeSupported(g: Game) {
|
||||
return g.advanced && g.expansions.length === 1 && g.expansions[0] === 'ecaz' &&
|
||||
g.players.some(p => p.faction === 'moritani') &&
|
||||
g.players.every(p => ['moritani','atreides','beneGesserit','guild','emperor','fremen'].includes(p.faction)) &&
|
||||
!g.nexusCards && (!g.leaderSkills || advancedMoritaniLeaderSkillsProfile(g)) &&
|
||||
g.players.every(p => p.faction === 'moritani' || (p.faction !== 'harkonnen' && CLASSIC_FACTIONS[p.faction] === true)) &&
|
||||
(!g.nexusCards || (!!g.leaderSkills && standaloneE3NexusLeaderSkillsProfile(g))) &&
|
||||
(!g.leaderSkills || advancedMoritaniLeaderSkillsProfile(g)) &&
|
||||
(!g.strongholdCards || e3StrongholdFactionProfile(g) ||
|
||||
(!!g.leaderSkills && strongholdLeaderSkillsProfile(g))) && !g.homeworlds &&
|
||||
!g.discoveryEnabled && !g.discoveries &&
|
||||
@@ -9267,6 +9268,8 @@ export function initializeFactionExpansionsGameForAudit(state: Game): Game {
|
||||
return initialized;
|
||||
}
|
||||
function ecazOccupyCompositionSupported(g: Game): boolean {
|
||||
if (g.leaderSkills && standaloneE3NexusLeaderSkillsProfile(g) &&
|
||||
g.players.some(p => p.faction === 'ecaz')) return true;
|
||||
return typeof g.advanced === 'boolean' && Array.isArray(g.expansions) &&
|
||||
g.expansions.includes('ecaz') &&
|
||||
g.expansions.every(id => ['ecaz', 'ix', 'choam'].includes(id)) &&
|
||||
@@ -9403,7 +9406,7 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
|
||||
requireRule(!state.leaderSkills, 'Leader Skills cannot redeal existing skill cards.');
|
||||
const g = structuredClone(state);
|
||||
g.leaderSkills = createLeaderSkills(random);
|
||||
const initialized = initializeSetupGameForAudit(g, false, classicNexusLeaderSkillsProfile(g) || pairedNexusLeaderSkillsProfile(g), false, false, true,
|
||||
const initialized = initializeSetupGameForAudit(g, false, classicNexusLeaderSkillsProfile(g) || pairedNexusLeaderSkillsProfile(g) || standaloneE3NexusLeaderSkillsProfile(g), false, false, true,
|
||||
g.expansions.length === 1 && g.expansions[0] === 'choam', nativeExpansionLeaderSkillsProfile(g));
|
||||
if (advancedMoritaniLeaderSkillsProfile(initialized)) initializeMoritaniAssassinateState(initialized);
|
||||
return initialized;
|
||||
@@ -9443,7 +9446,7 @@ function initializeSetupGameForAudit(state: Game, homeworlds: boolean, nexus = f
|
||||
const skillTech = leaderSkills && (classicTechLeaderSkillsProfile(g) || nativeTechLeaderSkillsProfile(g) || skillStronghold);
|
||||
const nativeTech = factions && !leaderSkills && nativeFactionTechProfile(g);
|
||||
const nexusModules = nexus && (classicNexusModulesProfile(g) || pairedNexusModulesProfile(g) ||
|
||||
(leaderSkills && (classicNexusLeaderSkillsProfile(g) || pairedNexusLeaderSkillsProfile(g))));
|
||||
(leaderSkills && (classicNexusLeaderSkillsProfile(g) || pairedNexusLeaderSkillsProfile(g) || standaloneE3NexusLeaderSkillsProfile(g))));
|
||||
requireRule(!g.techTokens || ((skillTech || nativeTech || nexusModules) &&
|
||||
JSON.stringify(g.techTokens) === JSON.stringify(createTechTokens())),
|
||||
'Tech Tokens require a fresh supported three-through-six-seat lobby with unused tokens.');
|
||||
@@ -20470,6 +20473,13 @@ function diplomatRetreatOffer(g: Game, quote: BattleResolutionQuote) {
|
||||
(!skill.faceUp || skill.captured)) || p.noField?.deployed?.location.territory === b.territory) return null;
|
||||
const opponent = getPlayer(g, quote.winner);
|
||||
const forces = combatForces(g, p, b.territory, opponent);
|
||||
const occupy = ecazBattleProfile(g);
|
||||
const combined = occupy?.lead === player && !occupy.canceled;
|
||||
const dial = combined
|
||||
? player === occupy.ecaz ? occupy.fixedEcazDial : plan.dial - occupy.fixedEcazDial
|
||||
: plan.dial;
|
||||
const support = combined && player === occupy.ecaz
|
||||
? g.advanced ? dial : 0 : plan.support;
|
||||
const strength = battleLeaderStrength(leader,
|
||||
controlledLeaders(g, opponent).find(candidate => candidate.id === b.plans[opponent.id].leader));
|
||||
const origins = Object.entries(p.forces).filter(([key, count]) =>
|
||||
@@ -20486,7 +20496,7 @@ function diplomatRetreatOffer(g: Game, quote: BattleResolutionQuote) {
|
||||
const elite = p.elites?.forces[origin] ?? 0;
|
||||
return { normal: sum.normal + count - elite, elite: sum.elite + elite };
|
||||
}, { normal: 0, elite: 0 });
|
||||
return { location: key, choices: diplomatRetreatChoices(forces, plan.dial, plan.support, strength, available) };
|
||||
return { location: key, choices: diplomatRetreatChoices(forces, dial, support, strength, available) };
|
||||
})
|
||||
.filter(option => option.choices.length);
|
||||
return destinations.length ? { player, destinations } : null;
|
||||
@@ -20602,6 +20612,8 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
};
|
||||
const winner = quote.winner ? getPlayer(g, quote.winner) : undefined;
|
||||
const casualtyCommitment = quote.casualties ?? undefined;
|
||||
const fixedSukLoss = winner && quote.sukGraduate
|
||||
? quote.fixedLosses?.find(loss => loss.owner === winner.id) : undefined;
|
||||
const sardaukar = currentNexusSardaukar(g);
|
||||
if (sardaukar?.stage === 'active' && casualtyCommitment && winner?.id === sardaukar.receipt.owner) {
|
||||
sardaukar.casualties = {...structuredClone(casualtyCommitment),outcome:'pending'};
|
||||
@@ -20760,6 +20772,7 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
: { faction: winner!.faction, name: 'Battle' },
|
||||
);
|
||||
for (const loss of quote.fixedLosses ?? []) {
|
||||
if (loss === fixedSukLoss) continue;
|
||||
const owner = getPlayer(g, loss.owner);
|
||||
takeBattleLosses(g, owner, b.territory, { normal: loss.normal, elite: loss.elite, paidNormal: 0, paidElite: 0 });
|
||||
log(g, `${owner.name} sent ${loss.normal + loss.elite} mandatory Occupy forces to the Tanks.`, { faction: 'ecaz', name: 'Occupy casualties' });
|
||||
@@ -20934,12 +20947,19 @@ function resolveBattle(g: Game, retreat?: DiplomatRetreatSelection) {
|
||||
}
|
||||
if (winner && quote.sukGraduate && casualtyCommitment) {
|
||||
requireRule(!g.pendingSukRescue, 'Finish the preceding Suk Graduate rescue.');
|
||||
let commitment: SukRescueReceipt['commitment'];
|
||||
if (fixedSukLoss) {
|
||||
const forces = combatForces(g, winner, b.territory, losingPlayer!);
|
||||
const dial = fixedSukLoss.normal + fixedSukLoss.elite, support = g.advanced ? dial : 0;
|
||||
commitment = { forces, dial, support, options: casualtyOptions(forces, dial, support) };
|
||||
} else commitment = structuredClone(casualtyCommitment);
|
||||
g.pendingSukRescue = {
|
||||
event: g.lastBattleContext.event, turn: g.turn, player: winner.id,
|
||||
territory: b.territory, skill: quote.sukGraduate,
|
||||
commitment: structuredClone(casualtyCommitment), pool: sukForcePool(winner, b.territory),
|
||||
commitment, pool: sukForcePool(winner, b.territory),
|
||||
cards: [...quote.winnerCards], physical: sukPhysicalSignature(winner), losses: null, signature: '',
|
||||
...(winner.faction === 'ixians' ? { eliteOrigins: true as const } : {}),
|
||||
...(fixedSukLoss ? { occupyCasualties: { ...structuredClone(casualtyCommitment), owner: casualtyCommitment.owner! } } : {}),
|
||||
};
|
||||
updateSukReceipt(g);
|
||||
}
|
||||
@@ -21060,6 +21080,18 @@ function settleWinnerCasualties(
|
||||
) {
|
||||
if (g.pendingSukRescue) {
|
||||
const pending = g.pendingSukRescue;
|
||||
if (pending.occupyCasualties) {
|
||||
const allied = pending.occupyCasualties;
|
||||
requireRule(forceOwner === allied.owner && forceOwner !== p.id &&
|
||||
allied.options.some(option => JSON.stringify(option) === JSON.stringify(choice)),
|
||||
'Choose the original allied Occupy casualties before rescuing your own forces.');
|
||||
takeBattleLosses(g, getPlayer(g, forceOwner), to, choice);
|
||||
delete pending.occupyCasualties;
|
||||
updateSukReceipt(g);
|
||||
observeOccupation(g);
|
||||
settleWinnerCasualties(g, p, to, cards, pending.commitment.options[0], automatic);
|
||||
return;
|
||||
}
|
||||
requireRule(pending.player === p.id && pending.territory === to &&
|
||||
JSON.stringify(cards) === JSON.stringify(pending.cards) && !pending.losses &&
|
||||
pending.commitment.options.some((o) => JSON.stringify(o) === JSON.stringify(choice)),
|
||||
@@ -21202,11 +21234,13 @@ function sukRescueIntegrity(g: Game) {
|
||||
} else {
|
||||
const lossDecision = homeworldSavedDecisions(g).find((d) => d.kind === 'battleLosses' && d.player === pending.player);
|
||||
const discard = g.pendingTreacheryDiscard?.continuation;
|
||||
const commitment = pending.occupyCasualties ?? pending.commitment;
|
||||
requireRule(!decisions.length && (lossDecision ? lossDecision.kind === 'battleLosses' &&
|
||||
lossDecision.territory === pending.territory && JSON.stringify(lossDecision.options) === JSON.stringify(pending.commitment.options) &&
|
||||
lossDecision.territory === pending.territory && JSON.stringify(lossDecision.options) === JSON.stringify(commitment.options) &&
|
||||
(!pending.occupyCasualties || lossDecision.forceOwner === pending.occupyCasualties.owner) &&
|
||||
JSON.stringify(lossDecision.cards) === JSON.stringify(pending.cards) :
|
||||
discard?.kind === 'battleResolved' && discard.event === pending.event &&
|
||||
JSON.stringify(discard.casualties) === JSON.stringify(pending.commitment)),
|
||||
JSON.stringify(discard.casualties) === JSON.stringify(commitment)),
|
||||
'The Suk Graduate rescue lost its casualty continuation.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,15 @@ export function noOtherLeaderSkillModules(game: LeaderSkillProfile): boolean {
|
||||
!game.discoveryEnabled && !game.strongholdCards && !game.techTokens && !game.ecazTreachery;
|
||||
}
|
||||
|
||||
function nexusLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean {
|
||||
function nexusLeaderSkillModulesSupported(game: LeaderSkillProfile, assassination = false): boolean {
|
||||
return !!game.nexusCards && typeof game.advanced === 'boolean' &&
|
||||
!!game.players && game.players.length >= 2 && game.players.length <= 6 &&
|
||||
!game.homeworlds && !game.discoveries && !game.discoveryEnabled &&
|
||||
!game.discoveryStash && !game.greatMaker &&
|
||||
(!game.techTokens || game.players.length >= 3) &&
|
||||
(!game.strongholdCards || game.advanced === true) &&
|
||||
!game.ecazTreachery && !game.semutaPreview && !game.moritaniAssassinatePreview &&
|
||||
!game.ecazTreachery && !game.semutaPreview &&
|
||||
(!game.moritaniAssassinatePreview || assassination) &&
|
||||
!game.advancedPreview && !game.kullPreview && !game.nexusKullPreview &&
|
||||
!game.guildBetrayalPreview && !game.richeseBetrayalPreview &&
|
||||
!game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview &&
|
||||
@@ -83,6 +84,30 @@ export function pairedNexusLeaderSkillsProfile(game: LeaderSkillProfile): boolea
|
||||
return hasPrimary && hasSecondary;
|
||||
}
|
||||
|
||||
/** One original E3 native with classics; no shared-Duke assignment or mixed family. */
|
||||
export function standaloneE3NexusLeaderSkillsProfile(game: LeaderSkillProfile): boolean {
|
||||
if (game.expansions.length !== 1 || game.expansions[0] !== 'ecaz' ||
|
||||
!nexusLeaderSkillModulesSupported(game, game.advanced === true)) return false;
|
||||
const players = game.players!;
|
||||
let native: 'ecaz' | 'moritani' | null = null, harkonnen = false;
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
for (let j = 0; j < i; j++) if (players[j].faction === players[i].faction) return false;
|
||||
const player = players[i];
|
||||
if (player.faction === 'ecaz' || player.faction === 'moritani') {
|
||||
if (native !== null) return false;
|
||||
native = player.faction;
|
||||
} else {
|
||||
let classic = false;
|
||||
for (const faction of FACTIONS)
|
||||
if (faction.id === player.faction) { classic = faction.expansion === 'base'; break; }
|
||||
if (!classic) return false;
|
||||
if (player.faction === 'harkonnen') harkonnen = true;
|
||||
}
|
||||
}
|
||||
return native !== null && !(game.advanced && harkonnen) &&
|
||||
(native === 'moritani' || !game.moritaniAssassinatePreview);
|
||||
}
|
||||
|
||||
/** E1/E2 native skills may add Tech and Advanced Strongholds; original roster/decks remain. */
|
||||
function nativeLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean {
|
||||
if (noOtherLeaderSkillModules(game) || pairedNexusLeaderSkillsProfile(game)) return true;
|
||||
@@ -106,7 +131,7 @@ function nativeLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean {
|
||||
|
||||
/** Standalone E3 native predicates retain their own roster, Duke and assassination limits. */
|
||||
function e3LeaderSkillModulesSupported(game: LeaderSkillProfile): boolean {
|
||||
if (noOtherLeaderSkillModules(game)) return true;
|
||||
if (noOtherLeaderSkillModules(game) || standaloneE3NexusLeaderSkillsProfile(game)) return true;
|
||||
const players = game.players;
|
||||
if ((!game.techTokens && !game.strongholdCards) || !players ||
|
||||
players.length < (game.techTokens ? 3 : 2) || players.length > 6 ||
|
||||
@@ -331,5 +356,5 @@ export function ordinaryLeaderSkillModeSupported(game: LeaderSkillProfile): bool
|
||||
(!game.expansions.length || nativeExpansionLeaderSkillsProfile(game))) ||
|
||||
classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) ||
|
||||
strongholdLeaderSkillsProfile(game) || classicNexusLeaderSkillsProfile(game) ||
|
||||
pairedNexusLeaderSkillsProfile(game);
|
||||
pairedNexusLeaderSkillsProfile(game) || standaloneE3NexusLeaderSkillsProfile(game);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { Game } from './engine';
|
||||
import { acquireDuke, DUKE_VIDAL_ID, type DukeSource } from './duke-vidal';
|
||||
import { nexusCardMode } from './nexus-cards';
|
||||
import { nexusCleanPlayBlocked } from './nexus-play-boundary';
|
||||
import { standaloneE3NexusLeaderSkillsProfile } from './leader-skill-profile';
|
||||
|
||||
const SUPPORTED_FACTIONS: Partial<Record<FactionId, true>> = {
|
||||
ecaz: true, moritani: true, atreides: true, harkonnen: true,
|
||||
@@ -64,7 +65,7 @@ export function quoteNexusEcazDuke(g: Game, owner: string, automaticPending = fa
|
||||
if (g.advanced && g.players.some(player => player.faction === 'harkonnen'))
|
||||
blocked = 'Duke Vidal battle use is unavailable with Advanced Harkonnen.';
|
||||
else if (!nexusEcazDukeModeSupported(g) || nexusCardMode('ecaz', holder.faction, roster) !== 'cunning')
|
||||
blocked = 'Ecaz Nexus Cunning currently requires paired Ecaz and Moritani with Nexus alone.';
|
||||
blocked = 'Ecaz Nexus Cunning requires the paired Nexus-only roster or fresh standalone Ecaz Skills/Nexus profile.';
|
||||
else if (holder.ally) blocked = 'Use Ecaz Nexus Cunning while unallied.';
|
||||
else if (g.status !== 'playing' || g.phase !== 6)
|
||||
blocked = 'Use Ecaz Nexus Cunning at the Battle phase boundary.';
|
||||
@@ -86,6 +87,9 @@ export function quoteNexusEcazDuke(g: Game, owner: string, automaticPending = fa
|
||||
}
|
||||
|
||||
export function nexusEcazDukeModeSupported(g: Game): boolean {
|
||||
if (g.leaderSkills && standaloneE3NexusLeaderSkillsProfile(g) &&
|
||||
g.players.some(player => player.faction === 'ecaz') && !g.sandtrout)
|
||||
return !!g.nexusCards?.cards && g.players.length >= 3;
|
||||
return !!g.nexusCards?.cards && g.players.length >= 3 && g.players.length <= 6 &&
|
||||
g.players.some(player => player.faction === 'ecaz') &&
|
||||
g.players.some(player => player.faction === 'moritani') &&
|
||||
|
||||
+4
-1
@@ -4369,6 +4369,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The same fresh local Nexus entry also composes Tech and/or Advanced Strongholds with paired Ixians + Tleilaxu OR CHOAM + Richese and classic opponents, one original family deck and no Skills. Tech needs three through six seats; Strongholds needs two through six Advanced seats. Real closing draws, native typed forces, physical Face Dancers and No-Fields, original payments/rewards and held effects remain. Mixed families, E3, Homeworlds, unrelated previews, public starts and unresolved rulings are not opened.',
|
||||
'The separate fresh local leader-skills --nexus-cards entry combines classic base33/all14 Skills/all12 Nexus in Basic or Advanced, two through six seats. Selected original Tech Tokens require three or more seats; Stronghold Cards require Advanced. Original starting hands/offers, closing draws, first-Storm Tech and end-Mentat held custody remain. Extra/ordinary free-three returns earn original phase-end Axlotl without changing skill custody. Emperor Cunning composes skill posture, support and Suk rescue; Arrakeen reduces payer cost, not committed support or rescue eligibility. Winner cleanup precedes mandatory Tech reward. Other overlays and public starts remain guarded.',
|
||||
'The same fresh skill/Nexus entry supports both Ixians + Tleilaxu OR both CHOAM + Richese with classics and one original family deck/all14/all12, Basic/Advanced two through six seats. Selected original Tech requires three or more; Strongholds requires Advanced. Full Suboid Cunning composes typed skill rescue before equal actual Cyborg Tank substitution. Original held subsidy changes debit, not rescue support. Card cleanup and mandatory original-winner Tech precede Face Dance; a matching trainer dies and returns its skill once without another bounty, while held cards and tokens remain with the original winner. Partial Tleilaxu refresh preserves Mentat rights. Original signed Richese pair and CHOAM fuel/industry stay distinct. Mixed families, E3, companions/mixed plans, native free-return accounting and pending rulings stay guarded.',
|
||||
'A separate standalone Ecaz OR Moritani skill/Nexus entry uses exact ecaz33/all14/all12 with classics, Basic/Advanced two through six seats; original Tech requires3+, Strongholds requires Advanced. Actual closing draws, five ordinary training discs, quiet living/unclaimed Duke Cunning and original Advanced assassination/normal-Traitor forfeiture remain. Native support/rescue, held effects, original card cleanup and mandatory winner Tech use existing handlers. Advanced Harkonnen, E3 pairs/mixed families/HW/previews, Duke training/exceptional custody, enhanced Terror relocation/HMS/Grumman and other pending rulings stay guarded.',
|
||||
'Atreides Cunning adds a second, different opposing plan element after the first native answer. Both remain binding. Use it in your own battle before sealing your plan; Karama may cancel the extra attempt while preserving the first disclosure.',
|
||||
'With Atreides absent, Secret Ally inspects one opposing element in your battle. A sealed plan supplies only that selected answer automatically. Otherwise the opponent chooses an answer that permits a legal plan. Other players receive no private answer.',
|
||||
'The No-Field restriction names Atreides, so Cunning cannot inspect that dial. A non-Atreides Secret Ally holder may request it without learning the token denomination or other plan elements.',
|
||||
@@ -4419,7 +4420,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
{ area: 'Player controls', status: 'Partial', detail: 'Private lifecycle, inspections, exchanges, Tleilaxu refresh, Ixian activation, advisor selection, temporary Sardaukar and CHOAM effects, Moritani Betrayal, Ecaz Betrayal and owner-only Secret Ally inquiry, native Duke Cunning, Richese shipment and bounded auction Betrayal, Emperor purchase and Fremen Cunning controls are connected. Remaining effect controls are unfinished.', evidence: ['components/nexus-cards.tsx', 'components/nexus-traitors.tsx', 'components/nexus-tleilaxu.tsx', 'components/nexus-suboids.tsx', 'components/nexus-advisors.tsx', 'components/nexus-sardaukar.tsx', 'components/choam-power-cost.tsx', 'components/moritani-terror.tsx', 'components/nexus-moritani-betrayal.tsx', 'components/nexus-ecaz-betrayal.tsx', 'components/nexus-ecaz-inquiry.tsx', 'components/nexus-ecaz-duke.tsx', 'components/nexus-richese-betrayal.tsx', 'components/game-table.tsx'] },
|
||||
{ area: 'AI', status: 'Partial', detail: 'All four profiles choose legal actions through private offers, including Moritani/Ecaz Betrayal, Ecaz Secret Ally inquiry and native Duke Cunning, Fremen Cunning, Emperor purchase and bounded Richese auction Betrayal. Complete module strategy and calibration remain unfinished.', evidence: ['game/nexus-card-options.ts', 'game/nexus-traitor-options.ts', 'game/nexus-tleilaxu-options.ts', 'game/nexus-suboid-options.ts', 'game/nexus-advisor-options.ts', 'game/nexus-sardaukar-options.ts', 'game/choam-power-options.ts', 'game/nexus-moritani-options.ts', 'game/nexus-moritani-betrayal-options.ts', 'game/nexus-ecaz-betrayal-options.ts', 'game/nexus-ecaz-inquiry-options.ts', 'game/nexus-ecaz-duke-options.ts', 'game/nexus-richese-options.ts', 'game/bot-nexus-richese-betrayal.ts', 'game/nexus-guild-cunning-options.ts', 'game/bots.ts'] },
|
||||
{ area: 'Documentation', status: 'Partial', detail: 'Common rules, twelve printed faces and bounded runtime contracts retain explicit unresolved Ecaz Duke capture/Tanks/Ghola and inquiry audience/scope, Fremen nested Cunning, Great Maker, combined modules, fresh advisors, storm and borrowed Voice. Richese auction Betrayal has a separate cost/prepayment/privacy contract and excluded-producer boundaries. Guild Betrayal separately records full funded payment, own pre-funding, donor escrow once, the printed full-Junction override, four native adapters and neutral privacy; ordinary occupation/rounding and general response-priority questions remain unresolved.', evidence: ['docs/NEXUS_CARD_RULES.md', 'docs/NEXUS_ATREIDES_RULES.md', 'docs/NEXUS_HARKONNEN_RULES.md', 'docs/NEXUS_TLEILAXU_RULES.md', 'docs/NEXUS_IXIAN_CUNNING_RULES.md', 'docs/NEXUS_CUNNING_RUNTIME.md', 'docs/NEXUS_BENE_GESSERIT_RULES.md', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'docs/NEXUS_EMPEROR_RULES.md', 'docs/NEXUS_SARDAUKAR_RUNTIME.md', 'docs/NEXUS_FREMEN_RULES.md', 'docs/NEXUS_ECAZ_RULES.md', 'docs/NEXUS_CHOAM_RULES.md', 'docs/NEXUS_CHOAM_RUNTIME.md', 'docs/NEXUS_MORITANI_RULES.md', 'docs/NEXUS_MORITANI_RUNTIME.md', 'docs/NEXUS_RICHESE_RUNTIME.md', 'docs/NEXUS_RICHESE_RULES.md', 'docs/NEXUS_GUILD_CUNNING_RUNTIME.md', 'docs/NEXUS_GUILD_RULES.md'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Physical custody, commitments, controls, legal bots, Basic/Advanced and in-memory SQLite recovery regressions include native Ecaz Duke override and private Secret Ally inquiry. A read-only local browser fixture showed the owner-only Duke choice and no rival offer; no room was written. Bounded Richese auction Betrayal additionally has local CLI/runtime, authenticated SQLite and refreshed phone Use/Pass evidence in owned disposable rooms. New Guild Betrayal verification is separately Planned pending frozen direct evidence; these earlier results do not certify it. Fresh classic and paired E1/E2 Tech/Stronghold compositions have bounded runtime cases, genuine games and original CLI/phone evidence. Classic all14 Skills additionally has actual free-return/purchase/exchange and Cunning/Suk lifecycle programs, original phone controls and genuine2..6-seat games. Full module games, exceptional Duke custody, combined interactions and deployed acceptance remain separate.', evidence: ['tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-controls.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts'] },
|
||||
{ area: 'Verification', status: 'Partial', detail: 'Physical custody, commitments, controls, legal bots, Basic/Advanced and in-memory SQLite recovery regressions include native Ecaz Duke override and private Secret Ally inquiry. A read-only local browser fixture showed the owner-only Duke choice and no rival offer; no room was written. Bounded Richese auction Betrayal additionally has local CLI/runtime, authenticated SQLite and refreshed phone Use/Pass evidence in owned disposable rooms. New Guild Betrayal verification is separately Planned pending frozen direct evidence; these earlier results do not certify it. Fresh classic and paired E1/E2 Tech/Stronghold compositions have bounded runtime cases, genuine games and original CLI/phone evidence. Classic all14 Skills additionally has actual free-return/purchase/exchange and Cunning/Suk lifecycle programs, original phone controls and genuine2..6-seat games. Full module games, exceptional Duke custody, combined interactions and deployed acceptance remain separate.', evidence: ['tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-controls.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts', 'tests/ecaz-nexus-skills-modules-runtime.test.ts', 'tests/moritani-nexus-skills-modules-runtime.test.ts'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -4608,6 +4609,8 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Fresh Advanced classic or supported native two-through-six-seat skill tables may retain six original Stronghold Cards, with Tech at three or more seats. Original end-Mentat/local held effects remain. Arrakeen changes actual payer cost, not rescue or Banker eligibility; native CHOAM income retains bank support. Physical defenses, normal disciplines, holder ties and original dial/Worthless earnings remain. Standalone Ecaz Duke and Moritani assassination compose original rules; mixed E3, allied Occupy skills, copied Diplomat defenses, active skill/Stone and other guards stay separate.',
|
||||
'Fresh classic Leader Skills may explicitly add Nexus through local leader-skills --nexus-cards, Basic/Advanced two through six players, retaining selected original Tech at three or more seats and/or Advanced Strongholds. Saved hands/offers remain authoritative; actual closing draws remain required. Free-return ledgers, bank purchase and Traitor Deck declarations stay distinct from training. Original phase-end industry, held payer support/physical defenses, physical Suk rescue and subsequent mandatory winner Tech rewards retain their own timing. Homeworlds, native factions with these combined components, Banker/Mentat previews, borrowed Smuggler arithmetic and unresolved interactions remain separate.',
|
||||
'Both original paired Ixian/Tleilaxu or CHOAM/Richese factions plus classics may add Nexus through that fresh local skill entry, with one family deck/all14/all12, Basic/Advanced two through six players. Selected original Tech requires3+; Strongholds requires Advanced. Full unsupported Suboids retain Cyborg support and skill posture; physical Suk rescue precedes equal actual Cyborg Tank substitution. Original winner cleanup and mandatory Tech precede Face Dance, which returns a matching killed trainer skill once without another bounty. Held custody stays with the original winner. Richese Cunning retains its signed two-marker/one-invoice operation and native phase-end industry; ordinary skill proofs and companion exclusions stay separate. CHOAM fuel precedes ordinary Planetologist movement without a second card effect or artificial combat Special.',
|
||||
'Standalone Ecaz OR Moritani with classics can add Nexus through the original fresh skill entry, exact ecaz33/all14/all12, Basic/Advanced2–6, optionally preserving original Tech3+ and/or Advanced Strongholds2+. Ordinary assignment discs and native skill bands remain. Ecaz spends actual Cunning before Battle to acquire the living/unclaimed separate Duke for the turn; normal discipline may apply, never a Duke assignment. Moritani retains Advanced skill-first assassination and normal-Traitor forfeiture, printed post-loss bounty and one private actual Mentat replacement, while Basic keeps legal Terror without gaining that Advanced power. Supply-only Cunning placement stays distinct from ordinary relocation; its enhanced relocation/HMS/Grumman questions remain pending. No public or saved-game conversion.',
|
||||
'In this standalone Ecaz Nexus/Skills profile, mandatory Advanced Occupy keeps the chosen faction’s own leaders, cards, payment and battle skill. Training is not lent between allies. Ecaz-led Suk rescues its own fixed casualties after the ally’s variable losses; Diplomat retreats the selected faction’s undialed own counters, not a remainder computed from the combined dial. Native cancellation restores that selected lead’s ordinary own pool. These owner-labelled compositions are explicit implementation inferences, not a new combined-skill FAQ.',
|
||||
'With this optional module, starting Treachery Cards are dealt after prediction and before skill assignment. Every faction privately receives two skills, keeps one, returns the other to the shuffled deck, and assigns its chosen card to an eligible leader. Skill and leader become public before traitor selection.',
|
||||
'Before Battle Plans, leave the skilled leader face up to keep its normal effect, or move it behind the shield for possible use. A face-up leader cannot be selected for the plan. Concealing it while choosing another leader gives neither effect. A leader that must be used because no alternative remains moves behind the shield automatically.',
|
||||
'Warmaster, Master of Assassins, Swordmaster of Ginaz, Killer Medic and Prana-Bindu Adept are connected. They give another leader +1 for the matching physical card role, or the surviving assigned leader +3. These bonuses do not stack to +4. They change battle strength, not the printed disc value or leader bounty.',
|
||||
|
||||
@@ -18,6 +18,8 @@ export type SukRescueReceipt = {
|
||||
territory: string;
|
||||
skill: SukGraduateSkill;
|
||||
commitment: { forces: CombatForces; dial: number; support: number; options: Casualties[] };
|
||||
/** Ecaz-led Occupy settles the ally's variable losses before this own-force rescue. */
|
||||
occupyCasualties?: { owner: string; forces: CombatForces; dial: number; support: number; options: Casualties[] };
|
||||
pool: SukForceGroup[];
|
||||
cards: string[];
|
||||
physical: string;
|
||||
|
||||
Reference in new issue
Block a user