'use client'; import { useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { territory } from '@/game/board'; import { DISCOVERY_TOKEN_BY_ID, type DiscoveryTokenFace } from '@/game/discoveries'; import { discoveryAction, discoveryDiscardAction } from '@/game/discovery-options'; import { CardInspector } from './card-inspector'; import { Button } from './ui/button'; type Props = { game: GameView; act: (action: Action) => void; busy: boolean }; /** Authored gameplay guidance from the component contract, never hidden state. */ const tokenRules: Record = { 'jacurutu-sietch': 'Jacurutu Sietch counts as a normal stronghold. After winning a battle here, receive one spice for each opposing undialed force sent to the Tanks.', cistern: 'During Spice Collection, an occupant of Cistern receives two spice from the bank.', 'ecological-testing-station': 'An occupant may increase or decrease storm movement by one during the Storm phase. This ability does not change movement set by Weather Control.', shrine: 'An occupant of Shrine may use a Truthtrance card as Karama, or a Karama card as Truthtrance.', 'orgiz-processing-station': 'During Spice Collection, an occupant takes one spice from each spice blow that is collected.', 'treachery-card-stash': 'Receive one Treachery Card, then remove this token. If your hand is already full, take the new card first and then choose any card in your hand to discard, including the new card.', 'spice-stash': 'Receive seven spice from the bank, then remove this token.', ornithopter: 'Take this token. On a later turn, you may spend it to move up to three territories instead of your usual allowance for one movement action. Remove the token after that action.', }; function DiscoveryRules({ face }: { face: DiscoveryTokenFace }) { const definition = DISCOVERY_TOKEN_BY_ID[face]; return (
Read {definition.name} rules

{tokenRules[face]}

{definition.kind === 'location' && ( <>

This location is a separate territory inside the surrounding territory. Revealing it leaves it empty. Normal ground movement enters the surrounding territory before entering the location; shipment uses stronghold prices.

At most two factions may occupy the location. Forces inside are protected from storms and sandworms.

At the start of the turn after this location is revealed, before the storm and the mobile stronghold move, factions in the surrounding territory may move some or all of their non-advisor forces inside.

)}
); } export function DiscoveryPanel({ game, act, busy }: Props) { const offer = game.discoveries; if (!offer) return null; const disabled = busy || !!game.players.find(player => player.id === game.me)?.autopilot; return (

Discoveries

{offer.supplyCount} tokens remain in the supply.

During Spice Collection, non-advisor forces in a token’s surrounding territory can inspect it privately and choose whether to reveal it. Fremen can always see placed Hiereg faces; the Guild can always see placed Smuggler faces.

{offer.blocked &&

{offer.blocked}

} {!offer.tokens.length &&

No Discovery tokens have been placed.

}
{offer.tokens.map(token => { const definition = token.face ? DISCOVERY_TOKEN_BY_ID[token.face] : null; const typeName = token.type === 'hiereg' ? 'Hiereg' : 'Smuggler'; const name = definition?.name ?? `${typeName} discovery`; const inspect = discoveryAction(game, token.id, false); const reveal = discoveryAction(game, token.id, true); const owner = game.players.find(player => player.id === token.owner); const status = token.status === 'removed' ? 'Resolved and removed' : token.status === 'carried' ? `Carried by ${token.owner === game.me ? 'you' : owner?.name ?? 'another faction'}` : token.revealedTurn === null ? token.face ? 'Face down · known to you' : 'Face down · unknown to you' : `Revealed on turn ${token.revealedTurn}`; return (

{typeName} token

{name}

{status}

{token.territory &&

In {territory(token.territory).name} · sector {token.sector}

} {token.face && } {(inspect || reveal) && (
{inspect && } {reveal && }
)}
); })}

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. Orgiz, mixed-force Jacurutu rewards and contested Cistern/Testing Station benefits remain pending.

); } export function DiscoveryDiscardDecision({ game, act, busy }: Props) { const [selected, select] = useState(''); const decision = game.decision; if (decision?.kind !== 'discoveryDiscard' || decision.player !== game.me) return null; const me = game.players.find(player => player.id === game.me); const hand = me?.hand ?? []; const card = hand.find(held => held.id === selected) ?? hand.find(held => held.kind === 'worthless') ?? hand[0]; const action = card ? discoveryDiscardAction(game, card.id) : null; const disabled = busy || !!me?.autopilot; return (

Treachery Card Stash · choose a discard

The new card is already in your hand. Choose any one of your cards to discard, including the card you just received.

{card && }
); }