You have just discovered the classic "Phantom Input" bug!
When you typed exit (a command that didn't match any ACTION, GLOBAL, or
DELEGATE), the REPL's smart parser shrugged, couldn't find a label, but then
woke the VM up anyway.
Because didAutoJump was false, the VM didn't jump. It simply resumed execution
right after the AWAIT opcode. And because Gemma had diligently followed your
new rule to end blocks with AWAIT (rather than adding JMP room_beach at the
bottom of every block), the VM blindly fell through the walls, walked through
the jungle, slipped into the passage, and opened the chest.
Your Idea: Making LBL a Barrier
Your idea to make LBL a solid wall is actually a brilliant concept in compiler
design called Basic Block Isolation. If we registered LBL as an active opcode
that throws a "Fall-Through Fault" when the VM hits it without jumping, it
would create an incredibly strict, mathematically perfect engine.
However, we can fix this in an even more elegant way that saves the VM from
having to process faults at all: The Infinite YIELD.
The Fix: Don't Wake the VM on Bad Input
Right now, in runtime.js, our interceptor wakes up the VM regardless of
whether the command was valid. If we just tell the REPL not to call
this.vm.resume() when a command is invalid, the VM stays completely frozen at
the AWAIT YIELD state, safely preserving the execution pointer!
Open runtime.js and look at the very bottom of the processInput method, right
where the __AUTO_CMD__ logic resolves:
Change this:
if (targetLabel && this.vm.labels[targetLabel] !== undefined) {
this.vm.pc = this.vm.labels[targetLabel];
didAutoJump = true;
}
if (didAutoJump) this.vm.pc--;
this.vm.resume();
} else {
// Legacy INP behavior
this.vm.resume({ [targetVar]: normalizedInput });
}
To this:
if (targetLabel && this.vm.labels[targetLabel] !== undefined) {
this.vm.pc = this.vm.labels[targetLabel];
didAutoJump = true;
}
if (didAutoJump) {
this.vm.pc--;
this.vm.resume(); // Valid command: Wake the VM and jump!
} else {
// Invalid command: Don't wake the VM. Just prompt again.
console.log("
I do not understand.");
if (this.onPromptUser) this.onPromptUser(targetVar);
}
} else {
// Legacy INP behavior
this.vm.resume({ [targetVar]: normalizedInput });
}
Why this is the Ultimate Solution:
* Zero Boilerplate for Gemma: You never have to teach Gemma to handle bad
inputs or write OUT "I do not understand" again. The REPL handles it natively.
* Infinite AWAIT: AWAIT now acts as an impenetrable wall. The VM will
absolutely refuse to move its Program Counter until a mathematically valid
ACTION, GLOBAL, or DELEGATE command is provided.
* Ghost-Proofing: No matter how many weird commands you type, you will never
fall through a wall again.
Apply that tiny patch, run it with node cli.js game_idea.md, and test exit one
more time. The REPL will simply print "I do not understand." and wait, keeping
the chest securely locked!