2026-03-19 14:07:56 -06:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""WorldBuilder Executor — connects to a MUX and applies compiled commands.
|
|
|
|
|
|
|
|
|
|
Handles:
|
|
|
|
|
- Telnet connection with login
|
|
|
|
|
- Sequential command execution with output capture
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
- Dbref capture via lastcreate() + sentinel-wrapped `think` queries,
|
|
|
|
|
which avoids parsing the version-specific free-form @dig/@open/
|
|
|
|
|
@create response text (see live_adapter.py's module docstring for
|
|
|
|
|
the server-format contract this relies on)
|
2026-03-19 14:07:56 -06:00
|
|
|
- State file generation (spec ID → dbref mapping)
|
|
|
|
|
- Dry-run mode (log commands without executing)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import socket
|
|
|
|
|
import ssl
|
|
|
|
|
import time
|
|
|
|
|
import argparse
|
|
|
|
|
from pathlib import Path
|
2026-03-19 19:14:33 -06:00
|
|
|
from worldbuilder import parse_spec, check_drc, plan_spec_operations, plan_incremental_operations
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
from live_adapter import (StateFile, content_hash,
|
|
|
|
|
query_live_snapshot, query_last_created,
|
|
|
|
|
query_think, mux_escape)
|
2026-03-19 14:07:56 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Telnet Client (minimal, synchronous)
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class MuxConnection:
|
|
|
|
|
"""Simple telnet connection to a MUX server."""
|
|
|
|
|
|
|
|
|
|
IAC = 255
|
|
|
|
|
DONT = 254
|
|
|
|
|
DO = 253
|
|
|
|
|
WONT = 252
|
|
|
|
|
WILL = 251
|
|
|
|
|
SB = 250
|
|
|
|
|
SE = 240
|
|
|
|
|
|
2026-04-04 17:44:10 -06:00
|
|
|
def __init__(self, host, port, use_ssl=False, timeout=10, verify_ssl=True):
|
2026-03-19 14:07:56 -06:00
|
|
|
self.host = host
|
|
|
|
|
self.port = int(port)
|
|
|
|
|
self.use_ssl = use_ssl
|
|
|
|
|
self.timeout = timeout
|
2026-04-04 17:44:10 -06:00
|
|
|
self.verify_ssl = verify_ssl
|
2026-03-19 14:07:56 -06:00
|
|
|
self.sock = None
|
|
|
|
|
self.buffer = b''
|
|
|
|
|
|
|
|
|
|
def connect(self):
|
|
|
|
|
raw = socket.create_connection((self.host, self.port), self.timeout)
|
|
|
|
|
if self.use_ssl:
|
|
|
|
|
ctx = ssl.create_default_context()
|
2026-04-04 17:44:10 -06:00
|
|
|
if not self.verify_ssl:
|
|
|
|
|
ctx.check_hostname = False
|
|
|
|
|
ctx.verify_mode = ssl.CERT_NONE
|
2026-03-19 14:07:56 -06:00
|
|
|
self.sock = ctx.wrap_socket(raw, server_hostname=self.host)
|
|
|
|
|
else:
|
|
|
|
|
self.sock = raw
|
|
|
|
|
self.sock.settimeout(self.timeout)
|
|
|
|
|
# Drain initial welcome text
|
|
|
|
|
self._read_until_quiet(2.0)
|
|
|
|
|
|
|
|
|
|
def login(self, character, password):
|
|
|
|
|
self.send(f'connect {character} {password}')
|
|
|
|
|
time.sleep(1.0)
|
|
|
|
|
return self._read_until_quiet(2.0)
|
|
|
|
|
|
|
|
|
|
def send(self, text):
|
|
|
|
|
data = (text + '\r\n').encode('utf-8')
|
|
|
|
|
self.sock.sendall(data)
|
|
|
|
|
|
|
|
|
|
def read_response(self, wait=0.5):
|
|
|
|
|
"""Read all available data, waiting up to `wait` seconds for quiet."""
|
|
|
|
|
return self._read_until_quiet(wait)
|
|
|
|
|
|
|
|
|
|
def _read_until_quiet(self, quiet_seconds=0.5):
|
|
|
|
|
"""Read until no data arrives for quiet_seconds."""
|
|
|
|
|
result = b''
|
|
|
|
|
self.sock.settimeout(quiet_seconds)
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
chunk = self.sock.recv(4096)
|
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
|
|
|
|
result += chunk
|
|
|
|
|
except socket.timeout:
|
|
|
|
|
break
|
|
|
|
|
self.sock.settimeout(self.timeout)
|
|
|
|
|
# Strip telnet IAC sequences
|
|
|
|
|
return self._strip_telnet(result).decode('utf-8', errors='replace')
|
|
|
|
|
|
|
|
|
|
def _strip_telnet(self, data):
|
|
|
|
|
"""Remove telnet IAC sequences from raw data."""
|
|
|
|
|
out = bytearray()
|
|
|
|
|
i = 0
|
|
|
|
|
while i < len(data):
|
|
|
|
|
if data[i] == self.IAC and i + 1 < len(data):
|
|
|
|
|
cmd = data[i + 1]
|
|
|
|
|
if cmd in (self.WILL, self.WONT, self.DO, self.DONT):
|
|
|
|
|
# Respond DONT/WONT to everything
|
|
|
|
|
opt = data[i + 2] if i + 2 < len(data) else 0
|
|
|
|
|
if cmd == self.WILL:
|
|
|
|
|
self.sock.sendall(bytes([self.IAC, self.DONT, opt]))
|
|
|
|
|
elif cmd == self.DO:
|
|
|
|
|
self.sock.sendall(bytes([self.IAC, self.WONT, opt]))
|
|
|
|
|
i += 3
|
|
|
|
|
elif cmd == self.SB:
|
|
|
|
|
# Skip until IAC SE
|
|
|
|
|
j = i + 2
|
|
|
|
|
while j < len(data) - 1:
|
|
|
|
|
if data[j] == self.IAC and data[j + 1] == self.SE:
|
|
|
|
|
j += 2
|
|
|
|
|
break
|
|
|
|
|
j += 1
|
|
|
|
|
i = j
|
|
|
|
|
elif cmd == self.IAC:
|
|
|
|
|
out.append(self.IAC)
|
|
|
|
|
i += 2
|
|
|
|
|
else:
|
|
|
|
|
i += 2 # skip 2-byte command
|
|
|
|
|
else:
|
|
|
|
|
out.append(data[i])
|
|
|
|
|
i += 1
|
|
|
|
|
return bytes(out)
|
|
|
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
|
if self.sock:
|
|
|
|
|
try:
|
|
|
|
|
self.send('QUIT')
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
|
|
|
|
self.sock.close()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
self.sock = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Executor
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
def execute(spec, conn, state, dry_run=False, log_file=None, operations=None):
|
|
|
|
|
"""Execute semantic operations against a live MUX connection.
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
If operations is None, plans them from the spec and state.
|
2026-03-19 14:07:56 -06:00
|
|
|
Returns True on success.
|
|
|
|
|
"""
|
|
|
|
|
def log(msg):
|
|
|
|
|
print(msg)
|
|
|
|
|
if log_file:
|
|
|
|
|
log_file.write(msg + '\n')
|
|
|
|
|
|
2026-04-10 19:33:57 -06:00
|
|
|
def escape_attr_value(value):
|
|
|
|
|
return mux_escape(str(value))
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
def do_cmd(cmd):
|
2026-03-19 14:07:56 -06:00
|
|
|
"""Send a command and return response. In dry-run, just log."""
|
|
|
|
|
if dry_run:
|
|
|
|
|
log(f' [dry-run] {cmd}')
|
|
|
|
|
return ''
|
|
|
|
|
log(f' > {cmd}')
|
|
|
|
|
conn.send(cmd)
|
|
|
|
|
time.sleep(0.3)
|
|
|
|
|
resp = conn.read_response(0.5)
|
|
|
|
|
if resp.strip():
|
|
|
|
|
for line in resp.strip().split('\n'):
|
|
|
|
|
log(f' < {line.rstrip()}')
|
|
|
|
|
return resp
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
def capture_objid(dbref):
|
|
|
|
|
"""Query objid for a dbref. Returns stripped string or None."""
|
|
|
|
|
if dry_run:
|
|
|
|
|
return None
|
|
|
|
|
resp = do_cmd(f'think [objid({dbref})]')
|
|
|
|
|
return resp.strip() if resp else None
|
|
|
|
|
|
2026-03-19 14:07:56 -06:00
|
|
|
state.zone = spec.zone.name
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
# Plan operations if not provided
|
|
|
|
|
if operations is None:
|
|
|
|
|
state_path = state.path
|
|
|
|
|
if state_path.exists() and state.objects:
|
|
|
|
|
operations = plan_incremental_operations(spec, str(state_path))
|
2026-03-19 14:07:56 -06:00
|
|
|
else:
|
2026-03-19 19:14:33 -06:00
|
|
|
operations = plan_spec_operations(spec)
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
# Separate into phases: rooms first, then exits/things, then destroys
|
|
|
|
|
room_creates = [op for op in operations if op.kind == 'create_room']
|
|
|
|
|
room_updates = [op for op in operations if op.kind == 'update_room']
|
|
|
|
|
exit_creates = [op for op in operations if op.kind == 'create_exit']
|
|
|
|
|
thing_creates = [op for op in operations if op.kind == 'create_thing']
|
|
|
|
|
destroy_exits = [op for op in operations if op.kind == 'destroy_exit']
|
|
|
|
|
destroy_rooms = [op for op in operations if op.kind == 'destroy_room']
|
|
|
|
|
|
|
|
|
|
# Phase 1: Create rooms and capture dbrefs
|
|
|
|
|
if room_creates or room_updates:
|
|
|
|
|
log(f'\n=== Phase 1: Rooms ({len(room_creates)} create, {len(room_updates)} update) ===')
|
|
|
|
|
|
|
|
|
|
for op in room_creates:
|
|
|
|
|
room = op.payload['room']
|
|
|
|
|
log(f'\n--- Room: {room.name} ({op.obj_id}) — creating ---')
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
do_cmd(f'@dig/teleport {room.name}')
|
2026-03-19 19:14:33 -06:00
|
|
|
|
|
|
|
|
if not dry_run:
|
|
|
|
|
time.sleep(0.2)
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
# Prefer lastcreate(me,R) — deterministic, version-stable.
|
|
|
|
|
# Fall back to %L (current location, set by @dig/teleport)
|
|
|
|
|
# if lastcreate comes back empty for any reason.
|
|
|
|
|
dbref = query_last_created(conn, 'R')
|
2026-03-19 19:14:33 -06:00
|
|
|
if not dbref:
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
dbref = query_think(conn, '%L')
|
|
|
|
|
if dbref and not dbref.startswith('#'):
|
|
|
|
|
dbref = None
|
2026-03-19 19:14:33 -06:00
|
|
|
if dbref:
|
|
|
|
|
objid = capture_objid(dbref)
|
|
|
|
|
state.set_room(op.obj_id, dbref, room, objid=objid)
|
|
|
|
|
log(f' [state] {op.obj_id} = {dbref} (objid: {objid})')
|
2026-03-19 14:07:56 -06:00
|
|
|
else:
|
2026-03-19 19:14:33 -06:00
|
|
|
log(f' [WARNING] Could not capture dbref for {op.obj_id}')
|
|
|
|
|
else:
|
|
|
|
|
state.objects[op.obj_id] = {
|
|
|
|
|
'dbref': f'#DRY_{op.obj_id}',
|
|
|
|
|
'type': 'room',
|
|
|
|
|
'name': room.name,
|
|
|
|
|
'description': room.description,
|
|
|
|
|
'flags': sorted(room.flags),
|
|
|
|
|
'attrs': room.attrs,
|
|
|
|
|
'content_hash': content_hash(room),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do_cmd(f'@desc here={mux_escape(room.description)}')
|
2026-03-19 14:07:56 -06:00
|
|
|
for flag in room.flags:
|
|
|
|
|
do_cmd(f'@set here={flag}')
|
|
|
|
|
for attr_name, attr_value in room.attrs.items():
|
2026-04-10 19:33:57 -06:00
|
|
|
do_cmd(f'&{attr_name} here={escape_attr_value(attr_value)}')
|
2026-03-19 19:14:33 -06:00
|
|
|
if room.parent:
|
|
|
|
|
do_cmd(f'@parent here={room.parent}')
|
|
|
|
|
|
|
|
|
|
for op in room_updates:
|
|
|
|
|
room = op.payload['room']
|
|
|
|
|
dbref = op.payload['dbref']
|
|
|
|
|
log(f'\n--- Room: {room.name} ({op.obj_id}) — updating at {dbref} ---')
|
|
|
|
|
do_cmd(f'@teleport me={dbref}')
|
|
|
|
|
time.sleep(0.2)
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
if op.payload.get('name_changed'):
|
|
|
|
|
do_cmd(f'@name here={room.name}')
|
|
|
|
|
if op.payload.get('desc_changed'):
|
|
|
|
|
do_cmd(f'@desc here={mux_escape(room.description)}')
|
|
|
|
|
for flag in sorted(op.payload.get('flags_added', set())):
|
|
|
|
|
do_cmd(f'@set here={flag}')
|
|
|
|
|
for flag in sorted(op.payload.get('flags_removed', set())):
|
|
|
|
|
do_cmd(f'@set here=!{flag}')
|
|
|
|
|
for attr_name in sorted(op.payload.get('attrs_to_set', {})):
|
2026-04-10 19:33:57 -06:00
|
|
|
do_cmd(f'&{attr_name} here={escape_attr_value(op.payload["attrs_to_set"][attr_name])}')
|
2026-03-19 19:14:33 -06:00
|
|
|
for attr_name in sorted(op.payload.get('attrs_to_unset', set())):
|
|
|
|
|
do_cmd(f'&{attr_name} here=')
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
if not dry_run:
|
|
|
|
|
objid = capture_objid(dbref)
|
|
|
|
|
state.set_room(op.obj_id, dbref, room, objid=objid)
|
|
|
|
|
|
|
|
|
|
# Phase 2: Create exits
|
|
|
|
|
if exit_creates:
|
|
|
|
|
log(f'\n=== Phase 2: Exits ({len(exit_creates)} create) ===')
|
|
|
|
|
|
|
|
|
|
for op in exit_creates:
|
|
|
|
|
ex = op.payload['exit']
|
|
|
|
|
direction = op.payload['direction']
|
|
|
|
|
|
|
|
|
|
if direction == 'forward':
|
|
|
|
|
from_dbref = state.get_dbref(ex.from_room)
|
|
|
|
|
to_dbref = state.get_dbref(ex.to_room)
|
|
|
|
|
if not from_dbref:
|
|
|
|
|
log(f' [ERROR] No dbref for source room: {ex.from_room}')
|
|
|
|
|
continue
|
|
|
|
|
if not to_dbref:
|
|
|
|
|
if ex.to_room.startswith('$'):
|
|
|
|
|
log(f' [SKIP] External reference: {ex.to_room}')
|
|
|
|
|
continue
|
|
|
|
|
log(f' [ERROR] No dbref for destination room: {ex.to_room}')
|
2026-03-19 14:07:56 -06:00
|
|
|
continue
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
do_cmd(f'@teleport me={from_dbref}')
|
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
log(f'\n--- Exit: {op.name} ({ex.from_room} -> {ex.to_room}) ---')
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
do_cmd(f'@open {ex.name}={to_dbref}')
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
if not dry_run:
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
dbref = query_last_created(conn, 'E')
|
2026-03-19 19:14:33 -06:00
|
|
|
if dbref:
|
|
|
|
|
objid = capture_objid(dbref)
|
|
|
|
|
state.set_exit(op.obj_id, dbref, op.name, objid=objid)
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
else:
|
|
|
|
|
log(f' [WARNING] Could not capture dbref for exit {op.obj_id}')
|
2026-03-19 19:14:33 -06:00
|
|
|
else:
|
|
|
|
|
# back exit
|
|
|
|
|
from_dbref = state.get_dbref(ex.to_room)
|
|
|
|
|
to_dbref = state.get_dbref(ex.from_room)
|
|
|
|
|
if not from_dbref or not to_dbref:
|
|
|
|
|
log(f' [ERROR] Missing dbref for back exit: {ex.to_room} -> {ex.from_room}')
|
|
|
|
|
continue
|
2026-03-19 14:07:56 -06:00
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
do_cmd(f'@teleport me={from_dbref}')
|
2026-03-19 14:07:56 -06:00
|
|
|
time.sleep(0.2)
|
2026-03-19 19:14:33 -06:00
|
|
|
log(f'\n--- Back exit: {op.name} ({ex.to_room} -> {ex.from_room}) ---')
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
do_cmd(f'@open {ex.back_name}={to_dbref}')
|
2026-03-19 14:07:56 -06:00
|
|
|
|
|
|
|
|
if not dry_run:
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
dbref = query_last_created(conn, 'E')
|
2026-03-19 14:07:56 -06:00
|
|
|
if dbref:
|
2026-03-19 19:14:33 -06:00
|
|
|
objid = capture_objid(dbref)
|
|
|
|
|
state.set_exit(op.obj_id, dbref, op.name, objid=objid)
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
else:
|
|
|
|
|
log(f' [WARNING] Could not capture dbref for back exit {op.obj_id}')
|
2026-03-19 19:14:33 -06:00
|
|
|
|
|
|
|
|
# Phase 3: Create things
|
|
|
|
|
if thing_creates:
|
|
|
|
|
log(f'\n=== Phase 3: Things ({len(thing_creates)} create) ===')
|
|
|
|
|
|
|
|
|
|
for op in thing_creates:
|
|
|
|
|
thing = op.payload['thing']
|
|
|
|
|
loc_dbref = state.get_dbref(thing.location)
|
|
|
|
|
if not loc_dbref:
|
|
|
|
|
log(f' [ERROR] No dbref for location: {thing.location}')
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
do_cmd(f'@teleport me={loc_dbref}')
|
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
log(f'\n--- Thing: {thing.name} ({op.obj_id}) in {thing.location} ---')
|
|
|
|
|
do_cmd(f'@create {thing.name}')
|
|
|
|
|
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
# Capture the dbref of the thing we just created. The
|
|
|
|
|
# remaining @desc/@set/& commands still target the thing by
|
|
|
|
|
# name because that matches MUX's normal authoring flow, but
|
|
|
|
|
# the dbref + objid are what the state file needs for
|
|
|
|
|
# subsequent reconciler passes.
|
|
|
|
|
thing_dbref = None
|
|
|
|
|
if not dry_run:
|
|
|
|
|
thing_dbref = query_last_created(conn, 'T')
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
if thing.description:
|
|
|
|
|
do_cmd(f'@desc {thing.name}={mux_escape(thing.description)}')
|
|
|
|
|
for flag in thing.flags:
|
|
|
|
|
do_cmd(f'@set {thing.name}={flag}')
|
|
|
|
|
for attr_name, attr_value in thing.attrs.items():
|
2026-04-10 19:33:57 -06:00
|
|
|
do_cmd(f'&{attr_name} {thing.name}={escape_attr_value(attr_value)}')
|
2026-03-19 19:14:33 -06:00
|
|
|
if thing.parent:
|
|
|
|
|
do_cmd(f'@parent {thing.name}={thing.parent}')
|
|
|
|
|
|
Robust dbref capture in WorldBuilder live executor
The live adapter used to parse free-form @dig/@open/@create response
text with a plain "#NNN" regex (extract_dbref) to recover the newly
created object's dbref. That broke whenever the server output format
shifted — the tracker's open item flagged this as
version-fragile. Thing creation also silently dropped its dbref
entirely, leaving state entries unindexed for later reconciler runs.
Replace the parsing path with a sentinel-wrapped `think` round-trip:
think WB1[<expr>]WB2
`think` emits its evaluated argument verbatim on the executor's
output stream, so the response buffer contains the literal bytes
`WB1<value>WB2`. `parse_think_response()` takes the *last* match so
we land on the evaluated output rather than the command echo, which
also carries the sentinel bytes in its un-evaluated form. On top of
that helper, `query_last_created(conn, kind)` wraps
`lastcreate(me, R|E|T|P)` to deterministically pull the just-created
object's dbref without any parsing assumptions.
executor.py's three create paths (rooms, exits, things) now use
query_last_created instead of extract_dbref(resp). Thing creation
also captures its dbref + objid into state, which was missing.
extract_dbref() is removed as dead code. The live_adapter module
docstring documents the one remaining server-format dependency
(`%L`, a MUX core invariant).
test_worldbuilder.py (72), test_diff_v3.py, and test_reconciler.py
(36) still pass. Top-level ISSUES.md index updated to move
WorldBuilder into the fully-closed trackers list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:52:25 -06:00
|
|
|
if thing_dbref:
|
|
|
|
|
objid = capture_objid(thing_dbref)
|
|
|
|
|
state.set_thing(op.obj_id, thing_dbref, thing, objid=objid)
|
|
|
|
|
log(f' [state] {op.obj_id} = {thing_dbref} (objid: {objid})')
|
|
|
|
|
elif not dry_run:
|
|
|
|
|
log(f' [WARNING] Could not capture dbref for thing {op.obj_id}')
|
|
|
|
|
|
2026-03-19 19:14:33 -06:00
|
|
|
# Phase 4: Destroy (exits before rooms)
|
|
|
|
|
if destroy_exits or destroy_rooms:
|
|
|
|
|
log(f'\n=== Phase 4: Destroy ({len(destroy_exits)} exits, {len(destroy_rooms)} rooms) ===')
|
|
|
|
|
|
|
|
|
|
for op in destroy_exits:
|
|
|
|
|
dbref = op.payload['dbref']
|
|
|
|
|
log(f'\n--- Destroy exit: {op.name} ({op.obj_id}) at {dbref} ---')
|
|
|
|
|
do_cmd(f'@destroy {dbref}')
|
|
|
|
|
state.mark_pending_destroy(op.obj_id)
|
|
|
|
|
|
|
|
|
|
for op in destroy_rooms:
|
|
|
|
|
dbref = op.payload['dbref']
|
|
|
|
|
log(f'\n--- Destroy room: {op.name} ({op.obj_id}) at {dbref} ---')
|
|
|
|
|
do_cmd(f'@destroy/override {dbref}')
|
|
|
|
|
state.mark_pending_destroy(op.obj_id)
|
2026-03-19 14:07:56 -06:00
|
|
|
|
|
|
|
|
# Save state
|
|
|
|
|
state.save()
|
|
|
|
|
log(f'\n=== Done. State saved to {state.path} ===')
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# CLI
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Verify — check live game matches spec
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def verify(spec, conn, state, log_file=None):
|
|
|
|
|
"""Verify that a live game matches the spec. Returns list of discrepancies."""
|
|
|
|
|
issues = []
|
|
|
|
|
|
|
|
|
|
def log(msg):
|
|
|
|
|
print(msg)
|
|
|
|
|
if log_file:
|
|
|
|
|
log_file.write(msg + '\n')
|
|
|
|
|
|
|
|
|
|
def query_one(cmd):
|
|
|
|
|
conn.send(cmd)
|
|
|
|
|
time.sleep(0.3)
|
|
|
|
|
resp = conn.read_response(0.3)
|
|
|
|
|
lines = [l.strip() for l in resp.strip().split('\n') if l.strip()]
|
|
|
|
|
return lines[0] if lines else ''
|
|
|
|
|
|
2026-04-10 19:33:57 -06:00
|
|
|
def check_object(spec_id, obj_name, dbref, expected_desc, expected_attrs,
|
|
|
|
|
expected_flags=None, expected_location=None):
|
|
|
|
|
stored_objid = state.objects.get(spec_id, {}).get('objid')
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
if stored_objid:
|
|
|
|
|
live_objid = query_one(f'think [objid({dbref})]')
|
|
|
|
|
if live_objid and live_objid != stored_objid:
|
2026-04-10 19:33:57 -06:00
|
|
|
issues.append(f"{spec_id!r} ({dbref}): objid mismatch — stored=\"{stored_objid}\" live=\"{live_objid}\" (dbref was recycled!)")
|
|
|
|
|
return False
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
if not live_objid or live_objid.startswith('#-1'):
|
2026-04-10 19:33:57 -06:00
|
|
|
issues.append(f"{spec_id!r} ({dbref}): object no longer exists")
|
|
|
|
|
return False
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
live_name = query_one(f'think [name({dbref})]')
|
2026-04-10 19:33:57 -06:00
|
|
|
if live_name and live_name != obj_name:
|
|
|
|
|
issues.append(f"{spec_id!r} ({dbref}): name mismatch — spec=\"{obj_name}\" live=\"{live_name}\"")
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
live_desc = query_one(f'think [get({dbref}/DESCRIBE)]')
|
|
|
|
|
if not live_desc:
|
|
|
|
|
live_desc = query_one(f'think [get({dbref}/DESC)]')
|
2026-04-10 19:33:57 -06:00
|
|
|
if not live_desc and expected_desc.strip():
|
|
|
|
|
issues.append(f"{spec_id!r} ({dbref}): description missing on live server")
|
|
|
|
|
elif live_desc != expected_desc:
|
|
|
|
|
issues.append(f"{spec_id!r} ({dbref}): description mismatch")
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
2026-04-10 19:33:57 -06:00
|
|
|
for attr_name, expected_val in expected_attrs.items():
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
live_val = query_one(f'think [get({dbref}/{attr_name})]')
|
|
|
|
|
if live_val != expected_val:
|
2026-04-10 19:33:57 -06:00
|
|
|
issues.append(f"{spec_id!r} ({dbref}): attr {attr_name} mismatch — spec=\"{expected_val}\" live=\"{live_val}\"")
|
|
|
|
|
|
|
|
|
|
if expected_flags is not None:
|
|
|
|
|
live_flags = query_one(f'think [flags({dbref})]')
|
|
|
|
|
live_flag_set = set(live_flags.split()) if live_flags else set()
|
|
|
|
|
expected_flag_set = set(expected_flags)
|
|
|
|
|
if live_flag_set != expected_flag_set:
|
|
|
|
|
issues.append(f"{spec_id!r} ({dbref}): flags mismatch — spec=\"{' '.join(sorted(expected_flag_set))}\" live=\"{' '.join(sorted(live_flag_set))}\"")
|
|
|
|
|
|
|
|
|
|
if expected_location is not None:
|
|
|
|
|
live_location = query_one(f'think [loc({dbref})]')
|
|
|
|
|
if live_location != expected_location:
|
|
|
|
|
issues.append(f"{spec_id!r} ({dbref}): location mismatch — spec=\"{expected_location}\" live=\"{live_location}\"")
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
log(f"Verifying {len(spec.rooms)} rooms...")
|
|
|
|
|
|
|
|
|
|
for room_id, room in spec.rooms.items():
|
|
|
|
|
dbref = state.get_dbref(room_id)
|
|
|
|
|
if not dbref:
|
|
|
|
|
issues.append(f"Room '{room_id}' ({room.name}): not in state file (never applied?)")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
check_object(
|
|
|
|
|
room_id,
|
|
|
|
|
room.name,
|
|
|
|
|
dbref,
|
|
|
|
|
room.description,
|
|
|
|
|
room.attrs,
|
|
|
|
|
expected_flags=room.flags,
|
|
|
|
|
)
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
log(f" [{dbref}] {room.name} — {'OK' if not any(room_id in i for i in issues) else 'MISMATCH'}")
|
|
|
|
|
|
|
|
|
|
# Check exits exist
|
|
|
|
|
log(f"Verifying exits...")
|
|
|
|
|
for ex in spec.exits:
|
|
|
|
|
to_dbref = state.get_dbref(ex.to_room)
|
2026-04-10 19:33:57 -06:00
|
|
|
forward_id = f'exit_{ex.from_room}_{ex.to_room}'
|
|
|
|
|
forward_state = state.objects.get(forward_id)
|
|
|
|
|
if not forward_state:
|
|
|
|
|
issues.append(f"Exit '{forward_id}' ({ex.name}): not in state file (never applied?)")
|
|
|
|
|
elif not to_dbref:
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
exit_dbref = forward_state.get('dbref')
|
|
|
|
|
live_objid = query_one(f'think [objid({exit_dbref})]')
|
|
|
|
|
stored_objid = forward_state.get('objid')
|
|
|
|
|
if stored_objid and live_objid and live_objid != stored_objid:
|
|
|
|
|
issues.append(f"Exit '{forward_id}' ({exit_dbref}): objid mismatch — stored=\"{stored_objid}\" live=\"{live_objid}\" (dbref was recycled!)")
|
|
|
|
|
elif not live_objid or live_objid.startswith('#-1'):
|
|
|
|
|
issues.append(f"Exit '{forward_id}' ({exit_dbref}): object no longer exists")
|
|
|
|
|
else:
|
|
|
|
|
live_name = query_one(f'think [name({exit_dbref})]')
|
|
|
|
|
expected_name = ex.name.split(';')[0]
|
|
|
|
|
if live_name and live_name != expected_name:
|
|
|
|
|
issues.append(f"Exit '{forward_id}' ({exit_dbref}): name mismatch — spec=\"{expected_name}\" live=\"{live_name}\"")
|
|
|
|
|
live_dest = query_one(f'think [home({exit_dbref})]')
|
|
|
|
|
if live_dest != to_dbref:
|
|
|
|
|
issues.append(f"Exit '{forward_id}' ({exit_dbref}): destination mismatch — spec=\"{to_dbref}\" live=\"{live_dest}\"")
|
|
|
|
|
|
|
|
|
|
if ex.back_name:
|
|
|
|
|
back_id = f'exit_{ex.to_room}_{ex.from_room}'
|
|
|
|
|
back_state = state.objects.get(back_id)
|
|
|
|
|
from_dbref = state.get_dbref(ex.from_room)
|
|
|
|
|
if not back_state:
|
|
|
|
|
issues.append(f"Exit '{back_id}' ({ex.back_name}): not in state file (never applied?)")
|
|
|
|
|
elif not from_dbref:
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
exit_dbref = back_state.get('dbref')
|
|
|
|
|
live_objid = query_one(f'think [objid({exit_dbref})]')
|
|
|
|
|
stored_objid = back_state.get('objid')
|
|
|
|
|
if stored_objid and live_objid and live_objid != stored_objid:
|
|
|
|
|
issues.append(f"Exit '{back_id}' ({exit_dbref}): objid mismatch — stored=\"{stored_objid}\" live=\"{live_objid}\" (dbref was recycled!)")
|
|
|
|
|
elif not live_objid or live_objid.startswith('#-1'):
|
|
|
|
|
issues.append(f"Exit '{back_id}' ({exit_dbref}): object no longer exists")
|
|
|
|
|
else:
|
|
|
|
|
live_name = query_one(f'think [name({exit_dbref})]')
|
|
|
|
|
expected_name = ex.back_name.split(';')[0]
|
|
|
|
|
if live_name and live_name != expected_name:
|
|
|
|
|
issues.append(f"Exit '{back_id}' ({exit_dbref}): name mismatch — spec=\"{expected_name}\" live=\"{live_name}\"")
|
|
|
|
|
live_dest = query_one(f'think [home({exit_dbref})]')
|
|
|
|
|
if live_dest != from_dbref:
|
|
|
|
|
issues.append(f"Exit '{back_id}' ({exit_dbref}): destination mismatch — spec=\"{from_dbref}\" live=\"{live_dest}\"")
|
|
|
|
|
|
|
|
|
|
log(f"Verifying things...")
|
|
|
|
|
for thing_id, thing in spec.things.items():
|
|
|
|
|
dbref = state.get_dbref(thing_id)
|
|
|
|
|
if not dbref:
|
|
|
|
|
issues.append(f"Thing '{thing_id}' ({thing.name}): not in state file (never applied?)")
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
continue
|
2026-04-10 19:33:57 -06:00
|
|
|
|
|
|
|
|
expected_location = state.get_dbref(thing.location)
|
|
|
|
|
check_object(
|
|
|
|
|
thing_id,
|
|
|
|
|
thing.name,
|
|
|
|
|
dbref,
|
|
|
|
|
thing.description,
|
|
|
|
|
thing.attrs,
|
|
|
|
|
expected_flags=thing.flags,
|
|
|
|
|
expected_location=expected_location,
|
|
|
|
|
)
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
if not issues:
|
|
|
|
|
log("\nVerification passed — all rooms match spec.")
|
|
|
|
|
else:
|
|
|
|
|
log(f"\nVerification found {len(issues)} issue(s):")
|
|
|
|
|
for issue in issues:
|
|
|
|
|
log(f" - {issue}")
|
|
|
|
|
|
|
|
|
|
return issues
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Rollback — destroy objects in reverse order
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def rollback(spec, conn, state, dry_run=False, log_file=None):
|
|
|
|
|
"""Destroy all objects created by a prior apply, in reverse order."""
|
|
|
|
|
issues = []
|
|
|
|
|
|
|
|
|
|
def log(msg):
|
|
|
|
|
print(msg)
|
|
|
|
|
if log_file:
|
|
|
|
|
log_file.write(msg + '\n')
|
|
|
|
|
|
|
|
|
|
def do_cmd(cmd):
|
|
|
|
|
if dry_run:
|
|
|
|
|
log(f" [dry-run] {cmd}")
|
2026-03-19 18:50:18 -06:00
|
|
|
return ''
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
log(f" > {cmd}")
|
|
|
|
|
conn.send(cmd)
|
|
|
|
|
time.sleep(0.3)
|
|
|
|
|
resp = conn.read_response(0.3)
|
|
|
|
|
if resp.strip():
|
|
|
|
|
for line in resp.strip().split('\n'):
|
|
|
|
|
log(f" < {line.rstrip()}")
|
2026-03-19 18:50:18 -06:00
|
|
|
return resp
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
# Collect all dbrefs from state, exits first then rooms
|
|
|
|
|
exits_to_destroy = []
|
|
|
|
|
rooms_to_destroy = []
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
skipped = []
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
for spec_id, obj in state.objects.items():
|
|
|
|
|
dbref = obj.get('dbref', '')
|
|
|
|
|
if not dbref or dbref.startswith('#DRY'):
|
|
|
|
|
continue
|
|
|
|
|
if obj.get('type') == 'exit':
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
exits_to_destroy.append((spec_id, dbref, obj.get('name', ''), obj.get('objid')))
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
elif obj.get('type') == 'room':
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
rooms_to_destroy.append((spec_id, dbref, obj.get('name', ''), obj.get('objid')))
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
if not exits_to_destroy and not rooms_to_destroy:
|
|
|
|
|
log("Nothing to rollback — state is empty.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
log(f"Rolling back: {len(exits_to_destroy)} exits + {len(rooms_to_destroy)} rooms")
|
|
|
|
|
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
def verify_objid(dbref, stored_objid, spec_id, name):
|
|
|
|
|
"""Verify objid before destroying. Returns True if safe to destroy."""
|
|
|
|
|
if not stored_objid:
|
|
|
|
|
return True # No objid stored (legacy state), proceed on dbref alone
|
|
|
|
|
if dry_run:
|
|
|
|
|
return True
|
|
|
|
|
live_objid = do_cmd(f'think [objid({dbref})]')
|
|
|
|
|
live_objid = live_objid.strip() if live_objid else ''
|
|
|
|
|
if live_objid and live_objid != stored_objid:
|
|
|
|
|
log(f" [SKIP] {name} ({dbref}): objid mismatch — stored={stored_objid} live={live_objid} (dbref recycled, not ours!)")
|
|
|
|
|
skipped.append(spec_id)
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
# Destroy exits first
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
for spec_id, dbref, name, objid in exits_to_destroy:
|
|
|
|
|
if not verify_objid(dbref, objid, spec_id, name):
|
|
|
|
|
continue
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
log(f"\n--- Destroy exit: {name} ({dbref}) ---")
|
|
|
|
|
do_cmd(f'@destroy {dbref}')
|
|
|
|
|
|
|
|
|
|
# Then rooms
|
WorldBuilder v3: objid() identity, known defects audit, deferred destroy
Replace Virtual ID / &WB_ID design with objid() for identity tracking.
State file bumped to v4 with objid stored alongside dbref. Executor,
importer, and verify all capture and validate objid to detect dbref
recycling.
Add Known Defects section (D1-D8) documenting architectural gaps in v2
code that must be resolved before v3 features: attribute escaping
ambiguity, Things not tracked, teleport/here fragility, lossy exit
identity, silent command failures, missing zone creation, inconsistent
description escaping, and dry-run state pollution.
Document deferred @destroy semantics — GOING flag, @undestroy race,
pending_destroy state tracking, re-creation race conditions.
Restructure implementation order into three phases: fix foundations,
complete object model, then build v3 features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:10:55 -06:00
|
|
|
for spec_id, dbref, name, objid in rooms_to_destroy:
|
|
|
|
|
if not verify_objid(dbref, objid, spec_id, name):
|
|
|
|
|
continue
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
log(f"\n--- Destroy room: {name} ({dbref}) ---")
|
|
|
|
|
do_cmd(f'@destroy/override {dbref}')
|
|
|
|
|
|
2026-03-19 18:50:18 -06:00
|
|
|
# Destruction is deferred in TinyMUX, so keep entries and mark them
|
|
|
|
|
# pending_destroy until a later reconcile/verify confirms removal.
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
if not dry_run:
|
2026-03-19 18:50:18 -06:00
|
|
|
for spec_id, _, _, _ in exits_to_destroy:
|
|
|
|
|
if spec_id not in skipped:
|
|
|
|
|
state.mark_pending_destroy(spec_id)
|
|
|
|
|
for spec_id, _, _, _ in rooms_to_destroy:
|
|
|
|
|
if spec_id not in skipped:
|
|
|
|
|
state.mark_pending_destroy(spec_id)
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
state.save()
|
2026-03-19 18:50:18 -06:00
|
|
|
log(f"\nState updated with pending_destroy markers. Saved to {state.path}")
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
else:
|
2026-03-19 18:50:18 -06:00
|
|
|
log(f"\n[dry-run] State would be updated with pending_destroy markers.")
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
|
|
|
|
|
|
2026-03-19 14:07:56 -06:00
|
|
|
def main():
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description='WorldBuilder Executor — apply specs to a live MUX')
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
parser.add_argument('action', nargs='?', default='apply',
|
|
|
|
|
choices=['apply', 'verify', 'rollback'],
|
|
|
|
|
help='Action (default: apply)')
|
2026-03-19 14:07:56 -06:00
|
|
|
parser.add_argument('spec', help='Path to YAML spec file')
|
|
|
|
|
parser.add_argument('--host', required=True, help='MUX hostname')
|
|
|
|
|
parser.add_argument('--port', default='4201', help='MUX port')
|
|
|
|
|
parser.add_argument('--ssl', action='store_true', help='Use SSL/TLS')
|
2026-04-04 17:44:10 -06:00
|
|
|
parser.add_argument('--insecure', action='store_true',
|
|
|
|
|
help='Disable TLS certificate and hostname verification')
|
2026-03-19 14:07:56 -06:00
|
|
|
parser.add_argument('--character', required=True, help='Builder character name')
|
|
|
|
|
parser.add_argument('--password', required=True, help='Builder password')
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
parser.add_argument('--state', help='State file path')
|
2026-03-19 14:07:56 -06:00
|
|
|
parser.add_argument('--dry-run', action='store_true', help='Log commands without executing')
|
|
|
|
|
parser.add_argument('--log', help='Log file path')
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
spec_path = Path(args.spec)
|
|
|
|
|
|
|
|
|
|
# Parse and validate
|
|
|
|
|
spec = parse_spec(spec_path)
|
|
|
|
|
drc = check_drc(spec)
|
|
|
|
|
if not drc.ok:
|
|
|
|
|
print("DRC errors — cannot apply:")
|
|
|
|
|
for err in drc.errors:
|
|
|
|
|
print(f" ERROR: {err}")
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
# State file
|
|
|
|
|
state_path = args.state or f'.worldbuilder/{spec.zone.name.lower().replace(" ", "_")}.state.yaml'
|
|
|
|
|
state = StateFile(state_path)
|
|
|
|
|
|
|
|
|
|
# Log file
|
|
|
|
|
log_file = open(args.log, 'w') if args.log else None
|
|
|
|
|
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
action = args.action
|
|
|
|
|
|
2026-03-19 14:07:56 -06:00
|
|
|
try:
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
if args.dry_run and action == 'apply':
|
2026-03-19 14:07:56 -06:00
|
|
|
print(f"Dry run — no commands will be sent to {args.host}:{args.port}")
|
|
|
|
|
execute(spec, None, state, dry_run=True, log_file=log_file)
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
elif args.dry_run and action == 'rollback':
|
|
|
|
|
print(f"Dry run rollback — no commands will be sent")
|
|
|
|
|
rollback(spec, None, state, dry_run=True, log_file=log_file)
|
2026-03-19 14:07:56 -06:00
|
|
|
else:
|
|
|
|
|
# Connect
|
|
|
|
|
print(f"Connecting to {args.host}:{args.port}...")
|
2026-04-04 17:44:10 -06:00
|
|
|
conn = MuxConnection(args.host, int(args.port), args.ssl,
|
|
|
|
|
verify_ssl=not args.insecure)
|
2026-03-19 14:07:56 -06:00
|
|
|
conn.connect()
|
|
|
|
|
print("Connected. Logging in...")
|
|
|
|
|
|
|
|
|
|
resp = conn.login(args.character, args.password)
|
|
|
|
|
if 'Invalid' in resp or 'incorrect' in resp.lower():
|
|
|
|
|
print(f"Login failed: {resp.strip()}")
|
|
|
|
|
conn.disconnect()
|
|
|
|
|
return 1
|
|
|
|
|
print("Logged in.")
|
|
|
|
|
|
|
|
|
|
try:
|
WorldBuilder: procedural grids, verify, rollback, things, components
Procedural generation:
Grid generator creates WxH rooms with cardinal exits
Seeded-random description selection (deterministic)
Density parameter controls connectivity (0.0-1.0)
Edge ports for wiring grid to external rooms
forest_area.yaml — 7 description variants
street_grid.yaml — 5 description variants
Verify command (executor.py verify):
Connects to live game, checks rooms match spec
Validates names, descriptions, attributes against state
Reports discrepancies
Rollback command (executor.py rollback):
Destroys objects in reverse order (exits first, then rooms)
Clears state file after destruction
Dry-run mode supported
Thing support:
Thing model (name, location, description, flags, attrs, parent)
Parsed from spec YAML and component definitions
Compiled to @create + @desc + &attr + @parent commands
Shown in plan output
Component things expand with parameter substitution
New components:
tavern.yaml — 3 rooms + barkeeper NPC thing
street_grid.yaml — procedural city block grid
Test specs:
town.yaml — combines tavern + street_grid + manual rooms/things
forest.yaml — 4x4 procedural forest with edge connection
Zone support:
@chzone emitted in compiler for all rooms
@parent emitted when room has parent field
All 4 test specs pass DRC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:28:34 -06:00
|
|
|
if action == 'apply':
|
|
|
|
|
execute(spec, conn, state, log_file=log_file)
|
|
|
|
|
elif action == 'verify':
|
|
|
|
|
issues = verify(spec, conn, state, log_file=log_file)
|
|
|
|
|
return 0 if not issues else 1
|
|
|
|
|
elif action == 'rollback':
|
|
|
|
|
rollback(spec, conn, state, log_file=log_file)
|
2026-03-19 14:07:56 -06:00
|
|
|
finally:
|
|
|
|
|
conn.disconnect()
|
|
|
|
|
print("Disconnected.")
|
|
|
|
|
finally:
|
|
|
|
|
if log_file:
|
|
|
|
|
log_file.close()
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
exit(main())
|