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