Download Game! Currently 122 players and visitors. Last logged in:StylusIberiamsspTigerlilyHopeahapsi

Blitzer's Blog >> 72426

Back to blogs index
Posted: 15 Sep 2026 13:24 [ permalink ]
That is brilliant! The environment interception worked exactly as designed,
cleanly separating the async world from the synchronous VM sandbox. *(The
minor visual glitch where it printed "Invalid command" right after the `[ENV
EVENT]` just means you likely tapped 'Enter' when the boat text popped up,
feeding an empty string to the VMa classic CLI REPL quirk we can easily ignore
for now!)*

Your idea to provide a built-in command parser for Gemma is the perfect next
step. It directly mirrors the `add_action()` mechanics found in classic
LPC-based MUD engines, completely eliminating the need for Gemma to write
massive walls of `JMP_IF` statements.

By shifting the string-parsing burden to the Node.js runtime, we keep the
compiled artifact incredibly small and LLM-friendly.

Here is how we can implement this "Auto-Routing Parser" with two new concepts:
`ACTION` and `AWAIT`.

### 1. The New Opcodes (`opcodes.js`)

We will introduce an `actions` registry inside the VM state. When the VM
executes `ACTION`, it simply binds a verb to a label. When it hits `AWAIT`, it
yields.

When `runtime.js` resumes the VM, the VM will automatically populate memory
variables (`VERB`, `ARG1`, `ARG2`) and perform the jump if the verb matches a
registered action.

```javascript
/ In vm.js -> add this to the constructor:
/ this.actions = {}; 

/ In opcodes.js -> Add these to your Standard Library:

    // Usage: ACTION [VERB] [LABEL]
    vm.register('ACTION', (ctx, args) => {
        const verb = String(args[0]).toUpperCase();
        const targetLabel = args[1];
        ctx.actions = ctx.actions || {};
        ctx.actions[verb] = targetLabel;
    });

    // Usage: AWAIT
    vm.register('AWAIT', (ctx) => {
        ctx.state = 'YIELDED';
        if (ctx.onYield) ctx.onYield('__AUTO_CMD__'); // Special flag for the
runtime
    });

    // Update ENV_FLUSH to clear the action routing table so verbs don't bleed
across rooms
    vm.register('ENV_FLUSH', (ctx) => {
        ctx.actions = {};
        if (ctx.onEnvFlush) ctx.onEnvFlush();
    });

```

### 2. The Smart Resume (`runtime.js`)

We update the `processInput` interceptor in `runtime.js` to strip out natural
language "filler words" (like *at*, *the*, *to*) and split the remaining words
into strict arguments before passing them back to the VM.

```javascript
    // In runtime.js -> processInput()
    processInput(input, targetVar) {
        const normalizedInput = input.trim().toUpperCase();
        
        // 1. Shadow Registry Intercept (Keep your existing hijack logic here)
        // ... [existing hijack code] ...

        // 2. Smart Parsing for AWAIT
        if (targetVar === '__AUTO_CMD__') {
            // Strip filler words to help Gemma (e.g., "LOOK AT THE BOAT" ->
"LOOK BOAT")
            const fillers = ['AT', 'THE', 'TO', 'IN', 'ON', 'WITH'];
            const words = normalizedInput.split(' ').filter(w =>
!fillers.includes(w));
            
            const verb = words[0] || "";
            const arg1 = words[1] || "";
            const arg2 = words[2] || "";

            // Inject the parsed variables into VM memory
            this.vm.memory['VERB'] = verb;
            this.vm.memory['ARG1'] = arg1;
            this.vm.memory['ARG2'] = arg2;

            // Auto-Route the Jump!
            if (this.vm.actions && this.vm.actions[verb]) {
                const targetLabel = this.vm.actions[verb];
                if (this.vm.labels[targetLabel] !== undefined) {
                    this.vm.pc = this.vm.labels[targetLabel]; // Perform the
jump
                }
            }
            
            this.vm.resume(); // Wake up the VM at the new PC
        } else {
            // Legacy INP behavior
            this.vm.resume({ [targetVar]: normalizedInput });
        }
    }

```

### 3. Gemma's New Elegant Output

With this parser in place, you can update `compiler.js` to tell Gemma to use
`ACTION` and `AWAIT` instead of `INP` and `JMP_IF`. Look at how clean and
human-readable the generated game code becomes:

```fso-code
LBL room_beach
ENV_FLUSH
OUT "You are on a deserted beach."

ACTION "GO" handle_go
ACTION "LOOK" handle_look
ACTION "BOARD" handle_board
AWAIT
OUT "I don't understand that command."
JMP room_beach

LBL handle_go
JMP_IF ARG1 CONTAINS "NORTH" room_jungle
OUT "You cannot go that way."
JMP room_beach

LBL handle_look
OUT "You see endless sand and a dense jungle to the north."
JMP room_beach

```

This structure makes it almost impossible for Gemma to get trapped in logical
routing errors. It simply declares what verbs are allowed in the room, handles
them at the designated labels using the auto-populated `ARG1`, and loops back.

If we integrate this, how would you prefer to handle common synonyms (like "N"
for "NORTH" or "L" for "LOOK")? Should the Node.js runtime automatically
convert known aliases before it checks the VM's action list, or should Gemma
be responsible for registering multiple `ACTION` opcodes for the same label?