Spaces:
Running
Running
| """Crime profiles: one frozen table that tells every layer how to talk about a case. | |
| The solver and engine are crime-agnostic - they only check structure (one culprit, a | |
| false alibi contradicted by discoverable evidence, innocents cleared). A profile maps a | |
| ``CrimeKind`` to the words each layer needs: how the generator prompts the model, how | |
| the suspect brief states the culprit's deed, and how the dossier/verdict label the case. | |
| Field semantics are REUSED rather than renamed, so no schema churn: | |
| - ``victim`` = the wronged party (the deceased / the one robbed / the missing person); | |
| - ``weapon`` = the instrument or object of the crime (knife / stolen jewel / forged deed); | |
| - ``time_of_death`` = the moment of the incident; | |
| - ``cause_of_death`` = one line describing what happened to the victim. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from ..schemas.enums import CrimeKind | |
| class CrimeProfile: | |
| kind: CrimeKind | |
| # --- generator prompts --- | |
| author_noun: str # "murder mystery" | |
| incident_noun: str # "murder" - used in "during the {incident_noun}" | |
| incident_line: str # world prompt: what happened that evening | |
| victim_ask: str # world prompt: who the victim is + what cause_of_death holds | |
| instrument_ask: str # world prompt: what weapon_name/kind hold | |
| deed_line: str # mystery prompt: "{culprit} ... {victim} ... {room} ... {instrument}" | |
| trace_ask: str # mystery prompt: what the two breaker clues prove | |
| # --- engine --- | |
| brief_deed: str # suspect brief, second person: "You killed {victim}." | |
| confession_verbs: str # extra regex alternatives for the confession scrubber | |
| # --- display --- | |
| perp_noun: str # "killer" | |
| division: str # "HOMICIDE DIVISION" | |
| victim_status: str # dossier stamp: "DECEASED" | |
| verdict: str # KEY FACTS verdict line: "Homicide" | |
| kind_label: str # title screen: "A PROCEDURAL {kind_label}" | |
| tod_label: str # "T.O.D." vs "LAST SEEN" vs "INCIDENT" | |
| found_verb: str # "Found in" vs "Last seen in" | |
| timeline_line: str # "{name} is killed. Time of death." | |
| boot_line: str # boot screen: "A death no one saw coming." | |
| fallback_titles: tuple[str, ...] | |
| _P = CrimeProfile | |
| PROFILES: dict[CrimeKind, CrimeProfile] = { | |
| CrimeKind.HOMICIDE: _P( | |
| kind=CrimeKind.HOMICIDE, | |
| author_noun="murder mystery", | |
| incident_noun="murder", | |
| incident_line="The murder happened between {start} and {end} one evening.", | |
| victim_ask="the victim (name, role, cause of death) and which room index (0-based) " | |
| "they were found in", | |
| instrument_ask="the murder weapon (name, kind)", | |
| deed_line="They murdered {victim} in the {room} with the {instrument} between " | |
| "{start} and {end}, then lied that they never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} during the murder", | |
| brief_deed="You killed {victim}.", | |
| confession_verbs=r"killed|murdered|stabbed|poisoned|strangled|shot|drowned|smothered", | |
| perp_noun="killer", | |
| division="HOMICIDE DIVISION", | |
| victim_status="DECEASED", | |
| verdict="Homicide", | |
| kind_label="HOMICIDE", | |
| tod_label="T.O.D.", | |
| found_verb="Found in", | |
| timeline_line="{name} is killed. Time of death.", | |
| boot_line="A death no one saw coming.", | |
| fallback_titles=( | |
| "A Death in the {room}", "The {room} Affair", "Murder at {setting}", | |
| "The {last} File", "Blood in the {room}", "Last Call at {setting}", | |
| "The {setting} Killing", "The {room} Verdict", | |
| ), | |
| ), | |
| CrimeKind.THEFT: _P( | |
| kind=CrimeKind.THEFT, | |
| author_noun="heist mystery - a prized object stolen from under everyone's nose", | |
| incident_noun="theft", | |
| incident_line="The theft happened between {start} and {end} one evening.", | |
| victim_ask="the victim - the OWNER of what was stolen (name, role, and in " | |
| "cause_of_death one line on how they discovered the loss) and which room " | |
| "index (0-based) the theft happened in", | |
| instrument_ask="the STOLEN OBJECT itself (name, kind) - one specific prized thing: " | |
| "a jewel, a painting, a rare manuscript, a strongbox", | |
| deed_line="They stole the {instrument} from {victim}'s {room} between {start} and " | |
| "{end}, then lied that they never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} during the theft", | |
| brief_deed="You stole the {instrument} from {victim}.", | |
| confession_verbs=r"stole|robbed|took\s+the|lifted\s+the|pocketed", | |
| perp_noun="thief", | |
| division="ROBBERY DIVISION", | |
| victim_status="ROBBED", | |
| verdict="Grand Larceny", | |
| kind_label="HEIST", | |
| tod_label="TAKEN AT", | |
| found_verb="Taken from", | |
| timeline_line="The {instrument} vanishes from the {room}.", | |
| boot_line="A theft no one saw happen.", | |
| fallback_titles=( | |
| "The Empty Case in the {room}", "The {setting} Job", "The {last} Collection", | |
| "What Left the {room}", "The {setting} Take", "A Hole in the {room}", | |
| ), | |
| ), | |
| CrimeKind.FRAUD: _P( | |
| kind=CrimeKind.FRAUD, | |
| author_noun="financial-fraud mystery - a swindle unravelling among people who " | |
| "trusted each other", | |
| incident_noun="fraud", | |
| incident_line="The decisive forged transaction was pushed through between {start} " | |
| "and {end} one evening.", | |
| victim_ask="the victim - the person DEFRAUDED (name, role, and in cause_of_death " | |
| "one line on what they lost and how they found out) and which room index " | |
| "(0-based) the scheme was run from", | |
| instrument_ask="the INSTRUMENT of the fraud (name, kind) - a forged deed, a doctored " | |
| "ledger, a fake certificate, a rigged contract", | |
| deed_line="They defrauded {victim} using the {instrument}, working from the {room} " | |
| "between {start} and {end}, then lied that they never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} when the {instrument} was made", | |
| brief_deed="You defrauded {victim} using the {instrument}.", | |
| confession_verbs=r"forged|defrauded|swindled|scammed|embezzled|doctored\s+the", | |
| perp_noun="fraudster", | |
| division="FRAUD DIVISION", | |
| victim_status="DEFRAUDED", | |
| verdict="Fraud", | |
| kind_label="SWINDLE", | |
| tod_label="SIGNED AT", | |
| found_verb="Uncovered in", | |
| timeline_line="The {instrument} is signed in the {room}.", | |
| boot_line="A fortune signed away in one evening.", | |
| fallback_titles=( | |
| "The {room} Signature", "The {setting} Accounts", "The {last} Trust", | |
| "Ink in the {room}", "The Paper Fortune", "The {setting} Ledger", | |
| ), | |
| ), | |
| CrimeKind.BLACKMAIL: _P( | |
| kind=CrimeKind.BLACKMAIL, | |
| author_noun="blackmail mystery - someone is being bled, and the extortionist sat " | |
| "at the same table", | |
| incident_noun="blackmail drop", | |
| incident_line="The latest blackmail demand was planted between {start} and {end} " | |
| "one evening.", | |
| victim_ask="the victim - the person BLACKMAILED (name, role, and in cause_of_death " | |
| "one line on the demand and what it is costing them) and which room index " | |
| "(0-based) the demand was planted in", | |
| instrument_ask="the LEVERAGE (name, kind) - compromising photographs, stolen letters, " | |
| "an incriminating ledger page", | |
| deed_line="They are blackmailing {victim} with the {instrument}, and planted the " | |
| "demand in the {room} between {start} and {end}, then lied that they " | |
| "never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} when the demand was planted", | |
| brief_deed="You are blackmailing {victim} with the {instrument}.", | |
| confession_verbs=r"blackmailed|blackmailing|extorted|bled\s+(him|her|them)", | |
| perp_noun="blackmailer", | |
| division="EXTORTION UNIT", | |
| victim_status="EXTORTED", | |
| verdict="Extortion", | |
| kind_label="EXTORTION", | |
| tod_label="PLANTED AT", | |
| found_verb="Planted in", | |
| timeline_line="The demand is planted in the {room}.", | |
| boot_line="An envelope no one will admit to leaving.", | |
| fallback_titles=( | |
| "The {room} Envelope", "The Price of {last}", "The {setting} Demand", | |
| "Postage Due at {setting}", "The {room} Letters", "What {last} Pays For", | |
| ), | |
| ), | |
| CrimeKind.ARSON: _P( | |
| kind=CrimeKind.ARSON, | |
| author_noun="arson mystery - a fire set by a familiar hand", | |
| incident_noun="fire", | |
| incident_line="The fire was set between {start} and {end} one evening.", | |
| victim_ask="the victim - the person whose property BURNED (name, role, and in " | |
| "cause_of_death one line on what the fire took from them) and which room " | |
| "index (0-based) the fire started in", | |
| instrument_ask="the MEANS of the fire (name, kind) - a kerosene tin, an oil lamp, " | |
| "a candle and curtains", | |
| deed_line="They set the fire in {victim}'s {room} using the {instrument} between " | |
| "{start} and {end}, then lied that they never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} just before the fire started", | |
| brief_deed="You set the fire in {victim}'s {room} using the {instrument}.", | |
| confession_verbs=r"torched|set\s+the\s+fire|burned\s+it|lit\s+the|started\s+the\s+fire", | |
| perp_noun="arsonist", | |
| division="ARSON UNIT", | |
| victim_status="BURNED OUT", | |
| verdict="Arson", | |
| kind_label="ARSON", | |
| tod_label="IGNITED AT", | |
| found_verb="Started in", | |
| timeline_line="The fire starts in the {room}.", | |
| boot_line="A fire that started exactly where it hurt most.", | |
| fallback_titles=( | |
| "The {room} Burn", "Ashes at {setting}", "The {last} Fire", | |
| "What the {room} Kept", "Kindling at {setting}", "The {setting} Blaze", | |
| ), | |
| ), | |
| CrimeKind.MISSING: _P( | |
| kind=CrimeKind.MISSING, | |
| author_noun="missing-person mystery - someone walked into a room and never walked out", | |
| incident_noun="disappearance", | |
| incident_line="The disappearance happened between {start} and {end} one evening.", | |
| victim_ask="the victim - the person who VANISHED (name, role, and in cause_of_death " | |
| "one line on how their absence was noticed) and which room index (0-based) " | |
| "they were last seen in", | |
| instrument_ask="the LURE (name, kind) - the thing used to draw them away: a forged " | |
| "note, a false telegram, a promised meeting", | |
| deed_line="They are behind {victim}'s disappearance - they lured them from the {room} " | |
| "using the {instrument} between {start} and {end}, then lied that they " | |
| "never left the {claimed}.", | |
| trace_ask="quietly place {culprit} in the {room} when {victim} was last seen", | |
| brief_deed="You are behind {victim}'s disappearance; you lured them away with the " | |
| "{instrument}. {victim} is alive, but you must hide what you did.", | |
| confession_verbs=r"abducted|kidnapped|lured\s+(him|her|them)|took\s+(him|her|them)", | |
| perp_noun="abductor", | |
| division="MISSING PERSONS", | |
| victim_status="MISSING", | |
| verdict="Missing Person", | |
| kind_label="DISAPPEARANCE", | |
| tod_label="LAST SEEN", | |
| found_verb="Last seen in", | |
| timeline_line="{name} is seen for the last time.", | |
| boot_line="A chair still warm, and nobody in it.", | |
| fallback_titles=( | |
| "The Empty Chair in the {room}", "Where {last} Went", "The {setting} Absence", | |
| "Last Seen in the {room}", "The {last} Vanishing", "Gone from {setting}", | |
| ), | |
| ), | |
| } | |
| # Weighted draw: homicide stays the backbone of a noir game; the rest add variety. | |
| _KIND_BAG: tuple[CrimeKind, ...] = ( | |
| CrimeKind.HOMICIDE, CrimeKind.HOMICIDE, CrimeKind.HOMICIDE, | |
| CrimeKind.THEFT, CrimeKind.THEFT, | |
| CrimeKind.FRAUD, CrimeKind.BLACKMAIL, CrimeKind.ARSON, CrimeKind.MISSING, | |
| ) | |
| def profile_for(kind: CrimeKind | str) -> CrimeProfile: | |
| return PROFILES[CrimeKind(kind)] | |
| def kind_for_seed(seed: int) -> CrimeKind: | |
| return _KIND_BAG[seed % len(_KIND_BAG)] | |