"""Session - the stateful controller the UI drives. Holds the case, the backend, the cached suspect briefs, and the latest immutable GameState snapshot. Each action advances the snapshot; the snapshots themselves are never mutated. """ from __future__ import annotations from collections.abc import Iterator from ..llm.backend import LLMBackend from ..projections.player_view import PlayerCaseView, build_player_view from ..projections.suspect_brief import SuspectBrief, build_suspect_brief from ..schemas.accusation import Accusation, Verdict from ..schemas.case import CaseFile from ..schemas.clue import Clue from .accusation import finalize_accusation from .evidence_board import available_evidence, search_location from .game_state import GameState, new_game_state from .interrogation_loop import InterrogationEvent, interrogate class Session: def __init__(self, case: CaseFile, backend: LLMBackend) -> None: self.case = case self.backend = backend self._briefs: dict[str, SuspectBrief] = { s.sus_id: build_suspect_brief(case, s) for s in case.suspects } self.player_view: PlayerCaseView = build_player_view(case) self.state: GameState = new_game_state( case.case_id, tuple(s.sus_id for s in case.suspects) ) def brief(self, sus_id: str) -> SuspectBrief: return self._briefs[sus_id] def interrogate( self, sus_id: str, question: str, presented_clue_id: str | None = None, seed: int | None = None, ) -> Iterator[InterrogationEvent]: for event in interrogate( self.backend, self.case, self._briefs[sus_id], self.state, sus_id, question, presented_clue_id, seed, ): if event.final is not None: self.state = event.final.state yield event def search(self, loc_id: str) -> tuple[Clue, ...]: self.state, found = search_location(self.state, self.case, loc_id) return found def add_note(self, text: str) -> None: from .state_update import add_player_note self.state = add_player_note(self.state, text) def evidence(self) -> tuple[Clue, ...]: return available_evidence(self.state, self.case) def accuse(self, accusation: Accusation) -> Verdict: self.state, verdict = finalize_accusation(self.state, self.case, accusation) return verdict