Skip to content

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.

local Log = require("Petrichor.Log")

function MyMod:init()
    Log.info("my-mod", "loaded")
end
import * as Log from "Petrichor.Log"

declare function mod(name: string): any

const MyMod = mod("my-mod")

MyMod.init = function () {
    Log.info("my-mod", "loaded")
}

export = MyMod

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:

  1. First line: the error and where it blew up (often [string "ModuleName"]:line).
  2. Next lines: callers, usually function name and a line number.
  3. Your code: frames in main.luau, hooks/..., or shared helpers like Courage.Hooks and Augment.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 read ctx.arg0, but this hook does not have that field.
  • valid: cancelled, i_stageName, ret, self: the only legal names on ctx. Use i_stageName here, not arg0.
  • [string "Courage.Hooks"]:34: the throw is inside Courage.Hooks. Check how you call that helper, or patch the helper itself.
  • [string "Augment.Hook"]:223: the hook did run. The crash is in a before / after / replace callback.

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:

  1. Read the valid: list in the error. Those are the only ctx keys for this symbol.
  2. Look up the function on Game Hooks. The detail panel shows parameter names and a sample hook.
  3. While iterating, print ctx.__fields once (snippet below).
  4. Struct proxies (ctx.self, struct params) work the same way. Use proxy.__fields if you are unsure.

Print valid names:

local Hook = require("Augment.Hook")

Hook["daAlink_c_checkStageName"]:before(function(ctx)
    print("ctx fields:", table.concat(ctx.__fields, ", "))
    -- cancelled, i_stageName, ret, self
end)
import * as Hook from "Augment.Hook"

Hook.daAlink_c_checkStageName.before((ctx) => {
    print("ctx fields:", ctx.__fields.join(", "))
    // cancelled, i_stageName, ret, self
})

Common mistake:

local Hook = require("Augment.Hook")

-- bad: arg0 is not on this ctx
local name = Hook.readCString(ctx.arg0 or ctx.i_stageName)

-- good
local name = Hook.readCString(ctx.i_stageName)
import * as Hook from "Augment.Hook"

// bad: arg0 is not on this ctx (cast only to show the Luau mistake)
const bad = Hook.readCString((ctx as any).arg0 ?? ctx.i_stageName)

// good
const name = Hook.readCString(ctx.i_stageName)

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:hookable or not: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:

cd pc
cmake -B build -DCOURAGE_USE_TRACY=ON
cmake --build build --target tp_pc

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

See also#