Files
Dune/components/truthtrance-knowledge.tsx
T
Quintine 8906b41bda Connect Truthtrance questions about recorded predictions and storm knowledge
Add typed questions about the respondent's stored Bene Gesserit prediction faction/turn, active submitted storm dial and actually known native Advanced Fremen storm card. Use the same eligible private facts for authoritative validation and target-only answer projection. Preserve unknown/rephrase/save continuation, compound aggregate privacy and physical card custody. Future promises, freeform interpretation and strategic AI question selection remain incomplete; no mode or publication gate opens.

Connect four question controls with all numeric comparisons, canonical faction choices and shared input guards. All four AI profiles answer the owner-only verified scalar. Independent source/privacy review and integration checks fixed missing comparisons, parent selector mapping and invalid submission guards. Thirteen new cases and the 44-case focused union cover normal/unknown/dishonest answers, private views, JSON and authenticated SQLite restoration, interrupted storm-peek continuation, stale/duplicate/competing requests and corrupt saved predicates without writes.

Validation: types, lint, 4662 offline tests, production build and 43 HTTP tests passed with unchanged source. Two genuine-setup four-profile games completed without rejected actions: Basic 192 actions/five periodic JSON rounds and Advanced 518/fourteen. Exact Treachery/force/elite custody and all eight final restored seat views pass. Three explicitly scripted legal questions use naturally held cards, covering prediction faction, prediction turn and submitted storm dial. Forecast questioning is not exercised by these complete games; the samples do not measure autonomous AI asking strategy or strength.

The separate Advanced browser exercise uses real prediction and forecast progression, staging only physical Truthtrance acquisition by relocation. Invalid dial input is blocked; an unknown answer can be rephrased; both compound prediction clauses and the forecast comparison work. Refresh after submission and final resolution restores history unknown/Yes/Yes and the remaining Shield. Both physical Truthtrances are discarded once, unrelated resources remain unchanged and private projection passes. SQLite tests establish pending-answer restoration; the browser observation does not claim a paused pending answer after reconnect.

After the reported outage, all 767 opening games and original seat/recovery records were preserved (801 after HTTP checks). All 768 rooms and original records immediately before verification also remained unchanged. Database integrity and foreign keys passed. Reused the healthy server without restart, reset or recurring automation.

Verified source tree: 09daaac288cb91ea407f4573f853a59d307e12ed30d2eccd39e671a47336f40d
2026-09-14 01:34:47 +10:00

79 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useId } from 'react';
import { FACTIONS } from '@/game/catalog';
import type { TruthFact } from '@/game/truthtrance';
import { knowledgeFactInputError } from '@/game/truthtrance-knowledge';
import { Input } from '@/components/ui/input';
type KnowledgeFact = Extract<TruthFact, { kind: 'prediction' | 'stormDial' | 'stormForecast' }>;
type NumericKnowledgeFact = Exclude<KnowledgeFact, { kind: 'prediction'; field: 'faction' }>;
function isNumericKnowledgeFact(fact: KnowledgeFact): fact is NumericKnowledgeFact {
return fact.kind !== 'prediction' || fact.field === 'turn';
}
export function KnowledgeFactFields({
value,
onChange,
}: {
value: KnowledgeFact;
onChange: (fact: KnowledgeFact) => void;
}) {
const id = useId();
const isFaction = !isNumericKnowledgeFact(value);
const numericValue = isNumericKnowledgeFact(value) ? value : null;
const isTurn = numericValue?.kind === 'prediction' && numericValue.field === 'turn';
const min = isTurn ? 1 : numericValue?.kind === 'stormForecast' ? 1 : 0;
const max = isTurn ? 10 : numericValue?.kind === 'stormForecast' ? 6 : 20;
const invalid = knowledgeFactInputError(value);
return (
<>
{isFaction ? (
<label>
Predicted faction
<select
value={value.faction}
onChange={(event) => onChange({ ...value, faction: event.target.value as typeof value.faction })}
>
{FACTIONS.map((faction) => <option key={faction.id} value={faction.id}>{faction.name}</option>)}
</select>
</label>
) : numericValue && (
<>
{(isTurn || numericValue.kind === 'stormDial' || numericValue.kind === 'stormForecast') && (
<label>
Compare stored value
<select
value={numericValue.compare}
onChange={(event) => onChange({ ...numericValue, compare: event.target.value as typeof numericValue.compare })}
>
<option value="eq">Exactly</option><option value="gte">At least</option><option value="lte">At most</option>
</select>
</label>
)}
<label htmlFor={`${id}-value`}>
{numericValue.kind === 'stormDial' ? 'Storm dial value' : numericValue.kind === 'stormForecast' ? 'Storm forecast sector' : 'Turn number'}
<Input
id={`${id}-value`}
type="number"
min={min}
max={max}
step={1}
required
value={Number.isNaN(numericValue.value) ? '' : numericValue.value}
aria-invalid={!!invalid}
aria-describedby={`${id}-help${invalid ? ` ${id}-error` : ''}`}
onChange={(event) => onChange({ ...numericValue, value: event.currentTarget.valueAsNumber })}
/>
</label>
<p id={`${id}-help`} className="fine">
This asks about an already recorded choice or a forecast the target currently knows. If no such value is available, the answer is I don’t know. Only the answer is published.
</p>
</>
)}
{isFaction && <p className="fine">This asks about an already recorded faction choice. If no such value is available, the answer is I don’t know. Only the answer is published.</p>}
{invalid && <p id={`${id}-error`} className="notice" role="alert">{invalid}</p>}
</>
);
}