From d1ba8e0c7e47356443673bd7fbb3863816cd5b85 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:46:17 +1100 Subject: [PATCH] Activate approved normal Mentat and document Emperor gift ruling --- README.md | 12 ++--- docs/BUREAUCRAT_PAYMENTS.md | 21 +++++--- docs/CURRENT_STATUS.md | 29 +++++++++-- docs/IMPLEMENTATION_STATUS.md | 35 +++++++++++++ docs/LEADER_SKILLS_RUNTIME.md | 23 +++++---- docs/MENTAT_QUESTION.md | 44 +++++++++++------ docs/MORITANI_ASSASSINATE_LEADERS.md | 9 ++-- docs/RULE_DECISIONS.md | 19 +++++-- game/discovery-module-profile.ts | 2 +- game/engine.ts | 14 ++---- game/leader-skill-profile.ts | 7 ++- game/mentat-question.ts | 7 +-- game/reference.ts | 6 +-- tests/advanced-native-skills.test.ts | 4 ++ tests/advanced-preview.test.ts | 1 - tests/bureaucrat-payment.test.ts | 25 ++++++++++ tests/choam-skills-ghola-fixture.ts | 2 + tests/choam-skills-integration.test.ts | 6 +++ tests/discovery-module-profile.test.ts | 5 +- ...discovery-native-e2-skills-runtime.test.ts | 1 - tests/faction-games-cli.test.ts | 1 - tests/fixture-discovery-native-e2-skills.ts | 2 +- tests/fixture-discovery-native-e3-skills.ts | 2 +- ...re-discovery-standalone-nexus-e3-skills.ts | 2 +- tests/fixture-moritani-skills-modules.ts | 1 + tests/fixture-native-mentat.ts | 3 +- tests/ix-skills-battle.test.ts | 2 + tests/leader-skills-capture.test.ts | 7 +++ tests/leader-skills-engine.test.ts | 18 +++++++ tests/leader-skills-recovery.test.ts | 11 +++++ tests/mentat-question-fixture.ts | 4 -- tests/mentat-question.test.ts | 49 ++++++++----------- tests/moritani-skills-battle.test.ts | 4 +- tests/native-mentat.test.ts | 5 +- tests/tleilaxu-skills-integration.test.ts | 1 + tests/tleilaxu-skills-review.test.ts | 2 + tests/tleilaxu-skills-revival.test.ts | 2 + tools/faction-games.ts | 3 +- tools/prototype-room.ts | 5 -- tools/start-prototype.ts | 12 ++--- 40 files changed, 270 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 97d754a8..4ca3ca82 100644 --- a/README.md +++ b/README.md @@ -141,12 +141,12 @@ cyborg movement, HMS routes and battle aftermath through the same full deck; see connects ordinary skill actions and saved market/revival choices; see [CHOAM integration](docs/CHOAM_LEADER_SKILLS.md). -For the private **Mentat question preview**, add `--mentat-question` to the -existing `--profile leader-skills` entry on a fresh ready lobby. It supports -existing classic/CHOAM skill profiles and Basic Ixian/Tleilaxu/CHOAM/Moritani -native starts without changing ordinary activation. The uniform-response UX -and empty-hand ruling remain pending; see -[native preview scope and controls](docs/MENTAT_QUESTION.md#native-skill-integration--2-october-2026). +Normal **Mentat questions** now use the user-approved uniform private +card-show step in supported Leader Skills development games. Use the +existing `--profile leader-skills` entry; no separate question flag is +needed. Exact matching and fallback both use the same private step. +Empty-hand interpretation and public start gates remain unfinished; see +[normal activation and controls](docs/MENTAT_QUESTION.md#normal-activation-and-approved-ux--11-october-2026). For local printed **Kull Wahad** testing, use `node --import tsx tools/start-prototype.ts --profile kull` on a fresh ready Basic/Advanced CHOAM-plus-classic lobby with `choam,ix` decks diff --git a/docs/BUREAUCRAT_PAYMENTS.md b/docs/BUREAUCRAT_PAYMENTS.md index a4289f31..55eec5e6 100644 --- a/docs/BUREAUCRAT_PAYMENTS.md +++ b/docs/BUREAUCRAT_PAYMENTS.md @@ -37,8 +37,18 @@ The connected paths are ordinary paid auctions, Guild shipment income, direct Richese auction proceeds and actual bribe transfers in the base or CHOAM-only skill profile. Basic Tleilaxu with Leader Skills and base opponents additionally supports a single payer's actual paid revival price to Tleilaxu. Other expansion -payment sources and direct gifts remain outside this batch; omission is not a -printed prohibition. +payment sources remain outside this batch. Emperor gifts are excluded because +the user ruled on 11 October that gifts are not payments. + + +### User ruling — 11 October 2026 + +**Gifts are not payments.** Emperor-to-ally gifts do not qualify for +Bureaucrat, consume its once-per-phase use or create a qualifying-threshold +prompt. Gift amounts remain private to giver and recipient. Ordinary paid +auctions, shipments, revival and bribes keep their existing eligible paths. +This explicit user classification resolves the earlier gift question; it +does not change any gift transfer cost or disclose more information. ### 21 September gift classification review @@ -52,10 +62,9 @@ amounts are projected only to Emperor and recipient. Even a threshold-only Bureaucrat prompt would reveal that the gift qualifies; the existing exact-amount payment projection must not be applied blindly. -The user has been asked whether to retain this boundary or apply Bureaucrat -with only the qualifying threshold public. No answer is assumed. Gifts remain -outside the connected Bureaucrat payment paths while independent ordinary -[bribe controls](BRIBE_CONTROLS.md) proceed. +The earlier question is now resolved by the 11 October ruling above. +Gift classification stays separate from the connected ordinary +[bribe controls](BRIBE_CONTROLS.md). For native income, the prototype resolves the existing Karama income window first, then offers Bureaucrat before crediting the eligible recipient and diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 3f45d669..afdab05d 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -1,6 +1,6 @@ # Current development status -Updated 8 October 2026. **The full twelve-faction goal is unfinished. Basic +Updated 11 October 2026. **The full twelve-faction goal is unfinished. Basic is playable and the six classic factions have a user-authorized Advanced preview. Expansion starts and full rules/publication acceptance remain gated.** @@ -29,6 +29,25 @@ optional skill bands, combinations and public expansion starts remain open. Rules implementation continues; comprehensive assurance and administration remain deferred until all rules are implemented. +**Approved skill decisions — 11 October.** Normal Mentat now uses the approved +uniform private card-show step throughout the existing supported Leader Skills +envelope; the separate flag and CLI option are removed. Newly opened battles +carry the question version. Already opened unversioned battles do not reopen. +The exact-match and sole-card target steps stay private; empty-hand skip and +Moritani's separate uniform-Continue decision remain unresolved. Emperor gifts +are explicitly not Bureaucrat payments: no threshold signal or skill consumption, +and exact gift amounts remain private to sender and ally. Other payments retain +their existing behavior. + +TypeScript/lint pass; the affected 20-file union passes 217 cases, and the two +changed recovery/module fixture consumers pass 11 cases. Nine isolated engine +scenarios cover Basic/Advanced matching/fallback shows, all four legal bots, +and a private 7-spice gift followed by a qualifying 5-spice bribe. Real 390px +controls show the compulsory private card step and owner-only receipt without +changing the target hand. CLI help uses normal activation and rejects the removed +option. No deployed or complete-mode acceptance is claimed. See +[Mentat](MENTAT_QUESTION.md) and [Bureaucrat](BUREAUCRAT_PAYMENTS.md). + **Owned HMS Sandmaster piles — 9 October.** Storm relocation and Advanced Ixian special Karama now select one bonus debit pile per entered territory, or decline that bonus while retaining native collection. Existing preparation @@ -338,10 +357,10 @@ implemented: points into, so Sandmaster collects there before the native collection (`game/sandmaster-movement.ts`, `game/engine.ts`). -The remaining recorded questions (Bureaucrat/Emperor gifts, Recruits late -accounting, ordinary Guild cancellation, base special Karama, Nexus -composition, Orgiz, Testing Station, Jacurutu, occupied Homeworlds and the -Homeworld restoration questions) stay pending. +The earlier remaining questions were Recruits accounting, ordinary Guild +cancellation, base special Karama, Nexus composition, Discovery benefits +and Homeworld interpretation/restoration. The Bureaucrat/gift question was +later resolved on 11 October: gifts are not payments. **Preserved captures resumed under the rulings.** All 123 preserved `failed-*.json` captures were resumed in place (same position, seed and diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index a80bd58e..a6944121 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,40 @@ # Dune implementation status +## 11 October 2026 — approved normal Mentat and Emperor gift classification + +The user approved normal Mentat's uniform private card-show step in the existing +Leader Skills development envelope. `mentatQuestionModeSupported` now follows +the ordinary supported profile; new battles receive the existing question version +without a separate global flag. The flag, profile exclusions, CLI option and +fixture opt-in are removed. Previously opened unversioned battles remain +unversioned. Matching and sole-card hands still require the private target show; +the empty-hand skip and Moritani's independent uniform-Continue question are not +resolved by this approval. Public faction/mode gates remain closed. + +The user also ruled Emperor gifts are not payments for Bureaucrat. The existing +gift implementation already excludes them; no production gift behavior changed. +The regression verifies a 7-spice gift remains private to Emperor/ally, does not +offer or consume Bureaucrat, and a later 5-spice bribe still offers the skill. +Other qualifying payment behavior is unchanged. + +Evidence: + +- `npm run check:quick` passes. The affected 20-file test union passes 217/217; + `leader-skills-recovery.test.ts` plus + `moritani-skills-modules-runtime.test.ts` pass 11/11. +- Nine throwaway engine scenarios pass: Basic/Advanced matching and fallback, + all four legal AI participants, and gift/bribe continuation. +- Real production controls at 390px show the normal question, compulsory exact + match show and private owner history; the observer sees no receipt and the + target's two-card hand is unchanged. +- Actual CLI help advertises normal activation. `--mentat-question` rejects + as an unknown option with exit 1 before accessing any game store. + +Temporary smoke scripts/services were removed. Existing games and stores were +not targeted. This is changed-path proof, not deployed or complete-mode acceptance. +Source and decisions: [Mentat](MENTAT_QUESTION.md), +[Bureaucrat](BUREAUCRAT_PAYMENTS.md), [rulings](RULE_DECISIONS.md). + ## 9 October 2026 — owned native HMS Sandmaster pile allocation Both native Storm relocation and Advanced special Karama carry optional diff --git a/docs/LEADER_SKILLS_RUNTIME.md b/docs/LEADER_SKILLS_RUNTIME.md index 31f4bae1..af5b68b5 100644 --- a/docs/LEADER_SKILLS_RUNTIME.md +++ b/docs/LEADER_SKILLS_RUNTIME.md @@ -193,14 +193,13 @@ and two preserved six-seat CLI/human-phone rooms pass; local HTTP remains 49/55 with six upstream503 failures. Full-module/deployed acceptance stays open; see the [existing Banker normal-band contract](SPICE_BANKER_RUNTIME.md#normal-income-local-contract--2-october-2026). -**2 October native Mentat checkpoint:** explicit fresh -`--profile leader-skills --mentat-question` connects the existing private -normal-band question to supported Basic Ixian/Tleilaxu/CHOAM/Moritani starts. -Canonical exact/default weapon roles, target-owned fallback, posture and native -battle continuation use the existing controls and legal policies. Ordinary -activation still awaits the uniform-response UX decision; empty hands and -Advanced native/combined-module skills remain guarded. See -[native preview scope](MENTAT_QUESTION.md#native-skill-integration--2-october-2026). +**2 October native Mentat checkpoint:** the formerly separate opt-in connected +private normal-band questions to Basic Ixian/Tleilaxu/CHOAM/Moritani starts. +The 11 October user approval now activates the same uniform flow normally +through supported skill entries. Exact/default weapon roles, target-owned +fallback, posture and native continuation remain. Empty-hand interpretation +and complete/public mode acceptance stay open. See +[normal activation](MENTAT_QUESTION.md#normal-activation-and-approved-ux--11-october-2026). ## Standalone E3 Nexus and original components — 5 October 2026 @@ -735,10 +734,10 @@ CHOAM keeps its original support payer/income distinction and unmodified Smuggler collection before ordinary Collection. Existing Planetologist, Sandmaster, Mentat, Bureaucrat and Banker helpers share this admitted native profile rather than independently accepting a different roster. -Explicit `--mentat-question` remains a private timing preview; ordinary -activation and empty-hand rulings are not resolved. The separate `banker-income` -profile also admits this native Advanced subset; normal front-shield income -remains unavailable until actual Mentat and keeps its provisional earned custody. +The 11 October user-approved normal Mentat question now shares this admitted +skill envelope; the obsolete timing opt-in is removed. Empty-hand +interpretation remains separate. The `banker-income` profile retains its +existing deferred front-shield custody and actual-Mentat spendability. Bounded Development Verified: affected122/122, types/lint/build, ten actual controlled native programs (five Ixian rescue/policy cases, one CHOAM collection, diff --git a/docs/MENTAT_QUESTION.md b/docs/MENTAT_QUESTION.md index 7969f4b5..1a32fc19 100644 --- a/docs/MENTAT_QUESTION.md +++ b/docs/MENTAT_QUESTION.md @@ -1,18 +1,31 @@ # Mentat before Battle Plans -13 September 2026. This developer preview adds the normal Mentat question to -the existing lower-band strength bonus. It requires an explicit private preview -opt-in while the user decides whether to accept the uniform card-show step. -Ordinary Leader Skills starts do not activate it. Leader Skills and public mode -acceptance remain partial. [Common skill rules](LEADER_SKILLS_RULES.md) govern assignment, -capture, death and the right to conceal a skilled leader. +**Normal activation approved 11 October 2026.** The user approved the existing +uniform private card-show step, including exact matches and sole eligible cards. +New supported Leader Skills battles now offer the normal Mentat question +without a separate preview flag. Public module/mode acceptance remains partial. +[Common skill rules](LEADER_SKILLS_RULES.md) govern assignment, capture, death and the right to conceal a skilled leader. + +## Normal activation and approved UX — 11 October 2026 + +Normal questions use the existing admitted Leader Skills envelope. Naming +and the compulsory target-owned private show step still precede posture +and faction powers. This approved extra interaction prevents public stages +from disclosing held-versus-absent information. It moves no physical card. + +The global preview flag and CLI option are removed. Per-battle version, +event, signed receipt and private observation history remain authoritative. +Already-opened unversioned battles retain their original continuation; +existing versioned questions continue without the obsolete global flag. +Empty-hand skipping remains the earlier explicit unfinished interpretation. + **4 October composition:** the explicit preview also admits the bounded Advanced native Ixian and/or CHOAM plus classic profile with distinct required family decks. Actual private naming/disclosure returns to native posture and keeps the shown card held; unrelated players receive no learned card. See the [current native contract](LEADER_SKILLS_RUNTIME.md#advanced-native-ixian-and-choam-composition--4-october-2026). -Ordinary activation, empty hands and other pending rulings remain unchanged. +The 11 October normal-activation cutover above supersedes the old opt-in; other boundaries remain unchanged. The bounded [Advanced Tleilaxu composition](TLEILAXU_LEADER_SKILLS.md#advanced-native-composition--4-october-2026) now uses the same explicit preview; original foreign revival does not assign a new Mentat or any other skill. @@ -28,9 +41,8 @@ fourteen skills. Existing classic/CHOAM Basic/Advanced preview coverage remains; this does not admit Advanced Ixian/Tleilaxu/Moritani skills or extra modules. For a fresh ready local lobby, use the existing exact-version/private-backup -entry with `--profile leader-skills --mentat-question`. Without the explicit -option, ordinary skill starts still do not activate the question. The option -cannot be applied through a player action or to a started game. +entry with `--profile leader-skills`. The normal question is part of supported +skill behavior; no separate question option or player action enables it. The same canonical question and target-owned answer controls handle exact Ix weapon names, including **Weirding Way**, with its default weapon role. @@ -43,8 +55,8 @@ Five new behavioral regressions cover all four native families and all four legal policies. A separate runtime driver exercises eight native exact/fallback battles through Collection plus four policy inquiries: twelve cases pass. The earlier Ixian regression failed at the missing question before admission. -The uniform-response UX and empty-hand questions below remain pending; no -public module gate or old-game retrofit is enabled. +The uniform-response UX was approved on 11 October; the empty-hand question +remains pending. No public module gate or old-battle reopening is enabled. Final local390px **8HTTTZMA v9** names Weirding Way, shows it privately, inspects the learned physical card and conceals the native Mentat. Genuine @@ -112,10 +124,10 @@ answers would reveal held/absent information through different public stages. Target controls alone receive the eligible physical cards; public decision shape, logs and number of response steps stay the same across equal-size hands. -That uniform step is an exception to the user's preference to omit forced -confirmations. The user has been asked to approve it or leave the ability disabled -pending another design. No answer has been applied. Private QA can exercise the -reviewable preview; normal activation and any claim of approved UX remain pending. +The uniform step is an exception to the user's usual forced-confirmation +preference. **Approved 11 October 2026:** use this private card-show step in +normal supported Leader Skills battles. Moritani's separate uniform-step +question is not answered by this approval. ## Preview evidence and remaining acceptance diff --git a/docs/MORITANI_ASSASSINATE_LEADERS.md b/docs/MORITANI_ASSASSINATE_LEADERS.md index 6527d084..482c17e8 100644 --- a/docs/MORITANI_ASSASSINATE_LEADERS.md +++ b/docs/MORITANI_ASSASSINATE_LEADERS.md @@ -76,10 +76,11 @@ Every publicly qualifying loss gets the same private reveal-or-continue opportun regardless of the hidden eligible card. Only Moritani sees its candidate identities and blocked reason; rivals see the same decision and no private options. This requires Continue even for an ineligible hand, conflicting with the user's usual -automatic-action preference. A uniform-private-step UX question is pending, shared -with Mentat. Until resolved, activation requires an explicit development preview; -no ordinary action or room API enables it. A publicly spent faction or prior -normal traitor call can be skipped without consulting hidden eligibility. +automatic-action preference. Moritani's uniform-private-step approval remains +separate: the 11 October Mentat approval applies only to that card-show flow. +This ability still requires its explicit development preview; no ordinary +action or room API enables it. +A publicly spent faction or prior normal traitor call can be skipped without consulting hidden eligibility. All four AI profiles use the same owner-only quote. They choose a legal card or decline and do not inspect rival hidden state. The public history shows only diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md index e178ceed..84295137 100644 --- a/docs/RULE_DECISIONS.md +++ b/docs/RULE_DECISIONS.md @@ -1,6 +1,20 @@ # Rule decision index -Updated 8 October 2026. This is the authoritative navigation index for existing decisions, not a replacement rulebook or a new ruling. Detailed linked contracts retain their source citations and exact supported boundaries. Runtime follow-ups supersede historical absence claims; passing tests establish software behavior, not publisher authority. +Updated 11 October 2026. This is the authoritative navigation index for existing decisions, not a replacement rulebook or a new ruling. Detailed linked contracts retain their source citations and exact supported boundaries. Runtime follow-ups supersede historical absence claims; passing tests establish software behavior, not publisher authority. + +## User decisions — 11 October 2026 + +- **Normal Mentat:** approve the existing uniform private card-show step, + including exact matches and sole eligible cards. Activate it normally in + supported Leader Skills development games; remove the separate question + opt-in. Older opened unversioned battles retain their original sequence. + [Approved flow](MENTAT_QUESTION.md#normal-activation-and-approved-ux--11-october-2026). +- **Bureaucrat / Emperor gifts:** gifts are not payments. They create no + Bureaucrat opportunity or qualifying-threshold disclosure; exact amounts + remain private to giver/recipient. Other payment paths are unchanged. + [Gift ruling](BUREAUCRAT_PAYMENTS.md#user-ruling--11-october-2026). + +These decisions do not open public module/faction or complete-mode gates. ## Content-first Basic prototypes — 8 October 2026 @@ -421,7 +435,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP | Native E1/E2 Leader Skills / Tech Tokens | Fresh supported Ixian/Tleilaxu/CHOAM/Richese3..6 Basic/Advanced keeps original native deck/roster limits, all14 skills, cache and printed Tech setup. Original typed payments/rescue/card cleanup precede mandatory winner token reward; Tleilaxu A.1–3 expressly preserves that reward before Face Dance. Actual Charity affects Production, not Axlotl; native CHOAM initial income is not poverty Charity. Separate Banker/Mentat bounds and unrelated guards remain. E3/other overlays, public starts and save conversion are excluded. This composes sourced rules, not a new interpretation. | [Scope, supplied physical21/38 and evidence](LEADER_SKILLS_RUNTIME.md#native-e1e2-tech-tokens-composition--4-october-2026) | | Advanced Leader Skills / Stronghold Cards | Fresh classic or supported E1/E2 native2..6 Advanced preserves all14 skills, six unused cards, original decks/cache/wallets and actual end-Mentat claims; optional Tech requires3..6. Arrakeen bank subsidy is separate from actual payer debit/Banker threshold and original CHOAM income. Physical defense/normal discipline, holder tie and Tabr/Tuek income compose original skill outcomes. Suk/native cleanup and mandatory Tech precede Face Dance, retaining original rewards and one killed-skill return. Copied-Diplomat/active skill-Stone and unrelated guards remain. Basic Strongholds, E3/other overlays, public starts and save conversion are excluded. | [Authorized physical21/38, existing effects and evidence](LEADER_SKILLS_RUNTIME.md#advanced-stronghold-composition--4-october-2026) | | Standalone E3 Skills / Tech / Stronghold Cards | Original Ecaz OR Moritani ecaz33/all14 rosters preserve Tech Basic/Advanced3..6 and Stronghold Advanced2..6, optionally both. Native Ecaz five-disc/temporary Duke/Advanced-Hark exclusion and Moritani skill-first assassination/normal-call forfeiture remain. Real claimed effects, original support/rescue, printed Duke6 or unused-disc bounty and one Mentat replacement compose native rewards without new ownership or income rules. E3 pairs, mixed families, allied Occupy skills, other overlays, public starts and save conversion remain excluded. | [Source contracts and bounded evidence](LEADER_SKILLS_RUNTIME.md#standalone-e3-module-composition--4-october-2026) | -| Native Mentat preview | Explicit fresh `leader-skills --mentat-question` reuses the existing private uniform question for already-supported Basic Ixian/Tleilaxu/CHOAM/Moritani profiles. Exact/default card roles, held fallback and skill-before-faction timing are unchanged. Ordinary/public activation, empty-hand handling, Advanced native skills and other modules stay gated; this is integration, not a new answer to the pending UX question. | [Native integration and existing authority](MENTAT_QUESTION.md#native-skill-integration--2-october-2026) | +| Normal Mentat | The 11 October user-approved uniform private show step runs before posture/faction powers in new supported Leader Skills battles, without an extra opt-in. Matching and fallback preserve the same target-owned stage and held cards. Already-opened unversioned battles do not reopen; versioned receipts remain authoritative. Empty-hand interpretation and full/public acceptance remain separate. | [Approved normal flow](MENTAT_QUESTION.md#normal-activation-and-approved-ux--11-october-2026) | | Normal Spice Banker local preview | Fresh classic/base33 Basic/Advanced or supported Basic native skill profiles, all14 skills alone. One additional deferred bank spice per physical card/turn/phase for another payer's one actual bank payment ≥4; no payer pooling or advance spending. Tleilaxu self-revival pays Bank; other-player revival pays Tleilaxu unless canceled. The separate free-revival award never contributes to payer debit. Actual initialized Mentat collects once; original earned-faction retention and separate-payer threshold remain provisional local policies, not answers to pending custody/split questions. No public start, retrofit or other overlays. | [Normal-band contract](SPICE_BANKER_RUNTIME.md#normal-income-local-contract--2-october-2026), [native integration](SPICE_BANKER_RUNTIME.md#basic-native-banker-integration--2-october-2026), [skill authority](LEADER_SKILLS_RULES.md) | | Advanced Stronghold faction integration | Fresh `stronghold-factions` admits selected E1/E2 native families and standalone Ecaz OR Moritani with classic2..6-seat Advanced, canonical selected decks and six Stronghold Cards; optional unused Tech Tokens requires3..6, without Leader Skills. Standalone E3 uses ecaz33; Moritani excludes Harkonnen. Ecaz co-control holds the card at Ecaz, but only the selected actual holder-plan receives its effect; fixed Ecaz strength is free, and Arrakeen funds only actual variable support. Losing Moritani retains printed Tuek income before original assassination, printed bounty and one actual Mentat replacement; winner cleanup finishes once and normal Traitor reveal forfeits assassination for the game. Existing E1/E2 No-Field/Stone/FaceDance and retained winner rewards remain. E3 pairs/mixed families, mixed No-Field/full-plan, Stone/Poison Tooth and other overlays stay guarded. This composes existing contracts, not a new ruling. Save/privacy/custody assurance follows all rules. | [E1/E2 scope](STRONGHOLD_CARDS.md#expanded-e1e2-faction-rules), [standalone E3 scope and evidence](STRONGHOLD_CARDS.md#standalone-e3-stronghold-composition--4-october-2026), [native Tech composition](EXPANSION_FACTIONS_PROTOTYPE.md#native-tech-composition--4-october-2026) | | Native Tech without Leader Skills | Fresh3..6 Basic/Advanced selected E1/E2 native families or standalone Ecaz OR Moritani plus classics preserve original unused three Tech Tokens through `factions`; Advanced Strongholds use the existing native entry. Physical setup, printed/first-Storm owners, phase-end income and single-player complete-set victory are unchanged. Actual Ecaz selected lead receives the original opposing token, never a joint allied holding; Basic odd Occupy remains guarded. Moritani original forfeiture/assassination and winner Tech before Face Dance reuse existing handlers. No new ruling, wallet grant, public start or save conversion. | [Original composition/source and evidence](EXPANSION_FACTIONS_PROTOTYPE.md#native-tech-composition--4-october-2026), [Stronghold contract](STRONGHOLD_CARDS.md#native-tech-composition-without-skills--4-october-2026) | @@ -470,7 +484,6 @@ These are unresolved in the cited records. Do not infer an answer from elapsed t | Boundary | Still unresolved | Existing record | | --- | --- | --- | -| Bureaucrat / Emperor gifts | Whether an allied gift is a qualifying payment and whether its threshold may be revealed. Existing gift amounts are private to payer/recipient; the card and retrieved primary sources do not explicitly settle this combination. The user question is pending; no answer or exact-amount disclosure is assumed. | [Gift classification review](BUREAUCRAT_PAYMENTS.md#21-september-gift-classification-review) | | Reinforcements cost | The user question still asks whether to keep the card disabled, always transfer three reserve forces on every outcome, or waive only a sole traitor-call winner's cost; no answer is assumed. The bounded classic and Ecaz/Moritani prototype provisionally charges on all revealed outcomes, chooses normal reserves before elite and counts the transfer as battle losses. Those are product interpretations, not a ruling. Same-plan Harass/Stone Burner remain gated; opposing Harass is supported. Eligibility never depends on concealed matching Traitors. | [Source cost review](ECAZ_TREACHERY_RULES.md#14-september-reinforcements-cost-review) · [Bounded runtime](REINFORCEMENTS_RUNTIME.md) | | Harass / Stone Burner | A user question has been sent: keep the combination gated or compare forces remaining after withdrawal. No answer is assumed. The Richese card-family configuration is guarded publicly; hidden Stone Burner custody never controls eligibility. Ambiguous physical allocations and other optional modules remain unfinished. | [Current boundaries](HARASS_WITHDRAW_RUNTIME.md#current-boundaries) | | Recruits late accounting and rates | The earlier one/two-further-free question remains unanswered. The content-first prototype now preserves paid spice, deducts recorded actual free usage from the doubled rate and all returns from the total cap: two-free/one-paid Atreides can take two more free. This is visibly provisional, not a numerical user ruling. Unknown ledgers, pending repricing, late rate changes and same-turn replay remain guarded. | [Source alternatives](RECRUITS_RULES.md), [provisional runtime](RECRUITS_RUNTIME.md) | diff --git a/game/discovery-module-profile.ts b/game/discovery-module-profile.ts index e899a272..e1b7cdaa 100644 --- a/game/discovery-module-profile.ts +++ b/game/discovery-module-profile.ts @@ -63,7 +63,7 @@ export function classicDiscoveryLeaderSkillsProfile(game: LeaderSkillProfile): b game.moritaniAssassinatePreview || game.advancedPreview || game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview || game.richeseBetrayalPreview || game.nexusIxianReplacementPreview || game.nexusIxianBetrayalPreview || - game.nexusHarkonnenBetrayalPreview || game.mentatQuestionPreview || + game.nexusHarkonnenBetrayalPreview || game.spiceBankerIncomePreview) return false; for (let i = 0; i < players.length; i++) { for (let j = 0; j < i; j++) if (players[j].faction === players[i].faction) return false; diff --git a/game/engine.ts b/game/engine.ts index 87a5b02a..859febae 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -2028,8 +2028,6 @@ export type Game = { }}; bureaucratPaymentEvent?: string; bureaucratUseEvents?: string[]; - /** Private development opt-in pending the uniform Mentat response UX decision. */ - mentatQuestionPreview?: true; /** Private development opt-in while uniform hidden-eligibility UX is pending. */ moritaniAssassinatePreview?: true; /** Explicit development-only neutral Semuta reaction profile. */ @@ -7508,7 +7506,7 @@ function baseStartProfileBlock(g: Game): string | null { return 'Discoveries are still being implemented.'; if (g.leaderSkills) return 'Leader Skills are still being implemented.'; if (g.ecazTreachery) return 'Ecaz Treachery Cards are still being implemented.'; - if (g.mentatQuestionPreview || g.moritaniAssassinatePreview || g.moritaniAssassinate) + if (g.moritaniAssassinatePreview || g.moritaniAssassinate) return 'Separate development profiles cannot be enabled by a normal start.'; if (!g.players.every(p => FACTIONS.some(entry => entry.id === p.faction && entry.expansion === 'base'))) return 'Expansion factions are still being implemented.'; @@ -7969,7 +7967,7 @@ function bankerIncomeProfile(g: Game): boolean { nativeExpansionLeaderSkillsProfile(g)) && !g.nexusCards && !g.homeworlds && (!g.techTokens || classicTechLeaderSkillsProfile(g) || nativeTechLeaderSkillsProfile(g) || strongholdLeaderSkillsProfile(g)) && (!g.strongholdCards || strongholdLeaderSkillsProfile(g)) && !g.discoveryEnabled && !g.discoveries && !g.discoveryStash && - !g.greatMaker && !g.ecazTreachery && !g.semutaPreview && !g.mentatQuestionPreview && + !g.greatMaker && !g.ecazTreachery && !g.semutaPreview && ((!g.moritaniAssassinatePreview && !g.moritaniAssassinate) || advancedMoritaniLeaderSkillsProfile(g)) && !g.advancedPreview && !g.kullPreview && !g.nexusKullPreview && !g.guildBetrayalPreview && !g.richeseBetrayalPreview && !g.nexusIxianReplacementPreview && !g.nexusIxianBetrayalPreview && !g.nexusHarkonnenBetrayalPreview; @@ -11483,8 +11481,6 @@ function createMentatQuestion(g: Game) { b.mentatQuestionEvent = event; } function mentatQuestionIntegrity(g: Game) { - requireRule(g.mentatQuestionPreview === undefined || g.mentatQuestionPreview === true, - 'The private Mentat preview flag is invalid.'); const history = g.mentatHistory, events = g.mentatHistoryEvents; requireRule(history === undefined && events === undefined || Array.isArray(history) && Array.isArray(events) && history.length === events.length && new Set(events).size === events.length && new Set(history.map(observation => observation?.event)).size === history.length, @@ -11506,11 +11502,11 @@ function mentatQuestionIntegrity(g: Game) { if (!receipt) { requireRule(receipt === undefined && !b?.mentatQuestionEvent && !decisions.length, 'The saved Mentat question lost its original receipt.'); - requireRule(b?.mentatQuestionVersion === undefined || b.mentatQuestionVersion === 1 && g.mentatQuestionPreview === true && !!g.leaderSkills, + requireRule(b?.mentatQuestionVersion === undefined || b.mentatQuestionVersion === 1 && !!g.leaderSkills && mentatQuestionModeSupported(g), 'The saved Mentat question has an invalid battle version.'); return; } - requireRule(b && b.mentatQuestionVersion === 1 && g.mentatQuestionPreview === true && g.leaderSkills && mentatQuestionModeSupported(g) && + requireRule(b && b.mentatQuestionVersion === 1 && g.leaderSkills && mentatQuestionModeSupported(g) && g.status === 'playing' && g.phase === 6 && receipt.battle === b.event && receipt.turn === g.turn && receipt.territory === b.territory && typeof receipt.event === 'string' && receipt.event.length > 0 && receipt.event === b.mentatQuestionEvent && [b.attacker,b.defender].includes(receipt.owner) && [b.attacker,b.defender].includes(receipt.target) && receipt.owner !== receipt.target && @@ -31548,7 +31544,7 @@ function applyActionInner( chooserEvent: battleChooserEvent(g.turn, battleOrderAfterBattle(g), g.battleOrder?.uses.length ?? 0), event: battleEvent, ...(g.leaderSkills ? { smugglerCollectionVersion: 1 as const } : {}), - ...(g.mentatQuestionPreview === true && g.leaderSkills && mentatQuestionModeSupported(g) ? { mentatQuestionVersion: 1 as const } : {}), + ...(g.leaderSkills && mentatQuestionModeSupported(g) ? { mentatQuestionVersion: 1 as const } : {}), ...(g.leaderSkills && diplomatDefenseModeSupported(g) ? { diplomatDefenseVersion: 2 as const } : {}), ...(g.leaderSkills && ordinaryLeaderSkillModeSupported(g) ? { diplomatRetreatVersion: 1 as const } : {}), ...(g.ecazTreachery ? { harassAllocationVersion: 1 as const } : {}), diff --git a/game/leader-skill-profile.ts b/game/leader-skill-profile.ts index c7a4d4fa..0307ddf8 100644 --- a/game/leader-skill-profile.ts +++ b/game/leader-skill-profile.ts @@ -26,7 +26,6 @@ export type LeaderSkillProfile = { nexusIxianReplacementPreview?: unknown; nexusIxianBetrayalPreview?: unknown; nexusHarkonnenBetrayalPreview?: unknown; - mentatQuestionPreview?: unknown; spiceBankerIncomePreview?: unknown; }; @@ -47,7 +46,7 @@ function nexusLeaderSkillModulesSupported(game: LeaderSkillProfile, assassinatio !game.advancedPreview && !game.kullPreview && !game.nexusKullPreview && !game.guildBetrayalPreview && !game.richeseBetrayalPreview && !game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview && - !game.nexusHarkonnenBetrayalPreview && !game.mentatQuestionPreview && + !game.nexusHarkonnenBetrayalPreview && !game.spiceBankerIncomePreview; } @@ -189,7 +188,7 @@ function discoveryLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean !game.advancedPreview && !game.kullPreview && !game.nexusKullPreview && !game.guildBetrayalPreview && !game.richeseBetrayalPreview && !game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview && - !game.nexusHarkonnenBetrayalPreview && !game.mentatQuestionPreview && + !game.nexusHarkonnenBetrayalPreview && !game.spiceBankerIncomePreview; } @@ -465,7 +464,7 @@ function homeworldLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview || game.richeseBetrayalPreview || game.nexusIxianReplacementPreview || game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview || - game.mentatQuestionPreview || game.spiceBankerIncomePreview) return false; + game.spiceBankerIncomePreview) return false; return true; } diff --git a/game/mentat-question.ts b/game/mentat-question.ts index 1de023e8..1fcaaec1 100644 --- a/game/mentat-question.ts +++ b/game/mentat-question.ts @@ -1,7 +1,7 @@ import { treacheryDeck, type Card } from './cards'; import { richeseCards } from './richese-cards'; import { quoteTupileIntelligenceAnswer } from './tupile-intelligence-answer'; -import {classicTechLeaderSkillsProfile,nativeExpansionLeaderSkillsProfile,nativeTechLeaderSkillsProfile,noOtherLeaderSkillModules,strongholdLeaderSkillsProfile,type LeaderSkillProfile} from './leader-skill-profile'; +import { ordinaryLeaderSkillModeSupported, type LeaderSkillProfile } from './leader-skill-profile'; export class MentatQuestionError extends Error {} export const MENTAT_EMPTY_HAND = @@ -40,10 +40,7 @@ export type MentatView = { history: Omit[]; }; export function mentatQuestionModeSupported(game: LeaderSkillProfile): boolean { - return (noOtherLeaderSkillModules(game) && - (!game.expansions.length || - (game.expansions.length === 1 && game.expansions[0] === 'choam') || - nativeExpansionLeaderSkillsProfile(game))) || classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) || strongholdLeaderSkillsProfile(game); + return ordinaryLeaderSkillModeSupported(game); } function inventory(expansions: readonly string[]): Card[] { return [ diff --git a/game/reference.ts b/game/reference.ts index 916aae27..87d032ad 100644 --- a/game/reference.ts +++ b/game/reference.ts @@ -4736,8 +4736,8 @@ export const RULE_TOPICS: RuleTopic[] = [ 'Smuggler can include one free accompanying force in an off-planet shipment into an empty territory. Select the entire physical total; three forces cost the ordinary price of two. The checkbox can decline this optional benefit. Guild rounding follows the free force. Richese may instead ship one separate free reserve force beside its owned No-Field into an empty territory; the marker retains its ordinary price and hidden value. Existing forces, including advisors, prevent the bonus; ordinary Fremen on-planet reinforcement has no bonus.', 'Spice Banker can commit one through three own spice with its Battle Plan, separate from force support. Only the surviving selected skilled disc gains that strength. The amount stays private until authorized plan inspection or public reveal. It is paid on win or loss, including leader death, except a sole traitor-call winner spends nothing. Normal deferred income connects only in the separate fresh classic or supported native banker-income profiles, including the bounded Advanced Moritani roster; ordinary/default starts are unchanged.', 'Diplomat may use one committed Worthless as the opponent’s canonical printed defensive role when you played no actual defense: Shield, Snooper, Shield Snooper, defensive Weirding Way or Chemistry, or sealed Portable Snooper. Hybrid protection, Weirding’s non-Shield behavior and Chemistry’s Poison Tooth exception remain distinct. Copying the used role without its activation or own-slot prerequisite is the explicit first-version interpretation. Choose or decline after reveal, before traitors; the Worthless must be discarded even if you win. Already-open base-only battles keep that source window. A native Diplomat can protect its own selected disc; a captive grants no upper band. A surviving trained loser may retreat selected undialed physical counters up to printed/copied disc strength to one empty adjacent non-stronghold before losses. Late/mirror/numeric modifications remain separate.', - 'Mentat has an explicit private developer preview for naming a specific weapon before posture and faction powers in supported classic and native skill profiles, including bounded Advanced Moritani. The target privately shows the named held card or chooses another held card. Weirding Way is a nameable default weapon; Chemistry is not. The card stays held and need not enter a Battle Plan. Every answer uses the same private response step; ordinary activation awaits the user’s timing-design decision. Empty hands skip without an answer. Private history is an observation, not a later hand guarantee.', - 'Bureaucrat may redirect two spice from a payment of at least five between two other players, once per phase. The payer still pays in full; the recipient receives two less. Declining keeps the use available for a later payment. Paid bribes qualify when transferred, with the remaining share still unavailable until Mentat Pause. Ordinary auctions, Guild income, Richese sales and actual paid Tleilaxu revival income in supported Basic or bounded Advanced native skill profiles have connected payment paths. A separate free-revival award is not part of the paid price and stays with Tleilaxu. Split funding, gifts, other payment families and combined modules remain unfinished.', + 'Normal Mentat questions use the user-approved uniform private card-show step before posture and faction powers in supported Leader Skills games. The opponent shows the named held weapon or chooses another held card, even for an exact match or sole eligible card. Weirding Way is a nameable default weapon; Chemistry is not. Cards stay held and need not enter a Battle Plan. New battles activate the question without an extra opt-in; already-opened unversioned battles retain their original flow. Empty hands skip without an answer. Private history records an observation, not a later hand guarantee.', + 'Bureaucrat may redirect two spice from a payment of at least five between two other players, once per phase. The payer still pays in full; the recipient receives two less. Declining keeps the use available for a later payment. Paid bribes qualify when transferred, with their remaining share unavailable until Mentat Pause. Supported auctions, Guild income, Richese sales and paid Tleilaxu revival have connected paths; a separate free-revival award does not make a smaller price qualify. By user ruling, Emperor gifts are not payments: no Bureaucrat prompt/use or threshold disclosure, and exact gift amounts remain private. Split funding and other payment-family/module interactions remain unfinished.', 'Smuggler records all existing positive battle-territory piles when plans reveal, capped in total by the selected leader’s unmodified strength. A surviving controller collects even when losing or no forces remain. A partial collection across several piles asks that owner for exact sector amounts; all/none and single-pile collection stay automatic. This allocation policy is provisional. Pending spice cannot fund the plan. Collection precedes Sandmaster addition; a drained pile receives none. Modified strength and other combined-module limits remain guarded. Older saved battles retain their recorded behavior.', 'In classic and supported E1/E2 native Tech composition, actual Smuggler prices and normal Banker qualifying fees remain distinct from phase-end Tech income. Tech piles cannot fund their triggering action; Banker front-shield grants still wait for actual Mentat. Winning physical Suk rescue and Sandmaster board spice precede original played-card cleanup and mandatory winner token reward. One token transfers automatically, several require a selection, and no token skips without an invented decline. A later Face Dancer retains the original winner’s cards, bounty and Tech reward, kills the matching trainer without another bounty and replaces only actual survivors from real sources. Native Tleilaxu-only free revival excludes Axlotl; CHOAM initial income is not Charity. Richese’s free companion does not change its marker price or create the concealed counters early.', 'Physical Fremen Ambassador relocation now offers optional Sandmaster destination collection before arrival reactions, without an invented route or ordinary movement use. Same-territory repositioning earns none; concealed relocation and other special movement still retain their own limits. Normal Banker income has separate fresh classic or supported native source-to-Mentat previews with provisional original-earned-faction custody, not general activation. Diplomat retreat retains its documented conservative outcome order. Native Advanced foreign gholas/Face Dancers, skill-first Moritani assassination and ordinary-disc Ecaz skills follow their bounded contracts; shared-Duke assignment, combined Occupy skills and other combined modules remain unfinished. This prototype does not enable public module starts.', @@ -4745,7 +4745,7 @@ export const RULE_TOPICS: RuleTopic[] = [ ], related: ['spice-banker-income', 'choam-leader-skills', 'ixian-leader-skills', 'tleilaxu-leader-skills', 'richese-leader-skills', 'ecaz-leader-skills', 'moritani-leader-skills', 'setup','choam-modules','implementation-checklist'], checklist: [ - { area:'Implementation',status:'Partial',detail:'Exact fourteen-card custody, source-ordered setup, public capture, concealment, death and own revival connect five role bonuses, Planetologist movement/Special substitution and Suk Graduate casualty rescue. Rihani inspection/exchange and lower Mentat/Bureaucrat/Sandmaster effects are connected. Smuggler normal reserve shipping, the owned Richese No-Field companion, unmodified battle collection and optional Sandmaster ground-route, native Fremen worm-ride and physical Fremen Ambassador destination collection are connected. A separately opted-in Mentat preview connects private pre-plan questions and historical observations while ordinary activation awaits the uniform-response decision. Spice Banker lower spending and native Diplomat canonical printed-defense copying are connected; skilled Diplomat retreat transfers undialed normal/elite forces before losing casualties. Normal Banker income connects only in its separate fresh profiles; remaining bands and combined modules stay open. Bureaucrat redirects supported third-party auction, shipment and bribe payments with saved once-per-phase use.',evidence:['game/leader-skill-cards.ts','game/leader-skills.ts','game/leader-skill-combat.ts','game/suk-graduate.ts','game/rihani-decipherer.ts','game/mentat-question.ts','game/bureaucrat-payment.ts','game/leader-skill-battle-board.ts','game/smuggler-shipment.ts','game/smuggler-no-field.ts','game/smuggler-battle.ts','game/spice-banker.ts','game/spice-banker-income.ts','game/sandmaster-movement.ts','game/sandmaster-worm.ts','game/fremen-ambassador.ts','game/diplomat-defense.ts','game/diplomat-retreat.ts','game/engine.ts'] }, + { area:'Implementation',status:'Partial',detail:'Exact fourteen-card custody, source-ordered setup, public capture, concealment, death and own revival connect five role bonuses, Planetologist movement/Special substitution and Suk Graduate casualty rescue. Rihani inspection/exchange and lower Mentat/Bureaucrat/Sandmaster effects are connected. Smuggler shipping, No-Field accompaniment and unmodified battle collection, plus Sandmaster ground, worm, physical Ambassador and native HMS allocation are connected. Normal Mentat private questions and historical observations use the user-approved uniform step in new supported battles without an extra opt-in. Spice Banker lower spending and native Diplomat printed-defense copying are connected; trained retreat transfers undialed forces before losing casualties. Normal Banker income remains a separate profile; other combined interactions stay open. Bureaucrat redirects supported third-party payments with saved once-per-phase use, while user-classified gifts stay outside payment handling.',evidence:['game/leader-skill-cards.ts','game/leader-skills.ts','game/leader-skill-combat.ts','game/suk-graduate.ts','game/rihani-decipherer.ts','game/mentat-question.ts','game/bureaucrat-payment.ts','game/leader-skill-battle-board.ts','game/smuggler-shipment.ts','game/smuggler-no-field.ts','game/smuggler-battle.ts','game/spice-banker.ts','game/spice-banker-income.ts','game/sandmaster-movement.ts','game/sandmaster-worm.ts','game/fremen-ambassador.ts','game/diplomat-defense.ts','game/diplomat-retreat.ts','game/engine.ts'] }, { area:'Player controls',status:'Partial',detail:'Private card/leader selection, readable inspectors, public battle posture and revival draw/decline, Planetologist range/gather controls, Special weapon selection and Suk Graduate physical rescue choices are connected. Rihani has private history and separate keep-new/reveal-old controls; automatic battle effects need no confirmation. Smuggler has explicit ordinary shipping opt-out and No-Field companion opt-in with physical-versus-priced force counts, plus surviving-leader battle collection with reveal-time guidance and owned multi-pile allocations. Banker exposes separate sealed spending and authorized inspection. Sandmaster exposes explicit legal routes, per-territory collection choices, optional worm/physical Ambassador destination checkboxes and direct victory-pile placement when several piles remain. Diplomat offers a named committed Worthless choice or decline after public reveal, and a separate surviving-loser destination/normal/elite retreat or decline. Bureaucrat offers redirect or full payment with explicit recipient amounts and deferred bribe custody. Remaining effect controls are missing.',evidence:['components/leader-skills.tsx','components/planetologist-movement.tsx','components/suk-graduate.tsx','components/rihani-decipherer.tsx','components/mentat-question.tsx','components/bureaucrat-payment.tsx','components/leader-skill-battle-guide.tsx','components/smuggler-shipment.tsx','components/smuggler-battle-collection.tsx','components/spice-banker.tsx','components/sandmaster-movement.tsx','components/sandmaster-worm.tsx','components/sandmaster-victory-spice.tsx','components/ambassador-movement.tsx','components/diplomat-defense.tsx','components/diplomat-retreat.tsx','components/game-table.tsx'] }, { area:'AI',status:'Partial',detail:'All four profiles use private offers, legal visibility/revival actions and Planetologist movement/battle candidates and legal Suk Graduate rescues, Rihani exchanges, shared battle scoring, Smuggler reserve/owned No-Field shipments and unmodified collection plans, exact multi-pile Smuggler allocations, Sandmaster route, worm, physical Ambassador destination collection and victory-pile placement, funded Banker commitments and optional Diplomat copying from public revealed defenses or choosing legal losing-battle retreat. Bureaucrat uses the public payee alliance to redirect enemy income or preserve allied income. These are legal prototype paths; complete skill strategy and difficulty calibration remain unfinished.',evidence:['game/bots.ts'] }, { area:'Documentation',status:'Partial',detail:'All fourteen faces and common rules are recorded. Skilled capture follows public physical-card custody; replacement entitlement and other material timing interpretations remain explicit.',evidence:['docs/LEADER_SKILLS_RULES.md','docs/LEADER_SKILLS_CAPTURE.md','docs/LEADER_SKILLS_RUNTIME.md','docs/PLANETOLOGIST_RULES.md','docs/SUK_GRADUATE_RULES.md','docs/LEADER_BATTLE_EFFECTS.md','docs/MENTAT_QUESTION.md','docs/BUREAUCRAT_PAYMENTS.md','docs/SMUGGLER_SHIPMENT.md','docs/SPICE_BANKER_RUNTIME.md','docs/SANDMASTER_WORM.md','docs/DIPLOMAT_DEFENSE.md'] }, diff --git a/tests/advanced-native-skills.test.ts b/tests/advanced-native-skills.test.ts index aaf6a2f9..f4370e9a 100644 --- a/tests/advanced-native-skills.test.ts +++ b/tests/advanced-native-skills.test.ts @@ -161,6 +161,10 @@ void test('CHOAM prepaid ally support consumes its original donor pool and exclu const donorFunds = donor.spice; const enemyFunds = enemy.spice; game = applyAction(game, owner.id, { type: 'chooseBattle', territory: 'wind_pass', target: enemy.id }); + if (game.decision?.kind === 'mentatQuestion') + game = applyAction(game, game.decision.player, { + type: 'decision', event: game.decision.event, decline: true, + }); while (game.decision?.kind === 'leaderSkillVisibility') game = applyAction(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); diff --git a/tests/advanced-preview.test.ts b/tests/advanced-preview.test.ts index afb94818..eed3179c 100644 --- a/tests/advanced-preview.test.ts +++ b/tests/advanced-preview.test.ts @@ -153,7 +153,6 @@ void test('the explicit preview keeps expansion, module and separate development game => { game.discoveryEnabled = true; }, game => { game.leaderSkills = createLeaderSkills(() => 0.25); }, game => { game.ecazTreachery = true; }, - game => { game.mentatQuestionPreview = true; }, game => { game.moritaniAssassinatePreview = true; }, ]; for (const change of changes) { diff --git a/tests/bureaucrat-payment.test.ts b/tests/bureaucrat-payment.test.ts index 9b4173ea..889617c8 100644 --- a/tests/bureaucrat-payment.test.ts +++ b/tests/bureaucrat-payment.test.ts @@ -120,6 +120,31 @@ void test('Bureaucrat requires two other players and tracks the unique physical !bureaucratPaymentModeSupported({ expansions: ['choam'], homeworlds: {} }), ); }); + +void test('Emperor gifts keep private amounts and do not consume Bureaucrat before a later qualifying payment', () => { + let game = bureaucratPaymentGame({ advanced: true }); + bureaucratPlayer(game, 'e').ally = 'g'; + bureaucratPlayer(game, 'g').ally = 'e'; + takeBureaucratCard(game, 'p', 'Karama'); + const pending = applyAction(game, 'e', { type: 'emperorGift', amount: 7 }); + assert.equal(pending.response?.kind, 'emperorGift'); + for (const id of ['b', 'p']) + assert.equal(viewGame(pending, id).response!.amount, undefined); + for (const id of ['e', 'g']) + assert.equal(viewGame(pending, id).response!.amount, 7); + game = allowBureaucratIncome(bureaucratReload(pending)); + assert.equal(bureaucratPlayer(game, 'e').spice, 23); + assert.equal(bureaucratPlayer(game, 'g').spice, 37); + assert.equal(bureaucratPlayer(game, 'b').spice, 30); + assert.equal(game.decision, null); + assert.equal(viewGame(game, 'b').bureaucrat.usedThisPhase, false); + const paid = applyAction(game, 'p', bribe); + assert.equal(paid.decision?.kind, 'bureaucratPayment'); + const redirected = choose(paid); + assert.equal(bureaucratPlayer(redirected, 'p').spice, 25); + assert.equal(bureaucratPlayer(redirected, 'e').bribes, 3); + assert.equal(viewGame(redirected, 'b').bureaucrat.usedThisPhase, true); +}); for (const advanced of [false, true]) for (const choam of [false, true]) void test(`single-payer ${choam ? 'CHOAM' : 'base'} ${advanced ? 'Advanced' : 'Basic'} shipment diverts two after payment without replaying arrival`, () => { diff --git a/tests/choam-skills-ghola-fixture.ts b/tests/choam-skills-ghola-fixture.ts index 70544fbc..440d538d 100644 --- a/tests/choam-skills-ghola-fixture.ts +++ b/tests/choam-skills-ghola-fixture.ts @@ -61,6 +61,8 @@ export function stagedMarketGhola( game = actChoamGhola(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent }); for (let step = 0; step < 30; step++) { if (game.response) game = passChoamGholaResponses(game); + else if (game.decision?.kind === 'mentatQuestion') + game = actChoamGhola(game, game.decision.player, { type: 'decision', event: game.decision.event, decline: true }); else if (game.decision?.kind === 'leaderSkillVisibility') game = actChoamGhola(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); else if (game.battle?.preparation) diff --git a/tests/choam-skills-integration.test.ts b/tests/choam-skills-integration.test.ts index ece0cca7..cb0c18d0 100644 --- a/tests/choam-skills-integration.test.ts +++ b/tests/choam-skills-integration.test.ts @@ -410,6 +410,12 @@ void test('a real concealed Planetologist green Special is discarded once after territory: 'arrakeen', target: 'a', }); + if (game.decision?.kind === 'mentatQuestion') + game = applyAction(game, game.decision.player, { + type: 'decision', + event: game.decision.event, + decline: true, + }); while (game.decision?.kind === 'leaderSkillVisibility') { const decision = game.decision; game = applyAction(game, decision.player, { diff --git a/tests/discovery-module-profile.test.ts b/tests/discovery-module-profile.test.ts index 802d266b..104e9f1a 100644 --- a/tests/discovery-module-profile.test.ts +++ b/tests/discovery-module-profile.test.ts @@ -54,10 +54,9 @@ void test('Discovery cannot redeal started components or silently remove another void test('Discovery Skills cannot silently drop unsupported native families or unrelated modules and previews', () => { const unsupported = lobby(['ixians', 'ecaz', 'guild'], ['ix', 'ecaz']); assert.throws(() => initializeLeaderSkillsGameForAudit(unsupported), RuleError); - const overlays = Array.from({ length: 3 }, () => lobby(['guild', 'emperor', 'harkonnen'], [])); + const overlays = Array.from({ length: 2 }, () => lobby(['guild', 'emperor', 'harkonnen'], [])); overlays[0].advanced = false; overlays[0].strongholdCards = createStrongholdCards(); - overlays[1].mentatQuestionPreview = true; - overlays[2].spiceBankerIncomePreview = true; + overlays[1].spiceBankerIncomePreview = true; for (const game of overlays) assert.throws(() => initializeLeaderSkillsGameForAudit(game), RuleError); const two = lobby(['guild', 'emperor'], []); two.techTokens = createTechTokens(); assert.throws(() => initializeLeaderSkillsGameForAudit(two), RuleError); diff --git a/tests/discovery-native-e2-skills-runtime.test.ts b/tests/discovery-native-e2-skills-runtime.test.ts index 812a165f..b3971a20 100644 --- a/tests/discovery-native-e2-skills-runtime.test.ts +++ b/tests/discovery-native-e2-skills-runtime.test.ts @@ -69,7 +69,6 @@ for (const advanced of [false, true]) for (const tech of [false, true]) { assert.equal(f.game.richeseCache!.some(c => c.id === f.green), false); assert.ok(f.game.richeseCache!.length > 0, 'Two-turn bounded recipes do not bypass the exhausted-cache ruling.'); assert.equal(f.game.spiceBankerIncomePreview, undefined); - assert.equal(f.game.mentatQuestionPreview, undefined); if (tech) { assert.deepEqual(f.setup.techTokens, createTechTokens()); assert.ok(TECH_TOKENS.every(token => f.afterFirstStorm.techTokens![token.id].owner !== null)); diff --git a/tests/faction-games-cli.test.ts b/tests/faction-games-cli.test.ts index f5b9eb9e..62aceb6b 100644 --- a/tests/faction-games-cli.test.ts +++ b/tests/faction-games-cli.test.ts @@ -582,7 +582,6 @@ void test('selected genuine three-player Advanced base sample fails honestly, re const snapshot = join(first, 'failed-base-3-advanced.json'); for (const field of [ 'ecazTreachery', - 'mentatQuestionPreview', 'moritaniAssassinatePreview', 'moritaniAssassinate', 'moritaniAssassinateResume', diff --git a/tests/fixture-discovery-native-e2-skills.ts b/tests/fixture-discovery-native-e2-skills.ts index b495c025..84523f50 100644 --- a/tests/fixture-discovery-native-e2-skills.ts +++ b/tests/fixture-discovery-native-e2-skills.ts @@ -217,7 +217,7 @@ export function createDiscoveryNativeE2SkillsFixture(options: DiscoveryNativeE2S joinGame(game, newPlayer(faction, faction, faction)); assert.deepEqual(game.expansions, ['choam']); if (options.advanced !== undefined) assert.equal(game.advanced, options.advanced); - assert.ok(!game.homeworlds && !game.nexusCards && !game.spiceBankerIncomePreview && !game.mentatQuestionPreview); + assert.ok(!game.homeworlds && !game.nexusCards && !game.spiceBankerIncomePreview); assert.ok(game.status === 'lobby' || game.status === 'setup' && game.turn === 1 && game.phase === 0 && game.leaderSkills?.assignments.length === 0 && game.players.every(p => p.hand.length <= (p.faction === 'harkonnen' ? 2 : 1) && p.traitors.length === 0 && p.traitorChoices.length === 0), diff --git a/tests/fixture-discovery-native-e3-skills.ts b/tests/fixture-discovery-native-e3-skills.ts index e911406f..9ce5d165 100644 --- a/tests/fixture-discovery-native-e3-skills.ts +++ b/tests/fixture-discovery-native-e3-skills.ts @@ -149,7 +149,7 @@ export function initializeDiscoveryNativeE3Skills(options: DiscoveryNativeE3Skil assert.ok(game.players.every(p => ['moritani', 'guild', 'atreides', 'emperor', 'fremen', 'beneGesserit', ...(advanced ? [] : ['harkonnen'])].includes(p.faction))); assert.ok(!strongholds || advanced); - assert.ok(!game.nexusCards && !game.homeworlds && !game.ecazTreachery && !game.spiceBankerIncomePreview && !game.mentatQuestionPreview); + assert.ok(!game.nexusCards && !game.homeworlds && !game.ecazTreachery && !game.spiceBankerIncomePreview); if (game.status === 'lobby') { if (!!game.techTokens !== tech) game = applyAction(game, game.host, { type: 'techTokens', enabled: tech }); if (!!game.strongholdCards !== strongholds) game = applyAction(game, game.host, { type: 'strongholdCards', enabled: strongholds }); diff --git a/tests/fixture-discovery-standalone-nexus-e3-skills.ts b/tests/fixture-discovery-standalone-nexus-e3-skills.ts index 0fb8cad9..3184c4e2 100644 --- a/tests/fixture-discovery-standalone-nexus-e3-skills.ts +++ b/tests/fixture-discovery-standalone-nexus-e3-skills.ts @@ -117,7 +117,7 @@ export function initializeDiscoveryStandaloneNexusE3Skills(options: DiscoverySta assert.ok(advanced || native !== 'ecaz' || game.players.length % 2 === 0, 'Basic odd-seat Ecaz remains source guarded.'); assert.ok(!strongholds || advanced); assert.ok(!game.homeworlds && !game.ecazTreachery && !game.advancedPreview && !game.semutaPreview && - !game.spiceBankerIncomePreview && !game.mentatQuestionPreview); + !game.spiceBankerIncomePreview); if (game.status === 'lobby') { if (!!game.techTokens !== tech) game = applyAction(game, game.host, { type: 'techTokens', enabled: tech }); if (!!game.strongholdCards !== strongholds) game = applyAction(game, game.host, { type: 'strongholdCards', enabled: strongholds }); diff --git a/tests/fixture-moritani-skills-modules.ts b/tests/fixture-moritani-skills-modules.ts index 8a9bbf3d..c075f508 100644 --- a/tests/fixture-moritani-skills-modules.ts +++ b/tests/fixture-moritani-skills-modules.ts @@ -46,6 +46,7 @@ export function nextMoritaniSkillsModulesStep(game: Game): StrongholdFactionsNat return nextStrongholdFactionsNativeStep(game); const d = game.decision; if (d?.kind === 'leaderSkillVisibility') return { actor: d.player, action: { type: 'leaderSkillVisibility', event: d.event, hide: false } }; + if (d?.kind === 'mentatQuestion') return { actor: d.player, action: { type: 'decision', event: d.event, decline: true } }; if (d?.kind === 'moritaniSetup') { const action = moritaniSkillsModulesPolicy(game, d.player)[0]; assert.ok(action); return { actor: d.player, action }; diff --git a/tests/fixture-native-mentat.ts b/tests/fixture-native-mentat.ts index 3582bac1..1a3f405e 100644 --- a/tests/fixture-native-mentat.ts +++ b/tests/fixture-native-mentat.ts @@ -10,14 +10,13 @@ export const NATIVE_MENTAT_FAMILIES = ['ixians','tleilaxu','choam','moritani'] a export type NativeMentatFamily = typeof NATIVE_MENTAT_FAMILIES[number]; /** Real native setup/assignment and Storm; conserved rule-unit battle position. */ -export function nativeMentatBattle(family: NativeMentatFamily, matching = true, preview = true, begin = true): Game { +export function nativeMentatBattle(family: NativeMentatFamily, matching = true, begin = true): Game { const options = {requestedSkill:'mentat' as const}; let game = family === 'ixians' ? completedIxSkillsGame(options) : family === 'tleilaxu' ? completedTleilaxuSkillsGame(options) : family === 'choam' ? completedChoamSkillsGame(options) : completedMoritaniSkillsGame(options); game = finishToMovement(game); - if (preview) game.mentatQuestionPreview = true; const owner = game.players.find(p => p.faction === family)!; const target = game.players.find(p => p.faction === 'emperor')!; for (const p of game.players) { diff --git a/tests/ix-skills-battle.test.ts b/tests/ix-skills-battle.test.ts index 0a4a2d1d..cfded555 100644 --- a/tests/ix-skills-battle.test.ts +++ b/tests/ix-skills-battle.test.ts @@ -57,6 +57,8 @@ function open(game: Game, territory = 'wind_pass') { game = act(game, 'i', { type: 'chooseBattle', territory, target: 'e' }); for (let step = 0; step < 30; step++) { if (game.response) game = passResponses(game); + else if (game.decision?.kind === 'mentatQuestion') + game = act(game, game.decision.player, { type: 'decision', event: game.decision.event, decline: true }); else if (game.decision?.kind === 'leaderSkillVisibility') game = act(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); else if (game.battle?.preparation) diff --git a/tests/leader-skills-capture.test.ts b/tests/leader-skills-capture.test.ts index fd2ac037..932cbcef 100644 --- a/tests/leader-skills-capture.test.ts +++ b/tests/leader-skills-capture.test.ts @@ -136,6 +136,7 @@ function clearBattlePreparation(state: Game): Game { let g = state; while ( g.decision?.kind === 'leaderSkillVisibility' || + g.decision?.kind === 'mentatQuestion' || g.decision?.kind === 'fullPlanOffer' || g.battle?.preparation ) @@ -145,6 +146,12 @@ function clearBattlePreparation(state: Game): Game { event: g.decision.event, hide: false, }); + else if (g.decision?.kind === 'mentatQuestion') + g = applyAction(g, g.decision.player, { + type: 'decision', + event: g.decision.event, + decline: true, + }); else if (g.decision?.kind === 'fullPlanOffer') g = applyAction(g, g.decision.player, { type: 'decision', diff --git a/tests/leader-skills-engine.test.ts b/tests/leader-skills-engine.test.ts index 3f6935bd..a8733e32 100644 --- a/tests/leader-skills-engine.test.ts +++ b/tests/leader-skills-engine.test.ts @@ -182,6 +182,12 @@ function battleFor( territory: 'arrakeen', target: 'e', }); + if (next.decision?.kind === 'mentatQuestion') + next = applyAction(next, next.decision.player, { + type: 'decision', + event: next.decision.event, + decline: true, + }); while (next.decision?.kind === 'leaderSkillVisibility') { const decision = next.decision; next = applyAction(next, decision.player, { @@ -404,6 +410,12 @@ void test('a Voice that forbids the last alternate Cheap Hero automatically conc territory: 'arrakeen', target: 'b', }); + if (g.decision?.kind === 'mentatQuestion') + g = applyAction(g, g.decision.player, { + type: 'decision', + event: g.decision.event, + decline: true, + }); while (g.decision?.kind === 'leaderSkillVisibility') g = applyAction(g, g.decision.player, { type: 'leaderSkillVisibility', @@ -513,6 +525,12 @@ void test('Ghola revival during an unfinished battle preserves its power stage t territory: 'carthag', target: 'e', }); + if (g.decision?.kind === 'mentatQuestion') + g = applyAction(g, g.decision.player, { + type: 'decision', + event: g.decision.event, + decline: true, + }); while (g.decision?.kind === 'leaderSkillVisibility') g = applyAction(g, g.decision.player, { type: 'leaderSkillVisibility', diff --git a/tests/leader-skills-recovery.test.ts b/tests/leader-skills-recovery.test.ts index e0666b4d..24fc10fc 100644 --- a/tests/leader-skills-recovery.test.ts +++ b/tests/leader-skills-recovery.test.ts @@ -381,6 +381,17 @@ void test('battle death and Ghola revival preserve one private replacement skill territory: 'arrakeen', target: bg.id, }); + if (game.decision?.kind === 'mentatQuestion') { + const owner = f.auths.findIndex( + (auth) => auth.playerId === game.decision!.player, + ); + assert.ok(owner >= 0); + game = await act(f, owner, { + type: 'decision', + event: game.decision.event, + decline: true, + }); + } while (game.decision?.kind === 'leaderSkillVisibility') { const owner = f.auths.findIndex( (auth) => auth.playerId === game.decision!.player, diff --git a/tests/mentat-question-fixture.ts b/tests/mentat-question-fixture.ts index 617d94b8..9bc6dcf4 100644 --- a/tests/mentat-question-fixture.ts +++ b/tests/mentat-question-fixture.ts @@ -34,14 +34,12 @@ export function mentatQuestionGame({ cards = ['Crysknife', 'Snooper'], begin = true, captured = false, - preview = true, }: { advanced?: boolean; choam?: boolean; cards?: string[]; begin?: boolean; captured?: boolean; - preview?: boolean; } = {}): Game { const native = choam ? 'richese' : 'emperor'; let game = createGame( @@ -57,8 +55,6 @@ export function mentatQuestionGame({ joinGame(game, newPlayer('o', 'Observer', 'guild')); for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); - // Server-only review fixture opt-in; ordinary Leader Skills starts remain unchanged. - if (preview) game.mentatQuestionPreview = true; const index = LEADER_SKILL_CARDS.findIndex((card) => card.id === 'mentat'); let cursor = LEADER_SKILL_CARDS.length - 1; mock.method(globalThis.crypto, 'getRandomValues', (array: Uint32Array) => { diff --git a/tests/mentat-question.test.ts b/tests/mentat-question.test.ts index c3f43e4e..12c16e57 100644 --- a/tests/mentat-question.test.ts +++ b/tests/mentat-question.test.ts @@ -107,38 +107,16 @@ void test('Mentat names exact printed/default weapons and validates physical han assert.throws(() => mentatHand(ixDeck().slice(0, 1), [])); }); -void test('Mentat requires a private preview opt-in and never exposes or accepts that flag through a player view or action', () => { +void test('fresh supported Leader Skills battles offer a private Mentat question without a separate opt-in', () => { for (const advanced of [false, true]) for (const choam of [false, true]) { - const ordinary = mentatQuestionGame({ advanced, choam, preview: false }); - assert.equal(ordinary.mentatQuestionPreview, undefined); - assert.equal(ordinary.battle!.mentatQuestionVersion, undefined); - assert.equal(ordinary.battle!.mentatQuestion, undefined); - assert.equal(ordinary.decision?.kind, 'leaderSkillVisibility'); - assert.equal(viewGame(ordinary, 'a').mentat.pending, null); - assert.equal( - applyAction(ordinary, 'a', { - type: 'advanceBots', - mentatQuestionPreview: true, - }).mentatQuestionPreview, - undefined, - ); + const game = mentatReload(mentatQuestionGame({ advanced, choam })); + assert.equal(game.battle!.mentatQuestionVersion, 1); + assert.equal(normalizeAutomaticGame(game).decision?.kind, 'mentatQuestion'); + assert.equal(viewGame(game, 'a').mentat.pending!.stage, 'name'); + assert.deepEqual(viewGame(game, 'd').mentat.pending!.weapons, []); + assert.deepEqual(viewGame(game, 'o').mentat.history, []); } - const preview = mentatReload(mentatQuestionGame()); - assert.equal(preview.mentatQuestionPreview, true); - assert.equal( - normalizeAutomaticGame(preview).decision?.kind, - 'mentatQuestion', - ); - for (const id of ['a', 'd', 'o']) - assert.equal('mentatQuestionPreview' in viewGame(preview, id), false); - delete preview.mentatQuestionPreview; - for (const operation of [ - () => viewGame(preview, 'a'), - () => normalizeAutomaticGame(preview), - () => applyAction(preview, 'a', { type: 'advanceBots' }), - ]) - assert.throws(operation); }); for (const advanced of [false, true]) @@ -430,6 +408,19 @@ void test('legacy unmarked battles are not reopened and later new battles receiv viewGame(normalizeAutomaticGame(mentatReload(legacy)), 'a').mentat.pending, null, ); + const continued = finishPowers(normalizeAutomaticGame(mentatReload(legacy))); + assert.equal(continued.battle!.event, legacy.battle!.event); + assert.equal(continued.battle!.mentatQuestionVersion, undefined); + assert.equal(continued.battle!.mentatQuestion, undefined); + assert.equal(continued.decision, null); + assert.deepEqual(viewGame(continued, 'a').mentat.history, []); + const planned = applyAction(continued, 'a', { + type: 'battlePlan', + dial: 0, + leader: 'emperor-0', + }); + assert.ok(planned.battle!.plans.a); + assert.equal(planned.battle!.mentatQuestion, undefined); let game = finishPowers(ask(mentatQuestionGame())); const original = game.battle!.mentatQuestionEvent; game = applyAction(game, 'a', { diff --git a/tests/moritani-skills-battle.test.ts b/tests/moritani-skills-battle.test.ts index ecd1081a..04cfa405 100644 --- a/tests/moritani-skills-battle.test.ts +++ b/tests/moritani-skills-battle.test.ts @@ -85,7 +85,9 @@ function openBattle(state: Game, owner = 'm', opponent = 'e', hide = true): Game if (g.decision?.kind === 'leaderSkillVisibility') { const d = g.decision; g = act(g, d.player, { type: 'leaderSkillVisibility', event: d.event, hide: d.player === owner ? hide : true }); - } else if (g.decision?.kind === 'mentatQuestion' || g.decision?.kind === 'fullPlanOffer') { + } else if (g.decision?.kind === 'mentatQuestion') { + g = act(g, g.decision.player, { type: 'decision', event: g.decision.event, decline: true }); + } else if (g.decision?.kind === 'fullPlanOffer') { g = act(g, g.decision.player, { type: 'decision', decline: true }); } else if (g.battle?.preparation) { g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' }); diff --git a/tests/native-mentat.test.ts b/tests/native-mentat.test.ts index 175198b7..c245f676 100644 --- a/tests/native-mentat.test.ts +++ b/tests/native-mentat.test.ts @@ -6,10 +6,7 @@ import {DIFFICULTIES} from '../game/bot-profiles'; import {NATIVE_MENTAT_FAMILIES,nativeMentatBattle} from './fixture-native-mentat'; for (const family of NATIVE_MENTAT_FAMILIES) { - void test(`${family} Mentat requires an explicit preview and resumes native posture after a matching or fallback answer`,()=>{ - const ordinary = nativeMentatBattle(family,true,false); - assert.equal(ordinary.battle!.mentatQuestion,undefined); - assert.equal(ordinary.decision!.kind,'leaderSkillVisibility'); + void test(`${family} Mentat asks normally and resumes native posture after a matching or fallback answer`,()=>{ for (const matching of [true,false]) { let game = nativeMentatBattle(family,matching); assert.equal(game.decision!.kind,'mentatQuestion'); diff --git a/tests/tleilaxu-skills-integration.test.ts b/tests/tleilaxu-skills-integration.test.ts index 8702e611..851d8537 100644 --- a/tests/tleilaxu-skills-integration.test.ts +++ b/tests/tleilaxu-skills-integration.test.ts @@ -32,6 +32,7 @@ function open(g: Game, owner = 't', opponent = 'e') { g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent }); for (let i = 0; i < 30; i++) { if (g.response) g = act(g, g.players.find(p => !g.response!.passed.includes(p.id))!.id, { type: 'passResponse' }); + else if (g.decision?.kind === 'mentatQuestion') g = act(g, g.decision.player, { type: 'decision', event: g.decision.event, decline: true }); else if (g.decision?.kind === 'leaderSkillVisibility') g = act(g, g.decision.player, { type: 'leaderSkillVisibility', event: g.decision.event, hide: true }); else if (g.battle?.preparation) g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' }); else break; diff --git a/tests/tleilaxu-skills-review.test.ts b/tests/tleilaxu-skills-review.test.ts index a41c21d2..0f68f12c 100644 --- a/tests/tleilaxu-skills-review.test.ts +++ b/tests/tleilaxu-skills-review.test.ts @@ -29,6 +29,8 @@ function open(g: Game, owner = 't') { g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' }); for (let i = 0; i < 30; i++) { if (g.response) g = act(g, g.players.find(p => !g.response!.passed.includes(p.id))!.id, { type: 'passResponse' }); + else if (g.decision?.kind === 'mentatQuestion') + g = act(g, g.decision.player, { type: 'decision', event: g.decision.event, decline: true }); else if (g.decision?.kind === 'leaderSkillVisibility') g = act(g, g.decision.player, { type: 'leaderSkillVisibility', event: g.decision.event, hide: true }); else if (g.battle?.preparation) g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' }); diff --git a/tests/tleilaxu-skills-revival.test.ts b/tests/tleilaxu-skills-revival.test.ts index 854d79d2..6ffa1583 100644 --- a/tests/tleilaxu-skills-revival.test.ts +++ b/tests/tleilaxu-skills-revival.test.ts @@ -63,6 +63,8 @@ function killedOriginalSkill(owner: 't' | 'a' = 't'): Game { game = act(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' }); for (let step = 0; step < 20; step++) { if (game.response) game = passWindow(game); + else if (game.decision?.kind === 'mentatQuestion') + game = act(game, game.decision.player, { type: 'decision', event: game.decision.event, decline: true }); else if (game.decision?.kind === 'leaderSkillVisibility') { const decision = game.decision; game = act(game, decision.player, { diff --git a/tools/faction-games.ts b/tools/faction-games.ts index 920ae309..6d9bebb2 100644 --- a/tools/faction-games.ts +++ b/tools/faction-games.ts @@ -1088,8 +1088,7 @@ function resumedGame(path: string) { if ( (game.leaderSkills && !nativeExpansionLeaderSkillsProfile(game) && !composedSkills) || ((game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker) && !discovery) || - (game.techTokens && !discovery && !composedSkills && !nativeFactionTechProfile(game) && !classicNexusModulesProfile(game) && !pairedNexusModulesProfile(game) && !mixedE3NexusProfile(game) && !mixedE1E2NexusProfile(game) && !pairedE3NexusProfile(game) && !singleE1E2NexusProfile(game) && !standaloneE3NexusProfile(game)) || - game.mentatQuestionPreview + (game.techTokens && !discovery && !composedSkills && !nativeFactionTechProfile(game) && !classicNexusModulesProfile(game) && !pairedNexusModulesProfile(game) && !mixedE3NexusProfile(game) && !mixedE1E2NexusProfile(game) && !pairedE3NexusProfile(game) && !singleE1E2NexusProfile(game) && !standaloneE3NexusProfile(game)) ) throw new Error('--resume sample scenarios exclude unsupported optional modules.'); const sourceName = basename(path), namedSource = sourceName.startsWith('failed-'); diff --git a/tools/prototype-room.ts b/tools/prototype-room.ts index d0fea2d4..a08c384a 100644 --- a/tools/prototype-room.ts +++ b/tools/prototype-room.ts @@ -51,8 +51,6 @@ export type PrototypeProfile = (typeof PROTOTYPE_PROFILES)[number]; export type PrototypeOptions = { /** Explicit independent card variant for the fresh combined-army profile. */ ecazTreachery?: boolean; - /** Private uniform-question preview; ordinary skill starts stay unchanged. */ - mentatQuestion?: boolean; /** Independent classic Nexus/Skills composition, not a public activation. */ nexusCards?: boolean; /** Explicit classic Discovery/Skills composition, not a public activation. */ @@ -98,14 +96,11 @@ export function startPrototypeRoom( throw new Error('Unknown prototype profile.'); if (options.ecazTreachery && profile !== 'ecaz-occupy') throw new Error('--ecaz-treachery requires the ecaz-occupy profile.'); - if (options.mentatQuestion && profile !== 'leader-skills') - throw new Error('--mentat-question requires the leader-skills profile.'); if (options.nexusCards && profile !== 'leader-skills') throw new Error('--nexus-cards requires the leader-skills profile.'); if (options.discoveries && profile !== 'leader-skills' && profile !== 'nexus') throw new Error('--discoveries requires the leader-skills or nexus profile.'); if (options.ecazTreachery) initial.ecazTreachery = true; - if (options.mentatQuestion) initial.mentatQuestionPreview = true; if ((profile === 'nexus' || options.nexusCards) && !initial.nexusCards) initial.nexusCards = { cards: null, phase: null }; if (options.discoveries) initial.discoveryEnabled = true; diff --git a/tools/start-prototype.ts b/tools/start-prototype.ts index 7e7a003d..3d9283f6 100644 --- a/tools/start-prototype.ts +++ b/tools/start-prototype.ts @@ -16,7 +16,6 @@ async function main() { version: { type: 'string' }, out: { type: 'string' }, 'ecaz-treachery': { type: 'boolean' }, - 'mentat-question': { type: 'boolean' }, 'nexus-cards': { type: 'boolean' }, discoveries: { type: 'boolean' }, help: { type: 'boolean' }, @@ -26,9 +25,9 @@ async function main() { console.log( 'Usage: node --import tsx tools/start-prototype.ts --profile ix|discovery|homeworld-occupation|leader-skills|banker-income|factions|stronghold-factions|nexus|moritani-assassinate|ecaz-treachery|ecaz-occupy|semuta|kull|richese-betrayal|nexus-kull|guild-betrayal|ixian-replacement|ixian-betrayal|harkonnen-betrayal --db PATH --room CODE --version NUMBER --out /private/new-directory\nStarts only a fresh ready lobby for the selected development prototype. Stronghold-factions opts a fresh ready 2–6-seat Advanced roster with at least one selected native Ixians, Tleilaxu, CHOAM or Richese faction and otherwise classic opponents into Stronghold Cards, optionally preserving unused Tech Tokens at 3–6 seats. Select one or both distinct Ix/CHOAM decks; each native faction requires its family deck. The selected decks supply 47 Ix or combined, or 35 CHOAM canonical physical Treachery Cards, with a separate ten-card Richese cache when Richese plays. Native setup choices, HMS placement, Face Dancers, Richese cache and CHOAM Auditor remain genuine; custody starts unowned and is claimed by actual END Mentat. It does not redeal, reset or retrofit a saved game. Banker-income opts a fresh 2–6-seat classic Basic/Advanced base-deck roster, supported Basic Ixian/Tleilaxu/CHOAM/Moritani skill roster with its single required deck, or Advanced native Ixians, Tleilaxu and/or CHOAM plus classic roster with distinct required family decks, into automatic deferred normal income and native Mentat collection with all fourteen Leader Skills. Original bank payer costs exclude the separate Tleilaxu free-revival award; already-earned spice stays with its original faction under the existing provisional policy. Nexus enables the existing private Nexus setup. Moritani-assassinate explicitly opts into the bounded Advanced assassination preview. Semuta opts a fresh Richese roster into bounded clean-discard reactions; an included Ix deck supplies physical Thumper without opening public starts. Kull opts a fresh eligible CHOAM roster into the user-selected Karama interception preview. Richese-betrayal opts a fresh paired CHOAM/Richese Nexus roster into cache-purchase veto and sale-bank diversion. Nexus-kull opts a fresh CHOAM-plus-classic roster with CHOAM/Ix decks and Nexus into source-aware printed or any-Treachery-fueled Cunning Kull. Guild-betrayal opts a fresh classic roster with native Guild into full funded shipment-payment replacement; it preserves an explicitly enabled Homeworld module for Junction routes. Ixian-replacement opts a fresh classic Basic/Advanced base-deck roster, or Basic Tleilaxu plus classic opponents with the original Ix47 deck and no optional Sandtrout, into the exact normal-auction purchased-card replacement with Nexus alone. Native Ixians, Harkonnen buyers and special purchase sources remain unsupported. Ixian-betrayal opts a fresh 2–6-seat Basic/Advanced native Ixian plus classic/Tleilaxu roster with Ix 47-card Treachery and Nexus only into prevention of one actual native Bidding draw or Advanced Technology exchange, after native Karama counters. Harkonnen-betrayal opts a fresh 2–6-seat classic Basic/Advanced roster with native Harkonnen, base 33-card Treachery and Nexus alone into cancellation of an actual declared personal or allied Harkonnen Traitor Card after native counters, physical retirement and shuffle, and one private actual Mentat replacement. Modules, overlays, public starts and old-game retrofits remain excluded except for each profile’s documented composition. Nexus is not dealt initially: a qualifying alliance requires at least three seats; two unallied receivers require four. Backs up all rooms first, preserves sessions and existing games, and rejects stale versions. Normal game-start and publication gates remain closed.', '\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. Explicit --mentat-question stays private; 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.', + '\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.' + - '\nLeader-skills and banker-income preserve canonical unused Tech Tokens selected in fresh three-through-six-seat classic or supported native Basic/Advanced lobbies. Original selected decks, Richese cache, all fourteen skills, printed native Tech owners, phase-end income and mandatory original-winner reward before Face Dance remain. Banker income and --mentat-question stay separate existing opt-ins. Mixed E3 families, other modules and played-game conversion remain excluded.', + '\nLeader-skills and banker-income preserve canonical unused Tech Tokens selected in fresh three-through-six-seat classic or supported native Basic/Advanced lobbies. Original selected decks, Richese cache, all fourteen skills, printed native Tech owners, phase-end income and mandatory original-winner reward before Face Dance remain. Banker income remains a separate opt-in. Mixed E3 families, other modules and played-game conversion remain excluded.', '\nLeader-skills and banker-income also preserve canonical unused Stronghold Cards in fresh Advanced classic or supported native two-through-six-seat lobbies. Optional Tech Tokens requires three-through-six seats. All14 original skills, decks/cache and actual end-Mentat Stronghold assignment remain. Basic Strongholds, mixed E3 families, other overlays, public activation and save conversion stay excluded.', '\nThese skill/module entries additionally preserve standalone Ecaz OR Moritani ecaz33/all14 profiles. Tech is Basic/Advanced three-through-six seats; Strongholds is Advanced two-through-six. Native Ecaz five-disc/Duke assignment and Advanced Harkonnen exclusion, plus original Advanced Moritani skill-first assassination/normal-call forfeiture, remain. E3 pairs, mixed families, allied Occupy skills and other overlays are not opened.', '\nWithout Skills, factions preserves unused original Tech Tokens in fresh 3–6-seat Basic/Advanced selected E1/E2 native or standalone Ecaz OR Moritani lobbies with classic opponents; stronghold-factions composes them with Advanced Stronghold Cards. Original physical inventories, first-Storm owners, selected Ecaz lead rewards, even-force Basic Occupy and non-Harkonnen Advanced Moritani assassination remain. Skills, unrelated overlays, paired/mixed E3 rosters and public starts remain separate.', @@ -38,7 +37,7 @@ async function main() { '\nNexus additionally admits exactly one native Ixians OR Tleilaxu/Ix47, or CHOAM OR Richese/CHOAM35 with its original cache, plus classic opponents, Basic/Advanced2–6. Original Homeworlds/Discovery7/8/Tech3+/Advanced Strongholds2+ may remain; leader-skills --nexus-cards retains all14 original offers. Ix offer/HMS, Tleilaxu Face Dancers, CHOAM Auditor and signed Richese markers remain physical. Original paired entries require both natives; additional exact mixed/paired E3 compositions are below. Public starts, played-save conversion and pending effect rulings remain separate.', '\nThe original Nexus and Skills/Nexus entries additionally admit natives from BOTH selected Ix+CHOAM families with classics and union47 Treachery Cards, or BOTH Ecaz+Moritani with classics/ecaz33, Basic/Advanced2–6. Optional Homeworlds/Discovery7+8/Tech3+/Advanced Strongholds2+ retain original setup and consumers. Richese cache stays separate; paired E3 preserves ordinary native training, untrained shared Duke, Arrakis Occupy and Advanced assassination, excluding Advanced Harkonnen. Existing mixed-family free-return/companion, own concealed-plan, Sandtrout, low-income/cache-count, shared/captured training and Terror relocation questions remain guarded. No public starts or played-save conversion.', '\nThe same original Nexus/Skills-Nexus entries now compose selected Ecaz plus Ix and/or CHOAM, with at least one actual native from EVERY selected family and optional classic opponents: two-family mixed E3 or all-three-family tables, Basic/Advanced2–6, all12 Nexus/all14 only with Skills, optional original Homeworlds/Discovery7+8/Tech3+/Advanced Strongholds2+. Ordinary Ix47 if selected, otherwise CHOAM35, and the seated native Richese cache stay physical. Fresh Ecaz gets original Arrakis Occupy; Advanced native Moritani gets original assassination against supported ordinary printed native opponents. The shared Duke never trains; Advanced Skills/capture and Moritani/Harkonnen, Sandtrout, own mixed markers, unusual Gholas and pending force/payment effects retain their guards. Original hands/offers, native setup, actual closing draw and winner/cleanup/Tech timing are reused. No new initializer, return adapter, public start or played-save conversion.', - '\nLeader-skills --nexus-cards adds all12 Nexus to original classic/base33, single or paired one-family E1/E2, both selected Ix+CHOAM/union47, standalone or paired E3/ecaz33, or mixed E3/two-or-three selected families with an actual native from each and classics. All14 original offers and Basic/Advanced2–6 remain; Homeworlds may be retained, Tech3+/StrongholdsAdvanced. Actual Ix pre-training hand, ordinary discs and native components remain. Each capture/exceptional shared-Duke, Banker/Mentat preview, borrowed Smuggler/companion/native-return and pending-effect boundary remains; no public start or played conversion.', + '\nLeader-skills --nexus-cards adds all12 Nexus to original classic/base33, single or paired one-family E1/E2, both selected Ix+CHOAM/union47, standalone or paired E3/ecaz33, or mixed E3/two-or-three selected families with an actual native from each and classics. All14 original offers and Basic/Advanced2–6 remain; Homeworlds may be retained, Tech3+/StrongholdsAdvanced. Actual Ix pre-training hand, ordinary discs and native components remain. Each capture/exceptional shared-Duke, Banker preview, borrowed Smuggler/companion/native-return and pending-effect boundary remains; no public start or played conversion.', '\nThese classic, single-native or paired one-family skill-Nexus entries preserve selected unused original Tech Tokens at3–6 Basic/Advanced and/or Stronghold Cards at2–6 Advanced; both require3–6 Advanced. Real first-Storm/end-Mentat custody, phase-end industry, held subsidy, typed skill rescue/equal substitution, signed one-invoice marker pairs and mandatory original-winner Tech before Face Dance use original handlers. No mixed-family/native free-return accounting or pending rulings are opened.', '\nThe same fresh skill/Nexus entry admits standalone Ecaz OR Moritani with classics, exact ecaz33/all14/all12, Basic/Advanced2–6. Optional unused Tech requires3+; Strongholds requires Advanced; both3+ Advanced. Native Ecaz five-disc/Duke assignment and quiet living-Duke Cunning, plus original Advanced Moritani skill-first assassination/normal-traitor forfeiture, remain. Advanced Harkonnen, shared-Duke assignment, enhanced Terror relocation/HMS/Grumman and other pending effects stay guarded.', '\nIn the standalone Ecaz skill/Nexus profile, mandatory Advanced Occupy composes the chosen faction’s skill with separately labelled armies. Ecaz-led Suk rescues its own fixed casualties after the ally’s variable losses; Diplomat uses the chosen faction’s undialed own pool. Shared training, Basic odd rounding and other pending effects remain separate.', @@ -54,8 +53,6 @@ async function main() { } if (values['ecaz-treachery'] && values.profile !== 'ecaz-occupy') throw new Error('--ecaz-treachery requires --profile ecaz-occupy.'); - if (values['mentat-question'] && values.profile !== 'leader-skills') - throw new Error('--mentat-question requires --profile leader-skills.'); if (values['nexus-cards'] && values.profile !== 'leader-skills') throw new Error('--nexus-cards requires --profile leader-skills.'); if (values.discoveries && values.profile !== 'leader-skills' && values.profile !== 'nexus') @@ -83,7 +80,7 @@ async function main() { const db = new DatabaseSync(values.db); try { const result = startPrototypeRoom(db, values.room, version, values.profile, - { ecazTreachery: values['ecaz-treachery'], mentatQuestion: values['mentat-question'], nexusCards: values['nexus-cards'], discoveries: values.discoveries }); + { ecazTreachery: values['ecaz-treachery'], nexusCards: values['nexus-cards'], discoveries: values.discoveries }); writeFileSync( resolve(values.out, 'prototype.json'), JSON.stringify( @@ -92,7 +89,6 @@ async function main() { startedAt: new Date().toISOString(), profile: values.profile, ecazTreachery: !!values['ecaz-treachery'], - mentatQuestion: !!values['mentat-question'], nexusCards: !!values['nexus-cards'], source, beforeVersion: version,