acdream/docs/research/2026-08-04-c4-route-7-retail-review-round2.md
Erik cd3129e9d6 fix(physics): C4 route 7 — child cell propagation moves from a render tick into Runtime
Retail re-cells children when their parent crosses a cell, recursively, to
unbounded depth. acdream did it from a RENDER tick, so headless parented
children were cell-less forever and the canonical cell had two writers. This
slice makes Runtime the sole authority and demotes App's tick to
presentation-only. Contract:
docs/research/2026-08-04-c4-route-7-contract.md; the research that unblocked
it is docs/research/2026-08-04-retail-parent-cell-propagation.md (ca96ea5e).

Retail: SetPositionInternal @0x00515330 branches on `this->cell == curr_cell`
@0x0051536d; the changed branch reaches change_cell @0x00513390, whose
delegates leave_cell @0x00510f50 and enter_cell @0x00510ed0 self-recurse over
children and write the FULL identity (add_object @0x00510ee2, objcell_id
@0x00510f1e, part-array cell id @0x00510f2b, cell pointer @0x00510f35).
change_cell itself has no child loop.

THE TRAP, recorded because it nearly shipped: the depth-1 loop
@0x0051539c-0x005153d8 is the SAME-CELL fast path (objcell_id and part-array
id only, deliberately not the cell pointer), NOT the propagation. An
implementer who finds it first concludes "depth-1, id-only" and strands every
equipped item at a landblock boundary — the #184 class. The clincher against
that reading: update_object @0x00515d10 early-returns on `parent != 0`
@0x00515d40, so a child never runs its own physics tick and parent
propagation is the ONLY mechanism maintaining its cell.

Route 7 performs NO placement (DoPickupEvent @0x00452240 = unset_parent +
leave_world; DoParentEvent @0x00452290 = set_parent + SetPlacementFrame), so
it arms ConstrainTo nowhere — the leash rule INVERTS relative to routes
2/4/5, and both reviewers confirmed nothing arms.

Propagation is an ITERATIVE WORKLIST, not recursion. The first implementation
recursed with a depth-64 cap; both reviews independently found the cap left a
truncated tail at a stale NON-ZERO cell — permanently unrecoverable, logged
only under a probe flag, and on the withdraw path exactly the #184 shape
AP-142 clause (a) exists to reject. Shipping a fresh #184 instance inside the
slice that fixes stranded children was not acceptable, so the cap was removed
rather than tuned. The worklist retires the cap, the constant, its register
clause, and the failure mode together. Termination: every record on the stack
is already at the target pair, so nothing can be pushed twice and a hostile
A->B->A cycle collapses without a visited set.

The child write deliberately bypasses the public RuntimeEntityDirectory
.SetFullCell and calls the record method directly. This is LOAD-BEARING:
the public method re-enters PropagateFullCellToChildren, which opens with
_propagationWorklist.Clear() — routing children through it mid-drain would
wipe the shared stack and silently drop every unprocessed sibling. Any future
side effect added to the public SetFullCell must be mirrored by hand at that
call site.

Deliberate divergence, recorded not disguised: retail's removal path leaves
children with a null cell pointer but a STALE nonzero objcell_id @0x005133c1.
acdream does not reproduce it, because FullCellId != 0 is the liveness
predicate at 45+ sites — faithful porting would mark dead children live.
AP-142 records this; clause (d) records that acdream cannot gate propagation
on HasPartArray the way enter_cell gates on part_array @0x00510ed8, because
the flag's only writers are graphical and headless never sets it — the reason
is Slice J LAYERING, not a semantic difference (retail's part_array is itself
a mesh-construction product, single assignment site makeAnimObject
@0x0050e930 -> CPartArray::CreateSetup @0x0050e93e).

D7 adopts retail's unset_parent-before-leave_world order @0x0045227f ->
@0x00452286, applied to BOTH pickup paths including the dormant executor
replay. Its inertness was verified by reverting it and finding all 12
propagation tests still green — reported honestly rather than papered over
with a manufactured test, and independently confirmed by both reviewers.

ClassifyLeaveWorld and its request/cause types are DELETED: retail has no
classification here, and method-per-cause IS the retail dispatch shape.
Wiring it would have forced a vacuous teleport-sequence predicate with the
#307 shape.

Two review rounds plus a coordinator-required third pass; 5 MAJORs. One was a
handoff failure worth recording: enter_cell's part_array guard was correctly
identified as load-bearing by the research, dropped by the contract when it
enumerated the writes, and inherited as an omission by the code — a right
finding that evaporated across two handoffs with nobody re-reading the source.
Another was a test that survived deleting the entire behaviour it claimed to
pin, because its assertion read a field written unconditionally one line
earlier.

NoProjection is structurally unreachable from TickChild (TryResolveExactAttachment
performs a strictly stronger form of the same guard one call earlier). Kept as
a fail-safe, unit-tested directly, and documented in two places rather than
wrapped in a fabricated end-to-end test.

Headless regression test — the direct gate for this defect, which FAILED
before this work because no code path existed:
RuntimeLiveEntitySessionControllerTests
.DirectSink_D5_StandaloneParentEventCommitsChildToParentsExactCell.

Probe: ACDREAM_PROBE_CHILD_CELL=1 emits [child-cell] lines at all four write
sites (attach / headless-attach / propagate / withdraw / delete). TEMPORARY.

Complete Release suite MEASURED at 11,079 passed / 4 skipped / 0 failed
(baseline 11,063 at cff52c44, +16). An allocation flake appeared once under
load and was proven NOT this slice by reachability — RuntimeCollisionReportingState
contains zero SetFullCell and zero ParentAttachments references.

STILL OWED: the two-client connected gate (equip/unequip, carry across
landblock boundaries, pickup, loot, reconnect) with ACDREAM_PROBE_CHILD_CELL=1,
and a session counts only if [child-cell] cause=propagate lines appear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:53:05 +02:00

22 KiB
Raw Blame History

C4 route 7 — retail-conformance review, ROUND 2 (delta)

Date: 2026-08-04 Reviewer role: independent retail-conformance reviewer, review-only. Subject: the uncommitted working tree at HEAD 19ebf043, after the round-1 fix pass. Round 1 is docs/research/2026-08-04-c4-route-7-retail-review.md (verdict FAIL, R1R11); the parallel architecture review is docs/research/2026-08-04-c4-route-7-architecture-review.md (A1A10). Scope: delta only. Round 1's §A retail verification (every address in enter_cell / leave_cell / change_cell / SetPositionInternal / update_object / set_parent / DoPickupEvent / DoParentEvent) stands unchanged and is not re-litigated here.


VERDICT: PASS

All three round-1 MAJORs are closed, and closed properly rather than argued away:

  • R1 — the implementer's pushback is CORRECT and my premise was wrong in the letter: RuntimeEntityRecord.HasPartArray does exist on the canonical record. I re-verified the writer enumeration independently and it is complete and correct (§1). R1's conclusion — that this is a real, unrecorded retail divergence needing a register row — was right, and AP-142 clause (d) is an honest row that does not mischaracterise retail's intent.
  • R2 / A1 — the D4 test now queries real GpuWorldState landblock membership before and after the tick. I verified by construction that it cannot pass with the demotion removed (§2).
  • R3 / A2 — the typed-disposition fork is sound, and the implementer's choice of the sanctioned alternative over the primary suggestion is the better call for a reason the review did not state (§3).

R5, R7, R9, R10, R11 and all three contract corrections landed. Build green (0 errors); focused suites 22/22 Runtime + 28/28 App. Per process rule 5 that is not the basis of this verdict.

Seven new MINORs (N1N7) below, none blocking. Two of them (N1, N5) are the comment-precision class this campaign keeps hitting, and one (N6) is a new untested control-flow branch created by the R3 fix itself.


1. R1 — the pushback, verified on all three questions

(a) Is the HasPartArray writer enumeration complete, and are both writers graphical-only? — YES, verified.

Repo-wide grep over src/ + tests/ (excluding bin/obj) for HasPartArray:

site role
src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs:203 SetHasPartArray(expectedCanonical, true)App / graphical
src/AcDream.App/Rendering/EquippedChildRenderController.cs:609 childRecord.HasPartArray = trueApp / graphical (the row cites :591; see N3)
src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs:1167 SetHasPartArray(canonical, false) — Runtime, but a clear, not a set
RuntimeEntityDirectory.cs:495-498, LiveEntityRuntime.cs:250-253/:534-537 plumbing (setter/forwarder), no value origin
6 sites under tests/ test-only
ProjectileController.cs:681/:720, RemotePhysicsUpdater.cs:101, LiveEntityAnimationScheduler.cs:240, EquippedChildRenderController.cs:920 readers only

No Runtime, Headless, or otherwise presentation-independent site ever sets HasPartArray = true, for a child or for an ordinary root. The implementer's claim is exactly right, and the consequence it draws is right too: gating D1/D2 on child.HasPartArray would permanently strand every headless committed child — the functional inverse of this slice's purpose. My round-1 R1 asserted "acdream's propagation has no analogue"; the field has an analogue, the canonical layer does not. The correction is accepted.

(b) Is AP-142 clause (d) an honest description? — YES, with two precision defects (N1, N2).

The row states the guard's address (@0x00510ed8), its full effect (the write and the recursion and the subtree skip), that acdream writes unconditionally, why gating is not available, and puts the consequence in the risk column. It does not claim fidelity it lacks. That is what a divergence row is supposed to look like.

(c) Is retail's guard moot under acdream's structure, or a real behavioural difference? — A REAL behavioural difference, correctly accepted.

Retail's guard is not decorative. enter_cell's CObjCell::add_object @0x00510ee2 maintains the cell's object list, which retail uses for both drawing and collision/visibility. So a part-array-less object in retail is neither drawn nor cell-resident, and acdream — where FullCellId is the residency predicate at 45+ sites — will mark such a child resident. The row says this plainly and puts "acdream celling a child retail would leave nowhere" in the risk column. No mischaracterisation of retail's intent. This is the question I was watching for and the row passes it.

(b')/R10 — clause (b)'s over-claimed equivalence: CORRECTED, accurately.

The row now reads: "this is a clean equivalence only on the REMOVAL side. The skip ALSO prunes the child's whole subtree on a same-value WRITE, which retail's enter_cell does not do — it recurses over children unconditionally (@0x00510f03); only leave_cell prunes (@0x00510f5b, on cell != 0). Currently unreachable-by-construction … but it is an asymmetry, not a proven equivalence." I re-read both addresses: enter_cell's recursion @0x00510f03 is inside the part_array guard but has no per-child cell test, and leave_cell's @0x00510f5b prune is exactly as described. The correction is verbatim-accurate. R10 closed.


2. R2 / A1 — the D4 presentation test: verified it cannot pass without the demotion

tests/AcDream.App.Tests/Rendering/EquippedChildProjectionWithdrawalTests.cs:321-402.

Verified by construction, not by taking the sabotage note on trust:

  1. fixture.Spatial is a real GpuWorldState (:1443, internal GpuWorldState Spatial { get; } = new()), and CopyLiveEntitiesNearLandblock (src/AcDream.App/Streaming/GpuWorldState.cs:493-520) is a genuine per-landblock query over _loadedLiveByLandblock, radius 0, that early-returns empty if the world is unavailable.
  2. The pre-assertions (Assert.Contains(old, child) + Assert.DoesNotContain(new, child)) do double duty: they establish a distinct starting bucket and they prove the query is live — a dead or always-empty query would fail the Contains immediately. This is what makes the post-assertion load-bearing rather than vacuous, and it is the exact property round 1's version lacked.
  3. TickChild's only spatial mutation for the child is the demoted call at EquippedChildRenderController.cs:409-431. CommitRebucket (the Runtime producer driven before the tick) writes canonical fields only and touches no spatial index. So stubbing RebucketEquippedChildPresentation leaves the child in oldLandblock and Assert.Contains(newBuffer, child) fails.

R2 closed. The test now asserts the layer that historically broke.


3. R3 / A2 — the typed-disposition fork: sound, and the deviation from the review's primary suggestion is the better call

LiveEntityRuntime.cs:45-61 (the enum), :1088-1148 (the method), EquippedChildRenderController.cs:406-431 (the caller).

The implementer took the sanctioned alternative (branch explicitly, do not fail the tick on NotAttached) over the review's primary suggestion (assert / propagate as a failure). That is correct, for a reason worth pinning:

  • NotAttached genuinely coincides with an in-flight subtree withdrawal. OnChildBecameUnparented already owns that teardown and drives BeginProjectionSubtreeWithdrawal. Returning false from TickChild would route the same child into WithdrawForPoseLoss in the same frame — a second concurrent withdrawal trigger on a subtree that already has one in flight. The implementer's stated worry is real, not defensive hand-waving.
  • NoProjection is the branch that should fail the tick, and it does. Its first guard (TryGetCurrent / WorldEntity is not { }) is unreachable from TickChildTryResolveExactAttachment (:379, and again at :409) already required IsCurrentRecord(child.ChildRecord) and a non-null WorldEntity — so in practice NoProjection only arises from RebucketLiveEntityPresentationOnly returning false, i.e. a projection operation displaced mid-flight, where withdrawal is the right response.

So the fork partitions cleanly: benign-decline vs displaced-projection, with the new failure path landing only on the genuinely-broken case. R3 closed (see N6 for the one gap it leaves).

A9 was also folded in: the new method now carries the active-initial-create- residence gate (:1136-1141), making its "can never become a general bypass" doc claim true at the entry point rather than by call-ordering three files away.


4. Round-1 findings — closure status

# Status
R1 CLOSED. Premise corrected by the implementer (verified §1a); AP-142 clause (d) filed and honest (§1b/c). N1/N2/N4 are precision follow-ups on the row's text, not on the decision.
R2 CLOSED. §2.
R3 CLOSED. §3.
R4 PARTIAL — confirmed still non-blocking, now more comfortably so. Writer families (b) RuntimePhysicsState.CommitCanonicalCell and (c) RuntimeSetPositionState still have no dedicated propagation test, and P2 is still only asserted on one path. Three things reduce the residual risk below the blocking line: the hook is at the funnel, not per-caller (so a missed writer is structurally impossible, not merely unobserved); both reviewers have now independently traced the complete SetFullCell caller set (the architecture review's 13-row table, which I spot-checked against my own round-1 grep and found consistent); and the connected gate's pass criterion is cause=propagate probe lines at each landblock crossing, which is the physics/placement writer families in live play. P8 and P4 are now stated in-code (RuntimeEntityDirectory.cs's doc <para> blocks) with A3's correction folded in.
R5 CLOSED. RuntimeInitialCreateContinuationExecutor.cs:2184-2190EndChildProjection now precedes LeaveWorld on the dormant replay, matching @0x0045227F-before-@0x00452286. Both pickup paths are one shape, and the fix is real code, not a narrowed comment.
R6 CLOSED as documentation. RuntimeLiveEntitySessionController.cs:345-368 now states the gap in full — the TryCommitParent position-timestamp gate, why nothing re-drives it, why RetryChildrenWaitingForParent does not cover it (the relation is staged, not unresolved), that it is not a regression, and that invariant 9 is untouched. That is the right disposition for a pre-existing gap surfaced by a new drive.
R7 CLOSED. AP-143 now names all three skipped checks with the correct retail anchors and an inertness argument for (1) and (2). Verified against the code: self-parent :915-916, HasPartArray :920, HoldingLocations :924-937. (Line citations are stale — N3.)
R8 CLOSED / withdrawn. My nested-descendant concern was wrong: a grandchild's own ParentEvent reaches ResolveAndCommitChildAttachment directly, and if it arrives before its parent spawns, RetryChildrenWaitingForParent(childGuid) fires on that spawn. No transitive descent is needed for this host. The new ChildrenUnresolvedForParent (ParentAttachmentState.cs:642-663) scopes the sweep correctly and its doc states the narrower claim honestly ("This is a narrower claim than '0 B'").
R9 CLOSED. The C3c-R1 F6 summary is back on FirstEntryDriveServesOneRouteAtATimeAndScopesClearToTheOwner (:422-430); the D5 tests carry their own.
R10 CLOSED. §1(b').
R11 CLOSED in substance, incomplete in its stated proof. See N7.

Contract corrections — all three landed and are accurate: §0 item 1 now carries the part_array guard correction with the "the contract causes the defect" attribution (:75-87); §2's two gate rows are marked UNVERIFIABLE with the BN always-false-lowering explanation and the note that D7/D6 do not depend on them (:236-237); test 10's sabotage instruction now requires two runs with round 1's failure recorded as the cautionary example (:724-736).


5. New findings (round 2)

N1 — MINOR — AP-142 clause (d) overstates the semantic gap between retail's part_array and acdream's HasPartArray

File: docs/architecture/retail-divergence-register.md:172. Retail address: CPhysicsObj::makeAnimObject @0x0050e930.

The row says acdream's flag "means 'the renderer built a mesh,' not retail's 'this CPhysicsObj has ANY part array.'" Retail's flag has exactly one assignment site:

0050e930  int32_t CPhysicsObj::makeAnimObject(CPhysicsObj* this, IDClass<_tagDataID,32,0> arg2, int32_t arg3)
0050e93e      class CPartArray* eax = CPartArray::CreateSetup(this, arg2, arg3);
0050e94d      this->part_array = eax;

part_array is the product of building the object's parts from its Setup — i.e. retail's flag also means "the client built this object's parts". The two are near-synonyms, not a broad/narrow pair. The real reason acdream cannot gate on it is a layering commitment, not a semantic mismatch: Slice J made the canonical Runtime layer presentation-independent by design, so the only place the flag can be set is App, and headless has no part-construction step at all.

The row's operative claim and its conclusion survive intact. The framing should be corrected so a future reader does not conclude retail's guard was looser than it is — that misreading would make the divergence look smaller than it is.

N2 — MINOR — clause (d)'s risk column scopes the divergence as headless-only; the graphical host has the same window

File: docs/architecture/retail-divergence-register.md:172 (risk column); src/AcDream.App/Rendering/EquippedChildRenderController.cs:609 vs :876-880.

The risk column argues (d) is "a headless-only concern, since the graphical HasPartArray gate is already implicitly satisfied by the time TickChild can run — a child's own WorldEntity/mesh must exist for TickChild to reach the rebucket call at all." That is true of the rebucket and false of the canonical write, which is what clause (d) is about. The graphical realize order is:

PrepareAndTryRealize: CommitStagedParent → CommitProjection
                    → CommitAcceptedParentCellless   ← D1 writes the child's cell HERE
                    → WithdrawPriorProjection → TryRealize
                                                 └─ :609  childRecord.HasPartArray = true

So the graphical host also cells a child before its part-array flag is true. Retail has no equivalent window: a CPhysicsObj's part_array is built at object creation (makeAnimObject), long before any set_parent @0x00515A90 can run. The window is bounded (nothing reads the child's cell between those two calls in the same synchronous realize) and inert, but the scoping sentence is wrong as written and should say "predominantly headless, plus a bounded graphical realize-ordering window".

N3 — MINOR — same-commit stale line citations in both new register rows

  • AP-142 cites EquippedChildRenderController.cs:591 for the HasPartArray writer; it is at :609.
  • AP-143 cites :897-898 (self-parent) and :902 (HasPartArray) in ValidateParentProjection; they are at :915-916 and :920.

Both are off by the +18 lines this same commit's D4 edit inserted above them — i.e. the citations were written against the pre-fix file and not re-checked after. Contract process rule 6 ("trust the symbol") makes them recoverable, and the symbols are named, so this is cosmetic. It is listed because "verify every comment the fix touched" is a standing rule and these were touched by the fix.

N4 — MINOR — clause (e)'s depth-cap residue is the shape clause (a) declares unacceptable, and is strictly worse on the withdraw path

File: docs/architecture/retail-divergence-register.md:172 (clause e); src/AcDream.Runtime/Entities/RuntimeEntityDirectory.cs:349-357/:389-397; tests/AcDream.Runtime.Tests/Entities/RuntimeEntityChildCellPropagationTests.cs:430-486.

Clause (a) says acdream deliberately refuses to reproduce retail's stale-nonzero-cell-under-a-dead-object residue, because a stale nonzero FullCellId reads as "resident" to every acdream predicate. Clause (e)'s cap re-introduces precisely that residue past 64 levels — the shipped test asserts it directly (Assert.Equal(originalCell, tail.FullCellId)).

On a crossing that is a stale-but-plausible cell (cosmetic). On a withdraw (SetFullCell(x, 0, 0)) the tail keeps a nonzero cell forever, which is the #184 invisible-but-solid shape verbatim. Clause (e) discloses "left at its PRIOR cell rather than partially propagated", so nothing is hidden — but the (a)/(e) interaction is not noted, and the withdraw case is the one worth a sentence. The architecture review's suggested iterative worklist would retire both.

N5 — MINOR — the NotAttached test's third assertion cannot distinguish decline from a same-destination move

File: tests/AcDream.App.Tests/Rendering/EquippedChildProjectionWithdrawalTests.cs:404-447.

The comment says "the draw bucket did NOT move, because the guard correctly declined (NotAttached) and RebucketLiveEntityPresentationOnly was never called", and the assertion is Assert.Contains(CopyLiveEntitiesNearLandblock(oldLandblock), child). In that fixture the parent's ParentCellId is never changed, so a rebucket that did run would target the child's current bucket and the assertion would pass identically. It cannot fail either way.

The test's load-bearing assertions — the tick still counted a pose-composition visit and the child is still in AttachedEntityIds (i.e. it was not treated as pose loss) — are sound and do pin the fork's benign branch. Only the third comment overclaims what its assertion demonstrates. Same class as round 1's R2, one severity lower. Moving the parent to a new cell first would make it real.

N6 — MINOR — the NoProjection branch is a new withdrawal trigger with no test

File: src/AcDream.App/Rendering/EquippedChildRenderController.cs:418-424.

TickChild now returns false on NoProjection, which routes the child into WithdrawForPoseLoss via Tick()'s failed list — a control-flow edge that did not exist before this slice (the old RebucketLiveEntity call's result was never consulted). Round 2 added a test for Moved and one for NotAttached; NoProjection has none. It is the branch with real consequences, and it is reachable only via RebucketLiveEntityPresentationOnly returning false (a displaced projection operation) — exactly the kind of narrow path that regresses silently.

N7 — MINOR — R11's "proven, not merely assumed" enumerates two of three PhysicsBody constructors

File: src/AcDream.App/World/LiveEntityRuntime.cs:1113-1124 (the R11 <para>).

The doc argues: a committed child's Snapshot.Position is always null, and "the ONLY production PhysicsBody constructors reachable from a live projection (DatLiveEntityProjectionMaterializer's static scheduler, ProjectileController) both require a non-null spawn.Position". Repo-wide grep for GetOrCreatePhysicsBody returns a third production constructor: src/AcDream.Runtime/Entities/RuntimeRemoteFirstEntryState.cs:425, which builds from lease.InitialCreate.Physics — and a committed child can hold an initial-create lease (that is the whole dormant-residence machinery).

This matters slightly more than a bookkeeping nit because RuntimeEntityRecord.SuspendObjectClock (RuntimeEntityRecord.cs:94-98) does not synchronise a body's TransientStateFlags.Active bit — it only deactivates the clock — so if a committed child ever did hold a body, the dropped SynchronizePhysicsBodyActiveState would be a real loss rather than a no-op. The conclusion is still very likely correct (first-entry admission should require a placement position, which a parented child never has), but I did not resolve that third path and am flagging it rather than guessing. One sentence covering RuntimeRemoteFirstEntryState would convert this back to a genuine proof. This is the same defect shape as the architecture review's A3 (an enumeration argued against an incomplete set).


6. Verification performed

  • Re-read CPhysicsObj::makeAnimObject @0x0050e930 in docs/research/named-retail/acclient_2013_pseudo_c.txt (N1's basis) and re-confirmed enter_cell @0x00510f03 / leave_cell @0x00510f5b for R10's correction.
  • Independent repo-wide grep of HasPartArray (§1a) and GetOrCreatePhysicsBody (N7).
  • Read GpuWorldState.CopyLiveEntitiesNearLandblock and the ControllerFixture to confirm the D4 test queries a real spatial index (§2).
  • Traced TickChildRebucketEquippedChildPresentationRebucketLiveEntityPresentationOnly for the disposition fork's reachability (§3).
  • dotnet build AcDream.slnx -c Debug → 0 errors.
  • AcDream.Runtime.Tests filtered to ChildCellPropagation + RuntimeLiveEntitySessionControllerTests22 passed / 0 failed.
  • AcDream.App.Tests filtered to EquippedChildProjectionWithdrawalTests28 passed / 0 failed.
  • Contract §0 item 1, §2 rows, and §6 test 10 re-read against the code they describe.
  1. N1 + N2: two sentences in AP-142 clause (d) — correct the part_array semantics with the @0x0050e930 anchor, and re-scope the risk column to include the bounded graphical realize-ordering window.
  2. N3: fix the four line citations (:591:609, :897-898:915-916, :902:920).
  3. N7: one sentence covering RuntimeRemoteFirstEntryState.cs:425, or drop the word "proven".
  4. N4: one clause noting that (e)'s residue on the withdraw path is (a)'s rejected shape.
  5. N5: move the parent's cell in the NotAttached test, or soften its comment.
  6. N6: a NoProjection test, whenever the withdrawal path is next touched.