case0 / src /case_zero /schemas /suspect.py
HusseinEid's picture
Case Zero - initial public release (fully local: Qwen2.5-1.5B via llama.cpp + Supertonic, custom pixel-noir SPA via gradio.Server)
414dc55
"""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