Making a game

Building an extension

Panels, tools, fields and routes added to the editor.

This covers a panel, a tool, a command, an inspector section, a field type or a route added to the editor, yarn editor <game>. Nothing a game runs imports an extension, so the game runs the same with the editor and without it.

Where it goes

It isHome
How the editor runs: shell, files, undo, design systemeditor/
Time: stepping, going back, replayThe recorder
What a plugin or addon ownsplugins/<p>/<p>.extension.tsx or addons/<a>/<a>.extension.tsx
One game's kinds, level or statesgames/<game>/<game>.extension.tsx, <kind>.extension.tsx, beside one of its plugins, or games/<game>/extensions/<name>/
Rows, kinds, seats or files in generalextensions/<name>/

A plugin's extension is on while a scene of the open game names the plugin. A game's own is on while that game is open. The editor finds extensions by file name; there is nothing to register.

The files

  • <name>.extension.tsx exports a function named <name>, handed the editor. Its header lists what it adds and its commands.
  • <name>.components.tsx holds its React components, built from editor/ui/.
  • <name>.route.ts, only when it needs the disk or a process, adds routes served at /api/x/<name>/<route>.

The work, in order

  1. Find the home above.
  2. Write <name>.extension.tsx with its header. Write each action as one function taking the editor, the edits and world values.
  3. Register the panel, tool and commands with ed.panel, ed.tool and ed.command, each calling that one function. Anything that outlives a call goes to ed.onDrop.
  4. Write every change through the edits: a running row with editRow, spawn or end, a file with editFile or editFiles, one value at a time. A panel's action runs in ed.together, so one action is one undo.
  5. Register any field type it defines with ed.field('<owner>.<type>', Component) and bind it with ed.bind or ed.bindFile.
  6. Write its test beside it and run yarn vitest run <file>, then yarn tsc and yarn lint.
  7. Open it in the editor and look at it in Chrome.

Commands

Every command is also a tool on the editor's MCP endpoint, /api/mcp, named by its id with each . as _. A command that takes arguments has a z.object as its input, with a description on it and on every field, and no .transform. It takes world points, never canvas pixels.

Panels

Build every panel from the design system in editor/ui/: PanelBody, Button, NumberField, PropertyGrid and the rest. An extension writes no CSS of its own; a colour drawn on a canvas is read from the theme. A component names every topic it reads in useEditor(topics). A viewer's preferences stay in the browser, never in a scene file.

Checking it

  • <name>.extension.test.tsx boots a scene it writes under the recorder, runs the command or drives the panel, and checks that only the one value changed in the file, that undo puts the file's bytes back exactly, one undo per action, and no error line in ed.log.
  • A test that attaches the game view is <name>.extension.view.test.tsx, run with yarn test:view <file>.
  • A route has its own <name>.route.test.ts.
  • In yarn editor <game>, edits to the extension reload the page; check the panel in Chrome, make a change, and undo it.