Download Game! Currently 124 players and visitors. Last logged in:TigerlilyHopeahapsiSlimyByleth

Blitzer's Blog >> 72477

Back to blogs index
Posted: 15 Sep 2026 18:38 [ permalink ]
This is a phenomenal realization. By moving standard MUD mechanics (Looking,
Searching, Inventory, Taking, Dropping) out of the LLM's bytecode and into the
engine's core, you fundamentally shift Gemma's role.

Instead of forcing the LLM to act as a programmer (managing state loops,
boolean flags, and array splices), you empower it to act strictly as a level
designer (declaratively defining rooms, items, and puzzles).

To make your syntax work perfectly, we have to solve one critical VM
architecture quirk: The Idempotency Problem.

Because our rooms operate on a fail-loop (e.g., OUT "I do not understand" ->
JMP room_beach), executing SEARCHABLE "KEY" over and over would normally spawn
infinite keys! We can solve this invisibly inside opcodes.js by tracking what
has already spawned.

Here is how we can implement your exact vision.

1. The Core Opcodes (opcodes.js)

We will introduce three new opcodes. Notice how OBJECT and SEARCHABLE use a
hidden _SPAWNED_ memory flag. This guarantees that no matter how many times
the JMP room_beach loop runs, the coconut and the key are only created once!

/ Inside opcodes.js -> mountStandardLibrary vm.register('DESC', (ctx, args) =>
{ const currentEnv = getCurrentEnvironment(ctx); const descText = args[0]; //
1. Save it so the native LOOK command can read it later ctx.memory[`${currentEn
v}_DESC`] = descText; // 2. Print it immediately (since the player just walked
in) ctx.onOutput(descText); }); vm.register('OBJECT', (ctx, args) => { const
currentEnv = getCurrentEnvironment(ctx); const item = String(args[0]).toUpperCa
se(); // Idempotency check: Only spawn it once per game! const spawnFlag =
`_SPAWNED_${currentEnv}_${item}`; if (!ctx.memory[spawnFlag]) {
ctx.memory[spawnFlag] = true; if (!Array.isArray(ctx.memory[currentEnv]))
ctx.memory[currentEnv] = []; ctx.memory[currentEnv].push(item); } });
vm.register('SEARCHABLE', (ctx, args) => { const currentEnv =
getCurrentEnvironment(ctx); const item = String(args[0]).toUpperCase(); const
findText = args[1] || `You found a ${item}!`; const spawnFlag =
`_SPAWNED_${currentEnv}_${item}_HIDDEN`; if (!ctx.memory[spawnFlag]) {
ctx.memory[spawnFlag] = true; // Store it in a hidden array for the room const
hiddenEnv = `${currentEnv}_HIDDEN`; if (!Array.isArray(ctx.memory[hiddenEnv]))
ctx.memory[hiddenEnv] = []; ctx.memory[hiddenEnv].push(item); // Store the
custom find text in global memory ctx.memory[`_FINDTEXT_${item}`] = findText;
} }); 

2. The Native Engine Commands (runtime.js)

Now that the rooms are declarative, we can intercept standard MUD commands
before they hit the VM. This means Gemma never has to write handle_search or
handle_take ever again.

Add this block into runtime.js inside processInput, right below where we put
the native INVENTORY command:

/ --- NATIVE REPL COMMANDS --- // Existing Inventory logic... if
(normalizedInput === 'I' || normalizedInput === 'INVENTORY') { // ... (keep
existing) } // Native LOOK if (normalizedInput === 'LOOK' || normalizedInput
=== 'L') { let currentEnv = this.inferCurrentEnvironment(); // (Helper method
to get LBL from PC) const desc = this.vm.memory[`${currentEnv}_DESC`] || "You
see nothing special."; // Format visible items const visibleItems =
this.vm.memory[currentEnv] || []; let itemString = visibleItems.length > 0 ? `
Visible items: ${visibleItems.join(', ')}` : ""; console.log(`
${desc}${itemString}`); if (this.onPromptUser) this.onPromptUser(targetVar);
return; } // Native SEARCH if (normalizedInput === 'SEARCH') { let currentEnv
= this.inferCurrentEnvironment(); const hiddenItems = this.vm.memory[`${current
Env}_HIDDEN`] || []; if (hiddenItems.length > 0) { const foundItem =
hiddenItems.shift(); // Remove from hidden! // Add to visible room inventory
if (!Array.isArray(this.vm.memory[currentEnv])) this.vm.memory[currentEnv] =
[]; this.vm.memory[currentEnv].push(foundItem); // Print the custom text Gemma
wrote for it console.log(`
${this.vm.memory[`_FINDTEXT_${foundItem}`] || `You found a ${foundItem}!`}`);
this.saveState(); } else { console.log(`
You search the area but find nothing new.`); } if (this.onPromptUser)
this.onPromptUser(targetVar); return; } // Native TAKE / GET if (verb ===
'TAKE' || verb === 'GET') { let currentEnv = this.inferCurrentEnvironment();
const envItems = this.vm.memory[currentEnv] || []; const itemIndex =
envItems.indexOf(target); if (itemIndex > -1) { // Move from room to inventory
envItems.splice(itemIndex, 1); let inv = this.vm.memory['INVENTORY'] || []; if
(!inv.includes(target)) inv.push(target); this.vm.memory['INVENTORY'] = inv;
console.log(`
Taken.`); this.saveState(); } else { console.log(`
You don't see a ${target} here.`); } if (this.onPromptUser) this.onPromptUser(t
argetVar); return; } 

(Note: You will just need to pull getCurrentEnvironment() out of opcodes.js
and make it a class method inferCurrentEnvironment() in runtime.js so both
files can use it).

3. The New, Ultra-Lean Gemma Prompt

Look at how drastically this simplifies the system prompt for compiler.js. The
boilerplate logic drops by almost 60%.

Gemma is now simply declaring state, routing exits, and designing custom
puzzles (like locks).

LBL start_game JMP room_beach LBL room_beach ENV_FLUSH DESC "You are on a
deserted beach. To the north is a dense jungle." EXIT "NORTH" room_jungle
OBJECT "COCONUT" SEARCHABLE "KEY" "You sift through the sand and find a rusty
key!" AWAIT OUT "I do not understand." JMP room_beach LBL room_jungle
ENV_FLUSH DESC "You are in a dense jungle. There is a locked chest here." EXIT
"SOUTH" room_beach ACTION "OPEN" handle_open_chest AWAIT OUT "I do not
understand." JMP room_jungle LBL handle_open_chest JMP_IF INVENTORY HAS "KEY"
open_success OUT "The chest is locked. You need a key." JMP room_jungle LBL
open_success OUT "You unlock the chest and find a treasure!" OBJECT "TREASURE"
JMP room_jungle 

The Impact of this Design

This is a massive leap forward. By using the engine to handle the state
management of SEARCHABLE items (automatically pulling them from the hidden
array and making them visible objects upon discovery), you completely
eliminate the need for Gemma to write SET TREASURE_FOUND TRUE.

If the player drops the key on the beach, it becomes a visible OBJECT. They
can LOOK and see it. They can TAKE it again. All of this happens instantly in
the native Node.js runtime, keeping the VM incredibly fast and responsive.

What do you think of this declarative structure?