packages/authoring and npm run author provide a file-based authoring path into the real compiler and Simulation. Import once, keep the resulting document, and edit that persisted document. Re-running a procedural builder is not a substitute for retaining authoring identities.
#Formats and identity
| Format | Contract |
|---|---|
| World 1 | Legacy primitive source; the compiler derives IDs from world/place/source names. Existing game fingerprints remain unchanged. |
| World 2 | Every object carries entityId, globally unique within the world. Source names remain place-local. |
| World 3 | Persistent identities plus an embedded imported-model catalog; see authored models. |
| World 4 | Persistent identities, imported models and bounded authored platform routes. |
| World 5 | Local transforms, persistent group containers and same-place parent relationships. |
| Authoring document 1 | Owned world-2 through world-5 content, exact decimal uint64 revision and SHA-256 of the canonical unsigned document. |
| Primitive compiler 2 | Accepts worlds 1 and 2, emits immutable identity and asset catalogs, and never allocates IDs during compilation. |
| Asset compiler 3 | Accepts world 3 and adds validated model assets and static triangle collision. |
| Route compiler 4 | Accepts world 4 and adds immutable route definitions and canonical platform rows. |
| Hierarchy compiler 5 | Accepts world 5 and emits parent-first group/component rows plus resolved native collision. |
| Simulation checkpoint 7 | Binds the matching compiler signature and retains route phase and full canonical/native state. Explicit migrations accept profiles 1–6 on their original content. |
createWorldDocument(world) compiles legacy source and materializes its existing authored:<SHA-256> IDs into world 2. Import preserves every entity ID and material catalog while deliberately changing the content fingerprint to include the new format and persisted identity fields. Importing existing world-2 through world-5 content preserves its IDs. The returned document and all nested values are frozen and independent of caller input.
New objects use prepareAuthoredObject(source) at trusted authoring ingress. It copies JSON data, rejects identity overrides and allocates authored:<canonical UUIDv4> using platform cryptographic entropy. Retain the prepared object in the command record for retries and replay. Missing entropy fails; compilation and replay never draw randomness. The transaction compiler performs full content validation, so preparation alone does not certify valid geometry.
Renaming an object's source ID, moving it between source places, changing its properties or reordering arrays preserves its persisted entityId. A move retains local transform values unless the same transaction changes them. These are authoring coordinates, not a physical portal crossing. Copying/forking worlds and cross-world identity ownership still need explicit policies; this API does not automatically remint copied identities.
#Atomic document transactions
applyWorldTransaction(document, transaction) verifies the document checksum and compiled content, checks the expected revision and document hash, and edits a private candidate. It validates the final complete world before returning a new frozen document. Failure leaves both inputs unchanged. Non-no-op commits increment the revision exactly; a no-op retains the previous document/hash. uint64 exhaustion rejects without wrapping.
Transactions have format: 1, baseRevision, baseHash and 1–256 commands:
| Command | Data |
|---|---|
rename |
entity, new place-local source name |
move |
entity, destination place, optional new source name |
set |
entity, property and value; properties are position, rotation, scale, material, visible, model, platform and parent; null removes optional rotation, platform or parent |
create |
place, a prepared object containing its persistent identity, and optional zero-based index |
delete |
entity |
world-format |
Target format 2–5; final validation must permit it, and a nonempty model catalog cannot be discarded |
create-group |
place, prepared group with persistent identity and optional array index |
set-group |
entity, property (position, rotation, scale, parent or id), value; null removes rotation or parent |
delete-group |
entity; remaining references reject the transaction |
put-model |
Catalog asset name and compiled model artifact; promotes older worlds to format 3 and retains newer formats |
remove-model |
Catalog asset name; remaining references reject the transaction |
Object commands select by persistent identity; catalog commands select by asset alias. Unknown entities/places, identity edits through set, duplicate IDs, invalid transforms/materials, unsupported fields and final name collisions reject the entire batch. Final-state validation permits an atomic source-name swap. A transaction cannot delete and recreate a reserved identity in the same batch. An optional insertion index must be an integer within the current destination array; omission appends. It lets deletion undo restore exact source ordering as well as identity. There is no durable deleted-ID ledger. The protected browser history endpoint retains object/group inverses and operation receipts; see saved undo.
Strict transactions against the same base conflict after the first changes the document. Opt-in disjoint merging allows independent changes through a complete retained lineage. Recorded commands replay identically against the same base; resubmitting an old transaction to the resulting revision reports a conflict rather than acknowledging a durable duplicate.
Document hashing and input files are bounded to 16 MiB. The compiler also enforces its entity, per-place object, material and conditional-update budgets. Checksums establish integrity, not authorization. The opt-in browser atelier now exposes these commands behind a separate editor credential and origin checks; editing is disabled by default.
#CLI workflow
Use a new output path for each step. Existing files are never overwritten; validation/conflict failures publish no output file. Parent directories must already exist.
mkdir -p authoring-output
npm run author -- import darkwind authoring-output/initial.json
npm run author -- check authoring-output/initial.json
npm run author -- compile authoring-output/initial.json authoring-output/initial-manifest.jsonimport also accepts engine-zoo or a JSON world file. To edit, write a transaction JSON using the document's revision and sha256 as baseRevision and baseHash. Copy the target entityId from that document; source names are descriptive and may change. Then run:
npm run author -- apply authoring-output/initial.json authoring-output/edit.json authoring-output/edited.json
npm run author -- export authoring-output/edited.json authoring-output/world.json
npm run author -- compile authoring-output/edited.json authoring-output/manifest.jsonFor a create command, npm run author -- prepare object.json prepared-object.json assigns the new persistent ID once. Put that prepared object into the transaction. Keep these documents/commands in source control or another durable store; the CLI does not provide a database, automatic backups or command history.
Exported world JSON can be passed directly to Simulation.create(world). The current Darkwind player still loads its game package. This workflow does not modify a running room, deploy a release or replace live physics. Preserving IDs is necessary but insufficient for live content activation: keeping unrelated gameplay state, collider changes, reference rewriting, undo/merge, publication/rollback and activation barriers remain required by milestones 7–9. Old full checkpoints remain bound to their original content hash; importing a world into the new authoring format does not silently rewrite those checkpoints to another release.
#Verification
tests/authoring.test.ts covers import identity preservation, immutable documents, rename/move/reordering, deterministic prepared-command replay, revision conflicts, atomic rejection/name swaps, malformed identities, revision exhaustion, CLI file preservation and real Simulation checkpoint restoration. A raw profile-3 checkpoint captured from d785573 retains a modified authored row, a spawned entity and exact time above 2^53; its migration preserves every previous payload field except the explicitly upgraded compiler signature.
The authored-world.1 golden fixture records an imported two-place world, renamed/moved wall, newly created prop, document revision and full native runtime checkpoint. Engine Observatory runs the same fixture in its real contract worker; browser acceptance compares the complete report with a fresh Node execution. Its digest is 0ef8e158576cdb84adb598e5229029f9529c20cdbd0678127c333f221619db69. This establishes the named authoring path, not a complete editor, asset pipeline or engine milestone.
Clean-source acceptance for 382ea48 passes 136 headless checks and 23 browser scenarios. The full Darkwind CLI run imports all 1,480 authored objects without changing their IDs, renames/moves a decorative object and exactly restores the exported runtime’s native checkpoint. This independent runtime does not alter the active beta.
The prefab compiler adds build-prefabs and expand-prefabs for reusable primitive source. Expanded worlds import into the document API with their persistent IDs intact; baking does not retain an editable link back to the prefab source.
Prefab file commands now use the isolated compiler process. npm run build:author packages both the CLI and its worker for Node-only execution, including in the production Docker image.
The current authored-world.2 fixture names simulation profile 5 explicitly. Its authoring content and identities are checked against the retained version-1 fixture; only the fixture metadata and native checkpoint identity change. Its digest is 3f95b637c8c49247c2291dab0bf95dcbfbadf66eb584ac425507823e4f341873.
#Platform route source
World format 4 accepts authored box routes described in the physics contract. A create command may include platform, and a set command may replace that whole property. These commands promote the source to format 4 while retaining existing persistent identities. set with property: "platform" and value: null removes the route; the source box remains with its authored initial pose and collider. Invalid speed, waypoint, binding or per-place budgets reject the entire transaction. Moving a route's initial pose requires updating the first waypoint in the same transaction. Compilation is supported; live activation into an already-running world and prefab route expansion remain future work.
#Reversible object preparation
packages/authoring/objects.ts prepares recorded create/delete command pairs. prepareInsertion assigns a new persistent identity once and retains it through undo/redo. prepareRemoval captures the complete source object and original array index. prepareAppearanceCopy assigns a fresh identity and place-local name while copying geometry, material, visibility and collision; it deliberately excludes interaction, route, patrol, hinge, pickup, flag and ambient-motion bindings. Full compiler validation still applies when any pair is executed. These helpers do not publish content or bypass the revision precondition.
#Shared browser playtests
The browser atelier can publish an accepted document as an immutable release. Publication uses the same revision/hash precondition and serialized writer as draft commits. Release-specific HTTP/WebSocket routing loads that exact world into the real player and a separate persistent multiplayer room. Editing the draft afterward leaves published sessions pinned. This is separate-room activation; changing an occupied development room, engine executable pinning and release-history browsing remain open.
#Persistent hierarchy preparation
prepareGroup(document, place, entities, name) allocates one group identity and returns reusable commands that upgrade to world 5 when necessary, insert the group and reparent the selected roots without moving them. prepareReparent and prepareUngroup preserve world matrices; prepareWorldTransforms converts world-space edits back to local source TRS. A selected descendant of another selected node is omitted from the root operation. Preparation does not commit; execute the returned command batch through the document/store/history API.
Groups live in optional nonempty place.groups arrays, share the object's place-local source-name and world-global persistent-ID namespaces, and accept only group parents in the same place. Final validation checks cycles, missing parents, depth and count budgets. The compiler rejects unrepresentable child shear. Gameplay-driven local-space behavior is not yet defined, so those bindings cannot be parented. Group transforms and grouped static object poses in a running Simulation change through compiled revisions, preserving native/render consistency.
Durable undo restores group array order, exact object properties, omitted parent/rotation fields and an initial format upgrade. Group/container operations carry structural and parent-frame dependencies into conflict detection. Read dependencies conflict with writes but not with other reads, preserving independent property edits. Fresh ECS installation orders parents before children across its bounded transaction batches.
#Structural selection preparation
prepareSelectionCopy(document, place, roots, worldOffset) snapshots the selected subtrees, allocates a fresh persistent identity once per copied group/object, remaps internal parent references and reserves unique place-local names across groups and objects. Root translations convert the shared world offset through an existing external parent; descendant local poses are preserved exactly. It copies geometry/material/visibility/collision while leaving interaction, route, patrol, hinge, pickup, flag and ambient-motion bindings on the original objects.
prepareSelectionRemoval(document, place, roots) emits deletion commands for the entire selected subtrees. Selecting a parent and its descendant does not duplicate work. Both helpers reject branches over the 256-node transaction budget before allocating IDs. Their StructuralEdit contains place, prepared roots, affected entities and replayable commands. The browser validates a private candidate before staging it; save submits the same prepared identities plus subsequent transform edits. Server-derived durable undo restores exact ordering and source properties. Concurrent subtree membership or lifetime changes conflict rather than deleting another builder's work.
#Durable model imports
Durable history accepts put-model and remove-model alongside instance creation/deletion. The server derives one catalog inverse per affected asset from the accepted source document, restores prior artifacts exactly, and restores the earlier world format when an import promoted it. A new catalog entry and its first instance commit as one transaction; undo removes both. Existing transaction, document and 8 MiB undo-state budgets still apply.
Creating/deleting model instances and changing their model binding records model-read dependencies. These dependencies conflict with catalog writes in either direction while independent readers coexist. A collaborator’s later use or replacement of an imported model permanently fences the earlier import inverse, including use followed by deletion. This protects both active instances and retained undo history. Existing saved dependency keys remain readable.
#Whole-world restoration
A transaction containing exactly one {kind:"replace-content",world,prefabs?} command replaces saved content while retaining the current document's revision sequence. world is a complete persisted world (formats 2–5); prefabs, when present, contains its validated source links. Omitting it removes the previous library. Use npm run author -- apply <document.json> <transaction.json> <new-document.json> for file authoring; the browser's published-world restore uses the same command through durable history. Existing output files are never overwritten by the CLI.
The full candidate compiles before acceptance. Equal content is a no-op. A changed candidate advances the current revision, never adopts the old release's revision/hash, and leaves its input snapshot unchanged. Whole-world conflict keys fence stale restoration and protect other builders from an inverse that would overwrite their work. Browser/API durable undo captures the previous full content subject to the normal history byte budget.
#World templates
instantiateWorldTemplate(source, name, uuid?) validates and owns a world source, assigns a new release identity, and remaps every authored object/group identity and parent reference. It retains source-local gameplay links, model catalogs, places and lighting. It returns a validated document; it does not write or change a live room. Record its world in a replace-content transaction to replace a draft through the usual conflict and undo path.
npm run author -- template <studio|house|garden|meadow|pond|river> <world-name> <new-document.json> exposes the same preparation in the source and production authoring CLI. See world templates in the atelier for the complete preview/save/playtest workflow and bounds.
#Observations and validate-only proposals
The proposal review contract adds author schema, author observe and author validate, plus protected HTTP equivalents. Validation returns a compiled candidate hash and bounded change report without committing. The atelier can review a file proposal and stage it through its existing Save/Discard/Undo path.
#Creator task notebook
Creator tasks retain a request, source revision, proposal,
review notes and accepted revision across reload and authoring-process restart.
Use Creator tasks… in the atelier or author tasks <editor-url> [request.json].
Acceptance uses the existing proposal, Save and durable undo contracts.