Keel ships a focused set of in-process developer tools that overlay directly on top of your running game. One call wires them all up: the ImGui world inspector lets you browse every entity and its component values in real time, the frame profiler shows per-system millisecond costs with rolling 60-frame averages, and the physics debug draw outlines every collider so you can see exactly what the physics engine sees. All three are optional and are only activated by callingDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/VKSFY/keel/llms.txt
Use this file to discover all available pages before exploring further.
keel.dev_tools(app) — nothing runs unless you ask for it.
Installation
The dev tools depend onimgui-bundle, which is not part of Keel’s base install. Add the tools extra when you install the package:
If
imgui-bundle is not installed and you call keel.dev_tools(app) (or any of the individual setup functions), Keel raises an ImportError with the exact pip install command you need. No silent failure.Enabling the bundle
keel.dev_tools(app) returns a DevTools object and caches it on the app. Calling it a second time returns the same cached instance rather than registering duplicate systems.
What dev_tools sets up
Profiler
Calls
setup_profiler(app) — attaches a FrameProfiler to the scheduler so every system is timed automatically.Debug Draw (if Physics2D is present)
Calls
setup_debug_draw(app) only when Physics2D is already registered as a world resource. If you have not called setup_physics_2d yet, tools.debug_draw is None.DevTools exposes three attributes after setup:
| Attribute | Type | Description |
|---|---|---|
tools.profiler | FrameProfiler | The profiler attached to the scheduler |
tools.debug_draw | DebugDraw2D | None | The collision-shape overlay, or None if no physics |
tools.inspector | WorldInspector | The ImGui entity browser |
World Inspector (F1)
The world inspector is an ImGui window that lists every archetype registered in the ECS world, including entity IDs and live component field values. It is the fastest way to answer “did my system actually write what I think it wrote?”How it works
setup_inspector(app) creates a WorldInspector and a ProfilerOverlay, registers a shared _ImGuiHost against the app’s ModernGL context, and wires two systems:
- A
PRE_UPDATEsystem that checks F1 (inspector) and F2 (profiler overlay) toggle keys. - A
POST_RENDERsystem that brackets the ImGui frame, draws both windows, and submits the draw data to GL.
Reading the inspector window
Entity count
The header shows the total entity count and the number of non-empty archetypes versus all registered archetypes.
Filter box
Type a component name (e.g.
Sprite) to narrow the list to archetypes that contain that component. The match is case-insensitive substring search.Tree rows
Each row shows
#<entity_id>: ComponentA, ComponentB, .... Click the row to expand it and see every field value sourced live from the structured arrays.Field values
Fields print as
ComponentName.field = value. Non-numpy components fall back to <opaque>. Values are read fresh each rendered frame.Programmatic control
Frame Profiler (F2)
FrameProfiler wraps every system that the scheduler invokes in time.perf_counter() markers. The overlay (top-right of the window) lists each system name alongside its rolling 60-frame average in milliseconds and a proportionally scaled bar chart.
Setup
setup_profiler is idempotent — if you call keel.dev_tools(app) first, tools.profiler is the same instance. The profiler is inserted into the world as a resource (FrameProfiler) so other systems can read it via resource injection.
Reading stats programmatically
get_stats() returns a snapshot of every system that has at least one recorded sample. Each SystemStats entry carries:
| Field | Type | Description |
|---|---|---|
name | str | System function name |
avg_ms | float | Rolling 60-frame average, in milliseconds |
min_ms | float | Minimum across the rolling window |
max_ms | float | Maximum across the rolling window |
last_ms | float | Most recent sample |
Attaching and detaching manually
setup_profiler calls Scheduler.attach_profiler(profiler) internally. You can also call these methods yourself:
Overlay controls
The profiler overlay shares the ImGui host with the world inspector. Toggle it with F2 while the game is running, or control it programmatically through theProfilerOverlay attached to the inspector setup:
Debug Draw 2D (F3)
DebugDraw2D renders a collider-outline overlay using GL line segments drawn at Phase.POST_RENDER. It walks every entity that has Transform2D + Collider2D + RigidBody2D and tessellates each shape into line segments, grouped by body type so the whole overlay issues one draw call per color.
Color coding
| Color | Meaning |
|---|---|
Green (0.20, 0.85, 0.30) | Dynamic body |
Gray (0.55, 0.55, 0.55) | Static body |
Blue (0.30, 0.55, 1.00) | Kinematic body |
Yellow (1.00, 0.95, 0.20) | Sensor collider (any body type) |
Shape tessellation
| Shape type | Lines drawn |
|---|---|
ShapeType2D.CIRCLE | 32-segment circle outline |
ShapeType2D.BOX | 4-segment rectangle, rotated by Transform2D.rotation |
ShapeType2D.SEGMENT | Single line segment |
Setup
keel.dev_tools(app) handles the ordering automatically: it only calls setup_debug_draw when Physics2D is already registered, and registers the debug draw system before the inspector so the GL lines appear beneath the ImGui windows.
Programmatic control
Keyboard shortcuts
All three tools use edge-detected polling viaapp.input.is_key_down, checked once per simulation tick. This means each key press toggles exactly once, even if the game runs at a high frame rate with multiple visual frames per sim tick. KeyEvents are not used here because input events can be dropped on visual frames where no sim tick fires.
F1
Toggle the World Inspector ImGui window.
F2
Toggle the Profiler overlay (top-right HUD).
F3
Toggle the 2D physics Debug Draw overlay.
Full example
The following example is drawn fromexamples/devtools_overlay.py in the Keel repository. It spawns 50 dynamic balls inside a static-walled box with zero gravity and enables every overlay with one keel.dev_tools(app) call.
