Appearance
Errors
Every error Kiln throws is a KilnError with a stable code and a message that reads "what happened. What to do." The CLI prints them as error <code>: <message>, and --json output carries the code. Most of the time the right move is to do what the message says.
ts
import { KilnError } from 'kiln'
try { app.step() } catch (err) {
if (err instanceof KilnError && err.code === 'not-ready') await app.ready()
}An exception thrown inside a system that isn't a KilnError is wrapped as system-threw, with the system's name and phase in the message and the original as cause.
The common ones
| Error | Fix |
|---|---|
A and B both take Mut(T) in Fixed with no order | chain(A, B) or B.after(A) |
X wrote T.f, but its query takes T read-only | Mut(T) in X's query |
X called entity.destroy() during Fixed | commands.despawn(entity) |
X wrote Score.value through res.get(Score) | res.mut(Score) |
X wrote T.f on the copy entity.get() returns | Write through the row, or entity.set(T, {...}) with Mut(T) |
app.step() ran while an async plugin is still loading | await app.ready() (scenarios do it) |
No trait named "X" (kiln run) | Check kiln describe; names come from exports |
TickTimers and X both take Mut(Timer) | X.after(TickTimers) |
'x' is not a value of this state | Use one of the listed values, or add it to state([...]) |
Saves needs the save plugin | .use(savePlugin({ resources, traits, persist })) |
Ui.click('x'): there is no button 'x' | The button isn't set yet (step first) or the id differs |
AnimateSprites and X both take Mut(SpriteAnimator) | X.before(AnimateSprites) (3D: .before(AnimateModels)) |
No animation param 'x' / trigger / state | Declare it in the graph's params / triggers / states |
Animation state 's' plays clip 'c', which ... doesn't have | Set the state's clip to one of the listed clips |
All codes
Schedule
| Code | When |
|---|---|
write-conflict | Two systems in one phase take Mut(T) with no order between them |
unknown-order-target | .after(X) / .before(X) where X isn't in the same phase |
cycle | Ordering constraints form a cycle (the message prints it) |
duplicate-system | A system was added to a phase twice |
not-a-phase | app.add() without a phase first |
not-schedulable | app.add() got something that isn't a system, chain or entry |
not-a-plugin | app.use() got something that isn't a plugin or function |
not-initable | app.init() got something that isn't a resource or event |
Access (dev builds)
| Code | When |
|---|---|
readonly-write | A system wrote a trait its query takes read-only |
undeclared-write | entity.set(T, ...) without Mut(T) in the system's queries |
write-to-copy | A write to the copy entity.get(T) returns |
readonly-resource | A write through res.get(R) |
structural-in-system | entity.add, entity.remove or entity.destroy inside a system |
world-write | A write through the read-only ctx.world |
app-in-system | app.spawn, app.set and friends called inside a system |
App lifecycle
| Code | When |
|---|---|
not-ready | Stepping before async plugins finished |
disposed | Using an app after dispose() |
too-many-apps | More than 16 live apps (dispose the ones you're done with) |
headless-render | app.render() on an app that hasn't started on a canvas |
system-threw | A system threw a non-Kiln error |
pending-entity | PendingEntity.get() before commands applied |
no-input-plugin | app.input without the input plugin |
States, timers, tweens, UI, saves
| Code | When |
|---|---|
unknown-state, empty-state | A state value not in the list, or a state with no values |
not-an-event | Delayed.emit without an event |
tween-args, tween-field, unknown-ease | Bad Tweens.to arguments |
ui-id, ui-anchor, no-button | Bad HUD ids or anchors; clicking a missing button |
no-save-plugin, save-slot, save-corrupt | Saves without the plugin; a bad slot name; unreadable slot data |
Assets, rendering, physics
| Code | When |
|---|---|
asset-missing | A path that isn't in public/ |
unknown-instanced | Instanced(key) with no defineInstanced(key, ...) |
physics-not-ready | Physics used before the plugin loaded |
no-body | A Physics call on an entity without a body (bodies appear the step after RigidBody is added) |
Animation
| Code | When |
|---|---|
graph-empty, graph-param, graph-event | A malformed animationGraph |
graph-unknown-state, graph-unknown-param, graph-unknown-trigger | The graph names something it doesn't declare |
anim-unknown-state, anim-unknown-param, anim-unknown-trigger | An animator call names something the graph doesn't declare |
anim-unknown-clip | A state plays a clip the sheet or model doesn't have |
anim-frame-event | A frame event on a glTF clip (use { at: seconds }) |
anim-no-clips, anim-model-failed | A 3D animator with nowhere to get clips, or a failed model |
sprite-clip, sprite-frame, sprite-grid, sprite-no-sheet, not-a-sheet | Malformed sprite sheets and animators |
Recordings
| Code | When |
|---|---|
not-a-recording, recording-version | The file isn't a Kiln recording, or is from another version |
record-after-boot, replay-after-boot | A Recorder or Replayer created after Startup ran |
replay-config | Replaying on an app with a different loop config |
replay-diverged | A strict replay's state stopped matching |
recorder-stopped | stop() called twice |
CLI
| Code | When |
|---|---|
no-entry, bad-entry | src/main.ts missing, or not default-exporting a function that returns the app |
unknown-trait, unknown-resource, unknown-event | A name passed to --snapshot, --resources or --events doesn't exist |
bad-script | A malformed input script event |
no-recording | kiln replay without a file, or with a missing one |
no-chromium | kiln shot can't find a browser |
unknown-plugin, plugin-exists | kiln add with an unknown name, or over an existing folder |
missing-dir, not-empty | kiln new without a folder, or into a non-empty one |