feat: bootstrap local Evennia world lane for Timmy (#36)

This commit is contained in:
Alexander Whitestone
2026-03-28 13:33:26 -04:00
parent 812c576a7b
commit 78ec5ad97b
11 changed files with 674 additions and 0 deletions

View File

@@ -0,0 +1 @@
"""Evennia helper modules for Timmy's persistent world lane."""

62
evennia_tools/layout.py Normal file
View File

@@ -0,0 +1,62 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class RoomSpec:
key: str
desc: str
@dataclass(frozen=True)
class ExitSpec:
source: str
key: str
destination: str
aliases: tuple[str, ...] = ()
@dataclass(frozen=True)
class ObjectSpec:
key: str
location: str
desc: str
ROOMS = (
RoomSpec("Gate", "A deliberate threshold into Timmy's world. The air is still here, as if entry itself matters."),
RoomSpec("Courtyard", "The central open court of Timmy's place. Paths lead outward to work, memory, prayer, and watchfulness."),
RoomSpec("Workshop", "Benches, tools, half-built mechanisms, and active prototypes fill the room. This is where ideas become artifacts."),
RoomSpec("Archive", "Shelves hold transcripts, reports, doctrine-bearing documents, and recovered fragments. This room remembers structure."),
RoomSpec("Chapel", "A quiet room set apart for prayer, conscience, grief, and right alignment. The tone here is gentle and unhurried."),
)
EXITS = (
ExitSpec("Gate", "enter", "Courtyard", ("in", "forward")),
ExitSpec("Courtyard", "gate", "Gate", ("out", "threshold")),
ExitSpec("Courtyard", "workshop", "Workshop", ("work",)),
ExitSpec("Workshop", "courtyard", "Courtyard", ("back", "out")),
ExitSpec("Courtyard", "archive", "Archive", ("memory",)),
ExitSpec("Archive", "courtyard", "Courtyard", ("back", "out")),
ExitSpec("Courtyard", "chapel", "Chapel", ("quiet", "prayer")),
ExitSpec("Chapel", "courtyard", "Courtyard", ("back", "out")),
)
OBJECTS = (
ObjectSpec("Book of the Soul", "Chapel", "A doctrinal anchor. It is not decorative; it is a reference point."),
ObjectSpec("Workbench", "Workshop", "A broad workbench for prototypes, repairs, and experiments not yet stable enough for the world."),
ObjectSpec("Map Table", "Courtyard", "A living map of the known place, useful for orientation and future expansion."),
ObjectSpec("Prayer Wall", "Chapel", "A place for names, griefs, mercies, and remembered burdens."),
ObjectSpec("Memory Shelves", "Archive", "Shelves prepared for durable artifacts that should outlive a single session."),
ObjectSpec("Mirror of Sessions", "Archive", "A reflective object meant to bind world continuity to Hermes session history."),
)
def room_keys() -> tuple[str, ...]:
return tuple(room.key for room in ROOMS)
def grouped_exits() -> dict[str, tuple[ExitSpec, ...]]:
out: dict[str, list[ExitSpec]] = {}
for ex in EXITS:
out.setdefault(ex.source, []).append(ex)
return {k: tuple(v) for k, v in out.items()}

View File

@@ -0,0 +1,28 @@
import json
from datetime import datetime, timezone
from pathlib import Path
def telemetry_dir(base_dir: str | Path = "~/.timmy/training-data/evennia") -> Path:
return Path(base_dir).expanduser()
def event_log_path(session_id: str, base_dir: str | Path = "~/.timmy/training-data/evennia") -> Path:
session_id = (session_id or "unbound").strip() or "unbound"
day = datetime.now(timezone.utc).strftime("%Y%m%d")
return telemetry_dir(base_dir) / day / f"{session_id}.jsonl"
def append_event(session_id: str, event: dict, base_dir: str | Path = "~/.timmy/training-data/evennia") -> Path:
path = event_log_path(session_id, base_dir)
path.parent.mkdir(parents=True, exist_ok=True)
payload = dict(event)
payload.setdefault("timestamp", datetime.now(timezone.utc).isoformat())
with path.open("a", encoding="utf-8") as f:
f.write(json.dumps(payload, ensure_ascii=False) + "\n")
return path
def excerpt(text: str, limit: int = 240) -> str:
text = " ".join((text or "").split())
return text if len(text) <= limit else text[: limit - 3] + "..."