diff --git a/docs/BATTLE_DISCARD_CONTINUATIONS.md b/docs/BATTLE_DISCARD_CONTINUATIONS.md index 2acef1e6..c4b1887f 100644 --- a/docs/BATTLE_DISCARD_CONTINUATIONS.md +++ b/docs/BATTLE_DISCARD_CONTINUATIONS.md @@ -27,7 +27,13 @@ Quoted Advanced CHOAM force-payment income now also survives those offers. The native quote remains unpaid through cleanup; its original allow/Karama response credits once or redirects income to bank without a payer refund. The original CHOAM market and following phase opening still run afterward. -Auditor, tech and other reward exclusions remain unchanged. +Tech and other reward exclusions remain unchanged. + +The native Auditor opportunity now also survives mandatory/winner offers. +Its original survival limit and opposing-player used IDs remain frozen; the +actual current hand determines eligibility and full cancellation price after +Semuta. Original audit payment/inspection and private historical insight resume +once under the existing income/capture/audit/Face Dance schedule. Sequence retirement precedes the remaining work. Ordinary public actions still drain these saved steps automatically. Test-only observation of unchanged private production dispatch captures real intermediate frames; this is not a public Semuta reaction window. diff --git a/docs/CHOAM_AUDITOR.md b/docs/CHOAM_AUDITOR.md index be877a8d..5ac33219 100644 --- a/docs/CHOAM_AUDITOR.md +++ b/docs/CHOAM_AUDITOR.md @@ -2,6 +2,37 @@ 7 September 2026 checkpoint, with an 8 October Advanced revival follow-up. Inspection, component identity and custody rules are connected. This does not certify the complete CHOAM faction or all expansion combinations; public Advanced and expansion starts remain gated. +## Semuta and current-hand eligibility — 11 October 2026 + +The explicit Semuta preview now preserves the original pending audit through +mandatory and winner-card discard reactions. Battle survival and the audited +opponent's used-card identities remain frozen; the original income/cleanup +order still precedes the native owner offer. The current opposing hand supplies +the actual pool and complete payment price afterward. + +The authorized supplied PDF, physical **p29 CHOAM E**, excludes cards **the +audited opponent used** in battle. The existing quote already records those +opponent-specific IDs; no calculation change was needed. A defense acquired +with Semuta from the other combatant can therefore be eligible, while the +target's own retained weapon remains excluded. This clarifies the generic +“cards used in battle” shorthand below, not a new all-card exclusion. +The old publisher URL currently redirects and the earlier temporary mirror is +absent; this wording check used the available authorized supplied document, +not newly fetched publisher text. + +Both new regressions fail before and pass after, covering target/third-party +Semuta holders, mandatory/winner producers, claim/pass, one-card payment or +exact private singleton inspection, unchanged hands and retired-event rejection. +Twenty-four isolated runtime cases cover those holders, claim/pass, four legal +Semuta AI profiles and pay/inspect. Real GameTable at 390px claims Snooper, +retains the target's Crysknife, shows the actual one-spice price, privately +inspects only Snooper for CHOAM and preserves that snapshot through the original +market to phase7's Ix opening. Target/observer projections have no insight and +there is no horizontal overflow. Temporary QA scaffolds are removed; no saved +store or deployed game was targeted. Full CHOAM/Semuta acceptance remains open. +Types/lint and 152 affected unit cases across twelve files pass. + + ## Advanced revival follow-up — 8 October 2026 The authorized supplied PDF, physical **p29 CHOAM E**, says the Auditor is @@ -33,9 +64,9 @@ For Advanced, the [authorized supplied PDF amendment](RULE_DECISIONS.md#authoriz ## Runtime and information -`game/cards.ts` defines the canonical extra `choam-auditor` disc and fresh factory; `leaders('choam')` still returns the five ordinary discs. Advanced setup installs the additional disc before prediction and traitor dealing. Existing traitor and remaining Face Dancer pools include this identity naturally. Capture and Tleilaxu foreign-ghola acquisition reject the canonical ID, not arbitrary display names. Leader Skills remains an unfinished module, so no skill assignment is enabled. +`game/cards.ts` defines the canonical extra `choam-auditor` disc and fresh factory; `leaders('choam')` still returns the five ordinary discs. Advanced setup installs the additional disc before prediction and traitor dealing. Existing traitor and remaining Face Dancer pools include this identity naturally. Capture and Tleilaxu foreign-ghola acquisition reject the canonical ID, not arbitrary display names. The Auditor is excluded from Leader Skill assignment; supported ordinary-disc skill prototypes remain separate. -`game/choam-auditor.ts` filters the opponent's current physical hand by exact used-card IDs. Uniform partial Fisher–Yates sampling selects one or two distinct cards; an entire eligible pool needs no random draw. Returned faces are detached snapshots. Eligibility excludes original weapon/defense, hero and late Portable Snooper, including retained cards. It does not inspect a deck, cache, another hand or old discard. +`game/choam-auditor.ts` filters the opponent's current physical hand by that opponent's exact used-card IDs. Uniform partial Fisher–Yates sampling selects one or two distinct cards; an entire eligible pool needs no random draw. Returned faces are detached snapshots. Eligibility excludes the opponent's original weapon/defense, hero and late Portable Snooper, including retained cards. It does not inspect a deck, cache, another hand or old discard. At battle resolution the engine freezes actual Auditor survival, the two combatants, used physical IDs, territory, turn and unique battle event before cleanup. Survival, rather than victory, determines the limit. The same trigger applies after traitor and explosion resolution. The engine preserves these facts through winner casualties, Moritani retention, CHOAM battle income, tech claims and Harkonnen capture, offers the audit, then resumes Face Dancing. This is an explicit application schedule where the sources do not prescribe every competing optional aftermath priority. Capture income can therefore fund cancellation; later Face Dance death does not alter the frozen audit limit. diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 0c436a36..080f4031 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 / native Auditor — 11 October.** Mandatory/winner discard reactions +now preserve the original audit before cleanup/income finish and its owner +offer. Frozen survival and opposing-player used IDs are unchanged; the current +hand supplies eligibility and payment. Authorized supplied p29 excludes cards +the audited opponent used, not every combatant's cards; the quote already +implements that distinction. Both regressions fail before and pass after. +Types/lint and 152 affected unit cases across twelve files pass. +Twenty-four runtime cases cover target/third-party holders, claim/pass, four +legal Semuta AI profiles and pay/inspect. Real GameTable at 390px claims Snooper, +retains Crysknife, shows Pay1/Allow and privately inspects only Snooper for +CHOAM; hands remain intact and target/observer insight is null. The original +market reaches phase7's Ix opening with the snapshot intact and no overflow. +Temporary QA scaffolds are removed; no saved store or deployed game was targeted. +See [source and live-hand semantics](CHOAM_AUDITOR.md#semuta-and-current-hand-eligibility--11-october-2026). + **Semuta / quoted CHOAM battle income — 11 October.** Mandatory and winner discard offers now preserve the original Advanced force-payment quote unpaid through cleanup, then restore its native allow/Karama response. Allow credits diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 10c83985..e7119757 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,41 @@ # Dune implementation status +## 11 October 2026 — native Auditor after Semuta battle discards + +Removing only the audit reward exclusion admits mandatory/winner discard +reactions while preserving the original native opportunity. Survival and the +audited opponent's used-card IDs remain frozen; the same cleanup/income schedule +precedes its owner offer, then current hand custody determines pool and price. +No quote, payment or sampling algorithm changed. + +A source check prevents the wrong wider exclusion: authorized supplied PDF +physical p29 CHOAM E names cards **that opposing player used**. The quote already +records those identities. A card acquired from the other combatant can be +eligible while the target's own retained weapon stays excluded. The old +publisher URL redirects and its temporary mirror is absent; the available +authorized local document supplied the wording, not a new publisher fetch. + +Both regressions fail before and pass after. Twenty-four isolated runtime +cases cover target/third-party holders and mandatory/winner producers, +claim/pass, four legal Semuta AI profiles and payment/private inspection. +The exact singleton price1 is paid once or that current card is inspected +once. Hands stay intact, target/observer insight stays null, retired +reaction/payment rejects and the original market completes to phase7 with +no repeat payment or sample. + +Real production GameTable at 390px performs target-held Semuta, acquires +Snooper, retains its own Crysknife, declares the native audit, shows Pay1/Allow, +and privately reveals only Snooper to CHOAM. The actual inspector is readable. +The owner then completes the original market with its historical snapshot +intact at phase7's Ix opening, without horizontal overflow. +Types/lint and 152 affected unit cases across twelve files pass. + +Temporary scripts, isolated server and browser tab are removed/stopped; no +saved store or deployed game was targeted. Tech and other parents remain +separate. Full CHOAM/Semuta and deployed acceptance remain open. +Contracts: [Auditor](CHOAM_AUDITOR.md#semuta-and-current-hand-eligibility--11-october-2026), +[Semuta](SEMUTA_SOURCE_UPDATE_20260907.md). + ## 11 October 2026 — quoted CHOAM income after Semuta battle discards The existing battle receipt validates the native CHOAM owner and positive diff --git a/docs/SEMUTA_DISCARD_CONTINUATIONS.md b/docs/SEMUTA_DISCARD_CONTINUATIONS.md index 41ad09a6..bae8a18a 100644 --- a/docs/SEMUTA_DISCARD_CONTINUATIONS.md +++ b/docs/SEMUTA_DISCARD_CONTINUATIONS.md @@ -51,6 +51,13 @@ cleanup, then its native allow/Karama response resumes. Allow credits once; cancellation redirects only the income and does not refund force payments. The original post-battle CHOAM market still finishes before the next phase. +Native Auditor opportunities may also remain pending across those offers. +The frozen survival limit and audited opponent's played IDs remain original; +claim/pass changes the current hand before eligibility and payment are quoted. +The authorized p29 exclusion names cards that opponent used, so an acquired +other-combatant card can be eligible. Original payment or private inspection +resumes once, with the snapshot entitled only to CHOAM. + Completed ordinary Ghola disposal may now offer Semuta during battle as well. Revival has already committed; the receipt binds the returned leader/forces and original battle, including a sealed opposing plan. Claim or decline restores @@ -191,8 +198,8 @@ winning-card cleanup uses a separate `winnerMandatoryDiscard` batch, and an optional winner selection uses a third `battleCleanup` batch. Every supported stage has a neutral all-seat offer; committed holders select one fresh card, leave the others in the pile and resume exactly -once. Native capture, Face Dance and quoted CHOAM force income remain pending -across the offer; Auditor, tech and other rewards remain automatic without Semuta. +once. Native capture, Face Dance, quoted CHOAM force income and Auditor remain +pending across the offer; tech and other rewards remain automatic without Semuta. The explicit Richese preview also accepts a fresh physical Ix deck. Clean Thumper disposal stages `thumperDiscard` before `blowSpice` diff --git a/docs/SEMUTA_SOURCE_UPDATE_20260907.md b/docs/SEMUTA_SOURCE_UPDATE_20260907.md index bf882f2f..fc9bf248 100644 --- a/docs/SEMUTA_SOURCE_UPDATE_20260907.md +++ b/docs/SEMUTA_SOURCE_UPDATE_20260907.md @@ -237,9 +237,9 @@ policy**, not a newly discovered publisher timing rule. Full-hand Semuta uses the explicit user-approved atomic exchange; its completed hand count is unchanged. Pending gifts, competing responses, completed Ornithopter arrivals with Ecaz Ambassador or Moritani Terror, -pending battle rewards other than native capture, Face Dance or quoted CHOAM -income and other typed producers retain automatic continuation; Semuta cannot -claim those events yet. +pending battle rewards other than native capture, Face Dance, quoted CHOAM +income or Auditor and other typed producers retain automatic continuation; +Semuta cannot claim those events yet. The completed Box's selected search card stays private and its paid shuffle is not repeated when another seat takes the used Box. No source ruling is inferred @@ -248,6 +248,39 @@ separate [reaction privacy audit](SEMUTA_PRIVACY_REVIEW.md) explains why auto-closing only when nobody secretly holds the card would leak possession. The main game and public expansion gates remain closed. +**11 October 2026 — native Auditor/current-hand suffix.** Mandatory and +winner discard batches now offer Semuta with an original native audit pending. +Only its reward exclusion changed; frozen survival, opponent-used IDs, +calculation and private snapshot rules are untouched. Claim/pass restores +original cleanup/income ordering before the owner offer, then the current hand +supplies eligibility and the full payment price. + +The available authorized supplied PDF, physical **p29 CHOAM E**, excludes cards +that the audited opponent used. The quote already records only those IDs; a +proposed broader all-combatant exclusion would have been wrong. A defense the +target acquires from the other combatant can remain eligible while its own +retained weapon is excluded. The original publisher URL now redirects and the +old temporary mirror is absent, so this check claims the authorized local text, +not a fresh publisher fetch. + +Both permanent regressions fail before and pass after. Twenty-four isolated +runtime cases cover target/third-party Semuta holders at mandatory/winner +producers, claim/pass, all four legal Semuta AI profiles and pay/inspect. +The killed Auditor's actual one-card quote charges one or inspects the exact +current singleton; payment changes only the two balances, inspection preserves +hands and is visible only to CHOAM. Retired reactions/audit payments reject, +and the original market completes to phase7 without another payment or sample. + +Real GameTable at 390px claims Snooper, retains the target's Crysknife, declares +the original audit and displays Pay1/Allow. Allow privately inspects only the +newly held Snooper; target/observer insight remains null, both held cards stay +intact, and the actual private inspector is readable. The original market +finishes with that snapshot intact at phase7's Ix opening, without horizontal +overflow. Temporary scripts/server/browser are removed or stopped; no saved +store or deployed game was targeted. Tech and other parents remain separate; +no complete CHOAM/Semuta or deployed acceptance is claimed. +Types/lint and 152 affected unit cases across twelve files pass. + **11 October 2026 — quoted CHOAM force-payment income.** Mandatory and winner discard batches now offer Semuta while the native Advanced income quote remains pending. Only that reward exclusion changed. The existing receipt @@ -271,7 +304,8 @@ income with Allow/Cancel controls, allows it once (CHOAM spice2→3), then finis the original market and reaches the phase7 opening with spice still3. Claimed card custody persists; there is no horizontal overflow. Temporary scripts, isolated server and browser are removed/stopped; no saved store or deployed -game was targeted. Auditor, tech and other rewards remain separate. This is +game was targeted. Auditor remained separate at this checkpoint; the later +audit continuation above admits it. Tech and other rewards remain separate. This is bounded income continuation proof, not complete CHOAM/Semuta or deployed acceptance. Types/lint and 119 affected unit cases across eight files pass. @@ -283,7 +317,8 @@ the pending native owner, original winner and territory. Claim/pass preserves casualties and winner-card cleanup before the same private opportunity; it cannot replace an army early, repeat replacement or move the claimed card back. CHOAM income was separate at this checkpoint; its later continuation above now -admits it. Auditor, tech and other reward exclusions remain separate. +admits it. The later audit continuation above also admits Auditor; tech and +other reward exclusions remain separate. The permanent regression fails before and passes after, covering both discard producers, claim/pass, one replacement, original leader death, force and card diff --git a/game/card-presentation.ts b/game/card-presentation.ts index 2fae7dcb..984bf996 100644 --- a/game/card-presentation.ts +++ b/game/card-presentation.ts @@ -40,7 +40,7 @@ export function richeseCardActionBlock(card: VisibleCard): string | null { if (definition?.card.effect === 'distrans') return 'Choose a recipient and card in the Distrans transfer panel.'; if (definition?.card.effect === 'semutaDrug') - return 'Use the neutral fresh-discard response in the Semuta development preview. Clean ordinary-card, paid Box, retired Ornithopter (before native Intrusion), Distrans, Truthtrance, four Sapho scopes, unready pre-leader Residual Poison, printed Karama canceling CHOAM Charity, Inflation or Bene Gesserit Charity, battle-card and Ix-deck Thumper/Amal discards are connected, as are private normal-auction and Black Market Ixian allied replacements. Battle discards may pause before native Advanced Harkonnen capture, Tleilaxu Face Dance or quoted CHOAM force income; original cleanup and the same opportunity resume once. A Basic Richese/Moritani CHOAM/Ecaz profile reacts to private Sabotage and Robbery overflow after clean shipment/movement arrivals. Clean reactive Baliset and La La La pause after cost without completing the stopped move or free revival. Proactive/final-Mentat Trip to Gamont preserves the exact ordinary/elite return or native No-Field reveal and capped recall, restoring its final owner window when applicable. The approved full-hand exchange spends Semuta without increasing the completed hand. Other pending rewards, producers and nested responses remain unfinished.'; + return 'Use the neutral fresh-discard response in the Semuta development preview. Clean ordinary-card, paid Box, retired Ornithopter (before native Intrusion), Distrans, Truthtrance, four Sapho scopes, unready pre-leader Residual Poison, printed Karama canceling CHOAM Charity, Inflation or Bene Gesserit Charity, battle-card and Ix-deck Thumper/Amal discards are connected, as are private normal-auction and Black Market Ixian allied replacements. Battle discards may pause before native Advanced capture, Face Dance, quoted CHOAM income or Auditor; original cleanup and the same opportunity resume once, using the current audited hand when relevant. A Basic Richese/Moritani CHOAM/Ecaz profile reacts to private Sabotage and Robbery overflow after clean shipment/movement arrivals. Clean reactive Baliset and La La La pause after cost without completing the stopped move or free revival. Proactive/final-Mentat Trip to Gamont preserves the exact ordinary/elite return or native No-Field reveal and capped recall, restoring its final owner window when applicable. The approved full-hand exchange spends Semuta without increasing the completed hand. Other pending rewards, producers and nested responses remain unfinished.'; return definition && definition.card.effect !== 'karama' ? 'This Richese card’s effect is not implemented yet. You can inspect its rules.' : null; @@ -92,7 +92,7 @@ export function cardPresentation(card: VisibleCard): CardPresentation { richese.card.effect === 'juiceOfSapho' ? 'Richese effect integration and verification are incomplete. Supported controls cover Once Around first or last while your bid remains open, bounded movement and battle-choice ordering, and aggressor during the shared pre-plan opportunity. Earlier bids stay committed. Later aggressor intervention and other phase or auction modes remain unfinished; expansion starts remain disabled.' : richese.card.effect === 'semutaDrug' - ? 'The explicit Semuta development preview offers neutral responses after clean public ordinary-card discards, paid Box disposal, retired Ornithopter movement (including a following Bene Gesserit Intrusion), Distrans, ordinary and queued definite Truthtrance answers, four Sapho scopes, unready pre-leader Residual Poison, printed Karama cancelling CHOAM Charity/Inflation/Bene Gesserit Charity or settling a normal-auction purchase or winning bid, a completed CHOAM sale, clean Kulon, proactive or reactive La La La/Baliset, proactive/final-Mentat Trip to Gamont with an ordinary/elite force or concealed No-Field, accepted Jubba Cloak before storm protection, Ix-deck Thumper/Amal, three battle discard stages (including before native Advanced Harkonnen capture, Tleilaxu Face Dance or quoted CHOAM force income) and private normal-auction or Black Market Ixian ally replacements. The Basic Richese/Moritani CHOAM/Ecaz entry adds private Sabotage and Robbery overflow after clean shipment/movement arrivals, preserving the original victim/draw and arrival suffix. Commitment is owner-only; the approved full-hand exchange spends Semuta without increasing your completed hand. Other pending rewards, producers, nested responses and normal expansion starts remain unfinished.' + ? 'The explicit Semuta development preview offers neutral responses after clean public ordinary-card discards, paid Box disposal, retired Ornithopter movement (including a following Bene Gesserit Intrusion), Distrans, ordinary and queued definite Truthtrance answers, four Sapho scopes, unready pre-leader Residual Poison, printed Karama cancelling CHOAM Charity/Inflation/Bene Gesserit Charity or settling a normal-auction purchase or winning bid, a completed CHOAM sale, clean Kulon, proactive or reactive La La La/Baliset, proactive/final-Mentat Trip to Gamont with an ordinary/elite force or concealed No-Field, accepted Jubba Cloak before storm protection, Ix-deck Thumper/Amal, three battle discard stages (including before native Advanced capture, Face Dance, quoted CHOAM income or Auditor) and private normal-auction or Black Market Ixian ally replacements. The Basic Richese/Moritani CHOAM/Ecaz entry adds private Sabotage and Robbery overflow after clean shipment/movement arrivals, preserving the original victim/draw and arrival suffix. Commitment is owner-only; the approved full-hand exchange spends Semuta without increasing your completed hand. Other pending rewards, producers, nested responses and normal expansion starts remain unfinished.' : richese.card.effect === 'mirrorWeapon' ? 'Mirror Weapon works in bounded CHOAM/Richese-deck classic-faction battles; copied Tooth and Stone choices resolve separately, and a winning physical Mirror may be kept even after copying Tooth or Artillery. This winner-retention treatment is a user-selected interpretation, not an official clarification. Combined modes and full Richese starts remain gated.' : richese.card.effect === 'stoneBurner' diff --git a/game/engine.ts b/game/engine.ts index 0245babe..77bc6b25 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -6654,7 +6654,7 @@ function treacheryDiscardIntegrity(g: Game) { } else throw new RuleError('Unknown saved discard continuation.'); } /** Bounded public single-card reaction after completed effects. Battle cleanup - * may preserve native capture, Face Dance and CHOAM income; other rewards remain gated. + * may preserve native capture, Face Dance, CHOAM income and Auditor; other rewards stay gated. * Final flight arrival may resume native Intrusion, but other entry responses remain gated. */ function semutaOfferSupported( g: Game, @@ -6702,7 +6702,7 @@ function semutaOfferSupported( if ((continuation.kind === 'battleCleanup' || continuation.kind === 'winnerMandatoryDiscard' || continuation.kind === 'battleResolved') && - (g.pendingAuditor || g.pendingTech || + (g.pendingTech || g.pendingWinnerDiscards || g.moritaniRetention || g.pendingIxSubstitution)) return false; if (continuation.kind === 'terrorDiscard' && diff --git a/game/reference.ts b/game/reference.ts index fb130abc..081a85bd 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -1175,6 +1175,7 @@ export const RULE_TOPICS: RuleTopic[] = [ 'Mandatory and winner battle discards also pause before native Advanced Harkonnen capture. Claim or Continue completes the original casualties and card cleanup before the capture offer; it does not sample a captive early or repeat capture. Other pending rewards remain outside this bounded path.', 'Mandatory and winner battle discards also pause before native Tleilaxu Face Dance. Claim or Continue preserves the original winner’s casualties and card cleanup, then restores the same private Face Dancer opportunity. Replacement, leader death and force sources remain native; the claimed Treachery card stays with its claimant, and no Face Dancer card becomes a Semuta target.', 'Mandatory and winner battle discards also pause before quoted Advanced CHOAM force-payment income. Claim or Continue leaves the original amount unpaid through casualties and card cleanup, then opens the same allow/Karama response. Allow pays once; cancellation sends that income to the bank without refunding the original force payments. The original post-battle market still completes afterward.', + 'Mandatory and winner battle discards also pause before a native Auditor opportunity. Claim or Continue preserves the original survival limit and opponent-used card identities, then eligibility and the full cancellation price use the current opposing hand. A card acquired from the other combatant can be eligible if the audited player did not use it. Payment or private inspection resumes once; only CHOAM receives the historical snapshot.', '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.', @@ -3646,7 +3647,7 @@ export const RULE_TOPICS: RuleTopic[] = [ 'An additional strength-2 leader inspects unused opposing hand cards after battle.', steps: [ 'Advanced CHOAM adds the Auditor and its matching traitor identity before setup draws. The five ordinary CHOAM leaders are unchanged. The Auditor can also appear in the remaining Face Dancer pool.', - 'Using the Auditor in a battle grants an optional inspection of two random opposing hand cards if it survives, or one if it dies. Victory is not required. Cards used in that battle are excluded, including a retained weapon, defense, hero or late Portable Snooper.', + 'Using the Auditor in a battle grants an optional inspection of two random opposing hand cards if it survives, or one if it dies. Victory is not required. Cards that opposing player used in the battle are excluded, including their retained weapon, defense, hero or late Portable Snooper. A later-acquired card used only by the other combatant can remain eligible; the current hand determines the pool.', 'Choose Audit or Decline after battle cleanup. The table completes battle income, tech claims and Harkonnen capture before this choice, then resolves Face Dancing afterward. Auditor survival is recorded from the battle itself.', 'Karama cancellation resolves first. If the power remains active, the opponent can pay CHOAM one spice per actually viewable card to cancel the entire inspection. A surviving Auditor with only one eligible card costs one spice to stop. There is no partial payment.', 'If the opponent cannot afford the full payment, inspection proceeds automatically. An empty eligible hand needs no decision. Sampling happens once after cancellation choices; no card is transferred or discarded.', diff --git a/tests/semuta-engine.test.ts b/tests/semuta-engine.test.ts index ff3503b0..e0c5a653 100644 --- a/tests/semuta-engine.test.ts +++ b/tests/semuta-engine.test.ts @@ -53,6 +53,85 @@ function fixture(ix = false, ixianAlly = false, advanced = false, opponent: 'emp return { g, semuta, hajr }; } +for (const holder of ['a', 'r'] as const) void test( + `Semuta held by ${holder} preserves the Auditor's current-hand price and opponent-used exclusions`, () => { + const { g, semuta, hajr } = fixture(true, false, true, 'choam'); + if (holder === 'a') { + player(g, 'a').hand = [semuta]; + player(g, 'r').hand = []; + g.deck.push(hajr); + } + const weapon = take(g, g.deck.find(c => c.kind === 'projectile')!.id); + const defense = take(g, g.deck.find(c => c.kind === 'snooper')!.id); + player(g, 'a').hand.push(weapon); + player(g, 'e').hand.push(defense); + for (const p of g.players) Object.assign(p, { forces: {}, reserves: 20, tanks: 0 }); + for (const id of ['a', 'e']) Object.assign(player(g, id), { forces: { 'arrakeen:10': 5 }, reserves: 15 }); + Object.assign(g, { phase: 6, active: 'a', order: ['a', 'e', 'r'], + response: null, decision: null, phaseOpening: null, movementRemaining: [], ready: [] }); + const original = inventory(g); + let state = applyAction(g, 'a', { type: 'chooseBattle', territory: 'arrakeen', target: 'e' }); + for (let step = 0; step < 40; step++) { + if (state.response) { + const actor = state.players.find(p => !state.response!.passed.includes(p.id))!; + state = applyAction(state, actor.id, { type: 'passResponse' }); + } else if (state.battle?.preparation) + state = applyAction(state, state.battle.preparation.owner, { type: 'declineBattlePower' }); + else if (state.decision?.kind === 'fullPlanOffer') + state = applyAction(state, state.decision.player, { type: 'decision', decline: true }); + else break; + } + for (const id of ['a', 'e']) + state = applyAction(state, id, { type: 'battlePreparationReady', event: state.battle!.preLeader!.event }); + state = applyAction(state, 'a', { type: 'battlePlan', dial: 2, support: 2, + leader: player(state, 'a').leaders[0].id, weapon: weapon.id }); + state = applyAction(state, 'e', { type: 'battlePlan', dial: 0, support: 0, + leader: player(state, 'e').leaders.find(isAuditorLeader)!.id, defense: defense.id }); + for (const id of ['a', 'e']) state = applyAction(state, id, { type: 'traitorCall', call: false }); + assert.equal(state.pendingTreacheryDiscard?.reaction?.stage, 'offer'); + const pass = (game: Game) => { + const event = game.pendingTreacheryDiscard!.batch.event; + for (const id of ['a', 'e', 'r']) game = applyAction(game, id, { type: 'semutaPass', event }); + return game; + }; + if (holder === 'r') { + state = pass(state); + state = applyAction(state, 'a', { type: 'decision', discard: [weapon.id] }); + assert.equal(state.pendingTreacheryDiscard?.reaction?.stage, 'offer'); + } + const event = state.pendingTreacheryDiscard!.batch.event; + for (const claim of [false, true]) { + let resumed = claim ? applyAction(reload(state), holder, { type: 'semutaCommit', event }) : pass(reload(state)); + if (resumed.decision?.kind === 'battleCards') + resumed = applyAction(resumed, 'a', { type: 'decision', discard: [] }); + assert.equal(resumed.decision?.kind, 'choamAudit'); + assert.equal(resumed.pendingAuditor?.survived, false); + const auditEvent = resumed.pendingAuditor!.event; + const expected = holder === 'r' ? hajr.id : claim ? defense.id : semuta.id; + for (const pay of [false, true]) { + let audited = applyAction(reload(resumed), 'e', { type: 'decision', event: auditEvent, audit: true }); + assert.equal(audited.decision?.kind, 'choamAuditPayment'); + const beforeTarget = player(audited, 'a').spice, beforeOwner = player(audited, 'e').spice; + audited = applyAction(audited, 'a', pay + ? { type: 'decision', event: auditEvent, pay: true, count: 1 } + : { type: 'decision', event: auditEvent, pay: false }); + assert.equal(audited.pendingAuditor, null); + assert.equal(player(audited, 'a').spice, beforeTarget - Number(pay)); + assert.equal(player(audited, 'e').spice, beforeOwner + Number(pay)); + if (pay) assert.equal(audited.auditorInsight == null, true); + else assert.deepEqual(audited.auditorInsight!.cards.map(c => c.id), [expected]); + assert.equal(viewGame(audited, 'r').auditorInsight, null); + assert.equal(viewGame(audited, 'a').auditorInsight, null); + assert.deepEqual(inventory(audited), original); + assert.throws(() => applyAction(audited, holder, { type: 'semutaCommit', event })); + assert.throws(() => applyAction(audited, 'a', { type: 'decision', event: auditEvent, pay: true, count: 1 })); + const closed = applyAction(audited, 'e', { type: 'decision', done: true }); + assert.equal(closed.phase, 7); + assert.deepEqual(inventory(closed), original); + } + } + }); + void test('Semuta preserves quoted CHOAM battle income through original allowance or cancellation', () => { const { g, semuta, hajr } = fixture(true, false, true, 'choam'); player(g, 'a').hand = [];