feat: bootstrap local Evennia world lane for Timmy (#36)
This commit is contained in:
1
evennia_tools/__init__.py
Normal file
1
evennia_tools/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Evennia helper modules for Timmy's persistent world lane."""
|
||||
62
evennia_tools/layout.py
Normal file
62
evennia_tools/layout.py
Normal 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()}
|
||||
28
evennia_tools/telemetry.py
Normal file
28
evennia_tools/telemetry.py
Normal 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] + "..."
|
||||
Reference in New Issue
Block a user