case0 / src /case_zero /solver /discoverability.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
raw
history blame
1.35 kB
"""The discoverability gate.
A case proven solvable on paper is worthless if a key clue can only ever come from a
suspect who chooses to stay silent. This gate guarantees every clue in the solution's
minimal set is reachable by a non-interrogation channel (search / forensic / document),
so the player can always obtain it.
"""
from __future__ import annotations
from ..schemas.case import CaseFile
from ..schemas.enums import DiscoveryMethod
_NON_INTERROGATION = frozenset(
{DiscoveryMethod.SEARCH, DiscoveryMethod.FORENSIC, DiscoveryMethod.DOCUMENT}
)
def undiscoverable_minimal_clues(case: CaseFile) -> tuple[str, ...]:
"""Return minimal-set clue ids with no non-interrogation discovery path."""
by_id = {c.clue_id: c for c in case.clues}
loc_ids = {loc.loc_id for loc in case.setting.locations}
bad: list[str] = []
for cid in case.solution.minimal_clue_set:
clue = by_id.get(cid)
if clue is None:
bad.append(cid)
continue
reachable = (
clue.discovery_method in _NON_INTERROGATION
and clue.discoverable_at_loc_id in loc_ids
)
if not reachable:
bad.append(cid)
return tuple(bad)
def is_discoverable(case: CaseFile) -> bool:
return not case.solution.minimal_clue_set or not undiscoverable_minimal_clues(case)