From ae66668e2e7835c117e2fec51fd6817c47f71d63 Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:39:23 +1000 Subject: [PATCH] Implement Moritani Nexus placement and preserve later Terror custody --- components/moritani-terror.tsx | 49 ++-- components/nexus-cards.tsx | 2 +- docs/COMPONENT_INVENTORY.md | 2 + docs/IMPLEMENTATION_STATUS.md | 2 + docs/NEXUS_ECAZ_RULES.md | 49 ++++ docs/NEXUS_MORITANI_RULES.md | 54 +++++ docs/NEXUS_MORITANI_RUNTIME.md | 39 +++ game/bots.ts | 3 + game/engine.ts | 157 ++++++++++-- game/grumman-collection.ts | 5 +- game/moritani-alliance-cancellation.ts | 7 +- game/moritani-terror.ts | 8 +- game/nexus-moritani-options.ts | 116 +++++++++ game/nexus-moritani.ts | 194 +++++++++++++++ game/placement-cancellation.ts | 7 +- game/reference.ts | 14 +- game/terror-entry-receipt.ts | 15 +- game/terror-location.ts | 32 +++ tests/fixture-nexus-moritani.ts | 274 +++++++++++++++++++++ tests/moritani-entry.test.ts | 21 +- tests/nexus-card-controls.test.ts | 2 +- tests/nexus-moritani-audit.test.ts | 213 ++++++++++++++++ tests/nexus-moritani-bots.test.ts | 58 +++++ tests/nexus-moritani-controls.test.ts | 166 +++++++++++++ tests/nexus-moritani-engine.test.ts | 207 ++++++++++++++++ tests/nexus-moritani-recovery.test.ts | 322 +++++++++++++++++++++++++ tests/nexus-moritani.test.ts | 263 ++++++++++++++++++++ 27 files changed, 2208 insertions(+), 73 deletions(-) create mode 100644 docs/NEXUS_ECAZ_RULES.md create mode 100644 docs/NEXUS_MORITANI_RULES.md create mode 100644 docs/NEXUS_MORITANI_RUNTIME.md create mode 100644 game/nexus-moritani-options.ts create mode 100644 game/nexus-moritani.ts create mode 100644 game/terror-location.ts create mode 100644 tests/fixture-nexus-moritani.ts create mode 100644 tests/nexus-moritani-audit.test.ts create mode 100644 tests/nexus-moritani-bots.test.ts create mode 100644 tests/nexus-moritani-controls.test.ts create mode 100644 tests/nexus-moritani-engine.test.ts create mode 100644 tests/nexus-moritani-recovery.test.ts create mode 100644 tests/nexus-moritani.test.ts diff --git a/components/moritani-terror.tsx b/components/moritani-terror.tsx index 04b48905..2d7af44a 100644 --- a/components/moritani-terror.tsx +++ b/components/moritani-terror.tsx @@ -15,12 +15,11 @@ import { } from '@/components/ui/dialog'; import type { Action, GameView } from '@/game/engine'; import { territory } from '@/game/board'; +import { moritaniPlacementChoices, nexusMoritaniAction } from '@/game/nexus-moritani-options'; import { TERROR_COMMON_GAMEPLAY, TERROR_DEFINITIONS, - TERROR_STRONGHOLDS, type TerrorKind, - type TerrorToken, } from '@/game/moritani-terror'; type ProjectedToken = NonNullable['tokens'][number]; @@ -266,6 +265,7 @@ export function MoritaniPlacement({ busy: boolean; }) { const formId = useId(); + const [useNexus, setUseNexus] = useState(false); const [selectedToken, setSelectedToken] = useState(''); const [selectedTerritory, setSelectedTerritory] = useState(''); const decision = game.decision; @@ -276,34 +276,32 @@ export function MoritaniPlacement({ !game.moritaniTerror ) return null; - const tokens = game.moritaniTerror.tokens.filter( - (token): token is TerrorToken => - 'kind' in token && - (token.status === 'available' || token.status === 'placed'), - ); - const token = - tokens.find((candidate) => candidate.id === selectedToken) ?? tokens[0]; - const destinations = TERROR_STRONGHOLDS.filter( - (to) => - to !== token?.location && - !game.moritaniTerror!.tokens.some( - (candidate) => - candidate.status === 'placed' && candidate.location === to, - ), - ); + const nexus = useNexus && !!game.nexusMoritani; + const { tokens, token, destinations } = moritaniPlacementChoices(game, nexus, selectedToken); const destination = destinations.includes(selectedTerritory) ? selectedTerritory : destinations[0]; const waiting = Boolean(game.response); const disabled = busy || waiting; + const nexusAction = nexus && token && destination ? nexusMoritaniAction(game, game.nexusMoritani!.event, token.id, destination) : null; return (

Place or relocate Terror

- Choose one token and a stronghold without a Terror token for this Mentat - Pause. Storm does not prevent placement. Choose privately; the token’s - face stays hidden from opponents. + {nexus + ? 'Choose one available supply token and a projected Arrakis territory for this Mentat Pause. Existing Terror tokens may remain there.' + : 'Choose one token and a stronghold without a Terror token for this Mentat Pause.'}{' '} + Storm does not prevent placement. Choose privately; the token’s face stays hidden from opponents.

+ {game.nexusMoritani && <> + +

Cunning places one available supply token in an Arrakis territory, including one with Terror already present. It spends the Nexus card and this Mentat placement opportunity. Relocation, Grumman use, Homeworlds and the Hidden Mobile Stronghold are unavailable.

+ {game.nexusMoritani.blocked &&

{game.nexusMoritani.blocked}

} + }