f-id / src /id /engine /crack.py
marcodsn's picture
Initial Gradio Space
0423b99
Raw
History Blame Contribute Delete
2.98 kB
"""Crack state machine (Section 8.3).
A crack is the designed climax, not an error. It triggers when either the
player has satisfied ``cracks_when`` (engine checks discovered clues +
confrontations) or the guard cannot produce a safe+consistent reply (logical
corner). The reply is generated via ``character/crack.md.j2`` honoring the
character's ``crack_behavior``; ``partial_confess`` yields a bounded admission
of a *specific* fact only.
"""
from __future__ import annotations
from dataclasses import dataclass
from ..llm.client import LLMClient
from ..llm.prompts import PromptRegistry
from ..models import CharacterCard
@dataclass
class CrackResult:
text: str
behavior: str
confessed_fact: str = ""
class CrackMachine:
def __init__(self, client: LLMClient, prompts: PromptRegistry) -> None:
self.client = client
self.prompts = prompts
def should_crack(
self, card: CharacterCard, discovered: set[str], confront_count: int
) -> bool:
"""Heuristic engine check of cracks_when against surfaced pressure.
``cracks_when`` is authored prose; we look for any clue id mentioned in
it that the player has discovered, OR a verified confrontation has
landed. The character-turn pipeline also forces a crack on a logical
corner (guard exhaustion), handled by the caller.
"""
text = card.cracks_when.lower()
mentioned = [cid for cid in discovered if cid.lower() in text]
# require ALL clue ids named in cracks_when to be discovered, if any
named = [tok for tok in text.replace(",", " ").split() if tok.startswith("clue_")]
if named:
return all(n in {c.lower() for c in discovered} for n in named)
# otherwise crack on first verified confrontation touching this char
return bool(mentioned) or confront_count > 0
def crack(
self,
*,
card: CharacterCard,
player_message: str,
trigger: str,
forced_by_corner: bool,
) -> CrackResult:
behavior = card.crack_behavior
prompt = self.prompts.render(
"character/crack.md.j2",
name=card.name,
voice=card.voice,
cover=card.cover,
crack_behavior=behavior,
cracks_when=card.cracks_when,
never_admit=card.never_admit,
trigger=trigger,
forced_by_corner=forced_by_corner,
player_message=player_message,
)
resp = self.client.complete(
tier="character", task="crack_gen", user=prompt,
)
text = resp.text.strip()
confessed = ""
if behavior == "partial_confess":
# The authored crack prompt is instructed to confess exactly one
# fact; we surface the trigger as the logged confessed fact.
confessed = trigger
return CrackResult(text=text, behavior=behavior, confessed_fact=confessed)