Offer completed Homeworld CHOAM market disposal to Semuta

This commit is contained in:
Quintine committed 2026-10-13 02:39:33 +11:00
1 parent 7004977e75
commit b8a66a864b
10 files changed
+239 -2

No files matched your search

+2
View File
@@ -1169,6 +1169,8 @@ export function GameTable({
Paid high-Kaitain closing discards, occupied Tupile lost-slot cleanup,
paid Nullentropy Box search, clean Distrans transfer, definite
Truthtrance answers and used ordinary special cards offer Semuta.
Completed legal CHOAM market sales also offer their used card;
recovery preserves the already credited two or three spice.
Native printed Kulon, Baliset, La La La, Trip to Gamont and Jubba
costs also pause before their original effect. Printed Karama
canceling these native powers or Guild, Emperor auction or Richese
+17
View File
@@ -29,6 +29,23 @@ 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 / native Homeworld market disposal — 13 October.**
Completed legal CHOAM market sales now offer their used card after the
original physical discard and two/three-spice credit. High Tupile's
Worthless-sale prohibition remains, including duplicate Worthless cards.
Actual shipment10 to Habbanya Sietch17 lowers native Tupile20→10 and pays
ten spice before cashout C0→2; ordinary Shield surplus C2→5 remains legal
at high Tupile, retaining its same-name witness. Four before programs had
no offer; eight after claim/pass programs retain credit, paid forces and
the original closing owner. Fourteen new cases cover both modes, full-hand
exchange, high prohibition and all four minimal legal profiles.
Two actual390px sale/allow/commit flows exchange at4/4 and finish the same
market. Artifact: `/tmp/dune-homeworld-native-sale-3217.json`. Native price
and bank-income classification are unchanged; no full/deployed certification.
Types/lint,236/236 across13 affected unit files and the application build
pass. Build warnings retain deprecation, large chunks and static route
classification; no suppression or public-release gate change.
**Semuta / original Homeworld storm and native counter costs — 13 October.**
The same profile now offers Jubba before protection and printed Karama
canceling a native CHOAM Worthless power before denial. Kull/Nexus and other
@@ -24,6 +24,13 @@ denial: its unused Worthless fuel remains held and is not a candidate.
Canceled Baliset resumes its original move once; no canceled native effect
is applied. Kull, Nexus and other producer/module combinations remain excluded.
Completed legal CHOAM market sales also offer the used card after the
original two/three-spice credit. Recovery keeps the paid amount and duplicate
witness, then restores the same closing market once. High Tupile's
Worthless-sale prohibition, including Worthless duplicates, is unchanged;
actual native shipment can lower population before a legal sale. Normal
duplicates need no low-population exception. No new bank-income ruling follows.
Actual selected original cleanup cards are privately discarded first. The
typed receipt retains only that cleanup's event, original held-ID commitment
and suspended opening; its lease row remains pending during the neutral
+38
View File
@@ -1,5 +1,43 @@
# Dune implementation status
## 13 October 2026 — native Homeworld CHOAM completed-sale discard
The existing Homeworld Semuta gate now admits `choamSaleDiscard`. Native
sale validation, Karama allowance, physical disposal and payment happen
first; only the used card is then offered. No payout, signature or profile
is rewritten. The original CHOAM market resumes once after recovery.
The fixture uses original CHOAM/Richese/Emperor/Atreides Homeworld setup.
For Worthless cashout, a real native shipment of ten counters pays ten spice
and lowers Tupile20→10. The high-population sale prohibition is retained,
including duplicate Worthless sales. A real low-Tupile Baliset sale keeps its
original two-spice credit. A non-Worthless Shield surplus is independently
legal at high Tupile and keeps three-spice credit plus its held witness.
All card positions are conserved; no clocks/wallets/population or ledgers
are assigned. The initial Stunner choice was wrong because it is a projectile;
canonical category inspection corrected the fixture to Baliset without
altering a production rule or the native bank-income interpretation.
Four actual before programs (both modes × both sale kinds) paid/discarded
without Semuta. Eight parent after claim/pass programs retain the credited
amount, native paid counter group and duplicate witness, then consume the
same market close. Fourteen new behavioral cases also cover high-Tupile
rejection before mutation, full-hand exchange and all four minimal profiles.
Two actual390px controls sell/allow/commit through unmodified GameTable.
The low-Tupile sale retains CHOAM0→2, reserves10 and board10; the high-Tupile
Shield sale retains CHOAM2→5/reserves20 and physical Shield witness10 while
Richese recovers sold Shield9. Both exchange at4/4 and restore original
market ownership before native Battle/Spice progression. Evidence outside
Git: `/tmp/dune-homeworld-native-sale-3217.json`. Four initial optimizer
requests aborted; subsequent controls completed. Only own QA service/tab
and root temporary files were removed. No deployed, saved-room, natural
full-game or completed-mode certification follows.
The frozen thirteen-file unit union passes236/236, with types/lint and the
application build clean apart from the recorded module.register deprecation,
large-chunk and Vinext static route-classification warnings. No warning
threshold was changed or suppressed.
## 13 October 2026 — Homeworld Jubba and native Worthless Karama costs
The Homeworld producer gate now admits only the additional existing
+21
View File
@@ -855,6 +855,27 @@ 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.
**13 October 2026 — completed native Homeworld CHOAM market disposal.**
`choamSaleDiscard` is now admitted in the existing Homeworld envelope. The
original sale still commits its physical discard and two/three-spice credit
before the Semuta offer; claim/pass restores the same market, not the sale.
High Tupile still prohibits Worthless sales, including Worthless duplicates.
A real ten-force native shipment into Habbanya Sietch17 lowers Tupile20→10
and pays the original ten-spice fee before the legal Worthless cashout.
Ordinary same-name Shield duplicates remain legal at high Tupile.
Four actual Basic/Advanced before programs paid/discarded without an offer.
Eight after claim/pass programs keep the original credit, paid counter group
and retained duplicate witness. Two actual390px controls sell Baliset for
two at low Tupile and a surplus Shield for three at high Tupile, pass native
Karama and exchange Semuta at4/4. Credit stays C0→2 or C2→5; Shield witness
stays held. The original market closes to Battle or Spice as before.
`/tmp/dune-homeworld-native-sale-3217.json` retains accepted actions/state
outside Git. No bank-income interpretation, profile or public gate changes.
The initial fixture incorrectly selected Stunner (a projectile); canonical
category inspection corrected the sale card to actual Worthless Baliset.
No production rule was changed for that fixture error.
**13 October 2026 — Homeworld Jubba and native Worthless cancellation.**
The same envelope now admits `choamJubbaDiscard` and
`karamaChoamWorthlessDiscard`. The existing native cancellation predicate
+2 -1
View File
@@ -6957,7 +6957,8 @@ function semutaOfferSupported(
continuation.kind !== 'choamKulonDiscard' &&
continuation.kind !== 'choamBalisetDiscard' &&
continuation.kind !== 'choamJubbaDiscard' &&
continuation.kind !== 'karamaChoamWorthlessDiscard') return false;
continuation.kind !== 'karamaChoamWorthlessDiscard' &&
continuation.kind !== 'choamSaleDiscard') return false;
if (continuation.kind === 'battleResolved' &&
!g.lastBattleContext?.mandatoryDiscard) return false;
if ((continuation.kind === 'battleCleanup' ||
+2
View File
@@ -1187,6 +1187,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'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 Box search, clean Distrans transfer, definite Truthtrance answers, used ordinary specials, paid Kaitain disposal, Tupile cleanup and printed Karama canceling native Guild/Emperor/Richese-purchase income offer Semuta. Native printed Kulon, Baliset, La La La, Trip to Gamont and Jubba costs also pause before their original effect: claim/pass adds one movement bonus, denies free allowance without charging/moving forces, stops the uncommitted Baliset move, returns one original board counter before the same CHOAM close, or applies protection before the same recorded storm moves. Printed Karama canceling these powers pauses separately before denial; the retained Worthless fuel is not a candidate. A canceled reactive Baliset resumes its original declared move once; canceled Kulon/La La La/Gamont/Jubba do not apply their effects. A recovered card grants no other faction CHOAM power. Kaitain private groups require commitment before selection; each Tupile holder has a separate cost. Native income denial retains payment and the original occupied-share allocation. Basic unresolved epochs, Kull, other producers and public starts remain guarded.',
'Legal completed CHOAM market sales also offer their used card in the Homeworld profile. Worthless cashout keeps its original two-spice credit; a non-Worthless duplicate keeps its three-spice credit and the original same-name witness stays held. The high-Tupile Worthless-sale prohibition still applies, including duplicate Worthless sales. Claim/pass cannot repeat payout, undo the actual reserve shipment or replace the original market close; no new bank-income interpretation is made.',
'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.',
@@ -1266,6 +1267,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'tests/semuta-homeworld-choam-returns.test.ts',
'tests/semuta-homeworld-choam-storm.test.ts',
'tests/semuta-homeworld-choam-karama.test.ts',
'tests/semuta-homeworld-choam-sale.test.ts',
],
},
],
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict';
import { applyAction, handLimit, viewGame, type Action, type Game } from '../game/engine';
import { treacheryDeck } from '../game/cards';
import { nativeShipmentSources } from '../game/homeworld-options';
import { homeworldWorthlessSaleBlock } from '../game/homeworld-card-economy';
import { moveSemutaEmperorCard, semutaEmperorPlayer } from './fixture-semuta-emperor-share';
import { createSemutaHomeworldChoamSetup, advanceSemutaHomeworldChoam, holdSemutaHomeworldChoamCard } from './fixture-semuta-homeworld-choam';
export type HomeworldChoamSaleKind = 'worthless' | 'duplicate';
/** Native setup, native closing market, conserved physical sale cards. A real
* ten-force shipment lowers Tupile before the Worthless sale; nothing assigns
* clocks, wallets, population, visitors or payment history. */
export function createSemutaHomeworldChoamSaleFixture(kind: HomeworldChoamSaleKind, advanced = false, fullHolder = false) {
const native = createSemutaHomeworldChoamSetup(advanced);
let game = native.game;
const owner = native.owner, holder = native.holder, actions = native.actions;
const card = holdSemutaHomeworldChoamCard(game, owner, kind === 'worthless' ? 'Baliset' : 'Shield');
let witness: string | undefined;
if (kind === 'duplicate') {
const other = treacheryDeck(game.expansions).find(c => c.name === 'Shield' && c.id !== card);
assert.ok(other); witness = moveSemutaEmperorCard(game, other.id, owner);
}
holdSemutaHomeworldChoamCard(game, native.canceller, 'Karama');
const action: Action = { type: 'decision', mode: 'sell', card, ...(witness ? { witness } : {}) };
const setup = structuredClone(game);
game = advanceSemutaHomeworldChoam(game, g => g.decision?.kind === 'choamMarket' && !g.response && !g.phaseOpening, actions);
const highMarket = structuredClone(game);
assert.equal(semutaEmperorPlayer(highMarket, owner).reserves, 20);
let beforeShipment: Game | null = null, afterShipment: Game | null = null;
if (kind === 'worthless') {
assert.ok(homeworldWorthlessSaleBlock(highMarket, owner,
semutaEmperorPlayer(highMarket, owner).hand.find(c => c.id === card)!));
game = advanceSemutaHomeworldChoam(game,
g => g.phase === 5 && g.active === owner && !g.phaseOpening && !g.response && !g.decision, actions);
beforeShipment = structuredClone(game);
const sources = nativeShipmentSources(viewGame(game, owner), 10, 0); assert.ok(sources);
const ship: Action = { type: 'ship', territory: 'habbanya_ridge_sietch', sector: 17,
amount: 10, elite: 0, homeworldSources: sources };
actions.push({ actor: owner, action: ship }); game = applyAction(game, owner, ship);
game = advanceSemutaHomeworldChoam(game, g => !g.phaseOpening && !g.response && !g.decision, actions);
afterShipment = structuredClone(game);
assert.equal(semutaEmperorPlayer(game, owner).reserves, 10);
assert.equal(semutaEmperorPlayer(game, owner).forces['habbanya_ridge_sietch:17'], 10);
const end: Action = { type: 'endMovement' };
actions.push({ actor: owner, action: end }); game = applyAction(game, owner, end);
game = advanceSemutaHomeworldChoam(game,
g => g.phase === 5 && g.decision?.kind === 'choamMarket' && !g.response && !g.phaseOpening, actions);
assert.equal(homeworldWorthlessSaleBlock(game, owner,
semutaEmperorPlayer(game, owner).hand.find(c => c.id === card)!), null);
}
if (fullHolder) {
const player = semutaEmperorPlayer(game, holder);
while (player.hand.length < handLimit(player)) {
assert.ok(game.deck[0]); moveSemutaEmperorCard(game, game.deck[0].id, holder);
}
}
assert.equal(game.decision?.kind, 'choamMarket');
assert.equal(game.decision!.player, owner);
return { game, setup, highMarket, beforeShipment, afterShipment, owner, holder,
canceller: native.canceller, card, witness, price: kind === 'worthless' ? 2 : 3, action, actions, kind };
}
export function playSemutaHomeworldChoamSale(f: ReturnType<typeof createSemutaHomeworldChoamSaleFixture>): Game {
let game = applyAction(f.game, f.owner, f.action);
for (let n = 0; game.response && n < 16; n++) {
assert.equal(game.response.kind, 'choamSale');
const actor = game.players.find(p => {
const controls = viewGame(game, p.id).responseControls;
return controls && !controls.hasPassed && controls.cancelCards.length > 0;
});
assert.ok(actor);
game = applyAction(game, actor.id, { type: 'passResponse' });
}
assert.ok(!game.response);
return game;
}
+73
View File
@@ -0,0 +1,73 @@
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 { createSemutaHomeworldChoamSaleFixture, playSemutaHomeworldChoamSale } from './fixture-semuta-homeworld-choam-sale';
import { reactSemutaHomeworldChoam } from './fixture-semuta-homeworld-choam';
import { semutaEmperorPlayer as player } from './fixture-semuta-emperor-share';
import { semutaHomeworldChoamMovementStock as stock, semutaHomeworldChoamMovementResources as resources } from './fixture-semuta-homeworld-choam-movement';
for (const advanced of [false, true]) for (const kind of ['worthless', 'duplicate'] as const) {
for (const claim of [false, true]) void test(`${advanced ? 'Advanced' : 'Basic'} Homeworld ${kind} market Semuta ${claim ? 'claim' : 'pass'} preserves its actual payout and original close`, () => {
const f = createSemutaHomeworldChoamSaleFixture(kind, advanced, true);
const pending = playSemutaHomeworldChoamSale(f);
assert.ok(pending.pendingTreacheryDiscard);
assert.equal(player(pending, f.owner).spice, player(f.game, f.owner).spice + f.price);
assert.equal(player(pending, f.owner).hand.some(c => c.id === f.card), false);
assert.equal(pending.discard.filter(c => c.id === f.card).length, 1);
if (f.witness) assert.ok(player(pending, f.owner).hand.some(c => c.id === f.witness));
if (kind === 'worthless') {
assert.equal(player(f.afterShipment!, f.owner).spice, player(f.beforeShipment!, f.owner).spice - 10);
assert.equal(player(pending, f.owner).reserves, 10);
assert.equal(player(pending, f.owner).forces['habbanya_ridge_sietch:17'], 10);
} else assert.equal(player(pending, f.owner).reserves, 20);
const paid = resources(pending), event = pending.pendingTreacheryDiscard.batch.event;
assert.throws(() => applyAction(pending, f.owner, { type: 'decision', done: true }));
const done = reactSemutaHomeworldChoam(pending, f.holder, claim);
assert.equal(player(done, f.holder).hand.some(c => c.id === f.card), claim);
assert.equal(player(done, f.holder).hand.some(c => c.id === SEMUTA_DRUG_ID), !claim);
assert.equal(player(done, f.holder).hand.length, player(f.game, f.holder).hand.length);
assert.deepEqual(resources(done), paid, 'Recovery cannot repeat payout, refund shipment or move a counter');
assert.deepEqual(stock(done), stock(f.game));
if (f.witness) assert.ok(player(done, f.owner).hand.some(c => c.id === f.witness));
assert.throws(() => applyAction(done, f.owner, f.action));
assert.throws(() => applyAction(done, f.holder, { type: 'semutaCommit', event }));
const next = applyAction(done, f.owner, { type: 'decision', done: true });
assert.equal(next.phase, kind === 'worthless' ? 6 : 1);
assert.equal(next.turn, f.game.turn);
assert.equal(player(next, f.owner).spice, player(done, f.owner).spice);
assert.equal(player(next, f.owner).reserves, player(done, f.owner).reserves);
assert.equal(next.pendingTreacheryDiscard, null);
});
}
for (const advanced of [false, true]) void test(`${advanced ? 'Advanced' : 'Basic'} high Tupile still rejects Worthless sale before payment or disposal`, () => {
const f = createSemutaHomeworldChoamSaleFixture('worthless', advanced);
const before = structuredClone(f.highMarket);
assert.throws(() => applyAction(f.highMarket, f.owner, f.action));
assert.deepEqual(f.highMarket, before);
});
for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) void test(`${difficulty} legally settles the native Homeworld market disposal`, () => {
for (const advanced of [false, true]) for (const kind of ['worthless', 'duplicate'] as const) {
const f = createSemutaHomeworldChoamSaleFixture(kind, advanced);
let game = playSemutaHomeworldChoamSale(f);
const paid = player(game, f.owner).spice;
const view = viewGame(game, f.holder);
view.players.find(p => p.id === f.holder)!.bot = difficulty;
const action = botActions(view)[0]; assert.ok(action);
game = applyAction(game, f.holder, action);
const event = game.pendingTreacheryDiscard?.batch.event;
for (const p of game.players) if (game.pendingTreacheryDiscard?.reaction?.stage === 'offer'
&& !game.pendingTreacheryDiscard.reaction.passed.includes(p.id))
game = applyAction(game, p.id, { type: 'semutaPass', event });
assert.equal(game.pendingTreacheryDiscard, null);
assert.equal(player(game, f.owner).spice, paid);
const claimed = player(game, f.holder).hand.some(c => c.id === f.card);
assert.equal(player(game, f.holder).hand.some(c => c.id === SEMUTA_DRUG_ID), !claimed);
assert.deepEqual(stock(game), stock(f.game));
const next = applyAction(game, f.owner, { type: 'decision', done: true });
assert.equal(next.phase, kind === 'worthless' ? 6 : 1);
}
});
+1 -1
View File
@@ -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 Box search, clean Distrans transfer, definite Truthtrance answers, used ordinary specials, paid Kaitain disposal, occupied-Tupile cleanup and printed Karama canceling native Guild/Emperor/Richese-purchase income offer Semuta before their original suffix. Native printed Kulon, Baliset, La La La, Trip to Gamont and Jubba costs also pause before their original effect. A printed Karama canceling these native powers is separately offered before denial; the retained Worthless fuel is not claimable. Recovery cannot repeat movement, return a force twice, redo storm movement or refund payment. Occupied income shares still use their original receiver once. Kull, 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. Paid Box search, clean Distrans transfer, definite Truthtrance answers, used ordinary specials, paid Kaitain disposal, Tupile cleanup and completed legal CHOAM market sales offer Semuta before their original suffix. Market recovery preserves the credited two/three spice and retained duplicate witness; high Tupile still prohibits Worthless sales. Native printed Kulon/Baliset/La La La/Gamont/Jubba costs pause before their effect; printed Karama canceling these powers or native Guild/Emperor/Richese income is separately offered before denial. Retained fuel is never claimable. No repeated movement/force return/storm/payout or refund is introduced. Original occupied shares still allocate once. Kull, other producers, Nexus, Skills, Tech, Strongholds, Discovery, Ix/E3 mixtures, 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.' +