You are exactly right. I fell back into the trap of monolithic compilation.
If we statically link every object into world.md, the file will become a
massive, unmanageable monolith. More importantly, it prevents us from dropping
new objects into a running game directory without breaking the world file.
We need In-Memory Dynamic Linking.
In a traditional LPC virtual machine, objects (like /obj/weapon.c) are stored
as separate files on disk. When the engine boots (or when the object is first
referenced), the VM dynamically parses the bytecode and loads its logic into
the global execution space without modifying the master world file.
Here is how we adapt your runtime.js to dynamically link an infinite number of
object prefabs directly into the VM's memory at boot time.
1. Structure the File System
First, let's create a dedicated directory for our prefabs and move the idol
there. Run this:
mkdir -p objects mv cursed_idol.md objects/
2. The Dynamic Linker Patch (runtime.js)
We will intercept the start() function in your runtime.js. Instead of just
parsing world.md, it will dynamically scan the ./objects/ folder, extract the
FSO envelopes, and seamlessly merge their state and bytecode into the VM's
memory heap before execution.
Run this script to patch runtime.js:
cat << 'EOF' > patch_dynamic_linker.js import fs from 'fs'; let runtimeCode =
fs.readFileSync('runtime.js', 'utf-8'); const dynamicLinkerLogic = ` start() {
let combinedState = ""; let combinedCode = ""; // 1. Load the master world
file (acts as the main logic and save state) const fileContent =
fs.readFileSync(this.filePath, 'utf-8'); const { stateRaw, codeRaw } =
Lexer.extractEnvelope(fileContent); combinedState += stateRaw + '\
'; combinedCode += codeRaw + '\
'; // 2. DYNAMIC LINKING: Load all object prefabs into ROM const objDir =
'./objects'; if (fs.existsSync(objDir)) { const objFiles = fs.readdirSync(objDi
r).filter(f => f.endsWith('.md')); for (const file of objFiles) { try { const
content = fs.readFileSync(\`\${objDir}/\${file}\`, 'utf-8'); const env =
Lexer.extractEnvelope(content); combinedState += env.stateRaw + '\
'; combinedCode += env.codeRaw + '\
'; console.log(\`[LINKER] Dynamically loaded ROM: \${file}\`); } catch (e) {
console.error(\`[LINKER FAULT] Failed to load \${file}: \`, e.message); } } }
// Tokenize state to hydrate initial memory correctly const stateTokens =
Lexer.tokenize(combinedState).program; `; // Replace the start() beginning up
to the state tokenization runtimeCode = runtimeCode.replace( /start\(\)
\{[\s\S]*?const stateTokens = Lexer\.tokenize\(stateRaw\)\.program;/,
dynamicLinkerLogic ); fs.writeFileSync('runtime.js', runtimeCode);
console.log('[SYS] In-Memory Dynamic Linking installed in runtime.js!'); EOF
node patch_dynamic_linker.js
3. Revert world.md
Since we used the static script earlier, we need to quickly remove the cursed
idol from world.md so it doesn't cause a duplicate label error.
# A quick sed command to delete everything from the "OBJECT: CURSED IDOL"
comment to the end of the file sed -i '/\/\/ --- OBJECT: CURSED IDOL ---/,$d'
world.md # And add the closing code block tick marks back echo "\`\`\`" >>
world.md
The Beauty of this Architecture
Restart your engine:
x-console world restart && x-console world follow
You will immediately see this in your boot sequence:
[LINKER] Dynamically loaded ROM: cursed_idol.md
Why this is incredibly powerful:
Separation of Concerns: world.md is now purely your Save Game State (the
memory heap) and macro-geography. objects/*.md are your immutable ROM
cartridges.
Atomic Saves: Because all state tokens are merged into this.vm.memory at boot,
when saveState() fires, it saves the IDOL_RUBBED variable seamlessly back into
the fso-state block of world.md. The idol remembers it was rubbed, even though
its logic file was never touched!
Hot-Swapping: You can instruct Gemma to build magic_sword.md via forge.js,
drop it into the objects folder, and the moment you restart the VM, the engine
will automatically parse and link the new item.
Now that the logic can be linked on the fly, we need a way for the engine to
dynamically call init_idol when the player enters a room containing the item.
Should we implement CALL and RET opcodes so the engine can invoke an object's
logic without getting trapped by AWAIT?