90 lines
6.2 KiB
Markdown
90 lines
6.2 KiB
Markdown
# ADR 0027: Stable Prefab Ownership, Overrides, And Variants
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
ADR 0005 established stable asset-registry identity and linked `PrefabInstance` roots, but the
|
|
first structural boundary treated every descendant of an instance as generated source content.
|
|
That prevented an authored scene or prefab from adding local children, nesting another linked
|
|
instance, or expressing a variant without flattening the source prefab.
|
|
|
|
Bevy allocates `Entity` identifiers at runtime, and display names can change. Neither is a durable
|
|
key for an override, especially when multiple nested prefab layers contain the same source
|
|
`ActorId`. Overrides must also produce the same result in a packaged game that does not include the
|
|
editor. Finally, publishing an override to a source file crosses the in-memory undo boundary and
|
|
must not overwrite unrelated external edits.
|
|
|
|
## Decision
|
|
|
|
- Keep `PrefabInstance { asset_id, source_path, overrides_ron }` as the authored, serialized link.
|
|
Its source path remains project-relative and under `assets/`.
|
|
- Tag entities spawned from that link with runtime-only `HydratedPrefabMember { instance_root }`.
|
|
Tagged source members are excluded from scene, recovery, and prefab serialization. The linked
|
|
root, authored local descendants, and nested `PrefabInstance` actors remain editable and
|
|
serializable.
|
|
- Parse `PrefabInstance` components through `SceneDocument` and validate the dependency graph
|
|
recursively. Reject empty asset IDs, unsafe or non-`.scn.ron` paths, missing sources, dependency
|
|
cycles, authored actors without `ActorId`, and duplicate `ActorId` values within one document.
|
|
Editor load, prefab save, project inspection, and level validation use this structured path.
|
|
- Store overrides in the versioned, shared `PrefabOverrides` contract. A `PrefabActorPath` combines
|
|
an `instance_chain` of nested link-anchor `ActorId` values with the target actor's `ActorId`, so
|
|
repeated IDs in different nested documents remain unambiguous. Runtime override application
|
|
lives in `shared` hydration rather than the editor.
|
|
- Property overrides carry a reflected component type, property path, base component value, and
|
|
replacement component value.
|
|
- Component overrides add, replace, or remove a reflected component and retain its base value.
|
|
- Structural overrides remove or reparent a source actor. The schema reserves local-child
|
|
attachment, but the editor does not expose attachment directly to a generated member until its
|
|
reload and ownership lifecycle is safe.
|
|
- Retain the v1 root transform, material, and child-visibility fields for wire compatibility. New
|
|
authoring uses stable property/component/structural operations; legacy child visibility remains
|
|
keyed by `ActorId`, with name keys read only as a compatibility fallback.
|
|
- Resolve a composed result in increasing specificity: source document, each nested link's override
|
|
layer, the containing variant document and its authored local structure, then the placed
|
|
instance's overrides. A containing layer may target a nested source actor through its stable
|
|
`instance_chain` without flattening that source.
|
|
- Distinguish **instance override** from **Apply to source**. Editing a linked member records an
|
|
undoable operation in the containing `PrefabInstance`. Applying a direct target to source patches
|
|
that source document. Applying a nested target transfers the operation into the immediate nested
|
|
link stored by the containing source; it does not skip the composition layer and mutate a leaf
|
|
prefab unexpectedly.
|
|
- Validate the complete graph and write source changes atomically. The source-apply history command
|
|
stores the exact source bytes before and after the write plus the instance state. Undo and redo
|
|
proceed only when the current file exactly matches the expected side of that command; an external
|
|
change blocks the history operation instead of being overwritten.
|
|
- Record a transitive deterministic source-graph revision and the base values needed for three-way
|
|
conflict checks. Health distinguishes loading, ready, untracked, changed-base, semantic/stale
|
|
conflict, broken-source, and malformed-override states. Conflict recovery can rebase while keeping
|
|
overrides, take the current source for conflicting operations, remove legacy stale targets,
|
|
retry a repaired source, or relink to another validated prefab.
|
|
- Make **Unpack Layer** an undoable outer-layer conversion: remove the selected instance link and
|
|
retain its hydrated members as authored local actors, while preserving nested `PrefabInstance`
|
|
links. Make **Convert to Local** the explicit recursive conversion: remove all nested links in the
|
|
selected instance result, assign fresh local `ActorId` values, and store snapshots so redo does not
|
|
depend on the source files.
|
|
- Define a variant as an ordinary prefab document whose selected root is itself a linked
|
|
`PrefabInstance`. Saving the selection serializes that link, its override layer, and authored local
|
|
descendants/nested instances while excluding hydrated source members. No separate variant asset
|
|
type is introduced.
|
|
|
|
## Consequences
|
|
|
|
- Nested prefab graphs and variants preserve their source links instead of duplicating generated
|
|
content into every owner file. The same shared override data is applied by editor and game
|
|
hydration.
|
|
- Renaming a source actor does not break current overrides while its `ActorId` and nested anchor
|
|
chain remain stable. Moving an actor between documents or replacing an anchor is an identity
|
|
change and can surface a recoverable conflict.
|
|
- Instance-local edits and Revert operations remain ordinary editor history. **Apply to source** is an
|
|
explicit asset mutation with stricter exact-file undo/redo preconditions.
|
|
- Generated members may be removed or reparented inside the same prefab layer through structural
|
|
overrides. Cross-layer parenting, generated sibling-order overrides, and direct authored-local
|
|
attachment to generated members remain intentionally blocked until their persistence semantics
|
|
are defined.
|
|
- **Unpack Layer** preserves reusable nested links; **Convert to Local** intentionally severs all
|
|
links and creates a source-independent authored snapshot. The destructive scope is visible in the
|
|
command name rather than inferred.
|