Activate approved normal Mentat and document Emperor gift ruling

This commit is contained in:
Quintine committed 2026-10-11 16:46:17 +11:00
1 parent eb63967b5c
commit d1ba8e0c7e
40 files changed
+270 -138

No files matched your search

+6 -6
View File
@@ -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 connects ordinary skill actions and saved market/revival choices; see
[CHOAM integration](docs/CHOAM_LEADER_SKILLS.md). [CHOAM integration](docs/CHOAM_LEADER_SKILLS.md).
For the private **Mentat question preview**, add `--mentat-question` to the Normal **Mentat questions** now use the user-approved uniform private
existing `--profile leader-skills` entry on a fresh ready lobby. It supports card-show step in supported Leader Skills development games. Use the
existing classic/CHOAM skill profiles and Basic Ixian/Tleilaxu/CHOAM/Moritani existing `--profile leader-skills` entry; no separate question flag is
native starts without changing ordinary activation. The uniform-response UX needed. Exact matching and fallback both use the same private step.
and empty-hand ruling remain pending; see Empty-hand interpretation and public start gates remain unfinished; see
[native preview scope and controls](docs/MENTAT_QUESTION.md#native-skill-integration--2-october-2026). [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` 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 on a fresh ready Basic/Advanced CHOAM-plus-classic lobby with `choam,ix` decks
+15 -6
View File
@@ -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 Richese auction proceeds and actual bribe transfers in the base or CHOAM-only
skill profile. Basic Tleilaxu with Leader Skills and base opponents additionally skill profile. Basic Tleilaxu with Leader Skills and base opponents additionally
supports a single payer's actual paid revival price to Tleilaxu. Other expansion 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 payment sources remain outside this batch. Emperor gifts are excluded because
printed prohibition. 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 ### 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 Bureaucrat prompt would reveal that the gift qualifies; the existing exact-amount
payment projection must not be applied blindly. payment projection must not be applied blindly.
The user has been asked whether to retain this boundary or apply Bureaucrat The earlier question is now resolved by the 11 October ruling above.
with only the qualifying threshold public. No answer is assumed. Gifts remain Gift classification stays separate from the connected ordinary
outside the connected Bureaucrat payment paths while independent ordinary [bribe controls](BRIBE_CONTROLS.md).
[bribe controls](BRIBE_CONTROLS.md) proceed.
For native income, the prototype resolves the existing Karama income window For native income, the prototype resolves the existing Karama income window
first, then offers Bureaucrat before crediting the eligible recipient and first, then offers Bureaucrat before crediting the eligible recipient and
+24 -5
View File
@@ -1,6 +1,6 @@
# Current development status # 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. is playable and the six classic factions have a user-authorized Advanced preview.
Expansion starts and full rules/publication acceptance remain gated.** 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 Rules implementation continues; comprehensive assurance and administration
remain deferred until all rules are implemented. 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 **Owned HMS Sandmaster piles — 9 October.** Storm relocation and Advanced
Ixian special Karama now select one bonus debit pile per entered territory, Ixian special Karama now select one bonus debit pile per entered territory,
or decline that bonus while retaining native collection. Existing preparation 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 points into, so Sandmaster collects there before the native collection
(`game/sandmaster-movement.ts`, `game/engine.ts`). (`game/sandmaster-movement.ts`, `game/engine.ts`).
The remaining recorded questions (Bureaucrat/Emperor gifts, Recruits late The earlier remaining questions were Recruits accounting, ordinary Guild
accounting, ordinary Guild cancellation, base special Karama, Nexus cancellation, base special Karama, Nexus composition, Discovery benefits
composition, Orgiz, Testing Station, Jacurutu, occupied Homeworlds and the and Homeworld interpretation/restoration. The Bureaucrat/gift question was
Homeworld restoration questions) stay pending. later resolved on 11 October: gifts are not payments.
**Preserved captures resumed under the rulings.** All 123 preserved **Preserved captures resumed under the rulings.** All 123 preserved
`failed-*.json` captures were resumed in place (same position, seed and `failed-*.json` captures were resumed in place (same position, seed and
+35
View File
@@ -1,5 +1,40 @@
# Dune implementation status # 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 ## 9 October 2026 — owned native HMS Sandmaster pile allocation
Both native Storm relocation and Advanced special Karama carry optional Both native Storm relocation and Advanced special Karama carry optional
+11 -12
View File
@@ -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 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). [existing Banker normal-band contract](SPICE_BANKER_RUNTIME.md#normal-income-local-contract--2-october-2026).
**2 October native Mentat checkpoint:** explicit fresh **2 October native Mentat checkpoint:** the formerly separate opt-in connected
`--profile leader-skills --mentat-question` connects the existing private private normal-band questions to Basic Ixian/Tleilaxu/CHOAM/Moritani starts.
normal-band question to supported Basic Ixian/Tleilaxu/CHOAM/Moritani starts. The 11 October user approval now activates the same uniform flow normally
Canonical exact/default weapon roles, target-owned fallback, posture and native through supported skill entries. Exact/default weapon roles, target-owned
battle continuation use the existing controls and legal policies. Ordinary fallback, posture and native continuation remain. Empty-hand interpretation
activation still awaits the uniform-response UX decision; empty hands and and complete/public mode acceptance stay open. See
Advanced native/combined-module skills remain guarded. See [normal activation](MENTAT_QUESTION.md#normal-activation-and-approved-ux--11-october-2026).
[native preview scope](MENTAT_QUESTION.md#native-skill-integration--2-october-2026).
## Standalone E3 Nexus and original components — 5 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, Smuggler collection before ordinary Collection. Existing Planetologist,
Sandmaster, Mentat, Bureaucrat and Banker helpers share this admitted native Sandmaster, Mentat, Bureaucrat and Banker helpers share this admitted native
profile rather than independently accepting a different roster. profile rather than independently accepting a different roster.
Explicit `--mentat-question` remains a private timing preview; ordinary The 11 October user-approved normal Mentat question now shares this admitted
activation and empty-hand rulings are not resolved. The separate `banker-income` skill envelope; the obsolete timing opt-in is removed. Empty-hand
profile also admits this native Advanced subset; normal front-shield income interpretation remains separate. The `banker-income` profile retains its
remains unavailable until actual Mentat and keeps its provisional earned custody. existing deferred front-shield custody and actual-Mentat spendability.
Bounded Development Verified: affected122/122, types/lint/build, ten actual Bounded Development Verified: affected122/122, types/lint/build, ten actual
controlled native programs (five Ixian rescue/policy cases, one CHOAM collection, controlled native programs (five Ixian rescue/policy cases, one CHOAM collection,
+28 -16
View File
@@ -1,18 +1,31 @@
# Mentat before Battle Plans # Mentat before Battle Plans
13 September 2026. This developer preview adds the normal Mentat question to **Normal activation approved 11 October 2026.** The user approved the existing
the existing lower-band strength bonus. It requires an explicit private preview uniform private card-show step, including exact matches and sole eligible cards.
opt-in while the user decides whether to accept the uniform card-show step. New supported Leader Skills battles now offer the normal Mentat question
Ordinary Leader Skills starts do not activate it. Leader Skills and public mode without a separate preview flag. Public module/mode acceptance remains partial.
acceptance remain partial. [Common skill rules](LEADER_SKILLS_RULES.md) govern assignment, [Common skill rules](LEADER_SKILLS_RULES.md) govern assignment, capture, death and the right to conceal a skilled leader.
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 **4 October composition:** the explicit preview also admits the bounded
Advanced native Ixian and/or CHOAM plus classic profile with distinct required Advanced native Ixian and/or CHOAM plus classic profile with distinct required
family decks. Actual private naming/disclosure returns to native posture and family decks. Actual private naming/disclosure returns to native posture and
keeps the shown card held; unrelated players receive no learned card. 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). 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) 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 now uses the same explicit preview; original foreign revival does not assign a
new Mentat or any other skill. 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. 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 For a fresh ready local lobby, use the existing exact-version/private-backup
entry with `--profile leader-skills --mentat-question`. Without the explicit entry with `--profile leader-skills`. The normal question is part of supported
option, ordinary skill starts still do not activate the question. The option skill behavior; no separate question option or player action enables it.
cannot be applied through a player action or to a started game.
The same canonical question and target-owned answer controls handle exact Ix The same canonical question and target-owned answer controls handle exact Ix
weapon names, including **Weirding Way**, with its default weapon role. 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 legal policies. A separate runtime driver exercises eight native exact/fallback
battles through Collection plus four policy inquiries: twelve cases pass. battles through Collection plus four policy inquiries: twelve cases pass.
The earlier Ixian regression failed at the missing question before admission. The earlier Ixian regression failed at the missing question before admission.
The uniform-response UX and empty-hand questions below remain pending; no The uniform-response UX was approved on 11 October; the empty-hand question
public module gate or old-game retrofit is enabled. remains pending. No public module gate or old-battle reopening is enabled.
Final local390px **8HTTTZMA v9** names Weirding Way, shows it privately, Final local390px **8HTTTZMA v9** names Weirding Way, shows it privately,
inspects the learned physical card and conceals the native Mentat. Genuine 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, Target controls alone receive the eligible physical cards; public decision shape,
logs and number of response steps stay the same across equal-size hands. 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 The uniform step is an exception to the user's usual forced-confirmation
confirmations. The user has been asked to approve it or leave the ability disabled preference. **Approved 11 October 2026:** use this private card-show step in
pending another design. No answer has been applied. Private QA can exercise the normal supported Leader Skills battles. Moritani's separate uniform-step
reviewable preview; normal activation and any claim of approved UX remain pending. question is not answered by this approval.
## Preview evidence and remaining acceptance ## Preview evidence and remaining acceptance
+5 -4
View File
@@ -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 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 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 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 automatic-action preference. Moritani's uniform-private-step approval remains
with Mentat. Until resolved, activation requires an explicit development preview; separate: the 11 October Mentat approval applies only to that card-show flow.
no ordinary action or room API enables it. A publicly spent faction or prior This ability still requires its explicit development preview; no ordinary
normal traitor call can be skipped without consulting hidden eligibility. 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 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 decline and do not inspect rival hidden state. The public history shows only
+16 -3
View File
@@ -1,6 +1,20 @@
# Rule decision index # 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 ## 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) | | 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) | | 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) | | 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) | | 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) | | 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) | | 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 | | 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) | | 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) | | 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) | | 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) |
+1 -1
View File
@@ -63,7 +63,7 @@ export function classicDiscoveryLeaderSkillsProfile(game: LeaderSkillProfile): b
game.moritaniAssassinatePreview || game.advancedPreview || game.kullPreview || game.moritaniAssassinatePreview || game.advancedPreview || game.kullPreview ||
game.nexusKullPreview || game.guildBetrayalPreview || game.richeseBetrayalPreview || game.nexusKullPreview || game.guildBetrayalPreview || game.richeseBetrayalPreview ||
game.nexusIxianReplacementPreview || game.nexusIxianBetrayalPreview || game.nexusIxianReplacementPreview || game.nexusIxianBetrayalPreview ||
game.nexusHarkonnenBetrayalPreview || game.mentatQuestionPreview || game.nexusHarkonnenBetrayalPreview ||
game.spiceBankerIncomePreview) return false; game.spiceBankerIncomePreview) return false;
for (let i = 0; i < players.length; i++) { for (let i = 0; i < players.length; i++) {
for (let j = 0; j < i; j++) if (players[j].faction === players[i].faction) return false; for (let j = 0; j < i; j++) if (players[j].faction === players[i].faction) return false;
+5 -9
View File
@@ -2028,8 +2028,6 @@ export type Game = {
}}; }};
bureaucratPaymentEvent?: string; bureaucratPaymentEvent?: string;
bureaucratUseEvents?: 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. */ /** Private development opt-in while uniform hidden-eligibility UX is pending. */
moritaniAssassinatePreview?: true; moritaniAssassinatePreview?: true;
/** Explicit development-only neutral Semuta reaction profile. */ /** Explicit development-only neutral Semuta reaction profile. */
@@ -7508,7 +7506,7 @@ function baseStartProfileBlock(g: Game): string | null {
return 'Discoveries are still being implemented.'; return 'Discoveries are still being implemented.';
if (g.leaderSkills) return 'Leader Skills 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.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.'; 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'))) if (!g.players.every(p => FACTIONS.some(entry => entry.id === p.faction && entry.expansion === 'base')))
return 'Expansion factions are still being implemented.'; return 'Expansion factions are still being implemented.';
@@ -7969,7 +7967,7 @@ function bankerIncomeProfile(g: Game): boolean {
nativeExpansionLeaderSkillsProfile(g)) && nativeExpansionLeaderSkillsProfile(g)) &&
!g.nexusCards && !g.homeworlds && (!g.techTokens || classicTechLeaderSkillsProfile(g) || nativeTechLeaderSkillsProfile(g) || strongholdLeaderSkillsProfile(g)) && !g.nexusCards && !g.homeworlds && (!g.techTokens || classicTechLeaderSkillsProfile(g) || nativeTechLeaderSkillsProfile(g) || strongholdLeaderSkillsProfile(g)) &&
(!g.strongholdCards || strongholdLeaderSkillsProfile(g)) && !g.discoveryEnabled && !g.discoveries && !g.discoveryStash && (!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.moritaniAssassinatePreview && !g.moritaniAssassinate) || advancedMoritaniLeaderSkillsProfile(g)) && !g.advancedPreview &&
!g.kullPreview && !g.nexusKullPreview && !g.guildBetrayalPreview && !g.richeseBetrayalPreview && !g.kullPreview && !g.nexusKullPreview && !g.guildBetrayalPreview && !g.richeseBetrayalPreview &&
!g.nexusIxianReplacementPreview && !g.nexusIxianBetrayalPreview && !g.nexusHarkonnenBetrayalPreview; !g.nexusIxianReplacementPreview && !g.nexusIxianBetrayalPreview && !g.nexusHarkonnenBetrayalPreview;
@@ -11483,8 +11481,6 @@ function createMentatQuestion(g: Game) {
b.mentatQuestionEvent = event; b.mentatQuestionEvent = event;
} }
function mentatQuestionIntegrity(g: Game) { 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; const history = g.mentatHistory, events = g.mentatHistoryEvents;
requireRule(history === undefined && events === undefined || Array.isArray(history) && Array.isArray(events) && 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, 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) { if (!receipt) {
requireRule(receipt === undefined && !b?.mentatQuestionEvent && !decisions.length, requireRule(receipt === undefined && !b?.mentatQuestionEvent && !decisions.length,
'The saved Mentat question lost its original receipt.'); '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.'); 'The saved Mentat question has an invalid battle version.');
return; 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 && 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 && 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 && [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), chooserEvent: battleChooserEvent(g.turn, battleOrderAfterBattle(g), g.battleOrder?.uses.length ?? 0),
event: battleEvent, event: battleEvent,
...(g.leaderSkills ? { smugglerCollectionVersion: 1 as const } : {}), ...(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 && diplomatDefenseModeSupported(g) ? { diplomatDefenseVersion: 2 as const } : {}),
...(g.leaderSkills && ordinaryLeaderSkillModeSupported(g) ? { diplomatRetreatVersion: 1 as const } : {}), ...(g.leaderSkills && ordinaryLeaderSkillModeSupported(g) ? { diplomatRetreatVersion: 1 as const } : {}),
...(g.ecazTreachery ? { harassAllocationVersion: 1 as const } : {}), ...(g.ecazTreachery ? { harassAllocationVersion: 1 as const } : {}),
+3 -4
View File
@@ -26,7 +26,6 @@ export type LeaderSkillProfile = {
nexusIxianReplacementPreview?: unknown; nexusIxianReplacementPreview?: unknown;
nexusIxianBetrayalPreview?: unknown; nexusIxianBetrayalPreview?: unknown;
nexusHarkonnenBetrayalPreview?: unknown; nexusHarkonnenBetrayalPreview?: unknown;
mentatQuestionPreview?: unknown;
spiceBankerIncomePreview?: unknown; spiceBankerIncomePreview?: unknown;
}; };
@@ -47,7 +46,7 @@ function nexusLeaderSkillModulesSupported(game: LeaderSkillProfile, assassinatio
!game.advancedPreview && !game.kullPreview && !game.nexusKullPreview && !game.advancedPreview && !game.kullPreview && !game.nexusKullPreview &&
!game.guildBetrayalPreview && !game.richeseBetrayalPreview && !game.guildBetrayalPreview && !game.richeseBetrayalPreview &&
!game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview && !game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview &&
!game.nexusHarkonnenBetrayalPreview && !game.mentatQuestionPreview && !game.nexusHarkonnenBetrayalPreview &&
!game.spiceBankerIncomePreview; !game.spiceBankerIncomePreview;
} }
@@ -189,7 +188,7 @@ function discoveryLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean
!game.advancedPreview && !game.kullPreview && !game.nexusKullPreview && !game.advancedPreview && !game.kullPreview && !game.nexusKullPreview &&
!game.guildBetrayalPreview && !game.richeseBetrayalPreview && !game.guildBetrayalPreview && !game.richeseBetrayalPreview &&
!game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview && !game.nexusIxianReplacementPreview && !game.nexusIxianBetrayalPreview &&
!game.nexusHarkonnenBetrayalPreview && !game.mentatQuestionPreview && !game.nexusHarkonnenBetrayalPreview &&
!game.spiceBankerIncomePreview; !game.spiceBankerIncomePreview;
} }
@@ -465,7 +464,7 @@ function homeworldLeaderSkillModulesSupported(game: LeaderSkillProfile): boolean
game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview || game.kullPreview || game.nexusKullPreview || game.guildBetrayalPreview ||
game.richeseBetrayalPreview || game.nexusIxianReplacementPreview || game.richeseBetrayalPreview || game.nexusIxianReplacementPreview ||
game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview || game.nexusIxianBetrayalPreview || game.nexusHarkonnenBetrayalPreview ||
game.mentatQuestionPreview || game.spiceBankerIncomePreview) return false; game.spiceBankerIncomePreview) return false;
return true; return true;
} }
+2 -5
View File
@@ -1,7 +1,7 @@
import { treacheryDeck, type Card } from './cards'; import { treacheryDeck, type Card } from './cards';
import { richeseCards } from './richese-cards'; import { richeseCards } from './richese-cards';
import { quoteTupileIntelligenceAnswer } from './tupile-intelligence-answer'; 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 class MentatQuestionError extends Error {}
export const MENTAT_EMPTY_HAND = export const MENTAT_EMPTY_HAND =
@@ -40,10 +40,7 @@ export type MentatView = {
history: Omit<MentatObservation, 'signature'>[]; history: Omit<MentatObservation, 'signature'>[];
}; };
export function mentatQuestionModeSupported(game: LeaderSkillProfile): boolean { export function mentatQuestionModeSupported(game: LeaderSkillProfile): boolean {
return (noOtherLeaderSkillModules(game) && return ordinaryLeaderSkillModeSupported(game);
(!game.expansions.length ||
(game.expansions.length === 1 && game.expansions[0] === 'choam') ||
nativeExpansionLeaderSkillsProfile(game))) || classicTechLeaderSkillsProfile(game) || nativeTechLeaderSkillsProfile(game) || strongholdLeaderSkillsProfile(game);
} }
function inventory(expansions: readonly string[]): Card[] { function inventory(expansions: readonly string[]): Card[] {
return [ return [
+3 -3
View File
@@ -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.', '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.', '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.', '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.', '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 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.', '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.', '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.', '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.', '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'], 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: [ 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:'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:'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'] }, { 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'] },
+4
View File
@@ -161,6 +161,10 @@ void test('CHOAM prepaid ally support consumes its original donor pool and exclu
const donorFunds = donor.spice; const donorFunds = donor.spice;
const enemyFunds = enemy.spice; const enemyFunds = enemy.spice;
game = applyAction(game, owner.id, { type: 'chooseBattle', territory: 'wind_pass', target: enemy.id }); 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') while (game.decision?.kind === 'leaderSkillVisibility')
game = applyAction(game, game.decision.player, game = applyAction(game, game.decision.player,
{ type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); { type: 'leaderSkillVisibility', event: game.decision.event, hide: true });
-1
View File
@@ -153,7 +153,6 @@ void test('the explicit preview keeps expansion, module and separate development
game => { game.discoveryEnabled = true; }, game => { game.discoveryEnabled = true; },
game => { game.leaderSkills = createLeaderSkills(() => 0.25); }, game => { game.leaderSkills = createLeaderSkills(() => 0.25); },
game => { game.ecazTreachery = true; }, game => { game.ecazTreachery = true; },
game => { game.mentatQuestionPreview = true; },
game => { game.moritaniAssassinatePreview = true; }, game => { game.moritaniAssassinatePreview = true; },
]; ];
for (const change of changes) { for (const change of changes) {
+25
View File
@@ -120,6 +120,31 @@ void test('Bureaucrat requires two other players and tracks the unique physical
!bureaucratPaymentModeSupported({ expansions: ['choam'], homeworlds: {} }), !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 advanced of [false, true])
for (const choam 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`, () => { void test(`single-payer ${choam ? 'CHOAM' : 'base'} ${advanced ? 'Advanced' : 'Basic'} shipment diverts two after payment without replaying arrival`, () => {
+2
View File
@@ -61,6 +61,8 @@ export function stagedMarketGhola(
game = actChoamGhola(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent }); game = actChoamGhola(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent });
for (let step = 0; step < 30; step++) { for (let step = 0; step < 30; step++) {
if (game.response) game = passChoamGholaResponses(game); 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') else if (game.decision?.kind === 'leaderSkillVisibility')
game = actChoamGhola(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); game = actChoamGhola(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true });
else if (game.battle?.preparation) else if (game.battle?.preparation)
+6
View File
@@ -410,6 +410,12 @@ void test('a real concealed Planetologist green Special is discarded once after
territory: 'arrakeen', territory: 'arrakeen',
target: 'a', 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') { while (game.decision?.kind === 'leaderSkillVisibility') {
const decision = game.decision; const decision = game.decision;
game = applyAction(game, decision.player, { game = applyAction(game, decision.player, {
+2 -3
View File
@@ -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', () => { void test('Discovery Skills cannot silently drop unsupported native families or unrelated modules and previews', () => {
const unsupported = lobby(['ixians', 'ecaz', 'guild'], ['ix', 'ecaz']); const unsupported = lobby(['ixians', 'ecaz', 'guild'], ['ix', 'ecaz']);
assert.throws(() => initializeLeaderSkillsGameForAudit(unsupported), RuleError); 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[0].advanced = false; overlays[0].strongholdCards = createStrongholdCards();
overlays[1].mentatQuestionPreview = true; overlays[1].spiceBankerIncomePreview = true;
overlays[2].spiceBankerIncomePreview = true;
for (const game of overlays) assert.throws(() => initializeLeaderSkillsGameForAudit(game), RuleError); for (const game of overlays) assert.throws(() => initializeLeaderSkillsGameForAudit(game), RuleError);
const two = lobby(['guild', 'emperor'], []); two.techTokens = createTechTokens(); const two = lobby(['guild', 'emperor'], []); two.techTokens = createTechTokens();
assert.throws(() => initializeLeaderSkillsGameForAudit(two), RuleError); assert.throws(() => initializeLeaderSkillsGameForAudit(two), RuleError);
@@ -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.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.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.spiceBankerIncomePreview, undefined);
assert.equal(f.game.mentatQuestionPreview, undefined);
if (tech) { if (tech) {
assert.deepEqual(f.setup.techTokens, createTechTokens()); assert.deepEqual(f.setup.techTokens, createTechTokens());
assert.ok(TECH_TOKENS.every(token => f.afterFirstStorm.techTokens![token.id].owner !== null)); assert.ok(TECH_TOKENS.every(token => f.afterFirstStorm.techTokens![token.id].owner !== null));
-1
View File
@@ -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'); const snapshot = join(first, 'failed-base-3-advanced.json');
for (const field of [ for (const field of [
'ecazTreachery', 'ecazTreachery',
'mentatQuestionPreview',
'moritaniAssassinatePreview', 'moritaniAssassinatePreview',
'moritaniAssassinate', 'moritaniAssassinate',
'moritaniAssassinateResume', 'moritaniAssassinateResume',
+1 -1
View File
@@ -217,7 +217,7 @@ export function createDiscoveryNativeE2SkillsFixture(options: DiscoveryNativeE2S
joinGame(game, newPlayer(faction, faction, faction)); joinGame(game, newPlayer(faction, faction, faction));
assert.deepEqual(game.expansions, ['choam']); assert.deepEqual(game.expansions, ['choam']);
if (options.advanced !== undefined) assert.equal(game.advanced, options.advanced); 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 && assert.ok(game.status === 'lobby' || game.status === 'setup' && game.turn === 1 && game.phase === 0 &&
game.leaderSkills?.assignments.length === 0 && game.players.every(p => 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), p.hand.length <= (p.faction === 'harkonnen' ? 2 : 1) && p.traitors.length === 0 && p.traitorChoices.length === 0),
+1 -1
View File
@@ -149,7 +149,7 @@ export function initializeDiscoveryNativeE3Skills(options: DiscoveryNativeE3Skil
assert.ok(game.players.every(p => ['moritani', 'guild', 'atreides', 'emperor', 'fremen', 'beneGesserit', assert.ok(game.players.every(p => ['moritani', 'guild', 'atreides', 'emperor', 'fremen', 'beneGesserit',
...(advanced ? [] : ['harkonnen'])].includes(p.faction))); ...(advanced ? [] : ['harkonnen'])].includes(p.faction)));
assert.ok(!strongholds || advanced); 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.status === 'lobby') {
if (!!game.techTokens !== tech) game = applyAction(game, game.host, { type: 'techTokens', enabled: tech }); 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 }); if (!!game.strongholdCards !== strongholds) game = applyAction(game, game.host, { type: 'strongholdCards', enabled: strongholds });
@@ -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(advanced || native !== 'ecaz' || game.players.length % 2 === 0, 'Basic odd-seat Ecaz remains source guarded.');
assert.ok(!strongholds || advanced); assert.ok(!strongholds || advanced);
assert.ok(!game.homeworlds && !game.ecazTreachery && !game.advancedPreview && !game.semutaPreview && assert.ok(!game.homeworlds && !game.ecazTreachery && !game.advancedPreview && !game.semutaPreview &&
!game.spiceBankerIncomePreview && !game.mentatQuestionPreview); !game.spiceBankerIncomePreview);
if (game.status === 'lobby') { if (game.status === 'lobby') {
if (!!game.techTokens !== tech) game = applyAction(game, game.host, { type: 'techTokens', enabled: tech }); 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 }); if (!!game.strongholdCards !== strongholds) game = applyAction(game, game.host, { type: 'strongholdCards', enabled: strongholds });
+1
View File
@@ -46,6 +46,7 @@ export function nextMoritaniSkillsModulesStep(game: Game): StrongholdFactionsNat
return nextStrongholdFactionsNativeStep(game); return nextStrongholdFactionsNativeStep(game);
const d = game.decision; const d = game.decision;
if (d?.kind === 'leaderSkillVisibility') return { actor: d.player, action: { type: 'leaderSkillVisibility', event: d.event, hide: false } }; 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') { if (d?.kind === 'moritaniSetup') {
const action = moritaniSkillsModulesPolicy(game, d.player)[0]; assert.ok(action); const action = moritaniSkillsModulesPolicy(game, d.player)[0]; assert.ok(action);
return { actor: d.player, action }; return { actor: d.player, action };
+1 -2
View File
@@ -10,14 +10,13 @@ export const NATIVE_MENTAT_FAMILIES = ['ixians','tleilaxu','choam','moritani'] a
export type NativeMentatFamily = typeof NATIVE_MENTAT_FAMILIES[number]; export type NativeMentatFamily = typeof NATIVE_MENTAT_FAMILIES[number];
/** Real native setup/assignment and Storm; conserved rule-unit battle position. */ /** 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}; const options = {requestedSkill:'mentat' as const};
let game = family === 'ixians' ? completedIxSkillsGame(options) let game = family === 'ixians' ? completedIxSkillsGame(options)
: family === 'tleilaxu' ? completedTleilaxuSkillsGame(options) : family === 'tleilaxu' ? completedTleilaxuSkillsGame(options)
: family === 'choam' ? completedChoamSkillsGame(options) : family === 'choam' ? completedChoamSkillsGame(options)
: completedMoritaniSkillsGame(options); : completedMoritaniSkillsGame(options);
game = finishToMovement(game); game = finishToMovement(game);
if (preview) game.mentatQuestionPreview = true;
const owner = game.players.find(p => p.faction === family)!; const owner = game.players.find(p => p.faction === family)!;
const target = game.players.find(p => p.faction === 'emperor')!; const target = game.players.find(p => p.faction === 'emperor')!;
for (const p of game.players) { for (const p of game.players) {
+2
View File
@@ -57,6 +57,8 @@ function open(game: Game, territory = 'wind_pass') {
game = act(game, 'i', { type: 'chooseBattle', territory, target: 'e' }); game = act(game, 'i', { type: 'chooseBattle', territory, target: 'e' });
for (let step = 0; step < 30; step++) { for (let step = 0; step < 30; step++) {
if (game.response) game = passResponses(game); 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') else if (game.decision?.kind === 'leaderSkillVisibility')
game = act(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true }); game = act(game, game.decision.player, { type: 'leaderSkillVisibility', event: game.decision.event, hide: true });
else if (game.battle?.preparation) else if (game.battle?.preparation)
+7
View File
@@ -136,6 +136,7 @@ function clearBattlePreparation(state: Game): Game {
let g = state; let g = state;
while ( while (
g.decision?.kind === 'leaderSkillVisibility' || g.decision?.kind === 'leaderSkillVisibility' ||
g.decision?.kind === 'mentatQuestion' ||
g.decision?.kind === 'fullPlanOffer' || g.decision?.kind === 'fullPlanOffer' ||
g.battle?.preparation g.battle?.preparation
) )
@@ -145,6 +146,12 @@ function clearBattlePreparation(state: Game): Game {
event: g.decision.event, event: g.decision.event,
hide: false, 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') else if (g.decision?.kind === 'fullPlanOffer')
g = applyAction(g, g.decision.player, { g = applyAction(g, g.decision.player, {
type: 'decision', type: 'decision',
+18
View File
@@ -182,6 +182,12 @@ function battleFor(
territory: 'arrakeen', territory: 'arrakeen',
target: 'e', 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') { while (next.decision?.kind === 'leaderSkillVisibility') {
const decision = next.decision; const decision = next.decision;
next = applyAction(next, decision.player, { next = applyAction(next, decision.player, {
@@ -404,6 +410,12 @@ void test('a Voice that forbids the last alternate Cheap Hero automatically conc
territory: 'arrakeen', territory: 'arrakeen',
target: 'b', 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') while (g.decision?.kind === 'leaderSkillVisibility')
g = applyAction(g, g.decision.player, { g = applyAction(g, g.decision.player, {
type: 'leaderSkillVisibility', type: 'leaderSkillVisibility',
@@ -513,6 +525,12 @@ void test('Ghola revival during an unfinished battle preserves its power stage t
territory: 'carthag', territory: 'carthag',
target: 'e', 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') while (g.decision?.kind === 'leaderSkillVisibility')
g = applyAction(g, g.decision.player, { g = applyAction(g, g.decision.player, {
type: 'leaderSkillVisibility', type: 'leaderSkillVisibility',
+11
View File
@@ -381,6 +381,17 @@ void test('battle death and Ghola revival preserve one private replacement skill
territory: 'arrakeen', territory: 'arrakeen',
target: bg.id, 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') { while (game.decision?.kind === 'leaderSkillVisibility') {
const owner = f.auths.findIndex( const owner = f.auths.findIndex(
(auth) => auth.playerId === game.decision!.player, (auth) => auth.playerId === game.decision!.player,
-4
View File
@@ -34,14 +34,12 @@ export function mentatQuestionGame({
cards = ['Crysknife', 'Snooper'], cards = ['Crysknife', 'Snooper'],
begin = true, begin = true,
captured = false, captured = false,
preview = true,
}: { }: {
advanced?: boolean; advanced?: boolean;
choam?: boolean; choam?: boolean;
cards?: string[]; cards?: string[];
begin?: boolean; begin?: boolean;
captured?: boolean; captured?: boolean;
preview?: boolean;
} = {}): Game { } = {}): Game {
const native = choam ? 'richese' : 'emperor'; const native = choam ? 'richese' : 'emperor';
let game = createGame( let game = createGame(
@@ -57,8 +55,6 @@ export function mentatQuestionGame({
joinGame(game, newPlayer('o', 'Observer', 'guild')); joinGame(game, newPlayer('o', 'Observer', 'guild'));
for (const player of game.players) for (const player of game.players)
game = applyAction(game, player.id, { type: 'ready' }); 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'); const index = LEADER_SKILL_CARDS.findIndex((card) => card.id === 'mentat');
let cursor = LEADER_SKILL_CARDS.length - 1; let cursor = LEADER_SKILL_CARDS.length - 1;
mock.method(globalThis.crypto, 'getRandomValues', (array: Uint32Array) => { mock.method(globalThis.crypto, 'getRandomValues', (array: Uint32Array) => {
+20 -29
View File
@@ -107,38 +107,16 @@ void test('Mentat names exact printed/default weapons and validates physical han
assert.throws(() => mentatHand(ixDeck().slice(0, 1), [])); 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 advanced of [false, true])
for (const choam of [false, true]) { for (const choam of [false, true]) {
const ordinary = mentatQuestionGame({ advanced, choam, preview: false }); const game = mentatReload(mentatQuestionGame({ advanced, choam }));
assert.equal(ordinary.mentatQuestionPreview, undefined); assert.equal(game.battle!.mentatQuestionVersion, 1);
assert.equal(ordinary.battle!.mentatQuestionVersion, undefined); assert.equal(normalizeAutomaticGame(game).decision?.kind, 'mentatQuestion');
assert.equal(ordinary.battle!.mentatQuestion, undefined); assert.equal(viewGame(game, 'a').mentat.pending!.stage, 'name');
assert.equal(ordinary.decision?.kind, 'leaderSkillVisibility'); assert.deepEqual(viewGame(game, 'd').mentat.pending!.weapons, []);
assert.equal(viewGame(ordinary, 'a').mentat.pending, null); assert.deepEqual(viewGame(game, 'o').mentat.history, []);
assert.equal(
applyAction(ordinary, 'a', {
type: 'advanceBots',
mentatQuestionPreview: true,
}).mentatQuestionPreview,
undefined,
);
} }
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]) 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, viewGame(normalizeAutomaticGame(mentatReload(legacy)), 'a').mentat.pending,
null, 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())); let game = finishPowers(ask(mentatQuestionGame()));
const original = game.battle!.mentatQuestionEvent; const original = game.battle!.mentatQuestionEvent;
game = applyAction(game, 'a', { game = applyAction(game, 'a', {
+3 -1
View File
@@ -85,7 +85,9 @@ function openBattle(state: Game, owner = 'm', opponent = 'e', hide = true): Game
if (g.decision?.kind === 'leaderSkillVisibility') { if (g.decision?.kind === 'leaderSkillVisibility') {
const d = g.decision; const d = g.decision;
g = act(g, d.player, { type: 'leaderSkillVisibility', event: d.event, hide: d.player === owner ? hide : true }); 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 }); g = act(g, g.decision.player, { type: 'decision', decline: true });
} else if (g.battle?.preparation) { } else if (g.battle?.preparation) {
g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' }); g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' });
+1 -4
View File
@@ -6,10 +6,7 @@ import {DIFFICULTIES} from '../game/bot-profiles';
import {NATIVE_MENTAT_FAMILIES,nativeMentatBattle} from './fixture-native-mentat'; import {NATIVE_MENTAT_FAMILIES,nativeMentatBattle} from './fixture-native-mentat';
for (const family of NATIVE_MENTAT_FAMILIES) { 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`,()=>{ void test(`${family} Mentat asks normally 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');
for (const matching of [true,false]) { for (const matching of [true,false]) {
let game = nativeMentatBattle(family,matching); let game = nativeMentatBattle(family,matching);
assert.equal(game.decision!.kind,'mentatQuestion'); assert.equal(game.decision!.kind,'mentatQuestion');
@@ -32,6 +32,7 @@ function open(g: Game, owner = 't', opponent = 'e') {
g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent }); g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: opponent });
for (let i = 0; i < 30; i++) { 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' }); 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.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 if (g.battle?.preparation) g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' });
else break; else break;
+2
View File
@@ -29,6 +29,8 @@ function open(g: Game, owner = 't') {
g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' }); g = act(g, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' });
for (let i = 0; i < 30; i++) { 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' }); 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') else if (g.decision?.kind === 'leaderSkillVisibility')
g = act(g, g.decision.player, { type: 'leaderSkillVisibility', event: g.decision.event, hide: true }); 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 if (g.battle?.preparation) g = act(g, g.battle.preparation.owner, { type: 'declineBattlePower' });
+2
View File
@@ -63,6 +63,8 @@ function killedOriginalSkill(owner: 't' | 'a' = 't'): Game {
game = act(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' }); game = act(game, owner, { type: 'chooseBattle', territory: 'wind_pass', target: 'e' });
for (let step = 0; step < 20; step++) { for (let step = 0; step < 20; step++) {
if (game.response) game = passWindow(game); 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') { else if (game.decision?.kind === 'leaderSkillVisibility') {
const decision = game.decision; const decision = game.decision;
game = act(game, decision.player, { game = act(game, decision.player, {
+1 -2
View File
@@ -1088,8 +1088,7 @@ function resumedGame(path: string) {
if ( if (
(game.leaderSkills && !nativeExpansionLeaderSkillsProfile(game) && !composedSkills) || (game.leaderSkills && !nativeExpansionLeaderSkillsProfile(game) && !composedSkills) ||
((game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker) && !discovery) || ((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.techTokens && !discovery && !composedSkills && !nativeFactionTechProfile(game) && !classicNexusModulesProfile(game) && !pairedNexusModulesProfile(game) && !mixedE3NexusProfile(game) && !mixedE1E2NexusProfile(game) && !pairedE3NexusProfile(game) && !singleE1E2NexusProfile(game) && !standaloneE3NexusProfile(game))
game.mentatQuestionPreview
) )
throw new Error('--resume sample scenarios exclude unsupported optional modules.'); throw new Error('--resume sample scenarios exclude unsupported optional modules.');
const sourceName = basename(path), namedSource = sourceName.startsWith('failed-'); const sourceName = basename(path), namedSource = sourceName.startsWith('failed-');
-5
View File
@@ -51,8 +51,6 @@ export type PrototypeProfile = (typeof PROTOTYPE_PROFILES)[number];
export type PrototypeOptions = { export type PrototypeOptions = {
/** Explicit independent card variant for the fresh combined-army profile. */ /** Explicit independent card variant for the fresh combined-army profile. */
ecazTreachery?: boolean; ecazTreachery?: boolean;
/** Private uniform-question preview; ordinary skill starts stay unchanged. */
mentatQuestion?: boolean;
/** Independent classic Nexus/Skills composition, not a public activation. */ /** Independent classic Nexus/Skills composition, not a public activation. */
nexusCards?: boolean; nexusCards?: boolean;
/** Explicit classic Discovery/Skills composition, not a public activation. */ /** Explicit classic Discovery/Skills composition, not a public activation. */
@@ -98,14 +96,11 @@ export function startPrototypeRoom(
throw new Error('Unknown prototype profile.'); throw new Error('Unknown prototype profile.');
if (options.ecazTreachery && profile !== 'ecaz-occupy') if (options.ecazTreachery && profile !== 'ecaz-occupy')
throw new Error('--ecaz-treachery requires the ecaz-occupy profile.'); 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') if (options.nexusCards && profile !== 'leader-skills')
throw new Error('--nexus-cards requires the leader-skills profile.'); throw new Error('--nexus-cards requires the leader-skills profile.');
if (options.discoveries && profile !== 'leader-skills' && profile !== 'nexus') if (options.discoveries && profile !== 'leader-skills' && profile !== 'nexus')
throw new Error('--discoveries requires the leader-skills or nexus profile.'); throw new Error('--discoveries requires the leader-skills or nexus profile.');
if (options.ecazTreachery) initial.ecazTreachery = true; if (options.ecazTreachery) initial.ecazTreachery = true;
if (options.mentatQuestion) initial.mentatQuestionPreview = true;
if ((profile === 'nexus' || options.nexusCards) && !initial.nexusCards) if ((profile === 'nexus' || options.nexusCards) && !initial.nexusCards)
initial.nexusCards = { cards: null, phase: null }; initial.nexusCards = { cards: null, phase: null };
if (options.discoveries) initial.discoveryEnabled = true; if (options.discoveries) initial.discoveryEnabled = true;
+4 -8
View File
@@ -16,7 +16,6 @@ async function main() {
version: { type: 'string' }, version: { type: 'string' },
out: { type: 'string' }, out: { type: 'string' },
'ecaz-treachery': { type: 'boolean' }, 'ecaz-treachery': { type: 'boolean' },
'mentat-question': { type: 'boolean' },
'nexus-cards': { type: 'boolean' }, 'nexus-cards': { type: 'boolean' },
discoveries: { type: 'boolean' }, discoveries: { type: 'boolean' },
help: { type: 'boolean' }, help: { type: 'boolean' },
@@ -26,9 +25,9 @@ async function main() {
console.log( 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.', '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.' + '\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.' + '\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.', '\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.', '\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.', '\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.', '\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 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.', '\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.', '\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.', '\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.', '\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') if (values['ecaz-treachery'] && values.profile !== 'ecaz-occupy')
throw new Error('--ecaz-treachery requires --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') if (values['nexus-cards'] && values.profile !== 'leader-skills')
throw new Error('--nexus-cards requires --profile leader-skills.'); throw new Error('--nexus-cards requires --profile leader-skills.');
if (values.discoveries && values.profile !== 'leader-skills' && values.profile !== 'nexus') if (values.discoveries && values.profile !== 'leader-skills' && values.profile !== 'nexus')
@@ -83,7 +80,7 @@ async function main() {
const db = new DatabaseSync(values.db); const db = new DatabaseSync(values.db);
try { try {
const result = startPrototypeRoom(db, values.room, version, values.profile, 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( writeFileSync(
resolve(values.out, 'prototype.json'), resolve(values.out, 'prototype.json'),
JSON.stringify( JSON.stringify(
@@ -92,7 +89,6 @@ async function main() {
startedAt: new Date().toISOString(), startedAt: new Date().toISOString(),
profile: values.profile, profile: values.profile,
ecazTreachery: !!values['ecaz-treachery'], ecazTreachery: !!values['ecaz-treachery'],
mentatQuestion: !!values['mentat-question'],
nexusCards: !!values['nexus-cards'], nexusCards: !!values['nexus-cards'],
source, source,
beforeVersion: version, beforeVersion: version,