Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/SimHacker/moollm --skill runtime명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| 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 |
"Test in Python, deploy to browser. Same semantics, both targets." — Vanessa Freudenberg (memorial voice)
The Runtime skill defines the dual Python/JavaScript adventure engines. Both runtimes:
CRITICAL: We ALWAYS generate BOTH _js AND _py code. Never one without the other.
| Runtime | Purpose |
|---|---|
| Python | Server-side, testing, LLM tethering, CLI |
| JavaScript | Browser, standalone play, offline |
# Dual Runtime Architecture
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"]
Both runtimes implement identical World classes:
class World:
"""Adventure runtime context for Python."""
def __init__(self, adventure_data: dict):
# Standard keys
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', {})
# Extended keys (set contextually)
self.object = None # During object simulation
self.target = None # During targeted action
self.npc = None # During NPC simulation
# Skill state namespaces
self.skills = DotDict() # world.skills.economy.gold
# INVENTORY
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)
class World {
/** Adventure runtime context for JavaScript. */
constructor(adventureData) {
// Standard keys
this.turn = 0;
this.timestamp = null;
this.adventure = adventureData;
this.player = adventureData.player || {};
this.room = null;
this.party = adventureData.party || {};
// Extended keys (set contextually)
this.object = null; // During object simulation
this.target = null; // During targeted action
this.npc = null; // During NPC simulation
// Skill state namespaces
this.skills = {}; // world.skills.economy.gold
// Internal queues
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;
}
}
Natural language compiles to BOTH targets:
# Source
guard: "player has brass-key AND room is not dark"
# Generated (BOTH always!)
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
| Type | JavaScript | Python |
|---|---|---|
| boolean | (world) => ... | lambda world: ... |
| action | (world) => { ... } | def action(world): ... |
| method | (world, arg) => { ... } | def method(world, arg): ... |
| closure | (world) => { ... } | def closure(world): ... |
# Source
methods:
consume_fuel: "reduce fuel by amount, minimum 0"
extinguish: "set lit to false, emit darkness event"
# Generated
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")
Both runtimes implement the same simulation loop with effective value phases:
# Simulation tick 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"
Messages are queued during simulation, then delivered deterministically:
# PHASE 4: Deliver mail (no LLM needed!)
for message in world.skills.postal.get('outgoing', []):
routing = message.get('routing')
# Transfer attachments
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'])
# Deliver to inbox
if routing['delivery_point'] == 'inbox':
add_to_inbox(world, routing['inbox_path'], message)
# Update status
message['status'] = 'delivered'
message['delivered'] = world.timestamp
# Trigger events (goal completion, etc)
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.
# Python
def tick(world: World):
world.turn += 1
world.timestamp = datetime.now().isoformat()
# PHASE 1: Reset all effective values to base
for obj in world.room.get('objects', []):
world.reset_effective(obj)
world.reset_effective(world.player)
# PHASE 2: Apply buffs
for buff in world.player.get('buffs', []):
if buff.get('effect_py'):
exec(buff['effect_py'])(world)
# Decrement duration, remove expired buffs
if buff.get('duration'):
buff['duration'] -= 1
world.player['buffs'] = [b for b in world.player.get('buffs', [])
if b.get('duration', 1) > 0]
# PHASE 3: Simulate all objects in current room
for obj in world.room.get('objects', []):
if obj.get('simulate_py'):
world.object = obj
exec(obj['simulate_py'])(world)
world.object = None
# PHASE 4: Process event queue (actions)
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 =
// JavaScript
function tick(world) {
world.turn += 1;
world.timestamp = new Date().toISOString();
// PHASE 1: Reset all effective values to base
for (const obj of world.room?.objects || []) {
world.resetEffective(obj);
}
world.resetEffective(world.player);
// PHASE 2: Apply buffs
for (const buff of world.player.buffs || []) {
if (buff.effect_js) {
buff.effect_js(world);
}
// Decrement duration
if (buff.duration !== undefined) {
buff.duration -= 1;
}
}
// Remove expired buffs
world.player.buffs = (world.player.buffs || [])
.filter(b => (b.duration ?? 1) > 0);
// PHASE 3: Simulate all objects in current room
for (const obj of world.room?. || []) {
(obj.) {
world. = obj;
obj.(world);
world. = ;
}
}
( event world.) {
(world, event);
}
world. = [];
(world.) {
(world, world.);
world. = ;
}
}
Both runtimes should produce identical results:
# Test in Python
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
# Tick 1: fuel 2 → 1
exec(lamp.simulate_py)(world)
assert lamp.state["fuel"] == 1
# Tick 2: fuel 1 → 0, lamp goes out
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:
// Test 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;
// Tick 1: fuel 2 → 1
lamp.simulate_js(world);
assert(lamp.state.fuel === 1);
// Tick 2: fuel 1 → 0, lamp goes out
lamp.simulate_js(world);
assert(lamp.state.fuel === 0);
assert(lamp.state.lit === false);
assert(world._eventQueue.some(e => e.name === "GRUE_APPROACHES"));
}
DUAL-RUNTIME — Python + JavaScript, always in sync