Files
Dune/components/choam-cash-in.tsx
T
Quintine f0d3d9c464 Prototype Jacurutu income, Testing Station and Shrine effects
Connect automatic unambiguous Jacurutu physical-force income, a sole-occupant
Testing Station storm choice and Shrine reciprocal physical card roles to
engine actions, player controls, legal AI and saved continuation. Preserve
Weather Control exclusion, exact card/force custody, private hand boundaries
and committed effects after restart. Shared role checks cover existing ordinary
and special Karama paths; reserved cards cannot enter another pending use.

Keep Orgiz, mixed-force Jacurutu allocation and contested Cistern/Testing
Station benefits explicitly pending. These are bounded prototypes; complete
Discovery/Advanced/expansion and publication gates remain closed.

Validation: types, lint and 4,345 offline tests passed (32 added), production
build passed, and 40 HTTP tests passed. Independent cross-reviews repaired
reserved-card deadlock, physical-card selection, alternate-role permission,
storm source/token binding and public-entitlement bot ordering. The first broad
run found five failures; focused repairs preceded the stable repeated check.

Browser A5EJQ2W4 used genuine Discovery setup/reveal/free entry with targeted QA
positioning. A human increased storm movement from two to three sectors,
moving sector 6 to 9. Refresh restored station forces and the private Snooper.
This was targeted control/recovery evidence, not an unmodified full game.
All 187 opening saved rooms remained unchanged; 31 QA/HTTP rooms were added
(218 total). Reused the running server; no restart or save reset.

Source-bound report:
/home/quintine/.local/share/dune/checkpoints/20260913-discovery-location-effects/verification-final/report.json
Source fingerprint: beecb85ab7ec39319115389c115434d161591e8549802c542a6343a0fc19bea6
Durations: check 140540ms; build 6741ms; integration 24956ms.
Aggregate goal counter 63,116,444 to 64,295,091 at final verification:
1,178,647 tokens before final Git/push overhead for three bounded functions.
Standing authorization: Push all from now on.
2026-09-13 14:50:09 +10:00

67 lines
2.0 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import type { Action, GameView } from '@/game/engine';
export function ChoamCashIn({
game: g,
act,
busy,
}: {
game: GameView;
act: (a: Action) => void;
busy: boolean;
}) {
const [activation, setActivation] = useState('');
const [selected, setSelected] = useState<string[]>([]);
const cash = g.choamCashIn;
if (!cash?.karamas.length) return null;
const card = cash.karamas.find((c) => c.id === activation) ?? cash.karamas[0];
const choices = cash.cards.filter((c) => c.id !== card.id);
const cards = selected.filter((id) => choices.some((c) => c.id === id));
return (
<section className="notice">
<h3>CHOAM special Karama</h3>
<p>
Once per game, spend Karama and discard other cards for three spice
each. Committed battle cards are excluded. The activating Karama earns
no spice.
</p>
<label>
Karama to spend{' '}
<select value={card.id} onChange={(e) => setActivation(e.target.value)}>
{cash.karamas.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
</label>
{choices.map((c) => (
<label key={c.id}>
<input
type="checkbox"
checked={cards.includes(c.id)}
onChange={(e) =>
setSelected(
e.target.checked
? [...selected, c.id]
: selected.filter((id) => id !== c.id),
)
}
/>
{c.name} · 3 spice
</label>
))}
<Button
className="game-action"
disabled={busy || !!g.truthtrance || cards.length === 0}
onClick={() =>
act({ type: 'card', mode: 'special', card: card.id, cards })
}
>
Spend Karama · gain {cards.length * 3} spice
</Button>
</section>
);
}