Ah, the mystery of the blank prompt is solved!
Looking at Gemma's output, it immediately executed `LBL start` and then hit
`INP player_command STR` before it ever printed a room description. It did
exactly what it was told, but logically, it put the cart before the horse by
waiting for an answer before telling you where you were.
This output is actually **fantastic** for a local 9B model. It perfectly
grasped the envelope structure, the label-and-jump flattening, and the state
vs. code separation. However, it also gave us a masterclass in why we need the
"Forgiving Parser" we talked about.
Here is a breakdown of the small "hallucinations" Gemma made, and exactly how
we can tweak our JavaScript to absorb them so the system never crashes.
### The "Gemma Quirks" in the Output
1. **Stop Token Leakage:** You can see `<end_of_turn>` leaked at the very end
of the file. This is common when hitting raw local cluster endpoints.
2. **Extra Arguments:** For the input command, it wrote `INP player_command
STR`. Our spec was just `INP [VAR]`. It tried to be helpful by strictly typing
it.
3. **Missing Operators:** For the jump condition, it wrote `JMP_IF
player_command "north" room1`. Our spec asked for `JMP_IF [VAR] [IS/NOT]
[VALUE] [LABEL]`. It skipped the `IS` and assumed equality.
4. **Variable References in OUT:** Instead of writing `OUT "{room1_description}
"`, it treated `OUT` like a native function and passed the variable name
directly: `OUT room1_description`.
### The Solution: A Forgiving VM
Instead of engineering an absolutely massive, convoluted system prompt to
force the 9B model to be 100% syntactically perfect, it is much easier (and
more robust) to make our Node.js runtime forgiving of these specific quirks.
Here are the minor tweaks you can make to your existing files to make them
bulletproof against this exact output.
#### 1. Strip Stop Tokens (in `lexer.js`)
We just add a quick string replacement before we extract the envelopes to
catch any LLM artifacts.
```javascript
/ In lexer.js -> extractEnvelope()
const cleanText = markdownText.replace(/<end_of_turn>|<\/s>|<eos>/g, '');
const stateMatch = cleanText.match(/```fso-state
([\s\S]*?)
```/);
/ ...
```
#### 2. Auto-Fill Missing Operators (in `opcodes.js`)
We can intercept the `JMP_IF` arguments and intelligently pad them if the LLM
forgot the `IS`.
```javascript
/ In opcodes.js -> JMP_IF handler
vm.register('JMP_IF', (ctx, args) => {
// If Gemma generated: player_command "north" room1 (3 args)
// We auto-insert "IS" to make it: player_command IS "north" room1
if (args.length === 3) {
args.splice(1, 0, 'IS');
}
const [varName, condition, val, targetLabel] = args;
const memVal = ctx.memory[varName];
// ... rest of the logic remains exactly the same
});
```
#### 3. Smart Output Resolution (in `opcodes.js`)
We can upgrade the `OUT` command so that if the argument matches a known
variable exactly, it prints the variable. Otherwise, it falls back to normal
string interpolation.
```javascript
/ In opcodes.js -> OUT handler
vm.register('OUT', (ctx, args) => {
let outputString = args[0];
// Check if Gemma passed a raw variable name instead of a string
if (ctx.memory[outputString] !== undefined) {
outputString = ctx.memory[outputString];
} else {
// Normal interpolation: replace {VAR} with memory value
outputString = outputString.replace(/{(\w+)}/g, (_, v) =>
ctx.memory[v] || '');
}
ctx.onOutput(outputString);
});
```
### The Next Compilation Iteration
If you apply those three tiny patches, your current VM will flawlessly execute
the `game_idea.md` file exactly as Gemma wrote it.
To fix the logical error (the blank prompt at the start), you just need to
slightly guide the LLM's logic in `compiler.js` by appending a tiny bit of
architectural advice to the `systemPrompt`.
```text
RULES:
1. No nested logic. Use LBL and JMP for control flow.
2. Space-delimited arguments.
3. Always use OUT to describe the current room BEFORE using INP to ask for
commands.
4. Output ONLY the markdown envelope. No conversational text.
```
How does the REPL feel now with these parser adjustments in place? I think
this proves that bridging a fast, local 9B model with a rigid state machine is
completely viable for generating dynamic software on the fly!