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) &&
|
||||
|
||||
+26
-12
@@ -62,7 +62,9 @@ export function Truthtrance({
|
||||
]);
|
||||
const factId = useId();
|
||||
const invalidSpice = (fact: TruthFact) =>
|
||||
(fact.kind === 'spice' || fact.kind === 'handCount') &&
|
||||
(fact.kind === 'spice' ||
|
||||
fact.kind === 'handCount' ||
|
||||
fact.kind === 'handInventory') &&
|
||||
(!Number.isSafeInteger(fact.value) || fact.value < 0);
|
||||
const invalidFact = clauses
|
||||
.slice(0, join === 'single' ? 1 : 2)
|
||||
@@ -357,9 +359,10 @@ export function Truthtrance({
|
||||
<p className="notice">
|
||||
Automatic enforcement currently supports the active
|
||||
player’s unused shipment in base Basic games and base
|
||||
Advanced games without Guild or optional modules. Select that player and finish pending
|
||||
decisions first. Earlier questions and other rules
|
||||
configurations remain unfinished.
|
||||
Advanced games without Guild or optional modules. Select
|
||||
that player and finish pending decisions first. Earlier
|
||||
questions and other rules configurations remain
|
||||
unfinished.
|
||||
</p>
|
||||
)}
|
||||
<p className="notice">
|
||||
@@ -442,16 +445,23 @@ export function Truthtrance({
|
||||
compare: 'gte',
|
||||
value: 2,
|
||||
}
|
||||
: e.target.value === 'spice'
|
||||
: e.target.value === 'handInventory'
|
||||
? {
|
||||
kind: 'spice',
|
||||
kind: 'handInventory',
|
||||
category: 'all',
|
||||
compare: 'gte',
|
||||
value: 6,
|
||||
}
|
||||
: {
|
||||
kind: 'traitor',
|
||||
leader: g.allLeaders[0].id,
|
||||
value: 2,
|
||||
}
|
||||
: e.target.value === 'spice'
|
||||
? {
|
||||
kind: 'spice',
|
||||
compare: 'gte',
|
||||
value: 6,
|
||||
}
|
||||
: {
|
||||
kind: 'traitor',
|
||||
leader: g.allLeaders[0].id,
|
||||
}
|
||||
: old,
|
||||
),
|
||||
)
|
||||
@@ -461,6 +471,9 @@ export function Truthtrance({
|
||||
<option value="handCount">
|
||||
Number of a named card
|
||||
</option>
|
||||
<option value="handInventory">
|
||||
Hand size or primary card role
|
||||
</option>
|
||||
<option value="traitor">Selected a traitor</option>
|
||||
<option value="spice">
|
||||
Current personal spice
|
||||
@@ -487,7 +500,8 @@ export function Truthtrance({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : c.kind === 'handCount' ? (
|
||||
) : c.kind === 'handCount' ||
|
||||
c.kind === 'handInventory' ? (
|
||||
<CardCountFields
|
||||
value={c}
|
||||
onChange={(next) =>
|
||||
|
||||
Reference in new issue
Block a user