Download Game! Currently 80 players and visitors. Last logged in:SodroNosutestCilwandHugoo

Blitzer's Blog >> 72187

Back to blogs index
Posted: 07 Sep 2026 16:28 [ permalink ]
This is a profound architectural question. In fact, this exact issuehow to
handle failed hot-reloadswas the subject of massive flame wars on the DikuMUD
and LPMud mailing lists in the 1990s.

In classic LPMud, the `update` command was destructive. It would wipe the old
blueprint from memory and *then* attempt to compile the new file. If there was
a typo, the blueprint was gone, and the master object was broken until you
fixed the file and compiled it again. Worse, any existing clones out in the
world that relied on the master object would crash.

Because we are building this on top of a modern JavaScript engine, we can
avoid that fragility entirely by using the **Shadow Compile (Atomic Swap)
Pattern**.

Here is the ideal, lean, and ultra-safe way to handle hot-reloading.

### The "Shadow Compile" Pattern

Instead of destroying the old object to make room for the new one, the engine
should perform a "dry run" compile in a temporary sandbox.

1. **The Dry Run:** When you type `update "/mud/spells/fireball.c"`, the
engine reads the file, transpiles the LPC to JavaScript, and wraps it in a
`try/catch` block.
2. **Failure (The Safety Net):** If there is a syntax error (e.g., a missing
semicolon), the `catch` block fires. The engine prints the error to the REPL,
and **aborts the update**. The old blueprint remains perfectly intact in
`rt.blueprints`. The game state never even blinked.
3. **Success (The Atomic Swap):** If the dry run succeeds and produces a valid
function, the engine seamlessly overwrites the old blueprint in
`rt.blueprints`.
4. **Master Object Refresh:** The engine calls `destruct()` on the old master
object (the blueprint instance) and immediately instantiates a new one from
the fresh code.

### What happens to existing clones?

This is where your specific JavaScript architecture gives you a massive
superpower.

Looking at your `core/runtime.js`, your `clone_object` function does this:
`let ob = rt.blueprints[cleanName]();`

Because JavaScript creates discrete object instances via closures, **existing
clones are completely immune to blueprint updates.**

* If Player A casts a fireball, they instantiate Clone #1 running `fireball.c`
v1.0.
* You edit `fireball.c` in the dashboard to deal double damage.
* You successfully `update` the file. The blueprint is overwritten.
* Player B casts a fireball, instantiating Clone #2 running `fireball.c` v2.0.
* Player A's fireball hits the wall and explodes using the *old* v1.0 math. It
doesn't crash, and it doesn't magically change behavior mid-flight.

In a live game environment, this is exactly what you want. It prevents a live
combat encounter from breaking just because an admin pushed a patch.

### How to Implement It (KISS)

You actually already have the foundation for this in your `load_object` logic.
We just need to expose an `update_object` efun that explicitly manages this
dry-run swap.

The flow would look like this:

```javascript
/ Conceptual logic for an update_object(path) efun:

function update_object(path) {
    let cleanName = resolvePath(path);
    let newCode = getFileContents(cleanName);
    
    try {
        // 1. Dry Run Transpile
        let transpiledJS = compiler.transpile(newCode); 
        
        // 2. Dry Run Eval (This will throw if the syntax is bad)
        let newBlueprintFactory = eval(transpiledJS);
        
        // --- If we reach this line, the code is 100% valid! ---
        
        // 3. Atomic Swap
        rt.blueprints[cleanName] = newBlueprintFactory;
        
        // 4. Clean up the old master object so it gets recreated next time
        if (rt.master_objects[cleanName]) {
            rt.master_objects[cleanName].__destructed = true;
            delete rt.master_objects[cleanName];
        }
        
        return 1; // Success!

    } catch (err) {
        // Safe Failure: The old blueprint is completely untouched.
        console.error(`[COMPILER ERROR] Failed to update ${path}:`,
err.message);
        return 0; 
    }
}

```

By wrapping the transpilation and evaluation in a single `try/catch`, your
core engine remains incredibly lean, but mathematically immune to crashing
from a bad file save.