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 atcff52c44, +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>
24 KiB
C4 route 7 — ARCHITECTURE / ADVERSARIAL review, round 2 (DELTA) — 2026-08-04
Verdict: PASS.
All three round-1 blocking findings (A1, A2, A3) are properly remediated, and each remediation is sabotage-sensitive in the direction that matters. Seven new findings (B1–B7) are recorded below; none is a correctness defect in shipped behaviour with a demonstrated failure, and each has a one-to-three-line fix. B1 and B3's logging clause are recommended before the connected gate, but neither blocks.
Scope: delta only. Round 1's accepted conclusions — the SetFullCell blast-radius
enumeration, the D2 chokepoint-completeness proof, and the D7 inertness
verification — are not re-litigated. Round-1 report:
2026-08-04-c4-route-7-architecture-review.md.
Gates re-run:
dotnet buildRuntime.Tests + App.Tests — 0 warnings, 0 errors.dotnet test AcDream.Runtime.Tests— 1,157 passed / 0 failed (was 1,156; +1 = the depth-cap test).dotnet test AcDream.App.Tests --filter EquippedChild— 37 passed / 0 failed (was 36; the round-1 D4 test was replaced by two).
1. Round-1 blockers — re-verified
A1 (MAJOR, "the D4 test cannot fail") — FIXED, and the fix is sound.
tests/AcDream.App.Tests/Rendering/EquippedChildProjectionWithdrawalTests.cs:321-393
(TickChild_D4_PresentationBucketMovesToTheDestinationLandblock).
Is it measuring the draw bucket or a proxy? The draw bucket.
fixture.Spatial is a real GpuWorldState (:1443, = new()), and
CopyLiveEntitiesNearLandblock (src/AcDream.App/Streaming/GpuWorldState.cs:493-523)
reads _loadedLiveByLandblock — the per-landblock HashSet<WorldEntity> the
renderer's live-entity publication actually maintains. It is not a mirror field
and not a derived view. It also destination.Clear()s at :500, so the
four sequential queries in the test do not contaminate each other (without that
clear, the DoesNotContain assertions would be false-negatives; with it, they
are real).
Can it pass with the demotion removed? No, and I checked both sabotage directions:
- Delete the
RebucketEquippedChildPresentationcall atEquippedChildRenderController.cs:411-425→ the child never leavesoldLandblock, so the post-tickAssert.Contains(newLandblock…)fails. This is the failure the round-1 test could not produce. - Revert to the legacy
RebucketLiveEntity→ the bucket moves (so theContainspasses) butCommitRebucket→RuntimeEntityRecord.SetFullCellbumpsSpatialAuthorityVersionunconditionally (RuntimeEntityRecord.cs:246-251), so the finalAssert.Equal(childSpatialVersionBeforeTick, …)fails.
Both halves of the transfer are therefore pinned in opposite directions. The
test also cannot pass vacuously on an empty query result: the pre-tick
Assert.Contains(oldLandblock…) and the post-tick Assert.Contains(newLandblock…)
are positive assertions on two different landblocks, and a
!_availability.IsWorldAvailable early-return (GpuWorldState.cs:501) would
fail the first one. The implementer's reported sabotage ("stubbing the demoted
call to claim success failed it — empty collection") is consistent with that
structure.
A2 (MAJOR, "silent write-nothing outcome") — FIXED. The fork is the right call; NotAttached-as-benign is safe.
I judged the fork against the alternative I originally suggested, and the implementer's choice is better. Reasoning, done by enumeration rather than by accepting the stated rationale:
NotAttached is returned only when !HasCommittedParent(serverGuid) or when an
initial-create residence is active. The complete set of things that can remove
_lastAcceptedByChild[child] is ParentAttachmentState.RemoveCommittedChild,
reached from exactly six call sites:
| remover | is it an unwind edge? |
|---|---|
CommitProjection (:581) |
re-adds on the next line — transient, unobservable |
EndChildProjection (:753) |
yes — pickup / Position-unparent |
EndGeneration(child) (:709) |
yes — child replaced |
DeleteGeneration(child) (:734) |
yes — child deleted |
RemoveCommittedParentReferences(parent) (:865-873, from EndGeneration/DeleteGeneration on the PARENT) |
yes — parent deleted/replaced |
RemoveObject / RemoveChild (:660, :756) |
zero production callers |
CommitProjection is also the only writer, and
PrepareAndTryRealize always calls it before TryRealize installs the
AttachedChild (EquippedChildRenderController.cs:861-891), with the recovery
branch gated on Relations.IsCommitted. So _attachedByChild non-empty ⇒
committed, unless one of the five genuine unwind edges has fired. There is
no state in which a child is legitimately attached, rendering, and
HasCommittedParent is false. NotAttached therefore cannot swallow a real
pose-loss — it can only fire in a window where a teardown is already in flight
and owns the child's fate.
The parent-deleted case is worth naming because it is the longest window
(_pendingOrphanRemovalByChild defers the withdrawal across frames,
EquippedChildRenderController.cs:342/:584): during it, D3 has already zeroed
the child's canonical cell and the bucket is frozen at the parent's last
landblock. That is unchanged from pre-route-7 behaviour (the old
RebucketLiveEntity would have rebucketed to the same stale
parent.ParentCellId), so it is not a regression.
Routing NoProjection into WithdrawForPoseLoss is also correct for the
_projections.TryGetCurrent failure it is named for — see B6 for the second
condition that shares the label.
A3 (MEDIUM, "P8 enumerated the wrong interface") — FIXED.
RuntimeEntityDirectory.cs's PropagateFullCellToChildren doc now carries a
dedicated <para> naming GameRuntimeEventHub as itself an
IRuntimeEntityObjectObserver that fans out to IRuntimeEventObserver, and
RuntimeTraceRecorder.OnEntity as the real non-stub consumer, and explicitly
retracts "no consumer at all". The decision (publish nothing) is unchanged and
its stated basis is now true. P4's write-up in the same comment also correctly
states the ProjectionKind is World reason for the no-spatial-root claim rather
than the round-1 relation-based reason.
A4, A5, A7, A9, A10 — spot-checked, all correct.
- A4 —
RuntimeEntityObjectLifetime.CommitAcceptedParentCelllesscarries the payload-contract doc, andAttach_ParentCelled_…now subscribes aRecordingEntityObserverand asserts theWithdrawndelta'sEntity.CellId == parent.FullCellIdand!= 0(RuntimeEntityChildCellPropagationTests.cs:34-57). Sabotage-sensitive: moving D1's re-cell after the publish makeswithdrawn.Entity.CellIdzero and fails two assertions. This is a real pin, not a source-text pin. - A5 —
RuntimeInitialCreateContinuationExecutor.cs:2184-2190: the dormant pickup replay now runsEndChildProjectionbeforeLeaveWorld, matchingTryApplyPickup. Both pickup paths are one shape. - A7 — the skip is now keyed on the
(FullCellId, CanonicalLandblockId)pair (RuntimeEntityDirectory.cs:389-395), with the reason stated. Cycle termination is unaffected: A→B→A still writes A, writes B, then finds A's pair already equal and stops (PropagationChokepoint_TerminatesAHostileTwoCycle…still green). - A9 —
RebucketEquippedChildPresentationnow carries the sameMaterializationResidence is AwaitRuntimePlacement && HasActiveInitialCreateResidencegateRebucketLiveEntityhonours (LiveEntityRuntime.cs:1140-1145). - A10 — the C3c-R1 F6 summary is back on
FirstEntryDriveServesOneRouteAtATimeAndScopesClearToTheOwner(RuntimeLiveEntitySessionControllerTests.cs:422-430); no duplicate<summary>remains.
2. New findings
B1 — MEDIUM. The NotAttached test's "the draw bucket did NOT move" assertion is non-discriminating.
Where: tests/AcDream.App.Tests/Rendering/EquippedChildProjectionWithdrawalTests.cs:395-450
(TickChild_D4_NotAttachedDisposition_SkipsTheBucketMoveWithoutFailingTheTick),
final assertion at :445-448.
Unlike its sibling, this test never moves the parent: both entities are spawned
at the fixture's default Cell and no CommitRebucket runs. So
parent.WorldEntity.ParentCellId still names the same landblock the child's
bucket is already in (0x0101FFFF). If the HasCommittedParent guard were
deleted outright, RebucketLiveEntityPresentationOnly would run and rebucket
the child to the landblock it is already in — and
Assert.Contains(buffer, kv => kv.Value == child.WorldEntity) on oldLandblock
would still pass.
Concrete failure scenario: delete the HasCommittedParent check at
LiveEntityRuntime.cs:1137-1138. The guard A2 exists to establish is gone; the
enum collapses to Moved/NoProjection; this test stays green.
The test's load-bearing half is sound — LastFullPoseCompositionVisits +
AttachedEntityIds genuinely pin "the tick was not torn down as a pose loss",
which is the fork judgement A2 asked for, and that half does fail if
NotAttached were routed to return false. Only the bucket clause is
decorative.
Fix direction: mirror the sibling — register the second landblock, drive
CommitRebucket(parent, newCell, newLandblock) before the tick, and assert the
child is still in oldLandblock afterwards. Then the assertion discriminates.
B2 — MEDIUM. A6's justification comment contradicts the R6 gap comment two methods away, and the narrowing removed a (weak) recovery path.
Where: src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs:414-433
(RetryChildrenWaitingForParent's A6 <para>) vs :346-370
(ResolveAndCommitChildAttachment's R6 KNOWN-GAP <para>).
The A6 comment justifies scanning only _unresolvedByChild on the grounds that
"this drive never populates _stagedByChild/_recoveryByChild through any path
those two sweeps exist to catch". That is false: ParentAttachmentState.Resolve
sets _stagedByChild[childGuid] on accept (:472-475), and the R6 gap
documented two methods above is precisely a relation left staged-but-uncommitted
because TryCommitParent's POSITION_TS gate was not satisfied during the child's
pending initial-create residence.
Concrete failure scenario: a headless ParentEvent arrives while the child's
initial residence is pending. Resolve stages it; TryCommitParent refuses; the
relation sits in _stagedByChild forever. Under round 1's
ChildrenWaitingForParent (which unioned all three tables) a later spawn naming
the same parent guid would have re-driven it; under ChildrenUnresolvedForParent
nothing will. Weak in practice — it needs a new parent generation to fire — but
the narrowing strictly reduced recovery, and the comment claims it did not.
The R6 gap itself is disclosed honestly and I accept it as an out-of-scope
residual (headless committed nothing on this path before D5 existed). The
finding is the contradiction, plus the consequence for the contract §7 headless
gate: its scenario is "the local player equips via the bot command surface and
crosses a boundary". If the equip lands during the item's own residence, the gate
produces no cause=headless-attach line and must be read as a not-run, not a
pass — the same rule the contract already states for cause=propagate.
Fix direction: correct the A6 <para> to say what is actually true (this
drive promotes unresolved relations; a staged-but-uncommitted relation is the R6
gap and is deliberately not retried here), and cross-reference the R6 paragraph.
No code change required.
B3 — MEDIUM. The depth cap's failure mode contradicts AP-142 clause (a), is unrecoverable, and is invisible without the probe flag.
Where: src/AcDream.Runtime/Entities/RuntimeEntityDirectory.cs:349-357
(the constant) and :433-441 (the early return).
Three separate points, in descending importance:
- The failure mode is the shape the model exists to avoid. Past depth 64
the tail keeps a stale non-zero
FullCellId. Under acdream's single-field model that reads as "still resident at the old cell" at all 45+ predicate sites — exactly the#184invisible-but-solid shape that AP-142 clause (a) cites as the reason the removal path propagates zero rather than reproducing retail's stale-objcell_idresidue. The cap therefore reintroduces, as its chosen failure mode, the residue the row rejects. Zeroing the truncated subtree instead ("we cannot maintain this, so it is not resident") is both consistent with the model and strictly safer. - It is unrecoverable. Every subsequent crossing truncates at the same depth, so a subtree that falls past the cap once never re-syncs. And because the skip is value-idempotent, a later same-value write prunes even earlier (the asymmetry AP-142 clause (b)'s R10 correction already records). A permanently-stale resident subtree is worse than a transiently-stale one.
- It is silent in production.
[child-cell-depth-exceeded]is emitted only underPhysicsDiagnostics.ProbeChildCellEnabled, i.e. only when someone has already setACDREAM_PROBE_CHILD_CELL=1. A defensive guard whose premise is "this must never happen" should be unconditionally observable — a one-shot log or a counter on the ownership ledger — or nobody will ever learn it fired.
Is 64 defensible? Yes, as a number. Real chains are 2–3 deep (player → weapon; quiver → arrow). 64 is ~20× any plausible depth and is well inside a 1 MB stack for a two-frame-per-level recursion. Is the cap the right mechanism? An iterative worklist over a pre-sized scratch would remove the hazard entirely, keep 0 B after warmup, and need no cap, no divergence row, and no failure mode — that is the answer I would take if this is revisited. But the cap is a strict improvement over round 1's uncatchable process kill, so it is not a blocker.
Fix direction (cheapest first): (i) make the past-cap log unconditional; (ii) zero the truncated subtree rather than leaving it stale, and amend AP-142 clause (e) to say so; (iii) if revisited, replace the recursion with an iterative worklist and retire clause (e).
B4 — LOW. The reused scratch buffer is not re-entrancy-safe; round 1's fresh array was.
Where: src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs:73
(_unresolvedChildrenScratch), :434-440 (filled, then iterated while calling
ResolveAndCommitChildAttachment).
ChildrenUnresolvedForParent clears and refills the caller's buffer
(ParentAttachmentState.cs:657-660). RetryChildrenWaitingForParent then
iterates that same instance while calling into
ResolveAndCommitChildAttachment → TryCommitParent /
CommitAcceptedParentCellless → AcknowledgeProjectionAndPublish → synchronous
observer fan-out through GameRuntimeEventHub.
Concrete failure scenario: an IRuntimeEventObserver synchronously feeds a
spawn back into the sink → nested OnSpawned → nested
RetryChildrenWaitingForParent → the shared buffer is cleared and refilled →
the outer loop's waiting.Count and indices now address the inner call's
contents, so children are skipped or retried twice. Requires an observer that
re-enters the sink; the shipped headless policies are empty stubs, so this is
latent, not live. The graphical sibling it mirrors
(ChildrenWaitingForParent → .ToArray()) is re-entrancy-safe by allocation,
and round 1's version inherited that safety; the A6 optimization traded it away
without a guard.
Fix direction: a _retryInProgress flag that falls back to a fresh List
when re-entered, or state the single-entry precondition in the doc and assert it.
B5 — LOW. The recursion no longer routes children through the public SetFullCell, creating a silent divergence trap.
Where: src/AcDream.Runtime/Entities/RuntimeEntityDirectory.cs:409-414 —
propagation now calls child.SetFullCell(...) (the record method) plus a
direct recursive call, where round 1 re-entered RuntimeEntityDirectory.SetFullCell.
The two are equivalent today — the public method is exactly
EnsureKnown + record.SetFullCell + propagate, and EnsureKnown is a
validation-only throw (:812-820) already subsumed by the preceding
TryGetActive. The change was needed to thread depth.
Concrete failure scenario: anyone later adding a side effect to
RuntimeEntityDirectory.SetFullCell — an index update, a delta publish, a
telemetry counter — gets it for the root and silently not for propagated
children, reintroducing exactly the "mapping written against one caller's
reachable set" class D2 was designed to eliminate. Nothing in the code says the
equivalence is load-bearing.
Fix direction: give the public method a private depth-carrying overload and
have the recursion call that, so there is one body; or add one comment line at
:409 naming the equivalence as an invariant to maintain.
B6 — LOW. NoProjection conflates two different outcomes, and one of them now triggers a teardown that round 1 did not.
Where: src/AcDream.App/World/LiveEntityRuntime.cs:1131-1152. The enum doc
(:45-56) defines NoProjection as "the graphical projection itself is gone",
but :1147-1152 also returns it when RebucketLiveEntityPresentationOnly
returns false — which means the projection operation was displaced by a
re-entrant callback (IsCurrentProjectionOperation at :1029/:1054), i.e. a
newer rebucket took over. TickChild then returns false →
Tick()'s failed list → WithdrawForPoseLoss.
Concrete failure scenario: a re-entrant observer starts a newer rebucket for
the same child mid-_spatial.RebucketLiveEntity; the older tick concludes
"projection gone" and withdraws the child's projection that the newer operation
just legitimately re-established. Low reachability — the
_presentationOnlySpatialMutationDepth guard (:3354-3360) already suppresses
the most likely re-entrant rebucket source — but round 1 discarded the bool, so
this teardown is new behaviour introduced by the A2 fix.
Fix direction: either split the displaced case into its own disposition
(Superseded, treated as benign — the newer operation owns the bucket), or
document at :1147 that displacement is deliberately treated as pose loss and
why.
B7 — INFO. One other test derives a built-collection size from a live production constant.
Asked directly, so answered directly: I swept tests/ for the shape that caused
the implementer's 64,000-node stack overflow (a test that both reads a
production constant and sizes work by it). Exactly one other match with real
blast radius:
tests/AcDream.Core.Tests/World/LandblockLoaderTests.cs:210 —
for (uint i = 0; i <= LandblockStaticEntityIdAllocator.MaxCounter + 1u; i++)
builds one Stab per iteration. MaxCounter = 0xFFF
(src/AcDream.Core/World/LandblockStaticEntityIdAllocator.cs:16), so 4,097 cheap
allocations today. A sabotage raising it by orders of magnitude would OOM or hang
rather than stack-overflow, and the test's Assert.Contains("4096-entry", …)
literal pins the constant — but only after the build loop, so the pin does not
protect the loop. Milder than the route-7 case and pre-existing; noted, not
filed against this slice.
The other three matches are benign: LightManagerTests (MaxGlobalLights = 128,
+50 iterations of trivial work) and the two CapacityTrimIdleFrames frame loops.
The route-7 depth test's own remaining constant read
(RuntimeEntityChildCellPropagationTests.cs:473,
for (i = 0; i <= MaxPropagationDepth; i++)) is correctly protected: the
literal pin Assert.Equal(64, RuntimeEntityDirectory.MaxPropagationDepth) at
:441 fires first, and the chain length at :442 is a literal. The boundary is
pinned in both directions — a cap of 70 would give index 69 the new cell and fail
the tail assertion; a cap of 60 would leave indices 61–64 stale and fail the loop.
3. Direct answers to the coordinator's remaining questions
The WarmedSteadyContactRefreshDoesNotAllocate flake — #302 class. Not this slice.
Stated definitely rather than hedged, because it is provable by reachability:
- The measured window
(
RuntimeCollisionReportingStateTests.cs:2375-2379) contains onlylifetime.Physics.HandleSetPositionCollisions(...). Entity registration and shadow setup happen beforeGC.GetAllocatedBytesForCurrentThread()is sampled. src/AcDream.Runtime/Physics/RuntimeCollisionReportingState.cscontains zero occurrences ofSetFullCelland zero ofParentAttachments(grepped). Its only_entitiesinteractions are read-only queries (TryGetByLocalId,IsCurrent,SessionLifetimeVersion,CurrentLifetimeMutation) plusStopMissileAfterCollision, which mutatesFinalPhysicsState, not the cell.StopMissileAfterCollisionis not in the completeSetFullCellcaller set enumerated in round 1 §0 and re-verified against this diff.
So PropagateFullCellToChildren is not reachable from the measured call at all,
and the fixture has no committed parent relations in any case. A
GC.GetAllocatedBytesForCurrentThread() == 0 assertion that passes in isolation
and on re-run but fails once under full-suite load is the documented #302
shape (tiered-JIT / first-touch allocation on the measuring thread), and should
be treated as such: re-run and name it, never chase it. The instinct to look
rather than auto-dismiss was right — the answer is that the new code is not on
that path.
Point 6 — deliberately-not-done items: my "not blocking alone" assessment holds.
- Writer-family coverage for
CommitCanonicalCell/RuntimeSetPositionState. Still covered by construction, and round 2 did not weaken that: both funnel through the identicalRuntimeEntityDirectory.SetFullCell:359-366line thatCommitRebucket(tested) andRefreshSnapshot(tested) use, and round 1's chokepoint-completeness proof (RefreshDerivedStatehas exactly two callers;RuntimeEntityRecord.SetFullCellexactly two) is unchanged by this diff. Two of the four writer families are tested end-to-end; the other two are the same three lines of code. - Fuller P2 across N crossings —
NeverArmPartition_D8_…already drives five crossings; the clock/suspension assertions live in the attach and pickup tests. Thin, not absent. - Dedicated P5/P6 tests — still argued by construction, and the construction
argument is still valid after round 2:
PropagateFullCellToChildrenremains field-writes + one dictionary probe +TryGetActive, with no callback, no LINQ, no closure, andChildrenAttachedToParentstill returning the storedList<uint>or the cachedArray.Empty<uint>(). The round-2 restructuring (directchild.SetFullCellinstead of re-entering the directory method) makes the path cheaper, not richer. B4 is the one place round 2 introduced new re-entrancy surface, and it is in D5's retry, not the propagation.
4. Recommended before the connected gate (non-blocking)
- B1 — make the
NotAttachedtest's bucket assertion discriminate (move the parent first). Two lines. - B3 clause 3 — make the past-cap log unconditional. One line.
- B2 — correct the A6 justification comment so it does not contradict the R6
gap paragraph, and carry the "no
cause=headless-attachline ⇒ not-run" reading into the headless gate step. Comment only.
The connected two-client gate remains unrun and is still the acceptance test
for the D4 transfer, with the contract's rule intact: a session showing zero
cause=propagate lines during the landblock-crossing step is a not-run, not a
pass. The headless gate now carries the same caveat for cause=headless-attach
(B2).