# C4 routes 6 and 7 — scoping (2026-08-04) Read-only research. Worktree `C:\Users\erikn\source\repos\acdream\.claude\worktrees\peaceful-visvesvaraya-e0a196`, branch `claude/acdream-physics-divergence-5aa784`, HEAD `d5bdc355`. Scoped together deliberately: the campaign groups route 6 (drops + split-recovery marking) and route 7 (residual pickup/parent/delete polish) as adjacent item/container work. **They turn out not to share a single production file.** The reason to keep them in one document is different from the reason they were paired: both routes' premises in [`2026-08-02-cutover-route-inventory.md`](2026-08-02-cutover-route-inventory.md) were written **before C3c flipped both hosts' Create paths onto the residence lease**, and both are now substantially or entirely satisfied by that flip. Splitting the scoping would have produced two documents each re-deriving the same C3c correction. **Headline: route 6 requires no production change. Route 7 has exactly one real defect, and it is a two-writer split on the child's canonical cell — which is also the reason headless has no parent-realize sequence at all.** --- ## 0. The correction that governs both routes The inventory's route-6 and route-7 sections both rest on this claim (`2026-08-02-cutover-route-inventory.md:759-767`, repeated at `:806-824`): > `RegisterEntityWithInitialResidence` is never called from any production > path (confirmed by full-repo grep …). Both drop flavors equally bypass the > canonical create-placement transaction. **That is no longer true.** C3c (`529e0e9d`) flipped both hosts: - Graphical: `src/AcDream.App/World/LiveEntityRuntime.cs:544-548` — `_entityObjects.RegisterEntityWithInitialResidence(incoming, isLocalPlayer, RetirePriorProjection)`, with the C3c rationale comment at `:538-543`. - Direct/headless: `src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs:118-123` — the same call, except for the deliberately-excluded content-less host (comment `:108-117`). Route 7's inventory paragraph "these cancellation calls are structurally present but functionally no-ops against always-empty state" is likewise obsolete: `InitialCreateResidences` is populated in production now, so every `ForgetInitialCreateResidence` in the pickup/parent/delete family is live. Anything in the inventory's routes 6/7 sections that reasons from "nothing upstream to cancel" must be re-derived, not cited. --- # ROUTE 6 — drops and unparent-to-world ## 6.1 What the route actually is **There is no route-6 classifier disposition and no route-6 `RuntimePositionEntityKind`.** A dropped item is an ordinary non-local CreateObject: - `RuntimePositionEntityKind.Remote` (`src/AcDream.Runtime/Physics/RuntimeAuthoritativePositionRouteClassifier.cs:8-14`). - `RuntimeCreateResidenceKind.TopLevel`, produced at `src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs:603-606` (`Parented` when the wire frame carries a parent, `PickedUp` when it has neither parent nor position). - `ClassifyCreate` (`RuntimeAuthoritativePositionRouteClassifier.cs:205-275`) returns disposition `SetPosition` with `InitialCreateFlags = Placement | Slide` (`:168-170`, applied `:254-258`). That is byte-for-byte route 1's create classification. Route 6 is a *source* of route-1 traffic, not a route of its own. ## 6.2 Both drop flavors already converge on the canonical transaction **Whole-item drop.** `ItemInteractionController.ExecutePlacementActions`, `DropToWorld` case (`src/AcDream.App/UI/ItemInteractionController.cs:974-991`): optimistic `MoveItemOptimistic(objectId, newContainerId: 0u, newSlot: -1)` then `_sendDrop(objectId)`. No physics, no position. The server's CreateObject comes back through the ordinary wire pump into `LiveEntityHydrationController.OnCreate` → `_runtime.RegisterLiveEntity` → `RegisterEntityWithInitialResidence` (`LiveEntityRuntime.cs:544-548`). **Split-to-world.** Same file `:992-1013` (`SplitToWorld` case) raises `WorldDropDispatched`; `InventoryWorldDropProjectionController.OnWorldDropDispatched` (`src/AcDream.App/World/InventoryWorldDropProjectionController.cs:81-98`) records the pending identity; the new GUID's F748 Position is recovered by `TryRecoverUnknownPosition` (`:54-68`), which calls **`_hydration.OnCreate(spawn)` at `:66` — the identical entry point**. Same residence lease, same conductor, same placement. **Therefore the campaign handoff's route-6 requirement (`2026-07-31-remaining-physics-campaign-handoff.md:357-362`) — "`TryRecoverUnknownPosition` may create the logical object, but it must enter the same canonical create-placement transaction" — is already met, by C3c, with zero route-6-specific code.** ## 6.3 "Do not expose a stale source position" — satisfied `PendingSplitToWorldProjection.BuildSpawn` (`InventoryWorldDropProjectionController.cs:171-209`) overrides every positional field from the arriving update: | Field | Line | |---|---| | `Physics.Position` | `:179` | | `Physics.Parent = null` | `:180` | | `Physics.Velocity` | `:181` | | `Physics.Timestamps.{Position,Teleport,ForcePosition,Instance}` | `:182-188` | | DTO `Position` | `:194` | | DTO `ContainerId = 0`, `WielderId = 0`, `CurrentWieldedLocation = 0` | `:197-199` | | DTO `{Instance,Position}Sequence`, `ParentGuid = null`, `ParentLocation = null`, `PlacementId` | `:200-206` | No source pose survives into the synthetic spawn. ## 6.4 "Do not replay create-time effects" — NOT ESTABLISHED as a defect The inventory calls this "a genuine new capability, not merely dormant" (`2026-08-02-cutover-route-inventory.md:768-775`). I could not find a reachable defect behind it, and the retail decomp says there should not be one. **acdream's only create-time effect replay is the F754/F755 network queue drain, keyed by server GUID.** `LiveEntityReadyPublisher.Publish` calls `effects.ReplayPendingForLiveEntity(record.ServerGuid)` (`src/AcDream.App/World/LiveEntityHydrationPorts.cs:97`, body at `src/AcDream.App/Rendering/Vfx/EntityEffectController.cs:199-205`). That queue is written only by `HandleDirect`/`HandleTyped` (`EntityEffectController.cs:92-126`) from inbound `PlayPhysicsScript` / `PlayPhysicsScriptType` for that exact GUID. A fresh split GUID has nothing queued unless ACE actually sent an effect for it — and draining it then is retail's own behaviour, which the class documents and ports: `SmartBox::HandlePlayScriptID @ 0x00452020` / `HandlePlayScriptType @ 0x00452070` queue while the object is absent, and `SmartBox::HandleCreateObject @ 0x00454C80` drains through `ProcessObjectNetBlobs` (cited `EntityEffectController.cs:17-24, :37-45`). **The one plausible mechanism — a cloned default script — does not fire at create in either client.** `BuildSpawn` does clone `DefaultScriptType` / `DefaultScriptIntensity` from the source (they are not in the override list; fields at `src/AcDream.Core.Net/Messages/PhysicsSpawnData.cs:58-59`), and they reach `EntityEffectProfile.RawDefaultScriptType` (`src/AcDream.App/Rendering/Vfx/EntityEffectProfile.cs:67`). But acdream's `PlayDefault` (`EntityEffectController.cs:409-418`) has exactly two callers, both animation hooks: `DefaultScriptHook` and `DefaultScriptPartHook` (`:428-435`). Retail matches: `CPhysicsObj::play_default_script @ 0x005132B0` / `@ 0x00513300` is reached only from `ACCWeenieObject::DoCollision @ 0x0058C3A0` (call @0x0058C3B4) and the animation-hook dispatcher (@0x00526C08, @0x00526C14). **Neither client plays a default script from `set_description` or CreateObject.** **Live evidence that would settle it if someone still doubts:** split a stack of a WCID whose PhysicsDesc carries a nonzero `DefaultScriptType` and watch for a script at the drop. Per the two call-site sets above there is none to observe, so this is a confirmation, not an open question. ## 6.5 Retail truth for drops and for "split-recovery marking" **Whole-item drop.** `ACCWeenieObject::UIAttemptPutIn3D @ 0x0058D700`: gate on `IsPlayerReadyToMakeInventoryRequest` @0x0058D70F, send `CM_Inventory::Event_DropItem` @0x0058D715, then record only `prevRequest = IR_MOVE` (@0x0058D74F) or `IR_DROP` (@0x0058D731). **No placement call, no split marker, no client-side position.** The dropped object re-enters the world through the ordinary CreateObject path. **Split.** `ACCWeenieObject::UIAttemptSplitTo3D @ 0x0058D850` sends `Event_StackableSplitTo3D` @0x0058D86B and then records exactly three fields: `splitStackSize = arg2` @0x0058D8A2, `splitClassID = this->pwd._wcid` @0x0058D8A8, `splitTime = cur_time` @0x0058D8AE. `ACCWeenieObject::UIAttemptSplitToContainer @ 0x0058D7D0` records the **identical three fields** at @0x0058D82D / @0x0058D833 / @0x0058D838. **The marker's consumer is `ACCWeenieObject::DeclareValid @ 0x0058E340`:** ``` splitClassID read @0x0058E44A if (splitClassID != INVALID) { if (splitClassID == this->pwd._wcid) @0x0058E462 stack = this->pwd._stackSize; if (0) 1 @0x0058E464-@0x0058E46E if (splitStackSize == stack) { @0x0058E479 ACCWeenieObject::SetSelectedObject(this->id, 0); @0x0058E481 splitClassID = INVALID; return; @0x0058E490 } if (cur_time - splitTime >= 10.0) @0x0058E49F-@0x0058E4B0 splitClassID = INVALID; @0x0058E4B2 } ``` **So retail's "split-recovery marking" is (i) a WCID + stack-size + 10-second identity recorded for BOTH split flavors, and (ii) a recovery action that is a SELECTION TRANSFER — `SetSelectedObject` — not effect suppression and not anything placement-related.** acdream implements (i) partially and (ii) not at all: - The 10-second window is exact: `PendingSplitToWorldProjection.RetailRecognitionSeconds = 10.0` (`InventoryWorldDropProjectionController.cs:113`), enforced `:146-153`. - The match is **"any unknown GUID that is not the source"** (`:155-156`), not WCID + stack size, because ACE's F748 carries neither. This is the already-registered **AP-124** (`docs/architecture/retail-divergence-register.md:274`), which cites both retail symbols correctly. - acdream records the marker only for `InventoryRequestKind.SplitToWorld` (`:86`); retail records it for split-to-container too. - No `SetSelectedObject` equivalent runs. The controller's constructor (`:33-52`) takes interaction / objects / runtime / hydration / clock and has no selection dependency at all. ## 6.6 Route 6 — what remains **Nothing that C4 must do.** The three genuine residuals are: | # | Item | Kind | |---|---|---| | R6-a | `DeclareValid`'s `SetSelectedObject(this->id, 0)` @0x0058E481 is not ported — the split result does not become the selected object, and the container-split flavor has no marker at all | UI/selection behaviour, **not placement**; file as an issue, out of C4 scope | | R6-b | AP-124's WCID/count approximation | already registered; retiring it needs ACE to send CreateObject to the initiator, not a client change | | R6-c | `BuildSpawn` clones `Children`, `Movement`, `AnimationFrame`, `SetupTableId` etc. wholesale from the source | **not established** as a defect: a stackable inventory item's `Children` should be empty and its `Movement` inert. Settled by dumping a source spawn's `PhysicsSpawnData` at a live split — one `ACDREAM_DUMP_*`-style probe, or an assertion in the new tests | **Recommended route-6 landing: evidence + tests only, no production diff.** Add the coverage the campaign handoff's route-6 test list names (`2026-07-31-remaining-physics-campaign-handoff.md:363-365`) against the now-flipped path: whole item, split stack, new GUID, second drop position, unavailable destination, newer Position while waiting. Note that "attached child becoming a world root" from that list is **not route 6's** — it is a cell-less Position on an existing entity, owned by 4b-3. ## 6.7 Route 6 — line budget - **Production: 0 lines** for the campaign mandate. If R6-a is adopted inside C4 (I recommend against it — it is selection UX, and mixing it in makes the landing un-reviewable against a placement contract), **40-80** lines. - **Tests: 150-250 lines.** Route 4a was 364 production lines; this is an order of magnitude below the threshold where splitting is a question. ## 6.8 Route 6 — connected gate Cheap and directly visible. 1. Stand on open ground in Holtburg. Drop a whole item from inventory (drag to 3D or the drop action). It must appear **at your feet**, resting on the ground, immediately, and be pickable. 2. Split a partial stack of a stackable (e.g. pyreals) to the ground. Same result, with the correct quantity on the new pile and the remainder still in inventory. 3. Drop a second item within ~1 m of the first. Both must remain visible and separately pickable. 4. Repeat once indoors (an inn interior) and once immediately after a portal recall. 5. Walk two landblocks away and back. Both piles must still be there and still pickable. **Regressions to watch for:** the item appears at the world origin or at your *previous* position (stale pose); the item is invisible but blocks you (invisible-but-solid, the #184 class); the item sinks into or floats above the floor; the item cannot be picked up; the split pile never appears at all (the recovery window failed); the second drop is swallowed by the first. --- # ROUTE 7 — pickup, parent, and delete ## 7.1 What the route actually is **Dispositions.** Route 7's classifier surface is `RuntimeAuthoritativePositionRouteClassifier.ClassifyLeaveWorld` (`:475-505`), taking a `RuntimeLeaveWorldRouteRequest` (`:141-145`) whose `RuntimeLeaveWorldCause` is `Pickup` or `Parent` (`:31-36`). It returns disposition `AwaitFreshPosition` with `LeaveWorld: true`, `SetPositionFlags.None`, `ConstrainPhase.None`, and no teleport hook. The create-side siblings are `RuntimeCreateResidenceKind.Parented` and `PickedUp`, which `ClassifyCreate` routes to the same `AwaitFreshPosition` / no-SetPosition branch (`:219-241`), with the comment at `:230-233` stating why initial residence invents no withdrawal edge. **`ClassifyLeaveWorld` has ZERO production callers.** Repo-wide grep returns its own definition and exactly one test (`tests/AcDream.Runtime.Tests/Physics/RuntimeAuthoritativePositionRouteClassifierTests.cs:341-342`), plus a comment in `RuntimeInitialCreateContinuationExecutorTests.cs:1672` noting that `ApplyPositionAction` does not call it. Every production pickup / parent / delete performs its leave-world work by hard-coded sequence inside `RuntimeEntityObjectLifetime`, never through the classifier. **Entity kind** is whatever the object is — `Remote` for items, and the classifier's `OperationKind(kind, initialCreate: false)` derives from it. ## 7.2 Retail truth: route 7 performs NO placement This is the single most important fact for scoping route 7, and it inverts the trap list that governed routes 2 / 4a / 4b. **Pickup — `SmartBox::DoPickupEvent @ 0x00452240`:** ``` gate on obj->update_times[0] (the POSITION stamp) @0x0045224B-@0x00452274 obj->update_times[0] = incoming @0x00452278 CPhysicsObj::unset_parent(obj); @0x0045227F CPhysicsObj::leave_world(obj); @0x00452286 ``` No `SetPosition`, no `MoveOrTeleport`, no `ConstrainTo`, no placement flags. **Parent — `SmartBox::DoParentEvent @ 0x00452290`:** ``` gate on child->update_times[0] @0x00452296-@0x004522C5 if (child had no parent && parentId != player) parentWeenie->SetParentedState(1); @0x004522F4 CPhysicsObj::set_parent(child, parent, loc); @0x00452305 CPhysicsObj::SetPlacementFrame(child, arg5, 1); @0x00452313 ``` **`CPhysicsObj::set_parent @ 0x00515A90` (3-arg):** ``` if (parent != 0 && add_child(parent, this, loc) != 0) { @0x00515A9D unset_parent(this); @0x00515ABA leave_world(this); @0x00515AC1 this->parent = parent; @0x00515AC6 if (parent->cell != 0) { @0x00515AD1 change_cell(this, parent->cell); @0x00515AD6 UpdateChild(parent, this, part_number, frame); @0x00515B0E recalc_cross_cells(this); @0x00515B15 } if (parent->state & NODRAW-bit) { @0x00515B26 this->state |= 0x20; part_array->SetNoDrawInternal(1); @0x00515B38 } return 1; @0x00515B45 } return 0; @0x00515B4D ``` The 4-arg overload @0x00515B50 is the same shape, minus the `CHILDLIST` index lookup and plus `m_bExaminationObject` inheritance @0x00515B76. **`CPhysicsObj::unset_parent @ 0x00513470`:** `CHILDLIST::remove_child` @0x00513484 → NoDraw restore @0x00513495-@0x005134A7 → `parent = null` @0x005134AC → `update_time = cur_time` @0x005134BF → tailcall `clear_transient_states` @0x005134CE. **Conclusion.** Route 7's entire mandate in this campaign is the one the campaign handoff already states (`2026-07-31-remaining-physics-campaign-handoff.md:367-373`): pickup / parent / delete must **cancel** the exact active placement / lost-cell family first and publish `Discard`/`Withdraw` before the later entity delta. There is no placement to port, no leash, no distance threshold, no flags. ## 7.3 What is already canonical (verified, not assumed) **Every cancellation choke point exists and is live.** Six sites in `src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs`, each running the identical `ForgetInitialCreateResidence` → `Physics.SetPosition.Forget` → `PreferCancellation` sequence and threading the receipt into `AcknowledgeProjectionAndPublish`: | Method | Cancellation lines | |---|---| | `CommitPositionChannelUpdate` | `:1074-1094` | | `TryApplyPickup` | `:1226-1245` | | `TryCommitParent` | `:1360-1374` (added at C0; rationale `:1344-1359`) | | `CommitAcceptedParentCellless` | `:1390-1407` | | `CommitWithdrawal` | `:1845-1860` (added at C0; rationale `:1837-1844`) | | `TryAcceptDelete` | `:1939-1952` | `ForgetInitialCreateResidence` is at `:2552-2569`; `PreferCancellation` (initial wins over ordinary) at `:2571-2574`. **The two C0 asymmetries the inventory flagged (`:826-837`) are FIXED.** `TryCommitParent` had no choke point at all; `CommitWithdrawal` cancelled only the residence family. Both now cancel both families, and both carry the explanatory comment naming the C0 finding. `TryCommitParent`'s deliberate omission of `Physics.CollisionReports.LeaveWorld` is documented `:1352-1359` against `set_parent @ 0x00515A90`'s single gated `leave_world` @0x00515AC1 — **do not "fix" this; the campaign contract already pins it.** **Ordering is correct.** `AcknowledgeProjectionAndPublish` (`:2187-2213`) runs `Physics.SetPosition.PublishCancellation(cancellation)` as its **first** statement at `:2194`, before the currency re-check, before the host acknowledgement callback, and before `PublishEntity`. That is exactly the handoff's "publish `Discard`/`Withdraw` before the later entity/inventory delta." **Cancellation receipts ARE host-visible.** `RuntimeSetPositionState.PublishCancellation` (`:5296-5309`) republishes the retained `Discard`-kind `RuntimePlacementProjectionSnapshot` through `PublishPlacement`, i.e. through the same one placement stream. Both hosts consume the kind: `src/AcDream.App/World/RuntimePlacementPresentationSink.cs:102`, `src/AcDream.App/World/LiveEntityRuntime.cs:1179`, and `src/AcDream.Headless/Hosting/HeadlessRuntimePlacementProjectionSink.cs:27`. **The C0 note "route-7 needs … host-visible cancellation receipts" is satisfied.** **Pickup/parent during a pending residence already defer.** `TryApplyPickup` `:1189-1215` enqueues a dormant `RuntimeInitialCreateContinuationKind.Pickup` continuation when `TryGetPendingInitialResidence` hits; `TryApplyParent` and `TryApplyMotion` have the same shape. **App and headless carry no duplicate placement authority for route 7.** `LiveEntityDeletionController` (102 lines) is purely logical. `LiveEntityHydrationController.OnPickup` (`:455-474`) calls `_runtime.TryApplyPickup` then `_relationships.OnChildBecameUnparented`. Headless `OnPickedUp` / `OnDeleted` / `OnParentUpdated` (`RuntimeLiveEntitySessionController.cs:176-180, :155-174, :313-317`) are thin pass-throughs. Grep for `SnapToCell` / `CommitRebucket` / `SuspendObjectClock` / `SetFullCell` across `src/AcDream.App` and `src/AcDream.Headless` returns no pickup/parent/delete site. ## 7.4 The one real defect — the child's canonical cell has two writers This is route 7's whole substance. **Retail** puts the child in the parent's cell **inside `set_parent`**: `change_cell(this, parent->cell)` @0x00515AD6 followed by `recalc_cross_cells(this)` @0x00515B15, gated on `parent->cell != 0` @0x00515AD1. **acdream splits that in half across two assemblies:** 1. Runtime commits the child **cell-less, unconditionally**. `RuntimeEntityObjectLifetime.CommitAcceptedParentCellless` (`:1377-1408`) runs `Physics.CollisionReports.LeaveWorld` `:1396`, `Entities.SuspendObjectClock` `:1397`, `Entities.SetFullCell(canonical, 0u, 0u)` `:1398`. Its App wrapper's doc comment (`LiveEntityRuntime.cs:2288-2292`) claims it "commits retail `CPhysicsObj::set_parent`'s cell-less edge" — accurate only for the `parent->cell == 0` case; retail's `parent->cell != 0` case immediately re-cells the child and acdream never performs that half in Runtime. 2. **App re-cells the child from a presentation tick.** `EquippedChildRenderController.TickChild` `:405-408` calls `_liveEntities.RebucketLiveEntity(child.ChildGuid, parentCellId)`, and the public `LiveEntityRuntime.RebucketLiveEntity` (`:801-977`) is *not* presentation-only — it writes the canonical cell through `_entityObjects.CommitRebucket(record.Canonical, committedFullCell, committedLandblock)` at `:904-907`. So the canonical `FullCellId` of every equipped child is produced by a per-frame render-composition tick, not by the parent commit. Two consequences, and they are the same bug seen from two sides: - **Headless leaves every parented child cell-less forever.** `RuntimeLiveEntitySessionController.OnParentUpdated` (`:313-317`) calls only `Entities.TryApplyParent`. Neither `TryCommitParent` nor `CommitAcceptedParentCellless` has any headless caller — repo grep finds only `EquippedChildRenderController.cs:869` and the App wrappers at `LiveEntityRuntime.cs:2280-2312`. There is no headless `EquippedChildRenderController`, so nothing ever supplies step 2. The inventory called this a "headless-specific gap, pre-existing, adjacent" (`:839-853`) — it is not adjacent, it is the *same* missing commit. - **The two-store shape is exactly the failure class this campaign exists to remove**, and it is the shape the trap list names ("two separate snapshot stores for one piece of state"). **Fix shape:** move the parent-cell commit into `TryCommitParent` / `CommitAcceptedParentCellless` (i.e. perform retail's `parent->cell != 0` half in Runtime, gated the same way), and demote App's `TickChild` rebucket to presentation-only — `RebucketLiveEntityPresentationOnly` (`LiveEntityRuntime.cs:993-1050`) is the existing shape, added by C3c for exactly this "Runtime already owns the canonical commit" case. Headless then gets the correct child cell for free. ## 7.5 The second item — `ClassifyLeaveWorld` is dead Either wire pickup/parent through it so route 7 stops being the one route with hard-coded dispositions, or delete it and record why the hard-coded sequence is the right shape. Leaving a classified disposition with one test and no production caller is how routes 2 and 4 accumulated their divergences. I lean **wire it**, because the cause discriminator (`Pickup` vs `Parent`) is the thing that decides whether `EndChildProjection` runs, and today that decision is implicit in *which method the caller picked*. ## 7.6 Route 7 — line budget | Piece | Production lines | |---|---| | Runtime child-cell commit in `TryCommitParent` / `CommitAcceptedParentCellless` (retail's `change_cell` + `recalc_cross_cells` half, gated on parent cell) | 120-180 | | Demote `EquippedChildRenderController.TickChild`'s rebucket to presentation-only + the follow path that replaces it | 60-100 | | Headless parent-realize drive (`OnParentUpdated` → the same commit) | 60-100 | | Wire or delete `ClassifyLeaveWorld` | 60-150 | | **Total** | **300-490** | Calibration: route 4a was 364 non-comment production lines, 4b-1 was 230 + a 57-line park fix. **Route 7 fits one slice and must not be split** — the child cell commit and the App demotion are the two halves of one transfer and cannot land separately without leaving the child cell-less in the interval. Test work ~400-700 lines. If the implementation crosses **550** production lines, stop and report; the most likely cause would be discovering that the per-tick follow (7.8, T5) needs its own Runtime mechanism, which is a second slice. ## 7.7 Route 7 — connected gate Two clients. `+Acdream` plus a second character observing. 1. **Equip / unequip cycle.** Wield a weapon, then a shield, then unwield both, five times, while the observer watches. The weapon must appear in the hand, at the hand, oriented with the hand, and disappear cleanly on unwield. 2. **Carry across a landblock boundary.** With the weapon equipped, run across at least two landblock boundaries and back, both while the observer watches and while observing the other character do it. 3. **Pickup.** Drop the weapon, pick it back up. It must leave the ground (route 7's `leave_world`) and re-appear equipped or in inventory with no ghost left on the ground. 4. **Delete under load.** Kill something and loot its equipped items. 5. **Reconnect.** Log out with equipment and back in; the equipment must re-attach. 6. **Headless.** Run the headless bot with an equipped item and assert the child's canonical `FullCellId` equals the parent's at a stable checkpoint — this is the direct regression test for 7.4, and it fails **today**. **Regressions to watch for:** the equipped weapon draws at the world origin or at its last ground position; the weapon is invisible while equipped; **invisible-but-solid** — the weapon's collision remains at the drop site after being picked up; the weapon is left behind when the wearer crosses a landblock boundary (this is the R1/#184 class and is the specific risk of demoting the tick rebucket); the child is culled when the parent is visible or vice versa; a picked-up item still blocks movement. ## 7.8 Traps Mapped to the shapes that already bit this campaign, plus route-7-specific ones. **T1 — the inverse leash trap. Route 7 must NOT arm `ConstrainTo`.** 4b-2's contract correctly says "arm on refusal and rejection too" because `MoveOrTeleport` returns 1 regardless and `HandleReceivedPosition` @0x00454254 arms @0x00454272. **Route 7 never reaches `HandleReceivedPosition` at all** — `DoPickupEvent` @0x00452240 and `DoParentEvent` @0x00452290 are separate wire handlers. An implementer arriving from 4b-2 will carry the "always arm" rule across. There is no leash on this route. **T2 — deleting the only handler for a rejected classification.** `ClassifyLeaveWorld` returns `RejectedAuthority` when `Cause is RuntimeLeaveWorldCause.Unknown` or `ValidCreateAuthority` fails (`:481-487`). Retail's `DoPickupEvent` has **only** a stale-timestamp no-op — there is no "rejected" outcome that skips `unset_parent`/`leave_world` once the stamp is accepted. If route 7 wires the classifier and performs the leave-world only on `Accepted`, every rejected classification silently drops a pickup. This is 4b-2's `null`/`Rejected*` trap, one level up: state the policy explicitly. **T3 — `ValidCreateAuthority`'s teleport-equality predicate is the #307 shape.** `ClassifyLeaveWorld` gates on `ValidCreateAuthority` (`:507-512`), which requires `PreviousTeleportSequence == AcceptedTeleportSequence`. That is exactly the predicate #307 (`19d95094`) had to fix on the live Position path, where `PreviousTeleport` was always 0 and every previously-teleported entity failed it. Before wiring T2's classifier, **verify first-hand** that the pickup/parent authority actually populates `PreviousTeleportSequence`, and that retail's own gate is the *position* stamp (`update_times[0]` @0x0045224B), not a teleport stamp at all — the two may simply not correspond. **T4 — do not carry `MoveOrTeleport`'s guards onto this route.** AP-87's 4 m `BodySnapThreshold` and `!willBeDrTicked` conditions and the 96 m `MaxPhysicsDistance` are near/far *interpolation* decisions inside `MoveOrTeleport @ 0x00516330`. Pickup and parent have no distance concept. Importing any of them is the "carry a guard onto a branch that does not need it" shape AP-87's own row and the 4b-2 contract both warn about. **T5 — demoting the tick rebucket can reproduce #184 / route-4a's R1.** `EquippedChildRenderController.TickChild`'s rebucket (`:405-408`) runs on every recomposition, and `LiveEntityRuntime.RebucketLiveEntity` is what moves the GPU draw bucket, sets `IsSpatiallyVisible`, calls `RefreshPresentation`, and publishes `ProjectionVisibilityChanged` (`:844-953`). Route 4a's R1 finding was precisely that gating this call left a creature body-and-shadow-correct but draw-bucket-stale — **invisible but solid**. The replacement must keep the child following the parent across cell boundaries. **Retail-side status: PARTIALLY ESTABLISHED.** `CPhysicsObj::UpdateChild @ 0x00512D50` composes the frame and calls `CPhysicsObj::set_frame(child, …)` @0x00512D8D; `set_frame @ 0x00514090` writes `m_position.frame` @0x005140E9, pushes it to the part array @0x00514101, and cascades `UpdateChildrenInternal` @0x00514108 — **it never touches `objcell_id` or `cell`.** So retail's per-move child update is frame-only, and the child's cell is established once at `set_parent`'s `change_cell` @0x00515AD6. **Whether a parent crossing a cell re-cells its children is NOT established** from `UpdateChild`/`set_frame`/`UpdateChildrenInternal` alone. Settle it before demoting: read `CPhysicsObj::change_cell` / `CPhysicsObj::set_cell`'s child handling, or take a cdb trace with breakpoints on `CPhysicsObj::change_cell` and `CPhysicsObj::UpdateChildrenInternal` while a retail character carrying an equipped weapon walks across a landblock boundary. **T6 — the pre-flight-guard trap probably does NOT apply, and saying so matters.** 4b-1's finding was that a pre-flight service-window guard cannot see conditions only Core can see (`AdjustToOutside` residency, `ResultTouchesPrefix` over `QueriedCellIds`). Route 7's child-cell commit is retail's `change_cell` — **no sweep, no `AdjustPosition`, no placement** — so there is no deferrable Core condition to miss. Do not import 4b-1's service-window machinery here. If an implementer finds a reason a child cell commit *can* defer, that is a new finding and needs its own stop-and-report. **T7 — ordering divergence on pickup, significance NOT ESTABLISHED.** Retail runs `unset_parent` @0x0045227F **before** `leave_world` @0x00452286. acdream inverts it: `RuntimeEntityObjectLifetime.TryApplyPickup` runs `Physics.CollisionReports.LeaveWorld` `:1229` → `SetFullCell(0,0)` `:1235` → `ParentAttachments.EndChildProjection` `:1236`, and at the App layer `LiveEntityHydrationController.OnPickup` (`:460-473`) runs `TryApplyPickup` first and `OnChildBecameUnparented` second. The two operations touch different structures (collision reporting vs the relation table), so I could not construct a case where the order is observable — but I did not prove it inert. Decide deliberately and record the decision; do not silently preserve the inversion because "tests are green". **T8 — do not add a second `LeaveWorld` to `TryCommitParent`.** Already pinned by the campaign plan and by the in-code comment at `:1352-1359`, but it is the most natural-looking "asymmetry" a reviewer will flag, because every sibling method calls `Physics.CollisionReports.LeaveWorld` and this one does not. Retail's `set_parent` has exactly one `leave_world` @0x00515AC1, inside the `add_child` success branch that acdream's staged/deferred realize sequence represents. --- ## 8. Coupling, merge/split, and order **Do they share files?** **No.** Route 6's surface is `InventoryWorldDropProjectionController.cs`, `ItemInteractionController.cs`, and (read-only) `LiveEntityHydrationController.OnCreate`. Route 7's surface is `RuntimeEntityObjectLifetime.cs`, `EquippedChildRenderController.cs`, `RuntimeLiveEntitySessionController.cs`, and `RuntimeAuthoritativePositionRouteClassifier.cs`. The only shared *concept* is that both funnel through `LiveEntityHydrationController`, and route 6 does not modify it. **Can 6 land without 7?** Yes, trivially — route 6 has no production diff. **Should they be ONE slice?** **No, but not for the campaign's reason.** They should be *two landings of very different kinds*: - **Route 6 is a closure, not a slice.** No production change; a short evidence note plus the drop/split coverage tests. Landing it as a "slice" with the full contract → implementer → dual-review discipline would be ceremony over a zero-line diff. - **Route 7 is one genuine slice** of ~300-490 production lines, and its two halves (Runtime child-cell commit; App demotion) **must not** be split from each other. **Recommended order: route 6 first, then route 7.** Not because of a dependency — there is none — but because route 6's closure is what removes the inventory's false "genuine new capability, not merely dormant" claim (`:768-775`) from the campaign's live record. Leaving that claim standing means route 7's implementer inherits a planning document that asserts an effect-replay suppression signal must exist. Cost of doing 6 first: a few hours. **One planning-record correction to fold in when route 6 closes:** the campaign plan's own pre-cutover gap list (`docs/plans/2026-08-02-placement-cutover.md:98-100`) still says "Route-6 split-recovery creates need an effect-replay suppression signal; route-7 needs `TryCommitParent`/`CommitWithdrawal` cancellation-symmetry fixes and host-visible cancellation receipts". The first clause is unsubstantiated (§6.4) and the second and third were both closed at C0 (§7.3). The list should say what actually remains: the child-cell two-writer split and the headless parent-realize gap. --- ## 9. What I could not establish 1. **Whether retail re-cells a child when its parent crosses a cell** (T5). `UpdateChild` @0x00512D50 → `set_frame` @0x00514090 is frame-only and never writes `objcell_id`. `change_cell`/`set_cell`'s child handling was not read. This is load-bearing for demoting App's per-tick rebucket, and it is the one thing route 7's contract must nail down before implementation. 2. **Whether the pickup ordering inversion is observable** (T7). Different data structures; no constructed failure case; not proven inert. 3. **Whether `BuildSpawn`'s wholesale clone of `Children` / `Movement` / `AnimationFrame` can carry anything harmful onto a split result** (R6-c). Expected empty for a stackable inventory item; not measured. 4. **Whether `ClassifyLeaveWorld`'s `ValidCreateAuthority` teleport-equality gate is even meaningful for pickup** (T3). Retail's gate is the position stamp @0x0045224B; the classifier's is a teleport-sequence equality inherited from the create path. The two may not correspond at all, in which case wiring the classifier unchanged would reject valid pickups the way #307 rejected valid force corrections.