A player-parented child never received a canonical cell. Its FullCellId stayed 0 for its whole attached lifetime, so it could not follow the player across a boundary. Scope was wider than the local player: every REMOTE player's equipment too. ROOT CAUSE. EquippedChildRenderController hardcoded ParentInstanceSequence: 0 for a parented CreateObject. Correct for creatures and statics, which really are sequence 0; wrong for players, whose ObjectInstance is Character.TotalLogins (ACE Player_Networking.cs:37). The relation filed under (playerGuid, 0) while the record carried TotalLogins, so both route-7 write sites — D1's attach re-cell and D2's propagation lookup — keyed on an incarnation that never matched. TryCommitParent did not validate the sequence, so the attach succeeded and printed normally. Silent. A ROUTE 7 REGRESSION (cd3129e9) that un-masked a latent bug: the TickChild call route 7 deleted was keyed on the child guid alone and was structurally immune to a wrong parent key. THE FIX IS TO STOP TREATING PLAYERS DIFFERENTLY, not to special-case them. Retail's attach path is guid-only end to end — PhysicsDesc::get_parent_id @0x00558a18 -> CObjectMaint::GetObjectA @0x00558a2d -> set_parent @0x00558a3e, with SetChildren @0x00509370 hash-walking by guid — and neither set_parent overload (@0x00515A90, @0x00515B50) nor enter_cell @0x00510ED0 contains any player test or instance-sequence read. Our player/non-player split was purely an artifact of keying relations by (guid, incarnation) against a wire message that carries no parent incarnation. Late-binding to whoever currently holds the guid is retail's own semantics. Fixed at BOTH producers: OnSpawn and OnCreateParentAccepted, the second carrying the byte-identical defect and not named in the contract's scope line. THE INVARIANT IS EQUALITY, NOT FRESHNESS. The contract rejected both framings I offered: every one of the 45 FullCellId liveness predicates excludes a committed child on a NON-cell clause first, so the child inherits only the parent record's existing staleness, which is already present today with no symptom. The key fix alone restores child-equals-parent for every parent class. TWO SITES GATED, inert only because the cell was zero and would have woken wrongly: the hydration candidate loop (a nonzero-cell child would take the legacy RebucketLiveEntity -> CommitRebucket, a second canonical writer — route 7's exact defect class) and RestoreShadow (would install a broadphase row for the weapon, the #184 shape, contradicting route 7's P4). Retail anchor: update_object's parent != 0 early-out @0x00515D40 — children are never independently re-placed. THREE MAJORS WERE FIXED BY DELETION. The first pass added a deferral queue for an unaddressable parent, carrying a missing child-freshness gate (A2), a sentinel-0 collision with the generation filters (A3), and unbounded accumulation (A5). Both reviewers then proved the deferred branch unreachable for BOTH producers — RegisterEntityCore defers the entire CreateObject one layer above, reading the same ?? chain, and CreateParentUpdate is produced only inside AcceptCreateCore, after that gate passes. The machinery was deleted rather than repaired, and the diff SHRANK to 76 added / 13 removed from 91/24 while gaining the A1 fix. Retail confirmed the deletion does not diverge: acdream's real port of retail's per-guid replay (QueueBlobForObject) is a different, untouched layer, and the deleted queue was a third redundant one downstream of it. THE GUARD MUST NOT TEAR WHAT IT PROTECTS. The first pass threw InvalidOperationException AFTER the canonical half had committed, so the one time it fired it left the child parented with no committed relation and a staged one blocking Resolve — a torn transaction, the exact outcome the contract pinned against. Now a pure CanCommitIncarnation precondition checked BEFORE the commit at both sites, with a logged refusal instead of a throw. Route 3's N3 principle (do not make a transient fatal on a host that must survive 30 sessions x 2 hours) reinforces it, but the tearing argument stands alone. TEST QUALITY, the recurring lesson in its most refined form. The A1 test initially passed sabotage FOR THE WRONG REASON: a mismatched ChildPositionSequence meant TryCommitParent's own gate refused in either ordering, so the three assertions carrying A1's meaning passed both ways and only an incidental staging assertion failed. It failed on stranding, not tearing. Corrected, the sabotage now names line 925 — Assert.Null(snapshot.ParentGuid), with the parent's guid in it — proving the canonical mutation happened before the catch. "Fails under sabotage" is necessary, not sufficient; WHICH assertion fails is the real question. The dual parent-class matrix (player 0x5… incarnation > 1 vs creature 0x8… incarnation 0, identical outcomes, sabotage-verified in both directions) is the structural fix for how this survived a full dual review and two connected sessions: every prior test and both captured gate logs used sequence-0 parents. Register: AP-142 clause (f); AP-132 amended to distinguish the two producers; new row AP-146 for the local player's coarse canonical cell (retail writes it per tick at SetPositionInternal @0x00515330 — which, per the retail review, ALSO walks this->children writing each child's objcell_id @0x005153AE-@0x005153D8, so retail's per-tick child propagation lives in the same function). That divergence had no row at all, a standing rule-1 violation now corrected. Follow-up #320 filed for making the player's cell track ordinary movement — deliberately excluded here: it touches the landblock-preserve contract, the Rebucketed cadence, route-2/4b-3 classification inputs AP-136/AP-138 spent four review rounds pinning, and the portal-space frozen-source-cell race. Two dual review rounds; 6 architecture MAJORs and 2 retail MAJORs closed. Diagnostic refusals are latched per child guid and the latch clears on Clear()/RemoveChild, so a recycled guid's next incarnation still logs rather than being silently suppressed. Complete Release suite MEASURED at 11,112 passed / 4 skipped / 0 failed (baseline 11,090 at52175aa1, +22). Neither known flake fired. STILL OWED: the connected gate, with the CORRECTED positive criterion — assert the equipped child's FullCellId EQUALS the parent's after a crossing (a zero is a failure, not a silence), run with BOTH a player and a creature parent, plus the new step carrying an armed creature across a landblock unload/reload. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
998 lines
66 KiB
Markdown
998 lines
66 KiB
Markdown
# 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<uint>` 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<Setup>`) — 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.
|