"""The suspect dossier (immutable ground truth) and anchored lies. A suspect never improvises a lie at runtime. Every lie is authored here as a fixed ``claimed`` string with a known set of clues that break it, so a small model only has to *deliver* a known lie, never invent and track one. """ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field from .timeline import StatedAlibi, WhereaboutsSegment from .visual import VisualDescriptor class PhysicalCapability(BaseModel): model_config = ConfigDict(frozen=True) strength: bool = True mobility: bool = True class PersonalityAxes(BaseModel): model_config = ConfigDict(frozen=True) composure: float = Field(default=0.5, ge=0.0, le=1.0) aggression: float = Field(default=0.5, ge=0.0, le=1.0) evasiveness: float = Field(default=0.5, ge=0.0, le=1.0) class VoiceAssignment(BaseModel): """Deterministic TTS voice binding, assigned after generation.""" model_config = ConfigDict(frozen=True) engine: str speaker_id: int length_scale: float = 1.0 noise_w: float = 0.8 class AnchoredLie(BaseModel): model_config = ConfigDict(frozen=True) lie_id: str topic: str claimed: str truth_ref: str breaks_on: tuple[str, ...] = () fallback: str = "" class Suspect(BaseModel): model_config = ConfigDict(frozen=True) sus_id: str name: str role: str persona_summary: str # How they behave under questioning (confident, frightened, hostile, ...). Prompt-only: # it shapes the actor LLM's voice but is NEVER shown to the player (not in PlayerCaseView). demeanour: str = "" # Director-only. NEVER placed in the actor prompt (see projections.suspect_brief). is_culprit: bool = False physical_capability: PhysicalCapability = PhysicalCapability() personality: PersonalityAxes = PersonalityAxes() tells: tuple[str, ...] = () knows_facts: tuple[str, ...] = () secrets: tuple[str, ...] = () true_whereabouts: tuple[WhereaboutsSegment, ...] = () stated_alibi: StatedAlibi must_lie_about: tuple[str, ...] = () anchored_lies: tuple[AnchoredLie, ...] = () voice: VoiceAssignment | None = None visual: VisualDescriptor | None = None