Add verified Truthtrance hand inventory questions
Players can ask total-hand and primary-role card-count comparisons using the existing count controls and compound facts. Component presentation shares the same default-role classification, including canonical Richese cards, Ix alternate uses, and Ecaz slot-only specials. Only the respondent receives the verified aggregate answer; current facts do not impose future custody promises. Eight new tests cover all 60 catalog cards, real engine actions, malformed input, private worlds, all four bots, JSON continuation, and a legal custody transfer. Expanded production-room SQLite cases cover restored total/category/compound facts, suspended auction state, and one winning concurrent answer/discard. Independent review's two copy issues were fixed before broad verification. Validation: types, lint and 4,200 offline tests passed; production build passed; 40 HTTP tests passed. One stable broad run (check 123036 ms, build 6505 ms, integration 23467 ms). Browser room LC3E55GY verified combined role counts, total hand size, invalid-input feedback and refresh at 390px phone width. All 33 pre-integration rooms preserved unchanged; HTTP checks added 30 rooms. No complete-mode, full-game or publication gate opened. Private report: 20260913-hand-inventory-final/report.json Source fingerprint: c6a69a9e69941cdd1e5734d92afedbf10d31fda36c91d26ca7e15f4fb7c59b18 Efficiency pilot milestone 3: aggregate counter 58,695,053 to 59,144,653 at completed verification = 449,600 tokens, including context/coordination overhead. Two bounded workhorse-model helpers, reused UI and SQLite fixtures, one browser room. Counter is not monetary cost and this differently sized batch does not establish a savings percentage. Final Git/push overhead follows this sample.
This commit is contained in:
1 parent
32791ed5e2
commit
7454456755
14 files changed
+980
-151
No files matched your search
@@ -4,29 +4,56 @@ import { useId } from 'react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { TRUTH_CARD_NAMES } from '@/game/truthtrance';
|
||||
import type { CardCountFact } from '@/game/truthtrance-card-count';
|
||||
import {
|
||||
HAND_INVENTORY_LABELS,
|
||||
type HandInventoryFact,
|
||||
} from '@/game/truthtrance-hand-inventory';
|
||||
|
||||
export function CardCountFields({
|
||||
export function CardCountFields<T extends CardCountFact | HandInventoryFact>({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: CardCountFact;
|
||||
onChange: (fact: CardCountFact) => void;
|
||||
value: T;
|
||||
onChange: (fact: T) => void;
|
||||
}) {
|
||||
const id = useId();
|
||||
const invalid = !Number.isSafeInteger(value.value) || value.value < 0;
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
Card name
|
||||
<select
|
||||
value={value.name}
|
||||
onChange={(event) => onChange({ ...value, name: event.target.value })}
|
||||
>
|
||||
{TRUTH_CARD_NAMES.map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{value.kind === 'handCount' ? (
|
||||
<label>
|
||||
Card name
|
||||
<select
|
||||
value={value.name}
|
||||
onChange={(event) =>
|
||||
onChange({ ...value, name: event.target.value })
|
||||
}
|
||||
>
|
||||
{TRUTH_CARD_NAMES.map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<label>
|
||||
Cards to count
|
||||
<select
|
||||
value={value.category}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...value,
|
||||
category: event.target.value as HandInventoryFact['category'],
|
||||
})
|
||||
}
|
||||
>
|
||||
{Object.entries(HAND_INVENTORY_LABELS).map(([category, label]) => (
|
||||
<option key={category} value={category}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
<label>
|
||||
Compare card count
|
||||
<select
|
||||
@@ -44,7 +71,7 @@ export function CardCountFields({
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor={`${id}-count`}>
|
||||
Number of copies
|
||||
{value.kind === 'handCount' ? 'Number of copies' : 'Number of cards'}
|
||||
<Input
|
||||
id={`${id}-count`}
|
||||
type="number"
|
||||
@@ -61,10 +88,23 @@ export function CardCountFields({
|
||||
/>
|
||||
</label>
|
||||
<p id={`${id}-help`} className="fine">
|
||||
Counts copies of this exact named card in the player’s hand now. Cards
|
||||
in the deck, discard pile, or a separate cache do not count. The answer
|
||||
reveals only whether the comparison is true.
|
||||
{value.kind === 'handCount'
|
||||
? 'Counts copies of this exact named card'
|
||||
: value.category === 'all'
|
||||
? 'Counts all physical cards'
|
||||
: 'Counts physical cards of the selected primary role'}{' '}
|
||||
in the player’s hand now. Cards in the deck, discard pile, or a separate
|
||||
cache do not count. The answer reveals only whether the comparison is
|
||||
true.
|
||||
</p>
|
||||
{value.kind === 'handInventory' && value.category !== 'all' && (
|
||||
<p className="fine">
|
||||
Primary roles follow the cards: Weirding Way is a weapon and Chemistry
|
||||
is a defense. Worthless cards stay separate, including for Bene
|
||||
Gesserit or CHOAM. Alternate uses and faction powers do not change
|
||||
this count.
|
||||
</p>
|
||||
)}
|
||||
{invalid && (
|
||||
<p id={`${id}-error`} className="notice" role="alert">
|
||||
{Number.isInteger(value.value) &&
|
||||
|
||||
Reference in new issue
Block a user