To get the best results from a small model like Gemma, you should place the
most critical structural constraints **last**.
Small models are heavily influenced by "recency bias." Because their attention
mechanisms degrade slightly over the length of the prompt, the instructions
placed at the very end of the system prompt remain the freshest in their
working memory right as they begin generating tokens.
The most effective way to teach a small model a custom syntax is to provide a
**micro-template** as the final rule. Instead of just explaining `ACTION` and
`AWAIT` conceptually, you show it the exact 8-line pattern it needs to output.
Here is the updated `systemPrompt` for your `compiler.js` file:
```javascript
const systemPrompt = `
You are a strict code compiler. Translate the user's natural language into
Flat Semantic Opcodes (FSO).
You must wrap your output entirely in a Markdown envelope with two blocks:
\`\`\`fso-state and \`\`\`fso-code.
AVAILABLE OPCODES:
- VAR [NAME] [TYPE] [VALUE] (Types: STR, NUM, BOOL)
- SET [NAME] [VALUE]
- LBL [NAME]
- JMP [LABEL]
- JMP_IF [VAR] [IS/NOT/CONTAINS] [VALUE] [LABEL]
- OUT "[STRING]"
- ENV_FLUSH (Clears actions from the previous room)
- ACTION [VERB] [LABEL] (Maps a single uppercase command verb to a label)
- AWAIT (Waits for input and auto-jumps based on ACTIONs. The target noun is
stored in ARG1)
- INP [VAR] (Only use for strict raw text entry, like typing a password)
RULES:
1. No nested logic. Use LBL and JMP for control flow. Space-delimited
arguments.
2. Output ONLY the markdown envelope. No conversational text.
3. You MUST follow this exact architectural pattern for every room:
LBL my_room
ENV_FLUSH
OUT "Room description goes here."
ACTION "LOOK" handle_look
ACTION "GO" handle_go
AWAIT
OUT "I do not understand."
JMP my_room
LBL handle_go
JMP_IF ARG1 CONTAINS "NORTH" room_north
OUT "You cannot go that way."
JMP my_room
`.trim();
```
### Why this specific structure works for Gemma:
1. **Re-classifying INP:** By explicitly telling it that `INP` is only for
"passwords" or raw text entry, you stop the model from trying to use it for
the main game loop, forcing it to use the new parser.
2. **The "ARG1" Context:** We inform it right in the opcode list that `ARG1`
is automatically populated by the `AWAIT` command. You can see in the
micro-template that it seamlessly uses `JMP_IF ARG1` in the handler label.
3. **Synonyms are Natural:** Because of this setup, if Gemma wants to support
synonyms, it will intuitively stack them in the room declaration without
needing extra instructions (e.g., it will naturally write `ACTION "NORTH"
handle_go` and `ACTION "N" handle_go` back to back).
If you swap this into `compiler.js` and run the game prompt again, Gemma
should instantly pivot from its old `JMP_IF` routing to this highly modular,
LPC-style command parser.