acdream/docs/research/2026-08-05-issue-319-retail-review.md
Erik 392c1e22c1 fix(physics): bind a parented child to the parent's live incarnation (#319)
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 at 52175aa1, +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>
2026-08-05 11:56:31 +02:00

380 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Issue #319 — independent retail-conformance review (2026-08-05)
**Verdict: PASS.**
Reviewed: the uncommitted working tree at base HEAD `af828a8a`, branch
`claude/acdream-physics-divergence-5aa784``git diff HEAD` (13 files,
+709/-30) plus the untracked contract
`docs/research/2026-08-05-issue-319-contract.md`.
Review only. No file in the tree was modified by this review except this
report.
Independent gates run for this review (a green suite is not evidence, but a
red one is):
- `dotnet build AcDream.slnx -c Release` — exit 0.
- `AcDream.Runtime.Tests` Release — 1170/1170, 0 skipped.
- `AcDream.App.Tests` Release — 4125 passed, 3 skipped, 0 failed.
---
## 1. The central retail claim — independently verified, and the contract
## UNDERSTATES its own anchor
Every retail claim below was read at
`docs/research/named-retail/acclient_2013_pseudo_c.txt` for this review. None
was inherited from the contract or the investigation.
| contract claim | address | verified |
|---|---|---|
| CreateObject's attach is GUID-only | `ACCObjectMaint::CreateObject` @0x00558870; `PhysicsDesc::get_parent_id` @0x00558a18`if (eax_20 != 0)` @0x00558a1f`CObjectMaint::GetObjectA(this, eax_20)` @0x00558a2d`get_parent_location_id` @0x00558a31`CPhysicsObj::set_parent(result, arg2, eax_22)` @0x00558a3e | ✓ **exact**. `GetObjectA` takes `(this, guid)`. There is no instance-sequence read, comparison, or argument anywhere in the block. |
| The reverse direction is the same shape | `CObjectMaint::SetChildren` @0x00509370: `unparent_children` @0x00509379, hash-bucket walk on `PhysicsDesc::get_child_id` @0x005093b2@0x005093ca, `GetNullObject(this, get_child_id(...), 1)` placeholder @0x005093e6, `set_parent(hash_next_1, arg2, get_child_location_id(...))` @0x005093f8 | ✓ **exact**, including the `GetNullObject` placeholder for a not-yet-constructed child. No instance field participates. |
| No player branch in the relation path | `set_parent` @0x00515A90 (3-arg) and @0x00515B50 (4-arg, Frame); `enter_cell` @0x00510ED0; `change_cell` @0x00513390 | ✓. Both `set_parent` overloads operate on `CPhysicsObj*` uniformly: `add_child``unset_parent``leave_world``parent = edi``if (cell != 0) change_cell``UpdateChild``recalc_cross_cells`. The only conditional in either body is the parent's `state & 0x4000` no-draw cascade @0x00515B26. `enter_cell`'s only guard is `part_array != 0` @0x00510ED8. **No player test exists in any of them.** |
| Retail's player cell is written per tick | `CPhysicsObj::SetPositionInternal` @0x00515330 | ✓ — and **stronger than the contract states**, see below. |
| `update_object` early-outs on a parented object | @0x00515D40: `if ((this_3->parent != 0 \|\| (this_3->cell == 0 \|\| (this_3->state & 0x1000000) != 0))) { transient_state &= ~0x80; return; }` | ✓ **exact**. A parented child is never independently updated or re-placed. |
**The contract's §1 row 5 cites the weaker half of its own best anchor.**
`SetPositionInternal` @0x00515330 does not merely write the mover's own cell:
in the same-cell branch it writes `this->m_position.objcell_id = objcell_id`
@0x00515385, then **walks `this->children` and writes each child's cell id
directly** — `*(uint32_t*)((char*)eax_2 + 0x4c) = objcell_id_1` @0x005153BD,
followed by `CPartArray::SetCellID` @0x005153CC, looping @0x005153AE
@0x005153D8; the cross-cell branch delegates to `change_cell` @0x00515372.
That is retail's D2 equivalent living **inside the same per-tick function** as
the player's own cell write. It is a strengthening, not a contradiction: the
child-equals-parent equality invariant and AP-146's "retail's player cell is
never stale" both get a single anchor. (AP-142 clause (b) already cites this
range; only §1's summary table understates it.)
### Conclusion on the central claim
**Retail does NOT distinguish a player parent, and there is no
instance-sequence check anywhere in the relation path — confirmed
independently.** acdream's player/non-player split is exactly what the
contract says it is: an artifact of keying committed relations by
`(guid, incarnation)` against a wire message that carries only a GUID.
**Late-binding to whoever currently holds the GUID is the faithful mapping**,
because "the current holder of the GUID" is literally what `GetObjectA` /
`GetNullObject` return. The fix shape is right.
---
## 2. Scope expansion (`OnCreateParentAccepted`) — JUSTIFIED, and the fix is
## correct there
The contract's Scope line named only `EquippedChildRenderController.OnSpawn`.
The implementer also fixed `OnCreateParentAccepted`. Verified:
- `CreateParentUpdate` is declared at
`src/AcDream.Runtime/Entities/InboundPhysicsStateController.cs:1399-1405`
as `(ChildGuid, ParentGuid, ParentLocation, PlacementId,
ChildInstanceSequence, ChildPositionSequence)`. Its producer,
`BuildSameGenerationEvents` (`:1240-1248`), fills
`ChildInstanceSequence` from `ts.Instance` where `ts` is
`incoming.Physics.Timestamps` and `incoming` **is the child**. So
`ChildInstanceSequence` is the CHILD's own timestamp, exactly as the
implementer reports. **No parent instance sequence exists on this shape.**
- The codebase already knew this: `InboundPhysicsStateController.
TryApplyCreateParent`'s pre-existing doc comment (`:216-218`) reads
"Unlike standalone ParentEvent it carries no parent INSTANCE_TS, so only
the child's shared POSITION_TS participates in freshness."
- Pre-fix, `OnCreateParentAccepted` hardcoded `ParentInstanceSequence: 0`
into the identical `Relations.AcceptCreateObjectRelation` call. **Byte-
identical defect.** Shipping the key fix on one producer and not the other
would have left #319 half-open for every same-generation ObjDesc refresh of
a player-parented child.
Routing both through one `AcceptLateBoundCreateObjectRelation`
(`EquippedChildRenderController.cs:836-857`) is the right shape and the right
commit.
---
## 3. Findings
### R1 — MINOR (incorrect load-bearing comment). Both deferred branches are production-unreachable, not just `OnSpawn`'s.
`tests/AcDream.App.Tests/Rendering/EquippedChildProjectionWithdrawalTests.cs:875-881`
The test's XML doc asserts:
> `CreateParentUpdate`'s producer … has no equivalent parent-addressability
> precondition, so this is the reachable shape for #319's deferred branch.
**Verified false.** The only production driver of `OnCreateParentAccepted` is
`LiveEntityHydrationController.OnCreate`'s `result.SameGenerationEvents is
{ } refresh` branch (`LiveEntityHydrationController.cs:340-341` →
`LiveEntityNetworkUpdateController.cs:380` →
`LiveEntitySameGenerationUpdateRouter.Apply` → `OnParent` → `:400-401`).
`SameGenerationCreateObjectEvents` are produced only inside
`InboundPhysicsStateController.AcceptCreate` (`:70-90`), reached only from
`RuntimeEntityObjectLifetime.RegisterEntityCore` — and `RegisterEntityCore`'s
deferral gate runs **before** `PreviewCreateDisposition`:
```
RuntimeEntityObjectLifetime.cs:798-812
uint parentGuid = incoming.ParentGuid ?? incoming.Physics?.Parent?.Guid ?? 0u;
if (beginInitialResidence && parentGuid != 0u
&& !Entities.TryGetActive(parentGuid, out _))
{ Entities.ParentAttachments.EnqueueDeferredCreate(...); return DeferredForParent: true; }
```
The `??` chain covers both the flattened `ParentGuid` and the nested
`Physics.Parent.Guid`, i.e. both shapes `OnSpawn` and `OnCreateParentAccepted`
read. The App graphical host is the `beginInitialResidence: true` caller
(`LiveEntityRuntime.cs:583`). So by the time either producer runs, the parent
is active. The implementer's `OnSpawn` finding is correct; the extension of it
to `OnCreateParentAccepted` is not.
Retail address contradicted: none — this is a reachability claim about
acdream, not about retail.
**Not a code defect.** The deferred machinery is correct, and it is genuinely
defensive: the deferral gate reads `Entities.TryGetActive` (the
`_activeByGuid` map) while the late-bind reads `TryGetSnapshot` (the
`_inbound._snapshots` map) — two distinct stores, so a producer-side fallback
is warranted rather than an assertion. Correct behaviour: the comment should
say the deferred branch is defensive against store drift and is not exercised
by any production path today, and the test should be labelled as covering the
mechanism rather than a reachable production shape.
### R2 — MINOR (latent bug, unreachable today by R1). The late-bind sentinel `0` is filtered as if it were a wire-named incarnation.
`src/AcDream.Runtime/Entities/ParentAttachmentState.cs:828-834` (`EndGeneration`)
and `:853-857` (`DeleteGeneration`), via `FilterParentCandidates` (`:1002-1018`).
`DeferCreateObjectRelation` (`:412-426`) queues the relation into
`_unresolvedByChild` carrying `ParentInstanceSequence: 0` plus
`LateBindParentInstance = true`. The `0` is a **meaningless placeholder** —
that is the whole premise of the fix. But `EndGeneration`'s retain predicate
treats it as a wire-named value:
```
relation => relation.ParentInstanceSequence == replacementGeneration
|| PhysicsTimestampGate.IsNewer(replacementGeneration,
relation.ParentInstanceSequence)
```
`IsNewer(oldStamp, newStamp)` (`AcDream.Core/Physics/PhysicsTimestampGate.cs:58-63`)
returns true when `newStamp` is newer. For a player parent replaced at
generation 7: `0 == 7` false, `IsNewer(7, 0)` false → **the late-bind relation
is dropped**, which is the exact opposite of its own semantic ("attach to
whoever currently holds the GUID" — the replacement generation is precisely
whom it should attach to) and of retail's GUID-keyed blob replay
(`QueueBlobForObject`, AP-132's retail half: replay on GUID (re)creation with
only an addressability check).
Not reachable in production today because of R1, and **not a regression** —
pre-fix a CreateObject relation went straight to `_stagedByChild`, which
`EndGeneration` clears unconditionally via `RemoveParentReferences`
(`:822-827`). Correct behaviour when the path becomes reachable: both
`FilterParentCandidates` retain predicates should keep
`relation.LateBindParentInstance` unconditionally, or the deferred relation
should carry a nullable incarnation rather than a `0` sentinel that collides
with a legitimate creature-parent value.
### R3 — MINOR (incorrect comment, two sites; the same wrong rationale twice).
`tests/AcDream.App.Tests/World/LiveEntityHydrationControllerTests.cs:149-153`
and `tests/AcDream.App.Tests/World/LiveEntityPresentationControllerTests.cs:101-105`.
Both comments justify the fixture setup with:
> …giving it a nonzero canonical `FullCellId` via D1/D2 (exercised
> elsewhere) — impossible to reach before #319's fix for a player-class
> parent.
Two errors:
1. **"the relation never committed for a player-class parent at all" is
false.** `HasCommittedParent` is keyed by the CHILD guid
(`ParentAttachmentState.cs:571-572`: `_lastAcceptedByChild.ContainsKey(childGuid)`),
and pre-fix `CommitProjection` succeeded under `(playerGuid, 0)` — the
contract's own §0 says so verbatim ("`TryCommitParent` never validates the
sequence; the attach succeeds silently"). A player-parented child **did**
have a committed parent at HEAD. What was inert at HEAD is the child's
zero `FullCellId`, which is the contract's actual §3.1/§3.2 argument.
2. **The nonzero cell in both fixtures does not come from D1/D2.** The
presentation fixture materialises at `0x01010001u`
(`LiveEntityPresentationControllerTests.cs:690`, seeded from the spawn's
own `ServerPosition` at `:744`); the hydration fixture's record carries
`Cell` from its own spawn. Neither test calls
`CommitAcceptedParentCellless`.
The tests themselves are **valid and their sabotage claims hold** — I
verified independently that `ProjectionCellId => WorldEntity is not null ?
FullCellId : …` (`LiveEntityRuntime.cs:387-389`) makes a committed child's
candidate cell exactly `FullCellId`, so it is 0 at HEAD and the parent's cell
post-fix; the gates are therefore behavior-preserving at HEAD, exactly as the
contract argued. Only the stated rationale is wrong. Correct behaviour: cite
the zero-`FullCellId` inertness, not a non-existent absence of a committed
relation.
### R4 — MINOR (comment made partially false by this diff; the comment itself is untouched).
`src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs:1529-1531` (D1):
> The committed relation (not the guid alone) resolves the parent so a stale
> or superseded incarnation can never re-cell the child.
After F1 a CreateObject-carried relation is, by construction, always equal to
the parent's live incarnation at commit (and the new tripwire at
`ParentAttachmentState.cs:651-661` enforces it). For that producer the
sentence now describes a protection that is vacuous rather than active. It
remains true for the `ParentEvent` producer, whose incarnation the wire names
(AP-132). The diff changed the semantics of a comment it did not edit — the
exact class the review brief flags. Correct behaviour: qualify the sentence to
name the ParentEvent producer.
### R5 — LOW (comment misdirection).
`src/AcDream.App/World/LiveEntityPresentationController.cs:228` — "the same
predicate the hydration gate above uses". The hydration gate is in a different
file (`LiveEntityHydrationController.OnLandblockLoaded:561`), not "above".
### R6 — OBSERVATION, partly UNVERIFIABLE (flagged rather than guessed). The tripwire converts a silent skip into a production throw.
`ParentAttachmentState.CommitProjection` (`:639-661`) now throws
`InvalidOperationException` when the parent is addressable and the relation's
incarnation disagrees. The contract explicitly authorised this ("throw or
logged refusal … pinned outcome: never a silent success"), so it is in scope.
I enumerated the false-positive surface as far as source allows:
- Only the **Staged** branch of `PrepareAndTryRealize`
(`EquippedChildRenderController.cs:954-960`) reaches it in the graphical
host; the Recovery branch does not.
- That branch is gated by `ValidateParentProjection` (`:986-996`), which
requires a live `LiveEntityRecord` for the parent.
- A staged relation is staged either at the parent's live incarnation
(`AcceptLateBoundCreateObjectRelation:846-853`), or after `Resolve`'s
equality check (`ParentAttachmentState.cs:484-498`), or by the late-bind
adoption (`:473-483`).
- A parent generation change removes every staged/recovery/committed
reference to that parent (`EndGeneration:822-827`,
`DeleteGeneration:847-852`).
- In the headless host (`RuntimeLiveEntitySessionController.cs:410`) relations
originate only from `Enqueue(ParentEvent.Parsed)` and pass `Resolve`'s
equality gate, so the tripwire is a no-op there.
**What I could NOT establish:** whether `_inbound._snapshots[parent].
InstanceSequence` (the tripwire's source, reached via `TryGetSnapshot`) can
ever lead `_activeByGuid[parent].Incarnation` inside a re-CreateObject
transaction — `AcceptCreate` writes `_snapshots[guid] = incoming`
(`InboundPhysicsStateController.cs:77`) before `RegisterEntityCore` completes
the record replacement, and the two stores are documented as "related but
distinct" (`:1198-1210`). I found no call into `CommitProjection` inside that
window, but I did not prove the absence exhaustively. Reporting as
unverified. If the connected gate ever produces this exception, the message
already names #319 and the two incarnations, which is the right diagnostic.
### R7 — OBSERVATION. The hydration gate is broader than its cited retail anchor.
`LiveEntityHydrationController.cs:551-562` excludes a committed child from the
**entire** candidate loop, so `ProjectExact(CreateSupersessionRecovery)`
(`:595`) and `ProjectExact(SpatialRecovery)` (`:614`) are also refused for it,
not only the legacy `RebucketLiveEntity → CommitRebucket` branch (`:607`).
This is what the contract pinned ("the candidate loop excludes records with a
committed parent", §3.1) and it is behavior-preserving at HEAD (verified: a
committed child's `projectionCellId` is `FullCellId` = 0 today). But the
retail anchor in the comment, `update_object`'s `parent != 0` early-out
@0x00515D40, speaks to **physics re-placement**, not to render-projection
recovery. The broader exclusion actually rests on route 7's P4 record (a
committed child is never a spatial root, joins no workset, has no shadow row)
plus the fact that a child's projection is driven exclusively through
`EquippedChildRenderController`'s realize/retry path. Worth one sentence in
AP-142 clause (f) or the contract rather than a code change — as written the
comment claims more anchoring than @0x00515D40 provides.
---
## 4. The three unwritten contract tests — NONE blocks
- **§6 test 5 (residence non-event).** Not blocking. I verified §3.3's premise
directly in source rather than relying on the test:
`RuntimeEntityObjectLifetime.InitializeAcceptedCreateResidence:2750-2752`
reads `if (canonical.FullCellId != 0u) Entities.SetFullCell(canonical, 0u,
0u);` immediately before `InitialCreateResidences.Begin`. `Begin`'s
`FullCellId != 0` refusal is therefore a defensive invariant, not a
reachable gate, exactly as §3.3 concluded. The zero-then-re-cell atomicity
claim remains argued-not-measured, but no production change was made at
that site.
- **§6 test 6 (unwield classification population).** Not blocking. The
contract itself recorded (from `af828a8a`) that ACE advances TELEPORT_TS at
a drop and the classifier is a short-circuit OR
(`RuntimeAuthoritativePositionRouteClassifier.cs:391`), so the observable
label is `teleport-ts` either way. The test would pin a flavour, not a
behaviour the fix changes.
- **§6 test 9 (ledger convergence).** Not blocking, and I checked why:
`RestoreShadow`'s refusal for a committed child is byte-identical pre- and
post-fix (the `FullCellId == 0` clause already refused every committed child
at HEAD). The one ledger asymmetry that exists —
`_suspendedShadowOwners.Add` on `BecameHidden`
(`LiveEntityPresentationController.cs:172`) with no matching removal on
`BecameVisible` because `RestoreShadow` returns false — is **pre-existing
and unchanged**, and converges through `Forget`/`Clear` (`:117`, `:125`).
The connected §7 gate (both halves, player class mandatory) remains the real
acceptance test and has not run. That is correctly recorded in ISSUES.md's
#319 status line, which honestly says "FIX IMPLEMENTED, awaiting the connected
acceptance gate … NOT YET COMMITTED".
---
## 5. Register and bookkeeping — verified row by row
- **AP-146 (new row, `retail-divergence-register.md:174`).** Retail anchor
`CPhysicsObj::SetPositionInternal` @0x00515330 **verified** — the address
resolves to the `(CPhysicsObj*, CTransition const*)` overload (the 4-arg
`SetPositionInternal` at @0x00515BD0 is a different function; the row cites
the right one), and the cell write is unconditional across both branches
(`m_position.objcell_id = …` @0x00515385 same-cell, `change_cell`
@0x00515372 cross-cell). The three acdream writers, the
`LocalPlayerProjectionController` landblock coarsening, and the
`LiveEntityRuntime.cs:935-938` preserve are all cited. The risk column names
the unresolved `ParkCollisionResidents` question **openly** rather than
smoothing it. Register rule 1 is satisfied: this was a standing
undocumented divergence and the diff files it. Header count 102 → 103,
correct.
- **AP-142 clause (f).** Every retail address in the clause
(@0x00558a18/@0x00558a2d/@0x00558a3e, @0x00509370/@0x005093e6) was
re-verified above and is exact. The clause states the acdream side honestly
(the `(guid, incarnation)` key is acdream's, retail has none) and names the
tripwire. Correct.
- **AP-132 amendment.** The added sentence distinguishes the two producers
correctly and does not weaken the row's own gate. Verified against the wire
types: `ParentEvent.Parsed` carries `ParentInstanceSequence`;
`CreateParentUpdate` carries `ChildInstanceSequence`. Correct.
- **Route-7 contract §7 supersession blockquote.** Accurate — the old
criterion is genuinely unfalsifiable under #319 (a zero-cell child emits no
probe line, which the old criterion read as clean) and it points at the
corrected positive-equality criterion. Correct.
- **#320.** A faithful transcription of contract §2.2's five-item enumeration
plus §9 item 2 as the mandatory first verification step, with the correct
"do not implement without a fresh retail-conformance argument" gate. It
inherits exactly what #319 deliberately excluded — nothing more, nothing
less.
---
## 6. Errors in the CONTRACT itself
Two, neither material to the verdict:
1. **§1 row 5 understates its own anchor** (see §1 of this review).
`SetPositionInternal` @0x00515330 also walks `this->children` writing each
child's `objcell_id` @0x005153AE@0x005153D8 — retail's per-tick D2
equivalent in the same function. This *strengthens* the equality invariant
and AP-146; citing only the mover's own write leaves the strongest
available evidence on the table.
2. **§4 F4's "ISSUES.md: #319 → Recently closed with the commit SHA" was not
executed** — correctly, since the connected gate has not run and the tree
is uncommitted. The contract's instruction was written assuming the fix
commit and the gate land together. Deviation is honest and recorded in the
issue's own status line.
Everything else in the contract that this review touched — §0's defect chain,
§1's five rows, §2.1's four-consumer enumeration, §3.1/§3.2's wake mechanics,
§3.3's residence non-event, §4 F1's pinned constraints, §5's invariants — was
confirmed against source or pseudo-C.