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>
This commit is contained in:
Erik 2026-08-04 23:53:05 +02:00
parent 19ebf043e3
commit cd3129e9d6
24 changed files with 4500 additions and 105 deletions

View file

@ -403,9 +403,52 @@ public sealed class EquippedChildRenderController : IDisposable
child.Entity.ParentCellId = parent.ParentCellId;
CaptureParentPresentation(child, parent);
PublishChildPose(child.Entity, parentWorld, parent.ParentCellId, pose);
// C4 route 7 D4: the canonical cell is Runtime's D1/D2
// propagation write, not this render tick's job any more — move
// only the graphical draw bucket to match. The disposition is
// consumed explicitly (review A2/R3, route 5's A1 class): a
// silently-discarded bool must not advance presentation on a
// write-nothing outcome.
if (TryResolveExactAttachment(child, out parent)
&& parent.ParentCellId is { } parentCellId)
_liveEntities.RebucketLiveEntity(child.ChildGuid, parentCellId);
{
EquippedChildPresentationRebucketDisposition disposition =
_liveEntities.RebucketEquippedChildPresentation(
child.ChildGuid,
parentCellId);
if (disposition is
EquippedChildPresentationRebucketDisposition.NoProjection)
{
// Defensive: as this call site is structured today,
// this branch cannot actually fire. TryResolveExactAttachment
// (just above, and again at the top of this method) requires
// _liveEntities.IsCurrentRecord(child.ChildRecord), which is
// the exact same _projections.TryGetCurrent(guid) lookup
// RebucketEquippedChildPresentation's own NoProjection guard
// performs for the same guid one call later — if the
// projection were gone, TryResolveExactAttachment would
// already have failed and this method would already have
// returned false above, never reaching here (round-3 review
// N6; verified by sabotage — see
// RebucketEquippedChildPresentation_D4_NoLiveProjection_ReturnsNoProjectionDisposition
// in EquippedChildProjectionWithdrawalTests.cs, which tests
// the disposition value directly rather than through this
// unreachable path). Kept as a fail-safe in case a future
// refactor reorders or removes the TryResolveExactAttachment
// gate above.
return false;
}
// Moved: normal case. NotAttached: the known-benign
// in-flight unparent/pending-residence window —
// OnChildBecameUnparented (or the residence conductor) owns
// the child's fate. Displaced (B6, round-3 review): a newer
// operation already superseded this exact rebucket attempt
// mid-flight, so the record is current and healthy, just not
// via THIS call — not evidence of a problem. Both leave the
// bucket wherever the more current operation put it, and
// this tick still reports success because the pose itself
// composed and published correctly.
}
ProjectionPoseReady?.Invoke(child.ChildGuid);
return true;
}

View file

@ -42,6 +42,44 @@ internal enum LiveEntityMaterializationResidence
AwaitRuntimePlacement,
}
/// <summary>
/// C4 route 7 A2/R3 remediation:
/// <see cref="LiveEntityRuntime.RebucketEquippedChildPresentation"/>'s typed
/// outcome. <see cref="Moved"/> is the ordinary case. <see cref="NotAttached"/>
/// is the KNOWN-benign decline — the child's Runtime relation already unwound
/// (pickup/Position-unparent, or an active initial-create residence) while
/// <c>EquippedChildRenderController._attachedByChild</c> still holds it, ahead
/// of <c>OnChildBecameUnparented</c>'s own teardown — callers must not treat it
/// as success.
///
/// <para>
/// Round-3 review (B6): <see cref="NoProjection"/> and <see cref="Displaced"/>
/// were originally ONE case and are now split, because they mean different
/// things and warrant different caller behavior. <see cref="NoProjection"/> —
/// no <c>LiveEntityRecord</c>/<c>WorldEntity</c> exists for this guid AT ALL
/// despite <c>_attachedByChild</c> holding the key — is NOT expected and is
/// treated as a genuine pose-loss failure. <see cref="Displaced"/> — the
/// spatial rebucket STARTED but a re-entrant caller superseded this exact
/// projection operation before it finished (<c>IsCurrentProjectionOperation</c>
/// went false mid-call, inside <c>RebucketLiveEntityPresentationOnly</c>) —
/// means the record was current a moment ago and some OTHER, newer operation
/// already committed a different outcome for it. Unlike
/// <see cref="NoProjection"/>, this is treated as BENIGN, the same as
/// <see cref="NotAttached"/> (see <c>TickChild</c>): a superseded operation
/// is not evidence anything is wrong with the entity, only that a fresher
/// write already ran — tearing it down as pose loss would act on a stale
/// snapshot against a projection a newer, presumably-valid operation just
/// updated.
/// </para>
/// </summary>
internal enum EquippedChildPresentationRebucketDisposition
{
Moved,
NotAttached,
NoProjection,
Displaced,
}
/// <summary>
/// Logical-resource seam coordinated by <see cref="LiveEntityRuntime"/>.
/// Spatial bucketing is deliberately absent: registering or removing meshes,
@ -978,12 +1016,14 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
/// <summary>
/// C3c: the graphical-bucket-only projection of a conductor-owned
/// initial placement — called ONLY from
/// initial placement — called from
/// <see cref="TryApplyInitialCreateCompletionPresentation"/> (the
/// completion receipt at the initial-create residence boundary), never
/// from the public <see cref="RebucketLiveEntity"/> (C3c-R1 review R2:
/// post-residence moves take the full legacy branch there).
/// Deliberately never calls <c>CommitRebucket</c>,
/// completion receipt at the initial-create residence boundary) and,
/// since C4 route 7 D4, from <see cref="RebucketEquippedChildPresentation"/>
/// (an attached child, whose canonical cell Runtime's D1/D2 propagation
/// already owns). Never from the public <see cref="RebucketLiveEntity"/>
/// (C3c-R1 review R2: post-residence moves take the full legacy branch
/// there). Deliberately never calls <c>CommitRebucket</c>,
/// <c>SuspendObjectClock</c>, or <c>ResetObjectClockForEnterWorld</c> —
/// Runtime's SetPosition commit already owns all of those for the
/// residence-driven placement this receipt projects. May place into a
@ -1064,6 +1104,91 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return true;
}
/// <summary>
/// C4 route 7 D4: the equipped-child render-tick presentation-only
/// rebucket. Runtime's D1/D2 propagation
/// (<see cref="RuntimeEntityDirectory.SetFullCell"/>) is now the
/// canonical writer for a committed child's cell — the render tick only
/// needs to move the DRAW bucket to match. Deliberately the same C3c
/// shape as <see cref="RebucketLiveEntityPresentationOnly"/>: no
/// <c>CommitRebucket</c>, no clock edges, because a committed child's
/// <c>ObjectClock</c> stays suspended for its whole attached lifetime
/// (retail <c>update_object</c>'s <c>parent != 0</c> early-out,
/// @0x00515D40) and Runtime already owns the canonical spatial
/// authority. Guarded to a record with a currently COMMITTED parent
/// relation, AND (review A9) the same active-initial-create-residence
/// gate <see cref="RebucketLiveEntity"/> itself honours, so this can
/// never become a general bypass of the legacy branch for an ordinary
/// root entity. Returns a typed disposition rather than a bare bool
/// (review A2/R3) — <see cref="EquippedChildPresentationRebucketDisposition.NotAttached"/>
/// is the KNOWN-benign case (the Runtime relation already unwound while
/// <c>_attachedByChild</c> still holds the child, ahead of
/// <c>OnChildBecameUnparented</c>'s own teardown); the caller must not
/// discard the result.
///
/// <para>
/// R11 (retail-conformance review, LOW; DOWNGRADED at the round-3
/// review, N7 — an earlier draft of this note overstated "proven"):
/// unlike the legacy <see cref="RebucketLiveEntity"/>, this path never
/// calls <c>SynchronizePhysicsBodyActiveState</c>. VERY LIKELY inert,
/// not fully proven: a committed child's <c>Snapshot.Position</c> is
/// always null (contract §0 item 10 / P1), and two of the production
/// <c>PhysicsBody</c> constructors reachable from a live projection
/// (<c>DatLiveEntityProjectionMaterializer</c>'s static scheduler,
/// <c>ProjectileController</c>) both require a non-null
/// <c>spawn.Position</c> before calling <c>GetOrCreatePhysicsBody</c>,
/// ruling them out for a committed child. A THIRD constructor —
/// <c>RuntimeRemoteFirstEntryState.cs:425</c>, the remote first-entry
/// conductor's own body construction for an entity's OWN
/// initial-create residence — is NOT ruled out with the same rigor: it
/// does not gate on <c>spawn.Position</c>, and a committed child CAN
/// hold an initial-create residence lease during route 7's dormant-
/// residence deferral window (invariant 9). The argument for inertness
/// there is that <c>CommitAcceptedParentCellless</c> calls
/// <c>ForgetInitialCreateResidence</c> before this method's caller
/// (<c>TickChild</c>) can ever run — by the time a child has an
/// <c>AttachedChild</c> entry at all, its residence should already be
/// cancelled — but this is NOT independently traced end-to-end against
/// <see cref="RuntimeRemoteFirstEntryState"/>'s own state machine, and
/// <c>SuspendObjectClock</c> does not itself synchronize a body's
/// Active transient bit, so IF a body existed in that window the
/// dropped call would not be a no-op. Flagged honestly as unverified
/// rather than asserted; the correct fix if this is ever found reachable
/// is to add the call back or route the synchronization through the
/// residence-cancellation edge that already runs first.
/// </para>
/// </summary>
internal EquippedChildPresentationRebucketDisposition RebucketEquippedChildPresentation(
uint serverGuid,
uint parentCellId)
{
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| record.WorldEntity is not { } entity)
{
return EquippedChildPresentationRebucketDisposition.NoProjection;
}
if (!_directory.ParentAttachments.HasCommittedParent(serverGuid))
return EquippedChildPresentationRebucketDisposition.NotAttached;
if (record.MaterializationResidence is
LiveEntityMaterializationResidence.AwaitRuntimePlacement
&& HasActiveInitialCreateResidence(record.Canonical))
{
return EquippedChildPresentationRebucketDisposition.NotAttached;
}
// B6 (round-3 review): a `false` here ALWAYS means the projection
// operation was superseded mid-flight (RebucketLiveEntityPresentationOnly's
// only `return false` sites are both guarded by
// `!IsCurrentProjectionOperation`) — the record was current when
// this method started, so this is Displaced, never NoProjection.
return RebucketLiveEntityPresentationOnly(
serverGuid,
record,
entity,
parentCellId)
? EquippedChildPresentationRebucketDisposition.Moved
: EquippedChildPresentationRebucketDisposition.Displaced;
}
/// <summary>
/// C3c: applies one initial-Create ExecutorCompleted receipt's
/// presentation — the graphical binding point for a residence-driven
@ -2340,6 +2465,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
/// after the parent accepted the child (PartArray + holding-location
/// validation). The POSITION_TS is consumed before this step, matching
/// retail; invalid parent relationships retain the old world projection.
/// Since C4 route 7 D1, this also completes retail's second half — if
/// the committed parent is celled, the child is re-celled to the
/// parent's exact values in the SAME transaction (see
/// <see cref="RuntimeEntityObjectLifetime.CommitAcceptedParentCellless"/>).
/// A cell-less parent leaves the child cell-less, exactly as before,
/// now by the retail-cited gate instead of by omission; a later parent
/// cell commit re-cells the child through D2's propagation.
/// </summary>
internal bool CommitAcceptedParentCellless(
LiveEntityRecord record,

View file

@ -61,6 +61,20 @@ public static class PhysicsDiagnostics
public static bool ProbeCellEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL") == "1";
/// <summary>
/// C4 route 7 (pickup/parent/delete) connected-gate confirmation
/// signal — TEMPORARY, part of the existing probe family. When true,
/// one <c>[child-cell]</c> line is emitted per Runtime committed-child
/// canonical cell write: parent guid, child guid, old and new cell,
/// and a cause tag (<c>attach</c> / <c>headless-attach</c> /
/// <c>propagate</c> / <c>withdraw</c> / <c>delete</c>). A clean-looking
/// session with zero <c>cause=propagate</c> lines during a landblock
/// crossing is a not-run, not a pass. Initial state from
/// <c>ACDREAM_PROBE_CHILD_CELL=1</c>.
/// </summary>
public static bool ProbeChildCellEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CHILD_CELL") == "1";
/// <summary>
/// Issue #309's connected-gate confirmation signal (2026-08-04, C4 route
/// 4b-2). The gate's quiescence steps need a ForcePosition to land INSIDE

View file

@ -516,6 +516,31 @@ public sealed class ParentAttachmentState
public bool HasCommittedParent(uint childGuid) =>
_lastAcceptedByChild.ContainsKey(childGuid);
/// <summary>
/// Route 7 D1/D5: the exact (guid, incarnation) a child's committed
/// relation currently names, read straight from the committed table
/// rather than the staged/recovery timing window - the source the
/// attach-time re-cell and the headless drive both resolve the parent
/// through, per the contract's "currency, never by guid alone" pin.
/// </summary>
public bool TryGetCommittedParent(
uint childGuid,
out uint parentGuid,
out ushort parentInstanceSequence)
{
if (_lastAcceptedByChild.TryGetValue(
childGuid,
out ParentAttachmentRelation relation))
{
parentGuid = relation.ParentGuid;
parentInstanceSequence = relation.ParentInstanceSequence;
return true;
}
parentGuid = 0u;
parentInstanceSequence = 0;
return false;
}
public bool IsPending(
ParentAttachmentRelation relation,
ParentProjectionCandidateKind kind) =>
@ -614,6 +639,39 @@ public sealed class ParentAttachmentState
return result.ToArray();
}
/// <summary>
/// Route 7 D5/A6 (architecture review): a lighter-weight sibling of
/// <see cref="ChildrenWaitingForParent"/> for a caller that only needs
/// to discover a child whose relation is still UNRESOLVED (waiting on
/// this exact parent guid to become addressable) — the headless
/// <c>RuntimeLiveEntitySessionController</c> retry, whose own
/// <c>Resolve</c>/<c>TryGetStagedProjection</c>/<c>CommitProjection</c>
/// sequence handles a relation already sitting in
/// <c>_stagedByChild</c>/<c>_recoveryByChild</c> when it re-resolves a
/// specific child directly, so this query does not need to sweep those
/// two dictionaries or allocate the <c>HashSet</c>
/// <see cref="ChildrenWaitingForParent"/> uses to de-duplicate across
/// all three. Allocates a fresh <see cref="List{T}"/> only when it has
/// something to return (round-3 review, B4): a shared reused buffer was
/// tried and reverted — it broke reentrancy safety a fresh-array return
/// already had, since a nested call into the same query while an outer
/// caller was still iterating its result would clear/refill the SAME
/// list out from under it.
/// </summary>
public IReadOnlyList<uint> ChildrenUnresolvedForParent(uint parentGuid)
{
List<uint>? result = null;
foreach ((uint childGuid, Queue<ParentAttachmentRelation> queue) in _unresolvedByChild)
{
if (queue.Any(relation => relation.ParentGuid == parentGuid))
{
result ??= new List<uint>();
result.Add(childGuid);
}
}
return (IReadOnlyList<uint>?)result ?? Array.Empty<uint>();
}
/// <summary>
/// Returns only the exact direct children currently committed to one
/// parent incarnation. Lost-cell destruction follows retail's live

View file

@ -234,8 +234,16 @@ public sealed class RuntimeEntityDirectory
bool refreshPosition = false)
{
EnsureKnown(record);
uint previousCell = record.FullCellId;
record.Snapshot = accepted;
record.RefreshDerivedState(refreshPosition);
if (record.FullCellId != previousCell)
{
PropagateFullCellToChildren(
record,
record.FullCellId,
record.CanonicalLandblockId);
}
}
public void AdvanceCreateAuthority(RuntimeEntityRecord record)
@ -344,6 +352,157 @@ public sealed class RuntimeEntityDirectory
{
EnsureKnown(record);
record.SetFullCell(fullCellId, canonicalLandblockId);
PropagateFullCellToChildren(record, fullCellId, canonicalLandblockId);
}
/// <summary>
/// Reused across every <see cref="PropagateFullCellToChildren"/> call.
/// Always empty on entry and on exit (the loop drains it unconditionally
/// before returning) — see that method's remarks for why sharing it is
/// safe rather than a repeat of the round-3 A6/B4 scratch-buffer
/// reentrancy lesson: this stack never escapes the method, and nothing
/// on the write path can trigger a nested call while it holds state.
/// </summary>
private readonly Stack<RuntimeEntityRecord> _propagationWorklist = new();
/// <summary>
/// C4 route 7 D2 — the sole propagation hook. Retail's parent-cell-
/// crossing propagation (<c>CPhysicsObj::SetPositionInternal</c>
/// @0x00515330's changed-cell branch @0x00515372 -&gt; <c>change_cell</c>
/// @0x00513390 -&gt; the self-recursive <c>enter_cell</c>/<c>leave_cell</c>
/// @0x00510ed0/@0x00510f50) has exactly one acdream analogue: every
/// canonical cell write already funnels through this method or
/// <see cref="RefreshSnapshot"/>'s derived-state write (see
/// docs/research/2026-08-04-retail-parent-cell-propagation.md and the
/// route-7 contract's D2).
///
/// <para>
/// ITERATIVE, not recursive (round-3 remediation — both the retail and
/// architecture reviews independently converged on the same finding,
/// N4/B3: a depth-capped RECURSIVE version left a subtree beyond the cap
/// at a STALE NONZERO cell permanently — on the withdraw path that is
/// the #184 shape verbatim, the exact defect AP-142 clause (a) exists to
/// reject, shipped fresh inside the slice whose headline is fixing it.
/// A reused <see cref="_propagationWorklist"/> stack removes the depth
/// concept entirely rather than mitigating it: there is no C# call-stack
/// growth to bound, so the only limit is the number of committed
/// relations actually in the system (which cannot exceed the live
/// entity count) — matching retail's own genuinely unbounded recursion
/// exactly, with no acdream-only cap and no register row for one. This
/// also retires B5 in the sense that there is no longer a RECURSIVE call
/// to reason about — but the bypass itself is NOT retired: the loop
/// below still calls a child's own <c>SetFullCell</c> directly rather
/// than the public <see cref="SetFullCell"/>, and that bypass is
/// DELIBERATE and LOAD-BEARING, not incidental. The public
/// <see cref="SetFullCell"/> calls this method, and this method opens
/// with <c>_propagationWorklist.Clear()</c> — routing a child through
/// the public method would re-enter this method mid-drain, clear the
/// shared worklist out from under the OUTER loop, and silently drop
/// every sibling still waiting on the stack, with no error and no
/// exception. Any side effect added to the public
/// <see cref="SetFullCell"/> in the future MUST be mirrored by hand at
/// this call site, because this call site cannot route through it.
/// </para>
///
/// <para>
/// Skipping a child whose <see cref="RuntimeEntityRecord.FullCellId"/>
/// (and <see cref="RuntimeEntityRecord.CanonicalLandblockId"/>, A7)
/// already equals the target is BOTH the cycle guard (an A→B→A wire-
/// induced relation cycle writes A, pushes B, writes B, pops B, finds A
/// already at the target value, and does not re-push it — no visited
/// set needed) and the retail-behavioral subsumption of retail's
/// separate same-cell depth-1 id refresh (@0x0051539c-@0x005153d8)
/// under this project's single-field cell model (AP-142). Zero
/// allocation after warmup:
/// <see cref="ParentAttachmentState.ChildrenAttachedToParent"/> returns
/// the stored list or <see cref="Array.Empty{T}"/>, and the worklist
/// stack is a reused field, never a fresh collection per call. Field
/// writes only — no clock, workset, shadow, or placement work — so this
/// is safe to run re-entrantly inside whatever transaction is mid-commit
/// on the parent's own cell; the worklist field itself is safe to share
/// across calls because the loop below unconditionally drains it to
/// empty before this method returns, and nothing on the write path
/// (a field assignment and an optional diagnostic log line) can call
/// back into a nested <see cref="SetFullCell"/>. NOT gated on retail's
/// <c>part_array != 0</c> guard (@0x00510ed8) — see AP-142 clause (d)
/// for why that guard has no reproducible analogue at acdream's
/// canonical layer.
///
/// <para>
/// P8 (contract proof obligation, corrected at the architecture review
/// round — A3): this step deliberately publishes NO per-child
/// <c>RuntimeEntityChange.Rebucketed</c> delta, matching
/// <c>RuntimePhysicsState.CommitCanonicalCell</c>'s precedent. The
/// correct basis for that decision is BOTH observer interfaces, not
/// just <c>IRuntimeEntityObjectObserver</c>'s direct implementations:
/// <c>GameRuntimeEventHub</c> itself implements
/// <c>IRuntimeEntityObjectObserver</c> and fans every entity delta out
/// to <c>IRuntimeEventObserver</c>, and <c>RuntimeTraceRecorder.OnEntity</c>
/// (<c>GameRuntimeEvents.cs</c>) is a non-stub shipped consumer that
/// records <c>(delta.Change, delta.Entity.CellId)</c> for every entity
/// delta — it is diagnostic tracing, not gameplay logic, and the
/// entries it would lose are graphical-only equipped-child
/// <c>Rebucketed</c> deltas that TickChild's demoted rebucket used to
/// produce, so silence here is acceptable, but "no consumer at all" is
/// false and must not be restated that way.
/// </para>
///
/// <para>
/// P4 (contract proof obligation — the child broadphase story, stated):
/// this step writes canonical cell FIELDS only. At attach, the
/// preceding cell-less edge already force-ends collision reporting
/// (<c>Physics.CollisionReports.LeaveWorld</c>) and no child broadphase
/// registration exists to rebuild — retail's own
/// <c>recalc_cross_cells</c> @0x00515A30 runs at attach only, never
/// per-crossing (the §0 trap this hook deliberately does not port). At
/// every crossing thereafter, a committed child never becomes a spatial
/// root (<c>LiveEntityRuntime.HasSpatialRuntimeProjection</c> keys off
/// <c>ProjectionKind is World</c>, and an attached child is always
/// <c>ProjectionKind.Attached</c> — <c>AcknowledgeSpatialProjection</c>
/// is never called on this path, headless or graphical), so it never
/// joins any physics workset or shadow/cross-cell list this step would
/// need to maintain. Confirmed, not assumed: zero shadow work is
/// performed anywhere in this method.
/// </para>
/// </summary>
private void PropagateFullCellToChildren(
RuntimeEntityRecord root,
uint fullCellId,
uint canonicalLandblockId)
{
_propagationWorklist.Clear();
_propagationWorklist.Push(root);
while (_propagationWorklist.Count > 0)
{
RuntimeEntityRecord current = _propagationWorklist.Pop();
IReadOnlyList<uint> children = ParentAttachments.ChildrenAttachedToParent(
current.ServerGuid,
current.Incarnation);
for (int i = 0; i < children.Count; i++)
{
// A7 (architecture review, LOW): key the idempotence/cycle
// skip on the PAIR, not FullCellId alone. Every production
// writer derives canonicalLandblockId from fullCellId, so
// the two are coupled today, but the coupling is not
// enforced anywhere (LiveEntityRuntime.CanonicalLandblockId's
// setter can write a same-cell, different-landblock pair) —
// testing the pair is the version that is correct
// independent of that coupling.
if (!TryGetActive(children[i], out RuntimeEntityRecord child)
|| (child.FullCellId == fullCellId
&& child.CanonicalLandblockId == canonicalLandblockId))
{
continue;
}
if (PhysicsDiagnostics.ProbeChildCellEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[child-cell] parent=0x{current.ServerGuid:X8} child=0x{child.ServerGuid:X8} old=0x{child.FullCellId:X8} new=0x{fullCellId:X8} cause={(fullCellId == 0u ? "withdraw" : "propagate")}"));
}
child.SetFullCell(fullCellId, canonicalLandblockId);
_propagationWorklist.Push(child);
}
}
}
public void SetFinalPhysicsState(

View file

@ -997,6 +997,15 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
if (result.Disposition
is CreateObjectTimestampDisposition.NewGeneration)
{
// D3 (route 7): the replacement-generation path has the same
// stranding shape as delete - EndGeneration tears down the
// OLD generation's committed-children edge with no cell work.
if (prior is not null)
{
WithdrawCommittedChildrenToCellless(
prior.ServerGuid,
prior.Incarnation);
}
Entities.ParentAttachments.EndGeneration(
incoming.Guid,
result.Snapshot.InstanceSequence);
@ -1292,6 +1301,10 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
Entities.AdvancePositionAuthority(canonical);
// D7 (route 7): retail order is unset_parent @0x0045227F THEN
// leave_world @0x00452286 (SmartBox::DoPickupEvent). This was
// inverted; EndChildProjection now runs first.
Entities.ParentAttachments.EndChildProjection(update.Guid);
Physics.CollisionReports.LeaveWorld(canonical);
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
@ -1299,7 +1312,6 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
PreferCancellation(initialCancellation, ordinaryCancellation);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
Entities.ParentAttachments.EndChildProjection(update.Guid);
ulong positionVersion = canonical.PositionAuthorityVersion;
ulong spatialVersion = canonical.SpatialAuthorityVersion;
return AcknowledgeProjectionAndPublish(
@ -1440,6 +1452,54 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
cancellation);
}
/// <summary>
/// D3 (route 7): retail DeleteObject/set_parent's replacement-
/// generation shape both null a subtree's children while they are
/// still attached - acdream's relation teardown
/// (<see cref="ParentAttachmentState.DeleteGeneration"/>/
/// <see cref="ParentAttachmentState.EndGeneration"/>) removes the
/// committed-children edge outright with no cell work of its own, so
/// callers run this first, through the SAME D2 write path
/// (<see cref="RuntimeEntityDirectory.SetFullCell"/>, recursive by
/// construction) - acdream's single-field cell model propagates zero
/// rather than reproducing retail's stale-`objcell_id`-under-a-null-
/// pointer residue (AP-142).
/// </summary>
private void WithdrawCommittedChildrenToCellless(
uint parentGuid,
ushort parentInstanceSequence)
{
IReadOnlyList<uint> children = Entities.ParentAttachments.ChildrenAttachedToParent(
parentGuid,
parentInstanceSequence);
for (int i = 0; i < children.Count; i++)
{
if (!Entities.TryGetActive(children[i], out RuntimeEntityRecord child))
continue;
if (PhysicsDiagnostics.ProbeChildCellEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[child-cell] parent=0x{parentGuid:X8} child=0x{child.ServerGuid:X8} old=0x{child.FullCellId:X8} new=0x00000000 cause=delete"));
}
Entities.SetFullCell(child, 0u, 0u);
}
}
/// <summary>
/// Route 7 D1 (architecture review A4): the published
/// <see cref="RuntimeEntityChange.Withdrawn"/> delta's payload contract
/// changed under this slice. Before D1 existed, a <c>Withdrawn</c> from
/// this method always carried <c>FullCellId == 0</c>. It now carries
/// the PARENT's exact cell whenever the parent is celled at attach —
/// i.e. in the ordinary equip case — because D1's re-cell runs before
/// this method's <see cref="AcknowledgeProjectionAndPublish"/> call, in
/// the SAME synchronous transaction (so no caller ever observes the
/// child cell-less in between). This is deliberate: the kind still
/// correctly says "this projection was withdrawn and must be
/// re-realized," and the payload's cell is the child's ACTUAL resulting
/// cell, not a stale zero. Pinned by
/// <c>Attach_ParentCelled_ChildEndsAtParentCellAndStaysSuspended</c>.
/// </summary>
public bool CommitAcceptedParentCellless(
RuntimeEntityRecord canonical,
ulong positionAuthorityVersion,
@ -1462,6 +1522,31 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Physics.CollisionReports.LeaveWorld(canonical);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
// D1 (route 7): retail set_parent's second half, run on the SAME
// synchronous transaction so no caller observes the child
// cell-less between the leave-world edge and the re-cell.
// @0x00515AC6 parent = ...; @0x00515AD1 if (parent->cell != 0)
// @0x00515AD6 change_cell(this, parent->cell). The committed
// relation (not the guid alone) resolves the parent so a stale or
// superseded incarnation can never re-cell the child.
if (Entities.ParentAttachments.TryGetCommittedParent(
canonical.ServerGuid,
out uint parentGuid,
out ushort parentInstanceSequence)
&& Entities.TryGetActive(parentGuid, out RuntimeEntityRecord parent)
&& parent.Incarnation == parentInstanceSequence
&& parent.FullCellId != 0u)
{
if (PhysicsDiagnostics.ProbeChildCellEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[child-cell] parent=0x{parentGuid:X8} child=0x{canonical.ServerGuid:X8} old=0x00000000 new=0x{parent.FullCellId:X8} cause=attach"));
}
Entities.SetFullCell(
canonical,
parent.FullCellId,
parent.CanonicalLandblockId);
}
ulong spatialVersion = canonical.SpatialAuthorityVersion;
return AcknowledgeProjectionAndPublish(
canonical,
@ -1993,6 +2078,13 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
Entities.AdvanceLifetimeMutation(delete.Guid);
// D3 (route 7): retail's DeleteObject nulls a subtree's children
// while they are still attached (leave_world @0x00508472 runs
// BEFORE unparent_children @0x005084B9) - DeleteGeneration below
// performs no cell work of its own, so run the children's
// leave-world edge first or a deleted parent's children are
// stranded at a stale nonzero cell (the #184 shape).
WithdrawCommittedChildrenToCellless(delete.Guid, delete.InstanceSequence);
Entities.ParentAttachments.DeleteGeneration(
delete.Guid,
delete.InstanceSequence);

View file

@ -2181,12 +2181,18 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
// must not tear down the residence mid-drain: only ordinary
// SetPosition.Forget runs here, never ForgetInitialCreateResidence.
_entities.AdvancePositionAuthority(canonical);
// D7 (route 7, architecture review A5): retail order is
// unset_parent @0x0045227F THEN leave_world @0x00452286
// (SmartBox::DoPickupEvent) - the same reorder
// RuntimeEntityObjectLifetime.TryApplyPickup applies to the live
// pickup path, applied here to the DORMANT replay of the same wire
// event so both pickup paths are one shape.
_entities.ParentAttachments.EndChildProjection(canonical.ServerGuid);
_physics.CollisionReports.LeaveWorld(canonical);
RuntimePlacementCancellationReceipt cancellation =
_physics.SetPosition.Forget(canonical);
_entities.SuspendObjectClock(canonical);
_entities.SetFullCell(canonical, 0u, 0u);
_entities.ParentAttachments.EndChildProjection(canonical.ServerGuid);
// Round 3 B2: mutate -> rebaseline -> publish. Round 4 R4-4:
// AdvancePositionAuthority + SetFullCell(0,0) move
// PositionAuthorityVersion and FullCellId, the only two of the

View file

@ -28,13 +28,6 @@ internal enum RuntimeAcceptedPositionSource : byte
SameIncarnationCreate,
}
internal enum RuntimeLeaveWorldCause : byte
{
Unknown,
Pickup,
Parent,
}
internal enum RuntimeAuthoritativePositionDisposition : byte
{
RejectedAuthority,
@ -138,12 +131,6 @@ internal readonly record struct RuntimeAcceptedPositionRouteRequest(
bool HasAnimations,
RuntimePositionPlacementFacts PlacementFacts);
internal readonly record struct RuntimeLeaveWorldRouteRequest(
RuntimeAuthoritativePositionAuthority Authority,
RuntimePositionEntityKind EntityKind,
RuntimeLeaveWorldCause Cause,
RuntimePositionPlacementFacts PlacementFacts);
/// <summary>
/// Immutable action plan for retail HandleReceivedPosition/MoveOrTeleport.
/// It deliberately contains no renderer, world entity, UI, or host callback.
@ -477,38 +464,6 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
reporting);
}
internal static RuntimeAuthoritativePositionRoute ClassifyLeaveWorld(
in RuntimeLeaveWorldRouteRequest request)
{
RuntimeSetPositionOperationKind operation = OperationKind(
request.EntityKind,
initialCreate: false);
bool reporting = request.PlacementFacts.CollisionBatchEligible;
if (!ValidCreateAuthority(request.Authority)
|| !ValidEntityKind(request.EntityKind)
|| request.Cause is RuntimeLeaveWorldCause.Unknown)
{
return RejectedAuthority(request.Authority, operation, reporting);
}
return new RuntimeAuthoritativePositionRoute(
request.Authority,
RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
operation,
PhysicsSetPositionFlags.None,
0u,
UnparentBeforeRouting: false,
ApplyPlacementFrameBeforeRouting: false,
LeaveWorld: true,
TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false,
ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false,
ZeroVelocity: false,
SendPositionImmediately: false,
reporting);
}
private static bool ValidCreateAuthority(
in RuntimeAuthoritativePositionAuthority authority) =>
authority.IsStructurallyValid

View file

@ -4536,11 +4536,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
/// read from that same record field. It is NOT invariant for a RETAINED
/// operation: both drives re-submit from their own cadence pump with no
/// fresh merge in between (<c>SubmitAndResolve</c> re-reads the record as
/// it then stands). The surviving non-Position rebucket writers (C4
/// it then stands). The surviving non-Position rebucket writer (C4
/// route 4b-3 deleted the third, <c>RemoteTeleportController</c>'s
/// rollback) are the projection materializer
/// (<c>DatLiveEntityProjectionMaterializer</c>) and the equipped-child
/// renderer (<c>EquippedChildRenderController.TickChild</c>).
/// rollback; C4 route 7 D4 demoted the equipped-child renderer's
/// <c>EquippedChildRenderController.TickChild</c> to a presentation-only
/// bucket move — Runtime's own D1/D2 propagation is the child's
/// canonical writer now, so it is no longer in this list) is the
/// projection materializer (<c>DatLiveEntityProjectionMaterializer</c>).
/// <c>RuntimeRemotePlacementDriveController</c>'s
/// <c>CanAttemptDestination</c> doc states this correctly; treat the arm
/// as live, not as dead code.

View file

@ -65,6 +65,13 @@ public sealed class RuntimeLiveEntitySessionController
/// </summary>
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
private bool _initialLoginCompleteSent;
// A6 (architecture review): D5's ResolveAndCommitChildAttachment ran on
// every accepted spawn and every ParentEvent, allocating three
// this-capturing closures per call. These capture nothing per-call
// (only `this`), so cache them once instead of per invocation.
private readonly Func<uint, bool> _isChildGuidKnown;
private readonly Func<uint, ushort?> _resolveParentInstance;
private readonly Func<ParentEvent.Parsed, bool> _acceptParentEvent;
public RuntimeLiveEntitySessionController(
GameRuntime runtime,
@ -78,6 +85,15 @@ public sealed class RuntimeLiveEntitySessionController
_log = log ?? (_ => { });
_worldProjection = worldProjection;
_acceptedPositionDrive = acceptedPositionDrive;
_isChildGuidKnown = guid => Entities.Entities.TryGetSnapshot(guid, out _);
_resolveParentInstance = guid =>
Entities.Entities.TryGetSnapshot(guid, out WorldSession.EntitySpawn spawn)
? spawn.InstanceSequence
: null;
_acceptParentEvent = candidate => Entities.TryApplyParent(
candidate,
acknowledgeProjection: null,
out _);
}
public LiveEntitySessionSink CreateSink() => new(
@ -138,6 +154,9 @@ public sealed class RuntimeLiveEntitySessionController
canonical,
canonical.ServerGuid
== _runtime.PlayerIdentity.ServerGuid);
// D5: this spawn may be the parent a standalone ParentEvent
// already named before its own CreateObject arrived.
RetryChildrenWaitingForParent(canonical.ServerGuid);
if (_worldProjection is null
&& canonical.ServerGuid
== _runtime.PlayerIdentity.ServerGuid
@ -309,11 +328,146 @@ public sealed class RuntimeLiveEntitySessionController
out _,
out _);
private void OnParentUpdated(ParentEvent.Parsed update) =>
_ = Entities.TryApplyParent(
update,
acknowledgeProjection: null,
out _);
private void OnParentUpdated(ParentEvent.Parsed update)
{
Entities.Entities.ParentAttachments.Enqueue(update);
ResolveAndCommitChildAttachment(update.ChildGuid);
}
/// <summary>
/// C4 route 7 D5: the headless parent-realize drive. Resolves a queued
/// standalone <see cref="ParentEvent.Parsed"/> through the SAME staged
/// -&gt; committed protocol the graphical
/// <c>EquippedChildRenderController.ResolveAndTryRealize</c> /
/// <c>PrepareAndTryRealize</c> pair runs —
/// <see cref="ParentAttachmentState.Resolve"/>, then
/// <see cref="RuntimeEntityObjectLifetime.TryCommitParent"/> -&gt;
/// <see cref="ParentAttachmentState.CommitProjection"/> -&gt;
/// <see cref="RuntimeEntityObjectLifetime.CommitAcceptedParentCellless"/>
/// (which carries D1's attach re-cell) — so a direct/no-window host
/// gets the same canonical child-cell commit the graphical host has
/// always had. Deliberately does NOT drive pose composition, the
/// render bucket, or <c>ValidateParentProjection</c>'s self-parenting
/// / part-array / <c>Setup.HoldingLocations</c> checks — see AP-143.
///
/// <para>
/// KNOWN GAP, stated rather than silently left implicit (retail-
/// conformance review R6): if <paramref name="childGuid"/> has a
/// PENDING initial-create residence when the relation resolves to
/// staged, <see cref="RuntimeEntityObjectLifetime.TryCommitParent"/>'s
/// gate (<c>InboundPhysicsStateController.TryCommitParent</c>'s
/// <c>gate.PositionTimestamp == positionSequence</c> check) is not yet
/// satisfied and this method returns <see langword="false"/>. Nothing
/// re-drives it: the residence executor's own parent-attach tail
/// (<c>RuntimeInitialCreateContinuationExecutor.CommitParentAttachment</c>)
/// deliberately does not commit the relation either — that has always
/// been the graphical host's job — and headless has no
/// <c>EquippedChildRenderController</c>-equivalent post-drain retry.
/// The relation stays staged and the child stays cell-less until SOME
/// other event re-invokes <see cref="ResolveAndCommitChildAttachment"/>
/// for the same child (a later ParentEvent, or a spawn naming the same
/// parent guid via <see cref="RetryChildrenWaitingForParent"/> — which
/// does not cover this case either, since the relation is already
/// staged, not unresolved). Not a regression (headless committed
/// nothing on this path before D5 existed), and the invariant-9
/// dormant-residence deferrals themselves are untouched — but a
/// headless ParentEvent arriving during a child's own pending initial
/// residence is NOT closed by this slice.
/// </para>
/// </summary>
private bool ResolveAndCommitChildAttachment(uint childGuid)
{
ParentAttachmentState relations = Entities.Entities.ParentAttachments;
relations.Resolve(
childGuid,
_isChildGuidKnown,
_resolveParentInstance,
_acceptParentEvent);
if (!relations.TryGetStagedProjection(
childGuid,
out ParentAttachmentRelation staged))
{
return false;
}
if (!Entities.Entities.TryGetActive(
childGuid,
out RuntimeEntityRecord canonical))
{
return false;
}
ulong positionAuthorityVersion = canonical.PositionAuthorityVersion;
if (!Entities.TryCommitParent(staged, acknowledgeProjection: null, out _)
|| !relations.CommitProjection(staged))
{
return false;
}
bool committed = Entities.CommitAcceptedParentCellless(
canonical,
positionAuthorityVersion,
acknowledgeProjection: null);
if (committed && PhysicsDiagnostics.ProbeChildCellEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[child-cell] parent=0x{staged.ParentGuid:X8} child=0x{canonical.ServerGuid:X8} new=0x{canonical.FullCellId:X8} cause=headless-attach"));
}
return committed;
}
/// <summary>
/// D5 companion: a ParentEvent can precede the parent's own CreateObject
/// (retail: the standalone parent handler queues by parent guid). Retry
/// every child waiting on the guid that just became addressable.
///
/// <para>
/// A6 (architecture review): unlike the graphical
/// <c>EquippedChildRenderController.RetryWaitingDescendants</c> →
/// <c>ParentAttachmentState.ChildrenWaitingForParent</c>, this drive's
/// OWN <c>Resolve</c>/<c>TryGetStagedProjection</c>/<c>CommitProjection</c>
/// sequence in <see cref="ResolveAndCommitChildAttachment"/> consumes a
/// relation out of <c>_stagedByChild</c> within the SAME synchronous
/// call it was staged in, for the ordinary case. **Correction (B1/B2,
/// round-3 review): this is NOT an absolute "never populates
/// _stagedByChild" claim** — the R6 gap documented on
/// <see cref="ResolveAndCommitChildAttachment"/> is exactly the
/// counter-example: a relation CAN be left sitting in
/// <c>_stagedByChild</c> across calls when the child has a pending
/// initial-create residence, because <c>TryCommitParent</c>'s gate
/// isn't satisfied yet. What is true is narrower: THIS retry method
/// never needs <c>ChildrenWaitingForParent</c>'s STAGED/RECOVERY sweeps
/// to find that dangling relation, because it re-resolves through
/// <c>childGuid</c> directly via <c>ResolveAndCommitChildAttachment</c>
/// on every retry rather than needing a separate discovery query for
/// already-staged children — only the UNRESOLVED sweep matters for
/// discovering a NEW parent guid becoming addressable. Scanning the
/// shared (heavier) <c>ChildrenWaitingForParent</c> on every accepted
/// headless spawn would pay for two sweeps and a <c>HashSet</c>
/// allocation this discovery step never needs;
/// <c>ChildrenUnresolvedForParent</c> scans only
/// <c>_unresolvedByChild</c>. **Correction (B4, round-3 review): the
/// first round shared a reused scratch buffer across calls for this
/// query, which broke reentrancy safety a fresh-array return had —
/// a reentrant call into this method (or into
/// <see cref="ResolveAndCommitChildAttachment"/>'s loop below) could
/// clear/refill the SAME shared list the outer call was still
/// iterating. Reverted to a fresh return per call, matching
/// <c>ChildrenWaitingForParent</c>'s own allocation shape**, since this
/// query already only allocates when it has something to return (most
/// parent guids have no unresolved children waiting on them). This is
/// a narrower claim than "0 B" either way — the per-child
/// <c>queue.Any(lambda)</c> predicate check still allocates a closure
/// per call, same as the pre-existing graphical sweep; a full
/// incremental parent-guid-&gt;children index would remove both
/// allocations and is not done in this slice.
/// </para>
/// </summary>
private void RetryChildrenWaitingForParent(uint parentGuid)
{
IReadOnlyList<uint> waiting = Entities.Entities.ParentAttachments
.ChildrenUnresolvedForParent(parentGuid);
for (int i = 0; i < waiting.Count; i++)
ResolveAndCommitChildAttachment(waiting[i]);
}
private void OnTeleportStarted(uint rawSequence)
{

View file

@ -1613,12 +1613,14 @@ internal sealed class RuntimeRemotePlacementDriveController
/// <para>
/// <see cref="Advance"/> re-reads this predicate and is subject to the
/// same two gaps, plus a third: a non-Position rebucket (the projection
/// materializer <c>DatLiveEntityProjectionMaterializer</c>, the
/// equipped-child renderer <c>EquippedChildRenderController.TickChild</c>
/// — C4 route 4b-3 deleted the third shipped writer,
/// <c>RemoteTeleportController</c>'s rollback) can move
/// <c>record.FullCellId</c> to a THIRD landblock between the retained
/// submit and the retry. Both remaining writers are
/// materializer <c>DatLiveEntityProjectionMaterializer</c> — C4 route
/// 4b-3 deleted the second shipped writer, <c>RemoteTeleportController</c>'s
/// rollback, and C4 route 7 D4 demoted the third,
/// <c>EquippedChildRenderController.TickChild</c>, to a presentation-
/// only bucket move that no longer touches <c>record.FullCellId</c> —
/// Runtime's own D1/D2 propagation is the child's canonical writer now)
/// can move <c>record.FullCellId</c> to a THIRD landblock between the
/// retained submit and the retry. The remaining writer is
/// harmless for the same reason (delta review N3). That reason is the
/// paragraph below — NOT, as the round-2 text claimed, that re-reading
/// <c>record.CurrentCellId</c> here would "re-derive a private Core