That is the exact architectural insight that turns this from a neat demo into
an infinite, expandable game world.
Instead of asking a 9B model to keep track of a massive 20-room
macro-worldwhich would inevitably blow its context window and break the
geometryyou treat each 34 room segment as an isolated, compiled Sector Object
or "chunk".
Building a deterministic Sector Linker (linker.js) to stitch these artifacts
together is straightforward because FSO code is completely flat and stateless.
1. Core Technical Hurdles to Solve
To cleanly merge game_area_001.md and game_area_002.md, the linker script
needs to handle three distinct collision domains:
| Domain | Problem | Deterministic Fix |
|---|---|---|
| Label Collisions | Both areas define LBL start_game, LBL handle_help, etc. |
Namespace every label on ingest (e.g., a1_room_cove vs a2_room_cove). |
| Start Hook | Area 2 has its own entry jump that conflicts with Area 1. |
Strip start_game from Area 2; preserve Area 1's as the master spawn. |
| Compass Alignment | Adding a passage between two rooms requires opposing
exits. | Find an unused compass pair on both border rooms, update their DESC
text, and append reciprocal EXIT opcodes. |
2. Architecture of the Sector Linker (linker.js)
Here is a blueprint for a clean, deterministic script that links two compiled
FSO game areas:
/ linker.js
import fs from 'fs';
function parseEnvelope(content) {
const stateMatch = content.match(/```fso-state([\s\S]*?)```/);
const codeMatch = content.match(/```fso-code([\s\S]*?)```/);
return {
state: stateMatch ? stateMatch[1].trim() : '',
code: codeMatch ? codeMatch[1].trim() : ''
};
}
function prefixArea(code, areaPrefix) {
// 1. Rename LBLs (except start_game and handle_help)
let processed = code.replace(/LBL (?!start_game|handle_help)(\w+)/g, `LBL
${areaPrefix}_$1`);
// 2. Rename JMP and JMP_IF targets
processed = processed.replace(/JMP (?!handle_help)(\w+)/g, `JMP
${areaPrefix}_$1`);
processed = processed.replace(/JMP_IF (.+) (\w+)$/gm, `JMP_IF $1
${areaPrefix}_$2`);
// 3. Rename EXIT targets
processed = processed.replace(/EXIT "([^"]+)" (?!handle_help)(\w+)/g,
`EXIT "$1" ${areaPrefix}_$2`);
return processed;
}
export function mergeAreas(area1File, area2File, linkConfig) {
// linkConfig: { room1: 'treasureroom', room2: 'jungle_path', dir1:
'EAST', dir2: 'WEST' }
const a1Raw = fs.readFileSync(area1File, 'utf-8');
const a2Raw = fs.readFileSync(area2File, 'utf-8');
const a1 = parseEnvelope(a1Raw);
const a2 = parseEnvelope(a2Raw);
// Apply namespace prefixes to prevent identifier collisions
let c1 = prefixArea(a1.code, 'a1');
let c2 = prefixArea(a2.code, 'a2');
// Strip start_game from Area 2 so it doesn't hijack execution
c2 = c2.replace(/LBL start_game[\s\S]*?JMP \w+
/, '');
// Strip duplicate global handlers
c2 = c2.replace(/LBL handle_help[\s\S]*?AWAIT
/, '');
// Inject the bidirectional stitch into the respective room blocks
const exit1 = `EXIT "${linkConfig.dir1}" a2_room_${linkConfig.room2}
`;
const exit2 = `EXIT "${linkConfig.dir2}" a1_room_${linkConfig.room1}
`;
c1 = c1.replace(new RegExp(`(LBL a1_room_${linkConfig.room1}[\\s\\S]*?)(AWA
IT)`), `$1${exit1}$2`);
c2 = c2.replace(new RegExp(`(LBL a2_room_${linkConfig.room2}[\\s\\S]*?)(AWA
IT)`), `$2${exit2}$2`);
// Merge code and deduplicate base state
const mergedCode = `${c1}
// --- SECTOR LINK: AREA 2 ---
${c2}`;
const mergedState = `VAR INVENTORY ARR '[]'`;
return `\`\`\`fso-state
${mergedState}
\`\`\`
\`\`\`fso-code
${mergedCode}
\`\`\``;
}
3. Why This Approach Scales
* Infinite Modular Worlds: You can chain chunks indefinitely (area_001
connects to 002, which connects to 003), building expansive dungeon complexes
without overwhelming the model.
* Deterministic Portals: The connection can be an ordinary direction
(EAST/WEST) or thematic transit opcodes like EXIT "PORTAL" a2_room_dungeon or
EXIT "SAIL" a2_room_island.
* Zero Model Drift: Because the linking is executed entirely by regex in
Node.js, there is zero probability of the model breaking an existing room
while opening a passage to the next area.