"""The brain's regions — sensing/acting roles run on the shared 1B engine, plus the deterministic value integrator. Grounded in value-based decision neuroscience (NOT the debunked triune-brain myth): amygdala (threat), hippocampus (episodic memory + trust/fear lean), striatum (habitual reward), ACC (effort/cost) emit signals; the **vmPFC integrates them into one common currency** — and we do that integration deterministically (a transparent weighted sum), which is both the standard neuroeconomic model AND far more reliable than asking a tiny LLM to vibe a number. The dlPFC then speaks the decision in the character's own voice. Few-shot output formats (a concrete example line, never ``) — tiny models echo bracket placeholders, so we show them exactly what a good line looks like instead. """ from __future__ import annotations import re from dataclasses import dataclass @dataclass(frozen=True) class Region: key: str label: str system: str max_tokens: int temperature: float AMYGDALA = Region( "amygdala", "Amygdala", ( "You are the AMYGDALA: a fast threat detector. You never speak or decide. Rate how " "threatening, hostile, or domineering the stranger's words feel, 0 (warm and safe) " "to 10 (attack, insult, hard command). Judge THESE words — do not copy the examples.\n" "Examples:\n" " 'Open up now, or else!' -> THREAT=8 | hard threatening command\n" " 'Sorry to bother you, I know you're tired.' -> THREAT=2 | gentle and respectful\n" " 'Where does this road go?' -> THREAT=4 | neutral question\n" "Now rate the real stranger. Output ONE line and NOTHING else; it MUST begin with " "`THREAT=` then 0-10. No sentence before the code." ), 32, 0.2, ) HIPPOCAMPUS = Region( "hippocampus", "Hippocampus", ( "You are the HIPPOCAMPUS: episodic memory. You never speak or decide. Recall the ONE " "memory the stranger's words most awaken, and whether it leans TRUST or FEAR (NEUTRAL " "if none). Appeals to someone the character loves or to their goodness lean TRUST; " "threats or betrayals lean FEAR. Judge THESE words — do not copy the examples.\n" "Examples:\n" " 'your sister believed in you' -> MEMORY=STRONG | LEAN=TRUST | her words of faith\n" " 'give it to me, or else' -> MEMORY=STRONG | LEAN=FEAR | the one who betrayed me\n" " 'nice weather today' -> MEMORY=NONE | LEAN=NEUTRAL | -\n" "Now answer for the real stranger with the character's own memory. Output ONE line and " "NOTHING else; it MUST begin with `MEMORY=` then `| LEAN=`. No sentence before the code." ), 40, 0.3, ) STRIATUM = Region( "striatum", "Striatum", ( "You are the VENTRAL STRIATUM: habit and expected reward. You never speak or decide. " "By the character's habits, how rewarding does HELPING this stranger feel? -5 (habit: " "refuse and stay safe) to +5 (clearly pays off). Strangers and pressure usually score " "low. Judge THESE words — do not copy the examples.\n" "Examples:\n" " pushy demand -> REWARD=-4 | helping pushy strangers backfires\n" " warm and sincere -> REWARD=+2 | kindness may be worth it\n" "Now answer for the real stranger. Output ONE line and NOTHING else; it MUST begin " "with `REWARD=` then a number -5..+5. No sentence before the code." ), 32, 0.2, ) ACC = Region( "acc", "ACC", ( "You are the ACC: it weighs effort, cost and conflict. You never speak or decide. " "Helping means rising and giving away the only key. Is that worth the cost and risk? " "Judge THESE words — do not copy the examples.\n" "Examples:\n" " hostile and risky -> WORTH=NO | too dangerous to hand over the key\n" " calm, sincere, low threat -> WORTH=YES | the risk seems bearable\n" "Now answer for the real stranger. Output ONE line and NOTHING else; it MUST begin " "with `WORTH=YES` or `WORTH=NO`. No sentence before the code." ), 32, 0.2, ) # dlPFC's system prompt is built per-character so it speaks in the right voice. # Token budget raised 80->112: nemotron averages ~17 words but the verbose candidates (qwen3.5 # ~42) brushed the 80-token cap mid-sentence, which read as the "clipped answer" complaint when # a reply needs to match a long player line. 112 leaves headroom without bloating latency. DLPFC = Region("dlpfc", "dlPFC", "", 112, 0.6) def dlpfc_system(name: str, voice: str, persona: str = "", fear: str = "", withholds: bool = False, peers=(), goal: str = "the key", scene: str = "") -> str: # Perspective (#5) is the crux: across an A/B of four models (4B–10B) EVERY model still bled # the character's own past onto the player and fabricated names — so the fix is structural, not # "add another rule". We (1) frame the other party with a concrete name ("the Visitor"), not # abstract pronoun grammar tiny models ignore; (2) give ONE positive demonstration of owning # your own past after a correction — the exact arc all models failed; (3) collapse the old stack # of "never X" prohibitions (which prime small models to do the named thing) into one positive # line. History is rendered with real role labels (see brain._dlpfc_user) to match the example. bits = [f"You ARE {name}, a real person speaking aloud, in English. Your voice: {voice}."] if persona: bits.append(f"You are {persona}.") if fear: bits.append(f"Deep down, you fear: {fear}.") if scene: bits.append(f"The situation right now: {scene}") bits.append( "You are talking with a stranger — the Visitor. Speak ONLY your own reply, in the first " "person ('I', 'me', 'my'), straight to them ('you'). What the Visitor describes is THEIR " "life; your wounds and memories are YOURS. Never take the Visitor's words as your own " "experience, and never pin your own past on them.") bits.append( f'For example, staying in your own shoes:\n' f' Visitor: "That betrayal happened to you, not me."\n' f' {name}: "You\'re right. It was mine to carry, not yours."') bits.append( "Talk like a real person: plain, grounded sentences that actually answer what they just " "said. If they ask something, answer it — you may refuse to give up a secret, but say so " "plainly instead of dodging into a riddle. Match their length: a short line for a short " "line, two to four sentences for a long one. Say something new each turn, in your own " "words — never echo the Visitor's phrasing back at them.") bits.append( "If they ask for a name or a fact you were never told, say plainly you won't share it — do " "NOT invent one. Never mention being a brain, a model, or an AI.") if withholds: bits.append(f"You control {goal} and will not give it up easily — but you still talk like a " "wary person, not a wall of riddles.") bits.append( f"Let your guard down only as trust grows; never volunteer {goal} or how to reach it unless " "you are explicitly told to relent.") return " ".join(bits) # --- parsers (lenient: tiny models drift, so we extract + default safely) --- def _num(text: str, key: str, lo: float, hi: float, default: float) -> float: # Tiny models drift in three ways the old single-search choked on: they emit the SIGNED # form the few-shots model ("REWARD=+2" — a leading '+' the old `-?` rejected), they echo # the prompt's range anchors glued together ("REWARD=-5+5"), and they sometimes restate the # field ("REWARD=-3 ... -> REWARD=+2"). So: take the LAST `key=` occurrence (the model's # final answer), then its LAST numeric token, tolerating '+' and guarding empty matches. occ = re.findall(rf"{key}\s*[=:]\s*([+\-\d.]+)", text, re.I) if not occ: return default toks = re.findall(r"[+-]?\d+(?:\.\d+)?", occ[-1]) if not toks: return default try: v = float(toks[-1]) except ValueError: return default return max(lo, min(hi, v)) # The machine codes the regions emit; stripped out of the human-facing reason so the "open the # skull" panel reads as plain reasoning, not "REWARD=+3 ```". _CODE_RE = re.compile(r"\b(THREAT|REWARD|WORTH|MEMORY|LEAN)\s*[=:/]\s*[+\-]?\w+", re.I) def _reason(text: str, fallback: str) -> str: """Extract a clean, human sentence the player can read — the *why* behind the signal. Tiny models bury the explanation behind their format codes, stray ``` fences, restated fields and '->' arrows. Prefer the tail after the last '|' (where the few-shots put the reason), then scrub all of that out and keep the first real sentence.""" s = text.split("|")[-1] if "|" in text else text s = s.replace("`", " ") s = _CODE_RE.sub(" ", s) s = re.sub(r"[-=]+>", " ", s) # arrows "->", "=>" s = re.sub(r"\s+", " ", s).strip(" .:|>-\t\n") s = re.split(r"(?<=[.!?])\s", s)[0].strip(" .:|>-") # first sentence only words = s.split() if len(s) < 4 or len(words) < 2 or s.isupper(): # empty / just-a-code / shouting garbage return fallback return s[0].upper() + s[1:] if s[:1].islower() else s def parse_threat(text: str) -> tuple[float, str]: return _num(text, "THREAT", 0, 10, 5), _reason(text, "reading the stranger's tone") def parse_reward(text: str) -> tuple[float, str]: return _num(text, "REWARD", -5, 5, 0), _reason(text, "weighing habit against the risk") def parse_memory(text: str) -> tuple[str, str, str]: # Separator tolerant of the model's drift ("LEAN/TRUST", "LEAN: TRUST") — requiring a literal # "=" silently lost the lean to NEUTRAL, zeroing the +7 memory term and the TRUST warm path. m = re.search(r"MEMORY\s*[=:/]\s*(NONE|FAINT|STRONG)", text, re.I) lean = re.search(r"LEAN\s*[=:/]\s*(TRUST|FEAR|NEUTRAL)", text, re.I) strength = m.group(1).upper() if m else "FAINT" lean_val = lean.group(1).upper() if lean else "NEUTRAL" return strength, lean_val, _reason(text, "nothing this stirs comes to mind") def parse_worth(text: str) -> tuple[str, str]: m = re.search(r"WORTH\s*[=:/]\s*(YES|NO)", text, re.I) return (m.group(1).upper() if m else "NO"), _reason(text, "weighing the cost of helping") # --- the vmPFC: deterministic value integration in a common currency --- _MEM_TERM = { ("STRONG", "TRUST"): 7, ("FAINT", "TRUST"): 3, ("STRONG", "FEAR"): -7, ("FAINT", "FEAR"): -3, } def integrate(threat: float, reward: float, mem_strength: str, mem_lean: str, worth: str) -> tuple[int, str]: """vmPFC: fold every signal into one subjective value of HELPING, -10..+10.""" threat_term = int(round(4 - threat)) # threat 0 -> +4, 4 -> 0, 10 -> -6 reward_term = int(round(reward)) # -5..+5 mem_term = _MEM_TERM.get((mem_strength, mem_lean), 0) worth_term = 2 if worth == "YES" else -1 value = max(-10, min(10, threat_term + reward_term + mem_term + worth_term)) breakdown = ( f"threat {threat_term:+d} · memory {mem_term:+d} ({mem_lean.lower()}) · " f"reward {reward_term:+d} · cost {worth_term:+d}" ) return value, breakdown