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}", | |
| ), | |
| ), | |
| CrimeKind.CON: _P( | |
| kind=CrimeKind.CON, | |
| author_noun="confidence-game mystery - a charming swindler emptied someone's " | |
| "savings through a venture that never existed", | |
| incident_noun="swindle", | |
| incident_line="The decisive payment of the swindle changed hands between {start} " | |
| "and {end} one evening.", | |
| victim_ask="the victim - the person CONNED (name, role, and in cause_of_death " | |
| "one line on what they handed over and the moment they realized the " | |
| "venture was smoke) and which room index (0-based) the deal was closed in", | |
| instrument_ask="the BAIT of the con (name, kind) - a false prospectus, fake share " | |
| "certificates, a salted mine sample, a counterfeit letter of credit", | |
| deed_line="They conned {victim} out of a fortune using the {instrument}, closing " | |
| "the deal 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 deal was closed", | |
| brief_deed="You conned {victim} out of their savings using the {instrument}.", | |
| confession_verbs=r"conned|swindled|fleeced|duped|took\s+(him|her|them)\s+for", | |
| perp_noun="con artist", | |
| division="BUNCO SQUAD", | |
| victim_status="FLEECED", | |
| verdict="Confidence Fraud", | |
| kind_label="CON GAME", | |
| tod_label="CLOSED AT", | |
| found_verb="Closed in", | |
| timeline_line="The deal is closed in the {room}.", | |
| boot_line="A fortune handed over with a smile.", | |
| fallback_titles=( | |
| "The {room} Prospectus", "The {setting} Promise", "The {last} Venture", | |
| "Paper Gold at {setting}", "The Long Game in the {room}", "The {setting} Pitch", | |
| ), | |
| ), | |
| CrimeKind.POISONING: _P( | |
| kind=CrimeKind.POISONING, | |
| author_noun="poisoning mystery - a tainted glass passed at a familiar table", | |
| incident_noun="poisoning", | |
| incident_line="The poison was administered between {start} and {end} one evening.", | |
| victim_ask="the victim (name, role, cause of death - what they drank or ate and " | |
| "how it took them) and which room index (0-based) they were stricken in", | |
| instrument_ask="the VECTOR of the poison (name, kind) - a decanter, a teacup, a " | |
| "tonic bottle, a dessert plate", | |
| deed_line="They poisoned {victim} via the {instrument} in the {room} between " | |
| "{start} and {end}, then lied that they never left the {claimed}.", | |
| trace_ask="quietly place {culprit} near the {instrument} before {victim} was stricken", | |
| brief_deed="You poisoned {victim}.", | |
| confession_verbs=r"poisoned|dosed|laced|tainted|spiked", | |
| perp_noun="poisoner", | |
| division="TOXICOLOGY UNIT", | |
| victim_status="POISONED", | |
| verdict="Poisoning", | |
| kind_label="POISONING", | |
| tod_label="T.O.D.", | |
| found_verb="Stricken in", | |
| timeline_line="{name} is stricken. The glass is still warm.", | |
| boot_line="A toast no one should have drunk.", | |
| fallback_titles=( | |
| "The Bitter Glass in the {room}", "The {setting} Toast", "The {last} Dose", | |
| "What the Decanter Held", "Last Pour at {setting}", "The {room} Vintage", | |
| ), | |
| ), | |
| CrimeKind.RANSOM: _P( | |
| kind=CrimeKind.RANSOM, | |
| author_noun="ransom mystery - someone was taken, and a demand was left where it " | |
| "would be found", | |
| incident_noun="abduction", | |
| incident_line="The abduction happened between {start} and {end} one evening.", | |
| victim_ask="the victim - the person TAKEN (name, role, and in cause_of_death one " | |
| "line on how the ransom demand was discovered) and which room index " | |
| "(0-based) they were taken from", | |
| instrument_ask="the RANSOM NOTE (name, kind) - cut letters, a typed card, a wax-" | |
| "sealed demand", | |
| deed_line="They are behind {victim}'s abduction - they took them from the {room} " | |
| "and left 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 taken", | |
| brief_deed="You took {victim} and left the {instrument}. {victim} is alive and " | |
| "unharmed, but you must hide what you did.", | |
| confession_verbs=r"abducted|kidnapped|took\s+(him|her|them)|snatched|holding\s+(him|her|them)", | |
| perp_noun="kidnapper", | |
| division="RANSOM DETAIL", | |
| victim_status="TAKEN", | |
| verdict="Kidnapping", | |
| kind_label="RANSOM", | |
| tod_label="TAKEN AT", | |
| found_verb="Taken from", | |
| timeline_line="{name} is taken. The note is found.", | |
| boot_line="An empty chair and a demand on the mantel.", | |
| fallback_titles=( | |
| "The {room} Demand", "The Price of {last}", "Taken from {setting}", | |
| "The {setting} Note", "What the {room} Lost", "The {last} Exchange", | |
| ), | |
| ), | |
| CrimeKind.SABOTAGE: _P( | |
| kind=CrimeKind.SABOTAGE, | |
| author_noun="sabotage mystery - machinery, a performance, or a life's work wrecked " | |
| "by a familiar hand", | |
| incident_noun="sabotage", | |
| incident_line="The sabotage was carried out between {start} and {end} one evening.", | |
| victim_ask="the victim - the person whose WORK WAS WRECKED (name, role, and in " | |
| "cause_of_death one line on what failed and what it cost them) and which " | |
| "room index (0-based) the tampering happened in", | |
| instrument_ask="the TAMPERED MECHANISM (name, kind) - a severed cable, a loosened " | |
| "valve, a doctored mixture, a cut harness", | |
| deed_line="They sabotaged {victim}'s work - tampering with the {instrument} 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 tampering was done", | |
| brief_deed="You sabotaged {victim}'s work by tampering with the {instrument}. " | |
| "{victim} survived, but you must hide what you did.", | |
| confession_verbs=r"sabotaged|tampered|rigged\s+it|cut\s+the|loosened\s+the|wrecked\s+it", | |
| perp_noun="saboteur", | |
| division="INCIDENT UNIT", | |
| victim_status="RUINED", | |
| verdict="Sabotage", | |
| kind_label="SABOTAGE", | |
| tod_label="FAILED AT", | |
| found_verb="Tampered with in", | |
| timeline_line="The {instrument} gives way in the {room}.", | |
| boot_line="It did not break. It was broken.", | |
| fallback_titles=( | |
| "The {room} Failure", "What Gave Way at {setting}", "The {last} Collapse", | |
| "A Loose Thread in the {room}", "The {setting} Incident", "Made to Fail", | |
| ), | |
| ), | |
| } | |
| # 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, | |
| CrimeKind.CON, CrimeKind.POISONING, CrimeKind.RANSOM, CrimeKind.SABOTAGE, | |
| ) | |
| 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)] | |