RunOptions.editor:
fromEditor: editor → engine commands.EditorLibrarydrains it at the start of every tick, including paused ticks.toEditor: engine → editor events.
EditorLibrary from @nanoforge-dev/editor-lib, which handles the protocol. The engine itself (@nanoforge-dev/core, @nanoforge-dev/common) knows nothing about the editor.
RunOptions.editor the library does nothing, so a game can always register it. Libraries reach the editor through Context.editor, which is undefined when no editor started the game or EditorLibrary is not registered.
The protocol version is EDITOR_PROTOCOL_VERSION (@nanoforge-dev/editor-lib), currently 1. The command and event payloads are typed by EditorCommandMap and EditorEventMap. Libraries add their own entries by module augmentation.
Handshake
- On its first tick, once every library is initialized, the engine emits
hello:{ protocolVersion, libraries }, wherelibrarieslists the keys of the registered libraries. - The editor answers with the
welcomecommand:{ protocolVersion, features }, wherefeatureslists the optional features it wants (below). EditorLibrarycalls__editorInit(ctx)once on every library that has it (below). Libraries read the answer fromctx.editor.welcome.
welcome, before or after it, are not seen by the listeners added in __editorInit: they are dispatched before the hook runs. The editor sends the commands meant for libraries (mute, ecs-*…) at least one tick after its welcome.
If the two protocol versions differ, the editor tells the user to update the engine or the editor.
Editor wiring in a library: __editorInit
A library that has editor-only wiring implements EditorAwareLibrary. __editorInit is called once, in run order, on the tick the editor’s welcome arrives, before the libraries’ __events of that tick. It is never called when no editor started the game. A later welcome only replaces ctx.editor.welcome.
EditorLibrary calls it with ctx.app.callHook("__editorInit", ctx), which any library can use to define a lifecycle step of its own.
Run control
A command applies on the tick it is drained in. After a
stop, that tick still completes before the libraries are cleared.
The engine reports each change of its run state with the state event: "running", "paused" or "stopped". It sends stopped only after every library’s __clear has run and the viewport has been disposed, so the editor can safely remove the game container or end the process.
Optional features
Each feature is enabled only if the editor asks for it inwelcome.features.
Log forwarding only forwards console calls whose stack contains a frame of the game bundle. Output from before the
welcome (library __init, the start of main) is kept, up to 200 lines, and sent as soon as the editor asks for logs. It is dropped if the editor doesn’t ask for them. A client runs inside the editor page, and this keeps the editor’s own console output out of the game logs.
Logged values belong to the host
The trees invalues (EditorLogValue) are made by the host, not by the engine: the host passes its serializer in RunOptions.editor.serializeLogValue, and the bridge calls it on each argument when the line is logged, so later changes to a logged object don’t show. A host runs the games it wants values of in its own page (the editor runs clients this way), so the function can be passed as is.
- A host that gives no serializer gets text only, even if it asks for values.
- An engine older than this field ignores it, and keeps sending text only.
- Lines logged between the creation of
EditorLibraryand its__initare snapshotted when the channels arrive.
Viewport
A point(x, y) of the game is drawn at (contentLeft + originX + x * scaleX, contentTop + originY + y * scaleY) CSS px of the game’s container. The editor uses it to draw over the running game (selecting and moving entities in its Game screen).
Hosting a game
main must have registered EditorLibrary, or RunOptions.editor is ignored.