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

Plugin audit

Packages and kits builds every gameplay system from the public API, so that a kit outside the engine could rebuild it. This page lists what stood in the way.

The following table lists, for each folder, what its systems use today that a game’s own code cannot reach through the public API, and what depends on it. Line numbers are at commit ebc716025 on 2026-09-30, and each entry names the kind of gap: a save field the schema fixes, a message every world registers, a call hardwired into the step or the world, a trait map private to the dump, the character’s spawn, or an import that has no exported equivalent. Every gap the first column names is closed by a layer at the end, so the plugin that replaces the folder uses only public doors.

Folder Uses what a game cannot What depends on it
abilities The engine.cast message every world registers (gameplay/runtime.ts:35). Private dump maps: AbilitiesTrait streams to its owner alone (gameplay/harness.ts:286), PendingCastTrait stays on the room (:316), TargetTrait holds a handle (:325). The character’s spawn grants abilities from the Player prop (components/Player.tsx:127-130, :307) and from CharacterSpawnTrait in the room (room/src/room.ts:1046-1047). Unexported helpers: the resource reserve and settle (gameplay/resources/resources.ts:239-269), the strike, line of sight and slow (gameplay/abilities/abilities.ts:124-237). The projectile (gameplay/weapons/projectile.ts:3-7); equipment grants and revokes (gameplay/inventory/equipment.ts:2); the room (room/src/room.ts:39, :1047); the dump; no CLI, MCP or devtools code, until the abilities inspector lands. Not a predicted system: a replay never runs it.
cooldowns CooldownsTrait streams to its owner alone through the private map (gameplay/harness.ts:288), and the room sends owned traits only for a player’s character (room/src/room.ts:1779), so an NPC’s cooldowns reach no page. Abilities (gameplay/abilities/cast.ts:283-291), resources (gameplay/resources/resources.ts:198-200), item use (gameplay/inventory/use.ts:135-165).
resources The fixed resources save field (schema/src/save.ts:85-87, read at gameplay/save.ts:37, restored at components/Player.tsx:250). The character’s spawn declares them from the Player prop (components/Player.tsx:131-134) and from CharacterSpawnTrait (room/src/room.ts:1039-1040). Abilities’ costs (gameplay/abilities/cast.ts:10-14), the save (gameplay/save.ts:4), the room (room/src/room.ts:40).
stats The fixed stats save field (schema/src/save.ts:82, read at gameplay/save.ts:33-35, restored at components/Player.tsx:296 and room/src/room.ts:1041). The character’s spawn adds the trait with maxHealth (gameplay/character/actions.ts:54-60). The formula’s totals are unexported (gameplay/stats/totals.ts:12-32), and weapons import them (gameplay/weapons/traits.ts:7-13). Abilities, resources, weapons, inventory, health (gameplay/behaviours/systems.ts:302), movement (gameplay/character/movement.ts:116), the devtools’ stats rows (stores/devtools.ts:1603-1627, devtools/src/WorldPanels.tsx:33-48), the wiki tab (stores/devtoolsWiki.ts:223-247).
weapons Shots ride fixed protocol types rather than a registered message (gameplay/replication/protocol.ts:58, :43, gameplay/replication/delta.ts:25), and the room hardwires the judge, the rewind and the shot results (room/src/room.ts:1512, :1809-1815, :1870). FiredShotsTrait and LatencyTrait stay on the room through the private map (gameplay/harness.ts:314-315). The character’s spawn adds HeldWeaponsTrait from the Player prop (components/Player.tsx:121-126) and the room adds LatencyTrait and FiredShotsTrait (room/src/room.ts:1057-1058). The crosshair module is unexported (gameplay/weapons/crosshair.ts:66-176). Abilities’ projectile and cover (gameplay/abilities/cast.ts:16-17); the room’s shot rules (room/src/shots.ts:167-240) and bot; spawnite play aim (cli/src/play.ts:2631); the devtools’ aim read (stores/devtools.ts:1825).
inventory The fixed inventory and equipment save fields (schema/src/save.ts:90-91, read at gameplay/save.ts:38-39, restored at components/Player.tsx:299, :304). Item verbs ride a fixed protocol type (gameplay/replication/protocol.ts:63, applied at gameplay/replication/items.ts:29, sent by the frame loop at components/Frameloop.tsx:622). InventoryTrait and EquipmentTrait stream to their owner alone through the private map (gameplay/harness.ts:289-290). The character’s spawn declares the bag from the Player prop (components/Player.tsx:135-139) and in the room (room/src/room.ts:1050-1051). Pickups (gameplay/behaviours/systems.ts:13-15), the room’s item checks (room/src/room.ts:590-617), spawnite add item and place_loot (cli/src/items.ts:60, :121; mcp/src/tools/item/index.ts:13, :31), the wiki tab.
npc The engine.dialog message every world registers (gameplay/runtime.ts:34). The conversation streams to its owner alone through the private map (gameplay/harness.ts:287) and through the dialog machine’s ownerOnly (gameplay/npc/dialog.ts:382); the routine and dialog machine traits are hidden from the dump (gameplay/npc/traits.ts:94, :121, :158). toMachineId is unexported (gameplay/npc/machineId.ts:8). Faction, which the projectile and abilities read (gameplay/weapons/projectile.ts:8, gameplay/abilities/traits.ts:4); add_npc and add_dialog (mcp/src/index.ts:140, :158) and their templates; the inspector’s NPC section (devtools/src/surfaces/InspectorPanel.tsx:140-148); DialogPanel (components/DialogPanel.tsx:45).
state The step calls reconcileStateTags before its first system (gameplay/step.ts:474) and the world subscribes watchStateTags as it is made (gameplay/runtime.ts:41). ownerOnly on a machine is the one public route into the owned stream (gameplay/state/states.ts:198, gameplay/harness.ts:438-440). The round, the NPC runner and the conversation; the checkpoint; the room’s trait writes (room/src/room.ts:2112); the AI tree (gameplay/ai/tree.ts:246-248); the devtools (stores/devtools.ts:2460, :2488).
character The fixed character save field (schema/src/save.ts:69-77, read at gameplay/save.ts:14-18). Join, input and walk ride fixed protocol types (gameplay/replication/protocol.ts:48-53). RespawnTrait stays on the room through the private map (gameplay/harness.ts:317). The room spawns a character from CharacterSpawnTrait’s fixed fields (gameplay/character/movement.ts:128-151, room/src/room.ts:1019-1072), and no join hook lets a plugin add to her. Twenty-four gameplay files, the room’s spawn and correction, the replay (gameplay/replication/replay.ts:107-120), the CLI’s dump names (cli/src/playtest.ts:617-623), the devtools.
track copyClientInput copies the step’s input into each track mover inside the character’s own system (gameplay/step.ts:142-146). TrackTriggerMoversTrait holds handles through the private map (gameplay/harness.ts:326). The round’s laps (gameplay/world/round.ts:33-35), the track camera (gameplay/camera/trackCamera.ts:4-5), the AI tree (gameplay/ai/tree.ts:110-113).
world The fixed levels save field (schema/src/save.ts:94, read at gameplay/world/levels.ts:149). The round’s score counts the coins addCoins credits while it plays (gameplay/world/round.ts), and no public call adds to it otherwise. spawnite simulate (cli/src/simulate.ts:286-287); the round in the dump (cli/src/playtest.ts:688-695).
behaviours The engine.interact message every world registers (gameplay/runtime.ts:33). The fixed loot save field (schema/src/save.ts:97-99). The runner runs a closed list (gameplay/behaviours/systems.ts:391-403) and reads runsOn for that list alone (:412), so a game’s behaviour with runsOn set runs wherever the game’s own list puts it. The InteractedEvent event is never registered (gameplay/behaviours/traits.ts:76). The approach module is unexported (gameplay/behaviours/approach.ts). Every gameplay folder; the room (room/src/room.ts:1032, :1048, :1054); add_behaviour (mcp/src/index.ts:118); the devtools’ Add behaviour menu.
checkpoint The physics codec is hardwired (gameplay/checkpoint/world.ts:103-109) from an unexported module (gameplay/physics/checkpoint.ts). The room’s continue (room/src/room.ts:2038-2161), spawnite replay (cli/src/replay.ts:564, :682).
the others entity, replication, physics, map, ai, camera and clips are building blocks: what they use is the step itself, and the gaps are unexported modules (gameplay/replication/codec.ts, gameplay/map/colliderBake.ts, the physics shapes) that no plugin needs. Everything.

Three findings cut across the table:

  • Two lists, and they differ. A game’s page reads <Game systems> and a game’s room reads the scene file’s systems export, and nothing checks that they match (components/Game.tsx:190, room/src/scene.ts:343-366). Holdfast’s room runs its siege list while its page runs the engine’s default, because its app passes no list (games/holdfast/src/app/app.tsx:71-76). spawnite simulate reads a third source, src/systems.ts by convention (cli/src/simulate.ts:266-280), which misses holdfast’s src/siege/systems.ts.
  • A place bundles systems with order inside it. Depthfield splits baseSystems.input by position to put its dash between the input copy and the steer (games/depthfield/src/systems.ts:9-21), which the step’s own comment says a game avoids (gameplay/step.ts:226-228). The engine’s comments name three orderings inside a place, approachCastTargets before steerCharacters, runItemActivations before runBehaviours, castAbilities before flyProjectiles, and one across places, the stats’ expiry first in move so every behaviour reads a stat without its spent buffs.
  • One player only. A game can register a message (registerMessage, as holdfast’s signals do) and its systems can read it, but a game cannot stream a plain trait to one player only or keep one on the room: registerTrait writes only the named and event maps (gameplay/harness.ts:354-356). The one public route is a state machine with ownerOnly, which the dialog uses. Since then, defineTrait takes ownerOnly and serverOnly, as Choose who a trait reaches describes, so a game’s plain trait has both routes.