Skip to content

Debug Console

Debugging a game engine from the outside is painful. You end up littering your code with console.log calls, rebuilding, checking the output, and repeating. SAGE's debug console takes a different approach: it exposes your running game on window.sage so you can poke at it directly from the browser's developer console.

Open DevTools, type sage.entities.player, and you get the live entity. Call sage.tp('player', 0, 10, 0) and your player teleports. No rebuilds, no throwaway logging code, no friction.

Quick start

The debug console is enabled by default. Start your game normally:

typescript
const engine = await createGameEngine(canvas, entities);
await engine.start();

Open your browser's developer console and type:

javascript
sage.engine          // the GameEngine instance
sage.entities.player // look up any entity by name
sage.list()          // table of all entities
sage.inspect('player') // dump full entity state

That is it. Everything below is details.

Configuration

The debug console is configured through SageOptions when creating the engine.

Default (enabled)

typescript
// Debug console is on, accessible as window.sage
const engine = await createGameEngine(canvas, entities);

Custom namespace

typescript
// Accessible as window.myGame instead of window.sage
const engine = await createGameEngine(canvas, entities, {
    debug: { namespace: 'myGame' }
});

Now you type myGame.entities.player in the console instead of sage.entities.player.

Disabled (production)

typescript
// No debug namespace on window at all
const engine = await createGameEngine(canvas, entities, {
    debug: false
});

For production builds, disable the debug console entirely. There is no runtime cost when disabled -- the expose() calls become no-ops.

Built-in references

These are registered automatically when the engine starts. They give you direct access to the major subsystems without digging through manager hierarchies.

PropertyWhat it is
sage.engineThe GameEngine instance
sage.entitiesProxy-wrapped EntityManager (see below)
sage.levelCurrent level (dynamic -- updates on level transitions)
sage.sceneCurrent BabylonJS Scene (dynamic)
sage.physicsHavokPlugin instance
sage.collidersColliderDebugManager
sage.eventsEventBus

The entity proxy

sage.entities is special. It is a Proxy around the EntityManager that intercepts property access and resolves entities by name. This means:

javascript
// These are equivalent:
sage.entities.player
sage.entities.getByName('player')

// But the real EntityManager methods still work:
sage.entities.getByType('soldier')
sage.entities.getAllEntities()

Property access resolves via getByName() first. If there is no entity with that name, it falls through to the actual EntityManager method or property.

Built-in commands

These are registered as functions on the debug namespace. They are designed for the interactive console -- formatted output, sensible defaults, minimal typing.

Entity manipulation

CommandDescription
sage.tp('player', x, y, z)Teleport an entity to a world position
sage.spawn('soldier', x, y, z)Spawn an entity at a position
sage.list()List all entities using console.table
sage.list('soldier')List entities of a specific type
sage.inspect('player')Dump entity state, behaviors, and node info

Collider visualization

CommandDescription
sage.showColliders()Show collider debug vis for all entities
sage.showColliders('player')Show by entity name or type
sage.hideColliders()Hide all collider debug vis
sage.hideColliders('player')Hide by entity name or type

These commands wrap the same collider debug system described in the Collider Debugging guide. The difference is convenience -- you do not need to look up entity references or call methods on them. Just type a name.

Event debugging

CommandDescription
sage.logEvents('input:*')Log matching events to the console; returns an unsubscribe function
sage.stopLogging()Stop all active event loggers

logEvents accepts the same wildcard patterns as the event bus. A few useful patterns:

javascript
// Log all input events
sage.logEvents('input:*')

// Log everything (noisy, but sometimes that's what you need)
sage.logEvents('*')

// Log entity lifecycle events
sage.logEvents('entity:*')

// Save the unsubscribe function if you want fine-grained control
const unsub = sage.logEvents('physics:*')
unsub() // stop just this one

Game loop control

CommandDescription
sage.pause()Pause the game loop
sage.resume()Resume the game loop
sage.slow(0.5)Set time scale (0.5 = half speed, 2.0 = double speed)

sage.slow() is invaluable for debugging physics and animation issues. Slow the game down to quarter speed with sage.slow(0.25) and watch what happens frame by frame.

Registering game-specific commands

The built-in commands cover engine-level debugging, but your game has its own state and logic. Use gameEngine.debug.expose() to register your own entries on the debug namespace.

Direct values

typescript
// Expose a reference to your game state
gameEngine.debug.expose('gameState', myGameState);

Now sage.gameState returns your object in the console.

Callable commands

typescript
gameEngine.debug.expose('godmode', () => {
    const player = gameEngine.managers.entityManager.getByName('player');
    if(player)
    {
        player.state.invincible = true;
        console.log('[game] God mode enabled');
    }
});

gameEngine.debug.expose('giveItem', (itemType : string) => {
    const player = gameEngine.managers.entityManager.getByName('player');
    if(player)
    {
        player.state.inventory.push(itemType);
        console.log(`[game] Added ${ itemType } to inventory`);
    }
});

Then in the console:

javascript
sage.godmode()
sage.giveItem('railgun')

Dynamic getters

Sometimes you want a property that resolves fresh on every access, not a snapshot. Pass an object with a get function:

typescript
gameEngine.debug.expose('player', {
    get: () => gameEngine.managers.entityManager.getByName('player')
});

This way sage.player always returns the current player entity, even if it was destroyed and respawned since the last time you checked.

Where to register

The natural place to register game-specific commands is in the engine's onStart hook, after entities and levels are loaded:

typescript
engine.onStart(async (ge) => {
    // Entity references are available now
    ge.debug.expose('player', {
        get: () => ge.managers.entityManager.getByName('player')
    });

    ge.debug.expose('godmode', () => {
        const player = ge.managers.entityManager.getByName('player');
        if(player)
        {
            player.state.invincible = true;
            console.log('[game] God mode enabled');
        }
    });

    ge.debug.expose('resetLevel', async () => {
        await ge.managers.levelManager.reload();
        console.log('[game] Level reloaded');
    });
});

Cleanup

The debug namespace is automatically removed from window when the engine stops via gameEngine.stop(). You do not need to clean up manually.

Practical workflow

Here is a typical debugging session using the console:

javascript
// 1. See what's in the scene
sage.list()

// 2. Find the entity that's misbehaving
sage.inspect('broken-door')

// 3. Show its colliders to see if physics shapes are off
sage.showColliders('broken-door')

// 4. Slow down time to watch the interaction
sage.slow(0.25)

// 5. Teleport the player near it to trigger the issue
sage.tp('player', 5, 0, 3)

// 6. Log events to see what's firing
sage.logEvents('entity:broken-door:*')

// 7. Found it -- resume normal speed
sage.slow(1)
sage.stopLogging()
sage.hideColliders('broken-door')

API summary

gameEngine.debug.expose(name, value)

Register a value, function, or dynamic getter on the debug namespace.

ArgumentTypeDescription
namestringProperty name on the debug namespace
valueunknown | Function | { get: () => unknown }Value, callable command, or dynamic getter

SageOptions.debug

ValueEffect
undefined (default)Enabled as window.sage
{ namespace: 'name' }Enabled as window.name
falseDisabled entirely

Exports

typescript
import { DebugConsole } from '@skewedaspect/sage';
import { createEntityProxy } from '@skewedaspect/sage';
  • Collider Debugging -- visual collider debug tools, config-driven and programmatic
  • Events -- event bus patterns and wildcards used by logEvents
  • Entity Behaviors -- entity composition, state, and the behavior system

Released under the MIT License.