From cb4c32fc879d669b49ff39cb3969a56a581b5cb8 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Mon, 12 Oct 2026 23:29:37 +1100 Subject: [PATCH] React to ordinary used cards in the Homeworld Semuta envelope --- components/game-table.tsx | 7 +- docs/CURRENT_STATUS.md | 15 ++++ docs/HOMEWORLD_TUPILE_INTELLIGENCE_RUNTIME.md | 4 + docs/IMPLEMENTATION_STATUS.md | 24 ++++++ docs/SEMUTA_SOURCE_UPDATE_20260907.md | 10 +++ game/engine.ts | 3 +- game/reference.ts | 3 +- tests/fixture-semuta-homeworld-ordinary.ts | 56 ++++++++++++ tests/semuta-homeworld-ordinary.test.ts | 85 +++++++++++++++++++ tools/start-prototype.ts | 2 +- 10 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 tests/fixture-semuta-homeworld-ordinary.ts create mode 100644 tests/semuta-homeworld-ordinary.test.ts diff --git a/components/game-table.tsx b/components/game-table.tsx index fb7962d0..2d3dc4e1 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -1166,9 +1166,10 @@ export function GameTable({ <> Semuta Drug with Homeworlds development preview · original Richese, CHOAM and classic occupation setup, with no other optional modules. - Paid high-Kaitain closing discards, occupied Tupile lost-slot cleanup - and printed Karama canceling native Guild, Emperor auction or Richese - purchase income offer Semuta here. Private discarded faces remain + Paid high-Kaitain closing discards, occupied Tupile lost-slot cleanup, + an ordinary used special card played in this profile and printed + Karama canceling native Guild, Emperor auction or Richese purchase + income offer Semuta here. Private discarded faces remain hidden until commitment; a simultaneous group requires one selection. Claim or Continue keeps the paid price or lost slot and resumes the same original source once. Canceling native income does not cancel diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 22d4d01e..571d374b 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -29,6 +29,21 @@ optional skill bands, combinations and public expansion starts remain open. Rules implementation continues; comprehensive assurance and administration remain deferred until all rules are implemented. +**Semuta / Homeworld ordinary-card reaction — 11 October.** +The Homeworld Semuta envelope admitted only three producers, so a used +ordinary special card played there produced no reaction even though the +printed card has no module restriction. `semutaOfferSupported` now also +admits `ordinaryCardDiscard` in that roster; its existing signature already +binds Homeworld custody, occupation history and percentage ledger, so no new +source identity or payment rule was added. A genuine original Homeworld +occupation state at an active Movement owner plays Hajr, commits its +movement grant, then offers the used card. Claim recovers it, pass leaves it +discarded; wallets, custody, occupation history, percentage ledger and the +Hajr grant are unchanged and original Movement still finishes. One before +program had no offer; four claim/pass and all four legal bot profiles pass; +seven consumer cases and the frozen six-file union pass with types/lint +clean. Other Homeworld producers remain guarded. + **Tleilaxu Ambassador first-death Kwisatz Haderach — 11 October.** The source-supported separate KH identity (base p17) now appears in the private native or Bene Gesserit-copied Tleilaxu grant. It is not appended to diff --git a/docs/HOMEWORLD_TUPILE_INTELLIGENCE_RUNTIME.md b/docs/HOMEWORLD_TUPILE_INTELLIGENCE_RUNTIME.md index 5c66f0b3..d8812d24 100644 --- a/docs/HOMEWORLD_TUPILE_INTELLIGENCE_RUNTIME.md +++ b/docs/HOMEWORLD_TUPILE_INTELLIGENCE_RUNTIME.md @@ -7,6 +7,10 @@ Runtime checkpoint, 10 September 2026. Native CHOAM can now request and retain a The separate fresh `semuta --homeworlds` entry reuses original occupation setup for native Richese/CHOAM and classics, exact CHOAM, with no other optional modules. It does not convert played profiles or widen the Basic epoch ruling. +Besides the three dedicated producers, an ordinary used special card played in +this roster also offers Semuta: the used card is the same public fresh batch, +claim recovers it and pass leaves it discarded, and the card's own committed +effect, wallets, custody and occupation history are unchanged. Actual selected original cleanup cards are privately discarded first. The typed receipt retains only that cleanup's event, original held-ID commitment diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 2582c351..22493d67 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,29 @@ # Dune implementation status +## 11 October 2026 — Homeworld ordinary-card Semuta reaction + +The Homeworld Semuta envelope admitted only paid Kaitain disposal, Tupile +lost-slot cleanup and printed Karama canceling income, so a used ordinary +special card played in that roster produced no reaction. The printed Semuta +card carries no module restriction; the exclusion was an implementation +boundary of the new envelope. + +`semutaOfferSupported` now also admits `ordinaryCardDiscard` when the +Homeworld roster matches. That continuation's existing signature already +binds Homeworld custody, occupation history and the percentage ledger, so no +new source identity, price or economic rule was introduced. Ordinary resume +already restores the saved response/decision/pending-Karama/opening. + +A genuine original Homeworld occupation state at an active Movement owner +plays Hajr: its movement grant commits, the used card becomes the fresh +batch, and Semuta is offered. Claim recovers the exact used card; pass leaves +it discarded. Wallets, Homeworld custody, occupation history, percentage +ledger, the Hajr grant and the remaining Movement are unchanged; a second +play and a repeated commit are rejected. The before program produced no +offer; four claim/pass and all four legal bot profiles pass. Seven consumer +cases and the frozen six-file union pass with types/lint. Other Homeworld +producers, Nexus/Skills/module mixtures and public starts remain guarded. + ## 11 October 2026 — source-supported first-death KH Ambassador return The Tleilaxu Ambassador's own-leader alternative excluded Atreides' diff --git a/docs/SEMUTA_SOURCE_UPDATE_20260907.md b/docs/SEMUTA_SOURCE_UPDATE_20260907.md index b00d957c..aef527c7 100644 --- a/docs/SEMUTA_SOURCE_UPDATE_20260907.md +++ b/docs/SEMUTA_SOURCE_UPDATE_20260907.md @@ -855,6 +855,16 @@ consumer cases pass on that genuine setup. Later held cards/Tanks use conserved transfers, not replacement decks or minted forces. No new ruling, module entry, public gate or complete-mode certification follows. +**11 October 2026 — Homeworld ordinary-card reaction.** The bounded Homeworld +entry now also offers Semuta for an ordinary used special card played in its +roster, not only paid Kaitain disposal, Tupile cleanup and printed Karama +canceling income. The used card is the same public fresh batch; claim +recovers it and pass leaves it discarded, with Homeworld custody, occupation +history, the percentage ledger, wallets and the card's own committed effect +unchanged. One before program lacked the offer; four claim/pass and all four +legal bots pass. Other Homeworld producers remain guarded, not prohibited by +a newly found printed rule. + **11 October 2026 — native paid-income cancellation costs.** Printed Karama canceling original Guild shipment income, non-Ambassador Emperor auction income or Advanced Richese special-purchase income offers that public diff --git a/game/engine.ts b/game/engine.ts index e8c206c0..ac366872 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -6947,7 +6947,8 @@ function semutaOfferSupported( continuation.promise.kind === 'battle'))) return false; if (g.homeworlds && continuation.kind !== 'kaitainDiscard' && continuation.kind !== 'tupileCleanupDiscard' && - continuation.kind !== 'karamaIncomeDiscard') return false; + continuation.kind !== 'karamaIncomeDiscard' && + continuation.kind !== 'ordinaryCardDiscard') return false; if (continuation.kind === 'battleResolved' && !g.lastBattleContext?.mandatoryDiscard) return false; if ((continuation.kind === 'battleCleanup' || diff --git a/game/reference.ts b/game/reference.ts index 889057e6..850c28e6 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -1186,7 +1186,7 @@ export const RULE_TOPICS: RuleTopic[] = [ 'A distinct printed Karama canceling those five ordinary native CHOAM printed or Cunning powers also offers its public cost before the original denial completes. CHOAM retains the declared fuel; a Cunning Nexus remains spent. Claim or Continue restores the same saved response and one original movement, free-revival, storm or closing-Mentat suffix. Acquiring Karama cannot return the canceled force, grant range, stop the original request, protect the storm or restore Nexus. The retained CHOAM fuel is not a discard candidate; Kull and unsupported arrival/flight parents remain separate.', 'A printed Karama canceling the Emperor’s shared gift or extra allied revival also offers that exact public cost before the denial completes. The canceled share moves no spice, forces or revival; the Emperor’s ally and extra-revival ledger are unchanged. Claim or Continue restores the saved response and finishes the cancellation once. Only the actual Semuta holder sees the private claimant choice; other seats see the neutral offer.', 'A printed Karama canceling original paid Guild shipment income, non-Ambassador Emperor auction income or Richese special-purchase income also offers its exact public cost before the native denial. The price and physical transaction have already committed: claim or Continue cannot refund spice, re-ship forces, undo cache acquisition or replay a purchase. Original Movement, next-lot inspection or the Richese purchase’s suspended controls resume once. A winner may spend the very Karama it just bought; claiming that identity does not reverse its payment. Homeworld and Ambassador payment compositions remain separate.', - 'The separate fresh semuta --homeworlds entry retains original Richese/CHOAM and classic Homeworld occupation setup with exact CHOAM and no other modules. Paid high-Kaitain disposal, occupied Tupile lost-slot cleanup and printed Karama canceling original Guild/Emperor auction/Richese-purchase income offer Semuta in this envelope. Kaitain pays two spice per card; its private group requires commitment before one selection. Each lost Tupile holder has a separate private cost before the next cleanup or saved opening. Native income cancellation retains the full original price and acquisition/forces but does not cancel the printed occupied portion; its original receiver still allocates it once before Movement, the next lot or saved purchase controls. Low-native rounding, paid invoice and occupation source remain original. Basic unresolved epochs, other Homeworld producers, module mixtures and public starts stay guarded.', + 'The separate fresh semuta --homeworlds entry retains original Richese/CHOAM and classic Homeworld occupation setup with exact CHOAM and no other modules. An ordinary used special card played in this profile, paid high-Kaitain disposal, occupied Tupile lost-slot cleanup and printed Karama canceling original Guild/Emperor auction/Richese-purchase income offer Semuta in this envelope. Kaitain pays two spice per card; its private group requires commitment before one selection. Each lost Tupile holder has a separate private cost before the next cleanup or saved opening. Native income cancellation retains the full original price and acquisition/forces but does not cancel the printed occupied portion; its original receiver still allocates it once before Movement, the next lot or saved purchase controls. Low-native rounding, paid invoice and occupation source remain original. Basic unresolved epochs, other Homeworld producers, module mixtures and public starts stay guarded.', '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.', @@ -1261,6 +1261,7 @@ export const RULE_TOPICS: RuleTopic[] = [ 'tests/semuta-tupile.test.ts', 'tests/semuta-homeworld-guild-income.test.ts', 'tests/semuta-homeworld-emperor-income.test.ts', + 'tests/semuta-homeworld-ordinary.test.ts', ], }, ], diff --git a/tests/fixture-semuta-homeworld-ordinary.ts b/tests/fixture-semuta-homeworld-ordinary.ts new file mode 100644 index 00000000..7f7bad45 --- /dev/null +++ b/tests/fixture-semuta-homeworld-ordinary.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict'; +import { + applyAction, type Action, type Game, +} from '../game/engine'; +import { SEMUTA_DRUG_ID } from '../game/semuta-drug'; +import { createSemutaHomeworldGuildIncomeFixture } from './fixture-semuta-homeworld-guild-income'; +import { + moveSemutaEmperorCard, semutaEmperorCardId, semutaEmperorPlayer, +} from './fixture-semuta-emperor-share'; + +export type SemutaHomeworldOrdinaryFixture = { + game: Game; source: Game; holder: string; owner: string; semuta: string; + hajr: string; action: Action; +}; + +/** Genuine original Homeworld occupation state already at an active Movement + * owner. Only conserved later physical cards isolate this ordinary play; the + * occupation epoch, custody, wallets and clocks stay native. */ +export function createSemutaHomeworldOrdinaryFixture(): SemutaHomeworldOrdinaryFixture { + const income = createSemutaHomeworldGuildIncomeFixture({ advanced: false }); + const game = income.game; + assert.equal(game.phase, 5); + assert.equal(game.active, 'a'); + const holder = income.holder, owner = income.canceller; + assert.equal(semutaEmperorPlayer(game, holder).hand.some(card => card.id === SEMUTA_DRUG_ID), true); + const hajr = moveSemutaEmperorCard(game, + semutaEmperorCardId(game, card => card.effect === 'hajr'), 'a'); + assert.ok(semutaEmperorPlayer(game, 'a').hand.some(card => card.id === hajr)); + return { game, source: income.game, holder, owner, semuta: SEMUTA_DRUG_ID, hajr, + action: { type: 'card', card: hajr } }; +} + +export function reactSemutaHomeworldOrdinary( + state: Game, fixture: SemutaHomeworldOrdinaryFixture, claim: boolean, +): Game { + const event = state.pendingTreacheryDiscard!.batch.event; + if (claim) return applyAction(state, fixture.holder, { type: 'semutaCommit', event }); + let game = state; + for (const player of state.players) + game = applyAction(game, player.id, { type: 'semutaPass', event }); + return game; +} + +/** Homeworld authority that an ordinary card play must not disturb. */ +export function semutaHomeworldOrdinaryState(game: Game) { + return { + homeworlds: game.homeworlds, + history: game.homeworldOccupationHistory, + percentages: game.homeworldOccupiedPercentageLedger, + players: game.players.map(player => ({ + id: player.id, faction: player.faction, ally: player.ally, spice: player.spice, + reserves: player.reserves, tanks: player.tanks, forces: player.forces, + elites: player.elites, shipped: player.shipped, moved: player.moved, + })), + }; +} diff --git a/tests/semuta-homeworld-ordinary.test.ts b/tests/semuta-homeworld-ordinary.test.ts new file mode 100644 index 00000000..286267e4 --- /dev/null +++ b/tests/semuta-homeworld-ordinary.test.ts @@ -0,0 +1,85 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { applyAction, viewGame } from '../game/engine'; +import { botActions } from '../game/bots'; +import { SEMUTA_DRUG_ID } from '../game/semuta-drug'; +import { semutaEmperorPlayer } from './fixture-semuta-emperor-share'; +import { + createSemutaHomeworldOrdinaryFixture, reactSemutaHomeworldOrdinary, + semutaHomeworldOrdinaryState, +} from './fixture-semuta-homeworld-ordinary'; + +for (const claim of [false, true]) void test( + `Homeworld ordinary special play Semuta ${claim ? 'claims' : 'passes'} the used card without disturbing occupation`, () => { + const f = createSemutaHomeworldOrdinaryFixture(); + assert.equal(f.game.phase, 5); + assert.equal(f.game.active, 'a'); + assert.ok(semutaEmperorPlayer(f.game, f.holder).hand.some(card => card.id === SEMUTA_DRUG_ID)); + const played = applyAction(f.game, 'a', f.action); + const pending = played.pendingTreacheryDiscard; + assert.ok(pending, 'The used ordinary special must offer its printed Semuta cost'); + assert.equal(pending.batch.cause, 'ordinary:hajr'); + assert.deepEqual(pending.batch.entries.map(entry => ({ + id: entry.card.id, by: entry.discardedBy, face: entry.publicFace, + })), [{ id: f.hajr, by: 'a', face: true }]); + assert.equal(pending.reaction?.stage, 'offer'); + assert.deepEqual(played.hajr, ['a'], 'The Hajr movement grant already committed'); + assert.equal(played.discard.filter(card => card.id === f.hajr).length, 1); + for (const player of played.players) viewGame(played, player.id); + const before = semutaHomeworldOrdinaryState(played); + const event = pending.batch.event; + assert.throws(() => applyAction(played, f.owner, { type: 'semutaCommit', event })); + assert.throws(() => applyAction(played, f.holder, { type: 'semutaSelect', event, card: f.hajr })); + const done = reactSemutaHomeworldOrdinary(played, f, claim); + assert.equal(done.pendingTreacheryDiscard, null); + assert.equal(done.resolvedTreacheryDiscardSequence, pending.sequence); + const holder = semutaEmperorPlayer(done, f.holder); + assert.equal(holder.hand.some(card => card.id === f.hajr), claim); + assert.equal(holder.hand.some(card => card.id === SEMUTA_DRUG_ID), !claim); + assert.equal(done.discard.filter(card => card.id === f.hajr).length, Number(!claim)); + assert.equal(done.discard.filter(card => card.id === SEMUTA_DRUG_ID).length, Number(claim)); + assert.deepEqual(semutaHomeworldOrdinaryState(done), before, + 'Claiming the used card cannot move wallets, custody or occupation authority'); + assert.deepEqual(done.hajr, ['a']); + assert.throws(() => applyAction(done, 'a', f.action), 'The used card cannot be played again'); + assert.throws(() => applyAction(done, f.holder, { type: 'semutaCommit', event })); + const suffix = applyAction(done, 'a', { type: 'endMovement' }); + assert.ok(!suffix.movementRemaining?.includes('a')); + assert.equal(suffix.phase, 5); + assert.deepEqual(semutaHomeworldOrdinaryState(suffix), before); + }, +); + +void test('a full-hand Homeworld Richese exchanges Semuta for the used ordinary card', () => { + const f = createSemutaHomeworldOrdinaryFixture(); + const holder = semutaEmperorPlayer(f.game, f.holder); + holder.hand.push(...f.game.deck.splice(0, 4 - holder.hand.length)); + assert.equal(holder.hand.length, 4); + const played = applyAction(f.game, 'a', f.action); + const offer = viewGame(played, f.holder).semutaReaction!; + assert.equal(offer.canCommit, true); + assert.deepEqual(offer.candidates, []); + const done = applyAction(played, f.holder, { type: 'semutaCommit', event: played.pendingTreacheryDiscard!.batch.event }); + assert.equal(semutaEmperorPlayer(done, f.holder).hand.length, 4); + assert.ok(semutaEmperorPlayer(done, f.holder).hand.some(card => card.id === f.hajr)); +}); + +for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) void test( + `the ${difficulty} bot settles the Homeworld ordinary-card Semuta cost`, () => { + const f = createSemutaHomeworldOrdinaryFixture(); + const played = applyAction(f.game, 'a', f.action); + const view = viewGame(played, f.holder); + view.players.find(player => player.id === f.holder)!.bot = difficulty; + const action = botActions(view)[0]; + assert.ok(action); + const done = applyAction(played, f.holder, action); + assert.equal(done.pendingTreacheryDiscard, null); + const holder = done.players.find(player => player.id === f.holder)!; + const claimed = holder.hand.some(card => card.id === f.hajr); + assert.equal(claimed || done.discard.some(card => card.id === f.hajr), true, + 'A legal bot either acquires the used card or leaves it discarded'); + assert.equal(holder.hand.some(card => card.id === SEMUTA_DRUG_ID), !claimed); + assert.equal(done.hajr.includes('a'), true); + assert.equal(holder.spice, f.game.players.find(player => player.id === f.holder)!.spice); + }, +); diff --git a/tools/start-prototype.ts b/tools/start-prototype.ts index 7a43c856..aaed13b7 100644 --- a/tools/start-prototype.ts +++ b/tools/start-prototype.ts @@ -30,7 +30,7 @@ async function main() { '\nSemuta with CHOAM and optional Ix preserves original unused Tech Tokens in fresh 3–6-seat Basic/Advanced Richese lobbies under existing native prerequisites. Advanced additionally retains original unused Stronghold Cards at2–6; both modules require3–6. Native setup, first-Storm Tech and final-Mentat Stronghold assignment remain original. Mandatory/winner discards react before the original winner takes one loser-owned token, before Face Dance. Closing Gamont reacts before the original CHOAM finish and changed-board claim. Optional original skills require --leader-skills as described below; other modules, the separate CHOAM/Ecaz Moritani entry, retrofit and public starts remain excluded.', '\n--profile semuta --leader-skills explicitly composes original all14 skills with fresh native Basic Richese/CHOAM and classics using exact CHOAM, or Advanced native E1/E2 with Richese and selected required CHOAM/optional Ix decks, at2–6 seats. Optional original Tech requires3+; Strongholds requires Advanced. Original private offers/training, Auditor exclusion, normal Mentat question, typed Suk rescue and Rihani inspection/exchange remain. Traitor/Face Dancer movement is not a Treachery discard. Basic Ix mixtures without their original Nexus envelope, E3/Moritani, Homeworld/Discovery/Banker overlays, public starts and played-game conversion are not opened.', '\n--profile semuta --nexus-cards retains original all12 Nexus in fresh native Richese CHOAM/optional Ix lobbies, optionally --leader-skills, original Tech3+ and Advanced Strongholds2+. Single/paired CHOAM or both selected E1/E2 native families retain original prerequisites. No starting Nexus hand is added: a qualifying actual end-Spice draw follows a Nexus and settled alliance. CHOAM Secret Ally spends a Worthless for two bank spice only with CHOAM absent; CHOAM Betrayal spends its Nexus to discard one server-sampled native CHOAM hand card without compensation. Semuta claim/pass retires the original receipt once, after the committed fresh discard. No Homeworld, Discovery, E3, retrofit or public gate opens.', - '\n--profile semuta --homeworlds uses original fresh Basic/Advanced Richese with CHOAM and classic factions, exact CHOAM deck, at2–6 seats. Native occupation setup and source history remain original. Paid high-Kaitain disposal, occupied-Tupile lost-slot cleanup and printed Karama canceling original Guild/Emperor auction/Richese-purchase income offer Semuta before their original suffix. Canceling native income does not cancel the printed occupied portion: its original receiver still allocates it once before Movement, the next lot or saved purchase controls. Other Homeworld producers, Nexus, Skills, Tech, Strongholds, Discovery, Ix/E3 mixing, retrofit and public starts remain guarded.', + '\n--profile semuta --homeworlds uses original fresh Basic/Advanced Richese with CHOAM and classic factions, exact CHOAM deck, at2–6 seats. Native occupation setup and source history remain original. Ordinary used special cards, paid high-Kaitain disposal, occupied-Tupile lost-slot cleanup and printed Karama canceling original Guild/Emperor auction/Richese-purchase income offer Semuta before their original suffix. Canceling native income does not cancel the printed occupied portion: its original receiver still allocates it once before Movement, the next lot or saved purchase controls. Other Homeworld producers, Nexus, Skills, Tech, Strongholds, Discovery, Ix/E3 mixing, retrofit and public starts remain guarded.', '\nEcaz-occupy (--profile ecaz-occupy) opts only a fresh ready 2–6-seat lobby with native Ecaz and classic/Ixian/Tleilaxu/CHOAM/Richese opponents (optional native Moritani). Advanced uses distinct selected ecaz/ix/choam decks; Basic uses the exact ecaz deck with classic/optional Moritani opponents. Ordinary33/47/35 cards retain real original offers/HMS/Auditor/Face Dancers and Richese cache. Explicit --ecaz-treachery independently adds the three physical variant cards, making36/50/38 (Advanced only). Basic combined battles require an even Ecaz force count; the preserved publisher odd-force casualty-rounding conflict rejects before any lead choice. Native Richese ordinary and marker-only pools compose with selected lead plans; original own mixed No-Field dialing, whole-plan disclosure and optional overlays remain guarded.' + '\nLeader-skills retains existing classic and Basic native entries and adds native Basic Richese/CHOAM. Advanced Ixians, Tleilaxu, CHOAM and/or Richese plus classic rosters use distinct required family decks and all fourteen skills, with original cache, Face Dancers, Auditor exclusion and typed aftermath. Foreign revival grants no new skill. Separate non-Harkonnen Advanced Moritani uses the ecaz deck and original skill-first assassination preview. Native Ecaz supports its five ordinary discs, original Ambassador/Duke custody and ecaz deck with classic opponents in Basic/Advanced; Advanced Harkonnen retains its existing Duke capture exclusion. Normal Mentat questions use a uniform private card-show step in supported Leader Skills games. Banker-income additionally supports native Richese and Ecaz, never ordinary seller/allowed Emperor income or a bank grant as a payer cost. Basic auction recipients remain unchanged. Public activation, exhausted-cache counts, native Ixian Technology on Richese lots, own mixed No-Field, captured replacement, shared-Duke assignment, combined Occupy skills and other pending module/rulings remain guarded.', '\nStronghold-factions additionally admits standalone Advanced Ecaz OR Moritani with classic opponents and the exact ecaz deck, six Stronghold Cards with optional unused Tech Tokens at 3–6 seats. Original setup and actual end-Mentat claims remain. Ecaz holds shared-control cards and only the actual selected holder-plan benefits. Native Moritani retains original assassination with printed bounty and one private Mentat replacement; Harkonnen is excluded there. An E3 pair, E1/E2/E3 mixtures and other modules remain separate.' +