Implement Grumman collection and private stacked Terror choices

Add conserved token collection with four bank spice, owner-only stack selection, recovery proofs, controls and AI. Preserve unresolved removal and expansion release gates. Verified 3712 offline cases, types, lint, production build, 40 HTTP cases, three-seat desktop/mobile play, and unchanged 3081 preexisting saved games.
This commit is contained in:
Quintine committed 2026-09-10 08:38:06 +10:00
1 parent 28ba1618ba
commit 1db11a0187
27 files changed
+2451 -43

No files matched your search

+52
View File
@@ -459,6 +459,58 @@ export function MoritaniEntry({
);
}
// Stacked tokens are selected privately before any selected-kind lookup.
if (entry.stage === 'select') {
if (me.faction !== 'moritani') return null;
const selectionDisabled =
disabled ||
game.status !== 'playing' ||
!!game.phaseOpening ||
!!game.automaticContinuationPending;
return (
<section className="notice min-w-0" aria-labelledby={`${formId}-title`}>
<h3 id={`${formId}-title`}>Choose a Terror token</h3>
<p className="text-sm leading-6">
Choose one token in {territory(entry.territory).name} for this
arrival. Selecting a token keeps its face private; its reaction
choices follow. You may instead leave all tokens hidden and continue
the arrival.
</p>
<div className="flex min-w-0 flex-col gap-3">
{entry.candidates?.map((candidate) => (
<div key={candidate.token} className="space-y-2">
<Button
className={controlClass}
disabled={selectionDisabled}
onClick={() =>
act({ type: 'decision', token: candidate.token })
}
>
Select {TERROR_DEFINITIONS[candidate.kind].name}
</Button>
{candidate.revealBlocked && (
<p className="text-sm leading-6">{candidate.revealBlocked}</p>
)}
{candidate.canOfferAlliance && (
<p className="text-sm leading-6">
An alliance offer is available after selection.
</p>
)}
</div>
))}
<Button
variant="outline"
className={controlClass}
disabled={selectionDisabled}
onClick={() => act({ type: 'decision', decline: true })}
>
Leave all tokens hidden
</Button>
</div>
</section>
);
}
if (me.faction !== 'moritani' || !('kind' in entry) || !entry.kind)
return null;