This is the crowning feature of the engine. By treating `room_labels`,
`inventories`, and `bags` as mathematically identical string-array endpoints,
you have created a universal memory addressing system that requires almost
zero instruction tokens for the LLM to understand.
To make `PULL`, `PUSH`, and the `HAS` condition work magically without forcing
Gemma to specify the source or destination every time, we can use a classic
Virtual Machine trick: **Program Counter Inference.**
Because your VM executes sequentially, we can always figure out the "current
environment" simply by looking at where the Program Counter (`ctx.pc`) is
right now and finding the label that immediately preceded it!
Here are the precise patches to implement the entire Universal Container
Abstraction.
### 1. The Single-Quote Lexer Patch (`lexer.js`)
To allow arrays to be saved and loaded cleanly into the `fso-state` block, we
just need to tell the lexer to respect single quotes (so we can save an array
like this: `VAR INVENTORY ARR '["GOLD_KEY"]'`).
Update the token matching line inside `lexer.js`:
```javascript
// In lexer.js -> tokenize()
// Split by space, preserve double OR single quoted strings, remove quotes
let tokens = line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)
.map(t => t.replace(/(^["']|["']$)/g, ''));
```
### 2. The Item Opcodes (`opcodes.js`)
We will add a helper function to infer the current room, register
`PUSH`/`PULL`, update `VAR` to handle arrays, and teach `JMP_IF` the `HAS`
operator.
```javascript
// -- MEMORY -- (Update the VAR opcode)
vm.register('VAR', (ctx, args) => {
const [name, type, val] = args;
if (type === 'NUM') ctx.memory[name] = parseFloat(val);
else if (type === 'BOOL') ctx.memory[name] = (val === 'true');
else if (type === 'ARR') {
try { ctx.memory[name] = JSON.parse(val); } catch(e) {
ctx.memory[name] = []; }
}
else ctx.memory[name] = val;
});
// -- CONTAINER HELPERS & OPCODES --
// VM Trick: Find the label that immediately precedes the current Program
Counter
function getCurrentEnvironment(ctx) {
let currentEnv = "UNKNOWN";
let maxPc = -1;
for (const [label, pc] of Object.entries(ctx.labels)) {
if (pc <= ctx.pc && pc > maxPc) {
maxPc = pc;
currentEnv = label;
}
}
return currentEnv;
}
vm.register('PULL', (ctx, rawArgs) => {
// Strip out LLM filler words
const args = rawArgs.filter(w => !['THE', 'A', 'AN'].includes(w.toUpper
Case()));
const item = String(args[0]).toUpperCase();
// Defaults based on Context
let source = getCurrentEnvironment(ctx);
let dest = 'INVENTORY';
// Override if LLM explicitly used FROM / INTO
const fromIdx = args.findIndex(a => a.toUpperCase() === 'FROM');
if (fromIdx !== -1 && args[fromIdx + 1]) source = args[fromIdx + 1];
const intoIdx = args.findIndex(a => a.toUpperCase() === 'INTO');
if (intoIdx !== -1 && args[intoIdx + 1]) dest = args[intoIdx + 1];
// Ensure both endpoints exist as arrays
if (!Array.isArray(ctx.memory[source])) ctx.memory[source] = [];
if (!Array.isArray(ctx.memory[dest])) ctx.memory[dest] = [];
// Move the item
const itemIndex = ctx.memory[source].indexOf(item);
if (itemIndex > -1) {
ctx.memory[source].splice(itemIndex, 1);
ctx.memory[dest].push(item);
}
});
vm.register('PUSH', (ctx, rawArgs) => {
const args = rawArgs.filter(w => !['THE', 'A', 'AN'].includes(w.toUpper
Case()));
const item = String(args[0]).toUpperCase();
// Inverse defaults for PUSH (Drop)
let source = 'INVENTORY';
let dest = getCurrentEnvironment(ctx);
const fromIdx = args.findIndex(a => a.toUpperCase() === 'FROM');
if (fromIdx !== -1 && args[fromIdx + 1]) source = args[fromIdx + 1];
const intoIdx = args.findIndex(a => a.toUpperCase() === 'INTO');
if (intoIdx !== -1 && args[intoIdx + 1]) dest = args[intoIdx + 1];
if (!Array.isArray(ctx.memory[source])) ctx.memory[source] = [];
if (!Array.isArray(ctx.memory[dest])) ctx.memory[dest] = [];
const itemIndex = ctx.memory[source].indexOf(item);
if (itemIndex > -1) {
ctx.memory[source].splice(itemIndex, 1);
ctx.memory[dest].push(item);
}
});
```
*Don't forget to add this line to your `JMP_IF` operator logic inside
`opcodes.js`:*
```javascript
else if (condUpper === 'HAS') {
const arr = Array.isArray(ctx.memory[varName]) ? ctx.memory[varName] :
[];
isTrue = arr.includes(expectedVal);
}
```