"""Project-wide constants. No secrets, no environment reads here (see config.py).""" from __future__ import annotations from pathlib import Path from typing import Final # Repository layout anchors. config.py resolves user-overridable paths from here. PACKAGE_ROOT: Final[Path] = Path(__file__).resolve().parent PROJECT_ROOT: Final[Path] = PACKAGE_ROOT.parent.parent ASSETS_DIR: Final[Path] = PROJECT_ROOT / "assets" SPRITES_DIR: Final[Path] = ASSETS_DIR / "sprites" VOICES_DIR: Final[Path] = ASSETS_DIR / "voices" FONTS_DIR: Final[Path] = ASSETS_DIR / "fonts" CASES_DIR: Final[Path] = PROJECT_ROOT / "cases" SEED_CASES_DIR: Final[Path] = CASES_DIR / "seeds" MODELS_DIR: Final[Path] = PROJECT_ROOT / "models" GRAMMARS_DIR: Final[Path] = PACKAGE_ROOT / "llm" / "grammars" # Generation envelope. The model invents within these structural bounds so the # mystery stays solvable; the bounds constrain shape, never creative content. MIN_SUSPECTS: Final[int] = 3 MAX_SUSPECTS: Final[int] = 6 DAY_MINUTES: Final[int] = 24 * 60 # Interrogation pacing. Short, punchy suspect lines improve both UX and latency - and on # a 2-vCPU CPU Space the prompt is reprocessed every turn, so a smaller rolling buffer is # a direct latency win. SPOKEN_MAX_TOKENS: Final[int] = 96 ROLLING_BUFFER_TURNS: Final[int] = 5 # Deception is reported by the model on a 0-100 scale but is advisory only; # the deterministic director is the authority on whether a lie was caught. DECEPTION_MIN: Final[int] = 0 DECEPTION_MAX: Final[int] = 100 # Schema version stamped onto persisted cases so old saves are detectable. CASE_SCHEMA_VERSION: Final[str] = "1.0"