- Richese is offering a{' '}
- {offer.source === 'cache' ? 'cache' : 'Black Market'} card. Bidding will
- begin after you decline Technology for this lot.
-
-
{offer.exchangeBlocked}
-
- Declining leaves your once-per-round Technology use available for later
- lots. Your hand and the offered card stay unchanged.
-
-
-
- );
-}
diff --git a/docs/EXPANSION_FACTIONS_PROTOTYPE.md b/docs/EXPANSION_FACTIONS_PROTOTYPE.md
index 185dc509..f880afcd 100644
--- a/docs/EXPANSION_FACTIONS_PROTOTYPE.md
+++ b/docs/EXPANSION_FACTIONS_PROTOTYPE.md
@@ -266,11 +266,13 @@ No pieces were staged or phases skipped in these samples.
| All three expansion sets, Advanced | 614 | 16 | Stopped at turn three on an existing CHOAM movement/Ambassador arrival guard. |
The first combined Advanced run stopped at action 70 on the old Ixian/Richese
-special-lot guard. With the decline continuation, the same seed declined three
-Richese lots and reached eleven ordinary Technology decisions before the separate
-arrival stop. This is evidence of the connected auction path, not a completed
-combined Advanced game. The current movement stop and rejected arrival candidates
-remain integration work; public modes stay gated.
+special-lot guard. A temporary per-lot decline continuation once let the same seed
+pass three Richese lots, but the authorized supplied Advanced physical p34
+expressly excludes Richese auctions from Ixian Technology, so that scaffolding was
+removed on 8 October and a Richese special lot now opens directly. This is evidence
+of the connected auction path, not a completed combined Advanced game. The current
+movement stop and rejected arrival candidates remain integration work; public modes
+stay gated.
An initial CHOAM Basic custody audit mistakenly counted the already sold current
normal-auction card both in its winner's hand and in its sale receipt. Correcting
diff --git a/docs/RICHESE_AUCTION_RULES.md b/docs/RICHESE_AUCTION_RULES.md
index 26927f68..4a2fea61 100644
--- a/docs/RICHESE_AUCTION_RULES.md
+++ b/docs/RICHESE_AUCTION_RULES.md
@@ -176,33 +176,20 @@ Desktop QA confirmed all ten readable enlarged guides and working direct catalog
Remaining boundaries are unchanged: cache-cancellation count, empty-cache arithmetic, seller-positive Black Market bidding, advanced Ixian Technology special-lot custody, remaining card effects and actual Juice of Sapho play. Full starts remain gated. The hand’s disabled-effect fix and pre-offer guard explanation are implemented. The final browser recheck in QA room63NJ822S version9 confirmed the printed category, disabled unsupported Play card control, visible reason and available inspector. This is bounded desktop/persistence evidence, not mobile QA, a full Richese game, every advanced interaction or durable hosted-worker certification.
-## 13 September: saved per-lot Technology decline
+## 13 September: saved per-lot Technology decline — superseded 8 October
-Genuine combined Advanced faction play exposed the previous pre-offer guard as
-a deadlock when Ixians retained their optional Technology use. A validated Richese
-cache or Black Market offer now creates a saved decision for Ixians before opening
-that lot. The development continuation supports an explicit **decline for this lot**;
-it does not implement a card exchange or adjudicate the pending seller, income or
-unsold-card questions described above.
+Genuine combined Advanced faction play once exposed a pre-offer guard as a
+deadlock when Ixians retained their optional Technology use, so a temporary
+per-lot decline continuation was added. The authorized supplied
+`UNOFFICIAL_Revised_Dune_Rulebook_v.2.3_web.pdf`, physical **p34, Ixians D**,
+expressly excludes Richese auctions from Ixian Technology. That source resolves
+the recorded custody question by exclusion, so the whole per-lot offer, its
+pending receipt, its decision/control and its AI continuation were removed.
-The exact offered card stays in its original hand or cache. The pending receipt
-binds the current round, turn, offer terms, owner, independent event and physical
-custody. The public decision contains no card identity; the owner-facing control
-explains the unfinished exchange and uses the matching event. Declining opens the
-same lot once, preserves the normal pool count, and leaves `ixTechnologyTurn`
-unchanged. Ixians can still use Technology on a later normal lot or separately
-decline another Richese lot. All four AI profiles have this legal continuation.
-
-This opt-out is the ordinary exercise of an optional ability, not a publisher
-prohibition on exchanging special lots. The pending offer is kept private until
-the normal lot-opening point; this saved staging order is an implementation
-boundary rather than a separate official timing clarification. Public starts and
-full combined compliance remain gated.
-
-The focused engine and UI tests are
-`tests/ix-richese-technology.test.ts`,
-`tests/ix-richese-technology-controls.test.tsx`, and
-`tests/ix-richese-technology-recovery.test.ts`. Final checkpoint verification is
-recorded in the implementation log and source-bound private report. Historical
-absence claims about ordinary Richese technology cards above are superseded by
-their later runtime contracts, including [Juice of Sapho](JUICE_OF_SAPHO_RUNTIME.md).
+A Richese cache or Black Market lot now opens directly. `ixTechnologyTurn` and
+the once-per-round Technology use remain available for ordinary lots only. The
+obsolete decline scaffolding, `components/ix-richese-technology.tsx` and its
+three focused test files were deleted; `tests/richese-engine-review.test.ts`
+now asserts the direct lot opening. Historical absence claims about ordinary
+Richese technology cards above are superseded by their later runtime contracts,
+including [Juice of Sapho](JUICE_OF_SAPHO_RUNTIME.md).
diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md
index 0dfbcfe9..ed5cf8ae 100644
--- a/docs/RULE_DECISIONS.md
+++ b/docs/RULE_DECISIONS.md
@@ -353,7 +353,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP
| Sapho battle order | First/last reorders remaining Battle-phase choosers at clean boundaries. Phase priority persists through aftermath; actual legal pairs determine eligibility. Separating the chooser from physical attacker/tie advantage is an explicit implementation interpretation of the card's distinct alternatives, not a dedicated FAQ ruling. Last does not prevent others from choosing the holder as an opponent. | [Runtime scope and source interpretation](SAPHO_BATTLE_ORDER.md) |
| Harass & Withdraw | Either slot but neither category; private Prescience answers null. Return uniquely determined own undialed physical forces before weapon losses, canceled by an opposing successful Traitor. Mandatory after-use discard overrides winner retention. Explosion ordering and own-Traitor disposal are explicit timing/specificity inferences, consistent with the recorded Planetologist precedent. | [Runtime and sources](HARASS_WITHDRAW_RUNTIME.md) |
| Expansion-faction setup | Selecting Ecaz/Moritani factions does not select their independent three-card variant. Ordinary decks contain 33/35/47 cards; Richese's ten-card cache stays separate. Ecaz allocates six total forces within Imperial Basin sectors 9–11, retaining fourteen reserves. Flexible sector allocation is the literal printed territory constraint; Fremen→Ecaz→Advanced BG order is an implementation inference, while Moritani-last is printed. | [Source and profile contract](EXPANSION_FACTIONS_PROTOTYPE.md) |
-| Ixian Technology on Richese lots | Replacement-card ownership, income and unsold disposition remain unresolved. A saved explicit per-lot decline permits an unchanged offer to proceed and leaves Technology available later. This development continuation does not implement the exchange or settle its custody. | [Auction source boundary](RICHESE_AUCTION_RULES.md#13-september-saved-per-lot-technology-decline) |
+| Ixian Technology on Richese lots | Superseded 8 October by the authorized supplied Advanced physical p34, which expressly excludes Richese auctions from Ixian Technology. Richese cache/Black Market lots now open directly; the once-per-round Technology use remains for ordinary lots. The temporary per-lot decline scaffolding and its tests were deleted. | [Auction source boundary](RICHESE_AUCTION_RULES.md#13-september-saved-per-lot-technology-decline--superseded-8-october) |
| Spice Banker battle spending | Secret own-spice commitment is separate from support; only the surviving selected disc gains strength. Core Battle Plan payment rules spend it on death/loss and waive it for a sole traitor-call winner. This payment composition is explicit; upper deferred-income custody remains pending. | [Source contract and prototype](SPICE_BANKER_RUNTIME.md) |
| Smuggler shipping | Optional free companion is included in the actual total; apply Guild rounding to the reduced priced count. Ordinary Fremen reserves are on planet. The officially permitted owned No-Field plus a separate free force is now prototyped at the unchanged one-marker price. Legacy unstamped ordinary declarations keep their quoted full price. | [Ordinary contract](SMUGGLER_SHIPMENT.md), [No-Field combination](SMUGGLER_NO_FIELD.md) |
| Sandmaster movement | One optional board spice per distinct entered territory on an explicit legal route; source-only repositioning earns none. Per-movement Hajr reset is a documented grammatical inference. Canceled moves earn nothing. Native Fremen worm rides collect optionally at the destination before faction arrival reactions, with no intermediate route. Multiple-pile collection remains guarded. | [Ground routes](SANDMASTER_MOVEMENT.md), [worm rides](SANDMASTER_WORM.md) |
diff --git a/game/admin-seat-ai-shape.ts b/game/admin-seat-ai-shape.ts
index e035ecee..8b8586b4 100644
--- a/game/admin-seat-ai-shape.ts
+++ b/game/admin-seat-ai-shape.ts
@@ -65,7 +65,6 @@ const decisions = {
ixSetup: true,
ixAuction: true,
ixTechnology: true,
- ixRicheseTechnology: true,
ixAllyCard: true,
mobileStronghold: true,
ixSubstitution: true,
diff --git a/game/bots.ts b/game/bots.ts
index 896dfe1c..2d4e514d 100644
--- a/game/bots.ts
+++ b/game/bots.ts
@@ -1877,8 +1877,6 @@ function policyActions(g: GameView): Action[] {
const card = cards.find((c) => c.kind === 'worthless') ?? cards[0];
return card ? [{ type: 'decision', card: card.id }] : [];
}
- if (d.kind === 'ixRicheseTechnology')
- return [{ type: 'decision', event: d.event, decline: true }];
if (d.kind === 'richeseBlackMarket')
return [
{ type: 'decision', event: g.richeseBidding!.event, decline: true },
diff --git a/game/engine.ts b/game/engine.ts
index 5d6ea0cd..709554d4 100644
--- a/game/engine.ts
+++ b/game/engine.ts
@@ -852,7 +852,6 @@ export type Decision =
| { kind: 'ixSetup'; player: string }
| { kind: 'ixAuction'; player: string }
| { kind: 'ixTechnology'; player: string }
- | { kind: 'ixRicheseTechnology'; player: string; event: string; source: 'cache' | 'blackMarket' }
| { kind: 'ixAllyCard'; player: string }
| { kind: 'nexusIxianReplacement'; player: string; event: string }
| { kind: 'mobileStronghold'; player: string; placement: boolean }
@@ -1562,22 +1561,6 @@ export type Game = {
richesePeekKnown?: boolean;
richeseOfferedCard?: Card | null;
richeseClaim?: string | null;
- /** Private pre-lot offer. The public Ixian choice contains no Black Market card identity. */
- pendingIxRicheseTechnology?: {
- event: string;
- player: string;
- owner: string;
- round: string;
- turn: number;
- source: 'cache' | 'blackMarket';
- card: Card;
- method: RicheseAuction['method'];
- direction: 'clockwise' | 'counterclockwise' | null;
- claim: string | null;
- frame: string;
- signature: string;
- };
- ixRicheseTechnologyEvent?: string;
currentAuctionSale?: {
occupiedEvent?: string;
occupiedContributions?: { payer: string; amount: number }[];
@@ -4314,8 +4297,6 @@ function savedTransferResponses(g: Game) {
];
}
function transferCardBlock(g: Game, owner: Player, card: Card) {
- if (g.pendingIxRicheseTechnology?.owner === owner.id && g.pendingIxRicheseTechnology.card.id === card.id)
- return 'This card is reserved for the pending Richese lot and Ixian choice.';
if (isPortableSnooper(card) && homeworldSavedDecisions(g).some((d) => d.kind === 'homeworldDefense' && d.player === owner.id))
return 'Resolve the Homeworld late-defense choice before transferring its Portable Snooper.';
@@ -4844,8 +4825,6 @@ function draw(g: Game) {
return g.deck.shift();
}
function discard(g: Game, p: Player, id: string, role?: Omit) {
- requireRule(g.pendingIxRicheseTechnology?.card.id !== id,
- 'The card reserved for the pending Richese lot cannot be discarded.');
requireRule(
!g.pendingTreacheryDiscard,
'Finish the committed discard continuation before another discard.',
@@ -10943,7 +10922,7 @@ function richeseBetrayalSourcePreflight(g: Game, quote: Extract): string {
- return JSON.stringify({ ...pending, signature: undefined });
-}
-function ixRicheseTechnologyIntegrity(g: Game) {
- const pending = g.pendingIxRicheseTechnology;
- const decisions = homeworldSavedDecisions(g).filter(d => d.kind === 'ixRicheseTechnology');
- if (pending === undefined && g.ixRicheseTechnologyEvent === undefined && !decisions.length) return;
- const round = g.richeseBidding, ixians = byFaction(g, 'ixians'), owner = byFaction(g, 'richese');
- requireRule(pending && g.status === 'playing' && g.phase === 3 && g.advanced &&
- ixians && g.ixTechnologyTurn !== g.turn && owner && round &&
- pending.player === ixians.id && pending.owner === owner.id && pending.round === round.event &&
- pending.turn === g.turn && round.turn === g.turn && round.owner === owner.id &&
- typeof pending.event === 'string' && pending.event.length > 0 && pending.event === g.ixRicheseTechnologyEvent &&
- ['cache', 'blackMarket'].includes(pending.source) &&
- round.stage === (pending.source === 'cache' ? 'cacheOffer' : 'blackMarketOffer') &&
- !g.auction && !g.richeseAuction && !g.ixAuction && !g.currentAuctionSale && !g.richeseOfferedCard &&
- pending.frame === ixRicheseTechnologyFrame(g) && pending.signature === ixRicheseTechnologySignature(pending),
- 'The saved Ixian Richese-lot choice lost or changed its original offer, round or event.');
- requireRule(['normal', 'onceAround', 'silent'].includes(pending.method) &&
- (pending.source !== 'cache' || pending.method !== 'normal') &&
- (pending.direction === null || ['clockwise', 'counterclockwise'].includes(pending.direction)) &&
- (pending.method !== 'onceAround' || pending.direction !== null) &&
- (pending.source === 'cache' ? pending.claim === null : pending.claim === null ||
- typeof pending.claim === 'string' && pending.claim === pending.claim.trim() && pending.claim.length <= 300),
- 'The saved Ixian Richese-lot choice has invalid auction terms.');
- const held = (pending.source === 'cache' ? g.richeseCache : owner.hand)?.filter(c => c.id === pending.card?.id);
- requireRule(held?.length === 1 && JSON.stringify(held[0]) === JSON.stringify(pending.card) &&
- physicalTreacheryCards(g).filter(c => c.id === pending.card.id).length === 1,
- 'The pending Richese offer must retain its exact unique card in the original custody.');
- requireRule(decisions.length === 1 && decisions[0].player === pending.player && decisions[0].event === pending.event &&
- decisions[0].source === pending.source && Object.keys(decisions[0]).every(key => ['kind', 'player', 'event', 'source'].includes(key)),
- 'The saved Ixian Richese-lot choice lost or duplicated its original decision.');
-}
function beginRicheseLot(
g: Game,
source: 'cache' | 'blackMarket',
action: Action,
- technologyDeclined = false,
) {
const round = g.richeseBidding!,
owner = getPlayer(g, round.owner);
@@ -11232,25 +11168,6 @@ function beginRicheseLot(
action.method !== 'onceAround' || action.direction !== undefined,
'Choose a physical bidding direction.',
);
- if (richeseOfferBlock(g) && !technologyDeclined) {
- requireRule(!g.pendingIxRicheseTechnology && g.ixRicheseTechnologyEvent === undefined,
- 'Finish the existing Ixian choice before declaring another Richese offer.');
- const event = crypto.randomUUID(), player = byFaction(g, 'ixians')!.id;
- const pending: NonNullable = {
- event, player, owner: owner.id, round: round.event, turn: g.turn, source, card: structuredClone(card),
- method: action.method as RicheseAuction['method'],
- direction: (action.direction as 'clockwise' | 'counterclockwise' | undefined) ?? null,
- claim: source === 'blackMarket' && typeof action.claim === 'string' ? action.claim.trim().slice(0, 300) : null,
- frame: ixRicheseTechnologyFrame(g), signature: '',
- };
- pending.signature = ixRicheseTechnologySignature(pending);
- g.pendingIxRicheseTechnology = pending;
- g.ixRicheseTechnologyEvent = event;
- g.decision = { kind: 'ixRicheseTechnology', player, event, source };
- g.active = player;
- log(g, `${getPlayer(g, player).name} must choose whether to continue without exchanging this Richese ${source === 'cache' ? 'cache' : 'Black Market'} lot. The exchange is not yet implemented; Technology remains available for later lots.`);
- return;
- }
const positions = normalizedPlayerPositions(g);
let order = [...g.order];
if (action.method === 'onceAround') {
@@ -12157,8 +12074,6 @@ function karamaSpendingBlock(g: Game, p: Player, card: Card): string | null {
(g.pendingKull.card === card.id || g.pendingChoamWorthless?.card === card.id ||
g.pendingKull.worthless?.card === card.id))
return 'This physical card is reserved for the interrupted Kull transaction.';
- if (g.pendingIxRicheseTechnology?.card.id === card.id)
- return 'This card is reserved for the pending Richese lot and Ixian choice.';
if (g.battle?.lateDefense?.[p.id] === card.id)
return 'This Portable Snooper is already played and reserved for battle cleanup.';
if (giftReserved(g, p.id, card.id))
@@ -26900,8 +26815,6 @@ export function prepareSpecialKaramaIntent(
'Resolve the previous purchase income first.',
);
const acquire = stringField(action.acquire);
- requireRule(g.pendingIxRicheseTechnology?.card.id !== acquire,
- 'The card reserved for the pending Richese lot cannot be acquired by another effect.');
const chosen = g.richeseCache?.find((c) => c.id === acquire);
requireRule(
chosen && richeseCardDefinition(chosen),
@@ -27097,11 +27010,6 @@ export function prepareSpecialKaramaIntent(
g.pendingRicheseGift?.intent.owner !== target.id,
'Resolve the reserved Richese gift before randomly exchanging that hand.',
);
- requireRule(
- g.pendingIxRicheseTechnology?.source !== 'blackMarket' ||
- g.pendingIxRicheseTechnology.owner !== target.id,
- 'Resolve the pending Black Market Ixian choice before randomly exchanging that hand.',
- );
const count = integer(
action.amount,
1,
@@ -27566,7 +27474,6 @@ export function applyAction(state: Game, id: string, action: Action): Game {
harkonnenExchangeIntegrity(state);
validateEcazLoyalty(state);
ecazTreacheryIntegrity(state);
- ixRicheseTechnologyIntegrity(state);
leaderSkillsIntegrity(state);
moritaniExtortionIntegrity(state);
guildRateIntegrity(state);
@@ -27774,7 +27681,6 @@ export function applyAction(state: Game, id: string, action: Action): Game {
homeworldSubstitutionIntegrity(g);
homeworldDefenseIntegrity(g);
homeworldShipmentIntegrity(g);
- ixRicheseTechnologyIntegrity(g);
harkonnenExchangeIntegrity(g);
moritaniExtortionIntegrity(g);
guildRateIntegrity(g);
@@ -27950,7 +27856,6 @@ export function normalizeAutomaticGame(state: Game): Game {
harkonnenExchangeIntegrity(state);
validateEcazLoyalty(state);
ecazTreacheryIntegrity(state);
- ixRicheseTechnologyIntegrity(state);
leaderSkillsIntegrity(state);
moritaniExtortionIntegrity(state);
guildRateIntegrity(state);
@@ -28023,7 +27928,6 @@ export function normalizeAutomaticGame(state: Game): Game {
homeworldSubstitutionIntegrity(g);
homeworldDefenseIntegrity(g);
homeworldShipmentIntegrity(g);
- ixRicheseTechnologyIntegrity(g);
harkonnenExchangeIntegrity(g);
moritaniExtortionIntegrity(g);
guildRateIntegrity(g);
@@ -28600,19 +28504,6 @@ function applyActionInner(
return g;
}
if (decision.kind === 'bureaucratPayment') {actBureaucratPayment(g,action);return g;}
- if (decision.kind === 'ixRicheseTechnology') {
- const pending = g.pendingIxRicheseTechnology!;
- requireRule(action.event === pending.event && action.decline === true &&
- Object.keys(action).every(key => ['type', 'event', 'decline'].includes(key)),
- 'Ixian exchange custody for Richese lots is not implemented. Explicitly continue without this exchange.');
- delete g.pendingIxRicheseTechnology;
- delete g.ixRicheseTechnologyEvent;
- log(g, `${p.name} continued without exchanging this Richese lot; Technology remains available for later lots.`);
- beginRicheseLot(g, pending.source, { type: 'decision', card: pending.card.id, method: pending.method,
- ...(pending.direction !== null ? { direction: pending.direction } : {}),
- ...(pending.claim !== null ? { claim: pending.claim } : {}) }, true);
- return g;
- }
if (decision.kind === 'homeworldRevivalDeployment') {
decideHomeworldRevivalReturn(g, p, action);
return g;
@@ -32005,7 +31896,6 @@ export function viewGame(state: Game, id: string) {
harkonnenExchangeIntegrity(state);
validateEcazLoyalty(state);
ecazTreacheryIntegrity(state);
- ixRicheseTechnologyIntegrity(state);
leaderSkillsIntegrity(state);
moritaniExtortionIntegrity(state);
guildRateIntegrity(state);
@@ -33024,13 +32914,6 @@ export function viewGame(state: Game, id: string) {
? { ecazSpecialKarama: ecazSpecialKaramaView(g, me) } : {}),
...(g.moritaniSpecialKarama?.source.owner === me.id && g.moritaniSpecialKarama.stage === 'offer'
? { moritaniSpecialKarama: currentMoritaniSpecialKaramaQuote(g) } : {}),
- ixRicheseTechnology: g.pendingIxRicheseTechnology ? {
- event: g.pendingIxRicheseTechnology.event,
- player: g.pendingIxRicheseTechnology.player,
- owner: g.pendingIxRicheseTechnology.owner,
- source: g.pendingIxRicheseTechnology.source,
- exchangeBlocked: 'Ixian exchange custody for Richese lots is not implemented. You can explicitly continue without this exchange; Technology remains available for later lots.',
- } : null,
richeseBidding:
g.richeseBidding?.turn === g.turn
? {
diff --git a/game/nexus-play-boundary.ts b/game/nexus-play-boundary.ts
index 7860a96a..a0ecbd1e 100644
--- a/game/nexus-play-boundary.ts
+++ b/game/nexus-play-boundary.ts
@@ -12,7 +12,7 @@ export function nexusCleanPlayBlocked(g: Game, automaticPending = false): boolea
g.pendingChoamWorthless || g.pendingChoamBattleIncome ||
g.pendingChoamMarketGhola || g.pendingAuditor || g.pendingFaceDance ||
g.pendingTech || g.pendingIxTechnology || g.pendingIxAlly ||
- g.pendingIxSubstitution || g.pendingIxRicheseTechnology ||
+ g.pendingIxSubstitution ||
g.pendingWinnerDiscards || g.pendingSukRescue || g.ornithopter ||
g.summonedWorm || g.battle || g.auction || g.richeseAuction ||
g.choamMarket || g.nexusTraitorPending ||
diff --git a/game/reference.ts b/game/reference.ts
index 3cae0372..ad17ba39 100644
--- a/game/reference.ts
+++ b/game/reference.ts
@@ -4634,7 +4634,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'A selected surviving Planetologist may use a canonical printed green Richese Special or Special - Movement in its weapon slot for two strength. Exact physical identity is required. Stone Burner, Mirror Weapon and Portable Snooper do not qualify. The green card is discarded once without its native effect, fee, search or early leader kill.',
'The normal Smuggler own companion is one separate force alongside the marker, not added to its hidden value. Ordinary allied No-Field cancellation and Guild stages retain their exact owner/recipient/token frame; an allied free companion is not granted.',
'The authorized Advanced recipient cutover keeps Richese ally contributions distinct: buyer four goes to Richese, funded Richese four goes to Emperor or bank. This also applies when Emperor is the buyer. Cancellation redirects only the original Emperor share, not unrelated buyer credit, with no second donor debit or card transfer. Basic keeps its publisher receipt.',
- 'Separate normal Banker income uses only actual bank-routed payer legs and remains unavailable until actual Mentat. Allowed Emperor income and ordinary seller credit are not bank payments. Native Ixian Technology on Richese lots, exhausted-cache arithmetic, positive Black Market self-bidding, Bureaucrat split funding and other module/ruling boundaries remain explicit.',
+ 'Separate normal Banker income uses only actual bank-routed payer legs and remains unavailable until actual Mentat. Allowed Emperor income and ordinary seller credit are not bank payments. Supplied Advanced p34 expressly excludes Richese auctions from Ixian Technology, so no per-lot exchange or decline exists; exhausted-cache arithmetic, positive Black Market self-bidding, Bureaucrat split funding and other module boundaries remain explicit.',
],
related: ['leader-skills', 'richese-cards', 'richese-no-field', 'spice-banker-income', 'implementation-checklist'],
checklist: [
@@ -4709,7 +4709,7 @@ export const RULE_TOPICS: RuleTopic[] = [
steps: [
'Fresh all14 Leader Skills preserves original classic, supported E1/E2 or standalone Ecaz OR Moritani setup with Homeworlds/Discovery/Tech3+/Advanced Strongholds under each envelope. Explicit --nexus-cards additionally admits single/paired one-family E1/E2, BOTH selected Ix+CHOAM native families/union47 and standalone OR paired Ecaz/Moritani/ecaz33 with classics. Basic/Advanced2–6, original Ix pre-training offer, ordinary native discs and actual hands remain; Auditor/shared Duke never train. Winning normal/trained Suk uses source-labelled native, visitor or nested fixed-loss pools; native saved counters stay at that home and external Emperor returns allocate original homes. Original phase-end industry, held benefits, cleanup/Tech before Face Dance and native assassination remain; unreconciled bands, mixed E3/three-family, public starts and played-save conversion stay separate.',
'Basic Moritani retains base opponents including Harkonnen. Its separate bounded Advanced classic/non-Harkonnen roster now composes the original assassination preview and all fourteen skills: winner Suk rescue and Rihani choice finish first, then an eligible different trained disc may be assassinated with its exact card return and printed bounty. No casualties repeat; original set-aside and one private Mentat replacement remain. See Moritani with Leader Skills for the exact scope.',
- 'Fresh Advanced profiles compose native Ixians, Tleilaxu, CHOAM and/or Richese with classic opponents and distinct required family decks. All fourteen skills and native starts remain. Auditor stays unskilled; cyborg casualties, original CHOAM support and Richese capped marker revelation remain physical. Foreign revival creates no new skill. Native Ixian Technology on Richese lots, own mixed No-Field, captured replacement, Advanced Atreides Suk/KH and unrelated overlays retain their guards.',
+ 'Fresh Advanced profiles compose native Ixians, Tleilaxu, CHOAM and/or Richese with classic opponents and distinct required family decks. All fourteen skills and native starts remain. Auditor stays unskilled; cyborg casualties, original CHOAM support and Richese capped marker revelation remain physical. Foreign revival creates no new skill. Supplied Advanced p34 excludes Richese auctions from Ixian Technology; own mixed No-Field, captured replacement, Advanced Atreides Suk/KH and unrelated overlays retain their guards.',
'Fresh three-through-six-seat Basic/Advanced development tables may compose all fourteen Skills with original Tech Tokens: classic factions, supported E1/E2 native families, or standalone Ecaz/Moritani with the exact ecaz deck and original roster exclusions. Starting cards/cache, skill/Traitor choices and printed/first-Storm assignment stay genuine. Existing leader-skills/banker-income entry preserves unused selected tokens; mixed E3 families, other overlays, public activation and saved-game conversion remain separate.',
'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. --discoveries additionally composes original Discovery7/8 in this classic profile. Saved hands/offers remain authoritative; the actual end-Spice closing draw follows Maker votes/typed rides, both Advanced piles and settled alliance. 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, Banker/Mentat previews, borrowed Smuggler arithmetic and unresolved interactions remain separate.',
@@ -4829,18 +4829,18 @@ export const RULE_TOPICS: RuleTopic[] = [
'Ecaz places six starting forces in Imperial Basin, allocated among sectors 9, 10 and 11, and keeps fourteen in reserve. Finish Fremen placement first; Bene Gesserit then places its Advanced starting advisor. Moritani places its six forces in an unoccupied territory after all other setup is complete.',
'Ixians keep their private starting-card selection and physical cyborg/suboid forces. Tleilaxu receives three private Face Dancers. Advanced CHOAM receives its Auditor. Richese has a separate ten-card cache and three private No-Fields. Ecaz and Moritani receive their Ambassador and Terror inventories and share one Duke Vidal disc.',
'Without Ix, CHOAM adds Poison Tooth and Artillery Strike to the 33-card ordinary deck; Ix or combined decks contain47. Ecaz faction selection adds no cards. Explicit independent variant adds Recruits/Harass/Reinforcements for36/50/38, with classic/paired controls and a separately verified native Advanced Occupy combined-army path.',
- 'Advanced Ixians may explicitly decline Technology for one Richese special lot. Their once-per-round use remains available later. The special-lot exchange itself is unfinished pending replacement-card custody interpretation.',
+ 'Supplied Advanced p34 expressly excludes Richese auctions from Ixian Technology, so a Richese special lot begins directly with no Ixian exchange or decline prompt; the once-per-round Technology use remains available for ordinary lots.',
'Fresh unsupported movement arrivals still reject before opening prevention choices or reserving a new movement card. A saved CHOAM allow choice can return an uncommitted unsupported move for another choice, preserving its existing reserved Ornithopter. The bounded Ecaz/Moritani same-entry Ambassador/Terror overlap instead snapshots both eligible owners in storm order without replaying the original move.',
'Ordinary shipment checks unfinished arrival combinations before saving the Guild interception choice. An already saved ordinary shipment may return unspent when its still-gated arrival cannot commit; older No-Field, Smuggler, Nexus and Ambassador shipments retain their separate recovery boundaries. Classic plus Ecaz/Moritani Ambassador/Terror overlap has a provisional saved storm-order sequence, while other simultaneous priorities remain unfinished.',
'Remaining abilities and combined timing can still encounter explicit development guards. Setup and saved-continuation evidence does not certify complete expansion play.',
],
related: ['setup', 'ix-modules', 'choam-modules', 'ecaz-modules', 'implementation-checklist'],
checklist: [
- { area: 'Implementation', status: 'Partial', detail: 'Genuine setup for seven expansion selections reuses existing faction effects and corrects Ecaz starting placement. Saved per-lot Ixian decline resumes Richese bidding without changing card custody or consuming Technology. Optional variants and unfinished interactions remain separate.', evidence: ['game/engine.ts', 'game/cards.ts', 'game/ecaz-setup.ts', 'tools/prototype-room.ts'] },
- { area: 'Player controls', status: 'Partial', detail: 'Existing faction choices and private hands are reused. Ecaz has an owned sector-allocation control and setup waiting messages; Ixians have an explicit per-lot decline control. Public starts and full acceptance remain gated.', evidence: ['components/ecaz-setup.tsx', 'components/ix-richese-technology.tsx', 'components/game-table.tsx'] },
- { area: 'AI', status: 'Partial', detail: 'All four profiles use genuine setup choices and the shared Ecaz allocation quote, and can decline this Richese lot without using Technology. Combined gameplay strategy, guarded interactions and full-game reliability remain unfinished.', evidence: ['game/bots.ts', 'game/ecaz-setup.ts'] },
+ { area: 'Implementation', status: 'Partial', detail: 'Genuine setup for seven expansion selections reuses existing faction effects and corrects Ecaz starting placement. Supplied Advanced p34 excludes Richese auctions from Ixian Technology, so a Richese special lot begins directly. Optional variants and unfinished interactions remain separate.', evidence: ['game/engine.ts', 'game/cards.ts', 'game/ecaz-setup.ts', 'tools/prototype-room.ts'] },
+ { area: 'Player controls', status: 'Partial', detail: 'Existing faction choices and private hands are reused. Ecaz has an owned sector-allocation control and setup waiting messages; Richese special lots open directly with no Ixian prompt. Public starts and full acceptance remain gated.', evidence: ['components/ecaz-setup.tsx', 'components/game-table.tsx'] },
+ { area: 'AI', status: 'Partial', detail: 'All four profiles use genuine setup choices and the shared Ecaz allocation quote; no Ixian Richese-lot decision exists to answer. Combined gameplay strategy, guarded interactions and full-game reliability remain unfinished.', evidence: ['game/bots.ts', 'game/ecaz-setup.ts'] },
{ area: 'Documentation', status: 'Partial', detail: 'The faction profile records ordinary deck composition, independent variants, Ecaz territory allocation and deterministic setup-order interpretations, with unfinished play explicit.', evidence: ['docs/EXPANSION_FACTIONS_PROTOTYPE.md'] },
- { area: 'Verification', status: 'Partial', detail: 'Setup, inventory, private views, JSON/SQLite continuation, concurrent start and dirty-lobby rejection have focused checks. Ixian per-lot decline has ownership, stale/corrupt decision, private card custody and concurrent recovery checks. Sample gameplay and browser evidence are recorded separately, with remaining guards retained. Deferred movement and ordinary shipment checks cover arrival preflight, retained resources and saved concurrent recovery; special shipment receipts keep their separate guards.', evidence: ['tests/deferred-movement-arrival.test.ts', 'tests/deferred-movement-arrival-recovery.test.ts', 'tests/deferred-shipment-arrival.test.ts', 'tests/deferred-shipment-arrival-recovery.test.ts', 'docs/DEFERRED_SHIPMENT_ARRIVAL.md', 'tools/faction-games.ts', 'tests/expansion-factions-prototype.test.ts', 'tests/expansion-factions-recovery.test.ts', 'tests/ecaz-setup-controls.test.tsx', 'tests/ecaz-cards.test.ts', 'tests/ix-richese-technology.test.ts', 'tests/ix-richese-technology-controls.test.tsx', 'tests/ix-richese-technology-recovery.test.ts'] },
+ { area: 'Verification', status: 'Partial', detail: 'Setup, inventory, private views, JSON/SQLite continuation, concurrent start and dirty-lobby rejection have focused checks. Sample gameplay and browser evidence are recorded separately, with remaining guards retained. Deferred movement and ordinary shipment checks cover arrival preflight, retained resources and saved concurrent recovery; special shipment receipts keep their separate guards.', evidence: ['tests/deferred-movement-arrival.test.ts', 'tests/deferred-movement-arrival-recovery.test.ts', 'tests/deferred-shipment-arrival.test.ts', 'tests/deferred-shipment-arrival-recovery.test.ts', 'docs/DEFERRED_SHIPMENT_ARRIVAL.md', 'tools/faction-games.ts', 'tests/expansion-factions-prototype.test.ts', 'tests/expansion-factions-recovery.test.ts', 'tests/ecaz-setup-controls.test.tsx', 'tests/ecaz-cards.test.ts'] },
],
},
...[
diff --git a/game/richese-cancellation.ts b/game/richese-cancellation.ts
index eee42dfe..82e5748a 100644
--- a/game/richese-cancellation.ts
+++ b/game/richese-cancellation.ts
@@ -51,7 +51,6 @@ const DECISIONS = {
ixSetup: true,
ixAuction: true,
ixTechnology: true,
- ixRicheseTechnology: true,
ixAllyCard: true,
mobileStronghold: true,
ixSubstitution: true,
diff --git a/tests/faction-games-cli.test.ts b/tests/faction-games-cli.test.ts
index b9de92f0..f5b9eb9e 100644
--- a/tests/faction-games-cli.test.ts
+++ b/tests/faction-games-cli.test.ts
@@ -431,7 +431,7 @@ void test('six-seat Advanced Ixian/Tleilaxu game resolves Technology and Face Da
assert.ok(evidence.restores > 0);
});
-void test('combined-expansion Advanced sample completes with Richese Technology declines and saved continuation', (t) => {
+void test('combined-expansion Advanced sample completes with Richese lots and saved continuation', (t) => {
const out = join(temporary(t), 'combined-complete');
const result = run(out, '--profile', 'combined', '--rules', 'advanced', '--seed', '20260927');
assert.equal(result.status, 0, result.stderr);
@@ -445,7 +445,6 @@ void test('combined-expansion Advanced sample completes with Richese Technology
assert.equal(report.sourceUnchanged, true);
assert.equal(game.name, 'combined-advanced');
assert.equal(game.outcome, 'complete');
- assert.ok(game.used['decision:ixRicheseTechnology'] > 0);
assert.ok(game.used['decision:ecazAmbassador'] > 0);
assert.ok(game.used['decision:faceDance'] > 0);
assert.deepEqual(game.rejected, {});
diff --git a/tests/ix-richese-technology-controls.test.tsx b/tests/ix-richese-technology-controls.test.tsx
deleted file mode 100644
index 25558427..00000000
--- a/tests/ix-richese-technology-controls.test.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import test from 'node:test';
-import assert from 'node:assert/strict';
-import { createElement } from 'react';
-import { renderToStaticMarkup } from 'react-dom/server';
-import { IxRicheseTechnology } from '../components/ix-richese-technology';
-import { RicheseAuctionDecision } from '../components/richese-auctions';
-import {
- applyAction,
- createGame,
- initializeFactionExpansionsGameForAudit,
- joinGame,
- newPlayer,
- viewGame,
- type Game,
-} from '../game/engine';
-import { botActions } from '../game/bots';
-import { DIFFICULTIES } from '../game/bot-profiles';
-
-function until(state: Game, kind: string) {
- let game = state;
- for (let step = 0; step < 100; step++) {
- if (game.decision?.kind === kind) return game;
- let next: Game | undefined;
- for (const player of game.players) {
- const action = botActions(viewGame(game, player.id))[0];
- if (action) {
- next = applyAction(game, player.id, action);
- break;
- }
- }
- assert.ok(next, `No action before ${kind}`);
- game = next;
- }
- throw new Error(`Did not reach ${kind}`);
-}
-
-function offer(source: 'cache' | 'blackMarket') {
- let game = createGame(
- 'IXRICHUI',
- newPlayer('r', 'Richese', 'richese'),
- true,
- ['ix', 'choam'],
- );
- joinGame(game, newPlayer('i', 'Ixians', 'ixians'));
- joinGame(game, newPlayer('a', 'Atreides', 'atreides'));
- for (const player of game.players) {
- player.ready = true;
- player.bot = 'Medium';
- }
- game = until(
- initializeFactionExpansionsGameForAudit(game),
- 'richeseBlackMarket',
- );
- if (source === 'cache') {
- game = applyAction(game, 'r', {
- type: 'decision',
- event: game.richeseBidding!.event,
- decline: true,
- });
- game = applyAction(game, 'r', {
- type: 'decision',
- event: game.richeseBidding!.event,
- position: 'first',
- });
- game = until(game, 'richeseCache');
- }
- const markup = renderToStaticMarkup(
- createElement(RicheseAuctionDecision, {
- game: viewGame(game, 'r'),
- act() {},
- busy: false,
- }),
- );
- assert.match(markup, /Offer (?:cache|Black Market) card/);
- assert.doesNotMatch(markup, /