Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Errors and bug reports

The engine’s core ships minified, so its own function names are gone from a stack. To make up for them, every failure the engine sees carries what you can act on: a code for each error the engine makes, a line that names where the failure happened, and two spawnite commands that name the engine’s frames and write a bug report. This page shows how to read each of them, and what to do when the engine itself is at fault.

Every error the engine makes ends its first line with a stable code, such as [SP0077], and carries the same code as error.code. A code keeps its meaning in every version: a message whose words change takes a new code. The message itself says what was asked, what was wrong and what to change.

The engine labels a failure at four boundaries, where it leaves your code or the engine’s work and reaches the engine: a system’s run in the step, a render of one of the engine’s components, a frame callback, and the connection to the room. When an error crosses one, a second line follows the message. It starts with Spawnite: and names what the engine knew of the failure:

  • system: the system that ran, by its plugin.system name, such as dashes.coolDashes.
  • component: the engine’s component whose render threw, such as <Entity>.
  • frame: the frame callback that threw, by the component or hook that owns it.
  • room: what the connection to the room was doing, such as message from ws://localhost:5174.
  • tick: the tick the step ran.
  • call: the engine call that threw: a public export your code called, such as startCooldown. Only an error the engine makes, with a code, names it.
  • entity: the entity the call was given, by the key the dump files it under and the name a player reads for it. describe_entity takes the key. Only an error the engine makes names it.
  • page: the code’s page in the engine you installed.

The following plugin passes the cooldown it is given to startCooldown:

packages/engine/test/outside/dashes.ts
import type { World } from "koota";
import {
createQuery,
defineCooldown,
definePlugin,
defineTrait,
readEach,
startCooldown,
} from "@spawnite/engine/core";
/** An entity that dashes: each step it dashes, the dash cools down. */
export const DasherTrait = defineTrait("dasher", { dashing: false });
const dashers = createQuery(DasherTrait);
const dash = defineCooldown("dash");
/** Dashes that each cool down for `cooldownSeconds`: list it after
* `cooldowns()`, which counts the cooldowns down. */
export function dashes(cooldownSeconds: number) {
function coolDashes(world: World) {
readEach(world, dashers, ([dasher], entity) => {
if (dasher.dashing)
startCooldown(entity, {
key: dash,
seconds: cooldownSeconds,
});
});
}
return definePlugin({ name: "dashes", systems: { rules: { coolDashes } } });
}

With plugins: [cooldowns(), dashes(0)] and a dasher named Ada, the first step throws this error:

startCooldown: the cooldown "dash" has 0 seconds; pass seconds above 0, or start none [SP0077]
Spawnite: system dashes.coolDashes, tick 0, call startCooldown, entity 3 "Ada", page @spawnite/engine/dist/wiki/engine/errors/SP0077.md

A test asserts the refusal by its code, which stays the same when the message’s words change: expect(thrown).toMatchObject({ code: "SP0077" }).

A development world stops at the throw. A room the platform hosts keeps running, undoes the rest of that system’s tick, and logs each throw with its system, its tick, its code, its call and its entity.

An error with no code, such as a TypeError, is one the engine did not plan for. Its Spawnite: line names the boundary, such as system characters.movement, tick 412, but never a call or an entity: the engine names those only for the errors it makes. Its own message says only what JavaScript saw. Name its engine frames with spawnite symbolicate, below, to find the engine call it ran in.

Each code has a page that ships in the engine’s package, matched to the version you installed. Open it from the game’s folder:

node_modules/@spawnite/engine/dist/wiki/engine/errors/SP0077.md

node_modules/@spawnite/engine/dist/wiki/engine/errors/index.md lists every code with the engine call it is thrown in. Each page holds the message, with %s for each value it names, and the call’s entry, whose declaration says what the call requires.

A stack through the engine’s core shows mangled frames, such as at Oe (.../@spawnite/engine/dist/BMUQtXzM.js:1:2051). To name them, save the stack to a file and run spawnite symbolicate:

Terminal window
spawnite symbolicate stack.txt

The command sends each engine frame, and nothing else of the stack, to the platform. The platform resolves each frame to the public engine call it ran in, never to a source line, and the command prints the stack with those frames named:

The game called startCooldown, which failed inside the engine, from: at cool (src/systems/dash.ts:12:9)
at [engine, in startCooldown]
at cool (src/systems/dash.ts:12:9)
at [engine, its own work]
at [engine, in stepWorld]

Each engine frame prints as one of the following:

  • [engine, in startCooldown]: a frame inside that public export, whoever called it. The summary line above the stack is the one that names the call your game made.
  • [engine, its own work]: a frame no game calls by name.
  • [engine 0.1.0, a file its index does not hold: ...]: a file the platform’s index for that version lacks, such as a build from somewhere other than a release.

When no engine frame sits next to one of your own, the summary says that none of the engine’s frames is a call the game made by name.

Your game’s own frames stay as they are, since they map to your own source. Of each engine frame, the command sends the engine’s version, the file’s name inside the engine’s package, and the line and column; nothing else of the stack leaves the machine. It reads frames of the engine’s package, of a published page’s engine release, and of the dev server’s prebundled engine. --json answers the same as fields. It needs spawnite login, and resolves up to 60 stacks an hour.

Most errors name a mistake in the game, and the message says the fix. Suspect the engine when one of the following holds:

  • The error has no code, and its frames are all the engine’s, such as a TypeError whose frames are all [engine, its own work].
  • The system that failed belongs to one of the engine’s plugins, one your game imports from @spawnite/engine, such as characters(), rather than one in your game’s src/.
  • The call’s declaration says your use is allowed, and the error says otherwise.
  • The same input gives a different result on the room and on the page, which spawnite replay check shows.

When the engine is at fault, write a bug bundle from the replay of the failure. A bundle comes from a replay, which a game’s dev page and dev room keep as it plays: a failure a test found is played once on the dev page first, so a replay holds it. --from and --to take moments as the replay reads them, seconds into it or a mark such as m1, not ticks.

Terminal window
spawnite bugreport latest --stack stack.txt --note "The dash froze the room."

The bundle is a folder that holds the following files:

  • clip/: the replay, or the window --from and --to name, with the room’s checkpoints, so the platform’s team plays it back and continues it against the engine’s source. It holds each player’s name and input, what each page showed, and the world and the saves the game kept.
  • log.json: the replay’s log in that window.
  • bundle.json: the engine’s and the cli’s versions, the plugins src/game.ts lists, the step the room composed where the replay kept one, the Node version and the operating system, and your note.
  • error.txt: the stack you passed with --stack.

Nothing leaves the machine, and the cli has no step that sends a bundle. The command prints what each file holds: show the creator that list, and attach the folder to their report to Spawnite only once they agree. Replay says how a game keeps its replays.