Connect bounded arrival overlap and Fremen Nexus movement Betrayal

This commit is contained in:
Quintine committed 2026-09-29 18:01:14 +10:00
1 parent 8ddd138e87
commit b2603f6715
26 files changed
+1644 -118

No files matched your search

+17 -4
View File
@@ -33,6 +33,7 @@ import { NexusChoamBetrayal } from './nexus-choam-betrayal';
import { NexusMoritaniBetrayal } from './nexus-moritani-betrayal';
import { buildRevision, buildRevisionLabel } from '@/lib/build-revision';
import { NexusEcazBetrayal } from './nexus-ecaz-betrayal';
import { NexusFremenBetrayal } from './nexus-fremen-betrayal';
import { NexusEcazInquiry } from './nexus-ecaz-inquiry';
import { NexusEcazDuke } from './nexus-ecaz-duke';
import { NexusTleilaxu } from './nexus-tleilaxu';
@@ -1050,6 +1051,18 @@ export function GameTable({
<EcazLoyaltyCard loyalty={g.ecazLoyalty} />
<MoritaniTerrorSupply game={g} />
<MoritaniExtortionNotice game={g} />
{g.arrivalOverlap && (
<section className="notice" aria-label="Ambassador and Terror response order">
<p>
{g.players.find((player) => player.id === g.arrivalOverlap!.entrant)?.name ?? 'The entrant'}
{' '}entered {territory(g.arrivalOverlap.territory).name}. Ecaz Ambassador
and Moritani Terror resolve in storm order:{' '}
{g.arrivalOverlap.first === 'ambassador' ? 'Ecaz, then Moritani' : 'Moritani, then Ecaz'}.
{' '}{g.arrivalOverlap.active === 'ambassador' ? 'Ecaz' : 'Moritani'} decides now;
the original entry is not repeated.
</p>
</section>
)}
<MoritaniAssassinateHistory game={g} />
<AmbassadorSupply game={g} />
<AmbassadorInsights key={`${g.code}-${g.me}`} game={g} />
@@ -1607,6 +1620,7 @@ export function GameTable({
<NexusChoamBetrayal game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
<NexusMoritaniBetrayal game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
<NexusEcazBetrayal game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
<NexusFremenBetrayal game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
<NexusEcazInquiry game={g} act={act} busy={transportBusy || !!g.roomControl?.paused || !!g.roomControl?.closed || !!me.autopilot} />
<NexusEcazDuke game={g} act={act} busy={busy} />
<NexusTleilaxu game={g} act={act} busy={transportBusy || (!!g.roomControl?.paused || !!g.roomControl?.closed) || !!me.autopilot} />
@@ -4044,10 +4058,9 @@ export function GameTable({
me.faction === 'fremen' &&
me.fremenMovementBlocked && (
<p className="notice">
Karama limited this move to one territory. Your forces have
not moved yet. Available ornithopters still allow their
normal range; a later extra move can use your two-territory
advantage again.
{me.fremenNexusMovementBlocked
? 'Fremen Nexus Betrayal suppresses your two-territory movement advantage for this turn. Available ornithopters still use their normal range.'
: 'Karama limited this move to one territory. Your forces have not moved yet. Available ornithopters still allow their normal range; a later extra move can use your two-territory advantage again.'}
</p>
)}
{g.phase === 5 && me.faction === 'ixians' && (
+27
View File
@@ -0,0 +1,27 @@
'use client';
import type { Action, GameView } from '@/game/engine';
import { nexusCardReference } from '@/game/nexus-card-reference';
import { fremenBetrayalAction } from '@/game/nexus-fremen-betrayal-options';
import { Button } from './ui/button';
type Props = { game: GameView; act: (action: Action) => void; busy: boolean };
export function NexusFremenBetrayal({ game, act, busy }: Props) {
const offer = game.nexusFremenBetrayal;
if (!offer || game.nexusCards?.card !== 'fremen') return null;
const action = fremenBetrayalAction(game);
const fremen = game.players.find(player => player.id === offer.target);
return (
<section aria-label="Fremen Nexus Betrayal" className="space-y-3 rounded-lg border border-[#a88b60]/50 p-3">
<h3>Fremen Nexus · Betrayal</h3>
<p>{nexusCardReference('fremen').betrayal}</p>
{fremen && <p className="fine">This bounded use suppresses {fremen.name}’s native two-territory movement advantage for the rest of this turn. Independent ornithopters remain available. Suppressing worm riding is not yet connected.</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); }}>
Use Betrayal: suppress Fremen movement range
</Button>
</section>
);
}
+1 -1
View File
@@ -57,7 +57,7 @@ export function OrnithopterMovement({
if (Object.entries(selectedForces).some(([from, count]) => count > 0 &&
!botGroundMoveAllowed(game, me, from,
location(String(move.territory), Number(move.sector)), 0)))
reason = 'Karama removed this group’s two-territory advantage. Choose a reachable destination within the remaining normal range.';
reason = 'The Fremen two-territory advantage is unavailable. Choose a reachable destination within the remaining normal range.';
}
const physicalSource =
typeof move?.from === 'string'