Files
Dune/game/truthtrance-hand-inventory.ts
Quintine 7454456755 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.
2026-09-13 11:29:05 +10:00

89 lines
2.6 KiB
TypeScript

import type { Card } from './cards';
import {
CARD_CATEGORIES,
printedCardCategory,
type CardCategory,
} from './card-category';
/** A current custody question, not a promise to retain cards or use them. */
export type HandInventoryFact = {
kind: 'handInventory';
category: 'all' | CardCategory;
compare: 'eq' | 'gte' | 'lte';
value: number;
};
export const HAND_INVENTORY_LABELS: Record<
HandInventoryFact['category'],
string
> = {
all: 'All cards in hand',
weapon: 'Weapons · primary role',
defense: 'Defenses · primary role',
worthless: 'Worthless cards',
hero: 'Cheap Heroes and Heroines',
special: 'Special cards without a primary battle role',
};
export function parseHandInventoryFact(
v: Record<string, unknown>,
): HandInventoryFact {
if (
v.kind !== 'handInventory' ||
(v.category !== 'all' &&
!CARD_CATEGORIES.includes(v.category as CardCategory))
)
throw new Error('Choose all held cards or a known primary card role.');
if (v.compare !== 'eq' && v.compare !== 'gte' && v.compare !== 'lte')
throw new Error('Compare held cards using exactly, at least, or at most.');
if (
typeof v.value !== 'number' ||
!Number.isSafeInteger(v.value) ||
v.value < 0
)
throw new Error(
'Choose a nonnegative whole card count within the safe integer range.',
);
return {
kind: 'handInventory',
category: v.category as HandInventoryFact['category'],
compare: v.compare,
value: v.value,
};
}
export function handInventoryFactMatches(
hand: readonly Card[],
fact: HandInventoryFact,
): boolean {
const count =
fact.category === 'all'
? hand.length
: hand.filter((card) => printedCardCategory(card) === fact.category)
.length;
return fact.compare === 'eq'
? count === fact.value
: fact.compare === 'gte'
? count >= fact.value
: count <= fact.value;
}
export function handInventoryFactText(fact: HandInventoryFact): string {
const comparison =
fact.compare === 'eq'
? 'exactly'
: fact.compare === 'gte'
? 'at least'
: 'at most';
const cards = fact.value === 1 ? 'card' : 'cards';
const description: Record<HandInventoryFact['category'], string> = {
all: `${cards} in your hand`,
weapon: `${cards} whose primary role is a weapon`,
defense: `${cards} whose primary role is a defense`,
worthless: `Worthless ${cards}`,
hero: `Cheap Hero or Cheap Heroine ${cards}`,
special: `Special ${cards} without a primary battle role`,
};
return `you currently hold ${comparison} ${fact.value} ${description[fact.category]}`;
}