File size: 1,350 Bytes
414dc55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""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)