Step Debugger¶
The step debugger lets you pause game execution at any line, inspect local variables and the call stack, and resume with continue, step over, step into, or step out — all from the Debugger tab in the Feather desktop app.
It also hosts Feather's opt-in Hot Reload controls for selected Lua modules.
Warning
Hot reload sends Lua source from the app into the running game. Enable it only in trusted development sessions with a narrow module allowlist.
Setup¶
CLI-managed setup¶
The debugger is controlled by the debugger config option, not the plugin system. Enable it in feather.config.lua, then run through the CLI:
feather run path/to/my-game
feather run path/to/my-game --target web
feather run path/to/my-game --target android
feather run path/to/my-game --target ios
Manual setup¶
Manual Lua integration is only needed for projects that intentionally vendor Feather themselves:
local debugger = FeatherDebugger({
debug = true,
debugger = true, -- install the debug hook on startup
})
function love.update(dt)
debugger:update(dt)
end
You can also leave debugger = false and toggle it on from the Debugger tab in the desktop app at any time without restarting the game.
Setting breakpoints¶
- Open the Debugger tab.
- Browse your source files in the file tree on the left.
- Click any line number to add a breakpoint. Click again to remove it.
Breakpoints are shown as red dots in the gutter. They persist across desktop restarts and are synced to the game whenever the debugger is enabled or a breakpoint is changed.
Note
Debugger expects the folder structure to be the same where the main.lua is located. Feather is capable of automatically detecting the directory, but if it doesn't, just open the project folder where your main.lua is located.
The toolbar shows how many enabled breakpoints the game accepted after path normalization. If a breakpoint path or line cannot be synced, Feather keeps the local breakpoint but reports it in the debugger status instead of failing silently.
Profiler probes¶
Use the stopwatch gutter beside the breakpoint gutter to mark source lines that control the core profiler without pausing the game:
- Click an empty stopwatch slot to cycle
Start -> Stop -> Snapshot -> Empty. - Right-click the stopwatch slot to choose Start profiling here, Stop profiling here, Snapshot here, Profile function here, or Remove probe directly.
- Snapshot probes use the saved label when present, otherwise they create a snapshot named
Debugger probe. - Profile function here installs a
DEBUGGER.profiler:wrap(...)wrapper for supported global/table functions such aslove.update,Game.update,Player:update, orGame.update = function(...). It does not start recording by itself.
Profiler probes persist with debugger state and sync to the active session whenever the debugger is enabled. They run inside the same debugger line hook as breakpoints, so Feather does not install a second debug.sethook path. When a probe and breakpoint share a line, the probe action runs first and the breakpoint still pauses normally.
Probe captures appear in Performance -> Profiler. Use them for line-triggered capture windows around instrumented code, then inspect, diff, snapshot, or export the profiler rows from the Performance page.
Note
Automatic function profiling only supports functions Feather can resolve from _G through table fields. Local functions, closures, and module-private returned tables still need manual instrumentation.
Conditional breakpoints¶
Right-click a breakpoint (or use the condition field) to add a Lua expression. The game only pauses when the expression evaluates to truthy:
-- Only pause when the player takes damage
player.health < 50
-- Only pause on a specific enemy
enemy.id == "boss_1"
-- Pause on the 10th iteration
i == 10
The condition runs in the game's Lua context, so you can reference any global or local variable visible at that line.
If the condition cannot compile or errors while evaluating, Feather reports the condition error in the toolbar and marks the breakpoint line so you can fix the expression.
Pause on error¶
Enable Pause on Error in the Debugger toolbar, or set:
When a wrapped love.* callback crashes, Feather pauses before applying the configured crash behavior. By default the game still rethrows after you resume. If continueOnGameError = true, the game resumes after the pause and Feather keeps reporting the callback failure.
While paused¶
When execution stops at a breakpoint, the desktop shows:
Call stack¶
The full stack trace — file, line, and function name for each frame. Click a frame to navigate to it in the source view and load that frame's locals/upvalues. The highlighted frame is where execution is suspended.
Variables¶
Locals and upvalues of the currently selected frame, expanded one level deep for tables:
dt = 0.016
self = { x = 142, y = 320, health = 75, state = "jumping", … }
velocity = { x = 2.5, y = -8.1 }
onGround = false
Controls¶
| Button | Shortcut | Description |
|---|---|---|
| Continue | F8 |
Resume freely until the next breakpoint |
| Step Over | F10 |
Execute the next line; don't follow function calls |
| Step Into | F11 |
Follow the next function call into its body |
| Step Out | ⇧F11 |
Run until the current function returns |
Typical workflow¶
- Reproduce the bug — run the game and trigger the condition.
- Set a breakpoint — on the line you suspect, or just before the crash.
- Pause — the game freezes; desktop shows variables and call stack.
- Inspect — read locals and upvalues to understand the state.
- Step — use Step Over to walk through logic line by line.
- Continue — resume and wait for the next pause.
Example: tracking down a wrong jump height¶
-- player.lua, line 55
function Player:applyGravity(dt)
if self.onGround then
self.velocity.y = self.jumpForce -- ← breakpoint here
end
self.velocity.y = self.velocity.y + GRAVITY * dt
self.y = self.y + self.velocity.y * dt
end
With a breakpoint on line 55, pause and check:
self.jumpForce— is it the expected value?self.onGround— is the condition entering correctly?GRAVITY— is it the global constant you expect?
Step Over line by line to watch self.velocity.y and self.y update in real time.
Mobile / remote source files¶
Important
If the game is running on a mobile device or in an environment where the desktop can't access the source files directly, click Open folder in the file tree header and select the directory where your .lua files live. The debugger will read files from there for display.
How it works¶
Lua's debug.sethook line hook fires on every executed line. When a breakpoint or step condition is met, love.update blocks in a tight poll while the WS client keeps pumping — so the desktop stays connected and commands (continue, step) can arrive. Resuming any step command unblocks the loop and reinstalls the hook for the next pause.
Note
debug.sethook adds a small overhead to every executed Lua line. It is noticeable in CPU-heavy games. Enable the debugger only during active debugging sessions and disable it from the desktop when not in use.
Profiler probes also use this line hook. They are intentionally line-triggered start/stop/snapshot markers, not call/return hook profiling; heavier profile.lua-style call counting is deferred so the debugger remains the only hook owner.
Integration with Time Travel¶
If the Time Travel plugin is recording when a breakpoint fires, Feather automatically pushes the frame buffer to the desktop. The debugger toolbar shows a Time Travel (N frames) button — clicking it navigates to the Time Travel timeline, pre-loaded with the observer history leading up to the pause.
This lets you scrub backwards from a crash without re-running the game.