The atelier is an opt-in, authenticated editor for a persisted world draft. Open /editor.html on an editor-enabled server. Select a place and object, edit position/rotation/scale, inspect the unsaved preview, then save. The preview uses the engine's actual compiler, asset catalog and WebGPU renderer. Drag to orbit, Shift-drag to pan and scroll to zoom. Primitives and imported objects support bounding-box picking and selection from the searchable list. Frame selection brings the selected objects into view.
Only edited numeric fields are written; untouched values retain their original precision. Rotation inputs use degrees. Undo/redo retains up to thirty accepted edits per browser history identity on the server and commits compensating revision transactions. Reloading the editor restores those stacks. Export downloads a validated authoring document, including an unsaved transform when present. It does not publish a live release. A conflict preserves local fields so they can be exported or discarded explicitly before reloading.
#Add, duplicate and remove
Add an object offers blocks, columns, spheres and cones, with limestone, garden green, timber and brass finishes. Choose a unique place-local name and whether the object should block movement. The new object appears near the camera target as an unsaved draft; adjust its transform, then save. Cancel or Discard leaves the server unchanged.
Duplicate shape, Duplicate group and Duplicate selection stage a copy of every selected branch with fresh identities and unique names. Nested parent links stay intact. Each selected root receives the same world-space offset, including roots inside rotated or scaled parents; descendants retain their exact local transforms. Gameplay bindings remain on the originals. Adjust the copied selection in the inspector or handles, then save it as one undoable edit.
Delete object, Delete group and Delete selection remove the selected roots and all their descendants from the preview first. Save deletion confirms one persisted change; Discard draft restores the earlier selection. Undo restores exact identities, source ordering and parent links after reload. Interactions and lights remain separate and are not removed automatically; linked-world constraints can reject an edit. Structural transactions are limited to 256 nodes/commands, so an oversized arrangement is rejected before preparation allocates any identities.
Undo/redo covers creation, duplication, transform edits and deletion. It restores the original identity and source-array ordering, including selection across places. Preparation allocates identity once; redo never allocates a replacement. History remains limited to thirty accepted edits per browser history identity; the stacks and request receipts are durable alongside the draft.
#Model library
Choose Model library in the world sidebar to browse the imported assets in the current saved draft. Search by asset name; each entry shows its number of placements across the world. The selected model has a real WebGPU preview with its original materials and rest pose, dimensions in metres, part count and mesh triangle count. Use the turn buttons to inspect another side.
Set a unique object name, an optional size multiplier and whether the model blocks movement. Place in world stages one new object near the centre of the current view, centring its visible X/Z bounds and placing its lowest bound at the view target height (at least ground level). This is an initial placement, not a surface raycast. Move/rotate/scale the draft before saving; Discard draft cancels. Saving, durable undo/redo, shared Apply and publication use the ordinary authoring flow. The model catalog and original materials are retained; instances use a neutral white tint. Animation/gameplay bindings are not added automatically.
Import GLB accepts a local binary glTF file up to 8 MiB. Parsing, embedded image decoding and tangent generation run in a dedicated worker with a 30-second deadline; Cancel import, switching the selection or closing the dialog terminates pending work. The compiled artifact must fit the editor's 6 MiB model budget. Unsupported content reports the importer diagnostic and retains the previous valid selection. Filenames become safe catalog names, with numeric suffixes to avoid replacing an existing asset.
A new import is local until you place it and choose Save changes. The model and its first object are one validated, durable transaction; discard leaves both absent from the saved draft. Undo removes both and redo restores their exact content and identity after reload. Another builder's use or replacement of the model fences the earlier import undo. Browser uploads use the same GLB subset, geometry/texture budgets and original-material pipeline as the authoring tools. Prefab browsing remains open. Filtering to no matches leaves the selected preview available. Preview failures disable placement until a successful retry. Switching or closing releases the preview renderer and fences late asynchronous results. The catalog retains its existing 32-model limit.
#Save an arrangement
Select saved objects or a group, then choose Save arrangement… and name the collection. Download prefab produces an editable .prefab.json file containing the selected branches, nested groups and required model assets. Child transforms stay local; selected roots preserve their world pose. Appearance and collision are included; gameplay bindings stay in the original world. Save or discard outstanding edits before exporting. Cancel and download both leave the saved world unchanged.
The file uses prefab source 2 and works with the prefab compiler/CLI.
#Arrangement library and source updates
Choose Arrangements → Open prefab file, open your .prefab.json, and select a definition to see its actual geometry and materials. Place arrangement stages a fresh copy at the centre of the current view. Adjust it, then Save changes. Discard draft leaves the saved world untouched. The source and stable member bindings save with the first placement and remain available after reload. Each new placement gets fresh identities; imported model names are remapped when necessary to preserve existing assets.
Edits to linked members become explicit overrides. The inspector identifies linked members; those edits survive later source updates. To revise a source, open Arrangements, select the saved definition, and choose Update this source from a file…. The editor previews changes to every linked copy of that source; save to accept or discard to cancel. The preview reports how many members will be added or removed. Existing members retain their identities; new members receive fresh IDs once, before preview. Undo/redo restores the exact source revision, overrides, identities and geometry. Apply to shared world sends the saved geometry to connected travelers.
Files and the saved library are limited to 6 MiB; a document supports 16 sources and 64 instances, with 256 commands per placement/update. Source membership additions and removals are prepared as explicit commands. Removing a member with local overrides, changing an object into a group, removing a placed definition, or deleting a group containing independent children reports a conflict and leaves the saved draft untouched. Locally deleted members stay deleted while their source key exists; removing that source key also removes its obsolete deletion marker, and undo restores both. Individual linked members cannot move to another place or parent outside their linked instance. Concurrent edits within one linked instance use a conservative whole-instance conflict boundary, while independent inline objects and separate instances can still merge. Exporting existing inline objects does not retroactively link them. Source updates do not migrate gameplay bindings; character-art refinement remains separate.
To make a linked copy independent, select any one of its members and choose Detach this copy. This stages detachment of the complete copy, preserving every object, group, identity, transform and material. Save changes accepts it; Discard draft cancels it. The source remains in the library for other copies. Saved undo can reattach the exact source/overrides after reload; another builder's intervening edits prevent stale reattachment. Once detached, ordinary grouping and reparenting are available, and later source revisions do not change that copy. A source with no linked copies can still be updated and saved without selecting an object.
#Direct transform handles
Select an object and choose Move, Rotate or Scale in the viewport toolbar (shortcuts W, E, R when a text field is not focused). Drag a labelled X/Y/Z handle. Move uses world axes; scale uses the object's rotated local axes; rotation rings adjust the same Euler coordinates as the inspector. Imported objects keep all their child meshes together. Handles keep a readable screen size as the camera changes; axes that cannot be dragged reliably from the current view are hidden. Orbit the view or use numeric fields for those axes.
Enable Snap or hold Shift while dragging for 0.25-metre movement, 15-degree rotation and 0.1 scale steps. Scale stays positive. Focus a handle and use the arrow keys for the same fixed increments. Escape, pointer cancellation, focus loss or resizing cancels the current gesture and restores any draft that existed before it. Clicking a handle without dragging does not create an edit.
The object previews immediately while you drag. On release, normal compiler/gameplay validation checks the whole candidate; saving checks it again and rejects invalid changes. A rejected transform keeps the proposed numbers for correction and restores the previous valid scene. Discard draft restores the saved version. Save/undo/redo still use the same revision transactions; undo removes an originally omitted rotation field rather than adding zeroes. Handles never write intermediate positions to the server.
#Transform a selection
Shift-click, Ctrl-click or Command-click objects in the list or viewport to add/remove them from the selection. Plain click selects one object. A selection contains up to 64 objects in one place. Search and pagination keep the selection; Clear selection clears it. Gold outlines include imported-model bounds. Save or discard a draft before changing the selection or place.
With several objects selected, the inspector and handles edit a shared centre: position is its world position, rotation is an Euler rotation about that centre, and scale is a factor relative to the saved selection. Uniform scale starts enabled and changes all three factors together. Move, Rotate and Scale work with pointer drag, snapping and keyboard adjustment. Frame selection includes the spread of the objects. After saving, the centre is recalculated and the rotation/factors reset for the next edit.
A group transform is one transaction and one durable undo entry. It preserves identities, imported child transforms and untouched property precision; undo restores omitted rotation fields and the whole selection after a reload as well. Other builders' independent properties merge, while an overlap on any member rejects the entire transaction. A gameplay-linked member can also reject the whole edit. The proposed numbers stay available for correction, and no partial save occurs. A nonuniform scale that would shear a rotated object is rejected with a Uniform scale suggestion; no approximate geometry is silently saved.
This selection is temporary. Use Group selection to retain an arrangement as a named hierarchy. Duplicate/delete operate on the selected roots and their complete branches; use Ungroup to release a group’s children while keeping those children. Presence advertises the focused object; group transform previews are not yet broadcast as multiple collaborator cages. Box selection is available from the viewport toolbar; see below.
#Saved groups and hierarchy
Select one or more objects, choose Group selection, name the arrangement, and choose Save group. Group creation saves immediately as one undoable edit. It preserves every object's world position and persistent identity. The gold group rows remain in the nested object list after reloading. Groups can contain other groups; selecting a parent and one of its descendants transforms that branch only once.
Select the group row to move, rotate or scale its entire arrangement with the usual inspector and handles, then Save changes. The inspector displays world-space transforms; the source retains local transforms relative to the parent. Uniform scale starts enabled. A scale that would shear a rotated child is rejected as a whole. Selecting a child in the list edits that child. Viewport clicks select the outermost group; Alt-click picks the individual object.
Ungroup saves an edit that removes the selected container and reparents its direct children to its parent, keeping their world poses. To move an object or group between containers, select it, choose Parent group, then Move. World root releases it from its parent. These operations preserve placement and use the same durable undo, conflict checks, reload recovery and operation receipts as other edits.
Groups contain static primitives and imported models, including solid geometry. Routes, patrol actors, hinged objects, ambient motion and interaction-bound objects need local-space gameplay bindings before they can join a group; the editor explains a rejected attempt without changing the saved world. Group containers have no drawable or collider of their own. World format 5 persists the hierarchy; existing drafts upgrade when their first group is saved. Undo can restore the exact original format and omitted properties.
The compiler, editor, runtime parent components and native collision resolve the same hierarchy. Apply saved revision updates connected development players through the existing preparation barrier. A published playtest retains its own hierarchy and world revision. Limits are 256 groups per place, 128 parent levels, 64 selected roots and 256 commands per transaction; a large structural edit that exceeds a limit is rejected atomically. Parent-relative gameplay and speculative group presence previews remain future work.
#Box selection
Choose Select in the viewport toolbar, or press Q while a text field is not focused. Drag a rectangle around the shapes you want. A plain box replaces the selection; Shift adds and Ctrl/Command subtracts. Escape, pointer cancellation, window focus loss or resizing cancels the box without changing the previous selection. Choose Select again to return to orbit controls, or choose Move, Rotate or Scale to edit the selection.
A box selects fully enclosed projected bounds, including shapes hidden behind other shapes; it is a selection through the scene, not an occlusion test. Imported-model bounds include their child meshes. By default a saved group is selected only when the box encloses its visible arrangement. Hold Alt while beginning the box to select individual objects instead. Invisible objects and empty groups remain available in the object list. Near/far camera clipping is applied before projecting bounds.
The same 64-root selection limit applies. An oversized box keeps the old selection and explains the limit; it never selects a partial subset. Selection never writes a document revision. Save or discard a draft before starting a different selection. After selecting, duplicate or delete the arrangement with the same staged, reversible workflow described above.
#Saved undo history
Undo and redo now survive page reloads and server restarts. The server derives inverse commands from the accepted documents and verifies that they restore the original world exactly. Creation, deletion and property edits—including multi-object transforms—share this path. A new accepted edit clears your redo branch. Repeated undo/redo of your own changes remains usable, while a property changed by another history identity or the ordinary API/CLI is permanently fenced in older entries, even if that other edit is later reverted.
A random history identity is retained in this browser's local storage. Tabs on the same origin share that history; a stale tab cannot undo a newer top entry and must reload first. Clearing browser storage loses the link to this identity. Other devices/browser profiles do not automatically share it; creator accounts are still separate future work. If local storage is unavailable, the history panel explains that this tab cannot retain its identity after reload. The identity is an addressing key for trusted editors, not an authentication credential; the editor key remains required.
The bounded store retains up to 30 undo/redo entries and 64 operation receipts per identity, at most 32 identities, within an 8 MiB metadata budget. Old identities/entries may be removed to stay within those limits. An individual edit that cannot fit a reversible record is rejected before saving. Full-world and model-catalog operations continue through the ordinary API/CLI and protect affected undo entries, but are not yet recorded as reversible history actions. The inspector's Saved undo history section shows the available counts.
Saved operations have unique receipt IDs. A lost response is retried with the identical request; the server returns the accepted result without applying it twice. A reload also recovers an accepted save whose response never reached the tab. Saving a draft, its merge metadata, inverse record, stack transition and receipt uses one document-keyed prepare-before-commit protocol: metadata is durable first, then world.json advances. Reopening selects only metadata matching the accepted world hash. A storage failure stops writes until restart. An initialization marker distinguishes missing current undo metadata from first-time migration; corruption or missing initialized files fails closed.
#Editing together
Open the same draft in two authenticated tabs. Move an object in one tab and change its scale in the other, then save both. Independent properties merge automatically; the saved scene includes both changes. Two edits to position conflict even when they change different coordinates. Conflicts keep your local fields available for correction or export. Reload requires discarding unsaved changes first, then restores the saved draft and durable undo history.
Undo and redo preserve other editors' changes. If someone changes a property touched by an older undo entry, the editor refuses that undo after learning about the change. Saving another independent field cannot make that older undo overwrite their work. A quiet notice announces a saved revision from another editor without replacing your scene or fields. With no unsaved changes, choose Load saved version to load it and restore your retained undo history. With unsaved changes, save to merge or discard before loading. The notice also shows interrupted checks and a Check again action; recovery finds the latest saved version even if several saves were missed. The builder panel shows collaborators and their selected objects; see below.
The merge contract covers retention, recovery and API details.
#Builders in the atelier
Enter an optional Your name in the world sidebar. Other active tabs see your display name, place and selected object. A mint edge marks objects selected by someone else; the inspector explains when another builder is editing the same object. You can keep working. Presence is advisory and the existing revision/merge checks still protect saves.
Names are display labels for trusted creators, not verified accounts. Each page gets a fresh session identifier, so duplicated tabs remain separate builders. The name is remembered on this device. Selection and unsaved-edit hints update after a short delay, with a one-second heartbeat. Transform changes are throttled to one request at a time and at most five updates per second. Hiding or leaving the tab sends a departure; abandoned sessions expire after 20 seconds. Rejoining restores presence. A failed or timed-out check clears old hints and retries with backoff; it does not affect your draft.
Presence has a maximum of 32 recent sessions, including short-lived departure records that reject delayed updates. It lives only in server memory and resets on restart. It neither persists names/selections in world files nor reserves objects. Cursor positions and authenticated creator accounts remain future work.
POST /api/authoring/presence uses the same editor key and origin checks, a 4 KiB body limit, monotonically increasing per-page sequence numbers and the existing concurrent request cap. The response contains current builder display labels and selections only. Removed or unknown objects cannot retain editing hints. Existing Docker/Coolify routes and volumes need no change.
#Unsaved transform previews
When another builder adjusts an existing object, a mint dashed outline and their name show its proposed placement in your viewport. The saved object stays in place. The outline is a bounding cage, not replacement geometry; imported models include all child meshes and bind-pose skin bounds. Your local draft and transform controls remain independent.
Pointer drags send throttled proposals before release. Escape restores the prior proposal; invalid numeric values, discard, save, departure and timeout remove or replace the hint. Each proposal carries its exact saved-world checksum. The server and receiver hide proposals based on a different document, so an old draft cannot preview against newer content accidentally. A received preview never updates local fields, physics, undo entries, world files or published rooms.
Transform preview values are finite and bounded; scale is at least 0.001. The overlay is capped by the 32-session presence limit and uses eight corners per preview. Imported model bounds are prepared once per catalog change. New-object creation and deletion are not broadcast as speculative geometry; save those changes for another editor to load. Preview outlines are not collision or gameplay validation; saving still validates the complete candidate.
#Share a playable release
Save or discard all local changes, then choose Playtest in the header. The service preserves the exact saved revision and returns a link you can copy or open. Share that playtest link with another traveler; keep the editor key private. Both players join the same release-specific room, with the usual room passphrase when configured. No rebuild or deployment is required to test a new saved draft on an already-running editor-enabled server.
Publication checks the revision and checksum again. A stale editor must reload before publishing. Publishing the same saved revision again returns the same link. New edits and new playtests leave existing published players pinned to their chosen world. The link loads the complete validated world and its imported models; its physics uses the same snapshot. Local saves, session tokens, shared progress and authority epochs are isolated by release. Opening the ordinary / URL still enters the base game.
A playtest snapshots world content; it does not pin a separate engine executable or change the world under connected players. There are currently at most 32 active invitations, 128 archived releases and four resident playtest rooms per server process, each with the existing eight-player cap. A restart frees resident room slots and preserves their disk state. These are bounded trusted-playtest limits, not a capacity certification. Choose Worlds in the header to find and reopen earlier invitations.
#Browse published worlds
Choose Worlds in the header to browse the workspace's immutable published revisions, newest first. Search by revision number or world name. Each available row shows its places/object counts, marks a match to the loaded saved draft, and offers Copy link and Open. Open launches the existing shared playtest in a new tab; its world, journey and room remain pinned to that release. Clipboard denial exposes a selectable link. No editor key is added to an invitation.
The dialog works while an unsaved draft is open. Closing it keeps the draft, selection and undo history. Publish saved draft is disabled until those edits are saved; when the loaded draft already has an invitation, Share saved revision reuses it. The list displays the 32-active-invitation budget and disables a new publication when those slots are full. Archive an invitation to make room without breaking its link. Refresh retries failed catalog reads. A corrupt, missing-during-read or oversized snapshot appears as unavailable with no open action, while healthy snapshots remain usable.
GET /api/authoring/releases requires the editor key and the existing origin checks. It returns a bounded format-1 catalog with no supplied navigation URLs, validates snapshots one at a time, and coalesces simultaneous scans only while both snapshot names and retention metadata are unchanged. Revision ordering uses exact uint64 values. The read path is independent of the editable document and its merge/undo metadata, so published-world discovery still works if that draft needs repair. Browsing does not create a playtest room, change a snapshot or reset a saved draft. The snapshot directory and current playtest persistence stay in the existing Docker/Coolify volume.
#Archive invitations
Choose Archive on a row to move it from Active invitations to Archived worlds. The immutable snapshot, link, local saves and shared room state stay intact, including for connected players. Bring back moves it into an available active slot. Archiving is reversible and never deletes content; there are 32 active slots and 128 archive slots. Publishing a saved revision that is archived brings its existing invitation back when an active slot is available. Unavailable snapshots can also be archived without discarding their files.
The archive index is stored as a checksummed, atomically replaced authoring/releases/retention.json beside the immutable snapshots in the existing persistent volume. Back up the index, snapshots, authoring state and playtest room directories together. POST /api/authoring/retention accepts {format:1,id,archived} behind the editor key and origin checks. The format-1 catalog optionally includes an archived collection. This does not provide permanent deletion, automatic age-based eviction or cloud archival.
#Restore an older draft
Save or discard local edits, then choose Restore draft… on an available active or archived world. The editor previews the complete historical content, including environment settings, hierarchy, imported models and saved prefab source links. The saved draft stays unchanged until Save changes; Discard draft returns to it. Saving creates a new monotonically increasing revision instead of rewriting history. The original invitation and its connected players remain pinned to their published snapshot. Applying the restored draft to a development room is a separate explicit action.
Undo/redo restores exact world content and source metadata after reload. A restore conflicts with any intervening world edit, and another builder's later edits block its inverse. Network retries reuse the existing durable operation receipt. Restoration uses the same replace-content command as file/CLI/API authoring and must be the only command in its transaction. The existing 8 MiB undo storage budget applies; oversized restoration fails before changing the draft. A corrupt editable document still requires offline repair before editing can resume; the independent published-world browser/download route remains available.
Naming/annotating versions and engine-executable pinning remain separate work. Up to four playtest rooms can be resident in one server process, so browsing or archiving the catalog does not itself consume or free a room slot.
#Local preview
npm run dev:editorThe command starts a separate garden-based workspace at http://localhost:5181/editor.html, with its relay/API on port 8791. It prints a private link containing an editor key. The key is created once in .data/editor-ferry.1/editor-key with owner-only permissions. The browser removes the key from the address bar after reading it and retains it in tab session storage. Do not share the private link with players.
Draft content is stored at .data/editor-ferry.1/authoring/world.json. Existing garden preview and beta files are untouched. On first authorized access the service imports its source world and persists its identities. Subsequent starts use the saved draft; changing the game's procedural source does not overwrite it. Saving the draft does not alter a room. Choose Playtest to create or reopen an explicit published snapshot.
#Docker / Coolify
The Docker build includes editor.html. Set DW_EDITOR_TOKEN to a separately generated 24–256-character secret to enable /api/authoring; leaving it empty disables the endpoint. All Compose configurations forward this optional value. Do not prefix it with VITE_ and do not reuse the room passphrase. Continue to set the correct public HTTPS origin in DW_ALLOWED_ORIGINS.
The existing named /data volume retains /data/authoring/world.json, merge metadata in /data/authoring/merge-state/, durable stacks and receipts in /data/authoring/undo-state/, immutable /data/authoring/releases/<checksum>.json snapshots and /data/playtests/<checksum>/ room checkpoints/session keys. No second volume or extra port is necessary. Playtest HTTP and WebSocket requests use the same origin and existing proxy routes. The existing container single-writer lock covers the draft. Keep one replica per volume. Back up the entire volume, including merge/undo metadata, releases, playtest checkpoints and their session keys. The editor token is a coarse creator credential, not individual accounts, roles or an audit identity system.
#Revision and persistence contract
GET /api/authoring returns an authoring document. POST accepts the existing format-1 world transaction with its expected revision and checksum. Both require Authorization: Bearer <editor-key>; cross-origin browser requests are rejected. Without an explicit merge mode, a stale revision returns HTTP 409. Malformed or invalid content cannot alter the accepted document. Responses are not cached. The HTTP body is capped at 16 MiB and in-flight requests/queued commits are capped at eight.
One process serializes commits. Each candidate passes the existing full document/compiler validation, is written to a temporary file, fsynced, atomically renamed and followed by directory fsync before acknowledgement. A storage failure latches writes off until restart. The ordinary transaction endpoint does not retain idempotency receipts: reload the current document after an ambiguous response. Browser edits use the receipt-backed history endpoint described below. A saved document is separate from room checkpoints and can be exported through the established CLI/compiler path.
POST /api/authoring/history requires the same authorization and origin checks. Format-1 requests use a browser actor UUID and action: read returns one coherent document/history-summary snapshot; record accepts an operation UUID, a normal world transaction and selection view; undo/redo require an operation UUID, expected history version/top-entry ID and document revision/hash. Inverses remain server-owned. The response includes the current document, bounded history summaries, any intervening edit keys and the restored selection. A repeated accepted operation with the same request returns replayed: true; reuse with different content rejects. A stale history version/top returns HTTP 409. No creator key or browser identity is placed in player URLs.
GET /api/authoring/head returns only {revision, sha256} under the same creator authorization and origin checks, with Cache-Control: no-store. Active editor tabs check every five seconds, skip network checks while hidden or committing, and retry failures with a maximum 30-second delay. Focus, reconnect and Check again request an immediate check. Each request has an eight-second timeout. Old responses are discarded after a save, reload, pause or disposal. This is revision discovery; only an explicit load or successful save installs a document.
POST /api/authoring/publish accepts {baseRevision, baseHash} under the same creator authorization and origin checks. It returns {id, revision, url} after file/directory fsync. Publish and draft commits share one serialized queue. Repeating a publication is idempotent by document checksum. Reads verify the stored document checksum and compile it before use; corrupt or missing releases cannot silently fall back to the base game.
GET /api/playtests/<id>/world returns the published document without the editor key (world content is delivered to players). POST /api/playtests/<id>/session enforces the room passphrase and allowed origins, and /room/<id> admits only matching content/session tokens. Share links contain only a release checksum; they do not contain the creator key or room passphrase. Disabling DW_EDITOR_TOKEN disables editor and published-playtest routing; it does not delete persisted releases.
#Acceptance and remaining work
Run npm run test:browser:editor for the editor and handle journeys, and npm run test:browser:releases for shared published playtests.
Tests cover parallel revision conflicts, rejection without disk mutation, exact document/identity restoration after reopening, protected HTTP behavior, disabled-by-default editing and compiled transforms. The real garden preview has its renderer envelope validated independently. Browser journeys check a visible unsaved change, save/undo/redo/export/reload, and two editors with a stale-save conflict that preserves the local draft. Structural acceptance adds create/duplicate/delete previews, exact undo/redo, identity persistence through reload, duplicate-name rejection and discard without server changes. A native simulation check verifies that a newly authored solid blocks the character. Handle acceptance checks pointer move/rotate/scale with snapping, exact save/undo/redo/reload, cancelled gestures preserving earlier drafts, keyboard adjustment and linked-gameplay rejection. Independent geometry and compiler tests verify axis projection, continuous rotation through the angle seam, positive local scaling and imported child-transform parity.
Publication acceptance covers two live players, edited collision, imported models, host handoff, reload, separate releases/saves, corrupted snapshots, protected publication and durable shared state after server restart. Published snapshots stay pinned; the shared development Apply flow is documented in LIVE_REVISIONS.md. Automatic scene refresh, extended history retention, persistent object grouping, marquee selection, plane handles, interaction anchors, prefab editing, route controls, accounts/roles, engine-artifact pinning and release retention/restoration remain open. Selection uses bounds rather than exact triangle picking; imported-model bounds include child meshes and bind-pose skin bounds. Device loss preserves document controls and requires reload for a fresh graphics context. Physical-device graphics acceptance remains part of the overall engine gates.
Published invitations now retain their player and multiplayer engine across updates. After changing engine code locally, restart npm run dev:editor before publishing a new snapshot. Existing invitations continue to open their original engine.
#Begin with a world template
Choose New world… in the left sidebar, then Quiet studio, Lantern house or Garden ferry. The picker renders the actual starting place; use its turn buttons to look around. Name the world and choose Preview in atelier. The preview changes no saved content. Discard draft returns to your current draft; Save changes accepts the new world as the workspace draft. Save or discard any earlier unsaved edit before opening the picker.
Quiet studio is a small enclosed, walkable starting space with warm stone, columns, inlaid lines and a display plinth. Lantern house includes the three-place journey; Garden ferry includes the cooperative moving platform and lantern puzzle. Every object remains editable through the ordinary inspector, hierarchy and asset tools.
The atelier has one editable draft. Publish the current draft first if you want to retain it in Worlds. Starting a new template uses a recorded whole-world replacement with fresh persistent entity identities and a distinct release identity. Saved undo/redo restores either world exactly after reload, subject to the existing 8 MiB history budget. A concurrent builder's accepted changes block a stale replacement. Templates begin without the previous draft's prefab library.
Choose Playtest after saving to share the new world with another browser. It receives its own pinned engine, room and local journey. Existing published invitations keep their worlds. An already running development room remains on its prior world identity; cross-world live migration is not supported. Use the new published invitation to test this world.
The same preparation is available from files:
npm run author -- template studio "Our little studio" new-world.json
npm run author -- check new-world.jsonTemplate names are studio, house and garden. Output files are created exclusively; existing files are never overwritten. To replace an existing file-backed draft, apply a normal replace-content transaction containing the prepared world's source. Identity allocation happens during preparation, so recorded commands replay identically.
#Review a proposed edit
Choose Review proposal… to load or paste a structured transaction. Check proposal compiles and summarizes the changes without saving. Preview in atelier lets you inspect them across places before Save or Discard. Strict/disjoint merge policy and durable undo are preserved. See proposal review and observation tools for the API, CLI and limits.
#Shape the atmosphere
Select a place, then choose Signs & lettering… to add a welcome, directions, or a small discovery. Write one or two short lines, set the center and height, and choose fixed direction or Face each traveler. Preview lettering updates the scene; Save changes retains it and Apply shares it with the development room. Discard draft and saved undo/redo retain their usual behavior. Travelers can pause and choose Read the signs for a readable list. See world lettering and limits.
Select a place, then choose Atmosphere…. Quiet morning, Golden hour and After dark provide editable starting points. Set ambient RGB, mist RGB/density, sun direction and brightness. The color swatches are shortcuts; numeric RGB fields retain exact source values (including ambient values above 1).
Under Floating light, add Motes or Embers, then adjust center, half size, drift, lifetime, color and glow. The budget shows the authored High count; the atelier previews at Balanced density. Save and Apply share these cosmetic effects with travelers. See particle effects and limits.
Choose Preview atmosphere to see the result in the actual atelier viewport. Reopen Atmosphere to refine that draft, Save changes to keep it, or Discard draft to return to the saved version. Cancelling the dialog keeps any existing preview. Saved undo/redo survives a browser reload and protects another builder's subsequent edits. Independent atmosphere properties and object edits can merge; overlapping properties require explicit resolution.
Save alone does not affect travelers. Choose Apply saved revision to update the shared development room, or publish a separate playtest. Each place keeps its own settings; lanterns and other local lights keep theirs. Player position, flags and NPC state survive compatible environment changes.
Source/API/CLI edits use the same transaction command:
{"kind":"set-environment","place":"garden","property":"fogDensity","value":0.024}Supported properties are ambient, fog, fogDensity, sun, and sunIntensity. Source compilation validates finite values: ambient RGB 0–4, fog RGB 0–1, fog density 0–1, sunlight intensity 0–16, and a nonzero sun vector with components strictly between -10,000 and 10,000. This increment edits the existing renderer environment; animated day/night cycles and advanced lighting remain separate work. Local lights have their own authoring panel below.
#Place local lights
Choose a place, then Local lights…. Select a light to edit its position, RGB color and brightness, or Add a light near the centre of your view. A place has eight slots; the Rain Garden already uses all eight, while the lantern house has spare slots. Removing a light frees a slot.
Choose Preview lighting to inspect the actual scene. Reopen the panel to refine the draft, then Save changes or Discard draft. Saved Undo/Redo retains additions, removals, exact colors and shadow selection after reload. The scene geometry is unchanged: these controls place illumination, rather than a visible lantern object.
The shadow selector chooses sunlight or one local light. The current local-light shadow projects downward; it is not an omnidirectional shadow. Removing that source returns to sunlight; removing an earlier light preserves the chosen source's identity. Story-linked lights are labelled: their original flag-triggered color and brightness remain intact when the starting appearance is edited. The atelier shows their starting state; a live traveler who has already triggered the story sees its override.
After saving, Apply saved revision updates connected travelers. The whole light list and shadow source form one atomic edit, so concurrent changes to that place's light list conflict explicitly. Fog, sunlight and unrelated object changes can still merge.
File/API/CLI transactions use set-lighting with place, the complete lights array, and optional shadowLight (zero-based index). Omit shadowLight or use -1 for sunlight. Source validation allows at most eight lights, finite position components strictly between -10,000 and 10,000, RGB components 0–4, and brightness 0–512. Story overrides have the same color/brightness bounds. All source values are checked before publication. Spot-light authoring, additional shadow sources, automatic exposure, expanded lighting budgets and physical-device performance targets remain open work.
#Edit an object's material
Select one object, then choose Material… in the inspector. Limestone, Honeyed timber, Aged brass, Glazed teal and Warm glow provide starting finishes. Edit RGB color, roughness, metalness and glow; primitive shapes also offer smooth, wood-grain, stone-grain and woven detail.
Preview material updates the actual scene without saving. Reopen Material to refine the preview; transform adjustments made between material previews stay in the draft. Save changes commits the combined edit, while Discard draft restores the saved version. Saved Undo/Redo retains exact source values after reload. Save alone does not change players' worlds: use Apply saved revision or publish a playtest afterward.
For imported models, Frame selection centres the actual geometry bounds, including offsets from the model origin. Color is a tint across their parts. Texture maps and model assets remain intact. Blank roughness, metalness and glow fields retain each part's original setting; primitives use engine defaults. Presets add explicit overrides, so clear those fields if only a tint is desired. Procedural surface detail is disabled for imported models. Story-triggered material responses remain unchanged and are identified in the dialog. Glow brightens the object and can bloom; it does not illuminate neighboring geometry without a local light.
Material edits use the existing set command with property: "material". The material is one conflict unit: another builder's transform can merge, while another material edit conflicts. Linked prefab members retain an explicit material override through later source updates. This panel edits one selected object, including a grouped child; it does not edit groups, multiple selections, individual imported submaterials, texture assets or a material/effect graph.
#Edit conversations
Choose a place, then Dialogue… in the left sidebar. Select a conversation such as Speak with Mara or Read the ferry notice. A selected object with an existing dialogue binding also has a Dialogue… inspector button.
Edit the interaction prompt, optional speaker and individual pages. Add, remove or reorder pages with the controls beside the page selector. The reading card lets you check the words in sequence. Switching conversations keeps the words you have entered during that dialog session. Cancel leaves the existing draft untouched.
Choose Preview changes, then reopen Dialogue… to refine the staged conversations. Save changes accepts them; Discard draft returns to the saved version. Saved Undo/Redo restores the exact original optional speaker and page fields, including after reload. Other builders can edit different conversations independently; conflicting changes to the same conversation retain your local preview and ask you to reload the saved version.
To test with a friend, start or join the shared development room, edit Mara's first page, Save, then Apply saved revision. A traveler already speaking with Mara finishes their existing pages. Their next conversation uses the new prompt, speaker and text. The NPC's conversation owner, patrol phase and player progress persist through the update. Published invitations retain their original content and engine; publish a new snapshot to share new words there.
Existing story conversations, including standalone notices, keep their prerequisites, flags, reach, positions, NPC routes and gameplay effects. New object-attached conversations use the separate binding controls described below. The editor requires a nonempty page for each changed conversation; source compatibility retains optional/empty page lists on non-NPC dialogues. Bounds are 64 pages, 4,096 characters per page and 256 characters for the prompt/speaker. Blank speaker uses the character name, falling back to the interaction prompt. General behavior scripting and branching dialogue remain roadmap work.
The API/CLI command is {kind:"set-dialogue", place, interaction, value:{label, speaker?, text?}}. value replaces only those presentation fields; omitting an optional field removes its override. Full compilation and existing source budgets apply. See acceptance evidence.
#Give an object dialogue
Start with New world… → Quiet studio, or choose an ordinary object in your current place. Save the object first, then choose Add dialogue… in its inspector. Write the prompt, speaker and pages using the reading preview. Set Reach in world metres; Local X/Y/Z locate the interaction point relative to the object’s origin. Imported models may have geometry away from their origin, so adjust the offset to the intended point.
Preview changes → Save changes → Apply saved revision makes the conversation available to connected travelers. The point follows the object’s translation, rotation, scale and parent groups. Reach stays in world metres. Moving a group therefore moves its signs’ interaction points too. The player’s nearest prompt and multiplayer authority resolve the same transformed point.
Reopen Dialogue… to edit the point/reach or choose Preview removal. Save detaches that conversation. Deleting the object or its containing group stages removal of the attached dialogues too. Saved Undo restores exact objects, group hierarchy, dialogue IDs, pages and source order after reload. Duplicating shapes or exporting an arrangement copies appearance/collision; original gameplay bindings stay with their source objects.
These are stateless conversations on static objects. Existing story bindings cannot be replaced; moving platforms, patrol actors, hinged/floating objects and carried-item bindings are excluded. Detach a source-linked arrangement copy before adding a dialogue to one of its objects. There are at most 256 object-attached dialogues per world. They do not set flags or grant items; general scripted behaviors and branching conversations remain separate work.
The command is {kind:"set-object-dialogue", entity, value:{id,label,text,speaker?,position,radius}}; value:null removes the binding. Prepare the dialogue.<UUID-v4> identity once and retain it on retries. position is a local offset; accepted source stores anchor:entity. Attachments upgrade source to world format 6 / interaction-world.6, so older clients reject the new semantics explicitly. Existing formats keep their compiler profiles. Undo can restore the prior format when undoing the original attachment. A raw object delete must include a binding-removal command; final compilation rejects orphan anchors.
Run npm run test:browser:dialogue-bindings for the blank-studio and two-player journeys. This uses its own isolated server because the journey replaces the entire draft and starts a development room with the new world identity. Acceptance evidence.