Scrypt / scrypt /engine /legacy.py
IMJONEZZ's picture
SCRYPT: initial commit β€” game, sandbox, Warden, Space web layer
9fca766
Raw
History Blame Contribute Delete
4.68 kB
"""Legacy: what survives between runs. The Warden keeps files on you.
One JSON store (SCRYPT_HOME/legacy.json) holds everything that crosses
run boundaries:
crashes post-mortems of dead runs β€” fuel for /var/crash dumps, the
Warden's taunts, and conscription (your dead fight for it)
estate cycles + one card sealed behind a password when you die;
recoverable next run via forensics on your own crash dump
contraband the one card you smuggled out of a won run
daemon your most-played card, installed in future sandboxes as a
service you can find and arm
intel the password you read in the Warden's diary (root epilogue);
next run's archive puzzle reuses it
wins/runs the uptime ledger; audit_level() scales director cruelty
shards the Warden's distilled memory of you, persisted
Everything mechanical that comes OUT of this store is bounded at load
time β€” corrupt or hand-edited files can never mint a broken card.
"""
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from .cards import KNOWN_SIGILS, Card, Cost, CostType
MAX_CRASHES = 5
MAX_STATEMENT = 120
_PRINTABLE = re.compile(r"[^\x20-\x7e]")
EMPTY = {
"initiated": False, # has this installation ever seen orientation?
"runs": 0,
"wins": 0,
"crashes": [],
"estate": None,
"contraband": None,
"daemon": None,
"intel": None,
"shards": [],
"diary": [], # the Warden's own entries about won runs, newest last
}
def _store_path() -> Path:
home = Path(os.environ.get("SCRYPT_HOME", "~/.scrypt")).expanduser()
return home / "legacy.json"
def load() -> dict:
try:
data = json.loads(_store_path().read_text(encoding="utf-8"))
if not isinstance(data, dict):
return dict(EMPTY)
except (OSError, ValueError):
return dict(EMPTY)
out = dict(EMPTY)
out.update({k: data[k] for k in EMPTY if k in data})
for key in ("crashes", "shards", "diary"):
if not isinstance(out[key], list):
out[key] = []
return out
def save(data: dict) -> None:
path = _store_path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
def clean_statement(raw: str) -> str:
"""The exit-interview statement: printable, trimmed, never empty."""
text = _PRINTABLE.sub("", raw).strip()[:MAX_STATEMENT].strip()
return text or "(they said nothing)"
def record_crash(
data: dict,
*,
encounter: str,
turn: int,
deck_ids: list[str],
strongest: dict | None,
statement: str,
cycles: int,
estate_card: str | None,
password: str,
) -> None:
"""File a dead run, and seal its estate behind the password."""
data["runs"] += 1
data["crashes"].append(
{
"n": data["runs"],
"encounter": encounter,
"turn": turn,
"deck": deck_ids[:16],
"strongest": strongest,
"statement": clean_statement(statement),
}
)
data["crashes"] = data["crashes"][-MAX_CRASHES:]
if cycles > 0 or estate_card:
data["estate"] = {
"cycles": min(cycles, 50),
"card": estate_card,
"password": password,
}
def record_win(data: dict, *, most_played: str | None, diary_password: str) -> None:
data["runs"] += 1
data["wins"] += 1
if most_played:
data["daemon"] = most_played
data["intel"] = diary_password
def audit_level(data: dict) -> int:
"""0 until your second win β€” the first victory is a pure payoff.
After that the machine starts taking you seriously. Capped: the
director's cruelty budget must stay bounded."""
return max(0, min(2, int(data.get("wins", 0)) - 1))
def conscript(crash: dict) -> Card | None:
"""The strongest process you died holding, now on the Warden's payroll.
Stats are clamped no matter what the JSON says."""
raw = crash.get("strongest")
if not raw:
return None
sigils = tuple(s for s in raw.get("sigils", ()) if s in KNOWN_SIGILS)[:1]
try:
power = max(1, min(4, int(raw["power"])))
health = max(1, min(6, int(raw["health"])))
except (KeyError, TypeError, ValueError):
return None
name = clean_statement(str(raw.get("name", "defector")))[:16]
return Card(
id="conscript",
name=name,
power=power,
health=health,
cost=Cost(CostType.FREE),
sigils=sigils,
flavor="It remembers being yours.",
art=" β–„β–„β–„\n▐ βˆ… β–Œ\n β–€β–€β–€",
)