Document vs Engine
DiceyTable is a PlayCanvas application, but the table is not modeled as a PlayCanvas scene graph. It is modeled as a replicated schema document, and the engine's entities are rebuilt from that document.
The corollary, stated once. The schema document is the source of truth.
pc.Entityis a render artefact rebuilt from it. Authored state lives in the document and the engine is driven from it — never the reverse.
This inversion is deliberate. It is also the single most useful thing to know about the editor, because it predicts behavior you would otherwise have to memorize.
What the document is#
The scene document (EditSceneSnapshot) holds everything about a scene that is authored:
entities and their transforms, physics, materials, tags, parents and components; seat zones
and their configuration; the room, its walls, posters and lights; project scripts; snap
points. At play time the same shape flows over the wire as a TableSnapshot.
The engine holds none of that authoritatively. It holds a tree of pc.Entity instances that
was constructed to look like the document, and which will be destroyed and reconstructed
the next time the document is applied.
What follows from it#
Anything written only to the engine is already gone#
The runtime destroys and recreates entities when it applies a snapshot. A change made
directly to a pc.Entity — a position, a color, a component flag — is not in the document,
so the next rebuild does not reproduce it. There is no error and no warning; the change
is not there any more.
That is why the codebase treats "patch the live entity" paths with no write-back to the
document as bugs rather than features. One such path existed and was removed: the scene
document once carried an entityPatches field that dumped the live PlayCanvas graph on
export and was never read back by any load path. Every saved scene carried a payload nothing
could apply. It has been deleted; older saves still load, because unknown keys are stripped.
You may still encounter the type EditEntityPatchIntent if you read the shared schema. It
survives only as a type, is not reachable from any authoring surface, and does nothing.
Ignore it.
Every authored field must round-trip#
Because the document is the source of truth and it is also the replication format, an authored field only really exists once it survives the whole chain: schema → equality comparison → incremental delta → host migration → save → load.
That chain is why the component exposure gates in the scene model exist, and why the runtime re-applies component state on every rebuild rather than only on creation. Re-applying on every rebuild is precisely what makes a component safe to author.
Rebuild safety is a feature, not a cost#
Reconstructing entities from the document on every apply sounds wasteful. What it buys is that the editor, the host and every peer converge on the same visible scene from the same bytes — after a reload, after a host migration, after restoring a version, after importing someone else's mod. There is no hidden engine state to diverge.
guids are unstable; id is not#
A Live Entity is addressed by a PlayCanvas guid. Because entities are destroyed and
recreated, that guid changes. A tree keyed on guid loses its selection, its expansion
state and its scroll position on every rebuild.
The document's id does not change. It is the only stable key, and it is the only thing
anything addresses an entity by — see IDs, names and
tags.
Two hierarchies — bridge, do not unify#
There are two trees, they describe different things, and both are needed.
| Hierarchy panel | Engine graph | |
|---|---|---|
| What it shows | An authoring view of the scene document | The real pc.Entity tree |
| Keyed by | id (stable) |
guid (unstable) |
| Contains | Room, Walls, Lights, Table, Player Zones, seat groups, Entities (N) |
Every entity, including internal ones and every sub-node of every imported model |
| Purpose | Authoring | Debug and inspection |
The Hierarchy panel surfaces concepts that have no single pc.Entity behind them — Room,
Walls, Lights, Table, Player Zones, seat groups — and hides the ones you must never touch.
The engine graph would be hostile as a primary authoring tree: it is full of internal
entities tagged dt:internal, it explodes an imported model into dozens of rows, and its
keys are unstable.
So the two are bridged, not unified:
- Each Hierarchy node carries the
idof the Scene Entity behind it, and resolves the live guid lazily when you select it. - The Inspector's
ENTITY (ADVANCED)section shows the live truth, read-only: guid, path, the real component types, child Live Entities. - A
Show engine entitiestoggle appends a read-only engine view, withdt:internalrows dimmed.
Nothing in the engine view is addressable from a scene script or a mod. It is there so you can see what the runtime actually built.
What this means for scripts and mods#
The same rule, one level up: a mod's world is the replicated snapshot, not the scene graph.
Mods filter the snapshot through the mod API behind a read-world capability. Scene
scripts address entities through the table script API. Neither surface exposes — and
neither ever will expose — pc.Entity, findByTag, entity.tags, engine guids, or any
other engine handle. For a mod, exposing one would be a sandbox escape.
If you find yourself wanting an engine handle, what you actually want is either an entity
id or a tag. Both are in the document, both replicate, and both are stable.
A checklist for "why did my change disappear?"#
- Did the change go through the editor, or was it made to the running scene some other way? Only the former writes to the document.
- Was the scene reloaded, restored from a version, or did the host change? Each re-applies the document.
- Is the field one the document actually stores? If it is not in the schema, it does not round-trip.
- Were you looking at
ENTITY (ADVANCED)? That section is read-only by design — it reports the engine, it does not author it.
