The dev loop
PERCH_MOD_DEV=~/perch-dev perch
The dev tap loads every package folder under the given directory alongside
the builtins. Edit, restart Perch, observe — a package that fails to load
shows its exact error in Settings → Mods and touches nothing else.
Taps
| Tap | What it gives you |
|---|---|
PERCH_LOG=mods=debug | the parsed manifest at DEBUG, every load/validation error with its node path, open/command activity traced |
PERCH_DUMP_DISPLAYS=<path> | writes the computed display trees to disk for inspection |
host.log(msg) | your own prints — prefixed perch: mod[<id>]: in the app log |
PERCH_MOD_DEV=~/perch-dev PERCH_LOG=mods=debug perch
Reading a validation error
mod[worldclock] displays.card.children[1].children[2]:
component 'text' has no prop 'colour'
The path is exact — count into your tree and you’re at the node. The validator checks components, props, value ranges, and the limits at load, so a tree that loads cannot fail at render.
The failure table
Every failure mode, one honest behavior each:
| Failure | Behavior |
|---|---|
| syntax error in the entry | mod skipped; Settings shows the Lua error |
api newer than this Perch | skipped: “needs a newer perch” |
| unknown component / prop / over-limit tree | rejected at validation with the exact path |
| callback exceeds the instruction or 50 ms budget | aborted; strike 1 of 3; three consecutive = disabled with a notice; any clean callback resets |
runaway push_data | melted at ~5/s; parked pushes merge per key, newest wins — never silently dropped |
| http outside granted scope / pinned host | refused; callback gets (-1, "scope"); logged |
| touching Perch’s own pref dir | denied regardless of scope |
| broken theme values | theme rejected at load; the picker never shows it |
| crash mid-callback | your lua_State alone dies; your widgets retract cleanly; neighbours and the overlay never notice |
Budget strikes in practice
If your mod dies with a budget notice, the usual suspects in order:
- Formatting in a hot event.
on_mediafires on every position poll — precompute what you can, and onlypush_datafields that changed. - Rebuilding big tables per tick. Hoist constants out of handlers; your file-level locals persist for the mod’s life.
- Hidden O(n²). String concatenation in loops over
listdata — usetable.concat.
The 50 ms wall and ~2M instruction budgets are generous for view-ready
computation — a mod that hits them honestly is almost always doing work
that belongs in a ticker, a
draw painter, or C++‘s side of an
async API.