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.
67 lines
2.0 KiB
TypeScript
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>
|
|
);
|
|
}
|