Onceworlds
Engine · Debugging

Seeing what a running game does

Why a game misbehaves: `game.debug.text()`, the F3 overlay, Build's Debugger.

Don't guess why a game misbehaves: ask it. Every engine game has game.debug, and its dump is written for you to read: a few kilobytes of what is in the world, what moves, how long each system takes, and what looks wrong, each problem saying what to fix. The full reference is the API reference's "The debugger" section (https://onceworlds.com/engine-api.md).

In a test or a headless run

import { simulate, dumpSimulation, simulationText } from '@onceworlds/engine/test';

const run = await simulate(config, { humans: 2, bots: 2, seconds: 60, profile: true });
console.log(simulationText(run));        // the run, then every page's world as text
if (run.failure) console.log(run.failure.error, run.failure.pages[0].dump);   // the world at the frame it first broke

With a game you step yourself: game.step(120); console.log(game.debug.text()), or game.debug.dump() for the same as JSON. dump({ focus: ['Hero', 1048581] }) always describes those entities; entities: 60 describes more; text({ max: 1500 }) fits it in 1,500 characters, what is wrong first.

Read it in this order:

  1. problems: they break the game. Fix them first, in the order given.
  2. warnings: likely bugs (a body with no collider, things far away, a slow system, messages Net refused).
  3. worlds: kinds says what exists and how many (a count that keeps growing is a leak: things spawned and never despawned); list shows the entities that matter with their place (pos), speed (vel), owner and their own components' values.
  4. systems (with profile: true): the slowest systems, in ms a frame. A frame at 60 fps has 16.7 ms for everything.

What the problems mean

It says Do
X.y is NaN or Infinity on Hero #… Find what writes X.y: a division by zero, normalize of a zero vector, the square root of a negative number.
World 'main' has … things to draw but no active Camera Spawn a camera: camera2D({ height: 12 }) in 2D, Camera({ projection: 'perspective' }) in 3D.
Hero #… has Body2D, but the game has no Physics2D() Add the named module to createGame({ modules }).
Body2D on Ball #… has no Collider2D Give the body (or a child) a collider, or it falls through everything.
Nothing is called 'hero' / There is no sound called 'zap' Register the file: assets.add('hero', { type: 'image', url: 'hero.png' }), or use a name that exists.
scenes: … a kind this engine doesn't know (or a component, a field) A scene file names something wrong: use the name the message suggests.
… entities are over 10,000 units from the origin Something fell out of the world or flew off: despawn it, or stop what launches it.
system 'x' in world 'main': … (×40) An error that keeps happening every frame: the message says where; fix it before anything else.
System 'x' takes 6 ms a frame Make the loop cheaper: make queries once, keep entity.get() and allocations out of per-step loops.

Make your own reasoning visible

Draw what the game is thinking (an AI's path, a hitbox, an aim line) instead of logging numbers. Lines show only while the overlay or Build's Debugger looks, so they can stay in the game:

game.debug.draw.path(bot.path, '#4ee1ff');             // from an update, late or render system
game.debug.draw.circle([x, y], attackRange, '#ff3b30');
game.debug.layer('hitboxes', (draw, world) => { /* draw every hitbox */ });   // a toggle in the overlay and in Build

In a browser

Time

game.debug.paused = true, game.debug.step(1) (one fixed step) and game.debug.speed = 0.25 freeze, step and slow a game, in a browser or a test. A bug that happens too fast to see happens slowly enough at 0.1.