Connect physical Jacurutu allocation and cumulative station choices

This commit is contained in:
Quintine committed 2026-10-14 14:49:07 +11:00
1 parent 373badd560
commit 554f2bf9e9
22 files changed
+1943 -129

No files matched your search

+1 -1
View File
@@ -96,7 +96,7 @@ export function DiscoveryPanel({ game, act, busy }: Props) {
</div>
<p className="fine">Inspection and reveal are optional. Next-turn free entry, sole-occupant Cistern income and later-turn Ornithopter movement are available in the development prototype. Jacurutu income, Testing Station storm choices and Shrine card conversion are prototyped. {game.advanced
? 'A sole Orgiz occupant takes one spice for each territory where one rival collected board spice.'
: 'Basic keeps the provisional one-spice transfer per positive board deposit collected by one rival.'} Contested Orgiz, different or shared collectors, mixed-force Jacurutu rewards and contested Cistern/Testing Station benefits remain pending; uncertain transfers do not block ordinary Collection.</p>
: 'Basic keeps the provisional one-spice transfer per positive board deposit collected by one rival.'} Mixed physical Jacurutu dials now use a defeated-player choice before settlement. Testing Station occupants each choose once in original storm order, cumulatively. These ordering/allocation interpretations are provisional. Contested Cistern/Orgiz and different or shared collectors remain pending; uncertain transfers do not block ordinary Collection.</p>
</section>
);
}
+4 -4
View File
@@ -20,11 +20,11 @@ export function DiscoveryStormDecision({
return (
<section aria-label="Ecological Testing Station" className="space-y-3">
<p>
Your forces occupy Ecological Testing Station. The storm will move{' '}
{offer.base} sectors. You may decrease or increase this by one, or keep
the ordinary movement.
Your forces occupy Ecological Testing Station. The storm currently
moves {offer.base} sectors. You may decrease or increase this by one, or
keep the current movement.
</p>
<p className="fine">This choice cannot change Weather Control.</p>
<p className="fine">Prototype ordering: each occupying faction chooses once in storm order; adjustments are cumulative. This choice cannot change Weather Control.</p>
<div className="flex flex-wrap gap-2">
{offer.options.map((option) => {
const action = actions.find(
+6
View File
@@ -34,6 +34,7 @@ import { GreatMakerDecision } from './great-maker';
import { DiscoveryPanel, DiscoveryDiscardDecision } from './discoveries';
import { DiscoveryEntryDecision } from './discovery-entry';
import { DiscoveryStormDecision } from './discovery-storm';
import { JacurutuDial } from './jacurutu-dial';
import { NexusChoamTrade, NexusChoamInspectionDecision, NexusChoamInspectionInsight } from './nexus-choam-trade';
import { NexusChoamBetrayal } from './nexus-choam-betrayal';
import { NexusMoritaniBetrayal } from './nexus-moritani-betrayal';
@@ -2616,6 +2617,8 @@ export function GameTable({
? 'Choose Smuggler collection sectors'
: g.decision.kind === 'sandmasterVictorySpice'
? 'Choose Sandmaster victory spice sector'
: g.decision.kind === 'jacurutuDial'
? 'Identify your Jacurutu physical dial'
: g.decision.kind === 'harassWithdraw'
? 'Choose undialed forces to return'
: g.decision.kind === 'diplomatRetreat'
@@ -3072,6 +3075,9 @@ export function GameTable({
<SmugglerBattleCollection key={g.decision.event} offer={g.decision} act={act} busy={busy} />
) : g.decision.kind === 'sandmasterVictorySpice' ? (
<SandmasterVictorySpice offer={g.decision} act={act} busy={busy} />
) : g.decision.kind === 'jacurutuDial' && g.jacurutuDial ? (
<JacurutuDial offer={g.jacurutuDial}
onChoose={allocation => act({type:'decision',event:g.jacurutuDial!.event,allocation})} busy={busy} />
) : g.decision.kind === 'harassWithdraw' ? (
<>
<HarassWithdrawChoice key={g.decision.event} game={g} act={act} busy={busy} />
+66
View File
@@ -0,0 +1,66 @@
'use client';
import {
JACURUTU_BATTLE_INCOME_RULE,
type JacurutuDialChoice,
type JacurutuDialOffer,
} from '@/game/discovery-battle';
import { Button } from './ui/button';
/** Concrete physical reading of one legal allocation of the revealed dial. */
export function jacurutuDialChoiceLabel(choice: JacurutuDialChoice): string {
const { normal, elite, paidNormal, paidElite } = choice.dialed;
const counters = [
`${normal} ordinary counter${normal === 1 ? '' : 's'} (${paidNormal} paid)`,
`${elite} elite counter${elite === 1 ? '' : 's'} (${paidElite} paid)`,
].join(' · ');
return `${counters} — ${
choice.amount === 0
? 'no spice from the bank'
: `${choice.amount} spice from the bank`
}`;
}
/**
* The defeated player's own allocation of their revealed dial. Only the legal
* physical readings reported for this battle are offered; the numeric wheel
* value alone never decides the counters.
*/
export function JacurutuDial({
offer,
onChoose,
busy,
}: {
offer: JacurutuDialOffer;
onChoose: (allocation: number) => void;
busy: boolean;
}) {
if (!offer.choices.length) return null;
return (
<section
className="flex flex-col gap-3"
aria-label="Jacurutu Sietch dial allocation"
>
<p>
You lost this battle in Jacurutu Sietch. Choose which counters your
revealed dial actually committed; the winner collects one bank spice
for each opposing undialed force sent to the Tanks.
</p>
<p className="fine">{JACURUTU_BATTLE_INCOME_RULE}</p>
<p className="fine">Prototype interpretation: the defeated player identifies the physical dial allocation before battle settlement.</p>
<div className="flex flex-wrap gap-2">
{offer.choices.map((choice, allocation) => (
<Button
key={`${choice.dialed.normal}:${choice.dialed.elite}:${choice.dialed.paidNormal}:${choice.dialed.paidElite}`}
className="game-action min-h-11 whitespace-normal"
variant={choice.amount === 0 ? 'outline' : 'default'}
disabled={busy}
onClick={() => !busy && onChoose(allocation)}
>
{jacurutuDialChoiceLabel(choice)}
</Button>
))}
</div>
</section>
);
}