# 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.