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

Packages and kits

This page proposes what each published package holds, how the engine builds a gameplay system, how a game switches one on and customizes it, and what a kit is, so the licence, the publish list, a room server’s install and a developer’s extension point follow one model. The plugin audit, Switching a system on, Extending a plugin and Picking a camera continue it. It is a design draft, and the layers at the end build it in order. The License page holds the licence, and this page settles neither the licence nor the @spawnite names.

The engine’s own gameplay systems are built the way a game builds its own. Spellcasting, mana, NPC routines and dialogue, targeting, inventory and weapons are not special: each is made from the public building blocks a game already has, each is switched on by the games that use it, and each splits into the shared machinery the engine keeps and the content a game copies and owns. Two rules hold the model up, the principle and the line, and the maintainer decided both on 2026-09-30.

An engine gameplay system uses only what a game can use: a phase of the step, a message, a save key, an event, a registry. When a system needs more, the public API grows first. The test is whether a kit outside the engine could rebuild the system.

The principle is written into the repository’s root AGENTS.md beside “No ceiling”, so every future system is held to it in review. It matters to all three people the engine serves:

  • The agent can read, extend or rebuild any system, because every seam the engine’s own systems use is documented and reachable.
  • The creator with no code is never stuck behind something only the engine can do: what a template switches on, an agent can change.
  • The player gets rules the room judges and replicates, which work on the first try because one copy of the machinery serves every game.

The audit lists what the engine’s systems use today that a game cannot, and the layers close each gap before a system is built on it.

Bevy builds most of its engine, the renderer included, as plugins added through the same add_plugins call a game uses for its own. Unity does not: several of its own packages reach engine internals that user code cannot, so a user cannot rebuild them from outside, and Unity developers have complained about that for years. Roblox ships its default camera and character controls as readable Luau a place copies and replaces, on the same API a place’s own scripts use. This platform follows Bevy and Roblox, for the agent: an engine that keeps a private door is an engine an agent cannot fully read.

The line: shared shape, then who changes it

Section titled “The line: shared shape, then who changes it”

Two tests place code on one side of the line between the engine and a kit. Trust is not one of them: the trust boundary page puts the security line at the game’s origin, and a game’s code shares a realm with the engine and can call anything the engine can. What the room judges protects players from each other; what the backend and the credentials hold protects the platform from a creator. Closed engine code protects nothing from a creator, so “the room owns it” no longer decides where code lands. The tests that remain are about sharing and correctness:

  1. Shared shape. Code that every game and every tool must agree on is engine code: the wire format, the save’s keys, a building block other systems call, and what the dump, the devtools and the MCP read. A copy would fork the agreement, and a fix would reach nobody.
  2. Who changes it. Everything else lands by who rewrites it. Code every game runs unchanged and tunes with data is engine code. Code each game rewrites is a kit: copied source the game owns. Where most games keep a default and some change one rule, the engine keeps the default and gives the rule a hook.

The backend keeps a save as text and reads no field of it (the saves table), so persistence no longer decides anything: a kit can keep its own save key, as Shared saves says.

Engine code is closed: it ships minified in @spawnite/engine under the Spawnite License, except the plugins the package ships readable under readable/, today rounds(), as the plugins page says. A game configures it through props, settings, plugin options and registries such as registerWeapon and registerItem, composes it with its own plugins and behaviours, reacts to its events, and replaces its views. A kit is MIT-0 source a command writes into the game.

Every engine the platform is compared with draws the same shape. Roblox keeps the Humanoid, replication and physics in the engine and ships the Weapons Kit in the Toolbox as scripts a place copies. Unity keeps the character controller and Netcode in packages and copies Starter Assets into the project as source. Unreal keeps the Gameplay Ability System in an engine plugin and copies Lyra and its templates as source. Godot keeps CharacterBody3D and the multiplayer API in the engine and publishes its demo projects. PlayCanvas keeps its engine on npm and ships starter kits as projects to fork. Phaser keeps its engine on npm and publishes about 1,800 examples to copy. This platform matches them on the shape and differs on the reason: the others keep replicated machinery in the engine because a copy could not be trusted, and this one keeps it because a copy could not be shared, fixed once, or read by every tool alike.

The following table lists where each package goes and what it holds:

Package Delivery Holds
@spawnite/engine npm, a minified core and readable plugins under readable/, the Spawnite License The runtime and every engine mechanic. Entries: . for the whole engine, /core for the simulation with no React, /three, /vite, /devtools for the hook and the store, and one entry per plugin the engine ships, such as /abilities.
@spawnite/ui, @spawnite/schema npm, minified, the Spawnite License The primitives the engine’s views and a game’s screens share, and the save and wire types the engine, the room and the backend share.
@spawnite/cli, @spawnite/mcp, @spawnite/devtools npm, minified, the Spawnite License The agent’s and the developer’s tools. The devtools overlay is a dev dependency of a game; the devtools says how.
@spawnite/room, @spawnite/dev-server, @spawnite/testing npm, minified, the Spawnite License The development room, which runs a game’s multiplayer on the creator’s machine as the platform’s rooms do, the routes a game’s dev server answers, and a game’s test setup.
templates the registry and the templates repository, MIT-0 The items and the kit manifests. Where kits live says how.
hosted-room, backend, supervisor, assets, plugin, platform-ui, model-viewer private The platform’s servers, the asset source, the Claude Code plugin and the team’s tools.

No package splits for size, and no split would save a published game a byte: a published page loads one shared, prebuilt engine through an import map the site writes, listed in the release modules, and that engine is cached across games: every page loads it from one address. The release serves each closed plugin’s entry as names re-exported from the engine’s root, which every page loads for Game, so that entry adds no code and no file to a page. A readable plugin, such as rounds(), is a module of its own in the release, built from the source the package ships, which loads the engine through the import map; a page that imports it loads that one file more.

A game’s own Vite build does not tree-shake the engine yet. Measured on 2026-10-01 on games/example, whose list is inventory() and rounds(), its first load carries 661.9 kB of minified engine code. Of that, 49.0 kB comes from the four plugins it does not list (npc 16.6, weapons 14.8, abilities 11.3 and track 6.3), plus their components and views. The entry point per plugin, the package’s sideEffects list and /*#__PURE__*/ on the trait constructors change none of it, because one thing still reaches every export: the root entry hands the whole engine namespace to provideEngineModule, for the engine() a spawnite play eval read calls. With that hand-off removed by hand, the same build carries 510.8 kB of engine code, the four plugins 8.9 kB, and the first load’s scripts fall from 844.7 to 748.4 kB gzipped, 11% less. What stays of the four is held by the character’s spawn, the private dump maps and the input copy, as the audit lists. The marks matter where nothing reaches the root: a module that imports only rounds() from @spawnite/engine/rounds carries 23.0 kB of engine code, down from 194.5 kB. So download size is a weak reason to switch a system off. The strong reasons are that an off system does not run each tick, its messages are not accepted, and it stays out of the dump, the devtools and what an agent reads.

Roblox, Unity, Unreal, Godot, PlayCanvas and Phaser each ship one engine and let the build strip what a game does not use. This one has the entry points, the sideEffects list and the pure marks for it, and a world runs only the plugins its game lists. A game’s build strips an unlisted plugin once the root no longer hands its namespace to provideEngineModule.

Every gameplay folder under packages/engine/src/gameplay/ lands in one of three layers, by the two tests above:

  • The core is engine code every world runs: the step, replication and its messages, the save, events and physics. A game never lists it. The shared pieces other systems call, such as stats, cooldowns, resources and state machines, are plugins a game lists, as the composition step decides, and a plugin calls them as functions: startCooldown, addStatModifier, changeResource, dealDamage, emitEvent. Their state is traits, koota’s word for a piece of state on an entity, which a plugin’s own state is too.
  • Plugins are engine code a game switches on: the ability pipeline, the judged shot and the projectile’s flight, the NPC runner, the bag and its verbs, the track and the round. Switching a system on says how, and each plugin is built from the public API by the principle above.
  • The game’s layer is a kit: copied source the game owns. Spells and their looks, the spell bar, the arsenal, the ride’s tuning, and a game’s NPC scripts and dialogue trees.

The following table places every system the engine’s default step runs today, and every gameplay folder, with what a game switches on and what a kit takes. The maintainer decided it on 2026-09-30. It keeps the 2026-09-26 decisions for character, weapons, stats and track, and differs from that table in one line: inventory becomes a plugin a game switches on, rather than a mechanic every game runs.

Folder Its systems, as the step names them Lands
entity, replication, physics entity.move, entity.blendCorrections, physics.step Building block. The step’s own machinery and the room’s protocol.
character characters.copyClientInput, characters.movement, characters.countDown, and respawnDowned inside the behaviours runner Building block, as decided on 2026-09-26. Every game has a character the room replays and corrects.
stats stats.expireModifiers, stats.copyFields Building block, as decided on 2026-09-26. One formula the room and every page resolve alike, and a shape movement, health, abilities and equipment all call.
cooldowns cooldowns.countDown Building block. One timer abilities, items and resources share, as Unreal keeps every cooldown as one kind of timed effect.
resources resources.regenerate Building block. Mana, rage and stamina are keys a game defines with defineResource, and the regeneration rate is a stat; health’s regeneration is set per world with registerHealth. Each defined resource costs one query a step, empty where no entity holds it.
state state.advanceMachines, and reconcileStateTags before the first system Building block. The round, the NPC runner and the conversation are machines, and a game’s own are declared the same way.
behaviours behaviours.run, which runs spin, bob, pickup, loot, lock, interact, trigger, health and chase Building block. The runner and the engine’s behaviours, which the interact message serves.
map, world (levels), ai, checkpoint, camera, clips core.advanceDayClock, core.restColliders, which the core runs; the rest run outside the step Building block. The ground, the levels, the AI tree the tools read, the checkpoint, the camera and the clips.
abilities abilities.approach, abilities.cast Plugin abilities(). The cast, its approach and the targeting, and the abilities.cast message. It requires weapons() for the projectile’s flight. Each spell is data a plugin registers, as Extending an ability says.
weapons weapons.flyProjectiles Plugin weapons(), the engine half of the 2026-09-26 split: the judged shot with its lag compensation, the aim test and the projectile’s flight. The arsenal, the weapon view, the crosshair and the tracer are the shooter kit, from holdfast.
npc npc.converse, npc.runRoutines Plugin npcs(). The routine runner, the conversation and the npcs.dialog message. A game’s scripts and dialogue trees are the game’s own, written with routines and dialog, and a template item carries an example of each.
inventory inventory.useItems Plugin inventory(). The bag, item use, equipment and the item verbs the room judges, with registerItem as the data. The panel stays the default a game replaces.
track track.move, track.markTriggers Plugin tracks(), the engine half of the 2026-09-26 split: the centreline, its surface, its scatter, the trigger and the mover’s step. The mover’s tuning and the ride camera are the ride kit, from sled.
world (round) round.advance Plugin rounds(). The round’s score and clock, for the games that play in rounds.

The views land by the same second test. A view most games keep and restyle stays an engine default a creator changes, as Roblox’s backpack is a default a place turns off: the resource bar, the stats card, the inventory panel, Targeting, SpellLook and DialogPanel, and, inside abilities(), the cast bar, the target frame and the damage numbers, which every class keeps, each switched off per view through the plugin’s options and replaced by the game’s own. A view each game rewrites goes to the kit that owns it: the spell bar, whose layout is the class’s, goes to the mage kit from the mmorpg, beside the shooter kit’s weapon view and crosshair and the ride kit’s camera. The maintainer moved the cast bar, the target frame and the damage numbers from the mage kit to the engine on 2026-09-30, once a second class would keep them.

Opus and Codex reviewed this placement before the maintainer decided. Opus kept resources, cooldowns and state machines wholly in the engine and split abilities and npc as above, which the table adopts. Codex moved more policy into kits: regeneration rules, cooldown grouping, stat formulas and equipment rules, keeping only accounting, save adapters, timers and geometry in the engine. The table keeps that policy in the engine as defaults with hooks, because every copy of a rule is a fork a fix never reaches, and because the room, the dump and the devtools read those shapes in every game.

Every layer below lands before the first npm tag, as the maintainer decided on 2026-09-30, because after the tag an added export breaks nothing while a removed, moved or renamed one, or a changed default, breaks every game that imports it. The following list names what each layer would break if it landed later:

  • The plugin seam removes baseSystems, nameSystem and the systems prop, renames the step’s places to phases, and the six plugins stop running unless a game lists them: removed exports and a changed default.
  • The trait rename gives every trait the Trait suffix: a rename of most engine exports.
  • A save key per plugin changes what a stored save holds, and stored saves outlive releases.
  • The ability data shape makes damage optional and adds targets and a kind: a changed field on every registration.
  • The public doors the audit found, a plain trait streamed to its owner or kept on the room and the character spawn hook, change the engine’s own plugins’ shape, and an entry point per plugin joins the release’s module list, which is a compatibility contract from the tag on.
  • The mage kit moves the mmorpg’s spells and views out of the engine and the game’s own files: an export that would otherwise have to stay.

/core stays an entry of @spawnite/engine and does not become @spawnite/core now.

The split would not reach the room it is for. A room loads its game’s scene file through the game’s Vite and mounts the scene’s component headless, with React and React Three Fiber, as packages/room/src/scene.ts does for holdfast and arena. That scene imports the whole engine, so a room for any game with a scene installs the whole engine whatever core’s package is. An agent’s Node test runs inside a game project, which has the engine installed already.

core could not drop three either. The simulation uses three’s vectors and quaternions in 45 files, and it imports koota, the schema, rapier’s compat build, recast, three-mesh-bvh and alea. What a separate package would leave out is React, fiber, drei, postprocessing, the VRM loader, motion, mediasoup’s client and the ui package.

Roblox, Unity, Unreal, Godot, PlayCanvas and Phaser each ship one engine and run it headless for a server: Roblox’s server runs the same engine, Unity and Unreal strip rendering from a dedicated server build, Godot runs --headless, and PlayCanvas and Phaser run their one package under Node with a null renderer. None ships a simulation package apart.

The entry becomes its own package when something installs the simulation without a scene: a room that runs a spawn function rather than a component, or a published server a developer runs. The first layer of that split cuts the three imports that cross the line today, from gameplay into render/daylight, render/palette and data.

A kit’s source lives in the game it came from, in this repository. A manifest in the templates package names the kit’s files in that game, so the game’s typecheck and lint check the kit on every release, and no second copy exists. The release writes each kit to spawnite/templates beside the templates, under MIT-0, and the registry serves it at /r/kits/<name>.json with the engine version it was built against. spawnite add kit <name> and the MCP’s add_kit tool write it into a game, and re-adding it merges the new version into the game’s copy, as A game’s files says.

No @spawnite/kits package ships. A package installs code a version bump replaces; a kit is code the game owns and changes, and a package would put two copies of it on disk, the installed one and the edited one. shadcn’s registry serves components as JSON for the same reason.

For the command and the tool: spawnite add kit and add_kit, recommended, beside spawnite add behaviour and add_behaviour; spawnite kit add and kit_add; or spawnite install kit and install_kit. Roblox inserts a Toolbox model, Unreal adds a Feature or Content Pack, Unity imports a package’s sample, and Godot installs from the Asset Library.

The sample behaviours are the template items the registry already serves: spawnite add behaviour and its siblings. The engine’s own behaviours, Spin, Bob, Pickup and the rest, stay engine code.

Roblox serves kits from the Toolbox and Unity samples from the Package Manager, each copied into the project. Unreal copies templates and Lyra from the launcher. Godot serves demos and addons from its Asset Library. PlayCanvas forks starter kits into a project, and Phaser serves its examples from a repository.

The 16 files stay in the engine’s tarball: seven animations, six scatter models, one sound and two ground textures, 0.86 MB. The built-in looks light from the engine’s own sky and carry no image. They ship as files, not data URLs, so a game’s build emits only the files its chunks reference. A game builds and runs offline, and a room loads none of them, because headless loads no file.

A CDN with a manifest would add a network fetch to every offline run and a second thing to version beside the engine, to save a download a developer makes once. The move comes when the engine carries assets most games never load, or when the platform’s CDN for a creator’s assets exists and one manifest can serve both; offline development then keeps a copy in the CLI’s cache.

Roblox serves every asset from its CDN by id, and a place opened offline loads none. PlayCanvas serves a project’s assets from its CDN. Unity and Unreal ship an engine’s default content inside the install. Godot and Phaser ship no default assets; their demos and examples carry them.

The devtools publish to npm as @spawnite/devtools, minified with no source like the engine, and a game installs them as a dev dependency. A game mounts <Devtools /> in development. The engine keeps the hook and the store, because a game’s panels register through them and the CLI’s and the MCP’s commands read the store. Whether the platform’s page later injects the overlay over any game, with nothing installed, is a later design, which waits until the play page needs it.

React DevTools attach to a page’s hook from an extension, and the app bundles nothing. Roblox’s developer console is part of the client. Unity, Unreal and Godot keep their tools in the editor and connect to a running game. PlayCanvas runs the game inside the editor’s page. Phaser has no official tools; a browser extension attaches to its global.

The word is kit. Roblox says kit, and PlayCanvas says starter kit, for the same thing: several files a creator copies and changes. Unity’s sample reads as something to look at rather than build on. Unreal’s plugin is installed engine code, which is the opposite of a kit. Godot says demo and addon, and Phaser says example, for a whole project and a snippet. A kit is finished content a game copies, a manifest over a game’s plugins, their looks and assets, where a template is a shape a tool fills in; a plugin is the unit the engine composes, and a kit is the unit a creator copies.

The first release ships three kits. The shooter kit from holdfast proves the whole path: a game’s files, a room-side reaction to DamagedEvent, the registry, spawnite/templates and the add_kit tool. The ride kit from sled follows it, with the first camera a kit registers. The mage kit from the mmorpg is the first kit made of plugins: its spells, their looks, the spell bar and the class’s HUD composition, with the cast bar, the target frame and the damage numbers left in the engine as abilities()’s defaults. A marketplace listing where a creator’s kit earns comes later.

The layers are sub-issues of the two design issues, in the order they run. Each one names the layers it waits for.

The package design issue holds these:

  1. Events are traits the step removes after one step, streamed by the room
  2. DamagedEvent and the trigger’s enter and exit events, with the useEvent hook
  3. A scene picks its camera by name, and every camera registers one
  4. An agent lists and picks a game’s cameras from the CLI and the MCP
  5. Kits: manifests, the registry’s kit routes, spawnite add kit and add_kit
  6. The shooter kit: the weapon views move from the engine to holdfast
  7. spawnite/templates publishes each kit beside the templates
  8. The ride kit: the track mover and its camera move from the engine to sled
  9. The wiki’s kit pages, and the behaviour pages name their events

The gameplay systems issue holds these, and every one lands before the first npm tag:

  1. Plugins: definePlugin, the composer, Game plugins, the six engine plugins, the Trait suffix and an entry point per plugin
  2. Plugins use only public doors: owner-only and room-only traits, onCharacterSpawn, runsOn on a plugin’s system
  3. Saves: a key per plugin, validated, dropped and migrated per key, with the remainder kept
  4. Abilities open up: effects as data, the target rule, a direct kind, and the cast events
  5. The mage kit: the mmorpg’s spells as plugins, the spell bar, and the shared views into abilities()
  6. Tooling: list_registry, the plugin line written by add_*, the kit lock, the update notice and spawnite update kits

The mage kit waits for the kit pipeline; every other layer waits only for the one above it.