Quick answer: Static initializers that reference each other create cycles. Use lazy init: assign a placeholder, populate on first access.

A “Database” struct uses static fields to define enemy templates. Each template references shared assets via another struct. Stack overflow at startup — circular initialization.

Lazy Init Pattern

function EnemyDB() constructor {
    static _cache = undefined;

    static get_all = function() {
        if (is_undefined(_cache)) {
            _cache = {
                goblin: new EnemyTemplate(...),
                orc: new EnemyTemplate(...),
            };
        }
        return _cache;
    };
}

First call builds; subsequent calls return cache. No initialization until needed.

Avoid Cyclic Static Refs

If TemplateA references TemplateB at static init time, and TemplateB references TemplateA, you have a cycle. Break by deferring — one reference becomes “set later”.

Init Function Approach

function init_database() {
    global.enemies = {};
    global.enemies.goblin = new EnemyTemplate(...);
    global.enemies.orc = new EnemyTemplate(...);
    // fill cross-refs after both exist
    global.enemies.goblin.boss_form = global.enemies.orc;
}

// Call once in obj_init Create
init_database();

Imperative init: declare all then wire up. No static initialization cycles.

Verifying

Game starts without stack overflow. Database accessible from any object after init. First access has small latency (lazy), subsequent free.

“Cycles in static init are unrecoverable. Defer one side via lazy init or imperative setup.”

For data-heavy projects, consider keeping database as JSON loaded at startup — sidesteps init order entirely.