case0 / src /case_zero /engine /game_state.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
"""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,
)