# C4 route 7 — pickup / parent / delete: pinned contract (2026-08-04) **Scope:** make Runtime the sole writer of a parented child's canonical cell — port retail `set_parent`'s attach-time `change_cell` half into the Runtime parent commit, add retail's parent-cell-crossing **propagation step** at the one canonical cell-write funnel, demote App's render-tick child rebucket (`EquippedChildRenderController.TickChild`) to presentation-only, give the headless host the parent-realize commit it has never had, adopt retail's pickup ordering, and delete the dead `ClassifyLeaveWorld` classifier entry. Route 7 performs **no placement**: there is no SetPosition, no park, no service window, no leash on this route. Pinned at HEAD **`cff52c44`**, clean tree, branch `claude/acdream-physics-divergence-5aa784`. **Line numbers in this contract are as-of `cff52c44` and WILL go stale; every citation also names the symbol — trust the symbol** (process rule 6). Predecessor documents, binding where they still apply: - [`2026-08-04-retail-parent-cell-propagation.md`](2026-08-04-retail-parent-cell-propagation.md) — **the settling research. Its §10 contract requirements are BINDING and restated in §0 below.** Do not re-derive the retail mechanism; it is read, cited, and offset-verified there. - [`2026-08-04-retail-child-cell-ownership.md`](2026-08-04-retail-child-cell-ownership.md) — the earlier child-cell research: `set_parent` contains no cell write of its own; `unset_parent` performs zero cell work; `leave_world` is where a detaching child is scrubbed. - [`2026-08-04-c4-routes-6-7-scoping.md`](2026-08-04-c4-routes-6-7-scoping.md) §7 — the route scoping. **Its file:line references predate routes 4b-3/5, the OnPosition collapse, and route 6's closure, and are stale throughout; §10 of this contract lists every claim found false or superseded.** Its trap list (T1–T8) survives and is resolved item-by-item below. - [`2026-08-04-c4-route-5-contract.md`](2026-08-04-c4-route-5-contract.md) plus its three dual review rounds — the contract standard, and the recurring defect classes each addressed by name here: an App glue site discarding the Runtime seam's status and advancing presentation on write-nothing outcomes; unrecorded divergences (register rule 1); a pinned obligation left unwired; zero coverage of the presentation layer; negative-only tests. - [`2026-08-04-c4-route-4b-3-contract.md`](2026-08-04-c4-route-4b-3-contract.md) — its 13 "must REMAIN true" invariants; the ones route 7 can even reach are re-asserted in §3. - [`2026-08-04-session-handoff-c4-remaining.md`](2026-08-04-session-handoff-c4-remaining.md) — the six process rules apply verbatim. Rule 1 (the contract causes the defect), rule 4 (assert the layer that broke), and rule 5 (a clean session is not a passed gate) are the load-bearing ones for this route. - [`docs/plans/2026-08-02-placement-cutover.md`](../plans/2026-08-02-placement-cutover.md) — the campaign plan. Its corrected route-7 gap statement ("the child's canonical cell has two writers … the same defect seen from two sides") is exactly this contract's scope; the plan also pins T8 (the `TryCommitParent` `LeaveWorld` omission is retail-REQUIRED). **Sequencing:** routes 4a, 4b-1/2/3, the OnPosition collapse, route 5 (`36255af0`), and route 6's zero-production closure (`1b484937`) are all in. Route 7 is next; route 3 (portal) remains after it. --- ## 0. Facts settled before this contract — BINDING, do not re-derive From `2026-08-04-retail-parent-cell-propagation.md` (all addresses verified against `acclient_2013_pseudo_c.txt`, struct offsets closed by the `acclient.h` walk — no PE byte-decode needed): 1. **Retail re-cells children when the parent crosses a cell, recursively, to unbounded depth.** `CPhysicsObj::SetPositionInternal` @0x00515330 branches on `this->cell == curr_cell` @0x0051536d; the cell-CHANGED branch @0x00515372 calls `change_cell` @0x00513390, which delegates to `leave_cell` @0x0051339f / `enter_cell` @0x005133af. **`change_cell` itself has NO child loop — the recursion lives in the delegates.** `enter_cell` @0x00510ed0 self-recurses over children @0x00510f03 and writes the FULL identity per level: `CObjCell::add_object` @0x00510ee2, `objcell_id` @0x00510f1e, part-array cell id @0x00510f2b, `cell` pointer @0x00510f35, lights @0x00510f3e. `leave_cell` @0x00510f50 mirrors it @0x00510f84. **CORRECTION (post-implementation retail-conformance review, R1 MAJOR): this enumeration silently dropped the guard AROUND all five writes and the recursion itself — `enter_cell`'s entire body is gated on `this->part_array != 0` @0x00510ed8 (the propagation research's own §3 called this "Guard, load-bearing"). A child with a null part array receives NONE of the five writes and its whole subtree is skipped. This is the contract defect the review traced R1 to — process rule 1, "the contract causes the defect" — and it is why an implementer following this list alone ships an unconditional write. See D2's AP-142 clause (d) for why the guard has no reproducible analogue at acdream's canonical layer (its `HasPartArray` field is populated only by the graphical mesh pipeline, never headless) and is therefore recorded, not ported.** 2. **The depth-1 loop @0x0051539c–0x005153d8 is the SAME-CELL fast path, NOT the propagation.** It refreshes only each direct child's `objcell_id` (child `+0x4c` @0x005153bd) and part-array id @0x005153cc, deliberately not the `cell` pointer, and only when the parent did NOT change cell. An implementer who finds this loop first will wrongly conclude "depth-1, id-only" and ship a stranded-child bug. **This contract says so explicitly: the propagation is the `else` @0x00515372, not this loop.** 3. **The clincher:** `update_object` @0x00515d10 early-returns on `parent != 0` @0x00515d40 — a child never runs its own physics tick, so parent propagation is the ONLY mechanism that maintains a child's cell. 4. **The four binding requirements** (research §10): (i) write at attach AND on every parent cell crossing; (ii) the authoritative write belongs on the physics-commit path — Runtime's write must be a PROPAGATION STEP, not a one-shot at `set_parent`; (iii) propagation is recursive — a depth-1 implementation needs an explicit stated assumption plus a register row; (iv) write the full identity — id-only leaves the #184 class half-closed. 5. **Two adjacent traps:** (a) child cross-cell/shadow lists are NOT refreshed per parent tick — `SetPositionInternal` calls the non-recursive `calc_cross_cells` @0x0051551b; the recursive `recalc_cross_cells` @0x00515a30 runs only at attach (`set_parent` @0x00515b15). Do not rebuild child shadow registrations per crossing. (b) On the removal path (`change_cell` with a null target) retail leaves children with `cell == nullptr` but a STALE non-zero `objcell_id` @0x005133c1 — `leave_cell` never touches child ids. §4 D3 resolves how acdream's single-field model maps this. From this contract's own HEAD verification: 6. **acdream DOES use `FullCellId != 0` / `== 0` as a residency/liveness predicate, pervasively** — 45+ sites, including `RuntimeInitialCreateResidenceState:583` (residence admission), `LiveEntityRuntime` `isOrdinaryRoot` (`:915-918`) and its two sibling predicates (`:3213`, `:3323`), `LiveEntityPresentationController:220`, `RuntimeSetPositionState:2985` (the lost predicate), `HeadlessLocalPlayerFrameHost:87`, and route 4b-3's cell-less classification input (`PreMergeCommittedCellId == 0` → the `SetPosition` cell-less arm). The retail stale-id asymmetry (item 5b) therefore MUST NOT be reproduced literally — see D3. 7. **Route 5 and route 6 landed after the scoping**, so the scoping's "route 6 first" ordering and its campaign-plan correction are already satisfied (`1b484937` corrected `docs/plans/2026-08-02-placement-cutover.md:97-116`). 8. **The canonical cell has exactly ONE funnel.** `RuntimeEntityRecord.SetFullCell` (`RuntimeEntityRecord.cs:244-251`) has exactly two callers: `RuntimeEntityDirectory.SetFullCell` (`RuntimeEntityDirectory.cs:340-346`) and `RuntimeEntityRecord.RefreshDerivedState` (`:230-242`), and `RefreshDerivedState` is itself reached only from the record constructor (`:29`, no children can exist yet) and `RuntimeEntityDirectory.RefreshSnapshot` (`:231-238`). Every producer — `CommitRebucket` (`RuntimeEntityObjectLifetime.cs:1863-1894`), `RuntimePhysicsState.CommitCanonicalCell` (`:2138-2160`, fed by the ordinary/remote/projectile simulation commits and the remote `writeCell` binding `:958-960`), `RuntimeSetPositionState`'s four direct writes (`:2743`, `:3660`, `:5001`, `:5222`), the withdrawal family (`SetFullCell(canonical, 0u, 0u)` at `:1301`, `:1464`, `:1921`, `:2660`), and the wire merge (`RefreshSnapshot` → `RefreshDerivedState`) — funnels through the directory. This is what makes D2's single-chokepoint design sound rather than a per-caller mapping (the 4b-3 review's "mapping written against one caller's reachable set" defect class). 9. **The per-parent committed-children list already exists in Runtime.** `ParentAttachmentState.ChildrenAttachedToParent(parentGuid, parentInstanceSequence)` (`ParentAttachmentState.cs:623-633`) returns the exact live CHILDLIST analog (doc comment already cites retail's live CHILDLIST), maintained by `CommitProjection` (`:546-572`) / `RemoveCommittedChild` (`:809-838`). Its two existing consumers are the lost-family deadline arm/cancel (`RuntimeSetPositionState:6024-6036`, `:6046-6060`). It allocates nothing on the read path (returns the stored `List` or `Array.Empty`). 10. **Parented children's snapshots carry no Position.** `InboundPhysicsStateController.ApplyParent` (`:1347-1373`) sets `Position = null` (top-level AND PhysicsSpawnData); `ApplyAcceptedParent` / `ApplyAcceptedCreateParent` are timestamp-only. So the wire merge's `RefreshDerivedState` cell stamp (`Snapshot.Position is { } position`, `RuntimeEntityRecord.cs:232`) cannot fire for a committed child and cannot fight the propagation. P1 pins this with a test. --- ## 1. Site inventory — re-located at `cff52c44` Every site verified by reading at HEAD, not inherited from the scoping. ### 1.1 The Runtime commit family (`src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs`) | symbol | at HEAD (was, in scoping) | route-7 relevance | |---|---|---| | `TryApplyPickup` | `:1249-1312` (was `:1226-1245`) | pickup: gate → `RefreshSnapshot` → `ForgetInitialCreateResidence` `:1293` → `AdvancePositionAuthority` `:1294` → `CollisionReports.LeaveWorld` `:1295` → `SetPosition.Forget` `:1297` → `SuspendObjectClock` `:1300` → `SetFullCell(0,0)` `:1301` → `ParentAttachments.EndChildProjection` `:1302` → publish `Withdrawn`. **T7's inversion lives at `:1295-1302`** (leave-world work before the unparent). Dormant-residence deferral `:1255-1281`. | | `TryApplyParent` | `:1336-1386` | accepts/stages the standalone ParentEvent; dormant-residence deferral `:1342-1379`; live path `Entities.TryApplyParent` → `CommitPositionChannelUpdate`. Untouched by this slice. | | `TryApplyCreateParent` | `:1314-1334` | envelope flavor; untouched. | | `TryCommitParent` | `:1388-1441` (was `:1360-1374`) | the parent-relation commit (retail `add_child`-success analog). Carries the C0-4(a) cancellation chokepoint `:1426-1431` and the **F4 deliberate `LeaveWorld` omission comment `:1418-1425`** (T8 — do not "fix"). `AdvanceParentCommit` `:1432`. **D1 does NOT add the re-cell here** — see D1 for why it lives on the cell-less commit's successor instead. | | `CommitAcceptedParentCellless` | `:1443-1474` (was `:1377-1408`) | retail `set_parent`'s `leave_world` edge: cancellations → `CollisionReports.LeaveWorld` `:1462` → `SuspendObjectClock` `:1463` → `SetFullCell(0,0)` `:1464` → publish `Withdrawn`. **D1's extension point: the missing `parent->cell != 0` → `change_cell` half goes immediately after this edge.** | | `TryApplyPosition`'s unparent edge | `EndChildProjection` at `:1838`, after `RefreshSnapshot` `:1830` | the Position-unparent (retail `HandleReceivedPosition`'s `unset_parent` @0x00454129). **Same inversion shape as T7 but on route 4's surface — recorded in §9 as a non-goal, NOT touched here.** Also carries 4b-3's `PreMergeCommittedCellId` measurement `:1801-1814` — see §11 for the cross-contract interaction. | | `CommitRebucket` | `:1863-1894` | the App rebucket's canonical write; publishes `Rebucketed` on an actual cell change. After D4, no equipped-child caller remains. | | `CommitWithdrawal` | `:1896-1929` (was `:1845-1860`) | withdrawal-to-cellless with the C0-4(b) symmetric cancellation; `SetFullCell(0,0)` `:1921`. D2's propagation covers its children automatically. | | `TryAcceptDelete` | `:1973-2034` (was `:1939-1952`) | **`ParentAttachments.DeleteGeneration` runs at `:1996-1998`, BEFORE the active record retires (`RemoveActive` `:2005`), and the delete path performs NO `SetFullCell`** — so D2's chokepoint never fires for a deleted parent's children and D3's explicit delete edge must run before `:1996`. | | `ForgetInitialCreateResidence` / `PreferCancellation` | `:2620-2637` / `:2639-2642` (was `:2552-2569` / `:2571-2574`) | unchanged by this slice. | | `AcknowledgeProjectionAndPublish` | `:2255` on (was `:2187-2213`) | publication discipline: cancellation receipt first, then currency re-check, host ack, publish. Unchanged. | | `CommitChildNoDraw` | `:1931-1950` | retail `set_parent`'s NoDraw inheritance — already ported; untouched. | ### 1.2 The classifier (`src/AcDream.Runtime/Physics/RuntimeAuthoritativePositionRouteClassifier.cs`) | symbol | at HEAD | relevance | |---|---|---| | `RuntimeLeaveWorldCause` | `:31-36` | deleted by D6. | | `RuntimeLeaveWorldRouteRequest` | `:141-145` | deleted by D6. | | `ClassifyLeaveWorld` | `:480-510` (was `:475-505`) | **ZERO production callers at HEAD** (re-verified: repo-wide grep returns the definition, one test at `RuntimeAuthoritativePositionRouteClassifierTests.cs:335-352`, and a comment at `RuntimeInitialCreateContinuationExecutorTests.cs:1672`). Deleted by D6, with the test. | | `ValidCreateAuthority` | `:512-516` (was `:507-512`) | requires `PreviousTeleportSequence == AcceptedTeleportSequence` — the #307 predicate shape. **T3 verified first-hand:** no pickup/parent gate measures a teleport pair (`InboundPhysicsStateController.TryApplyPickup` `:180-186` gates on `TryAcceptPositionChannelEvent` — retail's POSITION stamp @0x0045224B analog; `TryApplyParent` `:263-271` adds only parent-instance currency; `TryCommitParent` `:308-314` re-checks POSITION_TS currency). Wiring the classifier would force a fabricated, vacuously-equal teleport pair. This is D6's second leg. `ValidCreateAuthority` itself SURVIVES (the create route uses it); only the leave-world consumer dies. | ### 1.3 App (`src/AcDream.App`) | symbol | at HEAD | relevance | |---|---|---| | `EquippedChildRenderController.TickChild` | `Rendering/EquippedChildRenderController.cs:373-413`; the rebucket at `:406-408` (was `:405-408`) | the render-tick canonical writer: after pose composition succeeds, `_liveEntities.RebucketLiveEntity(child.ChildGuid, parentCellId)`. **D4's demotion target — the ONLY `RebucketLiveEntity` call in the file (re-verified).** | | `EquippedChildRenderController.PrepareAndTryRealize` | `:841-885` | the graphical realize protocol: `CommitStagedParent` (→ `TryCommitParent`) `:856` → `Relations.CommitProjection` `:857` → `CommitAcceptedParentCellless` `:869-871` → `WithdrawPriorProjection` `:875-881` → `TryRealize`. D1's attach re-cell rides inside the Runtime commit this already calls — the App protocol does not grow a fourth call. | | `EquippedChildRenderController.ValidateParentProjection` | `:887-912` | retail `add_child` validation (Setup `HoldingLocations` via `_dats.Get`) — graphical-only today. D5's headless validation question. | | `EquippedChildRenderController.ResolveRelations` | `:786-795` | drives `Relations.Resolve` with snapshot-lookup callbacks — the resolution shape D5's headless drive reproduces Runtime-side. | | `LiveEntityRuntime.RebucketLiveEntity` | `World/LiveEntityRuntime.cs:801-977` (scoping's range still accurate) | the full legacy branch: spatial bucket + `CommitRebucket` `:904-907` + object-clock edges `:919-946` (whose own comment already states "parented/attached objects take retail update_object's parent early-out and remain suspended") + visibility publication. | | `LiveEntityRuntime.RebucketLiveEntityPresentationOnly` | `:993-1065` (was `:993-1050`) | the C3c presentation-only shape: spatial bucket + visibility, **deliberately no `CommitRebucket` / clock work**, guarded by `BeginPresentationOnlySpatialMutation`. Private; sole caller `TryApplyInitialCreateCompletionPresentation` `:1107`. D4 adds the equipped-child entry point beside it. | | `LiveEntityRuntime` wrappers | `TryApplyPickup` `:2312-2316`; `CommitStagedParent` `:2330-2336`; `CommitAcceptedParentCellless` `:2338-2363` (was `:2280-2312`) | the cell-less wrapper's doc (`:2338-2343`) still says "Commits retail `set_parent`'s cell-less edge" — accurate only for `parent->cell == 0`; D9 corrects it with D1. | | `LiveEntityHydrationController.OnPickup` | `World/LiveEntityHydrationController.cs:460-474` (was `:455-474`) | `TryApplyPickup` then `_relationships.OnChildBecameUnparented` — App-level order unchanged by D7 (D7 reorders INSIDE the Runtime method). | | `LiveEntityDeletionController` | `World/LiveEntityDeletionController.cs` | purely logical (re-verified: no placement/cell API). Untouched. | ### 1.4 Headless (`src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs`) | symbol | at HEAD | relevance | |---|---|---| | `OnParentUpdated` | `:312-316` (was `:313-317`) | calls ONLY `Entities.TryApplyParent` — stages the relation forever. **Neither `TryCommitParent` nor `CommitAcceptedParentCellless` has any headless caller (re-verified: the only production callers are `EquippedChildRenderController.cs:856/:869` and the `LiveEntityRuntime` wrappers).** D5's insertion point. | | `OnPickedUp` / `OnDeleted` | `:176-180` / `:155-174` | thin pass-throughs; correct as-is. | ### 1.5 Confirmed-clean (scoping §7.3, re-verified at HEAD) All six cancellation choke points live and symmetric (`:1141`, `:1293-1299`, `:1426-1431`, `:1456-1461`, `:1913-1919`, `:2007-2017`); ordering correct (`AcknowledgeProjectionAndPublish` publishes the cancellation first); receipts host-visible (`RuntimeSetPositionState.PublishCancellation` → `PublishPlacement`, consumed by both graphical sinks and `HeadlessRuntimePlacementProjectionSink`); pickup/parent during a pending residence defer as dormant continuations; grep for `SnapToCell|CommitRebucket|SuspendObjectClock|SetFullCell` across `src/AcDream.App` + `src/AcDream.Headless` still returns no pickup/parent/delete site outside the inventoried ones. --- ## 2. Retail ground truth — verified for this contract; verify again yourself | claim | anchor | status | |---|---|---| | Pickup = `unset_parent` + `leave_world`, gated ONLY on the POSITION stamp; no placement, no rejection path that skips them once the stamp accepts | `SmartBox::DoPickupEvent` @0x00452240: gate @0x0045224B-0x00452274, stamp write @0x00452278, `unset_parent` @0x0045227F, `leave_world` @0x00452286 | **CORRECTED (retail-conformance review):** the gate span itself is UNVERIFIABLE from this source — Binary Ninja lowered both `DoPickupEvent`'s and `DoParentEvent`'s gate comparisons to a literal always-false expression (`if (-((eax_4 - eax_4)) != 0)`), losing the x87/flag-based wrapped-sequence compare. Only the gate's SHAPE (a wrap-aware sequence compare against `update_times[0]`) and the ORDER of the writes after it are legible — the write order is what D7 relies on and remains ✓. Was previously marked "✓ (scoping §7.2, re-read)", which overstated what the source supports. | | Parent = `set_parent` + `SetPlacementFrame`; `SetParentedState(1)` for a non-player parent gaining its first child | `SmartBox::DoParentEvent` @0x00452290: gate @0x00452296-0x004522C5, @0x004522F4, `set_parent` @0x00452305, `SetPlacementFrame` @0x00452313 | **CORRECTED, same basis as the row above:** the gate span is unverifiable from this source (same BN lowering artifact); the post-gate write order is legible and unaffected. D6's argument does not depend on this citation either — it rests on acdream already enforcing the POSITION_TS gate in `InboundPhysicsStateController` (verified directly), not on retail's gate expression being readable. | | `set_parent` order: `add_child` success → `unset_parent` @0x00515ABA → **one** `leave_world` @0x00515AC1 → `parent =` @0x00515AC6 → `if (parent->cell != 0)` @0x00515AD1 → `change_cell` @0x00515AD6 → `UpdateChild` @0x00515B0E → `recalc_cross_cells` @0x00515B15 → NoDraw inheritance @0x00515B26-38 | `CPhysicsObj::set_parent` @0x00515A90 (4-arg overload @0x00515B50 same shape) | ✓ | | `unset_parent` performs ZERO cell work: `remove_child` → NoDraw restore → `parent = null` → `update_time` → `clear_transient_states` | @0x00513470 (@0x00513484/@0x005134AC/@0x005134BF/@0x005134CE) | ✓ (child-cell-ownership doc §4) | | `leave_world` scrubs the detaching object: `remove_shadows_from_cells` @0x005155DD, recursive `leave_cell(this, 0)` @0x005155E6, zeroes only ITS OWN `objcell_id` @0x005155F4 | `CPhysicsObj::leave_world` @0x005155A0 | ✓ | | The propagation mechanism and its cadence — §0 items 1–3 | @0x00515330 / @0x00513390 / @0x00510ed0 / @0x00510f50 / @0x00515d40 | ✓ (settling research; binding) | | Delete order: `exit_world` @0x0050846B + `leave_world` @0x00508472 run BEFORE `unparent_children` @0x005084B9 — children's cells are nulled by the recursion while still attached, then unparented with no cell restore | `CObjectMaint::DeleteObject` @0x00508460 (same pattern in `DestroyObjects` @0x00508C30) | ✓ | | Cross-cell/shadow: `recalc_cross_cells` @0x00515A30 recurses children @0x00515A79 but runs only at attach; the per-move tail calls the non-recursive forms only @0x0051551B/@0x0051553E-4C | settling research §8 | ✓ (binding trap 5a) | | Route 7 never reaches `HandleReceivedPosition` @0x00453FD0 — `DoPickupEvent`/`DoParentEvent` are separate wire handlers; the single remote `ConstrainTo` arm @0x00454272 is unreachable from this route | scoping T1, re-affirmed | ✓ — the basis of D8 | --- ## 3. What must REMAIN true (process rule 1 — for every path, including every refusal) 1. **A committed child's canonical `FullCellId` equals its parent's at every stable observation point** — after attach (parent celled), after every parent cell crossing (any writer: simulation commit, rebucket, canonical placement, wire merge), after parent teleport, in BOTH hosts. This is the route's headline invariant and the headless gate's assertion (it fails today). 2. **The child never becomes a self-simulating object.** Its `ObjectClock` stays suspended, it is never a spatial root, it joins no physics workset, and the propagation path never changes any of that (retail `update_object`'s `parent != 0` early-out @0x00515D40; the existing comment at `LiveEntityRuntime.cs:919-922` already states this rule for the App side). 3. **No placement machinery engages on this route.** No `RuntimeSetPositionState` operation, no park, no `DeferredCell`, no service-window pre-flight, no ledger entry — the child cell write is retail `change_cell`: a direct identity write, not a placement (T6). The `ParkCollisionResidents` overlap throw stays unreachable and `RemotePlacementDrivePendingCount` is unaffected by any number of attach/crossing/withdraw events (4b-3 invariants 9/10 extended). 4. **`ConstrainTo` is NEVER armed by route 7** — not at attach, not at pickup, not at delete, not on any child, regardless of what routes 2/4a/4b/5 established for their arms (T1; D8's partition). 5. **The six cancellation choke points and their ordering are unchanged**: exactly-once `ForgetInitialCreateResidence` → `SetPosition.Forget` → `PreferCancellation`, receipt published before the entity delta. 6. **`TryCommitParent` keeps exactly zero `CollisionReports.LeaveWorld` calls** (T8; the F4 comment at `:1418-1425` and the campaign plan both pin it — retail `set_parent` has ONE `leave_world` @0x00515AC1, and it is the cell-less commit's edge in acdream's staged protocol). 7. **Presentation still advances, and is asserted** (process rule 4 / #312's layer): the equipped child renders in the hand, follows the parent across cell boundaries with no frame where it is bucket-stranded, disappears cleanly on unwield/pickup, and its collision leaves the world with it. A child must never be invisible-but-solid (#184) or solid-but-invisible. 8. **No per-crossing child shadow/cross-cell rebuild** (§0 trap 5a). The child's broadphase state is established at attach/unparent edges only. 9. **The dormant-residence deferrals are untouched**: pickup/parent arriving during a pending initial residence still enqueue `RuntimeInitialCreateContinuationKind.Pickup`/`Parent` continuations and replay through the executor (`RuntimeInitialCreateContinuationExecutor`'s parent replay `:2110-2126`, which already routes through `RuntimeEntityObjectLifetime.TryCommitParent`). 10. **Route 1/2/4/5 classification inputs and dispositions are byte-identical.** Route 7 deletes `ClassifyLeaveWorld` (zero production callers) and changes NOTHING else in the classifier — `ClassifyCreate`'s `Parented`/`PickedUp` residence handling, `ValidCreateAuthority`'s create-route use, and every accepted-position branch stay untouched. Zero expectation changes in surviving classifier tests is the tripwire. 11. **AP-135's writes, AP-131, #276, and #316 are untouched** (§9). 12. **The lost-family deadline enumeration keeps working**: `ArmLostFamilyDeadlines`/`CancelLostFamilyDeadlines` read `ChildrenAttachedToParent` — D2/D3 change nothing about relation lifetime, only cell values. 13. **Ledger convergence**: teardown, session reset, and generation change with committed children present (attached, mid-crossing, mid-unparent) converge the combined ownership ledger to zero — the J-series suites' shape, driven through the new edges. --- ## 4. Design decisions — pinned, not open for redesign ### D1 — the attach half: Runtime completes retail `set_parent`, on the cell-less commit Retail's attach sequence (§2 row 3) ends with `if (parent->cell != 0) change_cell(this, parent->cell)`. acdream's realize protocol today ends at the `leave_world` edge (`CommitAcceptedParentCellless`) and lets a render tick supply the re-cell. Pinned: - **`CommitAcceptedParentCellless` (or a successor commit it becomes part of) gains retail's second half**: after the cell-less edge's existing writes, if the PARENT's canonical record is active and `parent.FullCellId != 0`, write the child's full canonical cell identity to the parent's exact values (`FullCellId`, `CanonicalLandblockId`) — through the same D2 write path, so attach and crossing are one mechanism, not two. If the parent is cell-less (retail `parent->cell == 0` @0x00515AD1), the child stays cell-less — exactly today's behavior, now by the retail-cited gate instead of by omission. - **Both halves are ONE synchronous Runtime transaction.** No caller may observe the child cell-less between the edge and the re-cell within the same call; no deferred continuation may interleave. The method needs the parent's identity to do this — the natural source is the committed relation (`ParentAttachmentState.TryGetProjection`/`_lastAcceptedByChild` via a lookup, or a parent parameter threaded from the caller, both of which the realize protocol and the executor's parent replay already hold); implementer's choice, pinned constraint: the parent must be resolved by (guid, incarnation) currency, never by guid alone. - **Why not inside `TryCommitParent`?** Retail's cell write follows the `leave_world` (@0x00515AC1 precedes @0x00515AD6). acdream's protocol splits `set_parent` across `TryCommitParent` (relation commit) then `CommitAcceptedParentCellless` (leave-world edge); the re-cell belongs after the second, preserving retail's order. Putting it in `TryCommitParent` would re-cell BEFORE the leave-world edge zeroes it — a self-defeating order. The executor's deferred parent replay and the graphical realize both already call the pair in this order; D5's headless drive calls the same pair. - `UpdateChild` (frame composition) and NoDraw inheritance remain where they are (App pose composition; `CommitChildNoDraw`) — unchanged. - `recalc_cross_cells` @0x00515B15: acdream's analog at attach is the EXISTING behavior (the child's collision reports were force-ended by the cell-less edge; no child broadphase registration exists to rebuild). Pinned: **no new cross-cell/shadow machinery is built at attach**, and P4 requires the implementer to state the child's actual broadphase state at each edge with the retail anchor. ### D2 — the sustaining half: propagation at the one canonical-cell funnel **The load-bearing decision.** Retail's trigger is "every mechanism that changes the parent's cell" — in retail that is one function (`change_cell`); in acdream the analog is the one funnel every canonical cell write already passes through (§0 item 8). Pinned: - **The propagation hook lives at the directory funnel** — inside `RuntimeEntityDirectory.SetFullCell` and the `RefreshSnapshot` → `RefreshDerivedState` derived write (either by routing the latter through the former or by hooking both; implementer's structural choice, pinned outcome: **no canonical cell write can bypass the hook**). Per-committer hooks (≥8 sites) are REJECTED — that is the "mapping written against one caller's reachable set" defect class, and one missed site is a stranded child. - **The step:** when a record's `FullCellId` changes and `ParentAttachments.ChildrenAttachedToParent(record.ServerGuid, record.Incarnation)` is non-empty, write each active committed child's canonical cell to the parent's new exact values, **recursively** (a child's own committed children follow — retail `enter_cell`/`leave_cell` self-recursion, §0 item 1). Depth-1-only is NOT acceptable without an explicit stated assumption plus a register row (§0 item 4.iii) — and since recursion here is a dictionary probe per level, ship the recursion. - **Termination and idempotence:** skip a child whose `FullCellId` already equals the target value. This terminates any wire-induced relation cycle (self-parenting is already rejected at `EquippedChildRenderController.ValidateParentProjection:890-891`, but A→B→A via wire must still terminate), avoids spurious `SpatialAuthorityVersion` churn, and subsumes retail's same-cell depth-1 id refresh (§0 item 2): with one field playing both retail roles, a same-value restamp is unobservable, so the same-cell fast path needs no separate mechanism. **This equivalence is a stated assumption of the single-field model and rides in D9's register row.** - **What the step writes:** the child's canonical `FullCellId` + `CanonicalLandblockId` — acdream's full canonical identity (§0 item 6: the field IS the residency predicate). What it must NOT do: no clock changes, no workset/spatial-root changes, no shadow work, no placement operations, no `CollisionReports` calls, no App callbacks. Field writes plus version bumps only — safe to run re-entrantly inside a `RuntimeSetPositionState`/`RuntimePhysicsState` transaction that is mid-commit on the parent (P5). - **Publication:** per-child lifetime deltas are NOT published from the propagation step, matching the physics-commit precedent (`RuntimePhysicsState.CommitCanonicalCell` publishes no lifetime delta; it fires `CellCommitted`, which is parent-scoped and unchanged). The attach re-cell (D1) rides inside a commit that already publishes; the crossing propagation is silent. **Uniformity note:** today's TickChild path DID publish `Rebucketed` deltas for children via `CommitRebucket` (`:1889-1893`); D4 removes those. P8 requires enumerating `Rebucketed` consumers and confirming none needs a per-child delta — if one does, flip this default and publish uniformly from both D1 and D2, and say so in the commit. - **Allocation:** 0 B on the propagation path (the children list is the stored list; recursion uses the call stack or a pre-sized scratch — Slice I discipline). - **Do not** propagate to `_stagedByChild`/`_recoveryByChild`/unresolved relations — retail's CHILDLIST holds committed children only, and `ChildrenAttachedToParent`'s own doc already pins this ("must not capture staged, unresolved, or future-generation relations"). ### D3 — the withdrawal and delete edges (resolves §0 trap 5b under the single-field model) Retail's removal behavior: `leave_cell` recursion nulls each child's `cell` pointer but leaves a stale non-zero `objcell_id`; the functional state is "not resident anywhere." acdream has ONE field, and that field is the residency/liveness predicate at 45+ sites (§0 item 6). Pinned: - **Withdrawal propagates zero.** A parent's `SetFullCell(0,0)` (pickup `:1301`, `CommitWithdrawal` `:1921`, cell-less parent commit `:1464`, residence re-begin `:2660`) flows through D2's chokepoint like any other value: committed children (and their subtrees) go cell-less. This is the functional mapping of retail's recursive `leave_cell` — the retail stale-id residue is NOT reproduced, because reproducing it would leave a child "resident" per every acdream predicate while retail's own gating field (`cell == nullptr`) says it is not. **The id/pointer collapse and this deliberate non-reproduction are recorded in D9's register row.** - **Delete gets an explicit edge.** `TryAcceptDelete` performs no `SetFullCell`, and `ParentAttachments.DeleteGeneration` (`:1996`) removes the relations before the record retires — so the chokepoint alone leaves a deleted parent's children stranded at a stale non-zero cell, which under acdream's predicates means "still resident" (the #184 shape, until each child's own DeleteObject arrives). Pinned: **before `DeleteGeneration` runs, the delete path applies the children's leave-world edge** — for each active committed child of the exact deleted incarnation (recursively), cell-less via the same D2 write path. Retail order anchor: `DeleteObject`'s `leave_world` @0x00508472 runs before `unparent_children` @0x005084B9, i.e. children are still attached when the recursion nulls their cells. The children's RELATIONS are then torn down by the existing `DeleteGeneration` exactly as today; the children's own records stay alive awaiting their own wire terminal (retail: `unparent_children` does not destroy children either). - `EndGeneration` (`ParentAttachmentState:665-694`, the replacement- generation path) — same stranding shape, same fix, same edge, applied at its Runtime call site (`RuntimeEntityObjectLifetime:1000`). ### D4 — the App demotion: TickChild becomes presentation-only (resolves T5) - `EquippedChildRenderController.TickChild:406-408` stops calling the public `RebucketLiveEntity` and calls a new **internal equipped-child presentation rebucket** on `LiveEntityRuntime` — the `RebucketLiveEntityPresentationOnly` shape (`:993-1065`: spatial bucket move, visibility resolution, presentation refresh, visibility-change publication, `BeginPresentationOnlySpatialMutation` guard), with **deliberately no `CommitRebucket`, no clock edges** — because after D1/D2, Runtime already owns the canonical commit, which is exactly the C3c precondition that method's doc demands for presentation-only use. (The C3c-R1 R2 warning at `:824-832` — "post-residence moves take the full legacy branch" — does not apply: it protects entities whose ONLY cell authority would otherwise be the graphical rebucket; an equipped child's authority is now the D1/D2 Runtime write.) - The entry point is child-scoped (assert the record has a committed parent relation, or is called only from the equipped-child controller) so it can never become a general bypass of the legacy branch. - **T5's regression risk is the acceptance test, not a reason to keep the old writer:** route 4a's R1 showed that dropping the bucket move leaves an entity body-correct but draw-bucket-stale (invisible-but-solid). The demoted call MUST still move the graphical bucket every time the parent's `ParentCellId` changes — TickChild's existing cadence (per recomposition, with `ParentPresentationMatches`/`CaptureParentPresentation` change detection on `LastParentCellId`, `:426-446`) already provides the trigger; only the canonical half is removed. The connected gate's carry-across-landblock step plus the dual-layer tests (§6) enforce it. - All other TickChild effects (pose composition, `ParentCellId` mirror, draw-visibility inheritance, `PublishChildPose`, `ProjectionPoseReady`) are untouched. `WorldEntity.ParentCellId` remains presentation (AP-133's split is not re-litigated). ### D5 — the headless parent-realize drive `RuntimeLiveEntitySessionController.OnParentUpdated` (`:312-316`) grows the realize that headless never had: after `TryApplyParent` accepts/stages, resolve the staged relation (the `ParentAttachmentState.Resolve` + `TryGetStagedProjection` protocol `ResolveRelations` demonstrates — snapshot-known + instance-currency callbacks, all Runtime-readable) and run the SAME commit pair the graphical protocol runs: `TryCommitParent` → `CommitAcceptedParentCellless`-with-D1. Also drive the deferred/recovery retry the graphical controller performs on parent arrival (children waiting for a parent that appears later — `OnSpawned`'s projection path), to the extent the direct host receives those events; state what is deliberately not driven (pose composition, which is presentation and does not exist headless). **The validation gap, pinned rather than discovered later:** retail's `add_child` validates the holding location against the parent's Setup (`CSetup::GetHoldingLocation` @0x0050F896); the graphical host ports this via `ValidateParentProjection`'s DAT read. The headless host reads prepared collision content, which does not expose `Setup.HoldingLocations`. Pinned: **the headless drive commits on gate acceptance + relation resolution alone, skipping the holding-location validation, recorded as a register row in the same commit** (a server-sent invalid location would attach headless where retail/graphical reject — unreachable against a well-behaved ACE, but a divergence and it gets its row; precedent: the content-less host's documented reduced-fidelity registration at `RuntimeLiveEntitySessionController:108-117`). If the reviewer finds `HoldingLocations` cheaply exposable through existing prepared content, that retires the row — but do NOT extend the bake format for it in this slice (stop-and-report if that seems required). ### D6 — `ClassifyLeaveWorld` is DELETED (resolves T2, informed by T3) Delete `ClassifyLeaveWorld` (`:480-510`), `RuntimeLeaveWorldRouteRequest` (`:141-145`), `RuntimeLeaveWorldCause` (`:31-36`), and the one pinning test (`RuntimeAuthoritativePositionRouteClassifierTests:335-352`). Rationale, recorded in the commit: - **Retail has no classification here.** `DoPickupEvent` @0x00452240 and `DoParentEvent` @0x00452290 are separate wire handlers dispatching directly; they never reach `HandleReceivedPosition`. Method-per-cause in `RuntimeEntityObjectLifetime` IS the retail shape — the scoping's worry ("the cause discriminator is implicit in which method the caller picked") describes retail's own dispatch, not a defect. - **The only gate retail has is the POSITION stamp**, and acdream already enforces exactly that, in Runtime, at `InboundPhysicsStateController.TryApplyPickup/TryApplyParent/TryCommitParent` (§1.2). Wiring the classifier would ADD a second gate (`ValidCreateAuthority`'s teleport-pair equality) that no pickup/parent path can honestly populate (T3, verified) — a vacuous-or-wrong predicate with the #307 defect shape, plus T2's rejected-classification silent-pickup-drop hazard, for zero behavioral gain. - This closes the scoping's "wire it or delete it" demand in the direction the evidence points; the scoping's lean ("wire it") predates the T3 verification and is overridden with cause (§10). ### D7 — pickup ordering adopts retail's (resolves T7) `TryApplyPickup` reorders to retail's `unset_parent`-then-`leave_world`: `ParentAttachments.EndChildProjection` moves ahead of `CollisionReports.LeaveWorld` → `SetPosition.Forget` → `SuspendObjectClock` → `SetFullCell(0,0)` (anchors @0x0045227F before @0x00452286). The cancellation sequence, `AdvancePositionAuthority`, and the publication discipline are unchanged. Verified inert against the new machinery: the picked-up entity's own D2 propagation consults ITS children, not its relation to its parent, so the reorder cannot change propagation; no in-between callback exists (`AcknowledgeProjectionAndPublish` runs after both). This retires the recorded inversion instead of carrying the "not proven inert" caveat forward. **The sibling inversion on the Position-unparent edge (`TryApplyPosition:1830/:1838`) is route 4's surface and is NOT touched — recorded in §9.** ### D8 — the inverse-leash partition, and the guards that do NOT come along The route-7 column of the campaign's constraint-arm partition — stated so an implementer arriving from 4b-2/4b-3/5 ("arm on nonzero return, on every placement outcome") cannot carry the rule across: | event | retail path | `ConstrainTo`? | placement? | distance/snap guards? | |---|---|---|---|---| | pickup | `DoPickupEvent` — never reaches `HandleReceivedPosition` | **never** | none | none | | parent (attach) | `DoParentEvent` — same | **never** | none — `change_cell` is an identity write | none | | parent cell crossing (propagation) | `SetPositionInternal` child handling — the PARENT's own route arms whatever ITS route arms; the child arms nothing | **never (for the child)** | none | none | | delete | `DeleteObject` | **never** | none | none | Explicitly NOT imported (T4/T6): AP-87's 4 m `BodySnapThreshold`, the 96 m `MaxPhysicsDistance`, `MoveOrTeleport`'s near/far split, 4b-1's service-window machinery, and any `CanAttemptDestination` pre-flight — pickup/parent/delete have no distance concept and no deferrable Core condition (retail's `change_cell` runs no sweep and no `AdjustPosition`). If an implementer finds a reason a child cell write CAN defer, that is a new finding: stop and report. ### D9 — register and comment bookkeeping, in the implementation commit - **ONE new AP row — the parented-child cell model** (three clauses, all intentional-architecture): (a) acdream collapses retail's `cell`-pointer/`objcell_id` pair into one canonical `FullCellId` that is also the residency predicate; consequently (b) the removal path propagates ZERO to children where retail leaves a stale non-zero `objcell_id` under a null pointer (@0x005133C1 / `leave_cell`'s absent id write — deliberate non-reproduction, D3), and (c) retail's same-cell depth-1 per-tick id refresh (@0x005153BD) is subsumed by the value-idempotent chokepoint (D2) rather than ported as a tick loop. Anchors: @0x00513390, @0x00510ed0, @0x00510f50, @0x0051539c-@0x005153d8, @0x00515d40. - **ONE new AP row — headless holding-location validation skip** (D5), if the reviewer confirms no cheap prepared-content read exists. - **AP-136's writer list shrinks**: "the equipped-child renderer `EquippedChildRenderController.TickChild`" dies as a canonical rebucket writer (register line ~287); the surviving non-Position rebucket writer is the projection materializer alone. Update the row and the two doc comments that carry the same claim: `RuntimeSetPositionState.cs:4543` and `RuntimeRemotePlacementDriveController.cs:1617`. - **Comment corrections** (process rule 6, each verified against the code beside it): `LiveEntityRuntime.CommitAcceptedParentCellless`'s doc (`:2338-2343`) — "commits retail set_parent's cell-less edge" gains the D1 second half; the same class's `RebucketLiveEntityPresentationOnly` doc ("called ONLY from `TryApplyInitialCreateCompletionPresentation`") updates for the D4 entry point; `TickChild`'s surroundings; the `ParentAttachments.EndChildProjection` doc ("after Pickup or a world Position") if D7's reorder makes its phrasing stale; grep `TickChild|CommitAcceptedParentCellless|RebucketLiveEntity` across `src/` + `docs/architecture/` and re-point every survivor. - **No row deletion.** AP-124, AP-131, AP-132 (queued-parent incarnation gating), AP-133, AP-135 all survive untouched. - **ISSUES.md**: none closed by this slice unless the implementer finds the headless child-cell defect has a filed number (none found at HEAD — it is recorded only in the campaign plan's gap list; update that list's wording when this lands). --- ## 5. Proof obligations (must prove, not assume; stated in the implementation commit) - **P1 — no merge fights the propagation.** For a committed child, every snapshot-mutation path (ObjDesc, motion, state, PVP bitfield, parent re-commit) leaves `Snapshot.Position` null (§0 item 10), so `RefreshDerivedState` never stamps a child cell from its own snapshot. One test drives each mutation family against an attached child and asserts the canonical cell still tracks the parent. - **P2 — the child stays parent-suspended.** After attach, after ten crossings, and after a parent teleport: child `ObjectClock` suspended, not a spatial root, in no workset, no `RemoteMotion`, body (if any) inactive. (Invariant 2; retail @0x00515D40.) - **P3 — no placement-ledger engagement.** Attach/crossing/withdraw/delete sequences leave `RemotePlacementDrivePendingCount`, SetPosition operation counts, and park counts at their prior values. - **P4 — the child broadphase story, stated.** What is a child's shadow/broadphase registration at attach, across crossings, at unparent-by-Position, at pickup, at parent delete? The implementer writes the answer down with the retail anchors (`leave_world`'s `remove_shadows_from_cells` @0x005155DD; `recalc_cross_cells` at attach only; §0 trap 5a) and confirms the propagation path performs zero shadow work. If a gap is found (e.g. a child shadow that should exist and does not), it is FILED, not silently fixed in this slice. - **P5 — re-entrancy safety of the chokepoint.** The propagation runs inside whatever transaction wrote the parent's cell (`RuntimeSetPositionState` placement commit, `RuntimePhysicsState` simulation commit, `CommitRebucket`, the wire merge). Because it is field-writes-only (D2), it cannot re-enter those owners. Prove with a focused test per writer family plus the existing reset/reentrancy suites green. - **P6 — zero allocation** on the propagation path (the Slice I discipline): a warmed crossing with N children allocates 0 B. - **P7 — delete-edge ordering.** The children's leave-world edge reads `ChildrenAttachedToParent` BEFORE `DeleteGeneration` removes the relations; a test deletes a parent with an attached (and a grand-attached) child and asserts both went cell-less. - **P8 — publication consumers.** Enumerate `RuntimeEntityChange.Rebucketed` consumers; confirm none requires the per-child deltas TickChild's `CommitRebucket` used to produce, or flip D2's publication default and say so. (This is route 5's A1 lesson applied prospectively: the App/host layer must be shown to tolerate the Runtime seam's chosen silence.) --- ## 6. Test plan Rules (route 5 §7's, verbatim where they apply): assert the layer that historically broke — presentation and canonical cell, not only `InWorld`/clock; assert positive facts, not only negatives; every new test must fail against a broken implementation (no source-text pins). The **dual-HOST discipline** is this route's analog of route 5's dual-kind theories: every Runtime-level scenario runs against the Runtime owners directly (headless-shaped) AND through the graphical wrappers, asserting the same canonical outcome — that is what makes the headless gap a failing test rather than a host-specific accident. Focused Runtime tests (`tests/AcDream.Runtime.Tests`): 1. **Attach, parent celled** (D1): commit pair on a child whose parent has `FullCellId = A` → child ends at A (positive), `Withdrawn`-then-re-celled within one call (no observable cell-less escape), collision reports force-ended, clock suspended, POSITION_TS consumed. Companion: parent cell-less → child stays cell-less (the @0x00515AD1 gate), and a LATER parent cell commit re-cells the child through D2 (the deferred-attach catch-up retail gets for free from propagation). 2. **Crossing propagation per writer family** (D2): parent cell changed via (a) `CommitRebucket`, (b) `RuntimePhysicsState.CommitCanonicalCell` (simulation commit), (c) a canonical placement commit (`RuntimeSetPositionState`), (d) the wire merge (`RefreshSnapshot` with a Position) → child follows in every case; grandchild follows (recursion); a cycle (A→B committed both ways by hostile wire) terminates. 3. **Same-cell idempotence**: a parent commit to its CURRENT cell leaves child `SpatialAuthorityVersion` unchanged (the D2 short-circuit, positive form: the child was already correct). 4. **Withdrawal edges** (D3): pickup of the parent, `CommitWithdrawal` of the parent, and residence re-begin each zero the child (and grandchild); delete of the parent zeroes children BEFORE relations vanish (P7); `EndGeneration` same. 5. **Pickup of the child itself** (D7): relation removed before the leave-world writes (order pinned via the relation table's state at the cell write — e.g. a propagation-visible probe or the committed-children list emptiness at `SetFullCell(0,0)` time), cell zeroed, clock suspended, `Withdrawn` published with the cancellation receipt first — and the entity's own children (if any) went cell-less too. 6. **Never-arm partition** (D8): after attach + five crossings + pickup + delete, no constraint/`PositionManager` state exists for parent or child beyond what the parent's OWN route had already armed; arm counts unchanged by every route-7 event. 7. **No-placement invariant** (P3) and **ledger convergence** (invariant 13): teardown/reset/generation-change with children attached, mid-crossing, and mid-unparent. 8. **Headless parent-realize** (D5): through `RuntimeLiveEntitySessionController.OnParentUpdated` with a live directory: staged → committed → celled at the parent's cell — **the test named by the route-6 scoping as failing today** (child canonical `FullCellId == parent's` at a stable checkpoint), now in-tree and green; plus the deferred flavor (parent arrives after the relation). 9. **Classifier deletion** (D6): surviving classifier tests byte-identical (zero expectation changes — the §3 item 10 tripwire). App-layer tests (`tests/AcDream.App.Tests`): 10. **The demotion keeps presentation whole** (T5/#184's layer): drive the realize + a parent cell change through the graphical stack; assert the child's render entity moved buckets (spatial index / visibility state), `ParentCellId` mirrors the parent, AND the canonical cell was written by Runtime (not by the presentation path — assert `CommitRebucket` was not the writer, e.g. via the presentation-only guard). **Sabotage check (manual, TWO runs — corrected at the architecture review, A1): break D2's propagation and confirm THIS test fails on the CANONICAL half while presentation still moves; separately, stub out the presentation rebucket call (`RebucketEquippedChildPresentation`) and confirm THIS SAME test fails on the PRESENTATION half while the canonical cell is still correct.** The first implementation round ran only the first of these two and shipped an assertion (`child.WorldEntity.ParentCellId`) that TickChild writes unconditionally before the demoted call runs — satisfied whether or not the demoted call executes at all — so the presentation half had zero effective coverage despite the contract asking for it. If EITHER sabotage run leaves the test green, the test is asserting the wrong layer; fix the test. Assert against the actual spatial bucket (e.g. a landblock membership query), not a mirror field TickChild writes elsewhere. 11. **Unwield/pickup teardown**: after pickup, the child's projection is gone, no bucket residue, no shadow residue (the invisible-but-solid regression assert, stated positively: the cell is 0, the projection withdrew, the relation is gone). 12. **P8's publication check** as a test where feasible (a consumer-facing assertion that the graphical host converges without per-child `Rebucketed` deltas). --- ## 7. Gates > **SUPERSEDED 2026-08-05 (#319).** This section's gate criterion ("A session > counts as a pass ONLY if the probe shows the propagation executed") is > UNFALSIFIABLE in the presence of #319's defect — a zero-cell player child > emits NO `[child-cell]` line at all, which this criterion reads as "clean" > rather than "broken." Two captured gate sessions passed this exact > criterion while carrying the defect. The corrected criterion (a positive > equality assertion — the equipped child's `FullCellId` equals the parent's > after a crossing — instantiated for BOTH parent classes) lives in > [`2026-08-05-c4-closeout-handoff.md`](2026-08-05-c4-closeout-handoff.md) > and is run at > [`2026-08-05-issue-319-contract.md`](2026-08-05-issue-319-contract.md) §7. > Do not re-run this section's recipe as written; use the corrected one. - **Focused**: the §6 suites, green. - **Complete Release suite**: `$env:ACDREAM_PAK_PATH = "$env:USERPROFILE\Documents\Asheron's Call\acdream.pak"`, `dotnet test AcDream.slnx -c Release -m:1`. **Baseline 11,063 passed / 4 skipped / 0 failed at `cff52c44`.** The count will move (one classifier test deleted, new suites added) — measure and record the new figure; do not inherit the baseline. Two known flakes, never chase and never conflate: **#302** (`PortalProjectionTests.ClipToRegion_FrameOwnedStore_…`, GC-allocation assertion, App.Tests) and **#308** (`NakEmissionTests.LossSoak_…`, wall-clock deadline, Core.Net.Tests, full-suite load only). If either appears, re-run and say which. - **Connected two-client gate (user-run) — this route IS user-visible.** Probe: `ACDREAM_PROBE_CHILD_CELL=1`, `PhysicsDiagnostics`-owned, marked TEMPORARY with the existing probe family; one `[child-cell]` line per Runtime child-cell write with parent guid, child guid, old→new cell, and cause (`attach` / `propagate` / `withdraw` / `delete` / `headless-attach`). **A session counts as a pass ONLY if the probe shows the propagation executed** (process rule 5; 4b-3's gate precedent — a clean-looking session with zero `cause=propagate` lines during step 2 is a not-run). Recipe (scoping §7.7, carried): 1. Equip/unequip cycle — weapon then shield, five times, observer watching: in the hand, at the hand, oriented with the hand, clean disappearance on unwield. 2. **Carry across ≥2 landblock boundaries and back**, both directions of observation; expect `cause=propagate` lines at each crossing, child cell always equal to the player's. Include one indoor/dungeon traversal (EnvCell-to-EnvCell crossings are the high-frequency case). 3. Pickup: drop the weapon, pick it back up — leaves the ground, no ghost, no invisible collider at the drop site. 4. Loot an equipped item from a kill (the delete edge under load). 5. Reconnect with equipment — re-attaches. 6. Portal recall while equipped — equipment present and following after arrival. Regressions to watch: weapon drawn at the world origin or its last ground position; invisible while equipped; **left behind at a landblock boundary** (the demotion's specific risk); invisible-but-solid at a former position (#184); child culled while the parent is visible or vice versa. Graceful close per the standing ACE session rule. - **Headless gate**: the §6 test 8 assertion (child canonical `FullCellId` equals the parent's at a stable checkpoint) — the direct regression test for the defect, which **fails today** — plus one headless session where the local player equips (via the bot command surface) and crosses a boundary, asserting the same, with `cause=headless-attach`/`propagate` probe lines in the log. --- ## 8. Budget and stop conditions **Size estimate at HEAD** (supersedes the scoping's §7.6 table, whose shape changed twice — the propagation research added D2/D3, and D6 became a deletion): | piece | non-comment production lines | |---|---| | D1 attach re-cell in the cell-less commit | 40-80 | | D2 directory-funnel propagation + recursion/idempotence guards | 50-90 | | D3 delete/EndGeneration explicit edges | 15-35 | | D4 demotion + internal presentation-only child entry point | 40-80 | | D5 headless parent-realize drive | 60-110 | | D6 `ClassifyLeaveWorld` family deletion | net −60 to −70 | | D7 reorder | ~3 | | probe | 15-25 | | **net added** | **~165-355** | Within the scoping's 300-490 envelope (below it, thanks to D6 being a deletion). Tests are the larger share, ~450-750 lines. **Route 7 remains ONE slice and MUST NOT be split** — re-validated at HEAD: the Runtime canonical write (D1/D2) and the App demotion (D4) are two halves of one transfer. Landing D4 without D1/D2 leaves every equipped child cell-less/stranded (the #184 shape); landing D1/D2 without D4 creates a per-frame two-writer race on the canonical cell — the exact defect class this campaign exists to remove. D5 rides along because it is the same Runtime commit with a thin driver, and the headless gate is the route's direct regression test. **Stop and report rather than pushing through when:** 1. Added production lines exceed **550** — the likely cause would be the propagation needing its own publication/receipt machinery (P8 flipping the default into something structural) or the headless resolve needing more of the graphical protocol than the thin drive assumed; either is a decomposition conversation, not an ad-hoc build. 2. Any placement, park, service-window, or `ConstrainTo` machinery starts looking necessary on this route (D8's last paragraph). 3. P8 finds a `Rebucketed` consumer that genuinely needs per-child deltas AND publishing them breaks an ordering invariant. 4. P4 finds a live child broadphase registration that per-crossing propagation would leave stale (that would mean acdream has child shadow state retail does not, and the design changes). 5. D5's validation gap turns out to require extending the prepared-content bake format. 6. Any surviving classifier test changes expectation (§3 item 10). 7. The complete Release suite deviates from baseline beyond the two named flakes. --- ## 9. What this slice does NOT do - **AP-131** (shared merge call / `clearParent` gating) — C5. The Position-unparent edge's ordering inversion (`TryApplyPosition:1830/:1838` vs retail @0x00454129) is the same family: **recorded here, not touched** — it belongs with AP-131's route-4-side correction. - **AP-135, #276, #316** — untouched. - **R6-a** (retail `DeclareValid`'s `SetSelectedObject` split-recovery selection transfer) — out of C4, per route 6's closure; file separately. - **`UnparentBeforeRouting` / `ApplyPlacementFrameBeforeRouting`** stay recorded-not-consumed (4b-3's non-goal, carried). - **No child `PositionManager`/interpolation/`RemoteMotion` machinery**; no child self-simulation of any kind. - **No changes to routes 2/4/5/6**, the local-player paths, the remote tail, or the continuation executor beyond the parent-replay path already calling the extended commit. - **No re-litigation of AP-133** (`ParentCellId`/`EffectCellId` split) — the child's render-parent field remains presentation. - **Route 3 (portal)** — after this slice. --- ## 10. Stale and false scoping claims — reported, not smoothed (§7 of `2026-08-04-c4-routes-6-7-scoping.md`) **Substantively false or superseded (5):** 1. **T5's open question — "whether retail re-cells a child when its parent crosses a cell is NOT established" — is SETTLED (yes, recursively, from the physics commit)** by the propagation research, which the scoping demanded before demotion. Superseded, exactly as the scoping asked. 2. **§7.4's fix shape — "move the parent-cell commit into `TryCommitParent` / `CommitAcceptedParentCellless`" — is INSUFFICIENT as written.** An attach-only commit is correct at attach and stale from the parent's first crossing (research §10 item 2). D1+D2 replace it: attach half PLUS the sustaining propagation. The scoping's own §7.6 budget row inherited the insufficiency. 3. **§7.6's "retail's `change_cell` + `recalc_cross_cells` half" — porting `recalc_cross_cells` per the commit is WRONG**: retail runs the recursive form at attach only; per-move it calls only the non-recursive forms (research §8, binding trap). No cross-cell/shadow rebuild ships. 4. **§7.5's lean ("wire `ClassifyLeaveWorld`") is overridden with cause**: T3's verification (no pickup/parent gate measures a teleport pair; the only retail gate is POSITION_TS, already enforced in `InboundPhysicsStateController`) plus retail's separate-wire-handler dispatch make deletion the evidence-backed choice (D6). 5. **§8's "recommended order: route 6 first, then route 7" and the campaign-plan correction it demanded are MOOT** — both landed (`1b484937`; plan lines `:97-116` corrected). **Stale line references (every RuntimeEntityObjectLifetime citation, plus several others):** `TryApplyPickup :1226-1245` → `:1249-1312`; `TryCommitParent :1360-1374` → `:1388-1441`; `CommitAcceptedParentCellless :1377-1408` → `:1443-1474`; `CommitWithdrawal :1845-1860` → `:1896-1929`; `TryAcceptDelete :1939-1952` → `:1973-2034`; `ForgetInitialCreateResidence :2552-2569` → `:2620-2637`; `AcknowledgeProjectionAndPublish :2187-2213` → `:2255` on; `ClassifyLeaveWorld :475-505` → `:480-510`; `ValidCreateAuthority :507-512` → `:512-516`; TickChild rebucket `:405-408` → `:406-408`; `RebucketLiveEntityPresentationOnly :993-1050` → `:993-1065`; App wrappers `:2280-2312` → `:2312-2363` (doc comment `:2288-2292` → `:2338-2343`); headless `OnParentUpdated :313-317` → `:312-316`; `OnPickup :455-474` → `:460-474`. The scoping's §7.3 verification table (cancellation choke points `:1074-1094` etc.) is wholly re-verified at the new locations in §1.5. Its structural claims all still hold; only the coordinates moved. **Confirmed still true at HEAD:** `ClassifyLeaveWorld` has zero production callers; `RebucketLiveEntity` is not presentation-only (canonical `CommitRebucket` at `:904-907`); headless has no realize; the six cancellation choke points and their C0 fixes; T8's pinned `LeaveWorld` omission; `LiveEntityDeletionController` purely logical; the register's AP-124 status. --- ## 11. Cross-contract finding — route 7 changes route 4b-3's cell-less trigger population (reported honestly) Route 4b-3's connected gate recorded an honest gap: `cause=cellless` was never observed live, and its closure note says "the unwield-to-3D path is the cheapest reachable trigger" (`2026-08-04-c4-route-4b-3-contract.md`, final section). **After route 7 that provocation stops working, and that is the retail-faithful direction:** - Retail: `unset_parent` performs no cell work, so a wielded child's unwield Position reaches `MoveOrTeleport` with `this->cell` = the parent's cell — NON-zero. Retail's cell-less branch does NOT fire for unwield; it fires only for genuinely never-celled/withdrawn bodies. - acdream today: a parented child's canonical cell is whatever the render-tick writer last produced — nonzero in the graphical host while TickChild runs, **zero headless and zero in any pre-first-tick window** — so `PreMergeCommittedCellId == 0` (the 4b-3 D1 input, measured at `TryApplyPosition:1801-1814`) could classify an unwield as cell-less. - After D1/D2: a committed child's pre-merge cell is deterministically the parent's (nonzero whenever the parent is celled), so the unwield Position classifies by TELEPORT_TS/distance — matching retail's predicate population exactly. Consequences to carry: (a) 4b-3's test 4 ("unwield-to-3D shape classifies `SetPosition`") remains valid ONLY as a synthetic pre-merge-cell-0 fixture — it must not be re-labeled as the live unwield behavior; (b) the recorded live-closure recipe for `cause=cellless` needs a different provocation (a genuinely withdrawn body receiving a Position without an intervening Create — whether ACE ever emits that shape is unestablished); update the 4b-3 contract's closure note in this slice's docs commit rather than leaving a recipe that can no longer fire. No code in the 4b-3 arm changes. --- ## 12. Open questions routed to the reviewers 1. **D2's chokepoint placement** (retail-conformance + architecture): the directory funnel is argued from §0 item 8's caller closure — verify independently that no canonical cell write bypasses `RuntimeEntityDirectory.SetFullCell`/`RefreshSnapshot` at HEAD (the load-bearing claim; if a bypass exists, D2 has a hole exactly where the defect class predicts). 2. **D3's delete edge**: confirm by reading the App teardown/orphan flow (`EquippedChildRenderController`'s `_pendingOrphanRemovalByChild`, `LiveEntityRuntimeTeardownController`) that zeroing children's cells at parent delete cannot race a child projection teardown already in flight, and that the child's later own-DeleteObject converges. 3. **D4's entry point**: confirm the presentation-only child rebucket cannot be reached for a non-child record (the general-bypass hazard) and that `BeginPresentationOnlySpatialMutation`'s guard semantics hold for the per-frame cadence. 4. **D5's validation gap**: confirm no existing prepared-content surface exposes `Setup.HoldingLocations` before accepting the register row; and review what the headless drive deliberately does not drive. 5. **P8's publication decision**: adversarially hunt a `Rebucketed` consumer that needs the per-child deltas the demotion removes (route 5's A1 class — the App tolerating the seam's silence must be shown, not assumed). 6. **D7's inertness argument** — verify no observer distinguishes the reordered pickup sequence (the claim is argued, with the T7 history, not merely asserted; but it is an ordering change on a live path). 7. **§11's 4b-3 interaction** — confirm the synthetic fixture reading and that no OTHER consumer of `PreMergeCommittedCellId` changes population when children stop being cell-less.