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:
const engine = await createGameEngine(canvas, entities);
await engine.start();Open your browser's developer console and type:
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 stateThat is it. Everything below is details.
Configuration
The debug console is configured through SageOptions when creating the engine.
Default (enabled)
// Debug console is on, accessible as window.sage
const engine = await createGameEngine(canvas, entities);Custom namespace
// 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)
// 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.
| Property | What it is |
|---|---|
sage.engine | The GameEngine instance |
sage.entities | Proxy-wrapped EntityManager (see below) |
sage.level | Current level (dynamic -- updates on level transitions) |
sage.scene | Current BabylonJS Scene (dynamic) |
sage.physics | HavokPlugin instance |
sage.colliders | ColliderDebugManager |
sage.events | EventBus |
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:
// 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
| Command | Description |
|---|---|
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
| Command | Description |
|---|---|
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
| Command | Description |
|---|---|
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:
// 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 oneGame loop control
| Command | Description |
|---|---|
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
// Expose a reference to your game state
gameEngine.debug.expose('gameState', myGameState);Now sage.gameState returns your object in the console.
Callable commands
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:
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:
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:
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:
// 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.
| Argument | Type | Description |
|---|---|---|
name | string | Property name on the debug namespace |
value | unknown | Function | { get: () => unknown } | Value, callable command, or dynamic getter |
SageOptions.debug
| Value | Effect |
|---|---|
undefined (default) | Enabled as window.sage |
{ namespace: 'name' } | Enabled as window.name |
false | Disabled entirely |
Exports
import { DebugConsole } from '@skewedaspect/sage';
import { createEntityProxy } from '@skewedaspect/sage';Related pages
- 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
