Debug Mode
Debug mode enables visual and console diagnostics to understand what's happening inside the engine. When enabled, GWEN displays collider wireframes, system timing overlays, and structured logging—helping you diagnose performance issues and validate logic.
Enabling Debug Mode
Global Engine Debug
Set engine.debug: true in gwen.config.ts to activate engine-wide debug mode. This enables:
- Verbose logging for plugin registration and lifecycle events
- Per-frame sentinel checks
- Phase timing warnings when frame budget is exceeded
// gwen.config.ts
import { defineConfig } from '@gwenjs/app'
export default defineConfig({
engine: {
debug: true,
},
})Module Debug
Individual modules may also expose their own debug option via the module tuple:
export default defineConfig({
modules: [['@gwenjs/physics2d', { debug: true }]],
})This enables the physics debug renderer (collision shape overlays) independently of the global engine debug flag.
The Basics
Enable debug mode in your engine configuration:
// gwen.config.ts
export default defineConfig({
modules: [['@gwenjs/physics2d', { debug: true }]],
})When debug: true:
- Physics colliders render as colored wireframes
- System timing appears on screen
- Verbose logging is active
- Sentinel checks validate data integrity
Visual Debugging
Collider Visualization
With debug mode on, physics bodies are drawn with wireframes:
// Physics automatically renders colliders when debug: true
const world = usePhysics2D()
// All boxes, circles, and polygons are now visibleColors indicate body type:
- Blue — Static bodies (immovable)
- Green — Dynamic bodies
- Yellow — Kinematic bodies (player-controlled)
- Red — Sleeping bodies
System Timing Overlay
GWEN displays per-system execution time in milliseconds:
[FRAME 1248] (dt: 16.7ms)
├─ MovementSystem 2.1ms
├─ PhysicsSystem 4.8ms
├─ CollisionSystem 1.3ms
├─ RenderSystem 11.2ms
└─ Total 19.4ms (16% over budget)This helps identify bottlenecks. If a system consistently exceeds its budget (e.g., physics taking 5ms on a 16ms frame), you've found a performance issue.
Engine Stats
Access per-frame performance data via engine.getStats():
const stats = engine.getStats()
console.log(stats.fps) // current FPS
console.log(stats.deltaTime) // last frame delta in ms
console.log(stats.frameCount) // total frames since start
console.log(stats.budgetMs) // frame budget (1000 / targetFPS)
console.log(stats.overBudget) // true if last frame exceeded budget
// Per-phase breakdown (all in ms)
const p = stats.phaseMs
console.log(p.tick) // engine:tick hook
console.log(p.plugins) // onBeforeUpdate() calls
console.log(p.physics) // physics2d/3d step
console.log(p.wasm) // WASM module steps
console.log(p.update) // onUpdate() calls
console.log(p.render) // onAfterUpdate() + onRender() calls
console.log(p.afterTick) // engine:afterTick hook
console.log(p.total) // full frame wall-clock timeNote: Use
engine.getStats()— notengine.stats. It is a method call.
Structured Logging
GWEN provides a built-in logger via createLogger(). Log levels: debug < info < warn < error. Each entry is a structured LogEntry object, compatible with custom log sinks.
import { createLogger } from '@gwenjs/core'
const logger = createLogger('MyPlugin')
logger.debug('initializing...')
logger.info('plugin started')
logger.warn('slow frame detected', { frameMs: 32 })
logger.error('unhandled error', error)import { createLogger, defineSystem, onUpdate, useEngine } from '@gwenjs/core/system'
export const MySystem = defineSystem(function MySystem() {
const engine = useEngine()
const log = createLogger('game:my-system')
// This runs once during setup
log.info('System initialized')
onUpdate(() => {
if (someWarning) {
log.warn('Unexpected state detected', { state: 'foo' })
}
})
})Log Levels
The logger respects the debug flag:
| Level | When Active | Use |
|---|---|---|
debug | Only when debug: true | Detailed diagnostics (disabled in production) |
info | Only when debug: true | Informational events |
warn | Always | Unexpected but recoverable conditions |
error | Always | Problems that need attention |
This means your log.debug() calls are no-ops in production, avoiding overhead.
Custom Log Sinks
Redirect logs to a custom sink (e.g., a server, external service, or test spy):
import { createLogger } from '@gwenjs/core'
const log = createLogger('app:core', true)
// Replace the default console sink
log.setSink((entry) => {
console.log(`[${entry.level.toUpperCase()}] ${entry.source}: ${entry.message}`)
if (entry.data) {
console.table(entry.data)
}
// Forward to analytics
if (entry.level === 'error') {
analytics.logError(entry.source, entry.message, entry.data)
}
})
log.error('Critical issue', { userId: 123, errorCode: 'LOAD_FAILED' })Conditional Features
Environment-Based Debugging
Use process.env.NODE_ENV to enable debug features only during development:
// gwen.config.ts
export default defineConfig({
engine: {
debug: process.env.NODE_ENV !== 'production',
},
modules: [['@gwenjs/physics2d', {}]],
})Now:
- Development builds (
npm run dev) havedebug: true - Production builds (
npm run build) havedebug: false
Conditional System Registration
Register debug-only systems:
import { defineScene } from '@gwenjs/core/scene'
export const GameScene = defineScene({
name: 'game',
systems: [
GameplaySystem,
...(import.meta.env.DEV ? [DebugVisualizationSystem, PerformanceProfilingSystem] : []),
],
})In Practice
Profiling a Performance Problem
You've noticed frame rate drops. Debug mode helps:
Enable debug mode:
tsdebug: trueRun the game and observe the timing overlay. Notice
PhysicsSystemspikes to 8ms when lots of enemies are on screen.Check the system's logging:
tsconst log = createLogger('game:physics', engine.debug) onUpdate(() => { log.debug('Physics step', { bodyCount: physics.bodyCount() }) })Analyze the logs. You discover that body count jumps from 10 to 200 when enemies spawn, and physics thrashes.
Fix: Reduce the number of active physics bodies or use spatial partitioning.
Validating Collisions
Collider wireframes help verify collision geometry:
import { defineScene } from '@gwenjs/core/scene'
export const TestScene = defineScene({
name: 'test',
systems: [ColliderTestSystem],
})
// In your test system, spawn entities normally.
// When debug: true is set, physics colliders render as wireframes automatically.Filtering Logs During Testing
Redirect logs to a test spy:
import { createLogger } from '@gwenjs/core'
import { describe, it, expect } from 'vitest'
describe('MySystem', () => {
it('logs initialization', () => {
const messages: string[] = []
const log = createLogger('test:system', true)
log.setSink((entry) => messages.push(entry.message))
// ... run system setup ...
expect(messages).toContain('System initialized')
})
})Deep Dive
Performance Impact
Debug mode has measurable overhead:
- Collider rendering: ~1–2ms per frame
- Timing overlay: <0.1ms
- Structured logging: Negligible if filtered at runtime
Use import.meta.env.DEV to disable all overhead in production.
Sentinel Checks
When debug: true, GWEN performs extra validation:
- Component arrays are bounds-checked
- Entity IDs are verified to exist
- WASM memory layout is inspected for corruption
These checks catch bugs early but add ~5–10% overhead.
API Summary
| Function | Description |
|---|---|
defineConfig({ debug }) | Enable/disable debug mode |
createLogger(source, debugMode) | Create a logger instance |
logger.debug(msg, data?) | Log only when debug mode is on |
logger.info(msg, data?) | Informational log (debug-only) |
logger.warn(msg, data?) | Warning log (always active) |
logger.error(msg, data?) | Error log (always active) |
logger.child(source) | Create a scoped child logger |
logger.setSink(callback) | Redirect logs to custom sink |
import.meta.env.DEV | Vite flag for development builds |