Debug inspector

Usage guide of debug.inspector: what the panel shows, who may open it, settings and defaults, cost, the gripforge.debug/1 API for pages and agents, the debug.open hook, kit modes.

Source: module-kits:kits/debug_inspector/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs debug-inspectorMCP tool: gripforge_docs { "topic": "debug-inspector" }

Debug inspector of your game (debug.inspector) #

One debug panel for every game: frame time, GPU, rendering counters, wireframe, collision boxes, the scene tree, and the debug modes your kits add. The same data is available as JSON for a page and for agents. Readable offline: gripforge docs debug-inspector, or the MCP tool gripforge_docs (topic debug-inspector).

What it is for #

  • Frame: frames per second, mean frame time, p50 / p95 / p99, the worst frame, frames over 33 ms (a window of the last 240 frames), and where the time of a frame goes.
  • GPU: the backend (webgpu, webgl2, vulkan, dx12, dx11, metal, opengl, gles), the adapter, its vendor and type, and whether it is a software rasteriser (the measures then say nothing about a real GPU).
  • Rendering: draw calls, drawn meshes, triangles, materials, lights, shadow casters, textures and their bytes, entities, memory.
  • Modes: wireframe, collision boxes (the size of every entity), the bounds of every drawn mesh.
  • Scene tree: every scene and its nodes and meshes, within a depth and node budget. A node can be hidden, outlined or selected (components, transform, mesh, the kit entity it draws).
  • Kit modes: the modes other kits register (paths of the AI, display optimisation, and so on).

Turn it on #

debug.inspector is installed in every new game and open to everyone by default; existing games receive it at their next opening. Press F3 (on a Mac: fn + F3, or the fallback Ctrl + Alt + D). Nothing is loaded, measured, allocated or drawn until the key is pressed.

gripforge_gamekit_install   { project_id: "gkp_…", id: "debug.inspector" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "debug.inspector",
                              config: { "access": { "players": false }, "toggleKey": "F4" } }
gripforge_gamekit_configure { project_id: "gkp_…", id: "debug.inspector", config: { "enabled": false } }   # off for the game

Removing the kit has the same effect as enabled: false: no key, no panel, no page or agent API. It requires input.actions and game.engine. The full reference is gripforge kit api debug.inspector.

Settings and defaults #

KeyDefaultMeaning
enabledtruethe developer's switch; false turns the whole inspector off
toggleKeyF3the key that opens and closes it; null = no key (then only ?debug=1 or the API). Free in every control scheme
access.teamtruethe owner, the members of the workspace and a local build (engine editor, desktop export run by its developer)
access.playerstruepublic players (false: the team only)
access.querydebug?debug=1 opens it at start for those allowed
refreshFrames10frames between two snapshots while open
treeDepth, treeMaxNodes6, 2000budget of the scene tree
startModesall falsewireframe, colliders, bounds on at start
panel{ side: "right", width: 380, layer: "top" }where the panel sits
modeFrames15frames between two calls of an open kit mode

The key goes through input.actions like every key. It is registered only when the inspector is enabled and the viewer is allowed, and it is never shown in the panel nor listed in a Controls screen.

Cost #

Closed and unread, the inspector costs one key test per frame. On the web nothing of the panel is loaded (code, markup, styles, passport requests) until the first press; the first press brings it in with one dynamic import. Open, the engine measures every frame within fixed bounds (a ring of 240 frame times, counts every 30 frames), rebuilds the boxes every 6 frames and the snapshot every refreshFrames. The scene tree is built only when asked, the wireframe within 600 000 edges.

The API (gripforge.debug/1) #

One contract on every engine. A snapshot is { schema, engine, open, sampling, frame, timing, gpu, render, modes, selection, hidden, outlined, tree?, details?, tick?, extensions, notes }. Ops are sent alone, in a list, or as { ops: [...] }:

OpFieldsDoes
snapshotanswers the snapshot (keeps the measures for 10 s when the panel is closed)
open, closeopens or closes the panel
modewireframe, colliders, bounds (booleans, any subset)turns modes on or off
treeroot?, depth?, maxNodes?builds the scene tree once
selectidsselects nodes (outlined, details of the first)
hideid, hiddenhides or shows a node
outlineid, onoutlines a node
resetshows every hidden node and removes every outline
extensionid, onturns a kit mode on or off

TICK (section of the F3 panel, field tick of the snapshot): the game logic tick, separate from the render frame and the physics step. It gives the requested and played rate (hz), the tick counter, ticks per frame, logic milliseconds per tick, the time dropped by catch-up, the interpolation alpha and the configured periods (tick.systems). The 15/20/30/60 buttons change the rate live (next tick boundary, nothing reset; in multiplayer only the authority).

Ways to reach it:

  • Agents: MCP gripforge_debug_snapshot and gripforge_debug_set; CLI gripforge debug snapshot <project> and gripforge debug set <project> '<ops>'. They act on a game open in a browser by a member of the team, with the same rights as the panel.
  • The page: gripforgePresenter.debug(json) in the frame of the native player (synchronous: the last snapshot; the ops are applied at the next frame), and the runtime protocol operations debug_snapshot / debug_set.
  • A kit: the service debug.inspector (allowed(), isOpen(), open(by), close(), toggle(by), set(ops), view(), modes(), addMode(mode), setMode(id, on), scenes()).

Hooks, events, registry #

WhatNameDetail
Hookdebug.openpayload { by, viewer } (by: key, query, api; viewer: owner, member, local, player); cancellable. A game or a mod refuses it (a ranked match); the inspector stays closed and debug.refused follows
Eventsdebug.opened, debug.closed, debug.mode, debug.refused
Registrydebug_modesthe kit modes; a game or a mod adds one (moddable, extends allowed)
Moddable configenabled, toggleKey, access, refreshFrames, treeDepth, treeMaxNodes, startModes, panel, modeFramesa mod changes them like the game does

Add a mode #

A debug mode is data: { id: "<kit>.<name>", kit, label, kind, call, args?, on? }. kind is overlay (the call answers what to draw, { lines | paths | marks | boxes, color } in world units), info (rows for the panel) or scene (nodes listed beside the scene tree, for a kit with its own notion of scene). While a mode is on and the panel open, the inspector calls it every modeFrames frames and hands the answer to the engine. A kit adds modes with addMode(mode) when it mounts:

{ "id": "ai.navigation.paths", "kit": "ai.navigation", "label": "AI paths", "kind": "overlay", "call": "ai.navigation.debugPaths" }

Engines #

The measures and the drawing come from one Rust core, adapted by each engine with its own tools. The web and native players implement the full contract. Godot, Unity and Unreal follow the same contract.

When it does not do what you expect #

SymptomCause and fix
F3 does nothing on a Macthe F-keys are media keys: hold fn (fn + F3), or use the fallback Ctrl + Alt + D (same rule), or ?debug=1
F3 does nothingthe kit is removed or enabled: false; the viewer is refused by access (players: false) or by a debug.open tap; toggleKey was changed or is null
The GPU line says softwarethe measures come from a software rasteriser; measure on a machine with a real GPU
The scene tree is cutraise treeMaxNodes / treeDepth, or send a tree op with a narrower root
A kit mode is missing from the listthe kit did not register it with addMode, or it is not in debug_modes

Turning a Worker off (comparison) #

?workers=0 turns every Worker off; ?worker.render=0, ?worker.physics=0 or ?worker.<module id>=0 (e.g. worker.physics.rapier=0) one only; =1 asks it (it still falls back with a reason if impossible). ?playerWorker=0 still works. The same keys in localStorage: gripforge.workers, gripforge.worker.<id>; the address wins. F3 and the snapshot (renderThread, physicsThread) then read main (disabled by option (...)).