| name | runtime |
| description | Dual Python/JavaScript adventure runtimes — always in sync |
| allowed-tools | ["read_file","write_file"] |
| tier | 1 |
| protocol | DUAL-RUNTIME |
| tags | ["moollm","runtime","python","javascript","compilation","sync"] |
| related | ["context","adventure","object","expression"] |
| adversary | single-target |
Runtime
"Test in Python, deploy to browser. Same semantics, both targets."
— Vanessa Freudenberg (memorial voice)
What Is It?
The Runtime skill defines the dual Python/JavaScript adventure engines. Both runtimes:
- Execute compiled expressions
- Manage world state
- Process simulation ticks
- Handle events
CRITICAL: We ALWAYS generate BOTH _js AND _py code. Never one without the other.
Why Dual Runtimes?
| Runtime | Purpose |
|---|
| Python | Server-side, testing, LLM tethering, CLI |
| JavaScript | Browser, standalone play, offline |
dual_runtime:
source: "YAML"
compiler: "LLM"
outputs:
guard_js: "(world) => ..."
guard_py: "lambda world: ..."
runtimes:
python:
file: "adventure.py"
targets: ["CLI", "Server"]
javascript:
file: "engine.js"
targets: ["Browser", "SPA"]
The World Object
Both runtimes implement identical World classes:
Python
class World:
"""Adventure runtime context for Python."""
def __init__(self, adventure_data: dict):
self.turn = 0
self.timestamp = None
self.adventure = adventure_data
self.player = adventure_data.get('player', {})
self.room = None
self.party = adventure_data.get('party', {})
self.object = None
self.target = None
self.npc = None
self.skills = DotDict()
def has(self, item_id: str) -> bool:
return item_id in self.player.get('inventory', [])
def has_tag(self, tag: ) -> :
item_id .player.get(, []):
item = .get_object(item_id)
item tag item.get(, []):
() -> :
results = []
item_id .player.get(, []):
item = .get_object(item_id)
item tag item.get(, []):
results.append(item)
in_room .room:
obj .room.get(, []):
tag obj.get(, []):
results.append(obj)
results
():
inv = .player.setdefault(, [])
inv.append(item_id)
():
inv = .player.get(, [])
item_id inv:
inv.remove(item_id)
() -> :
.adventure.get(, {}).get(name, )
():
flags = .adventure.setdefault(, {})
flags[name] = value
():
._output_queue.append(message)
():
._output_queue.append({: message, : style})
():
._event_queue.append({: name, : data {}})
():
._pending_navigation = destination
() -> :
exits = .room.get(, {})
direction exits
() -> :
buffs = .player.get(, [])
(b.get() == buff_id b buffs)
():
buffs = .player.setdefault(, [])
buffs.append(buff)
():
buffs = .player.get(, [])
.player[] = [b b buffs b.get() != buff_id]
():
obj = obj .
obj obj:
state = obj[]
key (state.keys()):
key.endswith():
effective_key =
state[effective_key] = state[key]
():
state = obj.get(, {})
effective_key =
effective_key state:
state[effective_key]
state.get(prop)
():
state = obj.get(, {})
effective_key =
state[effective_key] = state.get(effective_key, state.get(prop, )) + delta
():
state = obj.get(, {})
effective_key =
state[effective_key] = state.get(effective_key, state.get(prop, )) * factor
() -> :
obj = obj .
obj:
{}
obj:
obj[] = {}
defaults = obj.get(, {})
key, default_val defaults.items():
key obj[]:
obj[][key] = default_val
obj[]
():
state = .ensure_defaults(obj)
key, val (state.items()):
(val, (, )) val < :
state[key] =
state
():
parts = path.split()
obj =
part parts:
(obj, ):
obj = obj.get(part)
:
obj = (obj, part, )
obj :
obj
():
parts = path.split()
obj =
part parts[:-]:
(obj, ):
obj = obj.setdefault(part, {})
:
obj = (obj, part)
(obj, ):
obj[parts[-]] = value
:
(obj, parts[-], value)
JavaScript
class World {
constructor(adventureData) {
this.turn = 0;
this.timestamp = null;
this.adventure = adventureData;
this.player = adventureData.player || {};
this.room = null;
this.party = adventureData.party || {};
this.object = null;
this.target = null;
this.npc = null;
this.skills = {};
this._outputQueue = [];
this._eventQueue = [];
this._pendingNavigation = ;
}
() {
(.. || []).(itemId);
}
() {
( itemId .. || []) {
item = .(itemId);
(item && (item. || []).(tag)) {
;
}
}
;
}
() {
results = [];
( itemId .. || []) {
item = .(itemId);
(item && (item. || []).(tag)) {
results.(item);
}
}
(inRoom && .) {
( obj .. || []) {
((obj. || []).(tag)) {
results.(obj);
}
}
}
results;
}
() {
.. = .. || [];
...(itemId);
}
() {
inv = .. || [];
idx = inv.(itemId);
(idx > -) inv.(idx, );
}
() {
(.. || {})[name] || ;
}
() {
.. = .. || {};
..[name] = value;
}
() {
..(message);
}
() {
..({ : message, style });
}
() {
..({ name, data });
}
() {
. = destination;
}
() {
exits = .?. || {};
direction exits;
}
() {
buffs = .. || [];
buffs.( b. === buffId);
}
() {
.. = .. || [];
...(buff);
}
() {
buffs = .. || [];
.. = buffs.( b. !== buffId);
}
() {
obj = obj || .;
(!obj || !obj.) ;
state = obj.;
( key .(state)) {
(!key.()) {
effectiveKey = ;
state[effectiveKey] = state[key];
}
}
}
() {
state = obj. || {};
effectiveKey = ;
(effectiveKey state) {
state[effectiveKey];
}
state[prop];
}
() {
state = obj. || {};
effectiveKey = ;
state[effectiveKey] = (state[effectiveKey] ?? state[prop] ?? ) + delta;
}
() {
state = obj. || {};
effectiveKey = ;
state[effectiveKey] = (state[effectiveKey] ?? state[prop] ?? ) * factor;
}
() {
obj = obj || .;
(!obj) {};
obj. = obj. || {};
defaults = obj. || {};
( [key, defaultVal] .(defaults)) {
(!(key obj.)) {
obj.[key] = defaultVal;
}
}
obj.;
}
() {
state = .(obj);
( [key, val] .(state)) {
( val === && val < ) {
state[key] = ;
}
}
state;
}
() {
parts = path.();
obj = ;
( part parts) {
obj = obj?.[part];
(obj === ) ;
}
obj;
}
() {
parts = path.();
obj = ;
( part parts.(, -)) {
obj[part] = obj[part] || {};
obj = obj[part];
}
obj[parts[parts. - ]] = value;
}
}
Compiled Expression Format
Natural language compiles to BOTH targets:
guard: "player has brass-key AND room is not dark"
guard_js: (world) => world.has("brass-key") && !world.room.is_dark
guard_py: lambda world: world.has("brass-key") and not world.room.is_dark
Expression Types
| Type | JavaScript | Python |
|---|
| boolean | (world) => ... | lambda world: ... |
| action | (world) => { ... } | def action(world): ... |
| method | (world, arg) => { ... } | def method(world, arg): ... |
| closure | (world) => { ... } | def closure(world): ... |
Complex Example: Object Methods
methods:
consume_fuel: "reduce fuel by amount, minimum 0"
extinguish: "set lit to false, emit darkness event"
methods_js:
consume_fuel: (world, amount = 1) => {
world.object.state.fuel = Math.max(0, world.object.state.fuel - amount);
}
extinguish: (world) => {
world.object.state.lit = false;
world.emit("Darkness falls.");
world.triggerEvent("DARKNESS");
}
methods_py:
consume_fuel: |
def consume_fuel(world, amount=1):
world.object.state['fuel'] = max(0, world.object.state['fuel'] - amount)
extinguish: |
def extinguish(world):
world.object.state['lit'] = False
world.emit("Darkness falls.")
world.trigger_event("DARKNESS")
Simulation Loop
Both runtimes implement the same simulation loop with effective value phases:
simulation_tick:
- phase: 1. RESET
action: "foo_effective = foo (reset to base)"
- phase: 2. BUFFS
action: "Apply buff modifiers to _effective values"
- phase: 3. SIMULATE
action: "Objects run simulate(), modify _effective"
- phase: 4. MAIL
action: "Deliver queued messages (deterministic!)"
- phase: 5. EVENTS
action: "Process event queue"
- phase: 6. NAVIGATE
action: "Move player if requested"
- phase: 7. DISPLAY
action: "UI shows _effective with base comparison"
Phase 4: MAIL — Deterministic Message Delivery
Messages are queued during simulation, then delivered deterministically:
for message in world.skills.postal.get('outgoing', []):
routing = message.get('routing')
for transfer in routing.get('attachments_transfer', []):
if transfer['action'] == 'send':
move_item(world, transfer['ref'],
from_path=transfer['from_inventory'],
to_path=transfer['to_inventory'])
if routing['delivery_point'] == 'inbox':
add_to_inbox(world, routing['inbox_path'], message)
message['status'] = 'delivered'
message['delivered'] = world.timestamp
for trigger in routing.get('triggers', []):
world.trigger_event(trigger['event'], trigger['data'])
world.skills.postal['outgoing'] = []
See skills/postal/ROUTING.md for full routing documentation.
def tick(world: World):
world.turn += 1
world.timestamp = datetime.now().isoformat()
for obj in world.room.get('objects', []):
world.reset_effective(obj)
world.reset_effective(world.player)
for buff in world.player.get('buffs', []):
if buff.get('effect_py'):
exec(buff['effect_py'])(world)
if buff.get('duration'):
buff['duration'] -= 1
world.player['buffs'] = [b for b in world.player.get('buffs', [])
if b.get('duration', 1) > 0]
for obj in world.room.get('objects', []):
if obj.get('simulate_py'):
world.object = obj
exec(obj['simulate_py'])(world)
world.object = None
for event in world._event_queue:
handle_event(world, event)
world._event_queue.clear()
world._pending_navigation:
navigate(world, world._pending_navigation)
world._pending_navigation =
function tick(world) {
world.turn += 1;
world.timestamp = new Date().toISOString();
for (const obj of world.room?.objects || []) {
world.resetEffective(obj);
}
world.resetEffective(world.player);
for (const buff of world.player.buffs || []) {
if (buff.effect_js) {
buff.effect_js(world);
}
if (buff.duration !== undefined) {
buff.duration -= 1;
}
}
world.player.buffs = (world.player.buffs || [])
.filter(b => (b.duration ?? 1) > 0);
for (const obj of world.room?. || []) {
(obj.) {
world. = obj;
obj.(world);
world. = ;
}
}
( event world.) {
(world, event);
}
world. = [];
(world.) {
(world, world.);
world. = ;
}
}
Testing Parity
Both runtimes should produce identical results:
def test_lamp_simulation():
world = World(load_adventure())
world.room = load_room("start/")
lamp = find_object(world, "brass-lantern")
lamp.state = {"lit": True, "fuel": 2}
world.object = lamp
exec(lamp.simulate_py)(world)
assert lamp.state["fuel"] == 1
exec(lamp.simulate_py)(world)
assert lamp.state["fuel"] == 0
assert lamp.state["lit"] == False
assert "GRUE_APPROACHES" in [e["name"] for e in world._event_queue]
Same test runs in JavaScript:
function testLampSimulation() {
const world = new World(loadAdventure());
world.room = loadRoom("start/");
const lamp = findObject(world, "brass-lantern");
lamp.state = { lit: true, fuel: 2 };
world.object = lamp;
lamp.simulate_js(world);
assert(lamp.state.fuel === 1);
lamp.simulate_js(world);
assert(lamp.state.fuel === 0);
assert(lamp.state.lit === false);
assert(world._eventQueue.some(e => e.name === "GRUE_APPROACHES"));
}
Protocol Symbol
DUAL-RUNTIME — Python + JavaScript, always in sync