Spaces:
Running
Running
| """A character: one fixed brain architecture + a unique biography and state. | |
| The thesis of Mindlock is that *same architecture + different experience -> different | |
| behavior*. So the six-region cascade is shared; everything that makes a character who | |
| they are lives here β chiefly the biography (hippocampus content) and their fear. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass, field | |
| class Character: | |
| name: str | |
| persona: str | |
| biography: str | |
| voice: str | |
| fear: str | |
| key_holder: bool | |
| key_location: str | |
| life_max: int = 1000 # "a thousand tokens to think with" β the track name, literally | |
| arousal: float = 4.0 | |
| # --- world/story fields (optional; absent in the slice character) --- | |
| title: str = "" | |
| gender: str = "" # "male"/"female" β so a generated character maps to a matching sprite | |
| portrait: str = "" # path (relative to project root) to the portrait image | |
| sprite_key: str = "" # roster slug β the client loads /static/sprites/npc/{slug}/ directly | |
| secrets: list = field(default_factory=list) # staged disclosures (Design v2) | |
| key_condition: str = "" # (legacy) superseded by key_approach | |
| key_approach: list = field(default_factory=list) # words that ARE the earned approach to the key | |
| goal: str = "the key" # what the holder withholds (procedural goals: 'the car keys', β¦) | |
| reveals: str = "" # (legacy, unused) one-shot hint | |
| reveals_about: str = "" # (legacy, unused) | |
| needs_reputation: int | None = None # won't engage if world reputation is below this | |
| known_people: list = field(default_factory=list) # names from their life (lie-catching: "I'm Mara") | |
| scripted: list = field(default_factory=list) # fixed beats instead of a brain β a scene, not a mind | |
| scripted_idx: int = 0 # runtime: which beat plays next | |
| yield_line: str = "" # canonical story beat spoken verbatim when the key is given | |
| # --- runtime state (not in the JSON) --- | |
| life_tokens: int | None = None | |
| rapport: float = 0.0 # accumulated relationship 0..10 (Design v2) | |
| history: list = field(default_factory=list) # recent (player, reply) exchanges | |
| peers: list = field(default_factory=list) # other characters in the room β for lie-catching | |
| relations: dict = field(default_factory=dict) # name -> tie ("cousin"); so a holder recalls a peer | |
| trust: float = 0.0 | |
| decision: str = "REFUSE" | |
| alive: bool = True | |
| gave_key: bool = False # sticky: set once the key-holder yields (Design v2) | |
| revealed: bool = False | |
| approach_landed: bool = False # the soft-spot word resonates fully only ONCE | |
| fear_pressure: int = 0 # consecutive hostile turns; sustained terror can break a holder | |
| forgotten: list = field(default_factory=list) # memories burned away with life β never return | |
| def __post_init__(self) -> None: | |
| if self.life_tokens is None: | |
| self.life_tokens = self.life_max | |
| self._arousal0 = self.arousal # remembered so reset()/reputation can restore it | |
| for s in self.secrets: | |
| s.setdefault("told", False) | |
| def load(cls, path: str) -> "Character": | |
| with open(path, encoding="utf-8") as fh: | |
| data = json.load(fh) | |
| return cls(**data) | |
| def reset(self) -> None: | |
| self.life_tokens = self.life_max | |
| self.arousal = self._arousal0 | |
| self.rapport = 0.0 | |
| self.history = [] | |
| for s in self.secrets: | |
| s["told"] = False | |
| self.trust = 0.0 | |
| self.decision = "REFUSE" | |
| self.alive = True | |
| self.gave_key = False | |
| self.revealed = False | |
| self.approach_landed = False | |
| self.fear_pressure = 0 | |
| self.scripted_idx = 0 | |
| self.forgotten = [] | |