Debugging#
How to log from your mod, read console output, and fix hook mistakes.
Logging#
Use Petrichor.Log from init, hook callbacks, or tick. Pick a short tag per mod so you can filter the console.
Petrichor.Log prints [my-mod] message. Engine output uses different tags (below).
Reading the console#
Mod output is tagged so you can grep or scroll to the right place.
| Prefix | Source | Typical meaning |
|---|---|---|
[mod:<your-mod-id>] | Your mod via Petrichor.Log | Lifecycle, custom diagnostics |
[mod:luau] | Petrichor Luau VM | Compile/load errors, hook crashes, tick / init failures |
[mod:augment] | Augment symbol resolver | Unknown or blocked hook symbol at registration |
[mod:mixin] | Mixin registration | Which hooks were registered at startup (debug builds) |
Luau errors come with a stack trace. Start at the top:
- First line: the error and where it blew up (often
[string "ModuleName"]:line). - Next lines: callers, usually
function nameand a line number. - Your code: frames in
main.luau,hooks/..., or shared helpers likeCourage.HooksandAugment.Hook.
Wrong parameter name:
[mod:luau] [string "Augment.Hook"]:194: ctx has no field 'arg0'
valid: cancelled, i_stageName, ret, self
[string "Augment.Hook"]:88: function notFoundError
[string "Augment.Hook"]:194: function __index
[string "Courage.Hooks"]:34
[string "Petrichor.Mod"]:15
[string "Augment.Hook"]:223
What this means:
ctx has no field 'arg0': something readctx.arg0, but this hook does not have that field.valid: cancelled, i_stageName, ret, self: the only legal names onctx. Usei_stageNamehere, notarg0.[string "Courage.Hooks"]:34: the throw is insideCourage.Hooks. Check how you call that helper, or patch the helper itself.[string "Augment.Hook"]:223: the hook did run. The crash is in abefore/after/replacecallback.
After a hook error the game keeps going. That one hook invocation is dropped. The original function may still run with vanilla arguments.
Hook context errors#
ctx is strict. If you read or write a name that is not on that function's context, Luau throws. There is no silent nil. Code like ctx.arg0 or ctx.i_stageName still touches ctx.arg0 first and can crash.
What to do:
- Read the
valid:list in the error. Those are the onlyctxkeys for this symbol. - Look up the function on Game Hooks. The detail panel shows parameter names and a sample hook.
- While iterating, print
ctx.__fieldsonce (snippet below). - Struct proxies (
ctx.self, struct params) work the same way. Useproxy.__fieldsif you are unsure.
Print valid names:
Common mistake:
For char const* stage names, use Hook.readCString or Courage.Hooks.readStageName.
Free functions vs methods#
| Hook kind | ctx.self | Receiver / first argument |
|---|---|---|
| C++ method | struct proxy (this) | Also on named params |
| Free function | always nil | Named param, e.g. ctx.i_this |
More detail: Augment.Hook, free functions vs methods.
Game Hooks reference#
Game Hooks is the quickest way to check symbol spelling, parameter names, return types, and whether something is hookable.
For vanilla actor and function names before you search hooks, see the TP Ultimate Spreadsheet.
Search tips:
- Fragment:
checkStageName,dComIfGp_setNextStage,daAlink - Several words (all must match):
alink checkStage - Category:
is:actors,is:other,is:j3d - Header file:
file:d_a_alink,path:d_com_inf_game - Skip inlined stubs:
is:hookableornot:inlined
Click a row for the full signature, source path, and copy-paste hook snippets.
Hooks failing#
Some problems show up in init when you register. Others only when the game actually calls the hooked function.
At registration#
| Message | Cause | What to do |
|---|---|---|
Hook: no such symbol '...' | Typo or name missing from manifest | Fix spelling, search Game Hooks, rebuild the game if the manifest is old |
Hook: '...' is ambiguous (N matches: ...) | Multiple overloads | Pick one with :sig("types"). Do not use :at(). See Hooks reference |
Hook: '...' has no overload (...) | Wrong :sig(...) | Copy types from Game Hooks |
symbol not found ([mod:augment]) | Mangled name not resolved | Same as "no such symbol" |
At runtime#
| Symptom | Likely cause |
|---|---|
[mod:luau] ctx has no field '...' | Wrong ctx or struct field name |
[mod:luau] ... has no field/enum '...' | Wrong field on ctx.self or a struct proxy |
| Hook "does nothing" | Early return, lookup returned nil, wrong overload |
| Original still runs | Normal unless you set ctx.cancelled = true in a before hook |
| Error every frame | Hot-path hook with a bad field access. Fix the name or add a guard |
pcall is fine for a quick experiment. Do not wrap production hook bodies in silent pcall. Fix the field names.
Tracy profiler#
Tracy profiles the game binary (CPU frames, engine zones). Build with Tracy enabled:
Run tp_pc, then open the Tracy profiler and connect to the running process.
For setup and day-to-day use, see Profiling with Tracy.
Luau mod code runs inside Petrichor's shared VM. It may not show up as its own Tracy zones. Use Petrichor.Log for mod tracing.
COURAGE_USE_TRACY is documented in CMake options.
Other Luau errors#
require, init, and tick failures also log as [mod:luau] with a traceback.
Common causes:
- Typo in
require("Petrichor.Task")or a module path - Bad JSON in a storage file
- Yielding from a hook without Petrichor.Task
- Throwing from
init(the mod may be half-loaded)
Common issues#
| Symptom | Things to check |
|---|---|
| Mod not loading | mod.json in its own folder under mods/, hostVersion ≤ game version, engineVersion ≤ Petrichor version |
| Hook never fires | Symbol on Game Hooks, not is:inlined, registered in init, correct overload |
ctx has no field | valid: list, Game Hooks parameters, ctx.__fields, no arg0 guessing |
require fails | Module name spelling, file under mod directory |
| Storage not persisting | Call Storage.save() or self:save(), files under <user_data>/mod_saves/<mod_id>/store.json |
| Shader not visible | Shaders.set_enabled then Shaders.apply(), logical shader name |