diff --git a/components/game-table.tsx b/components/game-table.tsx
index ab88fba3..23f0c7f0 100644
--- a/components/game-table.tsx
+++ b/components/game-table.tsx
@@ -1293,9 +1293,10 @@ export function GameTable({
{classicNexusPreview && (
Classic Nexus reactions preview · Basic/Advanced classic factions, original base33
- or selected independent36 Treachery, optionally all14 Leader Skills. Enabled native
- reactions retain original card, force and payment authority. Acknowledgement timing
- and purchase/bonus ordering remain provisional. Other modules and complete Nexus
+ or selected independent36 Treachery, optionally all14 Leader Skills. Original
+ Tech (3+ seats) and Advanced Strongholds (2+ seats) may compose. Native reactions
+ retain original card, force and payment authority. Acknowledgement timing and
+ purchase/bonus ordering remain provisional. Other modules and complete Nexus
acceptance remain separate.{' '}
Preview rules and limits
diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md
index 10012188..f042c240 100644
--- a/docs/CURRENT_STATUS.md
+++ b/docs/CURRENT_STATUS.md
@@ -29,6 +29,29 @@ optional skill bands and combinations remain open.
Rules implementation continues; comprehensive assurance and administration
remain deferred until all rules are implemented.
+**Classic core phase modules — 18 October. Prototyped / Partial.**
+Fresh classic Nexus now composes original Tech3+ and Advanced Strongholds2+
+with selected33/36 and optional14. Normal host controls retain all five
+requested modules through actual training/Traitors into first Storm. Real
+first-Storm ownership, phase payout and first-Mentat claims are not assigned
+or retrofitted; other native/World/Discovery/Semuta envelopes stay separate.
+
+Earned Arrakeen support3 uses bank2/personal1 beside real reserve fees;
+trained base33 Carthag Shield survives poison after native call cancellation
+and reaches original cleanup/private replacement. Fixed paid5 then ordinary
+free2 retains Axlotl pending2 at purse8 until actual phase4 close pays10.
+Combined phase funding/payout and paid2 replacement preserve original owners.
+
+Frozen43-file **439/439**, focused1/1 and repaired Arrakeen2/2, types/lint/
+build pass. Fixture corrections preserve original Guild-ally3 tariff and
+separate city Collection2; no game calculation changed. Four actual390px
+journeys and four legal policies/66 owned choices finish; no calibration claim.
+No gameplay/application exception or overflow. Two DELETE204 cleanup fetches
+logged ERR_ABORTED and an idle wait timed out; tool diagnostics are preserved.
+Temporary memory/routes/executable scripts removed/released204, no stored room/
+reset/restart/deployment. Full modes, universal timing and assurance stay open.
+[Source-bound owner verification](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
**Classic Nexus core — 18 October. Prototyped / Partial.**
Ordinary base33 all14 native calls/purchases lacked reaction prompts. One
public capability now admits original33 or selected36 with Nexus12 and
diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md
index d1da5414..8d504baa 100644
--- a/docs/IMPLEMENTATION_STATUS.md
+++ b/docs/IMPLEMENTATION_STATUS.md
@@ -1,5 +1,48 @@
# Dune implementation status
+## 18 October 2026 — Classic Nexus with earned phase modules
+
+Original independent36/Nexus+Tech and all14/phase starts rejected through
+native lobby actions; plain33/all14/phase started without reaction states.
+The public classic core now composes original Tech3+ and Advanced Strongholds2+,
+preserving Basic/Advanced prerequisites, selected33/36 and optional14. Bare
+independent-card consumers share that phase capability. Fee source snapshots
+bind only present modules; no played state, token/card owner or history is
+fabricated. Other native/World/Discovery/Semuta and reaction envelopes stay.
+
+Two disjoint lanes add original-action Tech and held-Stronghold sources plus
+eleven meaningful regressions. Real first-Storm owners and first-Mentat cards
+precede actual death, Revival, funded delivery and battle. Parent removed
+setup/wiring and same-prestate replay pins, kept real error/cost boundaries,
+added a second genuine free return for one-pile behavior and removed an unused
+copied drain snapshot. The Tech worker ran its four-case file twice despite
+the freeze instruction; those repeated lane checks are not owner certification.
+
+Parent frozen43-file **439/439**, focused second-free1/1, repaired native
+Arrakeen2/2, types/lint/build pass. Initial437/439 and the narrow repair exposed
+incorrect fixture expectations for real Guild-ally shipping (five cost3) and
+later Carthag Collection2; the original game calculations are unchanged.
+Four legal policies perform66 owned choices through native income/Revival
+and earned battles, with varying outcomes rather than a calibration claim.
+
+Four actual390px journeys finish original five-option lobby readiness/
+training/Traitors/Storm (stock36/Nexus12, no premature owners), earned Arrakeen
+support3 bank2/personal1 with three reserve fees and separate Collection2,
+trained base33 Carthag Shield survival/native call cancellation/cleanup/private
+Mentat replacement, and fixed paid5/free2 Axlotl pending2→actual phase-end
+purse8→10. Combined original paid5/Bureaucrat2 also retains claimedTurn1 cards
+and Heighliners pending1 until Guild6→7 at native phase5 close. Actual paid2
+Ixian replacement with both earned modules preserves holders/piles/wallet10.
+
+No application/gameplay exception or horizontal overflow. Two isolated cleanup
+DELETEs returned204 but browser diagnostics reported ERR_ABORTED; a network-idle
+wait timed out. Diagnostics are preserved and reported, not replayed away.
+Temporary routes/memory/executable programmes removed/released204; no saved room,
+reset, server restart or deployment. Existing build warnings and full-mode/
+universal-timing/assurance gates remain. Tech now has canonical helper/consumer
+evidence in the existing checklist instead of another broad history lookup.
+[Canonical current source and proof](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## 18 October 2026 — Classic Nexus core and funded native card shipments
Ordinary base33 all14 genuine Harkonnen call and Ixian purchase still lacked
diff --git a/docs/LEADER_SKILLS_RUNTIME.md b/docs/LEADER_SKILLS_RUNTIME.md
index 2ceaf467..703e5a88 100644
--- a/docs/LEADER_SKILLS_RUNTIME.md
+++ b/docs/LEADER_SKILLS_RUNTIME.md
@@ -7,6 +7,18 @@ and fixture opt-ins are removed; existing games are not retrofitted.
Earlier dated references to a separate entry describe historical checkpoints.
See [current activation and limits](SPICE_BANKER_RUNTIME.md#mandatory-ordinary-skills-default--17-october-2026).
+## Classic Nexus with original phase modules — 18 October 2026
+
+Classic base33/selected36 Nexus now retains optional all14 with original
+Tech3+ and Advanced Strongholds2+. Public capability and original setup
+preserve actual first-Storm tokens/end-Mentat cards; none is assigned by a
+fixture or retrofitted into played state. Native bank subsidy funds actual
+holder support, not reserve fees or personal money. Actual trained Carthag
+Shield defense, original call/cancellation, winner cleanup and private Mentat
+replacement remain. Fixed paid groups/free returns and phase Tech income
+stay separate from mandatory normal Banker.
+[439/439, actual normal controls and legal policies](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## Classic Nexus core without card dependency — 18 October 2026
Original base33 or explicitly selected independent36 Nexus core admits
diff --git a/docs/NEXUS_CARD_RULES.md b/docs/NEXUS_CARD_RULES.md
index 46e6317d..48d4f2cc 100644
--- a/docs/NEXUS_CARD_RULES.md
+++ b/docs/NEXUS_CARD_RULES.md
@@ -2,6 +2,66 @@
Original source audit, 10 September 2026, with dated bounded runtime follow-ups below. This document establishes the printed component inventory, source protocol and implementation boundaries; none lifts expansion release gates. The ordinary worm-triggered alliance Nexus already in the engine is distinct from this optional card module.
+## Classic Nexus original phase modules — 18 October 2026
+
+**Prototyped / Partial.** Fresh classic Nexus core now retains original
+Tech3–6 Basic/Advanced and/or Strongholds2–6 Advanced with base33 or explicitly
+selected independent36, optionally all14 Skills. The shared public predicate
+keeps the printed player-count/rules prerequisites. Other native families,
+Worlds, Discovery/Semuta and reaction envelopes remain separate. No played
+state is reflagged or assigned a token/card holder.
+
+Actual host controls retain all five requested modules through original
+readiness, starting cards, four private skill assignments and native Traitor
+selection into first Storm. Stock36/Nexus12 remain; Tech owners are still
+unassigned and Stronghold claim0 until their original earning boundaries.
+Only original setup lotteries/unopened decks select fixture sources.
+
+Original Guild fee parents conditionally bind the real Tech/Stronghold state;
+bare ordinary parents gain no unconditional new keys. Existing signed card
+shipments, native counters, original currency and physical continuation remain.
+The combined original-action payment reaches payer7/recipient13 after Bank2,
+Heighliners pending1 while Guild stays6, then actual phase5 close pays Guild7.
+Actual first-Mentat holders remain unchanged through that contested arrival.
+Paid2 Ixian replacement likewise preserves both earned module states, exact
+discard/draw, wallet10 and one following lot.
+
+New original-action consumer evidence:
+- Tech: real first-Storm owner/count, funded five-for-one delivery, matching
+ phase-end Heighliners income, fixed Tleilaxu paid5 versus ordinary free
+ returns, a second genuine free request without another pile, pending-spice
+ affordability and native winner transfer after Harass.
+- Earned Arrakeen: temporary5 support3 uses bank2/personal1, while independent
+ Reinforcements pays three actual reserves. Observed board2/Tanks6/reserves12
+ and purse6 before separate Collection2→8; real starred reserves stay unchanged.
+- Earned Carthag: an actual all14 trained disc behind the shield survives
+ poison with its physical Shield after the original call is canceled.
+ Nine forces survive; original winner card choices, losing retention,
+ capture decline, inspection and one private actual Mentat replacement remain.
+ Empty defense still loses the disc/army and held custody changes only at Mentat.
+
+Frozen43-file **439/439**, focused second-free1/1 and repaired Arrakeen2/2,
+types/lint/build pass. Initial437/439 exposed two new fixture assumptions:
+the real Guild ally pays rounded3 for five counters, and later native
+Carthag Collection2 is separate from support1. Assertions now preserve those
+existing rules; no tariff/income calculation changed. Setup/wiring and
+same-snapshot replay pins were removed in favor of actual consumer transitions.
+Tech now has canonical helper/consumer/check evidence in the existing checklist.
+
+Four original-control390px journeys finish normal five-module setup,
+Arrakeen support/fees, trained Carthag defense/Mentat and fixed5+free2 Axlotl.
+The last preserves purse8 while token2 waits, then actual phase4 close pays10.
+Four legal policies execute66 owned choices through original income, Revival
+and battles; outcomes vary and are not strength calibration.
+All gameplay requests/controls complete without application exceptions or
+horizontal overflow. Two isolated DELETE cleanup fetches returned204 but
+browser diagnostics logged `ERR_ABORTED`; a cleanup network-idle wait timed
+out. These tool/lifecycle diagnostics are retained, not erased or represented
+as failed gameplay. Temporary routes, memory and executable smoke scripts are
+removed/released204; no stored room/reset/restart/deployment.
+Source-bound proof: `/tmp/dune-classic-nexus-phase-18oct.json`. Complete modes,
+all combinations, universal timing and deferred assurance remain unfinished.
+
## Classic Nexus core and funded card shipments — 18 October 2026
**Prototyped / Partial.** Original E3 pp4/9/11 permits Nexus independently of
diff --git a/docs/NEXUS_GUILD_RULES.md b/docs/NEXUS_GUILD_RULES.md
index 9ead65d7..866c2c35 100644
--- a/docs/NEXUS_GUILD_RULES.md
+++ b/docs/NEXUS_GUILD_RULES.md
@@ -4,6 +4,17 @@
Source audit, 10 September 2026. This documents the three printed panels and their integration boundaries. It does not implement them or certify complete Guild, Homeworld, Nexus or expansion compliance. [Common Nexus lifecycle](NEXUS_CARD_RULES.md) and existing release gates continue to apply. No user question was sent during this audit.
+## Classic original phase-module funding — 18 October 2026
+
+The classic core entry now retains original Tech3+ and Advanced Strongholds2+,
+base33 or selected36 and optional14. Guild fee parents bind only genuinely
+enabled module state; original bare-parent grammar remains. Real token/card
+owners are earned at their original boundaries. Actual combined paid5/
+Guild-card receipt/Bureaucrat2 leaves payer7/recipient13/Guild6 while one
+Heighliners spice waits; actual phase5 close pays its owner Guild7, independent
+of the diverted shipping receiver. No phase income finances earlier fees.
+[Source and complete bounded evidence](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## Classic core funded card shipments — 18 October 2026
Fresh classic2–6 Basic/Advanced Nexus uses original base33 or selected
diff --git a/docs/NEXUS_HARKONNEN_RULES.md b/docs/NEXUS_HARKONNEN_RULES.md
index c348dfd9..325f4613 100644
--- a/docs/NEXUS_HARKONNEN_RULES.md
+++ b/docs/NEXUS_HARKONNEN_RULES.md
@@ -2,6 +2,17 @@
Source audit, 10 September 2026. This is a rules contract, not a runtime completion claim. The [common Nexus lifecycle](NEXUS_CARD_RULES.md) and existing expansion release gates still apply. The pending reaction-privacy question remains unresolved; this audit does not authorize a Betrayal reaction policy.
+## Earned phase cards and original replacement — 18 October 2026
+
+Fresh classic core now retains Tech3+ and Advanced Strongholds2+ with
+selected33/36, optionally14. Actual all14 base33 Carthag controls reveal the
+native matching call, spend the genuinely drawn Nexus, preserve physical
+Shield poison survival, finish original card/retention/capture/inspection
+priority and draw one private actual replacement at Mentat. The separate
+held module card is never added to the Treachery census or transferred early.
+Harass return and native winner token transfer create no free-Revival income.
+[Exact bounded proof](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## Original base33 core without card dependency — 18 October 2026
Fresh classic2–6 Basic/Advanced Nexus now initializes the original native
diff --git a/docs/NEXUS_IXIAN_REPLACEMENT_RULES.md b/docs/NEXUS_IXIAN_REPLACEMENT_RULES.md
index 17c02365..6822487d 100644
--- a/docs/NEXUS_IXIAN_REPLACEMENT_RULES.md
+++ b/docs/NEXUS_IXIAN_REPLACEMENT_RULES.md
@@ -2,6 +2,16 @@
Source audit: 2026-09-10. Bounded local prototype integration and verification: 2026-10-01. **Partial development verified**, not complete Ixian/Nexus family or expansion acceptance. This document records the source and runtime boundary of the fresh `ixian-replacement` profile. Existing [Nexus release gates](NEXUS_CARD_RUNTIME.md) remain in force.
+## Original phase-module purchased-card source — 18 October 2026
+
+Classic core retains Tech3+ and Advanced Strongholds2+, selected33/36 and
+optional14, through original setup/owner timing. Actual paid2 replacement
+with both earned modules keeps wallet10, discards only bought Reinforcements
+for the real deck top, spends Ixian Nexus once and reaches following lot1.
+Original module holders/piles remain; neither creates an extra purchase,
+card, subsidy or source alias. Other native/module envelopes stay separate.
+[Current source and evidence](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## Original base33 core without card dependency — 18 October 2026
Fresh classic2–6 Basic/Advanced Nexus initializes original purchase replacement
diff --git a/docs/NEXUS_RICHESE_RUNTIME.md b/docs/NEXUS_RICHESE_RUNTIME.md
index aeffa3cc..4791446e 100644
--- a/docs/NEXUS_RICHESE_RUNTIME.md
+++ b/docs/NEXUS_RICHESE_RUNTIME.md
@@ -2,6 +2,16 @@
This checkpoint adds the [source-audited Secret Ally](NEXUS_RICHESE_RULES.md) to an unallied holder's unused ordinary reserve shipment when Richese is absent. Choose one to five actual forces and price the group as one force. The physical force count, starred identity, destination and arrival restrictions remain ordinary shipment rules. It creates no concealed No-Field and no second shipment.
+## Original Tech and Stronghold composition — 18 October 2026
+
+Classic Nexus core retains the original phase modules with selected33/36
+and optional14. A real funded five-for-one arrival still delivers five and
+uses the one-counter invoice; original Heighliners accrues its actual owner's
+token count only after delivery and pays only at phase end. Neither the fee
+claim nor a held Stronghold card invents another trigger or personal subsidy.
+No other native/World/Discovery envelope or numerical rule changes.
+[Current phase consumers and controls](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## Classic core Guild fee composition — 18 October 2026
Classic base33 or selected independent36 Nexus, optionally all14, now retains
diff --git a/docs/RULE_DECISIONS.md b/docs/RULE_DECISIONS.md
index 356a8200..0e8e5edc 100644
--- a/docs/RULE_DECISIONS.md
+++ b/docs/RULE_DECISIONS.md
@@ -705,6 +705,7 @@ dedicated retrieved worked FAQs. [Contract and remaining limits](HOMEWORLD_OCCUP
| Classic independent Treachery / Nexus | Printed independent-variant authority composes fresh classic2–6 Basic/Advanced independent36/Nexus12, optionally all14, through one public configuration predicate and original setup. Recruits changes ordinary rates/total7, not fixed Tleilaxu group price/free classification. Cunning keeps temporary ordinary counters, separate Reinforcements reserve fees, actual Harass returns and original cancellation. No new numeric rule or played-save conversion. Native selected families, World/phase/Discovery/Semuta, reaction-preview and other effect-specific bounds remain separate; inherited provisional timings are not promoted to publisher rulings. | [Source and exact native proof](NEXUS_CARD_RULES.md#classic-independent-treachery-composition--18-october-2026) |
| Classic independent native Nexus reactions | Fresh independent36/Nexus12 optional14 initializes the existing Ixian purchase and actual Harkonnen declared-Traitor effect stamps; played games remain unchanged. Public selection binds canonical faces and the original census, with a new independent marker only where selected. Under standing choose-recommended authority, inherit the existing bounded native-counter-first/neutral acknowledgement and replacement-before-Harkonnen-bonus conventions, visibly provisional, not new publisher or universal rulings. Personal/allied source, real Mentat replacement and original paid/card/force mechanics remain. Other native/module/reaction envelopes stay separate. | [Current source and actual controls](NEXUS_CARD_RULES.md#classic-selected-card-reactions--18-october-2026) |
| Classic Nexus core / funded card shipments | Original Nexus independence admits fresh classic2–6 Basic/Advanced base33 or selected independent36, optionally all14, without a three-card dependency. Initialize actual Guild/Harkonnen/Ixian reaction states only at fresh setup. Original signed Cunning reserve/cross/return and Richese one-counter grants retain native counters, funding and physical continuation through the same Guild fee gate. One Guild singleton cannot simultaneously fund Cunning and rival Use; actual wrong-face Pass remains neutral. Actual third-party Bureaucrat sees the real recipient and printed payer exclusions. No new numerical/universal ruling, played reflag or wider module opening; inherited timing stays provisional. | [Current source and exact proof](NEXUS_CARD_RULES.md#classic-nexus-core-and-funded-card-shipments--18-october-2026) |
+| Classic Nexus core / original phase modules | Fresh classic base33 or selected36/Nexus12, optionally14, composes original Tech3–6 Basic/Advanced and Strongholds2–6 Advanced through the public core capability. Real first-Storm tokens, owner-count pending income, actual end-Mentat held cards and original matching payout remain. Fixed paid force groups do not become free under Recruits; free requests accrue once, pending income cannot fund earlier costs, and fees/Harass are not Revival/shipment. Earned local bank support does not fund reserve fees; physical effective Carthag defense, native counters/cleanup/winner token/private replacement keep their original order. No new numerical or universal policy, played reflag or wider family/World/Discovery opening. | [Source, controls and owner-frozen439/439](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026) |
| Fresh native Ecaz/Moritani defaults | Fresh actual Eca/Mori roles reuse registered selected-deck all14 capability, including mixed families and original World/phase/cards without Nexus. Printed Eca participation and Advanced Mori assassination initialize only for actual seats; Basic gains no Advanced power and unused decks create no faction seat. Bare rosters and original Nexus/Discovery/Semuta retain their envelopes; Advanced role/shared Duke guards stay. No played-save forfeiture/opportunity history is fabricated. | [Current registered source envelope](LEADER_SKILLS_RUNTIME.md#registered-native-e3-selected-decks--18-october-2026), [Mori authority](MORITANI_ASSASSINATE_LEADERS.md#connected-scope-and-private-information) |
| Independent Treachery / original phase modules | Printed independence retains original Tech3–6 Basic/Advanced and/or Strongholds2–6 Advanced with classic/E1/E2 and original standalone/paired E3 bare cards; all14 also composes registered mixed selected families and Worlds. Real first-Storm/end-Mentat owners, separate delayed income and physical funding remain. Fees/H are not revival/shipment and +2 is not supported force. Native coalition/assassination and original queue stay source-labelled. Bare mixed E3 World/phase, original Nexus/Discovery/Semuta and role/effect guards remain. | [Original phase contract](ECAZ_TREACHERY_RULES.md#original-phase-module-composition--16-october-2026), [registered scope](LEADER_SKILLS_RUNTIME.md#registered-native-e3-selected-decks--18-october-2026) |
| Classic World independent cards / phase modules | Compose original unused Tech3+ and/or Advanced Strongholds2+ with the existing classic36-card Homeworld entry. Preserve native typed custody, current separate-request rates/paid ledgers, exact source-bearing R fees, original face/support, fee/dial disjointness and fee-before-Harass. Keep every own-World Harass ban; native returns are not shipment/free Revival. Original delayed income and earned cards add no personal subsidy or supported +2. This inherits existing provisional timing, not a new publisher ruling or native expansion/public gate. | [Source-bound contract](ECAZ_TREACHERY_RULES.md#classic-world-phase-module-composition--16-october-2026) |
diff --git a/docs/STRONGHOLD_CARDS.md b/docs/STRONGHOLD_CARDS.md
index 1ec15c91..ce5306df 100644
--- a/docs/STRONGHOLD_CARDS.md
+++ b/docs/STRONGHOLD_CARDS.md
@@ -2,6 +2,54 @@
Historical development module checkpoint, 7 September 2026: the six printed effects, public card custody, mobile declaration, support accounting, AI choices and readable faces are integrated. The later user-authorized [classic Advanced preview](ADVANCED_PREVIEW.md) already permits Stronghold Cards; preserve that access. Complete Advanced/expansion release and arbitrary combinations remain gated.
+## Classic original Nexus composition — 18 October 2026
+
+The rules-first classic Advanced Nexus12 entry retains original Strongholds2–6,
+base33 or explicitly selected independent36, and optional all14 Skills. The six
+printed effects and original custody timing remain unchanged. This effect
+proof keeps Tech off; the shared core also composes original Tech3+. No
+World/Discovery/native-family envelope, numerical rule or universal timing
+ruling is added, and complete public-release acceptance remains gated.
+
+`tests/fixture-nexus-core-strongholds.ts` exports
+`createNexusCoreArrakeenFixture`, `createNexusCoreCarthagFixture`,
+`revealNexusCoreStrongholdBattle`, `callNexusCoreCarthagTraitor` and
+`finishNexusCoreStrongholdBattle`. Both scenes stop at an original pre-plan
+boundary after genuine first-end-Mentat claims. Arrakeen is earned by an actual
+turn-one five-reserve shipment; Carthag is retained from actual setup forces.
+Only unopened decks and original deal lotteries select source cards. No
+holders, hands, forces, deaths, clocks or wallets are assigned. Shared native
+controls handle legal participation; the fixture supplies its explicit plans.
+
+The worker's finite original-action programme was exercised locally; its
+source and reported branches are preserved with the parent's actual control
+proof in `/tmp/dune-classic-nexus-phase-18oct.json`. The executable throwaway
+was removed. Observed base33/independent Arrakeen results: five physical counters
+for dial6/support3, bank2/personal1; wallet7→8 includes the separate Collection2,
+with two board survivors. Independent Reinforcements additionally spends three
+real reserves (15→12), producing six total ordinary Tanks counters rather than
+three and never adding supported fighters. Without Cunning, dial5/support5
+costs personal3, consumes all five board counters plus three reserve fees, and
+keeps the now-empty city's card until actual next Mentat sets it aside.
+
+Actual Carthag runs cancel the original Harkonnen call with the genuinely drawn
+Harkonnen Nexus. The physical Shield protects its holder from poison: nine
+forces and the leader survive, and the Shield stays in the original hand.
+All14 uses the actually trained Prana Bindu disc after the original
+behind-shield choice. Omitting that defense kills the disc and all ten forces;
+Atreides controls Carthag immediately, but the old holder keeps the module card
+until Mentat transfers it. Original cancellation replaces one Harkonnen
+Traitor at Mentat; Pass instead preserves the single-traitor win, no ordinary
+winner dial casualty, unspent Nexus and no replacement.
+
+`tests/nexus-core-strongholds-runtime.test.ts` records payment, physical-counter,
+defense, custody and cleanup regressions. Parent frozen43-file439/439,
+types/lint/build and actual390px Arrakeen plus trained base33 Carthag controls
+pass. Initial fixture pricing/Collection assumptions were repaired against
+the original Guild-ally3 tariff and separate native city2 income. No all-six
+combined, full-mode, strategic or deployed acceptance claim.
+[Shared current proof and cleanup limitations](NEXUS_CARD_RULES.md#classic-nexus-original-phase-modules--18-october-2026).
+
## All14 Skills with independent cards — 17 October 2026
Supported original classic, selected E1/E2 and standalone/paired E3 all14
diff --git a/game/engine.ts b/game/engine.ts
index ca97c15e..f9ea1465 100644
--- a/game/engine.ts
+++ b/game/engine.ts
@@ -10233,6 +10233,8 @@ function guildBetrayalParentSignature(g: Game, continuation: GuildBetrayalContin
pendingGuildTransport: g.pendingGuildTransport,
...(cunning ? { nexusGuildCunning: cunning } : {}),
...(richese ? { nexusRichese: richese } : {}),
+ ...(g.techTokens ? { techTokens: g.techTokens } : {}),
+ ...(g.strongholdCards ? { strongholdCards: g.strongholdCards } : {}),
});
}
function guildBetrayalCleanParent(g: Game): void {
@@ -10738,7 +10740,7 @@ export function initializeLeaderSkillsGameForAudit(state: Game): Game {
const homeworlds = homeworldLeaderSkillsProfile(state);
const independent = independentTreacheryLeaderSkillsProfile(state);
if (state.ecazTreachery) {
- requireRule(independent, 'Independent Skills cards require supported selected decks with original World/phase modules, or classic Nexus without other overlays; Discovery and unrelated preview contexts remain gated.');
+ requireRule(independent, 'Independent Skills cards require supported selected decks with original World/phase modules, or classic Nexus with original phase modules; Discovery and unrelated preview contexts remain gated.');
requireFreshBaseRuntime(state);
requireFreshFactionInventory(state);
}
@@ -10800,6 +10802,7 @@ function ecazHomeworldProfile(g: Game): boolean {
}
/** Independent cards compose original phase modules, not unrelated previews. */
function independentCardModulesProfile(g: Game): boolean {
+ if ((g.techTokens || g.strongholdCards) && classicIndependentNexusProfile(g)) return true;
if ((g.techTokens || g.strongholdCards) && g.leaderSkills && !g.sandtrout &&
independentTreacheryLeaderSkillsProfile(g)) return true;
if (!g.ecazTreachery || (!g.techTokens && !g.strongholdCards) || !independentCardModuleRoster(g) ||
diff --git a/game/faction-module-profile.ts b/game/faction-module-profile.ts
index d89fa2f2..84f45f74 100644
--- a/game/faction-module-profile.ts
+++ b/game/faction-module-profile.ts
@@ -30,13 +30,13 @@ type ClassicNexusProfile = Partial & Pick 6 ||
- game.homeworlds || game.techTokens || game.strongholdCards || game.sandtrout ||
+ game.expansions.length !== 0 || !players || players.length < (game.techTokens ? 3 : 2) || players.length > 6 ||
+ game.homeworlds || (game.strongholdCards && !game.advanced) || game.sandtrout ||
game.discoveryEnabled || game.discoveries || game.discoveryStash || game.greatMaker ||
game.semutaPreview || game.moritaniAssassinatePreview || game.advancedPreview || game.kullPreview || game.nexusKullPreview ||
game.richeseBetrayalPreview ||
diff --git a/game/reference.ts b/game/reference.ts
index a5c7e531..7a44079c 100644
--- a/game/reference.ts
+++ b/game/reference.ts
@@ -3419,6 +3419,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'The mobile card copies a currently controlled stronghold, even if another faction still holds that location’s card. Holding a card after leaving its location does not itself make that location an eligible copy. The choice is public and fixed before battle plans; a sole eligible advantage is announced automatically.',
'Arrakeen pays only for actual support and creates no personal cash. CHOAM force-payment income includes the bank contribution, subject to its normal traitor and donor exclusions. Carthag does not protect an empty or Worthless defense, stop Poison Tooth, or remove a Shield’s Lasgun explosion risk.',
'Sietch Tabr uses the opposing dial, excluding leaders and other bonuses. A single traitor victory qualifies; mutual destruction does not. Tuek pays after an ordinary loss or a single traitor outcome as well as a win; a double traitor gives neither faction spice. Habbanya also decides a tied Stone Burner comparison.',
+ 'Classic Advanced Nexus core also retains original Strongholds2–6 and optional Tech3–6 with base33 or selected independent36, optionally all14. Earn cards only at actual end-Mentat. Earned Arrakeen composes temporary-Sardaukar support and real Reinforcements reserve fees without making bank money personal; earned Carthag still requires a physical effective defense after an original Harkonnen call is canceled. Original cleanup, token reward, held custody and private Mentat replacement remain.',
'Fresh Advanced all14 Skills uses registered actual seats and selected ordinary decks with original Strongholds2–6 and optional Tech3–6, including its independent-card/World envelope. Native E3 needs no Nexus or exact single family merely to use Skills. Original Nexus/Discovery/Semuta card contexts and Advanced role guards remain separate. Actual end-Mentat claims stay. Arrakeen funds only local holder-plan support, never native World support, personal cash or reserve fees; normal Banker uses the final payer debit. Rescue, cleanup, Tech and Face Dance retain their order.',
'Ecaz/Moritani Skills may include other registered selected-family seats and unused selected ordinary decks, with original Worlds/phase modules and optional independent cards. Native five-disc training and original role exclusions remain. Normal Warmaster one on a temporary Duke never grants trained three. Winner Suk precedes unused-disc assassination, bounty, winner Tech and one Mentat replacement. Ecaz-held support belongs only to the selected Ecaz plan, not an allied plan; fixed and variable armies remain separate. Advanced E3/Harkonnen, shared Duke and effect-specific guards stay.',
'Fresh independent Treachery previews retain classic/E1/E2 and original standalone/paired E3 bare rosters; all14 additionally admits registered mixed selected families. Original Worlds, Advanced Strongholds2–6 and optional Tech3–6 retain actual end-Mentat custody. Reinforcements adds no supported counters; Harass vacates control without transferring a held card early. Exact native fees/returns, card effects and winner Tech remain; bare mixed-E3 World/phase, original Nexus/Discovery card and role/effect guards stay separate.',
@@ -3470,20 +3471,12 @@ export const RULE_TOPICS: RuleTopic[] = [
'All six full effect guides, custody timing, examples and interaction explanations are available here.',
evidence: ['game/reference.ts', 'docs/STRONGHOLD_CARDS.md'],
},
- {
- area: 'Verification',
- status: 'Partial',
- detail:
- 'Dated standalone and E1/E2 Skills/Stronghold cases remain. Standalone E3 adds15 meaningful cases,152 affected, types/lint/build, nine actual programs and eight E3 games (1531 actions/no rejection,38 JSON continuations); the expanded47-game batch completes13936 actions/358 continuations. Human390px normal Duke/Carthag poison survival and Guild Suk before unused-disc assassination preserve original cards/Tech/income/replacement on refresh. Full/deployed acceptance remains unfinished.',
- evidence: [
- 'tests/stronghold-cards.test.ts',
- 'tests/stronghold-engine.test.ts',
- 'tests/classic-skills-stronghold-runtime.test.ts',
- 'tests/native-skills-stronghold-runtime.test.ts',
- 'tests/ecaz-skills-modules-runtime.test.ts',
- 'tests/moritani-skills-modules-runtime.test.ts',
- ],
- },
+ {area:'Verification',status:'Partial',detail:"Original-action classic Nexus phase sources now earn Arrakeen support beside temporary Sardaukar and real reserve fees, and Carthag Shield poison survival after native call cancellation. Actual normal five-module setup plus earned combat and Axlotl controls complete; four legal policies finish native income and battles. Prior E3 and broader evidence remains dated. Full combinations, deployed acceptance and comprehensive assurance remain open.",evidence:['tests/nexus-core-tech-runtime.test.ts','tests/fixture-nexus-core-tech.ts','tests/nexus-core-strongholds-runtime.test.ts','tests/fixture-nexus-core-strongholds.ts','tests/stronghold-cards.test.ts',
+ 'tests/stronghold-engine.test.ts',
+ 'tests/classic-skills-stronghold-runtime.test.ts',
+ 'tests/native-skills-stronghold-runtime.test.ts',
+ 'tests/ecaz-skills-modules-runtime.test.ts',
+ 'tests/moritani-skills-modules-runtime.test.ts',]},
],
},
{
@@ -3530,6 +3523,7 @@ export const RULE_TOPICS: RuleTopic[] = [
'Token ownership and pending income are public and persist. Fresh registered selected-family all14 keeps original Tech3–6 Basic/Advanced and optional Advanced Strongholds2–6, including supported Worlds and independent cards, without an incidental Nexus or single-family E3 requirement. Original Nexus/Discovery/Semuta and role/effect guards remain. Rescue/board-spice precedes winner Tech and Face Dance. Mandatory normal Banker waits for Mentat, separate from phase-end Tech; fees/H returns are not revival/shipment. Native CHOAM opening income is not Charity. Two-player Tech and full combined acceptance remain unfinished.',
'Without Skills, fresh native E1/E2 families or standalone Ecaz OR Moritani with classic opponents may preserve unused original Tech Tokens through the local factions entry, Basic/Advanced three through six players; Advanced stronghold-factions adds the original six Stronghold Cards. Native payments, typed casualties, selected Ecaz lead rewards and Moritani original assassination remain separate. Split allied holdings never qualify as a complete set. Basic odd Occupy, Richese cache/No-Field rulings, unrelated overlays, public starts and saved-game conversion remain guarded.',
'Classic/E1/E2 and original standalone/paired E3 bare independent-card previews retain Worlds, Tech3–6 and optional Advanced Strongholds2–6. All14 extends this to registered mixed selected families. Recruits changes current native rates without repeating Axlotl or advancing payout. Harass deposits and reserve fees are not free Revival or off-planet shipment. Original phase income/native custody/winner rewards remain; bare mixed-E3 World/phase and unrelated module/role guards stay separate.',
+ 'Classic Nexus core composes original Tech3–6 Basic/Advanced and optional Advanced Strongholds2–6 with base33 or selected independent36, optionally all14. Real first-Storm owners and actual token counts set income. A paid fixed Tleilaxu force group does not become free because Recruits is active; subsequent genuine free returns accrue one Axlotl pile, not one pile per request. Pending spice cannot fund Revival before phase end. Harass returns and reserve fees are not token-triggering Revival or shipment; actual battle winners take one original opposing token.',
],
example:
'You own Heighliners and Axlotl Tanks. When Emperor ships from reserves, place two spice on Heighliners. A later shipment adds nothing. Collect the two spice when Shipping and Movement ends.',
@@ -3541,6 +3535,13 @@ export const RULE_TOPICS: RuleTopic[] = [
'victory',
'ix-modules',
],
+ checklist: [
+ { area: 'Implementation', status: 'Partial', detail: 'Original first-Storm ownership, owner-count pending income, matching phase-end payout and native winner transfer remain. Classic Nexus core now admits original Tech with selected33/36 and optional14, preserving funded card shipment and fixed-versus-free Revival sources. Other module/native envelopes keep their bounds.', evidence: ['game/tech-tokens.ts', 'game/engine.ts', 'game/phase-resource-quote.ts', 'game/revival-resume.ts', 'game/faction-module-profile.ts'] },
+ { area: 'Player controls', status: 'Partial', detail: 'Actual normal five-module lobby reaches original training/Storm. Native fixed-five and ordinary-free controls show original prices, physical Tanks and pending pile separately; actual phase close pays it. Stronghold and card inspectors retain their own entitlements.', evidence: ['components/game-table.tsx', 'tests/fixture-nexus-core-tech.ts'] },
+ { area: 'AI', status: 'Partial', detail: 'Four existing own-view policies legally claim funded card shipment, complete real Revival into phase-end payout and finish earned Stronghold/Nexus battles. No strategic calibration or fixed-choice pin.', evidence: ['game/bots.ts', 'tests/fixture-nexus-core-tech.ts', 'tests/fixture-nexus-core-strongholds.ts'] },
+ { area: 'Documentation', status: 'Partial', detail: 'Printed trigger/exception, owner count, pending-versus-spendable spice, original payout, winner reward and complete-set victory remain distinct. Current classic core phase scope and inherited timing are explicit; full module/faction combinations and assurance remain open.', evidence: ['game/reference.ts', 'docs/NEXUS_CARD_RULES.md', 'docs/STRONGHOLD_CARDS.md', 'docs/RULE_DECISIONS.md'] },
+ { area: 'Verification', status: 'Partial', detail: 'Original-action consumers cover funded five-for-one delivery, paid fixed5 versus free2, a second genuine free request, pending-income affordability and Harass/winner transfer. Actual all14 Revival UI and four legal policies reach payout; older broader and saved evidence remains dated.', evidence: ['tests/nexus-core-tech-runtime.test.ts', 'tests/fixture-nexus-core-tech.ts', 'tests/tech-tokens.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts'] },
+ ],
},
{
id: 'captured-leaders',
@@ -4540,7 +4541,7 @@ export const RULE_TOPICS: RuleTopic[] = [
developmentStage: 'Verified',
summary: 'An unallied rival holding Guild Nexus may take one full original positive shipment payment, including their own pre-funded payment, instead of Guild, bank or occupied Junction income in fresh classic core or supported original standalone entries.',
steps: [
- 'Fresh classic2–6 Basic/Advanced Nexus core uses original base33 or selected independent36 Treachery, optionally all14 Skills. Actual native Guild receives its original payment-replacement state at fresh setup alongside supported Harkonnen/Ixian reactions; played games are not reflagged. The original explicit native Guild/Homeworld entry retains its own seeded boundary. Other selected families, World/phase/Discovery/Semuta and reaction envelopes are not expanded by this core entry.',
+ 'Fresh classic2–6 Basic/Advanced Nexus core uses base33 or selected independent36, optionally all14 Skills, original Tech3+ and Advanced Strongholds2+. Actual Guild receives original fee state at fresh setup alongside supported Harkonnen/Ixian reactions; played games are not reflagged. Original explicit native Guild/Homeworld entry retains its seeded boundary. Other selected families, Worlds, Discovery/Semuta and reaction envelopes stay separate.',
'Remain unallied and hold the actual singleton Guild Nexus while another seated player is native Guild. Use spends that physical Nexus once, with no Treachery Card, hand slot or extra activation spice. Cunning and absent-Guild Secret Ally are separate modes. An empty Guild board or temporary leader control does not change the mode.',
'Within classic core, original Guild Cunning second shipments and Richese Secret Ally discounted shipments retain their actual signed grant, physical group, paid tariff and native continuation through the same funded fee gate. One Guild singleton cannot simultaneously fund native Cunning and rival Betrayal Use; other held faces retain neutral Pass. Outside core, original producer envelopes remain separate. Guild Secret Ally still requires Guild to be absent and is not another native-Guild producer.',
'Wait for a legal positive shipment fee after any applicable native stop and rate responses. The original source, tariff and physical forces must already be legal and fully funded. Stopped, declined, invalid or zero-cost shipments cannot award spice or spend this Nexus; ordinary free Fremen reinforcement and free Guild Ambassador transport stay distinct.',
@@ -4574,7 +4575,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'An unallied holder with Ixians absent may spend Ixian Nexus to replace the exact card just bought in a normal auction or supported native Richese sale, preserving original settlement and drawing the actual deck top privately.',
searchText: 'Ixian Nexus Secret Ally purchased card replacement normal auction cache Black Market Karama full hand empty deck same card',
steps: [
- 'Original standalone entries admit classic/base33, Tleilaxu plus classics/Ix47, or Richese plus classics/CHOAM35 and cache, with Ixians absent. Fresh classic2–6 Basic/Advanced Nexus core initializes this same effect with base33 or selected independent36, optionally all14 Skills; independent cards are not a prerequisite. Other native/module envelopes retain their own bounds, and no played game gains a new stamp.',
+ 'Original standalone entries admit classic/base33, Tleilaxu plus classics/Ix47, or Richese plus classics/CHOAM35 and cache, with Ixians absent. Fresh classic2–6 Basic/Advanced Nexus core initializes the same effect with base33 or selected36, optional14, original Tech3+ and Advanced Strongholds2+; independent cards are not required. Other native/module envelopes keep their bounds; no played game gains a new stamp.',
'Nexus has no starting deal. A draw requires a qualifying closing Nexus with an alliance. A two-seat profile can continue its base game but cannot naturally create an unallied Secret Ally holder; actual replacement needs at least three seats, or four for two simultaneous unallied recipients.',
'The photographed publisher face grants an independent Secret Ally operation when Ixians are absent. Remain unallied and hold the actual singleton Ixian Nexus. Native Ixian allied replacement is a different cancelable advantage and is not removed or used as a fallback. The adopted unofficial Advanced rulebook supplies relevant core rules, not this absent Nexus face or a new Nexus ruling.',
'Normal paid/Karama purchases, genuine native Richese sold lots and actual Advanced special-Karama acquisition during Bidding supply their own purchase authority. A direct purchase has already spent its Karama, three spice and once-use; its target/price3 are not an unrelated row or sale. Use/Keep resumes original income, active player and suspended controls. Only unpaid bids remain reserved; already-delivered sale commitments are not held twice. Other phases keep native acquisition without a Bidding-only Nexus choice. Unbid keeps, gifts, bonus draws, Technology swaps and old hand cards supply no purchase.',
@@ -4643,7 +4644,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'Fresh supported classic starts cancel one original native Harkonnen traitor call, return that physical card immediately and give Harkonnen a private replacement during Mentat Pause.',
searchText: 'Harkonnen Nexus Betrayal revealed declared traitor ally caller beneficiary shuffle replacement Mentat neutral acknowledgement',
steps: [
- 'The original standalone preview requires native Harkonnen, classic2–6 Basic/Advanced, base33 and Nexus alone. Fresh classic Nexus core initializes the same effect with base33 or selected independent36, optionally all14 Skills, alongside actual native Guild and Ixian reactions. Original setup and public selection bind the census. No played game is reflagged; other native/module/reaction envelopes retain their bounds.',
+ 'Original standalone preview requires native Harkonnen, classic2–6 Basic/Advanced, base33 and Nexus alone. Fresh classic Nexus core initializes the same effect with base33 or selected36, optional14, original Tech3+ and Advanced Strongholds2+, alongside actual Guild/Ixian reactions. Public selection binds the census; real held-card effects and cleanup stay native. No played game is reflagged; other native/module envelopes remain separate.',
'Nexus has no initial deal. An actual alliance-qualified closing Nexus must supply the unallied holder. An effect scenario needs at least three seats; two seats can continue ordinary play without inventing a card. Two simultaneous receivers need four actual seats.',
'The photographed publisher face cancels a revealed Harkonnen traitor, shuffles that exact card back immediately, and gives Harkonnen one replacement during Mentat Pause. It is not a draw-now exchange, whole-battle ban or refund of an already resolved outcome. Native Cunning and absent-Harkonnen Secret Ally remain separate effects.',
'Only a genuine legally matching native personal or allied Harkonnen call is a source. Preserve the actual caller, combatant beneficiary, target, original revealed plans and physical identity. A false or invalid call, protected leader, another faction or finished battle cannot offer this effect.',
@@ -4675,7 +4676,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'A separate twelve-card deck rewards remaining unallied. Private draws and selected Atreides, Harkonnen, Tleilaxu, Ixian, Bene Gesserit, Emperor, Fremen, CHOAM, Moritani, Richese and Guild effects are connected. Other effects and complete module play remain in development.',
steps: [
'This optional module can be combined with Basic or Advanced rules independently of the factions and other expansion modules selected. Include one Nexus Card for each of the twelve factions.',
- 'Fresh classic two-through-six-seat Basic/Advanced Nexus core admits base33 or selected independent36 Treachery, optionally all14 Skills. Keep the actual Treachery, twelve Nexus and fourteen Skill stocks distinct; no independent-card dependency or silent addition is invented. Original fresh Guild/Harkonnen/Ixian reaction states use their actual role and card sources. Starting hands/training and real alliance-qualified closing draws remain. Other native families, World/phase/Discovery/Semuta and reaction envelopes stay separate.',
+ 'Fresh classic2–6 Basic/Advanced Nexus core admits base33 or selected independent36, optionally all14, original Tech3+ and Advanced Strongholds2+. Actual Treachery, twelve Nexus, fourteen Skills, three Tech and six held-card stocks remain distinct; no selection is silently added. Original fresh Guild/Harkonnen/Ixian states use actual role/card authority. Starting hands/training, real closing draws, first-Storm tokens and end-Mentat claims stay native. Other families, Worlds, Discovery/Semuta and reaction envelopes remain separate.',
'Recruits changes native Revival rates and the ordinary seven ceiling, not the fixed Tleilaxu Secret Ally group or its one-spice price. Five paid card counters may leave two ordinary free returns, using the one leader slot once. Emperor Cunning still makes five ordinary board counters temporarily Sardaukar: independent reserve fees use separate physical reserves, Harass returns actual undialed counters, and native Karama changes only the original Nexus effect. Native winner/inspection and Collection continue without extra cleanup prompts.',
'At the end of the entire Spice Blow and Nexus phase, if a Nexus occurred and at least one alliance exists, each unallied player may draw one card or discard their held card and draw a replacement. You may decline. The two Advanced spice piles do not grant two closing draws.',
'Keep your card secret until it is used. You may hold at most one Nexus Card. It is separate from your Treachery hand and does not occupy a Treachery hand slot.',
@@ -4749,7 +4750,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{area:"Player controls",status:'Partial',detail:"Four current original-action390px journeys prove native Junction5/full2, poor1/full2 release plus ordinary one-force replacement, still-held source sand2 disabled then five-force stronghold1, and independent purchased1. The response explains the spent-card consequence. No overflow, browser error or application exception; two tool-only stale-node errors were resolved by fresh evidence without replay. Earlier Junction/loan/refund surfaces remain scoped evidence.",evidence:['components/nexus-cards.tsx', 'components/nexus-traitors.tsx', 'components/nexus-tleilaxu.tsx', 'components/nexus-suboids.tsx', 'components/nexus-advisors.tsx', 'components/nexus-sardaukar.tsx', 'components/choam-power-cost.tsx', 'components/moritani-terror.tsx', 'components/nexus-moritani-betrayal.tsx', 'components/nexus-ecaz-betrayal.tsx', 'components/nexus-ecaz-inquiry.tsx', 'components/nexus-ecaz-duke.tsx', 'components/nexus-richese-betrayal.tsx', 'components/game-table.tsx', 'components/nexus-tleilaxu-revival.tsx', 'components/battle-preparation.tsx', 'components/nexus-fremen-revival.tsx','components/nexus-emperor-payment.tsx', 'components/nexus-emperor-secret-ally.tsx', "components/nexus-emperor-seller-purchase.tsx", "components/nexus-emperor-ambassador-purchase.tsx", "components/richese-special-karama.tsx", "components/junction-transport.tsx"]},
{area:"AI",status:'Partial',detail:"All four original policies execute a legal still-held Richese full-rate one-counter alternative after native cancellation, with actual physical delivery and card cost. Shared human/AI quotes no longer promise a canceled discount. Native permission alternatives, recipient-specific lender prices and immune Junction source choice remain. Optional non-reoffer is not forced. No strategic first-candidate pin, tuning or calibration.",evidence:['game/nexus-card-options.ts', 'game/nexus-traitor-options.ts', 'game/nexus-tleilaxu-options.ts', 'game/nexus-suboid-options.ts', 'game/nexus-advisor-options.ts', 'game/nexus-sardaukar-options.ts', 'game/choam-power-options.ts', 'game/nexus-moritani-options.ts', 'game/nexus-moritani-betrayal-options.ts', 'game/nexus-ecaz-betrayal-options.ts', 'game/nexus-ecaz-inquiry-options.ts', 'game/nexus-ecaz-duke-options.ts', 'game/nexus-richese-options.ts', 'game/bot-nexus-richese-betrayal.ts', 'game/nexus-guild-cunning-options.ts', 'game/bots.ts', 'game/nexus-fremen-revival-options.ts','game/nexus-emperor-payment.ts', 'game/nexus-emperor-secret-ally-options.ts']},
{area:"Documentation",status:'Partial',detail:"Richese printed one-counter pricing and native Guild eligibility stay distinct from absent-faction cancellation, physical force count and independent purchased price. Unfunded spent-card/no-refund release is selected existing no-debt composition, not a new publisher worked example. Immune Junction and earlier permission/refund/loan interpretations remain explicit. Full rules and public gates stay open.",evidence:['docs/NEXUS_CARD_RULES.md', 'docs/NEXUS_ATREIDES_RULES.md', 'docs/NEXUS_HARKONNEN_RULES.md', 'docs/NEXUS_TLEILAXU_RULES.md', 'docs/NEXUS_IXIAN_CUNNING_RULES.md', 'docs/NEXUS_CUNNING_RUNTIME.md', 'docs/NEXUS_BENE_GESSERIT_RULES.md', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'docs/NEXUS_EMPEROR_RULES.md', 'docs/NEXUS_SARDAUKAR_RUNTIME.md', 'docs/NEXUS_FREMEN_RULES.md', 'docs/NEXUS_ECAZ_RULES.md', 'docs/NEXUS_CHOAM_RULES.md', 'docs/NEXUS_CHOAM_RUNTIME.md', 'docs/NEXUS_MORITANI_RULES.md', 'docs/NEXUS_MORITANI_RUNTIME.md', 'docs/NEXUS_RICHESE_RUNTIME.md', 'docs/NEXUS_RICHESE_RULES.md', 'docs/NEXUS_GUILD_CUNNING_RUNTIME.md', 'docs/GUILD_RATE_KARAMA_RULES.md', 'docs/RICHESE_ALLIED_SHIPMENT_AUDIT.md', 'docs/GUILD_HOMEWORLD_TRANSPORT_RULES.md', 'docs/NEXUS_GUILD_RULES.md', "docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md", "docs/RULE_DECISIONS.md", "docs/RICHESE_AMBASSADOR_RULES.md", "docs/RICHESE_AMBASSADOR_IMPLEMENTATION.md", "docs/RICHESE_ACQUISITION_RULES.md", "docs/DUKE_VIDAL_RULES.md", "docs/ECAZ_DUKE_ACQUISITION.md", "docs/ECAZ_REVIVAL_RULES.md", "docs/JUNCTION_TRANSPORT_RULES.md", "docs/JUNCTION_TRANSPORT_RUNTIME.md"]},
- {area:"Verification",status:'Partial',detail:"One public classic-core capability composes base33 or selected independent36 with Nexus12, optionally all14, and initializes native reaction states only at fresh setup. Genuine card draws, original shipment grants, native counters, exact funded fee and actual delayed replacement remain. Ten combined consumer regressions, three390px journeys and twenty-eight legal policy choices pass. Other native/module envelopes, universal timing, comprehensive assurance and deployed acceptance remain open.",evidence:['tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts', 'tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/native-guild-rate-karama.test.ts', 'tests/native-guild-rate-modules.test.ts', 'tests/allied-marker-guild-rate.test.ts', 'tests/purchased-homeworld-rate.test.ts', 'tests/native-guild-transport-permission.test.ts', 'tests/native-guild-homeworld-rate.test.ts', 'tests/native-guild-rate-betrayal.test.ts', 'tests/nexus-guild-cunning-engine.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts', 'tests/ecaz-nexus-skills-modules-runtime.test.ts', 'tests/moritani-nexus-skills-modules-runtime.test.ts', 'docs/NEXUS_CARD_RULES.md', 'tests/nexus-tleilaxu-revival.test.ts', 'tests/fixture-nexus-tleilaxu-secret-ally.ts', 'tests/nexus-tleilaxu-secret-ally-runtime.test.ts', 'tests/nexus-borrowed-voice.test.ts', 'tests/fixture-nexus-borrowed-voice.ts', 'tests/nexus-borrowed-voice-runtime.test.ts', 'tests/nexus-fremen-protection.test.ts', 'tests/fixture-nexus-fremen-protection.ts', 'tests/nexus-fremen-protection-runtime.test.ts', 'tests/fixture-stronghold-factions.ts', 'tests/fixture-nexus-advisor-fresh.ts', 'tests/nexus-advisor-fresh-runtime.test.ts', 'tests/fixture-nexus-advisor-storm.ts', 'tests/nexus-advisor-storm-runtime.test.ts', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'tests/nexus-fremen-free-quota.test.ts', 'tests/fixture-nexus-fremen-free-quota.ts', 'tests/nexus-fremen-free-quota-runtime.test.ts', 'tests/nexus-fremen-revival.test.ts','tests/nexus-emperor-payment.test.ts','tests/fixture-nexus-emperor-payment.ts','tests/nexus-emperor-payment-runtime.test.ts','docs/NEXUS_EMPEROR_RULES.md', 'tests/nexus-emperor-extra-source.test.ts', 'tests/nexus-emperor-extra-continuation.test.ts', 'tests/fixture-nexus-emperor-extra.ts', 'tests/nexus-emperor-extra-runtime.test.ts', 'tests/fixture-single-nexus-e1.ts', 'docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md', "tests/fixture-nexus-emperor-seller.ts", "tests/nexus-emperor-seller-purchase.test.ts", "tests/nexus-emperor-seller-runtime.test.ts", "tests/fixture-nexus-emperor-ambassador.ts", "tests/nexus-emperor-ambassador-purchase.test.ts", "tests/nexus-emperor-ambassador-runtime.test.ts", "tests/fixture-nexus-emperor-special.ts", "tests/nexus-emperor-special-purchase.test.ts", "tests/nexus-emperor-special-runtime.test.ts", "tests/karama-intents.test.ts", "tests/fixture-nexus-emperor-bank.ts", "tests/nexus-emperor-bank-profile.test.ts", "tests/nexus-emperor-bank-runtime.test.ts", "tests/duke-captured-custody.test.ts", "tests/nexus-ecaz-duke-captured.test.ts", "tests/fixture-nexus-duke-capture.ts", "tests/nexus-duke-capture-runtime.test.ts", "tests/nexus-duke-timing-runtime.test.ts", "tests/junction-purchased-price.test.ts", "tests/richese-native-guild-rate.test.ts"]},
+ {area:"Verification",status:'Partial',detail:"Classic core now composes original Tech3+ and Advanced Strongholds2+ with base33/selected36 and optional14. Real first-Storm owners, phase-end income and first-Mentat held cards precede actual card grants, funded receipts, support and casualties. Four390px paths and four legal policies complete earned income/combat; new regressions cover fixed/free returns, spendability, one-pile accrual, native winner transfer and held custody. Other native/World/Discovery/Semuta envelopes and full assurance remain open.",evidence:['tests/nexus-core-tech-runtime.test.ts','tests/fixture-nexus-core-tech.ts','tests/nexus-core-strongholds-runtime.test.ts','tests/fixture-nexus-core-strongholds.ts','tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/mixed-nexus-modules-runtime.test.ts', 'tests/paired-e3-nexus-modules-runtime.test.ts', 'tests/sample-custody.test.ts', 'tests/ambassador-terror-overlap-engine.test.ts', 'tests/single-nexus-e1-runtime.test.ts', 'tests/single-nexus-e2-runtime.test.ts', 'tests/e3-nexus-ecaz-runtime.test.ts', 'tests/e3-nexus-moritani-runtime.test.ts', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts', 'tests/nexus-cards.test.ts', 'tests/nexus-card-engine.test.ts', 'tests/nexus-fremen-cunning.test.ts', 'tests/nexus-fremen-cunning-recovery.test.ts', 'tests/nexus-fremen-cunning-summon.test.ts', 'tests/nexus-fremen-cunning-summon-recovery.test.ts', 'tests/nexus-ecaz-betrayal-quotes.test.ts', 'tests/nexus-ecaz-betrayal.test.ts', 'tests/nexus-ecaz-betrayal-recovery.test.ts', 'tests/nexus-ecaz-inquiry.test.ts', 'tests/nexus-ecaz-duke-quote.test.ts', 'tests/nexus-ecaz-duke-engine.test.ts', 'tests/nexus-ecaz-duke-recovery.test.ts', 'tests/nexus-traitor-recovery.test.ts', 'tests/nexus-face-dancer-engine.test.ts', 'tests/nexus-suboids-engine.test.ts', 'tests/normal-free-support.test.ts', 'tests/nexus-suboid-controls.test.ts', 'tests/nexus-advisor-controls.test.ts', 'tests/nexus-advisor-bots.test.ts', 'tests/nexus-sardaukar-controls.test.ts', 'tests/nexus-sardaukar-bots.test.ts', 'tests/nexus-choam-controls.test.ts', 'tests/nexus-choam-bots.test.ts', 'tests/nexus-moritani-controls.test.ts', 'tests/nexus-moritani-bots.test.ts', 'tests/nexus-moritani-betrayal.test.ts', 'tests/nexus-richese-controls.test.ts', 'tests/nexus-richese-bots.test.ts', 'tests/nexus-richese-betrayal-engine.test.ts', 'tests/nexus-richese-betrayal-recovery.test.ts', 'tests/nexus-guild-cunning-controls.test.ts', 'tests/native-guild-rate-karama.test.ts', 'tests/native-guild-rate-modules.test.ts', 'tests/allied-marker-guild-rate.test.ts', 'tests/purchased-homeworld-rate.test.ts', 'tests/native-guild-transport-permission.test.ts', 'tests/native-guild-homeworld-rate.test.ts', 'tests/native-guild-rate-betrayal.test.ts', 'tests/nexus-guild-cunning-engine.test.ts', 'tests/nexus-guild-cunning-bots.test.ts', 'tests/nexus-module-payments-runtime.test.ts', 'tests/nexus-module-battles-runtime.test.ts', 'tests/paired-ix-nexus-modules-runtime.test.ts', 'tests/paired-choam-nexus-modules-runtime.test.ts', 'tests/classic-nexus-skills-payments-runtime.test.ts', 'tests/classic-nexus-skills-battles-runtime.test.ts', 'tests/nexus-skills-modules-payments-runtime.test.ts', 'tests/nexus-skills-modules-battles-runtime.test.ts', 'tests/paired-ix-nexus-skills-modules-runtime.test.ts', 'tests/paired-choam-nexus-skills-modules-runtime.test.ts', 'tests/ecaz-nexus-skills-modules-runtime.test.ts', 'tests/moritani-nexus-skills-modules-runtime.test.ts', 'docs/NEXUS_CARD_RULES.md', 'tests/nexus-tleilaxu-revival.test.ts', 'tests/fixture-nexus-tleilaxu-secret-ally.ts', 'tests/nexus-tleilaxu-secret-ally-runtime.test.ts', 'tests/nexus-borrowed-voice.test.ts', 'tests/fixture-nexus-borrowed-voice.ts', 'tests/nexus-borrowed-voice-runtime.test.ts', 'tests/nexus-fremen-protection.test.ts', 'tests/fixture-nexus-fremen-protection.ts', 'tests/nexus-fremen-protection-runtime.test.ts', 'tests/fixture-stronghold-factions.ts', 'tests/fixture-nexus-advisor-fresh.ts', 'tests/nexus-advisor-fresh-runtime.test.ts', 'tests/fixture-nexus-advisor-storm.ts', 'tests/nexus-advisor-storm-runtime.test.ts', 'docs/NEXUS_ADVISOR_RUNTIME.md', 'tests/nexus-fremen-free-quota.test.ts', 'tests/fixture-nexus-fremen-free-quota.ts', 'tests/nexus-fremen-free-quota-runtime.test.ts', 'tests/nexus-fremen-revival.test.ts','tests/nexus-emperor-payment.test.ts','tests/fixture-nexus-emperor-payment.ts','tests/nexus-emperor-payment-runtime.test.ts','docs/NEXUS_EMPEROR_RULES.md', 'tests/nexus-emperor-extra-source.test.ts', 'tests/nexus-emperor-extra-continuation.test.ts', 'tests/fixture-nexus-emperor-extra.ts', 'tests/nexus-emperor-extra-runtime.test.ts', 'tests/fixture-single-nexus-e1.ts', 'docs/NEXUS_EMPEROR_SECRET_ALLY_RUNTIME.md', "tests/fixture-nexus-emperor-seller.ts", "tests/nexus-emperor-seller-purchase.test.ts", "tests/nexus-emperor-seller-runtime.test.ts", "tests/fixture-nexus-emperor-ambassador.ts", "tests/nexus-emperor-ambassador-purchase.test.ts", "tests/nexus-emperor-ambassador-runtime.test.ts", "tests/fixture-nexus-emperor-special.ts", "tests/nexus-emperor-special-purchase.test.ts", "tests/nexus-emperor-special-runtime.test.ts", "tests/karama-intents.test.ts", "tests/fixture-nexus-emperor-bank.ts", "tests/nexus-emperor-bank-profile.test.ts", "tests/nexus-emperor-bank-runtime.test.ts", "tests/duke-captured-custody.test.ts", "tests/nexus-ecaz-duke-captured.test.ts", "tests/fixture-nexus-duke-capture.ts", "tests/nexus-duke-capture-runtime.test.ts", "tests/nexus-duke-timing-runtime.test.ts", "tests/junction-purchased-price.test.ts", "tests/richese-native-guild-rate.test.ts"]},
],
},
{
@@ -4935,7 +4936,7 @@ export const RULE_TOPICS: RuleTopic[] = [
summary: 'Fourteen physical skills, private assignment choices, five battle disciplines, Planetologist movement/battle effects and Suk Graduate rescues, Mentat/Bureaucrat scoring and Bureaucrat payment redirection, Sandmaster victory spice, Rihani exchanges, optional Smuggler shipping and provisional modifier-inclusive battle collection, Sandmaster route and worm-ride collection, sealed Spice Banker battle spending and optional Diplomat base-defense copying in development tables. Other effects and combined-module acceptance remain unfinished.',
steps: [
'Fresh all14 uses one selected-deck capability for every registered actual seat, Basic/Advanced, including mixed native Ecaz/Moritani with selected E1/E2 families. Neither an expansion faction, independent cards, Nexus nor one exact E3 family is required just to use selected ordinary decks. An unused deck creates no seated faction. Original World/phase envelopes retain components and native power bindings; original Nexus/Discovery/Semuta and Advanced role limits stay separate. Ix drafts before training; Auditor/shared Duke never train. No Advanced power, income, owner or played-save history is invented.',
- 'Classic Nexus core with original base33 or selected independent36 permits optional all14 in Basic/Advanced, two through six base seats, without other overlays. Actual offers, starting-card order, mandatory normal Banker and native reaction sources remain. Original Guild fee replacement also retains actual third-party Bureaucrat receipts, not a fictitious Guild recipient. Selection and printed roles are public capabilities, never inferred from hidden hands. Other native and module envelopes stay separate.',
+ 'Classic Nexus core with base33 or selected36 permits optional all14, original Tech3+ and Advanced Strongholds2+. Offers, starting-card order, mandatory normal Banker and native reactions remain. Actual third-party Bureaucrat sees the real Guild-card recipient. Tech pays only at its matching phase end; Banker waits for Mentat. Earned local support does not become personal money or reserve fees. Selection/printed roles are public capabilities; other native/module envelopes stay separate.',
'Explicit guild-betrayal --leader-skills remains a bounded classic preset of the original all14 setup. It does not require independent cards or another overlay. Native Mentat private questions, skilled-disc posture, Sandmaster collection and normal income retain their shared quotes; exact payer/recipient and printed Bureaucrat third-party conditions remain. Native cards, paid forces, Use/Pass and movement/plan continuation stay physical. Wider combinations and complete-mode acceptance remain separate.',
'Basic Moritani keeps legal Terror but gains no Advanced assassination, including registered mixed native Skills seats. Ecaz/Moritani with selected E1/E2 or unused decks keeps original Worlds/Tech3+/Advanced Strongholds2+ and optional independent cards without requiring Nexus. Advanced E3/Harkonnen remains guarded. Winner Suk and Rihani precede a different unused trained-disc death, one skill return, printed bounty and one original Mentat replacement. Native card/force sources, normal-call forfeiture and shared-Duke exclusion remain; unrelated overlays retain their original envelopes.',
'Basic and Advanced selected-deck Skills admits registered base, Ixian/Tleilaxu, CHOAM/Richese and Ecaz/Moritani seats only when their family is selected. Unused decks neither create a seat nor require one. All14/native setup remain. Auditor is Advanced and unskilled; foreign-ghola authorization still explicitly requires Advanced, grants no new skill and retains original Duke/custody limits. Cyborg losses, native funding and capped markers stay physical. Supplied Advanced p34 excludes Richese auctions from Ixian Technology; existing capture/KH/copy/role guards keep their own rules.',
@@ -4977,7 +4978,7 @@ export const RULE_TOPICS: RuleTopic[] = [
{ 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 combined-module and exceptional-timing controls are incomplete.',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 modifier-inclusive 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. The 13 October 2026 ruling resolves the captured-card replacement question: the original assignment is retained and no replacement is drawn while it exists; 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:'Verification',status:'Partial',detail:"Classic Nexus core no longer requires independent cards for all14 native reactions. Original offers, starting-card order and mandatory Banker remain; actual Guild fee claims retain the real recipient and printed Bureaucrat third-party conditions. Base33 Mentat replacement, purchase, native Cunning and three390px source controls pass with four legal policies. Earlier native/Richese evidence stays dated; full modes, strategy and deferred assurance remain open.",evidence:['tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/bureaucrat-richese-cache.test.ts','tests/bureaucrat-richese-black-market.test.ts','tests/fixture-bureaucrat-richese-funding.ts','docs/BUREAUCRAT_PAYMENTS.md','tests/diplomat-used-defense.test.ts', 'tests/diplomat-late-journey.test.ts', 'tests/fixture-diplomat-late-journey.ts', 'tests/diplomat-context-journey.test.ts', 'tests/fixture-diplomat-context-journey.ts', 'docs/DIPLOMAT_DEFENSE.md', 'tests/smuggler-kwisatz-journey.test.ts', 'tests/fixture-smuggler-kwisatz-journey.ts', 'tests/smuggler-artillery-journey.test.ts', 'tests/fixture-smuggler-artillery-journey.ts', 'docs/SMUGGLER_BATTLE.md', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts','tests/homeworld-native-e1e2-modules-runtime.test.ts','tests/homeworld-native-e3-modules-runtime.test.ts','tests/homeworld-classic-skills-runtime.test.ts','tests/homeworld-skills-discovery-runtime.test.ts','tests/homeworld-optional-modules-runtime.test.ts','tests/leader-skills-engine.test.ts','tests/leader-skills-capture.test.ts','tests/captured-skill-revival.test.ts','tests/fixture-captured-skill-revival.ts','tests/captured-skill-special-revival.test.ts','tests/fixture-captured-skill-special-revival.ts','tests/leader-battle-effects.test.ts','tests/suk-graduate.test.ts','tests/mentat-question.test.ts','tests/bureaucrat-payment.test.ts','tests/smuggler-battle.test.ts','tests/spice-banker.test.ts','tests/advanced-native-skills.test.ts','tests/fixture-advanced-native-skills.ts','tests/skills-tech-payments-runtime.test.ts','tests/skills-tech-battle-runtime.test.ts','tests/ix-choam-skills-tech-runtime.test.ts','tests/tleilaxu-skills-tech-runtime.test.ts','tests/richese-skills-tech-runtime.test.ts','tests/classic-skills-stronghold-runtime.test.ts','tests/native-skills-stronghold-runtime.test.ts','tests/ecaz-skills-modules-runtime.test.ts','tests/moritani-skills-modules-runtime.test.ts','docs/LEADER_SKILLS_RUNTIME.md','docs/HOMEWORLD_RUNTIME_IMPLEMENTATION.md']},
+ {area:'Verification',status:'Partial',detail:"Classic Nexus optional14 now retains original Tech3+ and Advanced Strongholds2+ with selected33/36. Mandatory Banker remains separate from native phase income; actual third-party receipts, trained-disc posture, local bank support and original cleanup/replacement stay distinct. Real normal lobby, earned Carthag/Arrakeen and fixed/free Axlotl controls complete with four legal policies. Earlier native/Richese evidence remains dated; full modes, strategy and deferred assurance stay open.",evidence:['tests/nexus-core-tech-runtime.test.ts','tests/fixture-nexus-core-tech.ts','tests/nexus-core-strongholds-runtime.test.ts','tests/fixture-nexus-core-strongholds.ts','tests/classic-nexus-core.test.ts','tests/fixture-classic-nexus-core.ts','game/faction-module-profile.ts','tests/classic-independent-nexus-utility.test.ts', 'tests/classic-independent-nexus-battle.test.ts', 'tests/fixture-classic-independent-nexus.ts', 'tests/bureaucrat-richese-cache.test.ts','tests/bureaucrat-richese-black-market.test.ts','tests/fixture-bureaucrat-richese-funding.ts','docs/BUREAUCRAT_PAYMENTS.md','tests/diplomat-used-defense.test.ts', 'tests/diplomat-late-journey.test.ts', 'tests/fixture-diplomat-late-journey.ts', 'tests/diplomat-context-journey.test.ts', 'tests/fixture-diplomat-context-journey.ts', 'docs/DIPLOMAT_DEFENSE.md', 'tests/smuggler-kwisatz-journey.test.ts', 'tests/fixture-smuggler-kwisatz-journey.ts', 'tests/smuggler-artillery-journey.test.ts', 'tests/fixture-smuggler-artillery-journey.ts', 'docs/SMUGGLER_BATTLE.md', 'tests/homeworld-classic-nexus-skills-runtime.test.ts', 'tests/homeworld-classic-discovery-nexus-runtime.test.ts','tests/homeworld-native-e1e2-modules-runtime.test.ts','tests/homeworld-native-e3-modules-runtime.test.ts','tests/homeworld-classic-skills-runtime.test.ts','tests/homeworld-skills-discovery-runtime.test.ts','tests/homeworld-optional-modules-runtime.test.ts','tests/leader-skills-engine.test.ts','tests/leader-skills-capture.test.ts','tests/captured-skill-revival.test.ts','tests/fixture-captured-skill-revival.ts','tests/captured-skill-special-revival.test.ts','tests/fixture-captured-skill-special-revival.ts','tests/leader-battle-effects.test.ts','tests/suk-graduate.test.ts','tests/mentat-question.test.ts','tests/bureaucrat-payment.test.ts','tests/smuggler-battle.test.ts','tests/spice-banker.test.ts','tests/advanced-native-skills.test.ts','tests/fixture-advanced-native-skills.ts','tests/skills-tech-payments-runtime.test.ts','tests/skills-tech-battle-runtime.test.ts','tests/ix-choam-skills-tech-runtime.test.ts','tests/tleilaxu-skills-tech-runtime.test.ts','tests/richese-skills-tech-runtime.test.ts','tests/classic-skills-stronghold-runtime.test.ts','tests/native-skills-stronghold-runtime.test.ts','tests/ecaz-skills-modules-runtime.test.ts','tests/moritani-skills-modules-runtime.test.ts','docs/LEADER_SKILLS_RUNTIME.md','docs/HOMEWORLD_RUNTIME_IMPLEMENTATION.md']},
],
},
{
diff --git a/tests/fixture-classic-independent-nexus-reactions.ts b/tests/fixture-classic-independent-nexus-reactions.ts
index a92bee6a..95a4eddb 100644
--- a/tests/fixture-classic-independent-nexus-reactions.ts
+++ b/tests/fixture-classic-independent-nexus-reactions.ts
@@ -49,7 +49,7 @@ export function drawClassicNexusReaction(face: FactionId, owner: string,
return { ...fixture, game, owner, actions, beforeDraw, afterDraw: structuredClone(game) };
}
-export function createIndependentHarkonnenCall(options: { advanced?: boolean; skills?: boolean; remote?: boolean; independent?: boolean } = {}) {
+export function createIndependentHarkonnenCall(options: { advanced?: boolean; skills?: boolean; remote?: boolean; independent?: boolean; tech?: boolean; strongholds?: boolean } = {}) {
const counter = baseDeck().find(card => card.effect === 'karama')!.id;
const starting = options.independent === false ? baseDeck().find(card => card.kind === 'worthless')!.id : HARASS_WITHDRAW_CARD;
const fixture = drawClassicNexusReaction('harkonnen', 'atreides', { ...options,
@@ -98,7 +98,7 @@ export function createIndependentHarkonnenCall(options: { advanced?: boolean; sk
return { ...fixture, game, beforeCall, beforeShipment, afterShipment, identity, opponent, key, counter };
}
-export function createIndependentIxianPurchase(options: { advanced?: boolean; skills?: boolean; owner?: 'atreides' | 'harkonnen'; independent?: boolean } = {}) {
+export function createIndependentIxianPurchase(options: { advanced?: boolean; skills?: boolean; owner?: 'atreides' | 'harkonnen'; independent?: boolean; tech?: boolean; strongholds?: boolean } = {}) {
const owner = options.owner ?? 'atreides';
const starting = options.independent === false ? baseDeck().find(card => card.kind === 'worthless')!.id : 'ecaz-recruits';
const fixture = drawClassicNexusReaction('ixians', owner, { ...options,
diff --git a/tests/fixture-classic-independent-nexus.ts b/tests/fixture-classic-independent-nexus.ts
index 09696e6e..fb56c45f 100644
--- a/tests/fixture-classic-independent-nexus.ts
+++ b/tests/fixture-classic-independent-nexus.ts
@@ -14,6 +14,8 @@ export type ClassicIndependentNexusOptions = {
skills?: boolean;
/** False exercises the original base33 without selecting the independent variant. */
independent?: boolean;
+ tech?: boolean;
+ strongholds?: boolean;
roster?: readonly FactionId[];
training?: Partial>;
/** Only original, still-undealt starting Treachery slots are ordered. */
@@ -36,6 +38,8 @@ export function createClassicIndependentNexusFixture(options: ClassicIndependent
};
for (const option of [...(independent ? ['ecazTreachery'] : []), 'nexusCards', ...(options.skills ? ['leaderSkills'] : [])])
act(game.host, { type: 'moduleOption', module: option, enabled: true });
+ if (options.tech) act(game.host, { type: 'techTokens', enabled: true });
+ if (options.strongholds) act(game.host, { type: 'strongholdCards', enabled: true });
for (const player of game.players) act(player.id, { type: 'ready' });
const lobby = structuredClone(game);
const rolls = options.skills ? skillOfferRolls(game, options.training) : [];
diff --git a/tests/fixture-classic-nexus-core.ts b/tests/fixture-classic-nexus-core.ts
index ceaddec7..94df54ae 100644
--- a/tests/fixture-classic-nexus-core.ts
+++ b/tests/fixture-classic-nexus-core.ts
@@ -7,7 +7,7 @@ import { drawClassicNexusReaction, reactionAction, reactionPlayer } from './fixt
import { advanceClassicIndependentNexus, type ClassicIndependentNexusOptions, type ClassicIndependentNexusStep } from './fixture-classic-independent-nexus';
import { classicDiscoveryNexusClean } from './fixture-discovery-classic-nexus';
-export type ClassicNexusCoreOptions = Pick;
+export type ClassicNexusCoreOptions = Pick;
function drawSecondFace(game: Game, owner: string, face: FactionId, actions: ClassicIndependentNexusStep[]) {
assert.equal(game.nexusCards!.phase!.stage, 'drawing');
const cards = game.nexusCards!.cards!, index = cards.deck.indexOf(face);
diff --git a/tests/fixture-nexus-core-strongholds.ts b/tests/fixture-nexus-core-strongholds.ts
new file mode 100644
index 00000000..b431dd26
--- /dev/null
+++ b/tests/fixture-nexus-core-strongholds.ts
@@ -0,0 +1,171 @@
+import assert from 'node:assert/strict';
+import { viewGame, type Game } from '../game/engine';
+import { baseDeck } from '../game/cards';
+import { territory } from '../game/board';
+import { REINFORCEMENTS_CARD } from '../game/reinforcements';
+import { createClassicIndependentNexusFixture, advanceClassicIndependentNexus,
+ type ClassicIndependentNexusStep } from './fixture-classic-independent-nexus';
+import { drawClassicNexusReaction, reactionAction, reactionPlayer } from './fixture-classic-independent-nexus-reactions';
+import { classicDiscoveryNexusClean as clean, nextClassicDiscoveryNexusStep,
+ applyClassicDiscoveryNexusStep } from './fixture-discovery-classic-nexus';
+
+export { reactionPlayer as nexusCoreStrongholdPlayer };
+export type NexusCoreStrongholdOptions = { independent?: boolean; skills?: boolean };
+export type NexusCoreStrongholdFixture = {
+ game: Game; setup: Game; actions: ClassicIndependentNexusStep[]; staging: string[];
+ claimed: Game; beforeShipment: Game; afterShipment: Game;
+ owner: string; opponent: string; key: string; ownerLeader: string; opponentLeader: string;
+ weapon: string | null; defense: string | null;
+};
+
+function prepare(game: Game, actions: ClassicIndependentNexusStep[]): Game {
+ for (let count = 0; count < 120; count++) {
+ const next = nextClassicDiscoveryNexusStep(game);
+ if (!next) {
+ assert.ok(game.battle && !game.battle.revealed && clean(game));
+ return game;
+ }
+ // The original decision must put the trained disc behind the shield
+ // before it can be selected in a sealed plan.
+ if (next.action.type === 'leaderSkillVisibility') next.action.hide = true;
+ game = applyClassicDiscoveryNexusStep(game, next, actions);
+ }
+ throw Error('Original Stronghold battle did not reach its sealed-plan boundary');
+}
+function bestLeader(game: Game, actor: string): string {
+ const leader = reactionPlayer(game, actor).leaders.filter(disc => !disc.dead && !disc.usedAt)
+ .sort((one, two) => two.strength - one.strength)[0];
+ assert.ok(leader);
+ return leader.id;
+}
+function chooseBattle(game: Game, location: string, actions: ClassicIndependentNexusStep[]): Game {
+ return prepare(reactionAction(game, game.active!, { type: 'chooseBattle', territory: location,
+ target: game.active === 'emperor' ? 'harkonnen' : 'emperor' }, actions), actions);
+}
+
+/** Emperor genuinely ships five original reserves into empty Arrakeen on turn
+ * one. Only unopened Spice/Nexus decks are ordered; every counter, payment,
+ * alliance and first end-Mentat card claim comes from original actions. */
+export function createNexusCoreArrakeenFixture(options: NexusCoreStrongholdOptions = {}): NexusCoreStrongholdFixture {
+ const independent = options.independent ?? true;
+ const original = createClassicIndependentNexusFixture({ advanced: true, independent,
+ skills: options.skills, strongholds: true, roster: ['emperor', 'harkonnen', 'guild'],
+ startingCards: independent ? { emperor: [REINFORCEMENTS_CARD] } : undefined });
+ const actions = original.actions;
+ let game = original.game;
+ const take = (worm: boolean) => {
+ const index = game.spiceDeck.findIndex(card => worm ? 'worm' in card : 'territory' in card);
+ assert.ok(index >= 0);
+ return game.spiceDeck.splice(index, 1)[0];
+ };
+ game.spiceDeck.unshift(take(false), take(false), take(true), take(false), take(false));
+ game = advanceClassicIndependentNexus(game, state => state.turn === 1 && state.phase === 5 &&
+ state.active === 'emperor' && clean(state), actions);
+ const sector = territory('arrakeen').sectors[0], key = `arrakeen:${sector}`;
+ assert.ok(!territory('arrakeen').sectors.includes(game.storm));
+ game = reactionAction(game, 'emperor', { type: 'ship', territory: 'arrakeen', sector,
+ amount: 5, elite: 0, allyPayment: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.strongholdCards?.claimedTurn === 1, actions);
+ const claimed = structuredClone(game);
+ assert.equal(claimed.strongholdCards!.owners.arrakeen, 'emperor');
+ game = advanceClassicIndependentNexus(game, state => state.turn === 2 && state.nexus &&
+ !state.spiceWindow && !state.spiceResolution && clean(state), actions);
+ game = reactionAction(game, 'harkonnen', { type: 'alliance', target: 'guild' }, actions);
+ game = reactionAction(game, 'guild', { type: 'alliance', target: 'harkonnen' }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.nexusCards?.phase?.stage === 'drawing' && clean(state), actions);
+ const cards = game.nexusCards!.cards!, index = cards.deck.indexOf('emperor');
+ assert.ok(index >= 0);
+ cards.deck.unshift(cards.deck.splice(index, 1)[0]);
+ game = reactionAction(game, 'emperor', { type: 'nexusCardChoice', turn: game.turn,
+ card: cards.hands.emperor, choice: 'draw', ownRedraws: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.phase === 5 && state.active === 'harkonnen' && clean(state), actions);
+ assert.ok(!territory('arrakeen').sectors.includes(game.storm));
+ const beforeShipment = structuredClone(game);
+ game = reactionAction(game, 'harkonnen', { type: 'ship', territory: 'arrakeen', sector,
+ amount: 5, elite: 0, allyPayment: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.phase === 6 && clean(state), actions);
+ const afterShipment = structuredClone(game);
+ game = chooseBattle(game, 'arrakeen', actions);
+ return { ...original, game, actions, claimed, beforeShipment, afterShipment,
+ owner: 'emperor', opponent: 'harkonnen', key, ownerLeader: bestLeader(game, 'emperor'),
+ opponentLeader: bestLeader(game, 'harkonnen'), weapon: independent ? REINFORCEMENTS_CARD : null,
+ defense: null };
+}
+
+/** Harkonnen retains its genuinely earned Carthag card. Atreides draws the
+ * physical Harkonnen Nexus, ships one counter and plays an actually offered
+ * Harkonnen Traitor. Optional all14 uses the original trained-disc choice. */
+export function createNexusCoreCarthagFixture(options: NexusCoreStrongholdOptions = {}): NexusCoreStrongholdFixture {
+ const poison = baseDeck().find(card => card.kind === 'poison')!.id;
+ const shield = baseDeck().find(card => card.kind === 'shield')!.id;
+ const original = drawClassicNexusReaction('harkonnen', 'atreides', { advanced: true,
+ independent: options.independent ?? true, skills: options.skills, strongholds: true,
+ training: options.skills ? { harkonnen: 'prana-bindu-adept' } : undefined,
+ startingCards: { atreides: [poison], harkonnen: [shield] } });
+ const actions = original.actions;
+ const claimed = structuredClone(original.beforeDraw);
+ assert.equal(claimed.strongholdCards!.owners.carthag, 'harkonnen');
+ let game = advanceClassicIndependentNexus(original.game, state => state.phase === 5 &&
+ state.active === 'atreides' && clean(state), actions);
+ const opponentLeader = reactionPlayer(game, 'atreides').leaders.find(leader =>
+ reactionPlayer(game, 'harkonnen').traitors.includes(leader.id))?.id;
+ assert.ok(opponentLeader, 'Use the actual offered Traitor, never assign a leader identity');
+ const sector = territory('carthag').sectors[0], key = `carthag:${sector}`;
+ assert.ok(!territory('carthag').sectors.includes(game.storm));
+ const beforeShipment = structuredClone(game);
+ game = reactionAction(game, 'atreides', { type: 'ship', territory: 'carthag', sector,
+ amount: 1, elite: 0, allyPayment: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.phase === 6 && clean(state), actions);
+ const afterShipment = structuredClone(game);
+ game = reactionAction(game, game.active!, { type: 'chooseBattle', territory: 'carthag',
+ target: game.active === 'atreides' ? 'harkonnen' : 'atreides' }, actions);
+ game = prepare(game, actions);
+ const trained = game.leaderSkills?.assignments.find(assignment => assignment.owner === 'harkonnen');
+ return { ...original, game, claimed, beforeShipment, afterShipment, owner: 'harkonnen',
+ opponent: 'atreides', key, ownerLeader: trained?.leader ?? bestLeader(game, 'harkonnen'),
+ opponentLeader, weapon: poison, defense: shield };
+}
+
+/** Return the revealed original battle, before any Traitor vote or cleanup. */
+export function revealNexusCoreStrongholdBattle(fixture: NexusCoreStrongholdFixture,
+ options: { cunning?: boolean; shield?: boolean; ownerDial?: number; opponentDial?: number } = {}): Game {
+ let game = structuredClone(fixture.game);
+ const arrakeen = fixture.key.startsWith('arrakeen:');
+ if (arrakeen && options.cunning !== false) {
+ const offer = viewGame(game, fixture.owner).nexusSardaukar!.offer!;
+ assert.equal(offer.blocked, null);
+ game = reactionAction(game, fixture.owner, { type: 'nexusSardaukar', event: offer.event });
+ game = advanceClassicIndependentNexus(game, clean);
+ }
+ const ownerDial = options.ownerDial ?? (arrakeen ? 6 : 0.5);
+ const opponentDial = options.opponentDial ?? (arrakeen ? 1 : 0);
+ for (const actor of [game.battle!.attacker, game.battle!.defender]) {
+ const owner = actor === fixture.owner;
+ game = reactionAction(game, actor, { type: 'battlePlan',
+ dial: owner ? ownerDial : opponentDial,
+ support: owner ? arrakeen ? options.cunning === false ? ownerDial : ownerDial / 2 : 0 : opponentDial,
+ leader: owner ? fixture.ownerLeader : fixture.opponentLeader,
+ weapon: arrakeen ? owner ? fixture.weapon : null : owner ? null : fixture.weapon,
+ defense: !arrakeen && owner && options.shield !== false ? fixture.defense : null });
+ while (game.response || game.phaseOpening || game.decision?.kind === 'fullPlanOffer') {
+ const next = nextClassicDiscoveryNexusStep(game); assert.ok(next);
+ game = applyClassicDiscoveryNexusStep(game, next);
+ }
+ }
+ assert.ok(game.battle?.revealed);
+ return game;
+}
+
+/** Original votes and native cleanup; no plan or strategy is invented. */
+export function finishNexusCoreStrongholdBattle(game: Game, stopAtCleanup = false): Game {
+ return advanceClassicIndependentNexus(game, state => (!state.battle && clean(state)) ||
+ (stopAtCleanup && state.decision?.kind === 'battleCards'));
+}
+
+/** Preserve the original native Harkonnen call and pending cancellation window. */
+export function callNexusCoreCarthagTraitor(game: Game): Game {
+ for (const voter of viewGame(game, 'atreides').battle!.traitorVoters)
+ if (voter !== 'harkonnen' && game.battle!.traitorCalls[voter] === undefined)
+ game = reactionAction(game, voter, { type: 'traitorCall', call: false });
+ return reactionAction(game, 'harkonnen', { type: 'traitorCall', call: true });
+}
diff --git a/tests/fixture-nexus-core-tech.ts b/tests/fixture-nexus-core-tech.ts
new file mode 100644
index 00000000..e2001eff
--- /dev/null
+++ b/tests/fixture-nexus-core-tech.ts
@@ -0,0 +1,123 @@
+import assert from 'node:assert/strict';
+import { viewGame, type Action, type Game } from '../game/engine';
+import { TERRITORIES } from '../game/board';
+import { treacheryDeck } from '../game/cards';
+import { ownedTech, type TechId } from '../game/tech-tokens';
+import { createClassicNexusGuildPayment, type ClassicNexusCoreOptions } from './fixture-classic-nexus-core';
+import { createIndependentHarkonnenCall, reactionAction, reactionPlayer } from './fixture-classic-independent-nexus-reactions';
+import { advanceClassicIndependentNexus, createClassicIndependentNexusFixture,
+ type ClassicIndependentNexusStep } from './fixture-classic-independent-nexus';
+import { classicDiscoveryNexusClean, nextClassicDiscoveryNexusStep } from './fixture-discovery-classic-nexus';
+
+/** Read the actual first-Storm custody; never select or retrofit a token owner. */
+export function nexusCoreTechHolding(game: Game, token: TechId) {
+ const owner = game.techTokens?.[token].owner;
+ assert.ok(owner, 'The original first Storm must assign this physical token');
+ return { owner, count: ownedTech(game.techTokens, owner).length };
+}
+
+/** A real five-counter Richese order, stopped before its funded Guild invoice. */
+export function createNexusCoreTechShipment(options: ClassicNexusCoreOptions = {}) {
+ const fixture = createClassicNexusGuildPayment({ independent: true, ...options, tech: true });
+ return { ...fixture, token: nexusCoreTechHolding(fixture.game, 'heighliners') };
+}
+
+/** Original Harass plan and native Harkonnen declaration, before Nexus Use/Pass.
+ * The reused producer forwards these original module options into its lobby. */
+export function createNexusCoreTechHarass() {
+ const options = { tech: true, independent: true };
+ return createIndependentHarkonnenCall(options);
+}
+
+/** Actual battle losses supply eight Tanks and one own dead leader. Recruits
+ * stays in its original starting hand, and a real closing draw supplies Tleilaxu.
+ * Only unopened original Spice/Nexus singletons are positioned. With `drain`,
+ * a genuine turn-one four-counter desert shipment spends eight of Atreides'
+ * own spice so later Revival prices face a genuinely thin wallet. */
+export function createNexusCoreTechRevival(options: Pick & { drain?: boolean } = {}) {
+ const weapon = treacheryDeck([]).find(card => card.kind === 'projectile')!;
+ const harmless = treacheryDeck([]).find(card => card.kind === 'worthless')!;
+ const { drain, ...modules } = options;
+ const fixture = createClassicIndependentNexusFixture({ advanced: true, skills: true, ...modules,
+ independent: true, tech: true,
+ training: options.training ?? { atreides: 'diplomat', guild: 'mentat', emperor: 'sandmaster' },
+ startingCards: { atreides: ['ecaz-recruits'], guild: [harmless.id], emperor: [weapon.id] } });
+ const actions = fixture.actions;
+ let game = fixture.game;
+ const take = (worm: boolean) => {
+ const index = game.spiceDeck.findIndex(card => worm
+ ? 'worm' in card && !card.greatMaker && !card.suppressed && !card.thumper
+ : 'territory' in card);
+ assert.ok(index >= 0);
+ return game.spiceDeck.splice(index, 1)[0];
+ };
+ game.spiceDeck.unshift(take(false), ...(game.advanced ? [take(false)] : []),
+ take(true), take(false), ...(game.advanced ? [take(false)] : []));
+ if (drain) {
+ game = advanceClassicIndependentNexus(game, state => state.turn === 1 &&
+ state.phase === 5 && state.active === 'atreides' && classicDiscoveryNexusClean(state), actions);
+ const desert = TERRITORIES.find(place => place.type !== 'stronghold' &&
+ !place.sectors.includes(game.storm) && game.players.every(player =>
+ Object.keys(player.forces).every(key => !key.startsWith(`${place.id}:`))))!;
+ assert.ok(desert);
+ const wallet = reactionPlayer(game, 'atreides').spice;
+ assert.ok(wallet >= 8, 'The genuine drain shipment needs its real turn-one wallet');
+ game = reactionAction(game, 'atreides', { type: 'ship', territory: desert.id,
+ sector: desert.sectors[0], amount: 4, elite: 0, allyPayment: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.turn === 1 && state.phase === 5 &&
+ state.active !== 'atreides' && classicDiscoveryNexusClean(state), actions);
+ assert.equal(reactionPlayer(game, 'atreides').spice, wallet - 8);
+ }
+ game = advanceClassicIndependentNexus(game, state => state.phase === 5 &&
+ state.active === 'emperor' && classicDiscoveryNexusClean(state), actions);
+ const beforeShipment = structuredClone(game);
+ game = reactionAction(game, 'emperor', { type: 'ship', territory: 'arrakeen', sector: 10,
+ amount: 1, elite: 0, allyPayment: 0 }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.phase === 6 && classicDiscoveryNexusClean(state), actions);
+ if (!game.battle) game = reactionAction(game, game.active!, { type: 'chooseBattle', territory: 'arrakeen',
+ target: game.active === 'atreides' ? 'emperor' : 'atreides' }, actions);
+ game = advanceClassicIndependentNexus(game, state => !!state.battle && !state.battle.revealed &&
+ classicDiscoveryNexusClean(state) && !nextClassicDiscoveryNexusStep(state), actions);
+ const leader = reactionPlayer(game, 'atreides').leaders.filter(disc => !disc.dead)
+ .sort((a, b) => a.strength - b.strength)[0].id;
+ const opponentLeader = reactionPlayer(game, 'emperor').leaders.filter(disc => !disc.dead)
+ .sort((a, b) => a.strength - b.strength)[0].id;
+ game = reactionAction(game, 'atreides', { type: 'battlePlan', dial: game.advanced ? 4 : 8, support: 0,
+ leader, weapon: null, defense: null }, actions);
+ game = reactionAction(game, 'emperor', { type: 'battlePlan', dial: 0, support: 0,
+ leader: opponentLeader, weapon: weapon.id, defense: null }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.turn === 2 && state.nexus &&
+ !state.spiceWindow && !state.spiceResolution && classicDiscoveryNexusClean(state), actions);
+ const afterBattle = structuredClone(game);
+ game = reactionAction(game, 'guild', { type: 'alliance', target: 'emperor' }, actions);
+ game = reactionAction(game, 'emperor', { type: 'alliance', target: 'guild' }, actions);
+ game = advanceClassicIndependentNexus(game, state => state.nexusCards?.phase?.stage === 'drawing', actions);
+ const cards = game.nexusCards!.cards!, index = cards.deck.indexOf('tleilaxu');
+ assert.ok(index >= 0);
+ cards.deck.unshift(cards.deck.splice(index, 1)[0]);
+ const beforeDraw = structuredClone(game);
+ game = reactionAction(game, 'atreides', { type: 'nexusCardChoice', turn: game.turn,
+ card: cards.hands.atreides, choice: 'draw', ownRedraws: 0 }, actions);
+ const afterDraw = structuredClone(game);
+ game = advanceClassicIndependentNexus(game, state => state.phase === 4 && classicDiscoveryNexusClean(state), actions);
+ const offer = viewGame(game, 'atreides').nexusTleilaxu!.secretAlly!;
+ assert.equal(offer.blocked, null);
+ const action: Action = { type: 'nexusTleilaxuRevival', event: offer.event, leader, amount: 5, elite: 0 };
+ return { ...fixture, game, actions, beforeShipment, afterBattle, beforeDraw, afterDraw,
+ leader, action,
+ token: nexusCoreTechHolding(game, 'axlotl') };
+}
+
+/** Retain the exact native phase-ending action and its pre-payout wallets. */
+export function finishNexusCoreTechPhase(state: Game, actions: ClassicIndependentNexusStep[] = []) {
+ let game = state;
+ const phase = game.phase;
+ for (let count = 0; count < 300; count++) {
+ const before = structuredClone(game), step = nextClassicDiscoveryNexusStep(game);
+ assert.ok(step, 'Finish the real pending control before leaving this phase');
+ game = reactionAction(game, step.actor, step.action, actions);
+ if (game.phase !== phase) return { before, after: game, step, actions };
+ }
+ throw new Error('The native phase did not finish');
+}
diff --git a/tests/nexus-core-strongholds-runtime.test.ts b/tests/nexus-core-strongholds-runtime.test.ts
new file mode 100644
index 00000000..4e896741
--- /dev/null
+++ b/tests/nexus-core-strongholds-runtime.test.ts
@@ -0,0 +1,161 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { viewGame, type Game } from '../game/engine';
+import { maxCombatDial } from '../game/combat';
+import { strongholdControllers } from '../game/stronghold-cards';
+import { REINFORCEMENTS_CARD } from '../game/reinforcements';
+import { advanceClassicIndependentNexus } from './fixture-classic-independent-nexus';
+import { reactionAction } from './fixture-classic-independent-nexus-reactions';
+import { classicDiscoveryNexusClean as clean } from './fixture-discovery-classic-nexus';
+import { createNexusCoreArrakeenFixture, createNexusCoreCarthagFixture,
+ revealNexusCoreStrongholdBattle, finishNexusCoreStrongholdBattle,
+ callNexusCoreCarthagTraitor, nexusCoreStrongholdPlayer as player } from './fixture-nexus-core-strongholds';
+
+function physicalCounters(game: Game): void {
+ for (const seat of game.players)
+ assert.equal(seat.reserves + seat.tanks + Object.values(seat.forces).reduce((sum, count) => sum + count, 0), 20);
+}
+function nextMentat(game: Game): Game {
+ return advanceClassicIndependentNexus(game, state => state.turn === 3 &&
+ state.strongholdCards?.claimedTurn === 2 && clean(state));
+}
+
+for (const independent of [false, true]) {
+ void test(`earned Arrakeen pays only actual holder support beside native Emperor Cunning${independent ? ' and three real Reinforcements reserves' : ' on base33'}`, () => {
+ const f = createNexusCoreArrakeenFixture({ independent });
+ assert.equal(f.setup.strongholdCards!.claimedTurn, 0);
+ assert.ok(Object.values(f.setup.strongholdCards!.owners).every(owner => owner === null));
+ assert.equal(f.claimed.strongholdCards!.owners.arrakeen, f.owner);
+ assert.equal(player(f.claimed, f.owner).forces[f.key], 5);
+ assert.equal(player(f.claimed, f.owner).reserves, player(f.setup, f.owner).reserves - 5,
+ 'First end-Mentat ownership follows an original five-counter shipment');
+ const before = player(f.game, f.owner), enemy = player(f.game, f.opponent);
+ assert.equal(player(f.afterShipment, f.opponent).reserves, player(f.beforeShipment, f.opponent).reserves - 5);
+ assert.equal(player(f.afterShipment, f.opponent).spice, player(f.beforeShipment, f.opponent).spice - 3,
+ 'The real reciprocal Guild alliance retains the rounded half-price tariff for five counters');
+ const revealed = revealNexusCoreStrongholdBattle(f);
+ const forces = viewGame(revealed, f.owner).battle!.ownForces!;
+ assert.equal(forces.normal, 5);
+ assert.equal(forces.temporaryElite, 5);
+ assert.equal(maxCombatDial(forces), 10);
+ assert.equal(revealed.battle!.plans[f.owner].dial, 6);
+ assert.equal(revealed.battle!.plans[f.owner].support, 3);
+ assert.equal(player(revealed, f.owner).spice, before.spice,
+ 'Sealing the plan neither pays support early nor credits bank money to the holder');
+ const done = finishNexusCoreStrongholdBattle(revealed);
+ assert.equal(done.lastBattleContext!.winner, f.owner);
+ assert.equal(player(done, f.owner).spice, before.spice - 1 + 2,
+ 'Actual support3 uses bank2/personal1 before separate native Arrakeen Collection2');
+ assert.equal(player(done, f.opponent).spice, enemy.spice - 1 + 2,
+ 'The opposing Carthag holder pays personal support1 in Arrakeen before its separate native city Collection2');
+ assert.equal(player(done, f.owner).reserves, before.reserves - (independent ? 3 : 0));
+ assert.equal(player(done, f.owner).tanks, before.tanks + 3 + (independent ? 3 : 0),
+ 'Three dialed temporary-Sardaukar casualties and three reserve fees retain distinct sources');
+ assert.equal(player(done, f.owner).forces[f.key], 2,
+ 'Reinforcements never adds supported board counters');
+ assert.deepEqual(player(done, f.owner).elites, before.elites);
+ assert.equal(player(done, f.opponent).tanks, enemy.tanks + 5);
+ assert.equal(done.nexusCards!.cards!.hands[f.owner], null);
+ assert.equal(done.nexusCards!.cards!.discard.filter(face => face === 'emperor').length, 1);
+ assert.equal(done.discard.filter(card => card.id === REINFORCEMENTS_CARD).length, independent ? 1 : 0);
+ assert.equal(done.nexusSardaukarHistory!.length, 1);
+ assert.equal(done.nexusSardaukarHistory![0].casualties!.outcome, 'complete');
+ assert.equal(done.strongholdCards!.owners.arrakeen, f.owner);
+ assert.equal(done.phase, 7);
+ physicalCounters(done);
+ });
+}
+
+void test('Arrakeen bank support cannot replace the Reinforcements reserve fee or ordinary five-counter dial losses when Cunning is not used', () => {
+ const f = createNexusCoreArrakeenFixture();
+ const before = player(f.game, f.owner);
+ const done = finishNexusCoreStrongholdBattle(revealNexusCoreStrongholdBattle(f, { cunning: false, ownerDial: 5 }));
+ assert.equal(done.lastBattleContext!.winner, f.owner);
+ assert.equal(player(done, f.owner).spice, before.spice - 3,
+ 'Support5 costs personal3 after bank2; no surviving city counter means no Collection2');
+ assert.equal(player(done, f.owner).reserves, before.reserves - 3);
+ assert.equal(player(done, f.owner).tanks, before.tanks + 8);
+ assert.equal(player(done, f.owner).forces[f.key] ?? 0, 0);
+ assert.equal(done.nexusCards!.cards!.hands[f.owner], 'emperor');
+ assert.equal(done.strongholdCards!.owners.arrakeen, f.owner,
+ 'Vacating the city through actual dial casualties does not transfer held custody early');
+ const settled = nextMentat(done);
+ assert.equal(settled.strongholdCards!.owners.arrakeen, null);
+ physicalCounters(settled);
+});
+
+for (const skills of [false, true]) {
+ void test(`earned Carthag physical Shield survives poison after an original Harkonnen call is canceled${skills ? ' with its actually trained all14 disc behind the shield' : ' on base33'}`, () => {
+ const f = createNexusCoreCarthagFixture({ skills, independent: skills });
+ const before = player(f.game, f.owner);
+ assert.equal(f.claimed.strongholdCards!.owners.carthag, f.owner);
+ assert.equal(f.claimed.strongholdCards!.claimedTurn, 1);
+ assert.equal(player(f.afterShipment, f.opponent).reserves, player(f.beforeShipment, f.opponent).reserves - 1);
+ assert.equal(player(f.afterShipment, f.opponent).spice, player(f.beforeShipment, f.opponent).spice - 1);
+ const revealed = revealNexusCoreStrongholdBattle(f);
+ const called = callNexusCoreCarthagTraitor(revealed);
+ assert.equal(player(called, f.opponent).leaders.find(disc => disc.id === f.opponentLeader)!.dead, false,
+ 'The original call is pending, not already a paid traitor outcome');
+ const event = called.pendingNexusHarkonnenBetrayal!.source.event;
+ const canceled = reactionAction(called, f.opponent, { type: 'nexusHarkonnenBetrayalUse', event });
+ assert.equal(canceled.nexusCards!.cards!.hands[f.opponent], null);
+ assert.equal(player(canceled, f.owner).traitors.includes(f.opponentLeader), false);
+ assert.equal(canceled.traitorReserve!.includes(f.opponentLeader), true);
+ const done = finishNexusCoreStrongholdBattle(canceled);
+ assert.equal(done.lastBattleContext!.winner, f.owner);
+ assert.equal(player(done, f.owner).leaders.find(disc => disc.id === f.ownerLeader)!.dead, false);
+ assert.equal(player(done, f.owner).forces[f.key], before.forces[f.key] - 1);
+ assert.equal(player(done, f.owner).tanks, before.tanks + 1);
+ assert.equal(player(done, f.owner).reserves, before.reserves);
+ assert.equal(player(done, f.owner).hand.some(card => card.id === f.defense), true,
+ 'Snooper protection never replaces the physical Shield or moves it to a new hand');
+ assert.equal(player(done, f.opponent).tanks, player(f.game, f.opponent).tanks + 1);
+ const top = done.traitorReserve![0];
+ const mentat = nextMentat(done);
+ assert.equal(mentat.nexusHarkonnenReplacementHistory!.length, 1);
+ assert.equal(mentat.nexusHarkonnenReplacementHistory![0].drawn, top);
+ assert.equal(player(mentat, f.owner).traitors.length, before.traitors.length);
+ assert.equal(mentat.strongholdCards!.owners.carthag, f.owner);
+ physicalCounters(mentat);
+ });
+}
+
+void test('a retained Carthag card cannot protect an empty defense; actual loss changes control now but held custody only at Mentat', () => {
+ const f = createNexusCoreCarthagFixture({ skills: true });
+ let game = callNexusCoreCarthagTraitor(revealNexusCoreStrongholdBattle(f, { shield: false }));
+ game = reactionAction(game, f.opponent, { type: 'nexusHarkonnenBetrayalUse',
+ event: game.pendingNexusHarkonnenBetrayal!.source.event });
+ const done = finishNexusCoreStrongholdBattle(game);
+ assert.equal(done.lastBattleContext!.winner, f.opponent);
+ assert.equal(player(done, f.owner).leaders.find(disc => disc.id === f.ownerLeader)!.dead, true);
+ assert.equal(player(done, f.owner).forces[f.key] ?? 0, 0);
+ assert.equal(player(done, f.owner).tanks, player(f.game, f.owner).tanks + 10);
+ assert.equal(player(done, f.owner).hand.some(card => card.id === f.defense), true,
+ 'An unplayed Shield stays physically held and grants no Carthag defense');
+ assert.equal(strongholdControllers(done.players, false).carthag, f.opponent);
+ assert.equal(done.strongholdCards!.owners.carthag, f.owner);
+ assert.equal(done.strongholdCards!.claimedTurn, 1);
+ const mentat = nextMentat(done);
+ assert.equal(mentat.strongholdCards!.owners.carthag, f.opponent);
+ assert.equal(mentat.strongholdCards!.claimedTurn, 2);
+ assert.equal(player(mentat, f.owner).hand.some(card => card.id === f.defense), true);
+ physicalCounters(mentat);
+});
+
+void test('passing the real Harkonnen Nexus acknowledgment retains the original single-traitor outcome and does not spend or replace the held card', () => {
+ const f = createNexusCoreCarthagFixture();
+ let game = callNexusCoreCarthagTraitor(revealNexusCoreStrongholdBattle(f));
+ game = reactionAction(game, f.opponent, { type: 'nexusHarkonnenBetrayalPass',
+ event: game.pendingNexusHarkonnenBetrayal!.source.event });
+ const done = finishNexusCoreStrongholdBattle(game);
+ assert.equal(done.lastBattleContext!.winner, f.owner);
+ assert.equal(player(done, f.opponent).leaders.find(disc => disc.id === f.opponentLeader)!.dead, true);
+ assert.equal(player(done, f.owner).traitors.includes(f.opponentLeader), true);
+ assert.equal(player(done, f.owner).tanks, player(f.game, f.owner).tanks,
+ 'A successful single-traitor call does not pay the ordinary half-dial casualty');
+ assert.equal(done.nexusCards!.cards!.hands[f.opponent], 'harkonnen');
+ assert.deepEqual(done.nexusCards!.cards!.discard, []);
+ assert.equal(done.pendingNexusHarkonnenReplacement, null);
+ assert.equal(done.strongholdCards!.owners.carthag, f.owner);
+ physicalCounters(done);
+});
diff --git a/tests/nexus-core-tech-runtime.test.ts b/tests/nexus-core-tech-runtime.test.ts
new file mode 100644
index 00000000..72cb6b63
--- /dev/null
+++ b/tests/nexus-core-tech-runtime.test.ts
@@ -0,0 +1,151 @@
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+import { applyAction, type Action, type Game } from '../game/engine';
+import { TECH_TOKENS, ownedTech } from '../game/tech-tokens';
+import { forceRevivalQuote, normalRevivalAllowance } from '../game/revival';
+import { reactionAction, reactionPlayer } from './fixture-classic-independent-nexus-reactions';
+import { advanceClassicIndependentNexus } from './fixture-classic-independent-nexus';
+import { classicDiscoveryNexusClean } from './fixture-discovery-classic-nexus';
+import { createNexusCoreTechHarass, createNexusCoreTechRevival,
+ createNexusCoreTechShipment, finishNexusCoreTechPhase } from './fixture-nexus-core-tech';
+
+const wallets = (game: Game) => Object.fromEntries(game.players.map(player => [player.id, player.spice]));
+function rejected(game: Game, actor: string, action: Action) {
+ const before = structuredClone(game);
+ assert.throws(() => applyAction(game, actor, action));
+ assert.deepEqual(game, before, 'A rejected consumer action cannot move cards, counters, wallets or tokens');
+}
+const quietPhaseFour = (game: Game) => advanceClassicIndependentNexus(game,
+ state => state.phase === 4 && classicDiscoveryNexusClean(state));
+function firstStormCustody(game: Game) {
+ assert.ok(game.techTokens);
+ for (const token of TECH_TOKENS) {
+ const owner = game.techTokens[token.id].owner;
+ assert.ok(owner, `The original first Storm must have assigned ${token.name}`);
+ assert.equal(game.techTokens[token.id].spice, 0, 'No token income exists before its phase');
+ }
+}
+
+void test('Basic independent36 Tech keeps first-Storm custody and pays the funded delivery Heighliners once', () => {
+ const f = createNexusCoreTechShipment();
+ firstStormCustody(f.game);
+ assert.equal(f.game.turn, 2);
+ assert.equal(f.game.pendingGuildBetrayal!.invoice.price, 1);
+ const owner = f.token.owner, pending = f.token.count;
+ const before = reactionPlayer(f.beforeShipment, 'atreides');
+ const game = reactionAction(f.game, 'emperor', { type: 'guildBetrayalUse',
+ event: f.game.pendingGuildBetrayal!.invoice.event });
+ // The delivered five counters, not the fee itself, are the real shipment.
+ assert.equal(reactionPlayer(game, 'atreides').forces[f.key], 5);
+ assert.equal(reactionPlayer(game, 'atreides').reserves, before.reserves - 5);
+ assert.equal(reactionPlayer(game, 'atreides').spice, before.spice - 1);
+ assert.equal(reactionPlayer(game, 'emperor').spice, reactionPlayer(f.beforeShipment, 'emperor').spice + 1);
+ assert.equal(game.techTokens!.heighliners.owner, owner);
+ assert.equal(game.techTokens!.heighliners.spice, pending, 'Delivery accrues the owner-count income as pending spice');
+ assert.equal(game.techTokens!.heighliners.triggeredTurn, game.turn);
+ const closed = finishNexusCoreTechPhase(game);
+ assert.equal(closed.after.techTokens!.heighliners.spice, 0, 'Phase end pays the pending pile exactly once');
+ assert.equal(closed.after.techTokens!.heighliners.triggeredTurn, game.turn);
+ const beforeClose = wallets(closed.before), afterClose = wallets(closed.after);
+ assert.equal(afterClose[owner], beforeClose[owner] + pending);
+ for (const [id, spice] of Object.entries(beforeClose))
+ if (id !== owner) assert.equal(afterClose[id], spice, 'No other seat collects another token owner\'s pile');
+});
+
+void test('Advanced all14 Tech fixed Tleilaxu group pays real bank spice and only a free return accrues Axlotl', () => {
+ const f = createNexusCoreTechRevival();
+ firstStormCustody(f.game);
+ const owner = f.token.owner, pending = f.token.count;
+ const start = reactionPlayer(f.game, 'atreides');
+ assert.equal(start.tanks, 8, 'Actual battle losses supply the physical Tanks');
+ assert.equal(start.leaders.find(disc => disc.id === f.leader)!.dead, true);
+ let game = reactionAction(f.game, 'atreides', { type: 'card', card: 'ecaz-recruits' });
+ const rate = normalRevivalAllowance(game, reactionPlayer(game, 'atreides'));
+ assert.equal(rate.freeRate, 4);
+ assert.equal(rate.limit, 7);
+ rejected(game, 'atreides', { ...f.action, elite: 1 });
+ rejected(game, 'atreides', { ...f.action, leader: '' });
+ rejected(game, 'atreides', { ...f.action, amount: 6 });
+ game = quietPhaseFour(reactionAction(game, 'atreides', f.action));
+ const fixed = reactionPlayer(game, 'atreides');
+ assert.equal(fixed.spice, start.spice - 5, 'Five exact counters cost one bank spice each');
+ assert.equal(fixed.tanks, 3);
+ assert.equal(fixed.reserves, start.reserves + 5);
+ assert.equal(fixed.revived, 5);
+ assert.equal(fixed.freeForcesRevived, 0, 'The paid card group never consumes the free quota');
+ assert.equal(fixed.leaderRevived, true);
+ assert.equal(fixed.leaders.find(disc => disc.id === f.leader)!.dead, false);
+ assert.equal(game.nexusCards!.cards!.hands.atreides, null);
+ assert.equal(game.discard.filter(card => card.id === 'ecaz-recruits').length, 1);
+ assert.equal(game.techTokens!.axlotl.owner, owner);
+ assert.equal(game.techTokens!.axlotl.spice, 0, 'A paid fixed group is not a free revival and accrues nothing');
+ assert.equal(game.techTokens!.axlotl.triggeredTurn, undefined);
+ game = quietPhaseFour(reactionAction(game, 'atreides', { type: 'revive', amount: 1 }));
+ const freeSeat = reactionPlayer(game, 'atreides');
+ assert.equal(freeSeat.spice, fixed.spice, 'The ordinary free return itself costs nothing');
+ assert.equal(freeSeat.freeForcesRevived, 1);
+ assert.equal(freeSeat.tanks, 2);
+ assert.equal(game.techTokens!.axlotl.spice, pending, 'One genuine free return accrues the owner-count pile');
+ assert.equal(game.techTokens!.axlotl.triggeredTurn, game.turn);
+ game = quietPhaseFour(reactionAction(game, 'atreides', { type: 'revive', amount: 1 }));
+ assert.equal(reactionPlayer(game, 'atreides').spice, fixed.spice);
+ assert.equal(reactionPlayer(game, 'atreides').reserves, start.reserves + 7);
+ assert.equal(reactionPlayer(game, 'atreides').tanks, 1);
+ assert.equal(game.techTokens!.axlotl.spice, pending,
+ 'A second genuine free return in this Revival does not mint another pile');
+ const closed = finishNexusCoreTechPhase(game);
+ assert.equal(closed.after.techTokens!.axlotl.spice, 0, 'Revival phase end pays the Axlotl pile once');
+ const beforeClose = wallets(closed.before), afterClose = wallets(closed.after);
+ assert.equal(afterClose[owner], beforeClose[owner] + pending);
+});
+
+void test('pending Axlotl spice cannot fund an unaffordable further paid revival', () => {
+ const f = createNexusCoreTechRevival({ drain: true });
+ const owner = f.token.owner, pending = f.token.count;
+ let game = reactionAction(f.game, 'atreides', { type: 'card', card: 'ecaz-recruits' });
+ const rate = normalRevivalAllowance(game, reactionPlayer(game, 'atreides'));
+ assert.ok(reactionPlayer(game, 'atreides').spice < 6,
+ 'The genuine turn-one shipment leaves a thin wallet for this boundary');
+ game = quietPhaseFour(reactionAction(game, 'atreides', { type: 'revive', amount: rate.freeRate }));
+ const seat = reactionPlayer(game, 'atreides');
+ assert.equal(game.techTokens!.axlotl.spice, pending, 'The owner\'s own free return created the pending pile');
+ const quote = forceRevivalQuote(game, seat, 3);
+ assert.ok(quote.cost > seat.spice && quote.cost - seat.spice <= pending,
+ 'Choose the smallest legal overage the pending pile would cover if it were spendable');
+ rejected(game, 'atreides', { type: 'revive', amount: 3 });
+ assert.equal(game.techTokens!.axlotl.spice, pending, 'The refusal cannot mint or clear token income');
+ game = quietPhaseFour(reactionAction(game, 'atreides', { type: 'revive', amount: 2 }));
+ const paid = reactionPlayer(game, 'atreides');
+ assert.equal(paid.spice, seat.spice - 4, 'Two genuinely affordable paid counters cost their printed four bank spice');
+ assert.equal(paid.tanks, 2);
+ assert.equal(game.techTokens!.axlotl.spice, pending, 'Axlotl accrues once per turn, not once per return');
+ const closed = finishNexusCoreTechPhase(game);
+ assert.equal(closed.after.techTokens!.axlotl.spice, 0);
+ assert.equal(wallets(closed.after)[owner], wallets(closed.before)[owner] + pending);
+});
+
+void test('Basic independent36 Harass return and native winner transfer mint no token income', () => {
+ const f = createNexusCoreTechHarass();
+ const before = f.beforeCall;
+ const game = reactionAction(f.game, 'atreides', { type: 'nexusHarkonnenBetrayalUse',
+ event: f.game.pendingNexusHarkonnenBetrayal!.source.event });
+ const lost = ownedTech(before.techTokens, 'atreides');
+ assert.ok(lost.length, 'The defeated seat genuinely held tokens');
+ const moved = TECH_TOKENS.filter(token =>
+ before.techTokens![token.id].owner !== game.techTokens![token.id].owner);
+ assert.equal(moved.length, 1, 'The battle winner takes exactly one token from the loser');
+ const token = moved[0];
+ assert.equal(before.techTokens![token.id].owner, 'atreides');
+ assert.equal(game.techTokens![token.id].owner, 'harkonnen');
+ assert.ok(lost.includes(token.id), 'The native choice belongs to the loser\'s actual holdings');
+ assert.equal(game.pendingTech ?? null, null);
+ for (const rule of TECH_TOKENS) {
+ assert.equal(game.techTokens![rule.id].spice, 0, `${rule.name} mints no income from a traitor battle`);
+ }
+ assert.equal(game.techTokens!.heighliners.triggeredTurn, game.turn,
+ 'The earlier genuine shipment accrual was already paid at its own phase end');
+ assert.equal(reactionPlayer(game, 'atreides').reserves,
+ reactionPlayer(f.beforeShipment, 'atreides').reserves, 'The Harass return is not a Revival grant');
+ assert.equal(reactionPlayer(game, 'atreides').tanks, 0);
+ assert.equal(reactionPlayer(game, 'atreides').leaders.find(disc => disc.id === f.identity)?.dead, false);
+});