Connect native Fremen Nexus effects across selected modules

Retain actual Cunning appearances, paid Karama and typed remote riding with optional Sandmaster collection and source-parent HMS entry. Preserve Betrayal movement sources and apply its declared worm restriction to Great Maker reserve riding without suppressing votes or casualties. Completed Maker history no longer blocks a later ordinary offer.

Validation: frozen 14-file86/86, types/lint/build, nine finite original-action/rendered-control programmes, four minimal legal policies. Discovery destination proof remains focused quotes pending actual arrival; full combinations, assurance and deployed acceptance remain required. No saved-game, restart or deployment operation.
This commit is contained in:
Quintine committed 2026-10-18 11:35:59 +11:00
1 parent bb9d292c4c
commit b4157aaa9b
20 files changed
+1349 -41

No files matched your search

+18 -4
View File
@@ -101,7 +101,8 @@ import { PlanetologistMovement } from './planetologist-movement';
import { selectedOriginElites, advisorFlipMovementBlocked } from '@/game/planetologist-movement';
import { SandmasterMovement } from './sandmaster-movement';
import { SandmasterWormChoice } from './sandmaster-worm';
import { sandmasterWormCollection } from '@/game/sandmaster-worm';
import { sandmasterWormCollection, sandmasterCunningCollection } from '@/game/sandmaster-worm';
import { fremenCunningPublicDestinationBlock } from '@/game/nexus-fremen-cunning';
import { ResidualPoison, BattleLeaderOpportunity } from './residual-poison';
import { PortableSnooper } from './portable-snooper';
import {
@@ -536,12 +537,18 @@ export function GameTable({
const [rideForces, setRideForces] = useState<Record<string, number>>({});
const [collectSandmasterRide, setCollectSandmasterRide] = useState(true);
const [sandmasterRidePile, setSandmasterRidePile] = useState<{ target: string; key: string } | null>(null);
const sandmasterRideTarget = JSON.stringify([g.turn, g.decision?.kind === 'wormRide' ? g.decision : null, selected, sector]);
const baseSandmasterRide = sandmasterWormCollection(g, me.id, selected, sector);
const sandmasterRideTarget = JSON.stringify([g.turn,
g.decision?.kind === 'wormRide' || g.decision?.kind === 'nexusFremenCunningRide' ? g.decision : null,
source, selected, sector]);
const baseSandmasterRide = g.decision?.kind === 'nexusFremenCunningRide'
? sandmasterCunningCollection(g, me.id, source, selected, sector)
: sandmasterWormCollection(g, me.id, selected, sector);
const selectedRidePile = sandmasterRidePile?.target === sandmasterRideTarget
? baseSandmasterRide?.piles.find(pile => pile.key === sandmasterRidePile.key) : null;
const sandmasterRide = selectedRidePile && baseSandmasterRide
? { ...baseSandmasterRide, key: selectedRidePile.key, before: selectedRidePile.before } : baseSandmasterRide;
const cunningDestinationBlocked = g.decision?.kind === 'nexusFremenCunningRide' && source
? fremenCunningPublicDestinationBlock(g, me.id, source, { territory: selected, sector }) : null;
const [exchangeCards, setExchangeCards] = useState<Record<string, boolean>>(
{},
);
@@ -3828,16 +3835,23 @@ export function GameTable({
)}
</div>)}
{destination}
{cunningDestinationBlocked && <p className="notice">{cunningDestinationBlocked}</p>}
<SandmasterWormChoice quote={sandmasterRide} collect={collectSandmasterRide}
onChange={setCollectSandmasterRide}
onPileChange={key => setSandmasterRidePile({ target: sandmasterRideTarget, key })} />
{actionButton('Ride remotely', {
type: 'decision', event: g.decision.event, accept: true,
source, territory: selected, sector,
...(sandmasterRide?.blocked === null
? { sandmasterCollect: collectSandmasterRide,
...(collectSandmasterRide ? { sandmasterPile: sandmasterRide.key } : {}) } : {}),
forces: Object.fromEntries(Object.entries(rideForces)
.filter(([key, count]) => count > 0 && splitLocation(key).territory === source)
.map(([key, count]) => [key, {
normal: count - (eliteForces[key] ?? 0),
elite: eliteForces[key] ?? 0,
}])),
}, !source || !Object.entries(rideForces).some(([key, count]) =>
}, !source || !!cunningDestinationBlocked || !Object.entries(rideForces).some(([key, count]) =>
count > 0 && splitLocation(key).territory === source))}
{actionButton('Decline remote ride', {
type: 'decision', event: g.decision.event, accept: false,
+4
View File
@@ -21,6 +21,10 @@ export function NexusFremenBetrayal({ game, act, busy }: Props) {
? `Declare before the first spice blow: ${fremen.name} cannot take ordinary or Cunning worm rides this turn. Worm destruction, survival, protection and Nexus still resolve.`
: `This use suppresses ${fremen.name}’s native two-territory movement advantage for the rest of this turn. Independent ornithopters remain available.`}
</p>}
{offer.mode === 'worm' && game.discoveries && <p className="fine">
Provisional Great Maker interpretation: its reserve ride is also prevented;
the original vote, worm effects and Nexus continue.
</p>}
{offer.blocked && <p className="notice">{offer.blocked}</p>}
<Button className="game-action min-h-11 whitespace-normal" disabled={busy || !action}
onClick={() => { if (!busy && action) act(action); }}>