Context
"The context IS the world as seen from inside the closure." — Dave Ungar, on lexical scope
What Is It?
The world object is passed to every compiled closure. It provides:
- •Standard keys — Always present (adventure, player, room, turn)
- •Extended keys — Contextual (object, target, npc)
- •Skill namespaces — Skills register state under
world.skills.skill_name - •Utility functions — API for interacting with the world
Why "world" not "ctx"?
- •More evocative — closures see the WORLD
- •Self-documenting —
world.player,world.room - •Matches the mental model
Standard Keys
Always present in every world:
world.turn // Current simulation turn world.timestamp // ISO timestamp world.adventure // Root adventure state .name .flags // Global boolean flags .world_state // Global key/value state world.player // Current player .id .name .location // Path to current room .inventory // Array of item ids .buffs // Active buffs world.room // Current room .id .name .path .exits .objects .is_dark .is_dangerous world.party // Party state .members .leader
Extended Keys
Present when relevant:
// When running object simulate/methods: world.object // The object being simulated .id .state // Object's mutable state // Methods are bound: world.consume_fuel(1) // When action targets something: world.target // The target .id .type // "object", "character", "room" // When NPC is simulating: world.npc // The NPC .id .goals .state
Skill State Namespaces
Skills register state under world.skills.<skill_name> using underscores:
// Skill "economy" → world.skills.economy world.skills.economy.gold // 100 // Skill "pie-menu" → world.skills.pie_menu (underscore!) world.skills.pie_menu.last_selection // "north" // Skill "time" → world.skills.time world.skills.time.hour // 14 world.skills.time.phase // "afternoon"
Why underscores? Dashes aren't valid JS/Python identifiers. foo-bar skill → foo_bar namespace.
This keeps skill state organized and avoids collisions.
Utility Functions
Methods bound to world for interaction:
Narrative
world.emit("The lamp dies!") // Show message
world.narrate("Darkness falls.", "dramatic")
Events
world.trigger_event("GRUE_APPROACHES", { room: world.room.path })
Inventory
world.has("brass-key") // true/false
world.give("gold-coins") // Add to inventory
world.take("used-potion") // Remove from inventory
Flags
world.flag("dragon_slain") // Get flag
world.set_flag("treasure_found", true) // Set flag
State
world.get("object.state.fuel") // Get by path
world.set("object.state.lit", true) // Set by path
Navigation
world.go("../maze/room-a/") // Move player
world.can_go("north") // Check exit
Buffs
world.add_buff({ name: "Caffeinated", effect: { energy: +2 }, duration: 5 })
world.remove_buff("caffeinated")
world.has_buff("grue_immunity")
Logging
world.log("Debug: fuel = " + world.object.state.fuel)
Example: Lamp Simulate
simulate_js: (world) => {
if (world.object.state.lit) {
world.consume_fuel(1); // Call object method
if (world.object.state.fuel <= 0) {
world.extinguish(); // Call object method
world.emit("The lamp sputters and dies!");
if (world.room.is_dark && world.room.is_dangerous) {
world.trigger_event("GRUE_APPROACHES");
}
}
}
}
Example: Guard Expression
guard: "player has the key AND room is not dark"
guard_js: (world) => world.has("brass-key") && !world.room.is_dark
Example: Score Calculation
score_if: "player is tired OR room is dark"
score_if_js: (world) => world.has_buff("tired") || world.room.is_dark
Example: Skill State
# Skill "economy" needs to check gold guard: "player has at least 10 gold" guard_js: (world) => world.skills.economy.gold >= 10 # Skill "pie-menu" checks last selection score_if: "last pie menu selection was north" score_if_js: (world) => world.skills.pie_menu.last_selection === "north"
Design Principles
Structured, Not Arbitrary
world is NOT just a bag of key/values. It has defined structure:
- •Standard keys are always present
- •Extended keys appear in context
- •Skills namespace their state (with underscores!)
- •Functions are bound methods
Skill Namespaces (Underscores!)
Skills don't pollute root world. They register under world.skills.skill_name:
// Skill "economy" → world.skills.economy world.skills.economy.gold world.skills.economy.currency // Skill "pie-menu" → world.skills.pie_menu (underscore!) world.skills.pie_menu.last_selection world.skills.pie_menu.hover_direction // Skill "foo-bar" → world.skills.foo_bar world.skills.foo_bar.some_state
Rule: skill-name with dashes → skill_name with underscores in namespace.
Methods Are Bound
Object methods appear as functions on world:
// Object defines: methods: consume_fuel: "reduce fuel by amount" // At runtime, method is bound: world.consume_fuel(1) // Works!
Related Skills
- •object — Provides ctx.object
- •room — Provides ctx.room
- •adventure — Provides ctx.adventure
- •buff — Used by ctx.add_buff/has_buff
Dual Runtime: Python + JavaScript
CRITICAL: We always generate BOTH _js AND _py versions of compiled expressions.
# Natural language
guard: "player has the key AND room is not dark"
# BOTH generated:
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
Why Dual Runtimes?
| Runtime | Purpose |
|---|---|
| Python | Server-side simulation, testing, LLM tethering |
| JavaScript | Browser runtime, standalone play |
Keeping Them In Sync
- •Same semantics — Both should produce identical results
- •Same world structure —
world.player,world.room, etc. - •Same utility functions —
world.has(),world.emit(), etc. - •Generated together — LLM produces both in one pass
The Compilation Event
- event: COMPILE_EXPRESSION
field: guard
source: "player has the key"
targets:
- field: guard_js
language: javascript
- field: guard_py
language: python
expected_type: boolean
Python Runtime Class
class World:
"""Python runtime context — mirrors JavaScript World class."""
def __init__(self, adventure_data):
self.turn = 0
self.adventure = adventure_data
self.player = adventure_data['player']
self.room = None # Set on navigation
self.party = adventure_data['party']
self.object = None # Set during object simulation
self.skills = {} # Skill state namespaces
def has(self, item_id: str) -> bool:
return item_id in self.player.get('inventory', [])
def flag(self, name: str) -> bool:
return self.adventure.get('flags', {}).get(name, False)
def emit(self, message: str):
print(message) # Or queue for output
def trigger_event(self, name: str, data=None):
# Event system handles this
pass
JavaScript Runtime Class
class World {
/** JavaScript runtime context — mirrors Python World class. */
constructor(adventureData) {
this.turn = 0;
this.adventure = adventureData;
this.player = adventureData.player;
this.room = null; // Set on navigation
this.party = adventureData.party;
this.object = null; // Set during object simulation
this.skills = {}; // Skill state namespaces
}
has(itemId) {
return (this.player.inventory || []).includes(itemId);
}
flag(name) {
return (this.adventure.flags || {})[name] || false;
}
emit(message) {
console.log(message); // Or queue for UI
}
triggerEvent(name, data) {
// Event system handles this
}
}
Protocol Symbol
RUNTIME-CONTEXT — The world passed to closures (Python + JavaScript)