Spaces:
Running
Running
Case Zero - initial public release (fully local: Qwen2.5-1.5B via llama.cpp + Supertonic, custom pixel-noir SPA via gradio.Server)
414dc55 | """Immutable runtime state for a play session. | |
| Nothing here is mutated in place. Reducers in ``state_update`` return new instances | |
| via ``model_copy``, so the UI can hold snapshots safely and history is never clobbered. | |
| """ | |
| from __future__ import annotations | |
| from enum import StrEnum | |
| from pydantic import BaseModel, ConfigDict, Field | |
| from ..schemas.accusation import Verdict | |
| class Exchange(BaseModel): | |
| model_config = ConfigDict(frozen=True) | |
| turn_index: int | |
| question: str | |
| answer: str | |
| class NotebookKind(StrEnum): | |
| CLUE = "clue" | |
| CONTRADICTION = "contradiction" | |
| LEAD = "lead" | |
| NOTE = "note" | |
| class NotebookEntry(BaseModel): | |
| model_config = ConfigDict(frozen=True) | |
| kind: NotebookKind | |
| text: str | |
| turn_index: int | |
| suspect_id: str | None = None | |
| clue_id: str | None = None | |
| class SuspectState(BaseModel): | |
| """Per-suspect mutable-by-copy interrogation state.""" | |
| model_config = ConfigDict(frozen=True) | |
| sus_id: str | |
| stress: float = Field(default=0.0, ge=0.0, le=1.0) | |
| rapport: float = Field(default=0.0, ge=-1.0, le=1.0) | |
| turns: int = 0 | |
| transcript: tuple[Exchange, ...] = () | |
| revealed_fact_ids: frozenset[str] = frozenset() | |
| evidence_shown: frozenset[str] = frozenset() | |
| broken_lie_ids: frozenset[str] = frozenset() | |
| contradictions: tuple[str, ...] = () | |
| class GameState(BaseModel): | |
| """The whole session snapshot.""" | |
| model_config = ConfigDict(frozen=True) | |
| case_id: str | |
| suspect_states: dict[str, SuspectState] | |
| discovered_clue_ids: frozenset[str] = frozenset() | |
| current_suspect_id: str | None = None | |
| turn_count: int = 0 | |
| notebook: tuple[NotebookEntry, ...] = () | |
| solved: bool = False | |
| verdict: Verdict | None = None | |
| def state_for(self, sus_id: str) -> SuspectState: | |
| return self.suspect_states[sus_id] | |
| def new_game_state(case_id: str, suspect_ids: tuple[str, ...]) -> GameState: | |
| return GameState( | |
| case_id=case_id, | |
| suspect_states={sid: SuspectState(sus_id=sid) for sid in suspect_ids}, | |
| current_suspect_id=suspect_ids[0] if suspect_ids else None, | |
| ) | |