Compose native Ecaz and Moritani Stronghold rule paths

This commit is contained in:
Quintine committed 2026-10-04 01:31:22 +10:00
1 parent 50808aed5e
commit 39e2954ab4
15 files changed
+1010 -91

No files matched your search

+34 -12
View File
@@ -7510,11 +7510,28 @@ function leaderSkillAssignmentUnavailable(
? ADVANCED_ATREIDES_SUK_ASSIGNMENT_BLOCK
: null;
}
/** Public standalone E3 roster; the existing held-card owner remains the plan actor. */
function e3StrongholdFactionProfile(g: Game): boolean {
if (!g.advanced || g.expansions.length !== 1 || g.expansions[0] !== 'ecaz' ||
g.players.length < 2 || g.players.length > 6 ||
g.homeworlds || g.nexusCards || g.leaderSkills || g.techTokens ||
g.discoveryEnabled || g.discoveries || g.discoveryStash || g.greatMaker ||
g.ecazTreachery) return false;
let native: 'ecaz' | 'moritani' | null = null;
for (const player of g.players) {
if (player.faction === 'ecaz' || player.faction === 'moritani') {
if (native !== null) return false;
native = player.faction;
} else if (!CLASSIC_FACTIONS[player.faction]) return false;
}
return native !== null && (native !== 'moritani' || !byFaction(g, 'harkonnen'));
}
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.strongholdCards && !g.homeworlds &&
!g.nexusCards && (!g.leaderSkills || advancedMoritaniLeaderSkillsProfile(g)) &&
(!g.strongholdCards || e3StrongholdFactionProfile(g)) && !g.homeworlds &&
!g.discoveryEnabled && !g.discoveries && !g.techTokens;
}
function moritaniAssassinateContext(g: Game, receipt: MoritaniAssassinateReceipt) {
@@ -9243,7 +9260,8 @@ function ecazOccupyCompositionSupported(g: Game): boolean {
g.players.every(p => faction(p.faction).expansion === 'base' ||
(['ecaz', 'moritani', 'ixians', 'tleilaxu', 'choam', 'richese'].includes(p.faction) &&
g.expansions.includes(faction(p.faction).expansion))) &&
!g.homeworlds && !g.nexusCards && !g.leaderSkills && !g.strongholdCards &&
!g.homeworlds && !g.nexusCards && !g.leaderSkills &&
(!g.strongholdCards || e3StrongholdFactionProfile(g)) &&
!g.techTokens && !g.discoveryEnabled && !g.discoveries &&
!g.discoveryStash && !g.greatMaker &&
(g.ecazTreachery === undefined || g.ecazTreachery === true);
@@ -9258,15 +9276,16 @@ export function initializeEcazOccupyGameForAudit(state: Game): Game {
g.ecazOccupyPreview = true;
return g;
}
/** Fresh Advanced E1/E2 faction composition; normal starts and other modules stay gated. */
/** Fresh native Stronghold composition; other overlays and public starts stay gated. */
export function initializeStrongholdFactionsGameForAudit(state: Game): Game {
requireRule(state.advanced === true && Array.isArray(state.expansions) &&
state.expansions.length >= 1 && state.expansions.length <= 2 &&
state.expansions.every(id => id === 'ix' || id === 'choam') &&
new Set(state.expansions).size === state.expansions.length &&
state.players.some(p => faction(p.faction).expansion !== 'base') &&
state.players.every(p => faction(p.faction).expansion === 'base' || state.expansions.includes(faction(p.faction).expansion)),
'Stronghold factions require Advanced Ixian/Tleilaxu or CHOAM/Richese factions, classic opponents and their selected distinct decks.');
requireRule(e3StrongholdFactionProfile(state) ||
(state.advanced === true && Array.isArray(state.expansions) &&
state.expansions.length >= 1 && state.expansions.length <= 2 &&
state.expansions.every(id => id === 'ix' || id === 'choam') &&
new Set(state.expansions).size === state.expansions.length &&
state.players.some(p => faction(p.faction).expansion !== 'base') &&
state.players.every(p => faction(p.faction).expansion === 'base' || state.expansions.includes(faction(p.faction).expansion))),
'Stronghold factions require supported Advanced native factions, classic opponents and their selected distinct decks.');
requireRule(!state.homeworlds && !state.nexusCards && !state.leaderSkills &&
!state.discoveryEnabled && !state.discoveries && !state.discoveryStash && !state.greatMaker &&
!state.techTokens && !state.ecazTreachery &&
@@ -9274,7 +9293,10 @@ export function initializeStrongholdFactionsGameForAudit(state: Game): Game {
'Stronghold factions admit only unused Stronghold Cards, without other optional modules.');
requireFreshFactionInventory(state);
requireFreshBaseRuntime(state);
return initializeSetupGameForAudit(state, false, false, false, false, false, false, true, false, true);
const initialized = initializeSetupGameForAudit(state, false, false, false, false, false, false, true, false, true);
if (e3StrongholdFactionProfile(initialized) && byFaction(initialized, 'moritani'))
initializeMoritaniAssassinateState(initialized);
return initialized;
}
function requireFreshFactionInventory(state: Game) {
requireRule(state.richeseCache === undefined && state.richeseRemoved === undefined &&
@@ -9415,7 +9437,7 @@ function initializeSetupGameForAudit(state: Game, homeworlds: boolean, nexus = f
),
),
strongholdFactions
? 'The Stronghold faction audit needs a fresh Advanced Ixian/CHOAM lobby without other optional modules.'
? 'The Stronghold faction audit needs a fresh supported Advanced native lobby without other optional modules.'
: factions && nexus
? 'The paired expansion Nexus audit needs a fresh lobby without other optional modules.'
: factions
+7 -5
View File
@@ -3246,23 +3246,25 @@ export const RULE_TOPICS: RuleTopic[] = [
category: 'Advanced & expansions',
coverage: 'Partial',
developmentStage: 'Verified',
summary: 'Fresh Advanced Stronghold play connects native Ixian/Tleilaxu and CHOAM/Richese paired and mixed families through existing rule handlers.',
summary: 'Fresh Advanced Stronghold play connects native E1/E2 families and standalone Ecaz or Moritani through original holder-only rule handlers.',
steps: [
'This private local profile admits selected native Ixian/Tleilaxu and CHOAM/Richese families with classic opponents in unique two-to-six-seat Advanced rosters. At least one expansion faction and its distinct selected ix/choam decks are required, with Stronghold Cards alone. Basic, Ecaz, Moritani and other overlays remain outside it.',
'This private local profile admits selected native Ixian/Tleilaxu and CHOAM/Richese families with classic opponents in unique two-to-six-seat Advanced rosters. Standalone Ecaz OR Moritani with classic opponents additionally uses the exact ecaz deck. Moritani excludes Harkonnen under its original assassination/Duke boundary. An E3 pair, E1/E2/E3 mixtures, Basic and other overlays remain separate; only Stronghold Cards are enabled.',
'CHOAM-only ordinary Treachery has 35 physical cards; Ix or both decks have47, not49, because CHOAM replaces the shared Ix cards. Native Richese’s ten-card cache remains separate. Genuine setup retains original faction offers/leaders/forces and any native Auditor/HMS, with all six Stronghold Cards initially unowned.',
'Card ownership begins only at the real first end of Mentat and is retained through the following turn. It is not assigned by a battle fixture or reset on refresh. Hidden Mobile Stronghold interior forces and its outside pointing territory stay distinct.',
'The mobile card copies an advantage of another currently controlled stronghold, not merely a retained card. A sole choice is automatic; multiple choices use the existing public pre-plan selection. Accepted choices stay bound to the same original battle through private plans and saved continuation.',
'Arrakeen provides at most two spice toward actual support, not extra personal cash. Native CHOAM income uses the original bank and player payment shares, including its established traitor and own-donor exclusions. Original cyborg/suboid and starred casualties, bounty and other native income stay separate.',
'Carthag, Habbanya, Sietch Tabr and Tuek’s Sietch retain their existing defense, tie, opposing-dial and played-Worthless contracts. New Richese coverage uses actual No-Field shipment/reveal and native cache acquisition for Stone Burner; mixed No-Field dials/full-plan and unresolved Stone/Poison Tooth cases are not silently enabled.',
'Shared Ecaz control holds the card at Ecaz; the selected lead gets only its own held advantage. Ecaz-led Arrakeen may bank-fund actual ally-variable support up to two, never fixed free Ecaz strength. Choosing the ally gives no automatically shared card benefit. Original Habbanya tie and Tabr income likewise follow the actual holder-plan.',
'Native Moritani losing Tuek holder still earns two bank spice per played Worthless card. Original post-loss assassination follows support/card income, retains only printed bounty and one actual private Mentat replacement. Winner cleanup and force losses finish once; normal Traitor revelation forfeits assassination for the game. Native Terror placement remains free.',
'Bounded expanded-family evidence includes sixteen rule cases, eleven actual No-Field/Stone/FaceDance program cases, all four legal policies and eighteen unstaged complete paired/mixed2..6 games (11066 accepted actions, no rejections). Human390px Stone kill/Habbanya and two-reserve Face Dance controls complete their native decisions. Save/recovery/privacy/custody assurance follows all rules, not a new prototype gate. Full-card/faction/module/deployed acceptance and public expansion starts remain open.',
],
related: ['stronghold-cards', 'mobile-stronghold', 'choam-combat', 'setup', 'implementation-checklist'],
checklist: [
{ area: 'Implementation', status: 'Implemented', detail: 'Fresh selected E1/E2 setup, 35/47 ordinary decks plus separate native Richese cache, actual end-Mentat ownership and native HMS/No-Field/Stone/FaceDance/CHOAM economy compose existing effects without public-start or old-game changes.', evidence: ['game/engine.ts', 'game/stronghold-cards.ts', 'game/battle-resolution-quote.ts'] },
{ area: 'Player controls', status: 'Implemented', detail: 'Original local390px controls cover HMS copy/support, real cache Stone reveal mode and partial Face Dance physical-source replacement. Native rules retain original winner benefits and turn-end controller changes.', evidence: ['components/stronghold-cards.tsx', 'components/game-table.tsx'] },
{ area: 'Implementation', status: 'Implemented', detail: 'Fresh selected E1/E2 setup and standalone Ecaz/Moritani exact ecaz deck, actual end-Mentat ownership, holder-only Ecaz coalition funding and original Moritani loss/assassination compose existing effects without public-start or old-game changes.', evidence: ['game/engine.ts', 'game/stronghold-cards.ts', 'game/battle-resolution-quote.ts'] },
{ area: 'Player controls', status: 'Implemented', detail: 'Original local390px controls cover HMS copy/support, real cache Stone reveal mode and partial Face Dance replacement. Standalone E3 human controls select the Ecaz holder lead, show bank2/personal1 for actual support3, seal native plans and keep the physical Shield; Moritani reveals printed Master Bewt bounty through its original inspector and choice. Original Collection and Mentat continue after refresh.', evidence: ['components/stronghold-cards.tsx', 'components/game-table.tsx'] },
{ area: 'AI', status: 'Partial', detail: 'Four minimal legal policies consume actual marker/Stone/FaceDance/copy choices; eighteen genuine paired/mixed Advanced2..6 games complete. Strategy/calibration and other combinations remain deferred.', evidence: ['game/bots.ts', 'tools/faction-games.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'Advanced family/deck/cache admission, existing six effects, original winner versus replacement and explicit guarded combinations are recorded without completing every card or faction.', evidence: ['docs/STRONGHOLD_CARDS.md', 'docs/RULE_DECISIONS.md'] },
{ area: 'Verification', status: 'Partial', detail: 'Targeted16 rule cases, native11-case program, eighteen complete games and390px human decisions cover expanded rule paths; prior Ixian/CHOAM proofs stay dated. No new save/privacy/custody or full-suite campaign. Final assurance remains after all rules.', evidence: ['tests/richese-strongholds-runtime.test.ts', 'tests/tleilaxu-strongholds-runtime.test.ts', 'tests/fixture-richese-strongholds.ts', 'tests/fixture-tleilaxu-strongholds.ts'] },
{ area: 'Verification', status: 'Partial', detail: 'Prior E1/E2 evidence remains dated. Standalone E3 adds20 focused cases, affected native/CLI179, types/lint/build and actual controlled holder-only funding, tie/income, assassination/death/forfeiture and four legal policies. Four genuine two/three-seat E3 games complete642 accepted actions,15 JSON continuations and3 battles without rejection; no natural assassination claim. Human390px Ecaz reaches v18/Mentat with bank2/personal1, separate Collection2 and physical Ecaz2/Guild1 survivors; Moritani reaches v11/Mentat with wallet18 and one original private replacement. No new assurance/full-suite campaign or public/deployed certification.', evidence: ['tests/ecaz-stronghold-runtime.test.ts', 'tests/moritani-stronghold-runtime.test.ts', 'tests/richese-strongholds-runtime.test.ts', 'tests/tleilaxu-strongholds-runtime.test.ts'] },
],
},
{