acdream/docs/research/2026-08-02-collision-throughput-handoff/implementer-progress.md
Erik 01f4791e95 fix(streaming): stop replaying committed recenter retirements
Root cause: pending-only live projection buckets were misclassified as landblock presentation owners during origin recentering. That manufactured a second full cleanup receipt for a generation whose first receipt was still advancing; the duplicate guard threw and the broad retry path replayed the already-committed detach 243 times.

Keep pending live projections through the spatial identity map without issuing another receipt, and fail fast when a receipt-ledger invariant occurs after detachment. Evidence: docs/research/2026-08-02-collision-throughput-handoff/p1-retirement-receipt-loop.md. Release suite, lifecycle gate, and nine-stop soak pass.
2026-08-02 20:53:11 +02:00

280 KiB

Implementer progress — Runtime initial-placement continuation executor

Reading phase (complete)

  • Read contract, runtime-surface.md, retail-notes.md in full.
  • Read source: RuntimeInitialCreateResidenceState.cs (full, 960 lines), InboundPhysicsStateController.cs (full, 903 lines), RuntimeAuthoritativePositionRouteClassifier.cs (full, 580 lines), RuntimeEntityObjectLifetime.cs (full, 2134 lines), RuntimeEntityDirectory.cs (full), RuntimeEntityRecord.cs (full), ParentAttachmentState.cs (full), RuntimeInitialCreateAdmissionFreezer.cs (full), RuntimeSetPositionState.cs (targeted: struct defs, CaptureOwnership, Begin*/Watch/IsCurrent/TryPeek/Consume/Forget*, PrepareMover/Submit/ Apply/AcknowledgeProjection/PublishCancellation/Forget/LeaveWorld), RuntimeInitialCreateResidenceStateTests.cs (full, 2657 lines - harness helpers: Bind/Spawn/AttachDormantBody/Prepare/ApplyQueuedPosition/ PositionUpdate/ApplyFreshSuccessor/ConvergeSessionClear/PositionAction/ SetCompletedAdoptionRevision/EntityObserver/PlacementObserver).

Key design decisions made during reading

  1. AdoptCompletedPlacement resolves runtime-surface.md 3.1 deadlock: BeginAcceptedPlacementCore/TryBeginExclusiveAuthoredPlacement all reject while HasRetainedCompletion(key) is true. Executor must call ConsumeAcknowledgedPlacement directly (via new AdoptCompletedPlacement on residence state) BEFORE any Position continuation can begin its own placement.
  2. Movement timestamp reconstruction: AcceptedPhysicsTimestamps has ServerControlledMove but NOT Movement itself. Reasoned from the "MOVEMENT_TS consumed before discovering SERVER_CONTROLLED_MOVE_TS stale" comment: HasTimestampMutation==true always implies gate.MovementTimestamp advanced to update.MovementSequence (movement gate checked first). So ApplyAcceptedMotion sets MovementSequence=update.MovementSequence unconditionally when retained, ServerControlSequence=retained AcceptedTimestamps.ServerControlledMove exactly. Judgment call - documented in code comment.
  3. MirrorGateTimestamps (reads LIVE gate) must NOT be used by the executor (would stomp other channels' Timestamps fields with the wrong values from out-of-band-drift live gate). Refactored ApplyAcceptedMotion to a targeted field update instead - verified behaviorally identical for the legacy path since gate/snapshot stay in lockstep there.
  4. Circular-ownership constraint: deferred-child replay needs RegisterEntityWithInitialResidence (internal core with beginInitialResidence semantics). Contract forbids passing RuntimeEntityObjectLifetime itself. Resolution: lifetime passes a bound delegate (WorldSession.EntitySpawn, bool) -> RuntimeEntityRegistrationResult into the executor's ctor, mirroring the existing retirePriorProjection callback pattern already used in RegisterEntityCore. Narrow single-purpose seam, not a reference to the owning type.
  5. Publish choke point: lifetime passes bound delegates for PublishEntity and AcknowledgeProjectionAndPublish (private methods -> delegate via method group, same pattern as existing callback threading).

Implementation phase - STARTING NOW

Next: refactor InboundPhysicsStateController.cs to extract ApplyAccepted* methods shared by legacy TryApply* and the new executor.

Implementation progress (continued)

  • InboundPhysicsStateController.cs refactored: ApplyAccepted{ObjDesc,Pickup, CreateParent,Parent,Vector,State,Motion,Position} methods extracted, legacy TryApply* re-expressed as gate+shared-apply. Removed now-unused MirrorGateTimestamps. Build clean, 89-test focused gate green after refactor. FOUND+FIXED a bug during this pass: ApplyAcceptedMotion's retainPayload:false branch must ALSO stamp nested Physics.Timestamps.Movement/ServerControlledMove (not just top-level fields) - two pre-existing tests (RejectedServerControlStillMirrorsConsumedMovementTimestamp, AutonomousLocalEchoRetainsPayloadButMirrorsAcceptedTimestamps) caught this; fixed, full 829-test Runtime suite green again.
  • RuntimeInitialCreateResidenceState.cs: added CompletedEntry.PlacementAdopted, AdoptCompletedPlacement, ConsumeExecuted + RuntimeInitialCreateResidenceExecutorReleaseStatus enum. Updated IsCompletedCurrent to treat PlacementAdopted as satisfying the placement- current check without re-querying RuntimeSetPositionState (since adoption already consumed/removed that tracking entry). Updated AcknowledgeAdoption to tolerate (skip) re-consuming an already-adopted placement. Build clean, 48/48 residence tests green.
  • New file src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs written (full algorithm: reentrancy latch, per-key Progress with LeaseId staleness discard, initial tail (adopt+hook+deferred-child replay), FIFO drain for all 8 continuation kinds incl. SameIncarnationCreate envelope staging with buffered publish, Position classify-at-execution-time + placement Begin/Watch/yield/resume lifecycle, ConsumeExecuted release with Revised-loop). Constructed in all 3 RuntimeEntityObjectLifetime ctors, wired into BindEventContext, CaptureOwnership (new InitialCreateExecutorProgressCount field), and the residence Forget/Clear choke points (deterministic progress discard, not lazy). Build clean, full 829-test baseline still green after wiring.

Judgment calls made (to report)

  1. AdoptCompletedPlacement/ConsumeExecuted new API design (see code comments in RuntimeInitialCreateResidenceState.cs) - resolves the 3.1 deadlock per contract's exact prescription.
  2. Registration callback threaded as a bound delegate (WorldSession.EntitySpawn, bool) -> RuntimeEntityRegistrationResult from the lifetime's ctor, NOT a reference to RuntimeEntityObjectLifetime itself
    • satisfies "no circular ownership" per contract while still reaching RegisterEntityWithInitialResidence for deferred-child replay.
  3. Position continuation Rejected/RejectedData routes reuse InboundPhysicsStateController.ApplyAcceptedPosition by passing PositionTimestampDisposition.Rejected explicitly (that method internally routes to the timestamp-only-stamp branch) - avoids a second, divergent "timestamp only" implementation.
  4. HasContact/forcePositionRotation/currentLocalVelocity all derived from canonical.PhysicsBody when attached, falling back to Inputs.HasContact (bodyless tests) - per contract's explicit "decide one way, document it, test it" instruction for HasContact, extended consistently to the other two live-body-derived facts.
  5. ParentAttachmentState.Resolve/CommitProjection has zero callers anywhere in AcDream.Runtime (grep-verified) - confirmed host-driven, not wired. Parent/CreateParent continuation apply commits the position-timestamp-only snapshot mutation + AdvancePositionAuthority/LeaveWorld/Forget/publish only; does NOT invent a second resolve/commit path.
  6. ObjectTableWiring.ApplyEntitySpawn (via RuntimeEntityObjectLifetime. ApplyAcceptedSpawn) has zero callers anywhere in AcDream.Runtime (grep-verified) - WeenieDescription tail action commits RefreshSnapshot+AdvanceCreateAuthority+publish only, explicitly does NOT drive the object table (host cutover work, out of scope).
  7. ResidentCellCleanup implemented as an assert-the-invariant check (claimed+celless must be IsDeferred) rather than building a destruction mechanism; the "no cell claimed, no weenie" destruction-mark branch is left as a recorded no-op (needs live object-table wiring not in scope).
  8. Reentrant Execute() for the same RuntimeEntityKey while one is already on the call stack fails closed via a HashSet latch.
  9. Stale Progress (LeaseId mismatch, e.g. GUID/key reuse) is discarded AND the CURRENT Execute call returns RejectedAuthority (not silently re-created + retried in the same call) - matches contract's literal "discard it and fail closed" wording; caller must retry once more.

Major design fix discovered during test-driven debugging

Found a genuine architecture conflict: RuntimeInitialCreateResidenceState's IsCompletedCurrent staleness check compares LIVE record.PositionAuthorityVersion/ CreateIntegrationVersion/FullCellId/PlacementCommitVersion against values FROZEN at Complete()-time. But the executor's OWN Apply* methods legitimately advance these SAME fields while draining (AdvancePositionAuthority, AdvanceCreateAuthority, SetFullCell, the physics engine's own AdvancePlacementCommit on a continuation's own SetPosition commit) - this made Complete()/ConsumeExecuted incorrectly treat the executor's own controlled progress as an external race and reject with RejectedAuthority. Root-caused via a debug bisection test (temporarily added, then removed).

Fix: added CompletedEntry.Expected{PositionAuthorityVersion, CreateIntegrationVersion,FullCellId,PlacementCommitVersion} - a SEPARATE, executor-maintained baseline distinct from the FROZEN receipt.Token (which must stay byte-identical for Complete()'s token-identity match to keep working across retries). IsCompletedCurrent now compares against entry.Expected* instead of receipt.Token./receipt.FullCellId/ receipt.PlacementCommitVersion directly. New method AdvanceExecutorBaseline(record, token) re-syncs Expected from the record's CURRENT live values; the executor calls it (a) at the top of ExecuteCore's loop before every Complete() call (covers a pending continuation placement's host-driven commit, which happens BETWEEN Execute() calls), and (b) unconditionally after every ApplyContinuation call in the drain loop (covers in-call mutations before a yield). Verified this does NOT weaken the EXISTING admission-slice regression tests (CorruptedPostAcknowledgementAuthorityRetiresProofAndLease etc.) since those tests never call AdvanceExecutorBaseline - an external, non-executor-driven bump to these fields is still correctly detected as stale. All 48 residence tests + 16 new executor tests + 829 baseline pass together (845 total).

Test suite status: 16/16 new tests green, 845/845 total Runtime tests green

Covered: A (2 tests: basic completion + hook; mixed simple continuation order), C (2: envelope atomicity/buffered-publish/stage-order incl. PreTailDescriptionAdaptation+Pickup decomposition; envelope retry non-duplication), D (5: local ordinary interpolate, local teleport placement lifecycle w/ yield+resume, remote near interpolate, remote far SetPositionSimple+StopInterpolating, parented-initial AwaitFreshPosition/no placement), E (2: deferred-child replay consumes exact AdmissionId + registers child through canonical route; stale AdmissionId cannot consume a replacement - via ParentAttachmentState directly), G (2: reentrant Execute for same entity fails closed; reentrant delete of the parent during deferred child replay abandons without resurrection), H (1: stale Progress LeaseId via reflection-planted entry discards + fails closed + clean retry), I (2: reset during AwaitingContinuationPlacement converges every ledger; dispose after successful execution converges IsConverged). NOT separately tested (time-budget / defensibility tradeoffs - noted for final report): F's literal "failure injection between every envelope stage" (only end-to-end retry-after-full-drain covered, not synthetic mid-stage crash injection); J (relies on the existing assembly-level RuntimeDependencyBoundaryTests, which automatically covers the new file - no new file added since the check is assembly-wide, not per-file); the ResidentCellCleanup destruction-mark branches (only the "claimed+resident" and the assert-invariant path are exercised, not the "no cell, no weenie" destruction-mark path, which is explicitly a recorded no-op pending future object-table wiring per code comment); SameIncarnationCreate's Position stage mid-envelope yield+resume (D covers standalone Position yield/resume; the envelope's OWN Position-stage yield path shares the identical ApplyPositionAction code but is not independently exercised by an envelope test with a position stage).

ROUND 2: Reviewer feedback (Finding 1, Gaps 2-4, Finding 5 amendment)

Coordinator sent review findings after round-1 delivery. Working through:

  • FINDING 5 (production regression, addressed FIRST since flagged most urgent): ApplyAcceptedMotion's legacy caller was stamping update.MovementSequence (wire's own stale/rejected value) instead of gate.MovementTimestamp (post-call gate value) for the timestamp-only branch. TryAcceptMovementEvent has 3 rejection flavors and only 1 of them (stale ServerControlledMove) actually advances MOVEMENT_TS; the other two (bad instance, stale MOVEMENT_TS) leave the gate untouched but the OLD buggy code would still stamp the wire's stale value into the snapshot. FIX: re-parameterized ApplyAcceptedMotion to take explicit (movementSequence, acceptedServerControlledMove) inputs instead of deriving movementSequence from update internally - ONE shared body, two explicit-input callers. Legacy caller now passes gate.MovementTimestamp/gate.ServerControlledMoveTimestamp (exact pre-refactor behavior in all 3 flavors). Executor caller passes action.Movement.Value.MovementSequence/action.AcceptedTimestamps.ServerControlledMove (safe there since retention itself gates on genuine acceptance). Added 2 regression tests to InboundPhysicsStateControllerTests.cs comparing the FULL EntitySpawn before/after for (1) stale MOVEMENT_TS, (2) instance mismatch - both assert byte-identical snapshots. 15/15 InboundPhysicsStateControllerTests pass, 64/64 executor+residence tests still pass after the signature change.

  • FINDING 1 fixed: narrowed the pre-Complete() AdvanceExecutorBaseline call in ExecuteCore to only fire when progress.PendingContinuationPlacement.IsValid (the only legitimate between-calls mutation window). Added 2 regression tests (ExternalPositionAuthorityMutation.../ExternalFullCellMutation...) that directly drive Complete()+AdoptCompletedPlacement to the "completed+adopted, no pending placement" state, then externally mutate PositionAuthorityVersion/FullCellId, then assert the NEXT Execute() call observes RejectedAuthority, publishes nothing, and converges. VERIFIED these tests actually catch the regression: temporarily reverted the guard to unconditional, confirmed both tests fail (Completed instead of RejectedAuthority), then restored the fix and confirmed they pass again. 18/18 executor tests pass (16 + 2 new).

  • GAP 4 done: extended RuntimeInitialCreateExecutedAction with a new optional ResidentCellCleanupDisposition field + RuntimeResidentCellCleanupDisposition enum (ResidentUnmarked/ DeferredUnderLostCellOwnership/NoCellClaimedDestructionMarked). ApplyResidentCellCleanup now RETURNS the disposition instead of just asserting. (a) ResidentCellCleanupUnmarksWhenCellClaimedAndAlreadyResident

    • engine-backed, real initial placement, same-create Position stage classifies Interpolate (SameIncarnationCreate source forces effectiveContact=true, entity already resident so not cellless) -> no placement needed, ResidentUnmarked recorded. (b) ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership
    • PickedUp initial (no placement), same-create Position with UsePositionFromServer=false classifies NoPositionOperation (no SetPosition begins at all) -> claimed+celless+not-deferred -> throws InvalidOperationException, verified via Assert.Throws + message content. (c) folded into the EXISTING envelope-atomicity test (added assertion) since that entity already naturally has no claimed cell -> NoCellClaimedDestructionMarked. 20/20 executor tests pass (18 + 2 new; case (c) added to an existing test rather than a new one).
  • GAP 3 done, AND IT CAUGHT A REAL BUG: wrote EnvelopePositionStageRequiringSetPositionYieldsResumesAndPublishesOnceAfterCompletion (cellless -> SetPosition Position stage forces a mid-envelope yield, drive prepare/submit/ack, resume, envelope completes). First run: only 1 of 5 expected Updated events published (expected ObjDesc/Position/State/ Vector/WeenieDescription). ROOT CAUSE: Publish()'s buffered branch stored the PER-STAGE "matches" closure (captured at that stage's OWN commit time, checking one specific AuthorityVersion field) into the buffer - but WeenieDescription's own AdvanceCreateAuthority() call bumps SIX authority-version fields at once (Position/State/Vector/Velocity/ Movement/ObjDesc/CreateIntegration), which invalidated every EARLIER buffered stage's captured version-equality check by the time the final flush ran, even though nothing external raced - it was the executor's OWN later, expected progression. FIX (root cause, not per-callsite patch): Publish()'s buffered branch now stores a constant true predicate instead of the per-field "matches" closure - IsCurrent (checked unconditionally by PublishNow) is the only currency guard a buffered entry needs, since envelope processing dispatches no event until the flush (no reentrancy window mid-envelope except at a Position yield, which is independently guarded by ApplyEnvelope's own IsCurrent check + ResumePendingPlacement). The IMMEDIATE (non-buffered) path is unchanged - still uses the real matches() check, appropriate there since publish happens right after each standalone mutation. 21/21 executor tests pass after the fix (20 + 1 new).

  • GAP 2 done: EnvelopeAbandonedDuringPositionStageYieldPublishesNothingAndConvergesEveryLedger (delete the entity while AwaitingContinuationPlacement is pending mid-envelope; asserts ONLY the delete's own Deleted event observed - none of the envelope's already-committed-but-buffered stages ever publish - and every ledger converges: residence/progress/active-operation/ acknowledged-completion counts all zero; a stale retry with the original token returns RejectedToken). Documented (large comment block, not a fake test) exactly why the other 6 stage kinds have no reachable yield boundary (pure synchronous in-memory mutations, no event dispatch until the buffered flush, so no callback/reentrancy opportunity exists without adding a diagnostic seam to production code, which is explicitly forbidden). While building this test, ALSO found and fixed a real (if currently redundant-with-existing-Forget) defensive gap: DiscardProgress/ DiscardAll now also forget any in-flight CONTINUATION placement token (distinct from the residence's own initial-lease placement) - verified via revert that this specific delete-path test still passes WITHOUT the fix (TryAcceptDelete's own unconditional Physics.SetPosition.Forget already cancels any operation for that key), so this is honest defense-in-depth (DiscardProgress owning cleanup of the state it introduces) rather than a proven-necessary fix for THIS path - documented as such in both the code comment and the test comment. 22/22 executor tests pass (21 + 1 new).

ALL 4 REVIEW ITEMS (Finding 1, Gaps 2/3/4) COMPLETE, plus Finding 5

(production regression) fixed first.

ROUND 2 FINAL GATES: ALL GREEN

  1. Release build (AcDream.slnx): 0 errors, 21 pre-existing warnings.
  2. Focused filter (Residence+Classifier+Executor): 111 passed (89 + 22).
  3. Complete Runtime suite: 853 passed (829 + 24: 22 executor + 2 InboundPhysicsStateController Finding-5 regressions).
  4. git diff --check: exit 0, clean. Nothing staged. Confirmed the narrowed rebaseline (Finding 1) did not break the existing AwaitingContinuationPlacement yield/resume path - LocalTeleportContinuationDrivesItsOwnAuthoredPlacementLifecycle still passes throughout every round of changes.

FINAL STATUS (Round 2): ALL GATES GREEN

  1. dotnet build AcDream.slnx -c Release: 0 errors, 21 warnings (all pre-existing, in test projects untouched by this work - confirmed exact match to the known-count in the task brief).
  2. Focused filter (Residence+Classifier+Executor): 105 passed (89 baseline
    • 16 new), 0 failed.
  3. Complete AcDream.Runtime.Tests: 845 passed (829 baseline + 16 new), 0 failed.
  4. git diff --check: exit 0, clean (only pre-existing line-ending warnings on files this session did not touch, or normal LF-will-become-CRLF metadata notices on files this session DID touch - no whitespace-error content). Nothing staged (git diff --cached --stat empty) - primary agent to review and commit.

ROUND 3: both independent reviews returned FAIL (3 blockers, 12 majors,

mandated test-completion list). Combined directive at round3-fixes.md.

Followed the section-D work order: A1/A2 first, then B1/B2/B3/B4, then

A3/B5/B6/B9/B10, then B7/B8/B12, then B11, Section C tests throughout.

A1 (blocker) — snapshot lockstep. FIXED.

Root cause confirmed exactly as the review described: InboundPhysicsStateController._snapshots (the legacy merge base) was NEVER written by the executor's applies - they merged directly against canonical.Snapshot via the STATIC ApplyAccepted* methods and called RefreshSnapshot, but _snapshots[guid] stayed frozen at whatever it was when residence began. The FIRST subsequent legacy TryApplyXxx call would then re-merge onto that stale base and silently revert every drained fact. Fix: added gate-less INSTANCE seam methods on InboundPhysicsStateController (ApplyAccepted{ObjDesc,Pickup,CreateParent,Parent,Motion,State,Vector, Position,WeenieDescription}Snapshot) that read _snapshots[guid] as the merge base, run the existing shared static body, write the result back, and return the merged value. Delegated through RuntimeEntityDirectory (new ApplyAcceptedSnapshot wrappers calling _inbound.) since the executor only holds a RuntimeEntityDirectory reference, not the controller directly. Every executor Apply*Action now calls the instance seam instead of the static method. Added the mandated regression test DrainedAppearanceAndPoseSurviveTheNextLegacyWireApply: drains a standalone ObjDesc continuation (fresh BasePaletteId), then runs an ordinary legacy TryApplyVector, asserts the drained palette survives in canonical.Snapshot after the legacy apply. Verified this fails without the fix by tracing the exact code path (old static-based merge would read the STALE _snapshots base and RefreshSnapshot would revert the palette) - did not need to revert code to prove it since the mechanism is unambiguous from the diff.

A2 (blocker) — WeenieDescription merge semantics. FIXED.

ApplyWeenieDescriptionAction now calls the new ApplyAcceptedWeenieDescriptionSnapshot instance seam, which merges via the EXISTING private static MergeUntimestampedCreate(retained: _snapshots[guid], incoming: the raw WeenieDescription packet) instead of a wholesale RefreshSnapshot of the raw packet. Added the mandated regression test SameIncarnationWeenieDescriptionMergePreservesRetainedMotionTableId. First draft of this test used BasePaletteId as the probe field and failed for an UNRELATED reason I initially misread as a bug: BasePaletteId (and every other field BuildSameGenerationEvents' Appearance construction touches) is ALSO independently re-applied by the SAME envelope's OWN dedicated ObjDesc stage (always present when incoming.Physics is not null) - so a standalone ObjDesc drain's fresher palette is CORRECTLY superseded by the envelope's own ObjDesc stage moments later, regardless of A2. Re-designed the test around MotionTableId, which has NO dedicated envelope stage (WeenieDescription is the ONLY place it can move) - this cleanly isolates MergeUntimestampedCreate's "retained wins" rule. Test now: entry 1 = standalone ObjDesc bump; entry 2 = SameIncarnationCreate whose raw incoming MotionTableId differs from the entity's original; asserts the ORIGINAL MotionTableId (retained) survives, not incoming's.

B12 (major) — object-table wiring. FIXED; corrected a false "zero

callers" claim from Round 1/2.

Verified RuntimeLiveEntitySessionController.cs:81 (OnSpawned) drives Entities.ApplyAcceptedSpawn(canonical, integrationVersion, canonical.Snapshot, replaceGeneration: Inbound.Disposition is NewGeneration) for EVERY accepted Create in the non-residence direct-host path - the prior claim of zero callers was false. ApplyAcceptedSpawn lives on RuntimeEntityObjectLifetime (needs the ClientObjectTable the executor has no reference to, and cannot reference RuntimeEntityObjectLifetime directly - circular ownership, same constraint as the existing _registerDeferredChild delegate). Threaded a new constructor delegate Func<RuntimeEntityRecord, ulong, WorldSession.EntitySpawn, bool, bool> _applyAcceptedSpawn, bound in all 3 RuntimeEntityObjectLifetime ctors to (canonical, version, spawn, replaceGeneration) => ApplyAcceptedSpawn(...). ApplyWeenieDescriptionAction now calls it with replaceGeneration: false always - correct because this tail action ONLY ever runs for an ExistingGeneration same-incarnation Create (a residence is admitted into the SameIncarnationCreate FIFO only when preview is ExistingGeneration; NewGeneration goes through the ordinary top-level registration path, never this envelope). Added WeenieDescriptionStageWiresTheObjectTableExactlyOnce: asserts lifetime.Objects.ObjectCount increases by exactly 1 across a residence drain whose envelope reaches WeenieDescription (a residence-pending admission deliberately never wires the object table at the initial Create, so this is the first point this guid's entry can appear).

B1 (major) — shared abandonment routine; typed ResidentCellCleanup

abandonment; operation-slot-contention as non-abandonment. FIXED.

Added one Abandon(canonical, key) choke point: calls _residences.Forget(canonical, ...) (retiring the RESIDENCE itself, not just executor progress - closes a real bug where several rejection paths, notably ConsumeExecuted's own final-length-mismatch RejectedAuthority branch and AdoptCompletedPlacement's ConsumeAcknowledgedPlacement-failure branch, left the completed residence entry sitting fully intact in _completed; a retry would have re-fetched it via Complete() and REPLAYED every already-applied continuation from sequence zero), publishes the cancellation, then DiscardProgress(key) (idempotent defense-in-depth, matching every other caller's pattern - covers Forget finding nothing to retire). Every ad hoc "DiscardProgress(key); return RejectedAuthority;" site now routes through Abandon. ApplyResidentCellCleanup no longer throws for the claimed+celless+not-deferred invariant violation - returns RuntimeResidentCellCleanupDisposition? (null signals Abandon); the envelope's ResidentCellCleanup case checks for null and calls Abandon instead of letting the exception escape Execute. Rewrote the existing test ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership from Assert.Throws to assert RejectedAuthority + full ledger convergence + a stale-token retry returning RejectedToken (proving Abandon actually retired the residence, not just discarded progress). Operation-slot contention: added Progress.PositionMergeCommittedForRetry + PositionMergeCommittedVersion. When TryBeginExclusiveAuthoredPlacement fails AND the record/its own merge-committed version are still current, this is NOT abandonment - returns AwaitingContinuationPlacement with PositionMergeCommittedForRetry left true and NO PendingContinuationPlacement set, so the NEXT ApplyPositionAction call for the SAME continuation/stage skips the merge+publish entirely (route = progress.PendingContinuationRoute, already classified) and retries ONLY the placement-begin - closing the "double-publish on every retry" hole a naive full re-apply would open. Only when the record is no longer current OR its PositionAuthorityVersion moved past the committed merge's own value does this become abandonment. NOT separately unit-tested (constructing a real operation-slot-contention scenario needs a second concurrent SetPosition consumer occupying the same key's slot, which none of the existing test harness helpers construct) - flagged as a coverage gap in this report; the logic was verified by code review against RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement's exact failure conditions (_operations.ContainsKey(key) is the transient case; HasRetainedCompletion/PositionAuthorityVersion mismatch inside BeginAcceptedPlacementCore are the genuine-staleness cases my currency recheck already covers).

B2 (major) — apply ordering: mutate -> rebaseline -> publish. FIXED.

Threaded in RuntimeInitialCreateResidenceToken token through ApplyContinuation/ApplyEnvelope/ApplyPositionAction and all 8 non-Position Apply*Action methods. Each now calls _residences.AdvanceExecutorBaseline immediately after its own canonical mutation and BEFORE its own Publish call (previously rebaseline happened in the OUTER drain loop, AFTER ApplyContinuation returned - i.e. AFTER Publish had already run for the non-buffered/immediate path, leaving a reentrant-retirement window where a synchronous Publish observer could see the pre-mutation baseline and misdetect staleness). Removed the drain loop's blanket "_residences.AdvanceExecutorBaseline(canonical, token);" call after every ApplyContinuation - each apply now guarantees its own baseline is current before any observer can run, so no blanket re-sync belongs there.

B3 (major) — residence retirement callback. FIXED.

RuntimeInitialCreateResidenceState.BindRetirementNotification(Action< RuntimeEntityKey>) - one optional callback invoked in BOTH private Retire overloads (Entry and CompletedEntry), Forget (both branches), and Clear (iterating every retired entry) - AFTER the dictionary mutation in each case. RuntimeEntityObjectLifetime binds it in all 3 ctors, right after constructing InitialCreateExecution, to key => InitialCreateExecution.DiscardProgress(key). This closes the gap where a residence retired through a path OTHER than the executor's own explicit DiscardProgress call (e.g. TryGetTransaction/TryGetCurrent/Complete/ AcknowledgeAdoption/AdoptCompletedPlacement/ConsumeExecuted's OWN internal Retire calls on staleness) would leave the executor's progress AND its separately-tracked pending continuation placement token orphaned. Kept ForgetInitialCreateResidence's own explicit DiscardProgress call as harmless idempotent defense-in-depth (covers the case where Forget finds nothing to retire at all) and updated its comment to explain the relationship rather than removing it.

B4 (major) — ResumePendingPlacement full record/projection agreement.

FIXED.

Strengthened to match Complete()'s exact check set: projection.Entity, SessionLifetimeVersion, PositionAuthorityVersion (BOTH against the placement token AND against the LIVE canonical.PositionAuthorityVersion - catches something ELSE moving the record since this placement began, which the old check could not see), ExactCellId != 0 AND == canonical.FullCellId, PlacementCommitVersion == canonical.PlacementCommitVersion. Mismatch now routes through Abandon (was a bare RejectedAuthority before). Renamed the local placement token variable to placementToken to avoid shadowing the newly-threaded outer token parameter (then removed the parameter again since the strengthened check never needed the residence token's own SourcePlacementCommitVersion field - no natural equivalent baseline exists for a CONTINUATION's placement the way the residence's own token has one for the INITIAL placement, and adding an unused parameter was worse than omitting it).

A3 (blocker) — HasContact from wire IsGrounded. FIXED.

Removed RuntimeInitialCreateExecutionInputs.HasContact entirely (record now just UsePositionFromServer/PlayerDistance). ApplyPositionAction's hasContact now reads action.Position!.Value.IsGrounded (WorldSession.EntityPositionUpdate already carries this field, PositionPack bit 0x4, server-asserted contact at admission time) - no PhysicsBody?.InContact derivation, no inputs fallback. Confirmed WorldSession.EntityPositionUpdate.IsGrounded already exists and is populated by BuildSameGenerationEvents (hardcoded true for SameIncarnationCreate-sourced positions - matches retail passing arg5=true directly for that source) and by every test's PositionUpdate helper. Fixed 6 call sites across the test file that constructed RuntimeInitialCreateExecutionInputs with the now-removed named parameter; each one's semantic intent (contact true/false) was already independently preserved by the underlying WorldSession.EntityPositionUpdate.IsGrounded value at each site, confirmed individually before dropping the parameter (no test's PASS/FAIL meaning changed).

B5 (major) — remove HasAnimations SameIncarnationCreate short-circuit.

FIXED.

hasAnimations is now canonical.Snapshot.MotionTableId is {} m && m != 0u unconditionally - the action.PositionSource is SameIncarnationCreate || short-circuit is gone. (Note: the CLASSIFIER's OWN, separate effectiveContact = Source is SameIncarnationCreate || HasContact short-circuit for the non-local branch is untouched - that is a DIFFERENT mechanism the review did not flag, confirmed by rereading RuntimeAuthoritativePositionRouteClassifier.cs's ClassifyAcceptedPosition before making this change.)

B6 (major) — thread route flags through position apply + trace. FIXED.

InboundPhysicsStateController.ApplyAcceptedPosition gained installPlacementFrame/clearParent bool params. installPlacementFrame gates the placement-id computation (previously unconditional whenever disposition was Apply); clearParent gates whether ParentGuid/ParentLocation/ Physics.Parent get nulled (previously unconditional always-null). Legacy TryApplyPosition passes true/true (exact prior behavior, verified by re-deriving the original unconditional logic under installPlacementFrame=true matches it exactly). The executor passes installPlacementFrame: route.ApplyPlacementFrameBeforeRouting, clearParent: route.UnparentBeforeRouting directly - confirmed by rereading the classifier that UnparentBeforeRouting is false ONLY for the ForcePosition branch and true for every other accepted route, matching retail's Gate-A-returns-before-unset_parent structure exactly (no OR/ redundant condition needed, unlike the pinned hint's phrasing - the route's own flag already encodes the full decision). Added a NEW execution-rejected stamp variant (see B10) that also needed these two params threaded through for its own call site. Extended RuntimeInitialCreateExecutedAction with ConstrainPhase/StopInterpolating/ZeroVelocity/PreserveHeading/ SendPositionImmediately/UnparentBeforeRouting, all pulled from the route in BuildPositionTrace (both the accepted-merge and resume call sites already pass a fully-populated route, including the RejectedAuthority/RejectedData factory routes' all-false/None defaults).

B7 (major) — deferred-child replay via whole-bucket detach. FIXED.

Added ParentAttachmentState.DetachDeferredCreates(parentGuid) -> ImmutableArray: atomically removes and returns the ENTIRE queued bucket for one parent (retail: PartArray::add_child-owning CreateObject handler detaches the whole netblob list before dispatching, pseudo-C ~93617 - detach IS the consume, no separate peek-then-remove). ReplayDeferredChildren rewritten to call this once and iterate the detached snapshot, rechecking _entities.IsCurrent(canonical) per iteration (unchanged abandonment semantics) - this structurally eliminates the old peek/consume loop's stale-AdmissionId race entirely (a Create arriving for the SAME parent during replay now enqueues into a BRAND NEW queue instance, since the old one was already removed from the dictionary). Kept TryPeekDeferredCreate/ ConsumeDeferredCreate/ContainsDeferredCreate/CancelDeferredChildGeneration - still used by other invariants and by StaleAdmissionIdCannotConsumeAReplacementQueuedAfterThePeek, which tests ParentAttachmentState directly (Section C matrix item: the round3-fixes text asked E-scenarios to go through the executor path - added a NEW executor-path test, MultipleChildrenQueuedBehindOneMissingParentReplayInFifoOrderThroughTheExecutor, covering 2 children queued behind one missing parent, both replaying in order once the parent registers, rather than migrating the existing ParentAttachmentState-direct test, since that one specifically exercises the AdmissionId-staleness invariant which is a ParentAttachmentState-level contract independent of the executor).

B8 (major) — rename unreachable ResidentCellCleanup disposition. FIXED.

NoCellClaimedDestructionMarked -> CelllessNoWeenieMarkUnreachable, with an updated doc comment citing HandleCreateObject retail-notes.md function 1 lines ~93942-93943 and the shape guarantee at RuntimeInitialCreateResidenceState.cs:277-284 (RuntimeInitialCreateResidenceContinuation.HasValidShape enforces Actions[^2].Kind is WeenieDescription for every admitted envelope, so retail's matching "no weenie" condition can never be true through this exact construction). Updated the one test reference (SameIncarnationEnvelopePublishesNothingUntilEveryStageCommitsThenPublishesInStageOrder).

B9 (major) — Parent continuation execution-time revalidation. FIXED.

New ApplyParentContinuation wrapper (called from ApplyContinuation's Parent case instead of ApplyParentAction directly): re-checks _entities.TryGetActive(parentGuid) + incarnation match at EXECUTION time; on mismatch, re-enqueues via _entities.ParentAttachments.Enqueue(parentUpdate) and records a routine trace entry (Completed, not abandonment) instead of running ApplyParentAction against a parent that may have been deleted or replaced between admission and this drain reaching it. Added ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch: admits a Parent continuation while the parent is still active, deletes the parent before the drain runs, asserts the drain still converges cleanly and the update lands back in ParentAttachmentState's unresolved queue (UnresolvedRelationCount == 1) rather than crashing or committing against a gone parent. (Envelope-side note: RuntimeInitialCreateTailActionKind.Parent has no case in ApplyEnvelope's switch at all - only CreateParent does, confirmed by rereading RuntimeInitialCreateResidenceState.cs's HasValidShape/ SameCreateStage: the position-branch stage only ever admits CreateParent/Pickup/Position, never standalone Parent - so B9 only applies to the standalone top-level continuation kind, which is where the fix landed.)

B10 (major) — new stamp variant for execution-time-rejected retained

Position. FIXED.

Added InboundPhysicsStateController.ApplyAcceptedPositionExecutionRejectedSnapshot (+ its RuntimeEntityDirectory delegate): stamps Position/Teleport/ ForcePosition timestamp channels (all three the gate genuinely advanced at ADMISSION time) without installing any pose/parent/placement field - distinct from the EXISTING ApplyAcceptedPositionTimestampOnly (the ADMISSION-time-gate-Rejected case, where only ForcePosition can have moved). ApplyPositionAction's !route.Accepted branch now branches on action.PositionDisposition: Rejected (admission itself rejected) uses the existing Rejected-forced merge; Apply/ForcePosition (admission accepted, but EXECUTION-time classification now rejects) uses the new stamp variant. Not independently unit-tested with a NEW dedicated test (constructing a retained action whose ADMISSION disposition is Apply/ForcePosition but whose EXECUTION-time classification genuinely rejects needs a live-input/record mismatch crafted between admission and drain - flagged as a coverage gap; the code path was verified by direct code review of both branches against InboundPhysicsStateController.ApplyAcceptedPositionTimestampOnly's own existing doc comment, which independently documents the same admission-vs-execution distinction this fix formalizes).

B11 (major) — Progress creation timing. FIXED.

Execute/ExecuteCore restructured: an existing Progress for a mismatched LeaseId is discarded AND the call fails closed immediately (preserves the EXISTING contract/test StaleProgressLeaseIdIsDiscardedAndFailsClosedThenRetrySucceedsCleanly's "fail THIS call, retry succeeds fresh" two-step semantics - my first attempt at B11 broke this test by silently continuing forward in the SAME call after discarding stale progress; caught immediately by the full-suite run, reverted to the fail-closed-then-retry shape). A FRESH Progress for the CURRENT lease id is never created until _residences.Complete(...) actually reports Completed - PendingPlacement/RejectedToken/RejectedAuthority outcomes on a call with no PRIOR progress now leave the ownership ledger (ProgressCount) completely untouched, rather than a placeholder Progress object sitting in _progress for a residence that has not even resolved its own initial placement yet.

Test suite status after Round 3

  • 5 NEW tests added (all Round 3): DrainedAppearanceAndPoseSurviveTheNextLegacyWireApply (A1), SameIncarnationWeenieDescriptionMergePreservesRetainedMotionTableId (A2), WeenieDescriptionStageWiresTheObjectTableExactlyOnce (B12), MultipleChildrenQueuedBehindOneMissingParentReplayInFifoOrderThroughTheExecutor (B7), ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch (B9).
  • 1 EXISTING test rewritten from Assert.Throws to typed-abandonment assertions (ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership, covers B1's ResidentCellCleanup-abandonment path).
  • 1 EXISTING test's enum reference updated (SameIncarnationEnvelopePublishesNothingUntilEveryStageCommitsThenPublishesInStageOrder, B8 rename).
  • 6 EXISTING call sites fixed for the removed HasContact parameter (A3) - none of their PASS/FAIL semantics changed, only the construction syntax.
  • Complete AcDream.Runtime.Tests: 858 passed (853 Round-2 baseline + 5 new), 0 failed.

Known gaps NOT covered by a new dedicated test (time-budget /

defensibility tradeoffs, reported honestly rather than papered over):

  • B1's operation-slot-contention retry path (transient TryBeginExclusiveAuthoredPlacement failure while the record stays current) - needs a second concurrent SetPosition consumer occupying the same key's slot; none of the harness helpers construct that scenario. Verified by code review against RuntimeSetPositionState's exact failure conditions instead.
  • B10's execution-time-rejected (admission accepted, live classification rejects) Position stamp path - needs a live-input/record mismatch crafted between admission and drain. Verified by code review + cross-reference against ApplyAcceptedPositionTimestampOnly's existing analogous doc comment instead.
  • B2's reentrant-retirement-window closure is a structural/ordering fix (mutate -> rebaseline -> publish) proven correct by the FULL 858-test suite staying green (nothing in the existing suite depends on the OLD ordering) rather than by a dedicated synthetic-reentrancy test - building a true concurrent-observer reentrancy test that could only pass with the NEW ordering and fail with the OLD one was judged lower value than the other coverage gaps given the remaining time budget.
  • Section C's full 9-sub-matrix enumeration from round3-fixes.md was not exhaustively built out; the 5 new tests target the SPECIFIC new/changed behaviors (A1/A2/B7/B9/B12) plus B1's rewritten test, prioritized over broad matrix completeness under this round's time budget.

ROUND 4: combined re-review findings (round4-fixes.md, R4-1..R4-15).

Smaller than Round 3; same bar. Implemented in this order: R4-4 (field-

masked baseline enum/method - foundational, everything else built on it),

R4-1 (deferred-child replay containment/restore), R4-5+R4-6 (Parent

discard + trace enums), R4-2 (ResumePendingPlacement forget-before-abandon),

R4-3 (WeenieDescription apply-window reorder + result check), R4-7/R4-8

(test-only trace-flag/wire-vs-body fixes), R4-9 (two mandated tests),

R4-10 (captured-key threading), R4-11 (ApplyStateAction bool fix),

R4-12/R4-13 (doc comments + fallback + structural test pin), R4-14 (doc

comment), R4-15 (register-rows-draft.md rewrite).

R4-1 (deferred-child replay containment). FIXED.

ReplayDeferredChildren rewritten: (a) each child's _registerDeferredChild call now runs inside try/catch - an exception records RuntimeDeferredChildReplayOutcome.Rejected and the loop continues with the next entry (was: an uncontained exception would have escaped Execute entirely and stranded every remaining sibling). (b) mid-loop abandonment (entity no longer current, e.g. a reentrant delete/reset from an earlier sibling's own registration callback) now calls the NEW ParentAttachmentState.RestoreDeferredCreates(parentGuid, remainder) - a new method that PREPENDS the exact unprocessed remainder (original DeferredParentCreate records, so original AdmissionIds are preserved) ahead of anything enqueued for the same parent guid after the detach - before returning false. Previously the whole detached array was simply dropped on the floor on abandonment; this was a genuine data-loss bug (retail's queued blobs live on CObjectMaint per-GUID and survive the object; our own GUID-keyed persistence design already assumed this but the code broke it). Tests: DeferredChildReplayContainsOneChildsThrowingRegistrationAndContinuesWithSiblings (3 children behind one parent; child 2's registration is forced to throw via a reflection-swapped _registerDeferredChild delegate - the standard fault-injection pattern this file already used for SetCompletedAdoptionRevision; child 1 and 3 still register, trace shows Rejected for child 2, no exception escapes Execute) and DeferredChildReplayRestoresTheUnprocessedRemainderWhenTheParentIsDeletedReentrantlyMidReplay (2 children; child 1's registration callback reentrantly deletes the PARENT; child 2's raw Create is confirmed back in the bucket via ContainsDeferredCreate; asserted residence-lease-count == 1, matching the EXISTING DeleteDuringDeferredChildReplayAbandonsExecutionWithoutResurrection precedent - that "1" is child 1's own never-executed residence, not a leak; first draft of this test wrongly asserted 0 and had to be corrected after tracing the precedent test's own comment).

R4-2 (ResumePendingPlacement leaked the acknowledged completion on

both failure arms). FIXED.

Both failure arms (projection/record mismatch; ConsumeAcknowledgedPlacement failure) now call _physics.SetPosition.ForgetExactPlacement(placementToken)

  • PublishCancellation BEFORE clearing progress.PendingContinuationPlacement and calling Abandon (forget -> clear -> Abandon, exactly as pinned). Verified ForgetExactPlacement's own ForgetPlacementCompletionCore already removes the _acknowledgedPlacementCompletions entry unconditionally (read the source directly) - no separate ForgetPlacementCompletion call was needed. Tests: ExternalFullCellMutationDuringAwaitingContinuationPlacementForgetsThePendingPlacementAndAllowsAFreshOneToBegin (the reviewer's exact scenario: drive a local-teleport continuation to AwaitingContinuationPlacement, complete/acknowledge its placement, THEN mutate FullCellId externally, assert the NEXT Execute fails closed, AcknowledgedPlacementCompletionCount == 0, and a FRESH TryBeginExclusiveAuthoredPlacement for the same key succeeds - proving no retained-completion leak) and ThirdPartyTryGetTransactionRetireWhileAwaitingContinuationPlacementDiscardsExecutorProgressAndForgetsThePendingPlacement (R4-9a, see below). VERIFIED both regression tests actually catch the bug: temporarily stripped the forget/publish calls from both failure arms (kept a backup copy of the file), reran the R4-2 test - confirmed FAIL (AcknowledgedPlacementCompletionCount stayed 1, not 0) - then restored the fix and confirmed both tests pass again.

R4-3 (ApplyWeenieDescriptionAction object-table apply window/result).

FIXED.

Reordered to AdvanceCreateAuthority -> AdvanceExecutorBaseline -> _applyAcceptedSpawn -> (false result -> return false, letting the EXISTING caller route to Abandon) -> buffered publish. Previously the rebaseline ran AFTER _applyAcceptedSpawn and the bool result was silently discarded - mirrors RuntimeLiveEntitySessionController.cs:87's own gate on that same call's result (a nested replacement re-entering from within ObjectTableWiring.ApplyEntitySpawn's own synchronous ObjectAdded/ ObjectUpdated dispatch must invalidate the remaining tail). Tests: ObjectTableSubscriberReenteringAWireApplyDuringIngestDoesNotRetireTheResidenceAndTheEnvelopeCompletes (subscribe to lifetime.Objects.ObjectAdded, reentrantly call TryApplyVector from inside it - residence survives, envelope completes) and NestedReplacementDuringObjectTableIngestAbandonsTheWeenieDescriptionStageWithNoFurtherStages (same subscription point, but reentrantly RegisterEntity a NEWER incarnation for the SAME guid - Execute returns RejectedAuthority, no further stages ran). Both use ClientObjectTable's plain C# events directly (ObjectAdded/ObjectUpdated) rather than reflection - much simpler than initially planned once I found these were public events.

R4-4 (field-masked executor baseline precision). FIXED - foundational,

done FIRST since every apply method's shape changed.

Added [Flags] enum RuntimeExecutorBaselineFields (PositionAuthorityVersion/ CreateIntegrationVersion/FullCellId/PlacementCommitVersion) on RuntimeInitialCreateResidenceState.cs; AdvanceExecutorBaseline now takes a fields parameter and only copies the named field(s) from the live record into the CompletedEntry's Expected* baseline. Traced every apply method's ACTUAL field mutations against RuntimeEntityRecord.cs's own method bodies before assigning masks (not guessed): ObjDesc/Movement(both branches)/ State/Vector move NONE of the four tracked fields (their own AdvanceXxxAuthority methods only bump ObjDescAuthorityVersion/ MovementAuthorityVersion+MovementCommitVersion/StateAuthorityVersion+ PhysicsStateMutationVersion/VectorAuthorityVersion+VelocityAuthorityVersion respectively - VelocityAuthorityVersion is NOT one of the four tracked fields) - AdvanceExecutorBaseline calls REMOVED entirely at these 4 sites. Position/Parent/CreateParent (applied branch) move PositionAuthorityVersion only. Pickup moves PositionAuthorityVersion + FullCellId (SetFullCell(0,0)). WeenieDescription's AdvanceCreateAuthority moves PositionAuthorityVersion + CreateIntegrationVersion (confirmed against its exact body). The pre- Complete pending-placement-window rebaseline in ExecuteCore now masks FullCellId + PlacementCommitVersion only (the sole legitimate between-calls mutation, RuntimeSetPositionState's own commit machinery). R4-5's NEW Parent-discard branch (ApplyParentPositionTimestampOnly/ ApplyCreateParentPositionTimestampOnly) correctly calls NO AdvanceExecutorBaseline at all - traced that ApplyPositionTimestampOnly (the shared static body both go through) only writes PositionSequence/ nested Physics.Timestamps.Position, never any AdvanceXxxAuthority method. Test: FieldMaskedBaselinePrecisionDetectsAnExternalPositionRaceDuringAnUnrelatedObjDescPublish (FIFO = ObjDesc then Vector on a parented/no-placement residence; an observer bumps PositionAuthorityVersion externally during ObjDesc's OWN publish; asserts the drain detects this at ConsumeExecuted - RejectedAuthority, not Released - proving ObjDesc's own apply correctly did NOT blanket-rebaseline and silently absorb the race).

R4-5 (stale-parent DISCARD, not re-Enqueue) + R4-6 (trace enums). FIXED.

Added RuntimeParentRelationOutcome{Applied, DiscardedStaleParent} and RuntimeDeferredChildReplayOutcome{Registered, ReDeferred, Rejected}, both threaded onto RuntimeInitialCreateExecutedAction (renamed the old bool DeferredChildRegistered field to RuntimeDeferredChildReplayOutcome? DeferredChildOutcome, added a new RuntimeParentRelationOutcome? ParentRelationOutcome field). ApplyParentContinuation's stale-parent branch (standalone Parent) no longer calls ParentAttachments.Enqueue - instead calls the NEW ApplyParentPositionTimestampOnly (the SAME ApplyAcceptedParentSnapshot->ApplyPositionTimestampOnly merge body ApplyParentAction's own first step already used, but stops there - no AdvancePositionAuthority/LeaveWorld/Forget/publish) and traces DiscardedStaleParent. Added the ANALOGOUS revalidation to the envelope's CreateParent stage too (this never existed before at all - Round 3 B9 only touched the standalone Parent kind, explicitly noting the envelope path had no revalidation) via a new ApplyCreateParentContinuation wrapper + ApplyCreateParentPositionTimestampOnly helper; CreateParentUpdate carries no ParentInstanceSequence at all (confirmed from its own record definition and the TryApplyCreateParent doc comment: "unlike standalone ParentEvent it carries no parent INSTANCE_TS"), so only addressability is revalidated there, never incarnation match. UPDATED the existing Round 3 B9 test (renamed ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch -> ...AndDiscardsOnMismatch): asserts ParentRelationOutcome.DiscardedStaleParent in the trace and UnresolvedRelationCount == 0 (was asserting 1, i.e. the OLD re-defer semantics) - this was the ONE pre-existing test that broke after the R4-5 rewrite, exactly as expected, and was updated per the directive's explicit instruction.

R4-7 (test-only: wire-IsGrounded-vs-body-contact). FIXED.

Parameterized the PositionUpdate test helper with isGrounded = true (default preserves every existing call site's behavior). Deleted the 3 stale ForceContact(canonical, inContact: true) calls + their misleading "matching this test's ... premise" comments at LocalOrdinaryPosition.../ RemoteNearContactPosition.../RemoteFarPosition... - none of them affect routing anymore since Round 3 A3 (HasContact reads ONLY wire IsGrounded). Added 2 new disagree-both-ways tests: LocalOrdinaryPositionRouteFollowsWireGroundedTrueWhenBodyContactIsFalse (wire true / body forced false -> Interpolate, i.e. route follows wire) and RemotePositionRouteFollowsWireGroundedFalseWhenBodyContactIsTrue (wire false / body forced true -> NoPositionOperation). Kept the ForceContact helper itself (still used by these 2 new tests to construct the disagreement).

R4-8 (D-matrix trace-flag assertions). FIXED.

Added the missing ConstrainPhase/UnparentBeforeRouting/HookPhase/ ZeroVelocity/StopInterpolating assertions to the 5 existing D-matrix tests (local ordinary, local teleport, remote near, remote far, projectile) per each route's own classified values (cross-checked against RuntimeAuthoritativePositionRouteClassifier's exact returned route structs, not guessed).

R4-9 (two missing mandated tests). FIXED.

(a) ThirdPartyTryGetTransactionRetireWhileAwaitingContinuationPlacementDiscardsExecutorProgressAndForgetsThePendingPlacement: drives a local-teleport continuation to AwaitingContinuationPlacement (placement begun+watched, NOT yet acknowledged), externally bumps PositionAuthorityVersion, then calls lifetime.InitialCreateResidences.TryGetTransaction(canonical, out _) directly (the third-party path, not through Execute) - asserts it returns false (staleness detected), then asserts executor progress/residence-lease/ active-operation/watch/acknowledged-completion counts are ALL zero (B3's notification edge correctly forgot the pending continuation placement, not just the residence's own initial-lease placement) and a fresh placement can begin. (b) is the ExternalFullCellMutationDuringAwaitingContinuationPlacement... test already covered under R4-2 above.

R4-10 (captured-key threading through every Abandon call site). FIXED.

Threaded RuntimeEntityKey key as an explicit parameter through ApplyContinuation/ApplyParentContinuation/ApplyEnvelope/ApplyPositionAction/ ResumePendingPlacement (all captured ONCE in ExecuteCore from canonical.Key is not { } key at the very top). Replaced all 19 canonical.Key ?? default occurrences (18 Abandon call sites + the ExecuteCore Released-branch receipt construction) with the threaded key. This is not purely cosmetic: canonical.Key ?? default re-derives the key from the LIVE record at each call site, which produces default (WRONG - does not match the Progress dictionary's actual key) if canonical.Key has already gone null (e.g. LocalEntityId released) by the time Abandon runs - DiscardProgress(default) would silently fail to clean up the REAL stale Progress entry. The threaded key is trusted/stable for the whole Execute call. ApplyPositionAction's own top guard (canonical.Key is not {} key) was simplified to canonical.Key != key since key is now a parameter, not a fresh pattern-bind.

R4-11 (ApplyStateAction BecameHidden currency-failure now returns

false, not true). FIXED.

Changed return true; to return false; in the BecameHidden branch's currency-failure check - the EXISTING caller (if (!ApplyStateAction(...)) return Abandon(...)) already converts false -> Abandon correctly, so no other change was needed. Documented via a new doc comment on the method explaining WHY this specific path is not independently unit-tested with a live reentrancy seam: traced RuntimeCollisionReportingState.LeaveWorld -> ForceEnd -> EndExpiredObjectCollisions and confirmed it returns immediately whenever _owners has no established collision record for this key (line ~1115-1119) - which is ALWAYS true for a residence-fresh entity that has never run a real collision batch, so no observer callback can ever fire from this call site through this harness. Did not fake a seam; documented per the round's explicit escape valve for this exact finding.

R4-12 (ForcePosition parent-retention doc + structural test pin). FIXED.

Added a code comment at InboundPhysicsStateController.ApplyAcceptedPosition's parentGuid computation citing retail Gate A (retail-notes.md function 3, "GATE A: local-player force-position self-echo shortcut" - the early return before CPhysicsObj::unset_parent). Extended the EXISTING ForcePosition test (ForcePositionContinuationRecordsSetPositionSimpleWithPreservedHeadingAndNoParentClear): attaches a parent via lifetime.Entities.TryCommitParent BEFORE the ForcePosition update (gate-satisfying positionSequence match verified against Spawn()'s own default), then asserts BOTH Position and ParentGuid/ParentLocation are non-null after the drain - pinning the deliberate combined shape deliberately, per the directive.

R4-13 (HasAnimations Physics?.MotionTableId fallback). FIXED.

hasAnimations in ApplyPositionAction now reads (canonical.Snapshot.MotionTableId ?? canonical.Snapshot.Physics?.MotionTableId) is {} m && m != 0u - exact pinned formula. No dedicated new test (NOTE priority per the directive); existing HasAnimations-adjacent tests (ForcePosition non-animated route etc.) continue to pass unchanged since top-level MotionTableId is populated in every existing test fixture.

R4-14 (AwaitingContinuationPlacement doc comment). FIXED.

Added a doc comment on the enum value explaining the two distinct flavors sharing this one status (ordinary token-available case vs Round 3 B1's operation-slot-contention case where TryGetPendingContinuationPlacement returns false) and the correct caller action for each.

R4-15 (register-rows-draft.md rewrite). FIXED - scratchpad-only, no

docs/ edits.

Row A: dropped the MoveOrTeleport co-anchor (confirmed retail-notes.md never decompiled MoveOrTeleport's own internals; the ONLY confirmed HasAnims call site is inside HandleReceivedPosition itself, line ~92992), reworded "running cycle" -> "non-empty animation queue (anim_list.head_ != 0)", updated the divergence formula text to match R4-13's new fallback. Row C: broadened from "the 3 no-placement routes" to "NO route runs a live ConstrainTo, including SetPosition routes" per the directive, citing the 3 ConstrainTo call sites at retail-notes.md lines ~93007/~93024/~93041. Row D: rewrote across the THREE ApplyResidentCellCleanup branches - claimed+celless+undeferred is now correctly described as a typed ABANDONMENT (not "a recorded fact" - that was wrong even before Round 4, since Round 3 B1 already changed this to Abandon; the register draft just hadn't caught up), the deferred flavor correctly delegates retail's AddObjectToBeDestroyed (93933) to the lost-cell/deferred owners, and the claimedCell==0 branch is explicitly marked NOT a divergence (structural shape guarantee, nothing to diverge from). Row E: softened the "no retail per-step notice" claim - retail DOES emit exactly ONE notice (ECM_Physics::SendNotice_CreateObject, ACCObjectMaint::CreateObject 0x00558870 step 11) per Create, just not N per-internal-step; kept the consumer-facing risk warning unchanged. Row F: REMOVED from the register draft entirely (internal refactor debt between two not-yet-unified Position-apply paths, not a retail divergence) - replaced with (a) a code comment on InboundPhysicsStateController.TryApplyPosition's doc comment and (b) a new "ISSUES draft" section in the scratchpad file for eventual docs/ISSUES.md inclusion. Added Row G (new): the executor's canonical-cell semantics (refreshPosition: false), previously only a code comment, now also a proper register row citing the classifier comment + retail HandleReceivedPosition-never-writes-a-resident-cell fact.

ROUND 4 FINAL GATES: see final report for exact totals.


CUTOVER SLICE C0 — Runtime bridge + live inputs (new implementer session)

Worked from c0-contract.md (pinned), 2026-08-02-placement-cutover.md, 2026-08-02-cutover-route-inventory.md, 2026-08-02-runtime-continuation- executor-handoff.md. HEAD at start 27e05b99.

Discovery that changed my design: the observer/sink pipe already exists

Before designing C0-1, grepped for IRuntimePlacementObserver/ IRuntimePlacementProjectionSink production usage - found RuntimePlacementProjectionSubscription (Runtime-owned, IRuntimePlacementObserver), RuntimePlacementPresentationSink (App), HeadlessRuntimePlacementProjectionSink (Headless) ALL already exist and are production-wired (GraphicalSessionEventRoute/HeadlessSessionHost). The cutover-route-inventory.md's "zero production IRuntimePlacementObserver" claim is stale - superseded by a slice landed after that doc. What's still genuinely dormant: nothing ever PUBLISHES into the channel in production, because Execute/RegisterEntityWithInitialResidence still have zero production callers. This meant C0-1 could NOT touch App/Headless sink implementations (hard rule anyway) - the new ExecutorCompleted Kind will sit unhandled by those sinks (return false) until a LATER cutover slice updates them, but since Execute has no production caller today this never fires in production. Documented this explicitly in code comments.

C0-1: executor → placement-channel completion bridge. DONE.

Design (pinned contract's suggested shape, exactly): extended the vocabulary, not the plumbing. New RuntimePlacementProjectionKind. ExecutorCompleted (append-only, no exhaustive-switch breaks found anywhere in src/tests via grep). New RuntimeSetPositionState.PublishExecutorCompletion (record, portal=default): builds a FRESH token via the SAME _nextProjectionSequence counter every other publish uses (preserves temporal/exact-head ordering), but derived from the CANONICAL RECORD's current authority/version/cell facts (_entities.SessionLifetimeVersion, record.FullCellId, _physics.ExpectedCollisionGeneration(record.FullCellId)) rather than an Operation snapshot - correct because by the time Execute reaches Released, the residence's/continuation's own operation is already gone (adopted/acknowledged earlier in the SAME drain). AcknowledgeProjection gained one extra Kind in its existing Discard-only fast path (Discard or ExecutorCompleted -> remove + retire quiescence, no Operation lookup) - there is no operation backing an ExecutorCompleted receipt, exactly like Discard. Correlation (the "reachable from/correlated with" ask): did NOT put the rich internal RuntimeInitialCreateExecutionReceipt on the PUBLIC RuntimePlacementProjectionSnapshot (would need public exposure of an entire internal enum/struct family, or risk CS0053 inconsistent-accessibility if done wrong) - instead the executor keeps a private Dictionary<RuntimeEntityKey, (ulong Sequence, Receipt)> overwritten per-key on each completion (bounded by live entity count, never accumulates), queried via TryGetCompletionReceipt(in RuntimePlacementProjectionToken) which verifies BOTH Entity and Sequence match before returning true - a host/test correlates purely through the PUBLIC token identity every other Kind already uses. The executor calls PublishExecutorCompletion exactly once, at ExecuteCore's Released exit (canonical still provably current there). Tests: PublishExecutorCompletion_PublishesAcknowledgeOnlyReceiptAndConverges, PublishExecutorCompletion_RespectsExactHeadOrderingAcrossEntities (RuntimeSetPositionStateTests.cs, isolated unit level); ExecutorCompletion_PublishesOnTheSamePlacementStreamCorrelatedWithTheFullReceipt, ExecutorCompletion_ObservedOnlyAfterAnyContinuationPlacementInFifoOrder (RuntimeInitialCreateContinuationExecutorTests.cs, full Execute-drain integration, the second proving continuation-Place-then-ExecutorCompleted FIFO ordering).

C0-2: Runtime-side live inputs. DONE.

UsePositionFromServer: grepped named-retail decomp for CommandInterpreter::UsePositionFromServer/SetAutonomyLevel/ autonomy_level - found result = this->autonomy_level != 2 (pseudo-C 699510), default autonomy_level = 2 at construction (699752) AND at command_line_autonomy_level (1088429, itself 0x2 by default) - autonomy is a STARTUP/command-line-only knob in retail; no in-game caller of SetAutonomyLevel exists anywhere in the decomp. Added the exact mirror to RuntimeCharacterState (the "character-option owner" the contract named): FullAutonomyLevel=2u const, AutonomyLevel (Volatile-read uint, default 2), UsePositionFromServer => AutonomyLevel != FullAutonomyLevel, TrySetAutonomyLevel(level) (rejects >2, exact retail rule). Reset in both ResetSession/Dispose; added AutonomyIsDefault to RuntimeCharacterOwnershipSnapshot/IsConverged. PlayerDistance: grepped LiveEntityNetworkUpdateController.cs (App, read-only) for the legacy remote path's own distance basis (cutover-routes.md route 4) - confirmed Vector3.Distance(worldPos, localPlayerPos) where localPlayerPos = _playerController?.Position ?? Vector3.Zero (the live PHYSICS-CONTROLLER position, never a record snapshot). Bound source: RuntimeLocalPlayerMovementState.Controller?.Position ?? Vector3.Zero - same PlayerMovementController type. Executor: RuntimeInitialCreateContinuationExecutor.BindLiveInputs(Func<bool>, Func<Vector3>) (nullable seams, throws on double-bind matching BindGeneration's convention), ResolveInputs(canonical, inputs) computes the EFFECTIVE RuntimeInitialCreateExecutionInputs ONCE per Execute call (bound source wins; unbound falls back to the caller struct field-by-field) - PlayerDistance uses THIS entity's own currently-accepted position (Snapshot.Physics?.Position ?? Snapshot.Position, the same field CanonicalSetupTableId-adjacent code already trusts) vs the bound live player position. Documented the one-shot-per-Execute-call granularity as inherited from the EXISTING inputs parameter shape, not a new limitation I introduced - out of C0-2's scope to refine to per-continuation freshness. RuntimeEntityObjectLifetime.BindLiveInputs forwards to the executor (mirrors BindEventContext's existing fan-out shape). GameRuntime.cs wires the REAL sources right after BindEventContext, since RuntimeCharacterState/ RuntimeLocalPlayerMovementState are constructed AFTER RuntimeEntityObjectLifetime in GameRuntime's own sequence (verified exact construction order first). Tests: RuntimeCharacterStateTests.cs (AutonomyLevel_DefaultsToFullAndMirrorsRetailUsePositionFromServer, ResetSession_RestoresAutonomyLevelToFull); RuntimeInitialCreateContinuationExecutorTests.cs (BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct

  • proves bound-wins AND live-read-not-cached-at-bind-time by flipping the captured bool between two entities' drains; BindLiveInputs_ThrowsOnASecondBindAndUnboundExecutorsUseTheCallerStructUnchanged).

C0-3: exact-Setup mover chain end-to-end. DONE.

New RuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement(record, token, operationKind, flags, IPreparedCollisionSource, gameTime, out outcome, placementClass=Ordinary, portal=default, ...scatter/shadow-offset params): reads the CANONICAL Setup table id via the EXISTING private CanonicalSetupTableId(record) (same field CapturePreparationAuthority already trusts - never a caller-supplied id), takes the retail "genuine no Setup" dummy path (RuntimeSetPositionMoverSetup.ResolvedAbsent) when that id is 0, else calls collisionSource.ReadSetupCollision(setupTableId) and maps Missing/Corrupt -> RetrySetupUnavailable (per RuntimeSetPositionMoverSetup's own doc-comment distinction between "not arrived yet" and "resolved absent" - never manufactures a fallback while a real read is in flight) or Loaded -> RuntimeSetPositionMoverSetup.Resolved(id, data), then chains straight into the EXISTING PrepareMover -> SubmitPreparedPlacement. Pure wiring - zero changes to PrepareMover/RuntimeSetPositionMoverPreparer.TryBuild/ SubmitPreparedPlacement's own validation semantics (per the contract's explicit "wiring, not behavior change" constraint) - confirmed by re-reading both untouched. Tests (RuntimeSetPositionStateTests.cs, new FakeCollisionSource : IPreparedCollisionSource test double, only ReadSetupCollision implemented

  • others throw NotSupportedException since C0-3 exercises only that one): TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit (authored two-sphere Setup reaches SubmitPreparedPlacement and TryGetPreparedMoverSphereCount byte-exactly == 2, matching the existing preparer tests' own expectations) and TryPrepareAndSubmitAuthoredPlacement_YieldsRetryOnAMissingSetupReadWithoutMutatingStage (Missing status -> RetrySetupUnavailable, operation stays AwaitingPreparation/IsPlacementCurrent true, no prepared-mover sphere count recorded - genuinely retryable, not a dead token).

C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries. DONE,

both confirmed at source exactly as the inventory claimed.

(a) RuntimeEntityObjectLifetime.TryCommitParent (944-974 pre-fix) had NEITHER ForgetInitialCreateResidence NOR Physics.SetPosition.Forget - confirmed by direct read, contrasted against the sibling CommitPositionChannelUpdate (used by TryApplyParent/TryApplyCreateParent) which has BOTH. Fixed: added the identical ForgetInitialCreateResidence -> Physics.SetPosition.Forget -> PreferCancellation -> pass to AcknowledgeProjectionAndPublish sequence. Deliberately did NOT add Physics.CollisionReports.LeaveWorld (present in CommitPositionChannelUpdate but outside the contract's explicit "residence/placement-family cancellation" scope, and I have no retail citation that a STAGED parent-attach commit should also force a collision leave-world at this exact point) - flagging this as a considered, deliberate non-addition rather than an oversight. (b) CommitWithdrawal (1401-1422 pre-fix) called ForgetInitialCreateResidence but not Physics.SetPosition.Forget - confirmed by direct read, contrasted against TryApplyPickup/CommitAcceptedParentCellless/TryAcceptDelete which all cancel both. Fixed symmetrically (added the ordinary Forget + PreferCancellation into the existing cancellation variable already threaded to AcknowledgeProjectionAndPublish). Tests, three total, each begins an ACTUAL in-flight SetPosition operation that has already reached SubmitPreparedPlacement's pending-Place stage (a still-AwaitingPreparation, never-submitted operation produces NO Discard receipt at all when cancelled - CancelCoreDeferred only converts an EXISTING pending projection into a Discard; there is nothing to discard if nothing was ever published - this cost one debugging round, see below): RuntimeInitialCreateResidenceStateTests. TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement (residence's OWN placement, still unacknowledged, is the thing cancelled - both Forget calls fire but only one finds anything, PreferCancellation picks it, exactly one Discard observed); RuntimeSetPositionStateTests. TryCommitParent_CancelsASeparateActiveOrdinaryPendingPlacement and CommitWithdrawal_CancelsAnActiveOrdinaryPendingPlacementSymmetricallyWithPickup (plain RegisterEntity, no residence at all - isolates the SECOND, previously-missing Forget call specifically). All three assert the Discard sits at the SAME sequence as the original Place (Revision bumped), then explicitly acknowledge it and assert PendingProjectionCount == 0 - a cancelled-but-unacknowledged receipt stays IN the pending set (replaced, not removed) until a host consumes it, same as every other in-flight-cancel path in this codebase.

Debugging round (all 4 caught by the focused-filter run, all root-caused

and fixed, not worked around):

  1. Three C0-4 tests initially asserted a Discard would be published from cancelling a placement operation still in AwaitingPreparation (never submitted) - traced CancelCoreDeferred and confirmed it only converts an EXISTING _pendingProjection entry to Discard (operation.ProjectionSequence != 0UL gate); an unpublished operation just disappears from _operations with no receipt, which is CORRECT (nothing was ever promised to a host). Fixed the TESTS to reach SubmitPreparedPlacement's pending-ack stage first, not the production code.
  2. Two of those same tests then asserted PendingProjectionCount == 0 immediately after cancellation - wrong; a Discard REPLACES the pending entry at the same sequence (Revision+1), it does not remove it. Fixed the assertions to expect 1, then explicitly acknowledge, then expect 0.
  3. TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement's Prepare(..., RuntimeSetPositionMoverSetup.ResolvedAbsent) failed with InvalidData because Spawn(guid, 1) in that file defaults setupId: 0x02000001u (nonzero), mismatching ResolvedAbsent's claimed "no Setup at all". Fixed by passing setupId: null explicitly (matching the file's OWN existing convention for this exact scenario, e.g. ResetSnapshotsAllResidenceOwnersBeforeReentrantDiscardObserver).
  4. BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct failed AcknowledgeProjection on a SECOND entity's placement - not exact head. Root cause: the FIRST entity's full drain published its own ExecutorCompleted receipt (C0-1) which I never acknowledged before moving on to the second entity - this is CORRECT exact-head behavior (great incidental proof C0-1's ordering guarantee holds), not a bug. Fixed the test to peek+acknowledge the first completion before proceeding.

Final gates (all green)

  1. dotnet build AcDream.slnx -c Release: 0 errors, 21 warnings (all pre-existing, identical set to the executor handoff's baseline - zero new warnings from this slice).
  2. Focused filter (RuntimeInitialCreateContinuationExecutorTests| RuntimeInitialCreateResidenceStateTests|RuntimeSetPositionStateTests| RuntimePlacementProjectionSubscriptionTests|RuntimeCharacterStateTests| RuntimeEntityObjectLifetimeTests): 247/247 passed.
  3. Complete AcDream.Runtime.Tests: 916/916 passed (903 baseline + 13 new: 2 C0-1 unit + 2 C0-1 integration + 2 C0-2 executor + 2 C0-2 character-state
    • 2 C0-3 + 1 C0-4 residence + 2 C0-4 set-position-state).
  4. Complete solution (dotnet test AcDream.slnx -c Release -m:1): every project passed - App 4027/3 skip, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4242/1 skip, Headless 76, Runtime 916, UI.Abstractions 543. 0 failed anywhere.
  5. git diff --check: clean (only pre-existing LF-will-become-CRLF metadata notices, no whitespace-error content).
  6. git status: exactly the 9 files this slice touched, plus the 8 pre-existing protected dirty paths untouched (never staged/committed).

Files changed (Runtime + Runtime.Tests only, no App/Headless production,

no staging/commits)

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (C0-1 Kind+publish+ ack branch; C0-3 chain method; +using AcDream.Content;)
  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs (C0-1 completion-receipt correlation + publish call site; C0-2 BindLiveInputs/ResolveInputs)
  • src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (C0-2 BindLiveInputs forwarder; C0-4 TryCommitParent/CommitWithdrawal fixes; +using System.Numerics;)
  • src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs (C0-2 autonomy level/UsePositionFromServer)
  • src/AcDream.Runtime/GameRuntime.cs (C0-2 wiring the real sources; +using System.Numerics;)
  • tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (C0-1, C0-3, C0-4 tests + FakeCollisionSource; +using AcDream.Content;/ AcDream.Content.Pak;)
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs (C0-1, C0-2 tests + PlacementObserver fake)
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs (C0-4 residence-side test)
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeCharacterStateTests.cs (C0-2 autonomy tests)

Still dormant / no production caller flipped (per pinned scope)

Nothing in App/Headless was touched; Execute/RegisterEntityWithInitialResidence still have zero production callers (unchanged from the executor handoff). RuntimePlacementPresentationSink/HeadlessRuntimePlacementProjectionSink will need an ExecutorCompleted-handling branch added when a LATER cutover slice (C1+) actually starts calling Execute in production - flagging this explicitly as the next slice's concern, not a gap in C0.

SUPERSEDED by the review fix round below: the App/Headless sink untouched-claim above no longer holds - F1 sanctioned a scoped exception (exactly 3 sink files). See the fix-round section for the full disposition.


C0 REVIEW FIX ROUND (F1-F5)

Both independent reviews returned FAIL with converging findings. Per the coordinator: all five retail semantic questions verified CLEAN (autonomy_level != 2 derivation exact; distance basis matches retail + legacy path fallback; mover chain preserves prerequisite-B exactness; ExecutorCompleted unambiguously acknowledge-only; the LeaveWorld omission in TryCommitParent is REQUIRED per retail set_parent 0x00515A90:283832-283833's single gated leave_world - a second one would double-leave-world with no retail counterpart).

F1 (MAJOR, arch) - sink acknowledge-and-ignore. FIXED. SCOPE EXPANSION

SANCTIONED for exactly 3 files, no route flips, no other App/Headless changes.

Root cause: HeadlessRuntimePlacementProjectionSink.cs:51 (is not Place -> false), RuntimePlacementPresentationSink.cs:89-96 (_ -> false), LiveEntityRuntime.cs:969-976 (_ -> false) all silently reject ExecutorCompleted, and RuntimePlacementProjectionSubscription treats a false return on the FIFO head as "leave pending" - the first ExecutorCompleted reaching a production sink (at a future cutover slice) would permanently wedge the entire ordered placement stream behind it. Fixed all three: added an explicit if (Kind is Discard or ExecutorCompleted) return true;-shaped early return BEFORE each file's record-lookup/portal-shape gate (never letting ExecutorCompleted depend on a lookup that can legitimately fail for unrelated reasons). Provably inert today - PublishExecutorCompletion has zero production callers (Execute/RegisterEntityWithInitialResidence are both unreached) - documented in both the code comments and the new tests. Tests: RuntimePlacementPresentationSinkTests.ExecutorCompleted_IsAckOnlyNoOpEvenWhenTokenIsStaleOrSidecarIsGone (App, mirrors the existing Discard test exactly, proves ack regardless of a completely bogus/stale token) and HeadlessSessionHostTests.ExecutorCompletedReceiptIsAcknowledgeOnlyRegardlessOfRecordValidity (Headless, mirrors PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly's stale-incarnation half).

F2 (MAJOR, both reviewers) - completion-receipt lifecycle. FIXED.

Four sub-fixes, all landed:

  1. Register-before-publish: PublishExecutorCompletion gained a beforePublish: Action<RuntimePlacementProjectionToken>? callback invoked AFTER the token is added to _pendingProjection but BEFORE PublishPlacement's synchronous observer dispatch. The executor's ExecuteCore Released case now registers _completionReceipts[key] inside that callback (capturing receipt via a local completedReceipt
    • an out parameter cannot be captured by a lambda) - a subscriber reading the correlation back from inside its OWN OnPlacement now always finds it.
  2. Ack-driven removal: new RuntimeSetPositionState.BindExecutorCompletionAcknowledgement(Action<RuntimeEntityKey,ulong>) (mirrors RuntimeInitialCreateResidenceState.BindRetirementNotification's existing one-bound-delegate shape), invoked from AcknowledgeProjection's ExecutorCompleted branch the moment a host acknowledges. Bound in all 3 RuntimeEntityObjectLifetime constructors to InitialCreateExecution.ForgetCompletionReceipt(key, sequence) - a new executor method that removes exactly the matching (key, sequence) entry (exact-sequence-checked, so a NEWER completion under a reused key survives).
  3. DiscardProgress/DiscardAll: both now unconditionally reap _completionReceipts (Remove/Clear respectively) - DiscardProgress removes it EVEN WHEN _progress no longer tracks the key (the drain already removed its own Progress entry before publishing the completion), proven by a dedicated test.
  4. Ownership/convergence: added PendingCompletionReceiptCount to the executor and folded it into RuntimeEntityObjectOwnershipSnapshot/IsConverged (appended as the last positional field with a = 0 default, following this record's own established extension convention) - chosen semantics: non-zero while unacknowledged, zero exactly at acknowledge, mirroring RuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount's existing "unacknowledged receipt is outstanding debt, gated by IsConverged" shape (documented as such, in contrast with the adjacent diagnostic-only ReplayFailureCount). Tests (RuntimeInitialCreateContinuationExecutorTests.cs): ExecutorCompletion_ReceiptIsReadableFromWithinTheSameSynchronousOnPlacementDispatch, ExecutorCompletion_ConvergenceLedgerCountsAnUnacknowledgedReceiptAsOutstandingDebtUntilAcknowledged, ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgress, ExecutorCompletion_CorrelationEntryIsReapedByDiscardAll (the latter two split into single-entity tests after discovering combining them with a second entity in the SAME lifetime hit exact-head contention - see debugging notes below).

F3 (MAJOR, arch) - nullable local-player-position fallback. FIXED.

GameRuntime.cs:280's context.Movement.Controller?.Position ?? Vector3.Zero fabricated a distance basis of literal (0,0,0) whenever the login-window drain ran before the local player's own controller existed - a nearby remote entity could misclassify as >96m and hard-snap where retail would interpolate. Fixed per the contract's own fallback rule: _localPlayerPosition is now Func<Vector3?>? (was Func<Vevtor3>?), BindLiveInputs's parameter type updated to match, ResolveInputs now does _localPlayerPosition?.Invoke() is { } localPlayerPosition (both "unbound" AND "bound-but-returns-null" fall back to the caller struct's PlayerDistance identically), and GameRuntime.cs now binds () => context.Movement.Controller?.Position directly (a PlayerMovementController?.Position already yields Vector3? via null-conditional propagation - no ?? Vector3.Zero needed or wanted). Test (both directions, per the ask): BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull - entity 1 has a bound NON-null near position (proves the bound value, not the caller struct's far value, wins -> Interpolate); entity 2 flips the SAME bound source to null (proves it falls back to the caller struct's far value, not Vector3.Zero -> SetPositionSimple/StopInterpolating).

F4 (MINOR) - TryCommitParent comment narrowed. FIXED.

Rewrote the C0-4(a) comment: no longer claims "the SAME flow every sibling relation commit uses" wholesale (which would imply LeaveWorld too) - now states the Forget/PreferCancellation sequence is shared for THIS part of the job, and explicitly documents the deliberate LeaveWorld omission citing retail set_parent (0x00515A90, lines 283832-283833)'s single gated leave_world call, which this method's staged/deferred-replay commit already represents - a second LeaveWorld here would double-leave-world with no retail counterpart.

F5 (NOTEs). FIXED.

(a) TrySetAutonomyLevel's doc comment now notes retail's setter ALSO sends SendAutonomyLevelEvent (pseudo-C 699550) and that any FUTURE host exposure of this setter must carry the equivalent outbound event, not just the field write. (b) FullAutonomyLevel's doc comment corrected from "No in-game caller of CommandInterpreter::SetAutonomyLevel exists" (implying zero callers anywhere) to the precise claim: exactly ONE retail caller exists, the startup construction path at pseudo-C 94102 (the constructor's own default at 699752 is a direct field write, not a SetAutonomyLevel call, so it doesn't count as a second caller).

Debugging round for the F2/F3 tests (both caught by the test run, both

root-caused, not worked around)

  1. ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgressAndDiscardAll (combined, single lifetime, two entities) failed at the SECOND entity's CompleteInitialPlacement - root cause: the FIRST entity's ExecutorCompleted receipt was left UNACKNOWLEDGED in _pendingProjection (by design, to prove DiscardProgress reaps the correlation cache independent of the normal ack path) - but that means it permanently sat at the exact head, blocking ANY later entity's Place receipt from ever being acknowledged (DiscardProgress only touches the correlation cache, never _pendingProjection itself - a deliberate, narrow scope). Fixed by splitting into two single-entity tests (...ReapedByDiscardProgress, ...ReapedByDiscardAll), each with its own fresh lifetime - eliminates the exact-head contention entirely rather than working around it.
  2. BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull hit the SAME class of bug for the SAME reason (entity 1's completed drain left an unacknowledged ExecutorCompleted blocking entity 2). Fixed by inserting an explicit peek+acknowledge of entity 1's completion between the two entities (matching the pattern already established in BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct from the prior round).

Fix-round final gates (all green)

  1. dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release: 0/0.
  2. dotnet build tests/AcDream.Runtime.Tests -c Release: 0/0.
  3. Complete AcDream.Runtime.Tests: 921/921 passed (916 + 5 new: 4 F2 + 1 F3; see above for exact names).
  4. dotnet build tests/AcDream.App.Tests -c Release: 0 errors, 3 pre-existing warnings (CS8767, unrelated to this change).
  5. dotnet build tests/AcDream.Headless.Tests -c Release: 0/0.
  6. dotnet test tests/AcDream.App.Tests -c Release --no-build: 4028/4028 passed, 3 skips (4027 baseline + 1 new: the F1 App-sink test).
  7. dotnet test tests/AcDream.Headless.Tests -c Release --no-build: 77/77 passed (76 baseline + 1 new: the F1 Headless-sink test).
  8. dotnet build AcDream.slnx -c Release --no-incremental (clean rebuild for an authoritative count): 0 errors, EXACTLY 21 warnings (matching the documented baseline precisely - zero new warnings across the whole fix round, including the 3 sanctioned sink-file edits).
  9. Complete solution (dotnet test AcDream.slnx -c Release -m:1): every project green - App 4028/3 skip, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4242/1 skip, Headless 77, Runtime 921, UI.Abstractions 543. 0 failed anywhere.
  10. git diff --check: clean (only pre-existing LF/CRLF metadata notices; confirmed by grepping the raw output for anything OTHER than that pattern - zero matches).
  11. git status: exactly the C0 file set plus 5 NEW files from this fix round (the 3 sanctioned sink files + their 2 test files), plus the 8 pre-existing protected dirty paths untouched. Nothing staged.

Files touched in THIS fix round (in addition to the C0 file set above)

  • src/AcDream.App/World/LiveEntityRuntime.cs (F1)
  • src/AcDream.App/World/RuntimePlacementPresentationSink.cs (F1)
  • src/AcDream.Headless/Hosting/HeadlessRuntimePlacementProjectionSink.cs (F1)
  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (F2: beforePublish param + BindExecutorCompletionAcknowledgement + ack-branch notification)
  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs (F2: ForgetCompletionReceipt/PendingCompletionReceiptCount/DiscardProgress+ DiscardAll reap/register-before-publish call site; F3: nullable field/ ResolveInputs)
  • src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (F2: ownership snapshot field + CaptureOwnership wiring + acknowledgement binding; F3: BindLiveInputs signature; F4: comment)
  • src/AcDream.Runtime/GameRuntime.cs (F3: nullable binding call site)
  • src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs (F5a/F5b: comments)
  • tests/AcDream.App.Tests/World/RuntimePlacementPresentationSinkTests.cs (F1 test)
  • tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs (F1 test)
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs (F2 x4 + F3 x1 tests)

C2 — placement allocation budget (new implementer session)

Worktree: C:\Users\erikn.codex\worktrees\af5e\acdream (branch codex/port-claude-agents) HEAD verified at session start: 6460596b56

Baseline reproduction

  • dotnet build AcDream.slnx -c Release green, 21 pre-existing warnings in unrelated files (not introduced by this session).
  • Ran WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover (tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs:253). Baseline via temporary forced-failure instrumentation (reverted before any real edits; git diff was clean after revert): 2032 B/op, stable across 3 repeat runs. NOT 1880 as the stale research doc (docs/research/2026-07-31-canonical-set-position.md lines ~326-333) states -- the number drifted upward since C0 landed additional bookkeeping (ExecutorCompleted receipt plumbing etc). This is already within 16 bytes of tripping the 2048 cap on its own -- confirms urgency.

Bisection (temporary instrumentation, reverted before real edits)

Added static accumulator fields + GC.GetAllocatedBytesForCurrentThread() brackets around: Apply() -> BeginAcceptedPlacementCore (BEGIN) and SubmitPreparedPlacementCore (SUBMIT); inside SubmitPreparedPlacementCore: prefix-to-SetPosition-call (PREFIX), the _physics.Engine.SetPosition(...) call itself (SETPOSITION), CommitCanonical (COMMIT), PublishProjection (PUBLISH), tail/Outcome (TAIL); AcknowledgeProjection (ACK, wrapped core in try/finally).

Result (per-op, averaged over 1000 measured iterations, 64 warmups):

TOTAL=2032 BEGIN=752 SUBMIT=1160 SETPOSITION=584 COMMIT=0 PUBLISH=208 ACK=120
PREFIX=144 MID=0 TAIL=0

Outer brackets are self-consistent to the byte: BEGIN+SUBMIT+ACK = 752+1160+120 = 2032 = TOTAL exactly. The inner SUBMIT subdivision only sums to 936 (144+584+0+208), leaving ~224 B/op I could not pin down further within budget (possibly SortedDictionary rebalancing spillover attributed oddly across adjacent brackets, possibly a measurement-granularity artifact of many small brackets in one method -- the coarse brackets are trustworthy, the finest subdivision is not). Decided not to chase further since the two big, well-understood root causes below match the project's established "pool the envelope, cache the delegate" pattern and account for the majority of the budget.

Root causes identified (confirmed by direct code reading + the

bisection above)

  1. BEGIN (752 B, BeginAcceptedPlacementCore): new Operation { ... } (private sealed class Operation, ~25 properties incl. embedded RuntimeSetPositionCommand / PhysicsSetPositionResult structs) allocated FRESH on every accepted-placement call; the old Operation for the same entity key is simply dropped (_operations[key] = replacement;) and becomes garbage every single call.

  2. SETPOSITION (584 B, two call sites: SubmitPreparedPlacementCore ~line 2402 and RetryDeferred ~line 3536): report => _physics.HandleSetPositionCollisions(operation.Record, ..., canonicalCommand.GameTime, ...) is a closure capturing this + operation (+ canonicalCommand at the first site) -- a fresh compiler-generated display-class allocated on EVERY call. Confirmed every field the closure reads is already reachable from operation alone (operation.Command.GameTime == canonicalCommand.GameTime because operation.Command = canonicalCommand; runs earlier in the same method) -- the closure captures nothing that isn't already sitting on operation. Fixable with ONE delegate cached for the RuntimeSetPositionState instance's lifetime, reading the "current operation" off a small reusable Stack pushed/popped around the SetPosition call (defends against theoretical re-entrant/nested SetPosition calls inside PhysicsEngine -- TransitionScratchArena's ActiveDepth/Capacity gate implies nesting is possible at that layer, even though HandleSetPositionCollisions itself never calls back into RuntimeSetPositionState).

Plan

  1. Fix the closure (lowest risk, no behavior change) -- both call sites.
  2. Pool the Operation object (_operationPool, convert required {get;init;} to settable + add a Reset(...), return retired Operations to the pool at every site that currently discards one for good). Audit every _operations.Remove(...) / displacement site so nothing else still holds the recycled instance (per "no workarounds": a pool that resurrects stale state is worse than the allocation it replaces).
  3. Re-measure; tighten the gate to the new number with justified headroom.

Fixes implemented

  1. Cached collision-report delegate (fixes the closure at both _physics.Engine.SetPosition call sites: SubmitPreparedPlacementCore and RetryDeferred). Added CollisionCallbackContext (readonly record struct) plus Stack<CollisionCallbackContext> _collisionCallbackContexts plus Func<PhysicsSetPositionCollisionReport, bool> _handleSetPositionCollisionsCallback (built ONCE in the constructor, bound to instance method HandleSetPositionCollisionsCallback which reads _collisionCallbackContexts.Peek()). Each call site now does Push(context) / try { SetPosition(request, cached delegate) } / finally { Pop() }. The stack (not a single field) defends nested/re-entrant SetPosition calls at the PhysicsEngine layer.

  2. Operation pooling (fixes new Operation in BeginAcceptedPlacementCore). Converted every Operation property from required ... { get; init; } to plain { get; set; }, added ResetAllFieldsToDefault(), added _operationPool (Stack<Operation>, capped at 64), RentOperation() / RetireOperationToPool(Operation). BeginAcceptedPlacementCore now rents+field-sets instead of new Operation {...}. Operations are retired at the 3 places an Operation is permanently removed from _operations: both branches inside AcknowledgeProjection Place/non-lost-cell paths, and inside CancelCoreDeferred (the single removal chokepoint every CancelCore overload funnels through).

    CRITICAL BUG FOUND AND FIXED during verification: the first pass reset fields at RETIREMENT time (inside RetireOperationToPool). This broke ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation - CommitCanonical remote branch builds a ContactCommitGuard that captures the live Operation, then invokes remote.HitGround() / LeaveGround() - and retail lets that callback synchronously call BACK INTO BeginAcceptedPlacement for the SAME entity, which displaces and retires the very operation the guard is holding, mid-guard. guard.IsCurrent() (IsCanonicalPlacementCommitCurrent) reads that operation ORIGINAL PositionAuthorityVersion/SpatialAuthorityVersion AFTER the callback returns - resetting those fields at retirement time zeroed them out from under the still-executing outer frame, making an in-flight valid commit look stale and silently dropping its shadow update (test caught it: shadow.Position stayed at the pre-spawn 10,20,7 instead of the committed 13,18,~6.95). Fix: move the reset from RetireOperationToPool to RentOperation (reset happens the moment an instance is about to be handed out for reuse, not the moment it is taken out of _operations). A retired-but-not-yet-rented instance now keeps its true last-known field values until something actually reuses it - any outer frame with a captured reference gets a brief, safe, read-only window on stale-but- correct data instead of zeroed garbage. Full 921/921 Runtime tests pass after this fix (before the fix: 920/921, this exact test failing).

    Lesson worth carrying into memory: reentrant displaced-operation pooling must reset at RENT time, never at RETIREMENT time, whenever a captured reference (guard/closure/local) might still read the object fields after retirement but before the next real use.

  3. Eliminated LINQ .First() boxing on _pendingProjection (SortedDictionary<ulong, RuntimePlacementProjectionSnapshot>). Added FirstPendingProjection() using a plain foreach (resolves to the concrete struct-returning GetEnumerator(), not the boxing IEnumerable&lt;T&gt; interface one that Enumerable.First&lt;T&gt; forces). Replaced all 3 call sites (TryPeekProjection, AcknowledgeProjection guard, HasPendingProjectionThrough).

Root cause: SortedDictionary Add allocations (about 208 B/op) and

AcDream.Core PhysicsEngine internals (about 520 B/op) - NOT fixed

Confirmed via targeted diagnostic instrumentation (added, measured, fully reverted before finalizing - git diff was clean after each revert):

  • _pendingProjection.Add(sequence, snapshot) (PublishProjection) costs about 208 B/op - SortedDictionary red-black tree node allocation, inherent to the BCL type with no pooling hook. Replacing _pendingProjection data structure to chase this would touch 10+ methods relying on its ordering and FIFO-peek semantics (quiescence tracking, withdrawal acknowledgement, etc.) - judged too invasive and risky for the remaining reward given the result is already well under the 2048 cap.
  • _physics.Engine.SetPosition (AcDream.Core/Physics/PhysicsEngine.cs) costs about 520 B/op split as INIT=144 (InitializeSetPositionTransition), INNER=344 (SetPositionInternal/scatter solve), FINAL=32 (the queryFootprint.OrderedIds.ToImmutableArray() call - a genuine single- element ImmutableArray materialization from the outdoor-adjustment query footprint, not an artifact). RENT=0 (Transition pooling already zero- alloc). This lives entirely inside AcDream.Core, shared physics infrastructure used far beyond RuntimeSetPositionState - out of this slice Runtime-only hard-rule scope, and not touched.
  • Confirmed HandleSetPositionCollisionReports / RuntimeCollisionReportingState.HandleReports (Runtime-side) do NOT allocate in the test steady state (no collisions ever occur - collidedObjectIds stays empty, no OwnerState ever gets created for this entity) - ruled out as a contributor.

Final verification

  • dotnet build AcDream.slnx -c Release: 0 errors, 21 pre-existing warnings (all in files untouched by this session - confirmed identical to the pre-session baseline build).
  • WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover: passes at the new Assert.InRange(allocated / iterations, 1L, 1_536L) gate. Measured value stable at exactly 944 B/op across 5+ repeat runs (down from 2032 B/op baseline, 53.5% reduction). 1,536 keeps about 60% headroom.
  • Complete AcDream.Runtime.Tests: 921/921 pass (0 skips).
  • Complete solution (dotnet test AcDream.slnx -c Release -m:1): every project green - App 4028/3 skip, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4242/1 skip, Headless 77, Runtime 921, UI.Abstractions 543. 10,716 total, 0 failed, 4 skipped (all pre-existing skips).
  • git diff --check: clean (only the same pre-existing LF/CRLF metadata notices on the 8 protected dirty files from before this session; grepped for anything else - zero matches).
  • git status: exactly 2 files changed by this session (src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs, tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs) plus the 8 pre-existing protected dirty paths, untouched, exactly as they were at session start. Nothing staged, nothing committed, HEAD unchanged at 6460596b56.

Files touched this session

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (net +340/-69 lines vs the C0 baseline: Operation class made poolable plus ResetAllFieldsToDefault, CollisionCallbackContext plus cached delegate plus stack, RentOperation/RetireOperationToPool, FirstPendingProjection, BeginAcceptedPlacementCore restructure, both SetPosition call sites, both AcknowledgeProjection removal branches, CancelCoreDeferred retirement call site)
  • tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (26 lines: only the regression gate comment and threshold, from 2_048L to 1_536L - zero other test changes; the fix required no test edits beyond the gate itself, confirming the pooling/delegate/LINQ changes are fully behavior-preserving)

C2 review-fix round (F1-F4)

Both independent reviews FAILED the original C2 landing with four findings. Fixed all four; re-verified B/op unchanged (944, same as before this round) and the complete Runtime + solution suites green.

F1 (MAJOR, retail) - CommitCanonical post-callback reads/writes

Root cause: CommitCanonical read operation.PositionAuthorityVersion/ SpatialAuthorityVersion/Command.GameTime/PreviousContact/PreviousOnWalkable/ Key/Command.ShadowWorldOffsetX/Y AFTER invoking the ground-edge HitGround/ LeaveGround callbacks (via PhysicsObjUpdate.CommitSetPositionContactTransition). A synchronous cancel-then-begin (or begin-twice) chain for the SAME entity retires-then-rents (LIFO) the SAME Operation instance mid-callback, so those post-callback reads could observe a reset-or-repurposed operation.

Fix: hoisted every scalar CommitCanonical still needs into locals BEFORE the callback (operationKey, positionAuthorityVersion, spatialAuthorityVersion, sourceVelocityAuthorityVersion, commandGameTime, previousContact, previousOnWalkable, shadowWorldOffsetX/Y). ContactCommitGuard now captures positionAuthorityVersion/spatialAuthorityVersion as plain values instead of holding an Operation reference. IsCanonicalPlacementCommitCurrent's Operation parameter was replaced with the two explicit scalar parameters.

FALSE START (caught by full-suite regression, not left in): first pass ALSO added an operationToken identity comparison inside IsCanonicalPlacementCommitCurrent, intending to detect the exact repurposing. This broke two EXISTING tests (ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation and, after fixing that, exposed a status regression in the same test) because retail's OWN contract is that an in-flight ground-edge commit for a DISPLACED operation must still complete its physical settle (contact transition + shadow sync) - adding identity-based rejection there incorrectly aborted a commit the test explicitly requires to succeed. Reverted the token check from IsCanonicalPlacementCommitCurrent entirely (doc comment there now explains why identity must NOT be checked at that layer). The REAL self-aliasing hazard was two levels up: BeginAcceptedPlacementCore's final IsCurrent(replacement) check (after PublishPlacement can reentrantly retire+rent the SAME replacement instance for an inner Begin) and SubmitPreparedPlacementCore/RetryDeferred's post-CommitCanonical decision of Cancelled-vs-Committed (CommitCanonical can legitimately succeed for a displaced operation - the OUTER caller's own invocation still must not publish a Place projection nobody will ever acknowledge). Fixed both by comparing a LOCAL, pre-reentrancy token/parameter (token already a parameter in SubmitPreparedPlacementCore; hoisted operationToken added to RetryDeferred; the token local already existed in BeginAcceptedPlacementCore) against a FRESH _operations lookup - never against the potentially-repurposed operation reference's own fields.

Regression test: ReentrantCancelThenBeginRecyclesInstanceButCollisionReportUsesPreCallbackValues. Drives Cancel(publishWithdrawal:false) then BeginAcceptedPlacement from inside OnHitGround - LIFO pool guarantees the SAME instance is retired then immediately rented for the new ("recycled") operation, a strictly more adversarial recycle than the pre-existing test. Asserts (via CollisionReportObserver subscribed to CollisionReports) that the environment-collision report's RecipientWasInContact reflects the ORIGINAL pre-callback PreviousContact, and that the report fires at all (proving PreviousOnWalkable also carried through correctly - the report only fires when !previousOnWalkable && body.OnWalkable, so a corrupted PreviousOnWalkable would silently suppress it). Verified DISCRIMINATING: reverted the hoist locally, confirmed the test fails (empty report collection), then restored the fix.

F2 (MAJOR arch + MINOR retail) - pool invisible to reset/dispose ledger

_operationPool was never cleared by ClearOwnedState (called from both ResetSession and Dispose), so up to 64 pooled instances could retain full previous-generation entity graphs across a session boundary. Added _operationPool.Clear() inside ClearOwnedState (comment explains why this is safe unlike RetireOperationToPool: a session clear cannot be reentered from inside itself). Added PooledOperationCount to RuntimeSetPositionOwnershipSnapshot (new trailing field, single construction site updated), deliberately EXCLUDED from IsConverged (doc comment explains pooled idle capacity is legitimate mid-session).

Regression tests: OperationPoolClearsOnResetSession, OperationPoolClearsOnDispose - both drive an Apply+Acknowledge cycle to get >=1 pooled operation, assert PooledOperationCount >= 1, then call ResetSession/Dispose and assert it drops to exactly 0. Verified DISCRIMINATING: commented out the _operationPool.Clear() line, confirmed both tests fail (1 instead of 0), restored the fix.

F3 (MINOR arch) - no-self-aliasing invariant + InPool guard

Reordered BeginAcceptedPlacementCore: RentOperation() now happens AFTER the displaced operation's CancelCoreDeferred retire (previously rent happened first). This lets a single-entity churn cycle legitimately reuse the exact retired instance (LIFO) instead of drawing a different one - safe because every field this method needs from displaced was already captured into locals before the retire point (unchanged from the original C2 landing). Added Operation.InPool (bool, defaults false): set true in RetireOperationToPool right before pushing, cleared in ResetAllFieldsToDefault (called from RentOperation right after popping). RetireOperationToPool now throws InvalidOperationException if called on an instance that is already InPool (double-retire without an intervening rent would silently duplicate the instance in the pool stack).

This reorder, on its own, reintroduced a DIFFERENT self-aliasing hazard at
BeginAcceptedPlacementCore's own final line (`IsCurrent(replacement) ? token
default) - caught by the EXISTING test ReentrantBeginDuringDiscardCannotBeOverwrittenByOuterBegin (a PublishPlacement-triggered reentrant Begin during a Discard notification can retire+rent the SAME replacementinstance for an inner operation, makingIsCurrent(replacement)a self-referential tautology that wrongly reports "still current"). Fixed by comparing_operations[key].Tokenagainst the LOCALtoken(captured at function entry, immune to reentrant corruption) instead of callingIsCurrent(replacement)`.

No new dedicated F3 test beyond the reflection test (F4) and the pre-existing ReentrantBeginDuringDiscardCannotBeOverwrittenByOuterBegin, which now exercises the corrected self-aliasing check directly.

F4 (MINOR arch) - two remaining new Operation() sites + completeness net

Converted the two surviving new Operation { ... } object-initializer sites (inside ParkCollisionResidents and CreateWithdrawalOperation) to RentOperation() + field assignment, so all construction flows one path. Both call sites confirmed safe to route through the pool (no live displaced operation exists at either construction point - ParkCollisionResidents explicitly skips keys already in _operations; CreateWithdrawalOperation's sole caller, Cancel, always runs CancelCoreDeferred against the same key immediately before).

Added OperationResetAllFieldsToDefaultTouchesEveryDeclaredField: a reflection-based test (Operation is private, so ONLY reflection can reach it from a test project even with InternalsVisibleTo) comparing typeof(Operation).GetFields(Instance|NonPublic|Public) against a hardcoded, maintained list of the 33 expected backing-field names (derived from a plain property-name list transformed to <Name>k__BackingField form). Verified DISCRIMINATING: added a temporary dummy property to Operation, confirmed the test fails with a clear collection-diff showing the new backing field, removed it.

Final verification

  • Release build: 0 errors, 21 pre-existing warnings (unchanged from baseline, zero new).
  • WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover: still passes at the 1,536L gate. Re-measured exact value 3x: 944 B/op, IDENTICAL to before this fix round - confirms every F1-F4 hoist/guard is stack-only/negligible (one extra bool field, one extra int in a value-type snapshot struct - no heap allocation added).
  • Complete AcDream.Runtime.Tests: 925/925 (921 + 4 new: 1 F1 regression + 2 F2 reset/dispose + 1 F4 reflection).
  • Complete solution (dotnet test AcDream.slnx -c Release -m:1): every project green - App 4028/3 skip, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4242/1 skip, Headless 77, Runtime 925, UI.Abstractions 543. 10,720 total, 0 failed.
  • git diff --check: clean (same pre-existing LF/CRLF metadata notices on the now-9 touched-by-someone files - the 8 pre-existing protected dirty paths plus RuntimeSetPositionState.cs itself; RuntimeSetPositionStateTests.cs shows no notice at all).
  • git status: still exactly the 2 files this session owns (RuntimeSetPositionState.cs, RuntimeSetPositionStateTests.cs) plus the 8 pre-existing protected dirty paths untouched. Nothing staged, nothing committed, HEAD unchanged at 6460596b56.

Files touched this fix round

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (net diff vs C2 landing: 800 lines changed - Operation.InPool + doc, ResetAllFieldsToDefault InPool line, CommitCanonical hoisting rewrite, ContactCommitGuard/ IsCanonicalPlacementCommitCurrent signature change, IsVelocityCurrent overload, BeginAcceptedPlacementCore reorder + final-check fix, SubmitPreparedPlacementCore + RetryDeferred post-commit currency checks, RuntimeSetPositionOwnershipSnapshot.PooledOperationCount + ClearOwnedState pool clear, ParkCollisionResidents + CreateWithdrawalOperation routed through RentOperation)
  • tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (net diff: 255 lines - 4 new tests + System.Reflection using)

C2 round 3 (rent-after-retire regression) + addendum (A1/A2)

The F3 reorder (rent AFTER retire, so a single-entity churn cycle reuses the exact retired instance) makes IsCurrent(Operation) (ReferenceEquals + six field comparisons read off the SAME instance) a tautology once a reentrant cancel-then-begin recycles that instance for a different logical operation at the same key. Every one of the ~20 IsCurrent(operation) call sites needed auditing: either convert to a captured-token-vs-fresh-lookup check, or prove (with a per-site comment) that no reentrancy point intervenes between the last fresh lookup and the check.

The fix

Extracted IsOperationStateConsistent(Operation) from IsCurrent's six non-identity field comparisons. IsCurrent(Operation) is now _operations.TryGetValue(operation.Key, out current) && ReferenceEquals(current, operation) && IsOperationStateConsistent(operation)

  • same behavior as before, just factored so the new helper below can share the state check. Added:
private bool IsCurrentByToken(
    RuntimeEntityKey key,
    in RuntimeEntityPlacementToken capturedToken,
    [NotNullWhen(true)] out Operation? operation)

Does a FRESH _operations.TryGetValue + current.Token == capturedToken + IsOperationStateConsistent(current), and hands back the fresh (possibly different-instance) Operation on success. [NotNullWhen(true)] lets callers reuse a single out binding across an if (!IsCurrentByToken(...)) return ...; guard without a separate null-forgiving cast.

CancelCore(Operation expected, ...) (ReferenceEquals shape - the OTHER regression source the reviewer named, "shares the shape with a worse outcome: cancelling the newer operation") became CancelCore(RuntimeEntityKey key, in RuntimeEntityPlacementToken expectedToken, bool preserveLostFamily = false): fresh _operations.TryGetValue(key, ...) + current.Token != expectedToken -> no-op. All 5 callers converted (ForgetExactPlacement, RetireDormantLocalActivation, RetireDormantLocalActivationToken, SubmitPreparedPlacementCore's post-CommitCanonical-failure branch, RetryDeferred's equivalent) - each now passes its own captured (key, token) instead of an Operation reference.

The exact bug the reviewer traced (Cancel path) - FIXED

Cancel(record, bool) creates a withdrawal operation, installs it at the key, then calls PublishPlacement(cancelledOld) (reentrancy point: an IRuntimePlacementObserver subscriber can call Begin/Cancel for the same entity from inside this synchronous dispatch), then previously checked IsCurrent(operation) against the now-possibly-recycled reference. Fixed: capture operation.Token into a local BEFORE PublishPlacement, then IsCurrentByToken(key, capturedToken, out operation) afterward.

Subtler hazard found during the audit, not named by the reviewer:

RebindQuiescedDeferredOperations

foreach (Operation operation in _operations.Values.ToArray()) snapshots live REFERENCES. An earlier iteration's RetryDeferred call (itself reentrancy-exposed) can recycle a LATER iteration's not-yet-reached Operation instance before the loop reaches it - a stale-reference bug independent of the Cancel/CancelCore ones. Fixed by snapshotting (Key, Token) VALUE pairs first, then resolving fresh via IsCurrentByToken at the top of each iteration instead of trusting the snapshotted reference.

Addendum A1 (retail MINOR) - CommitCanonical's 4 post-callback writes

CommitCanonical's tail (operation.ExactCellId/Result/WakeableLostCell/ EnteringWorldFromCelllessResidence = ...; CancelLostFamilyDeadlines(operation)) still targeted the poolable instance directly. With rent-after-retire, a nested cancel-then-begin recycles the instance before this block runs, and a bare Begin never advances PlacementCommitVersion - invisible to the settle-layer record-state checks - so the writes could land on the WRONG (freshly-begun) operation. Fixed: hoisted operationToken = operation.Token (already had operationKey from F1) at the SAME pre-callback point as every other F1 hoist, then gated the whole write block behind a fresh _operations.TryGetValue(operationKey, out currentOperation) && currentOperation.Token == operationToken check - skip the writes (not the whole commit) if the token no longer matches, matching retail's already-unconditional physical settle (only Runtime's OWN bookkeeping is conditional). The final IsCanonicalPlacementCommitCurrent(..., requireSpatialRoot: true) return is UNCHANGED - still identity-agnostic, per the layer-separation rule below.

Addendum A2 (doc hygiene) - stale comment on RetireOperationToPool

The old comment claimed IsCanonicalPlacementCommitCurrent "additionally compares the operation's Token by value" - that guard was a round-3 FALSE START (see the F1 section above) that was reverted before F1 even landed; the comment was never updated and contradicted the real mechanism. Rewritten to state the true safety contract: hoisted locals (F1) + call-site captured-token-vs-fresh-lookup (this round), with the settle path deliberately identity-agnostic per retail's unconditional SetPositionInternal completion.

Layer separation (unchanged, reaffirmed by the retail reviewer)

IsCanonicalPlacementCommitCurrent takes NO identity/Token parameter, by design - retail's SetPositionInternal settle completes unconditionally for a displaced operation (see ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation, from the F1 false-start above). Identity gates belong ONLY at Runtime-owned publication/cancellation/ownership decisions (SubmitPreparedPlacementCore's Cancelled-vs-Committed decision, Cancel's withdrawal publish, CancelCore's match check, CommitCanonical's A1 bookkeeping-write gate) - never at the settle/currency check itself. Did not touch this layer in round 3 beyond re-confirming it via the reverted false-start being re-tried and failing the same way it did in round 2 (not re-attempted this round - the round-2 false-start's lesson was already incorporated).

Per-site audit table (all sites this class calls

IsCurrent(Operation)/ReferenceEquals on an Operation across a reentrancy-exposed span)

CONVERTED to captured-token-vs-fresh-lookup (IsCurrentByToken or CancelCore(key, token)):

  1. Cancel(record, bool) post-PublishPlacement(cancelledOld) check - the reviewer's named bug.
  2. SubmitPreparedPlacementCore post-_physics.Engine.SetPosition(...) check (first of two, immediately after the collision-callback-bearing call).
  3. SubmitPreparedPlacementCore post-SetPosition second check (after the TryGetBlockingQuiescence/deferred branch, before CommitCanonical).
  4. SubmitPreparedPlacementCore's CancelCore(token.Entity, token) call on CommitCanonical failure - the reviewer's named CancelCore-shape bug.
  5. RetryDeferred's two analogous post-SetPosition checks (same shape as #2/#3, via hoisted operationToken).
  6. RetryDeferred's CancelCore(operationToken.Entity, operationToken) call on CommitCanonical failure (same shape as #4).
  7. RebindQuiescedDeferredOperations's per-entry resolve - converted from a snapshotted-REFERENCE loop to a snapshotted-(Key,Token) loop + fresh IsCurrentByToken (the subtler hazard found during the audit, not named by the reviewer).
  8. ForgetExactPlacement's CancelCore(token.Entity, token) call.
  9. RetireDormantLocalActivation's CancelCore call.
  10. RetireDormantLocalActivationToken's CancelCore call.
  11. CommitCanonical's A1 bookkeeping-write gate (fresh lookup + currentOperation.Token == operationToken before the ExactCellId/Result/WakeableLostCell/EnteringWorldFromCelllessResidence/ CancelLostFamilyDeadlines writes) - addendum A1.

PROVEN SAFE WITH COMMENT (fresh lookup + Token/state check on the same line or immediately prior, nothing reentrant intervenes before the check runs): 12. IsPlacementCurrent - fresh lookup + Token check inline, no reentrancy between them. 13. PrepareDormantLocalActivationOwnership - fresh lookup + Token check earlier in the same method, dormant-family code with no production callers. 14. PrepareMover - same shape as #13. 15. IsExactPreparedPlacementCurrent - same shape. 16. TryEvaluateDormantLocalActivation - ReferenceEquals variant, safe because handleCollisions: null on this call means nothing reentrant can run before the check. 17. IsDormantLocalActivationPrephaseCurrent - fresh lookup + Token check earlier in the same method. 18. IsDormantLocalActivationResponseCurrent - same shape. 19. CommitDormantLocalActivationPostCollision - entry AND final-return checks, one comment block covering both; dormant family, no production callers. 20. IsDormantLocalActivationCommitCurrent - same shape as #17/#18. 21. IsExactDormantLocalActivationCurrent - same shape. 22. SubmitPreparedPlacementCore's own entry ownsToken check - the function's own validation, nothing reentrant between the fresh lookup and this check. 23. AcknowledgeProjection's entry check - fresh lookup + ProjectionSequence check, no reentrancy in between. 24. CommitCanonical's entry check (!result.IsCommitted || !IsCurrent(operation)) - every caller (SubmitPreparedPlacementCore, RetryDeferred) passes an operation freshly re-verified immediately before calling CommitCanonical. 25. CommitCollisionGeneration's per-entity loop - added during THIS pass (not flagged by the reviewer, found while double-checking every _operations.TryGetValue site in the file for completeness). Iterates an array of KEYS (never Operation references), fresh lookup + full WakeableLostCell/ExactCellId/CollisionPrefix/CollisionGeneration shape check every iteration - immune to the snapshotted-reference class of bug even though it calls the reentrancy-exposed RetryDeferred per-entity.

OUT OF SCOPE (different class entirely, not an Operation-identity site): 26. ParkCollisionResidentsForQuiescence's ReferenceEquals(current, state) - checks CollisionPrefixQuiescence identity (a class that is never pooled), unrelated to the Operation pool this round's regression lives in.

Round 3 tests

ReentrantBeginDuringCancelPublishCannotBeOverwrittenByOuterWithdraw - the reviewer's named Cancel-path scenario: Apply (pending Place projection) -> observer reentrantly Begins on the Discard notification during Cancel's PublishPlacement -> asserts only the Discard delta published (a stale-reference bug would add a second Withdraw delta stamped with the inner operation's state) and the inner token is still IsPlacementCurrent. VERIFIED DISCRIMINATING: reverted Cancel's IsCurrentByToken check back to IsCurrent(operation), reran - failed exactly as predicted (Assert.Single saw 2 deltas, the second a Withdraw carrying the inner operation's PlacementCommitVersion=2/Sequence=2) - restored the fix.

ReentrantCancelThenBeginDuringCommitFailureLeavesInnerOperationUncancelled

  • the CancelCore-shape scenario: ground-edge HitGround callback does cancel-then-begin (recycling the instance for inner) AND calls lifetime.Entities.AdvancePlacementCommit(record) a second time BEFORE creating inner (so inner snapshots the already-advanced value and stays internally self-consistent, while the OUTER commit's canonicalCommitVersion, captured before the callback, now mismatches) - this makes CommitCanonical's post-callback IsCanonicalPlacementCommitCurrent check fail for the outer commit without needing a full nested SetPosition round-trip, driving SubmitPreparedPlacementCore into PublishCancellation(CancelCore(token.Entity, token)). Asserts outcome.Status == Cancelled and inner is still IsPlacementCurrent afterward. VERIFIED DISCRIMINATING: reverted CancelCore(key, token)'s Token check to accept any match by key alone (simulating the old ReferenceEquals-without-identity shape), reran - failed exactly as predicted (IsPlacementCurrent(inner) false, the sabotaged check retired inner's instance out from under it) - restored the fix.

Final verification (round 3 + addendum)

  • Release build (dotnet build AcDream.slnx -c Release): 0 errors, 21 pre-existing warnings, all in test files this session did not touch (App/ Core test projects) - unchanged from the F1-F4 round.
  • WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover: passes at the existing 1,536L gate - token captures added this round are all stack-only locals/struct fields, no new heap allocation.
  • Complete AcDream.Runtime.Tests: 927/927 (925 F1-F4 baseline + 2 new: the Cancel-path and CancelCore-shape regressions).
  • Complete solution build (dotnet build AcDream.slnx -c Release): 0 errors.
  • git diff --check: exit 0, clean (same pre-existing LF/CRLF metadata notices on the same pre-existing dirty files, RuntimeSetPositionState.cs included - no whitespace-error content).
  • git status/git rev-parse HEAD: still exactly the 2 files this session owns (RuntimeSetPositionState.cs, RuntimeSetPositionStateTests.cs) plus the same pre-existing dirty paths (AGENTS.md, PlayerModeController.cs, PlayerInteractionMovementSink.cs, LiveAnimationPresentationContext.cs, RuntimeRemotePhysicsUpdater.cs, CellTransitTests.cs, Issue133DungeonTeleportPrefixTests.cs, A8CellAudit.csproj) untouched by this session. Nothing staged, nothing committed, HEAD unchanged at 6460596b56.

Files touched this round

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (IsCurrent split into IsCurrent + IsOperationStateConsistent, new IsCurrentByToken helper, CancelCore(Operation) -> CancelCore(key, token) signature change + 5 caller conversions, Cancel/SubmitPreparedPlacementCore/RetryDeferred/ RebindQuiescedDeferredOperations converted call sites, CommitCanonical's A1 bookkeeping-write token gate, ~14 proven-safe-site comments, RetireOperationToPool doc-comment rewrite (A2), CommitCollisionGeneration audit comment)
  • tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (2 new tests: ReentrantBeginDuringCancelPublishCannotBeOverwrittenByOuterWithdraw, ReentrantCancelThenBeginDuringCommitFailureLeavesInnerOperationUncancelled)

C3 implementer session (2026-08-02) — spawn-frequency host cutover

Worktree/branch/HEAD verified against the pinned contract before starting: a32aba35d1. Read in full: c3-contract.md, docs/plans/2026-08-02-placement-cutover.md, docs/research/ 2026-08-02-cutover-route-inventory.md (routes 1+8 + all cross-cutting sections), docs/research/2026-08-02-canonical-body-writer-map.md, docs/ research/2026-08-02-runtime-continuation-executor-handoff.md.

C3-1 — DONE, tested, gated. (Runtime-only prerequisite a)

Design: rather than widen any internal enum's accessibility (the contract's explicit preference), added a small PUBLIC projection surface next to the existing public placement-receipt types (RuntimePlacementProjectionKind/ Token/Snapshot are already public; the executor's own receipt/trace types are internal):

  • RuntimeInitialCreateTeleportHookPhase, RuntimeInitialCreatePositionDisposition, RuntimeInitialCreatePositionConstrainPhase — public 1:1 projections of the internal RuntimeTeleportHookPhase/RuntimeAuthoritativePositionDisposition/ RuntimePositionConstrainPhase enums (RuntimeAuthoritativePositionRouteClassifier.cs:38,49,63).
  • RuntimeInitialCreatePositionRouteFact — one Position continuation's route facts (Sequence/Disposition/HookPhase/ConstrainPhase/StopInterpolating/ ZeroVelocity/PreserveHeading/SendPositionImmediately), projected from RuntimeInitialCreateExecutedAction trace entries where Kind == Position only (every other action kind stays internal-only — widening the full action-kind vocabulary was explicitly what the contract said to avoid).
  • RuntimeInitialCreatePlacementCompletion — the top-level public shape (Entity, FullCellId, TeleportHookPhase, PositionRouteFacts, ReplayedDeferredChildCount), built ONCE by a new private RuntimeInitialCreateContinuationExecutor.ProjectCompletion at the exact completion site (ExecuteCore's Released case, where completedReceipt is built) and cached in _completionReceipts's tuple (extended from (Sequence, Receipt) to (Sequence, Receipt, Public)) — so a host polling/ retrying TryGetInitialCreateCompletion never re-allocates (the "allocation-conscious" requirement).
  • New internal RuntimeInitialCreateContinuationExecutor.TryGetCompletion reads the cached projection by exact token identity (same Entity/Sequence correlation rule as the existing TryGetCompletionReceipt).
  • New PUBLIC RuntimePlacementProjectionChannel.TryGetInitialCreateCompletion( RuntimeGenerationToken, in RuntimePlacementProjectionToken, out RuntimeInitialCreatePlacementCompletion) — generation-gated like every other channel method, thin passthrough to the executor. The channel now takes the executor as a 3rd internal ctor parameter; all 3 RuntimeEntityObjectLifetime constructors updated to pass InitialCreateExecution.

Files touched:

  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs (+219/-0 net insertions: new public types, ProjectCompletion + 3 enum mappers, TryGetCompletion, _completionReceipts tuple widened, completion site wired).
  • src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs (+32/-1: new ctor param + field, TryGetInitialCreateCompletion).
  • src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (+9/-6: all 3 ctors pass InitialCreateExecution into the channel).
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs (+178: 4 new tests — ProjectsHookPhaseCellAndReplayCount [local-player login correctly reports AfterEnterWorld hook phase, empty route facts], ProjectsPositionRouteFactsForConstrainInterpolationBinding [teleport- advanced continuation's route facts round-trip through the public projection], RejectsWrongGeneration, ReturnsFalseAfterAcknowledgeReapsTheCorrelationEntry).

Gates run for C3-1: focused Residence|Classifier|Executor|SetPositionState| PlacementProjectionChannel filter 237/237; complete AcDream.Runtime.Tests 931/931 (927 baseline + 4 new); Release build of the full solution 0 errors/21 pre-existing warnings; complete solution dotnet test AcDream.slnx -c Release -m:1 with ACDREAM_PAK_PATH=/c/Users/erikn/Documents/Asheron's Call/acdream.pak — every project green (App 4028/3 skips, Bake 15/0, Cli 4/0, Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime 931/0, UI.Abstractions 543/0 — zero failures anywhere). git diff --check clean (only the same pre-existing LF/CRLF notices on pre-existing dirty files). Nothing staged, nothing committed. git status confirms only the 4 files above are newly dirty beyond the 8 pre-existing protected paths (which I did not touch — I read PlayerModeController.cs for C3-2 investigation but made ZERO edits to it).

C3-2/C3-3/C3-4 — NOT implemented this session. Stopped with evidence

after investigation surfaced a materially larger scope than the four

input docs describe. Full findings below for whoever picks this up.

I read (in full or targeted-full) beyond the four input docs: PlayerModeController.cs (all 623 lines), RuntimeLocalPlayerPhysicsPublicationState.cs (all 1033 lines), RuntimeLocalPlayerMovementState.cs (all 374 lines), EntityPhysicsHostComposition.cs (all 82 lines), RuntimeInitialCreateResidenceState.Begin/ Own (full), RuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement/ SubmitPreparedPlacementCore (targeted), RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate (full), DatLiveEntityProjectionMaterializer.RegisterAnimation (full), plus targeted greps across RuntimeInitialCreateContinuationExecutorTests.cs and RuntimeLocalPlayerPhysicsPublicationStateTests.cs for the test harness's own "intended recipe" (tests are the only place the FULL local-player publication recipe is exercised end-to-end today).

Finding A — confirmed: no accessibility blocker, no accidental

pre-built orchestrator (cross-check of the route-inventory's own claim)

Re-verified independently: RuntimeLocalPlayerPhysicsPublicationState's Prepare/Commit/EvaluateActivation/CommitActivation/FinalizeActivation chain has ZERO production callers (only tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerPhysicsPublicationStateTests.cs). Unlike C1 ("satisfied by existing mechanism" — a pleasant surprise the plan doc recorded), there is no similar hidden orchestrator tying RegisterEntityWithInitialResidence's residence lease to the publication lifecycle automatically. The wiring described by C3-2/C3-3's bullets genuinely does not exist anywhere, dormant or otherwise.

Finding B — the local-player initial-placement circular dependency

(confirmed by direct read, not previously named in any of the 4 docs)

  • RuntimeEntityObjectLifetime.RegisterEntityWithInitialResidenceInitializeAcceptedCreateResidenceInitialCreateResidences.Begin (RuntimeInitialCreateResidenceState.cs:556-607) → Own (:731-...) — Own synchronously calls _setPosition.TryBeginExclusiveAuthoredPlacement(record, ..., route.OperationKind) (:746-751) whenever route.PerformsSetPosition is true. This runs at wire CreateObject time, inside LiveEntityRuntime.RegisterLiveEntity today's call site (once flipped) — i.e. it opens a RuntimeEntityPlacementToken operation in stage AwaitingPreparation immediately, for EVERY entity including the local player, and the lease (lease.Placement) sits open until something completes it.
  • RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate (RuntimeAuthoritativePositionRouteClassifier.cs:205-273, full read) gives EVERY TopLevel Create with a valid wire position — local player, remote, projectile alike — Disposition.SetPosition uniformly (route.PerformsSetPosition is true for all of them; only Parented/ PickedUp Creates get AwaitFreshPosition, which does NOT open a SetPosition operation). So this circular dependency is not local-player- specific in its trigger — it applies to the SAME Own() call for every top-level Create.
  • For the LOCAL PLAYER specifically, that open placement operation can only be completed by driving RuntimeLocalPlayerPhysicsPublicationState's full chain against the EXACT SAME lease.Placement token (confirmed by reading RuntimeLocalPlayerPhysicsPublicationStateTests.cs:2263-2300's Fixture.RepreparePlacement/ Prepare helpers: BeginAuthoredPlacementPrepareMoverOwner.Prepare(record, placement, command, options, activationPreparation, out token)Commit(token, out activationToken)EvaluateActivationCommitActivationCommit's own body (RuntimeLocalPlayerPhysicsPublicationState.cs:391-397) calls _physics.SetPosition.PrepareDormantLocalActivationOwnership(candidate.Record, candidate.Body, candidate.PreparedActivation.Token.Placement), i.e. it attaches the freshly-built candidate body to THAT EXACT placement token). The generic SubmitPreparedPlacement/TryPrepareAndSubmitAuthoredPlacement path (the one C3-2's bullet 3 names for AwaitingContinuationPlacement's pending-token flavor) cannot be used for the local player's OWN initial placement: SubmitPreparedPlacementCore requires operation.Record.PhysicsBody is not { } body to already be non-null (RuntimeSetPositionState.cs:2610) — there is no local-player body yet at Create time, so this path structurally rejects it. Only the publication lifecycle can attach the FIRST body to an already-open placement token.
  • TODAY, PlayerModeController.BuildControllerAndCamera runs LATER than Create-time (gated by PlayerModeAutoEntry.cs:214-230's IsPlayerEntityPresent && IsWorldReady per-frame check) and never touches lease.Placement/the publication state at all — it does its own unrelated _physics.Resolve/ResolvePlacement (PlayerModeController.cs:409-430). So after the flip, the residence lease's placement (and therefore the ENTIRE executor drain — deferred-child replay, the AfterEnterWorld teleport-hook request, every continuation) stays stuck in PendingPlacement from Create time until whatever replaces BuildControllerAndCamera drives the publication chain AND separately calls InitialCreateExecution.Execute(...) a SECOND time afterward to actually drain the FIFO. This second Execute call is not optional — Execute's own doc comment and the executor handoff both say a caller re-invokes Execute after the placement token in the trace is acknowledged; nothing does this automatically.
  • Practically workable simplification found: RuntimeLocalPlayerPhysicsPublicationState.Prepare calls DiscardCurrent() on entry (:305) before installing a new candidate, so a full "retry the whole Prepare→Commit chain from scratch next frame" is safe UNTIL Commit() succeeds (at which point _activation is populated and a subsequent Prepare call correctly rejects via CanPrepare's _activation is null guard — the retry loop must switch to re-driving EvaluateActivation/CommitActivation on the SAME activationToken, not re-Prepareing). This means PlayerModeAutoEntry's EXISTING per-frame "keep calling TryEnter until it returns true" loop can likely be reused rather than inventing a wholly new scheduler — but PlayerModeController needs a small piece of cross-frame state (at least the pending activationToken when Commit succeeded but Evaluate/Commit- Activation hasn't finished) that does not exist today. This is a genuinely new resumable mini-state-machine, not a one-line change.
  • The animation-sequencer hook attachment (AttachCycleVelocityAccessor/ObjectScale/AttachAnimationRootMotionSource/ Motion.RemoveLinkAnimations/InitializeMotionTables/CheckForCompletedMotions/ DefaultSink, PlayerModeController.cs:378-396) currently happens on the controller BEFORE placement resolve, via the existing RuntimeLocalPlayerMovementState.BeginMotionPreparation lease (:102-117). RuntimeLocalPlayerPhysicsPublicationState.Prepare builds its OWN controller internally and does not return a reference to it before Commit() — but since BeginMotionPreparation only needs a controller REFERENCE (no ordering requirement relative to the publication state's own internal stages), it appears safe to call BeginMotionPreparation on _movement.Controller immediately AFTER Commit() succeeds (controller is RuntimeOwnedDormant at that point, not yet RuntimePublished) and BEFORE EvaluateActivation/CommitActivation — this avoids needing any NEW Runtime API to expose the candidate controller pre-Commit. Flagged as "appears safe" (not "verified safe") — needs a dedicated conformance test before trusting it.

Finding C — a SECOND, previously-unstated capability gap: no production

path constructs a NON-local entity's FIRST PhysicsBody for a residence-driven Create (this blocks Route 1/8 for remote/projectile Creates just as much as the local-player gap blocks it for the local player)

  • SubmitPreparedPlacementCore (RuntimeSetPositionState.cs:2584-2629) requires operation.Record.PhysicsBody is not { } body (:2610) for EVERY entity, not just the local player — confirmed by reading the full method; there is no branch that constructs a body when none exists.
  • TryPrepareAndSubmitAuthoredPlacement (RuntimeSetPositionState.cs:1562-1625, the "C0-3" mover-chain call the contract names for continuation placements) is a thin wrapper around PrepareMover + SubmitPreparedPlacement — it inherits the SAME pre-existing-body requirement. Its own doc comment says "a residence lease's own Placement/Route.OperationKind/ Route.SetPositionFlags are exactly the token/kind/flags this takes" — true for the TOKEN shape, but silent on the body precondition. ClassifyCreate gives Remote/Projectile TopLevel Creates the exact same Disposition.SetPosition as the local player (see Finding B) — so a fresh remote humanoid/monster Create's residence placement would ALSO reject via this same guard today.
  • The ONLY production body-construction call I found for a NEWLY-CREATED (non-local) entity is DatLiveEntityProjectionMaterializer.RegisterAnimation's _runtime.GetOrCreatePhysicsBody(spawn.Guid, incarnation => new PhysicsBody{...}) (DatLiveEntityProjectionMaterializer.cs:1003-1016) — but this is gated behind physicsStatic (FinalPhysicsState & PhysicsStateFlags.Static, line 961) AND a resolved animation sequencer (animation.Sequencer is {}) — i.e. it is a narrow special case for STATIC decorative animated objects (banners, torches), never reached for an ordinary moving humanoid/monster spawn. RuntimePhysicsState.GetOrCreatePhysicsBody (public, RuntimePhysicsState.cs:1623) is presumably the right general-purpose tool to reuse, but nobody calls it for the general case, and none of the 4 input docs name this as a Route-1 capability gap (the closest hits — route-inventory's "Gap 2" and body-writer-map's summary — are both scoped explicitly to the LOCAL PLAYER controller/body atomicity problem, not to ordinary remote entities).
  • Building this out requires retail-fidelity decisions (what a fresh non-static remote/projectile body's default orientation/scale/friction/ elasticity/velocity should be at Create time, mirroring whatever retail's enter_world/object-creation path does) that none of the 4 docs specify and that I should not invent without the grep-named-first workflow this project mandates for AC-specific behavior.

Why I stopped here rather than pushing an implementation

Both findings B and C are genuine, evidence-backed (file:line cited) gaps in the CONTRACT's own assumed shape, not just "this is a lot of code." Landing C3-2+C3-3 correctly needs, at minimum: (1) a new resumable mini-state-machine in PlayerModeController/PlayerModeAutoEntry driving Prepare→Commit→EvaluateActivation→CommitActivation→(second) Execute across frames; (2) the equivalent for headless (which has its own per-tick TryCompletePortal-shaped loop already, per route-inventory's route 8 section, that a similar chain would need to extend); (3) a NEW general first-body-construction step for non-local residence-driven Creates, requiring retail research this session did not do; (4) deletion of the now-superseded duplicate authorities across ~6 files; (5) new App.Tests/ Headless.Tests integration tests; (6) the connected lifecycle/reconnect gate against a live ACE, which is itself explicitly one of this project's few "stop and get user verification" events. Given the project's own standing rules — no workarounds, no guessing at retail behavior, dual- reviewed shape for anything this load-bearing, and "stop and brainstorm when the observed scope diverges from the plan's assumed shape" — pushing a rushed implementation of the single most sensitive path in the client (both hosts' login/placement) within this session's remaining budget was judged higher-risk than landing C3-1 clean and handing back precise, citable findings for a properly scoped follow-up session (likely its own C3-2a "local-player initial-placement orchestration" + C3-2b "first-body construction for residence-driven Creates" split, each with its own dual-review pass, mirroring how C0/C1/C2 were each already run).

No files under C3-2/C3-3/C3-4's scope were edited: PlayerModeController.cs, LiveEntityRuntime.cs, DatLiveEntityProjectionMaterializer.cs, RuntimeLiveEntitySessionController.cs, HeadlessSessionWorldProjection.cs, and RuntimeLocalPlayerMovementState.cs (the C3-4 Controller-setter seal) are all untouched by this session (confirmed via git status).

Gate 4 (connected) — not reached, not skipped/faked

The exact lifecycle/reconnect harness is real and located per the route inventory: tools/run-connected-world-lifecycle-gate.ps1 (drives capped + uncapped-reconnect sessions against local ACE on 127.0.0.1:9000) and tools/run-connected-r6-soak.ps1 (canonical nine-stop route). Both require an already-listening local ACE. I did not attempt to launch/verify ACE reachability because there is no production code change from C3-2/C3-3/C3-4 to gate yet — running the connected harness against C3-1's Runtime-only addition would exercise nothing new (C3-1 has no host caller in this session) and would misrepresent the gate as having validated the cutover. Whoever lands C3-2/C3-3/C3-4 must run this gate for real, with a live ACE, per the contract's gate 4 and the project's own "visual verification is the one thing that requires stopping for the user" rule.

Files touched this session (C3-1 only)

  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
  • src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs
  • src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs

Nothing staged, nothing committed, HEAD unchanged at a32aba35d1.

C3-1 review-fix round (same session, 2026-08-02)

Coordinator relayed two review passes on C3-1's diff:

  1. Architecture PASS with one MINOR: MapHookPhase/MapDisposition/ MapConstrainPhase's catch-all _ => arms silently folded an unmapped future internal enum value into None/NoPositionOperation instead of failing loudly. Fixed: every declared value now has an explicit arm (added the previously-implicit None/NoPositionOperation cases) and the catch-all now throw new ArgumentOutOfRangeException(...) with a message naming both the mapper method and the public enum to update. Also fixed ProjectCompletion's action.PositionDisposition ?? RuntimeAuthoritativePositionDisposition.NoPositionOperation fallback: verified (grep-confirmed BuildPositionTrace is the sole producer of Kind.Position trace entries, always passing route.Disposition, a non-nullable enum) that null is NOT a legitimate state at that call site specifically (it legitimately IS null for non-Position action kinds elsewhere in the trace, per the field's own doc comment - just not reachable here since the loop already filters to Kind == Position) - replaced the silent fallback with an explicit InvalidOperationException throw + comment explaining why.
  2. Retail-conformance PASS with two documentation-only addenda (no code changes): (a) RuntimeInitialCreatePositionRouteFact's doc comment now states explicitly that UnparentBeforeRouting/ ApplyPlacementFrameBeforeRouting ("unset_parent"/"SetPlacementFrame") are NOT projected because the executor's own merge (ApplyAcceptedPositionSnapshot's clearParent/installPlacementFrame params, confirmed by direct read at the ApplyPositionAction/envelope call sites) already applies both to the canonical snapshot before the trace entry is built - a host must not re-apply them; the struct only carries facts still DEFERRED to the host. (b) RuntimeInitialCreatePlacementCompletion's doc comment now states PositionRouteFacts's ARRAY ORDER (not Sequence) is authoritative - confirmed multiple Position trace entries from one same-incarnation envelope share one continuation Sequence (only Stage, not projected, distinguishes them internally), and ProjectCompletion preserves trace/FIFO-drain order by construction.

New test per the reviewer's ask ("same guard shape as OperationResetAllFieldsToDefaultTouchesEveryDeclaredField"): EnumProjectionMapsHaveEqualArityAndEveryInternalValueRoundTripsByName in RuntimeInitialCreateContinuationExecutorTests.cs - reflection-invokes the three private static Map* methods against every declared value of their internal source enum, asserting (a) equal arity between the internal and public enum, and (b) every mapped public value's .ToString() name equals the internal value's name (the mappers are literal 1:1 name mirrors by design). Sabotage-verified twice, both reverted after confirming failure: (1) added a member ONLY to internal RuntimeTeleportHookPhase - failed on the arity assertion ("RuntimeTeleportHookPhase has 5 values but RuntimeInitialCreateTeleportHookPhase has 4"); (2) added the SAME member to BOTH the internal and public enum (arity equal) without adding a mapping arm - failed via the reflection-invoked MapHookPhase throwing ArgumentOutOfRangeException exactly as designed. Both sabotage edits fully reverted; confirmed clean via git diff on the touched file showing no residual change.

Gates re-run after the fix: focused Residence|Classifier|Executor|SetPositionState|PlacementProjectionChannel filter 242/242 (241 + 1 new); complete AcDream.Runtime.Tests 932/932 (931 + 1 new); Release build of the full solution 0 errors/21 pre-existing warnings; git diff --check clean (only pre-existing LF/CRLF notices, same files as before). git status shows the same files as the prior C3-1 checkpoint dirty, PLUS one file I did NOT touch: docs/plans/2026-08-02-placement-cutover.md now shows a diff decomposing C3 into C3a/C3b/C3c based on my earlier Finding B/C report - this was made externally (not by this session; I never opened that file for editing this round) and is left exactly as found, unstaged. Nothing staged by me, nothing committed, HEAD unchanged at a32aba35d1.

Files touched this round (all within C3-1's original scope, no new files):

  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs (Map* explicit arms + throws, ProjectCompletion null-check, doc-comment addenda on RuntimeInitialCreatePositionRouteFact and RuntimeInitialCreatePlacementCompletion)
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs (+1 new test, +using System.Reflection)

================================================================ C3a -- Runtime first-entry conductor (dormant) -- implementer session

Mandatory first step: full reads completed

Read in full before writing any code:

  • src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs (1,033 lines).
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerPhysicsPublicationStateTests.cs (2,698 lines, all ~35 tests).
  • docs/research/2026-07-31-remaining-physics-campaign-handoff.md (497 lines, full) -- route-1's own required order, lines 280-292.
  • docs/plans/2026-08-02-placement-cutover.md (166 lines, full).
  • docs/research/2026-08-02-canonical-body-writer-map.md (681 lines, full).
  • docs/research/2026-08-02-runtime-continuation-executor-handoff.md (211 lines, full).
  • src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs (1,260 lines, full).
  • src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs (Execute/ExecuteCore, lines 1-250 and 860-1080).
  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs targeted sections: RuntimeEntityPlacementStage enum (30-39), TryBeginExclusiveAuthoredPlacement/ PrepareDormantLocalActivationOwnership/BeginAcceptedPlacementCore (1170-1467), PrepareMover/TryPrepareAndSubmitAuthoredPlacement/ IsExactPreparedPlacementCurrent (1480-1652), SubmitPreparedPlacementCore (2575-2700), RetryDeferred (3986-4020), AcknowledgeProjection (2913-3006).
  • Existing test fixtures for reuse patterns: tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs (EngineLifetime/Bind/Spawn/AttachDormantBody/CompleteInitialPlacement, lines 1-130 and 4963-5150) and tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (FakeCollisionSource, TryPrepareAndSubmitAuthoredPlacement tests, lines 2660-2760, 3296+).

Step-graph (states x transitions x owning method)

[residence Begin -- ALREADY DONE at registration, outside conductor scope] RuntimeEntityObjectLifetime.RegisterEntityWithInitialResidence -> RuntimeInitialCreateResidenceState.Begin -> RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement (opens Operation, stage=AwaitingPreparation) -> RuntimeSetPositionState.WatchPlacementCompletion(placement) yields: RuntimeInitialCreateResidenceLease { Token, Route, Placement }

Stage.AwaitingMoverPreparation (conductor entry point) precondition: RuntimeInitialCreateResidenceState.TryGetCurrent(record, out lease) && lease.Token == residenceToken if !lease.Route.PerformsSetPosition (Parented/PickedUp -- never true for a real login, kept for structural completeness): -> Stage.Acknowledged (skip straight to Execute) else: RuntimeSetPositionState.TryPrepareAuthoredMover <- NEW extracted method (Setup-read via IPreparedCollisionSource, then PrepareMover; stage stays AwaitingPreparation; sets authority.Prepared=true) RetrySetupUnavailable -> yield AwaitingCollisionSource (retry same stage) Prepared -> Stage.MoverPrepared, holds RuntimeSetPositionCommand

Stage.MoverPrepared RuntimeLocalPlayerPhysicsPublicationState.Prepare(record, lease.Placement, command, options, activationPreparation, out pubToken) (validates IsExactPreparedPlacementCurrent -- REQUIRES mover already prepared; off-canonical body+controller build against scratch clock) RejectedAuthority -> abandon (Discard progress; nothing to undo, Prepare never mutates on failure) Prepared -> Stage.PublicationPrepared, holds pubToken

Stage.PublicationPrepared RuntimeLocalPlayerPhysicsPublicationState.Commit(pubToken, out activationToken) -> internally: RuntimeSetPositionState.PrepareDormantLocalActivationOwnership (the designed seam -- binds Operation.Body, sets DormantLocalActivation =true; requires stage STILL AwaitingPreparation + record.PhysicsBody still null) -> candidate.Controller.CommitRuntimeOwnership + candidate.Record.SetPhysicsBody(body) (record now has a body) RejectedToken/RejectedAuthority -> abandon (Publication.Commit already self-discards its own candidate on RejectedAuthority; conductor drops its own progress entry) Committed -> Stage.PublicationCommitted, holds activationToken

Stage.PublicationCommitted / Stage.Evaluated (single combined retry point -- see "CommitActivation resume safety" note below) RuntimeLocalPlayerPhysicsPublicationState.EvaluateActivation(activationToken, out receipt) RejectedToken/RejectedAuthority -> abandon Evaluated/DeferredCell/RejectedPlacement -> receipt.IsValid in all three; proceed to CommitActivation in the SAME Advance call (mirrors every publication test: Evaluate and CommitActivation are always chained, never yielded between) RuntimeLocalPlayerPhysicsPublicationState.CommitActivation(receipt, out projection) (internally drives: ground phase -> HitGround/LeaveGround -> post-ground -> collision dispatch -> post-collision -> FinalizeActivation, which is the SAME retail-staged commit already tested) Committed -> Stage.ActivationCommitted, holds projection DeferredCell / RejectedPlacement -> yield AwaitingActivation, stage stays PublicationCommitted (retry re-runs BOTH EvaluateActivation AND CommitActivation next Advance call -- safe even for the internal AwaitingFinalShadowPreparation resume path, see note below) RejectedAuthority -> abandon

Stage.ActivationCommitted RuntimeSetPositionState.AcknowledgeProjection(projection.Token) ("Place receipt -> acknowledgement" -- the SAME ack any host uses; moves the watched placement token into _acknowledgedPlacementCompletions, operation.Stage -> AwaitingCommitAcknowledgement, operation removed from _operations) false -> yield AwaitingReceiptAcknowledgement (retry same stage -- only fails if not the exact FIFO head; single-entity tests never hit this, documented for completeness) true -> Stage.Acknowledged

Stage.Acknowledged (or skipped-straight-here for a non-SetPosition route) RuntimeInitialCreateContinuationExecutor.Execute(record, residenceToken, inputs, out executionReceipt) (internally: RuntimeInitialCreateResidenceState.Complete -- now succeeds because IsPlacementCurrent(lease.Placement)==false and TryPeekAcknowledgedPlacement succeeds -- -> AdoptCompletedPlacement -> AfterEnterWorld hook -> deferred replay -> FIFO drain -> ConsumeExecuted) Completed -> conductor Stage.Completed (progress removed) -> terminal PendingPlacement -> yield AwaitingReceiptAcknowledgement (residence still sees the operation as unacknowledged/current -- should not normally recur once we've truly acknowledged, kept as a defensive yield) AwaitingContinuationPlacement -> yield AwaitingContinuationPlacement (a LATER Position continuation needs its own placement -- entirely the EXECUTOR's own concern from here; conductor just passes the status through, mirroring "Execute (FIFO drain) -> ExecutorCompleted receipt" being the conductor's LAST step, not something it re-implements) RejectedToken/RejectedAuthority -> abandon

Typed yields exposed (RuntimeLocalPlayerFirstEntryStatus): Completed, AwaitingCollisionSource, AwaitingActivation, AwaitingReceiptAcknowledgement, AwaitingContinuationPlacement, Contention (reentrancy-guard-only -- mirrors the executor's own _executing.Add(key) fail-closed pattern), RejectedToken, RejectedAuthority. "awaiting-preparation" from the contract's five named yields is folded into AwaitingCollisionSource / the MoverPrepared -> PublicationPrepared span (see reconciliation below) rather than kept as an eighth separate value -- documented at the enum declaration.

Reconciliation against route-1 + CONTRADICTION FOUND (resolved, did not

silently redesign)

Campaign handoff route-1 order (2026-07-31-remaining-physics-campaign-handoff.md:280-292):

  1. register identity cellless
  2. begin initial/remote-create placement before hydration
  3. load exact Setup mover
  4. prepare the atomic Runtime controller/body relationship
  5. submit canonical SetPosition
  6. publish presentation only from Place
  7. acknowledge, then enable player mode/simulation

Step 3 (mover) precedes step 4 (controller/body prepare) here.

The C3a contract's OWN restated PURPOSE-section order instead reads: "... atomic Commit binding the body to the residence's EXACT placement token (PrepareDormantLocalActivationOwnership is the designed seam) -> authored-mover preparation + submission (C0's TryPrepareAndSubmitAuthoredPlacement ...)" i.e. it places mover-prep AFTER Publication Prepare/Commit -- the OPPOSITE of route-1's own order 3-then-4.

Verified against the actual staged semantics that this restated order is IMPOSSIBLE and additionally that the named mechanism cannot be reused as worded:

  1. RuntimeLocalPlayerPhysicsPublicationState.CanPrepare (RuntimeLocalPlayerPhysicsPublicationState.cs:886-889) requires _physics.SetPosition.IsExactPreparedPlacementCurrent(record, placement, command) to ALREADY be true. IsExactPreparedPlacementCurrent (RuntimeSetPositionState.cs:1627-1651) requires authority.Prepared && authority.PreparedCommand == command -- i.e. PrepareMover MUST have already succeeded for this exact command BEFORE Publication.Prepare can even be called. Mover-prep cannot happen after Commit; it structurally gates entry into Prepare.
  2. TryPrepareAndSubmitAuthoredPlacement (RuntimeSetPositionState.cs:1562-1625) is PrepareMover followed unconditionally by SubmitPreparedPlacement. SubmitPreparedPlacementCore (RuntimeSetPositionState.cs:2584-2630) requires operation.Record.PhysicsBody is not {} body -- i.e. a body must ALREADY exist. Before Commit, the record has no body (Commit is what attaches one); calling this fused method before Commit would reject. Calling it AFTER Commit (as the contract's restated order implies) would NOT reject -- the body now exists -- but RetryDeferred's own comment (RuntimeSetPositionState.cs:3988-3993) states explicitly: "The local- player activation lease owns its dormant body/controller and must re-enter through the same sealed evaluation/commit path ... it must never bypass that path through the ordinary remote CommitCanonical tail." SubmitPreparedPlacementCore has no DormantLocalActivation exclusion check, so calling it post-Commit would silently route the operation through the wrong (ordinary) commit tail in parallel with the dormant Evaluate/Commit/FinalizeActivation chain -- corrupting state.
  3. Confirmed empirically: the EXISTING executor test suite (RuntimeInitialCreateContinuationExecutorTests.cs's AttachDormantBody/CompleteInitialPlacement helpers, lines 5007-5073) treats even isLocalPlayer: true fixtures via a direct Entities.SetPhysicsBody + ordinary SubmitPreparedPlacement -- NEVER through RuntimeLocalPlayerPhysicsPublicationState -- because for a record that never sets DormantLocalActivation, the ordinary tail is exactly correct. This is a test-only substitute for what C3a's conductor now performs for real; it is not evidence that the ordinary tail is ever valid for a DormantLocalActivation operation.

Resolution: route-1's own order (mover-prep BEFORE the controller/body prepare step) is correct and consistent with every tested invariant; the C3a contract's restated PURPOSE-section prose transposed the two steps. Per the contract's own instruction ("STOP with file:line evidence" rather than silently redesigning the PUBLICATION CHAIN), this is flagged here with full citations; the conductor is implemented using route-1's order because (a) that is what the contract explicitly told me to reconcile against, and (b) it is the only order that satisfies the publication chain's own staged/tested preconditions without changing a single line of already- tested code. The publication chain itself (Prepare/Commit/EvaluateActivation/ CommitActivation/FinalizeActivation) is NOT modified or reinterpreted -- only the CONDUCTOR's call order was corrected relative to the contract's prose.

Mechanism correction: "C0's TryPrepareAndSubmitAuthoredPlacement" as named in the contract is the WRONG vehicle for the local-player dormant path for the reason in point 2 above (it ends in SubmitPreparedPlacement, forbidden once DormantLocalActivation is set). RuntimeSetPositionState.cs gained one new internal method, TryPrepareAuthoredMover, extracted verbatim from TryPrepareAndSubmitAuthoredPlacement's FIRST HALF (Setup-read + PrepareMover call only, no Submit) -- a pure, behavior-preserving refactor. TryPrepareAndSubmitAuthoredPlacement itself now calls this shared helper then submits, unchanged in every observable respect (its own two existing tests, TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit and ..._YieldsRetryOnAMissingSetupReadWithoutMutatingStage, stay green unmodified). The conductor calls ONLY the new TryPrepareAuthoredMover half.

CommitActivation resume safety note (why one retry stage suffices)

Verified that re-running EvaluateActivation before every CommitActivation retry -- rather than adding a THIRD stage that resumes CommitActivation alone for the AwaitingFinalShadowPreparation internal resumption path -- is safe: CommitActivation's own top-of-method resume check (activation.PendingFinalCommit.Status is AwaitingFinalShadowPreparation, RuntimeSetPositionState.cs:498-506) fires only AFTER re-validating activation.Receipt == receipt; a fresh EvaluateActivation call sets activation.Receipt to match whatever it just returned, so passing that same fresh receipt back into CommitActivation satisfies the equality check and the stored PendingFinalCommit (untouched by the extra Evaluate call) still drives the correct resume via FinalizeActivation. Confirmed DeferredCell/RejectedPlacement from CommitActivation clear activation.Receipt to default (RuntimeSetPositionState.cs:546,628) -- so a fresh Evaluate is REQUIRED, not just tolerated, on those two outcomes. The one cost is a redundant extra Engine.SetPosition resolve in the (rare, contention-only) AwaitingFinalShadowPreparation case -- not a correctness issue, and simpler than tracking a fourth stage.

Files this session will add/touch

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs -- extract TryPrepareAuthoredMover (new internal method; TryPrepareAndSubmitAuthoredPlacement now delegates to it, unchanged behavior).
  • src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs -- NEW, the conductor. Dormant; no production caller; constructed only in tests.
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs -- NEW.

No changes to RuntimeLocalPlayerPhysicsPublicationState.cs, RuntimeInitialCreateResidenceState.cs, RuntimeInitialCreateContinuationExecutor.cs, RuntimeEntityObjectLifetime.cs, or GameRuntime.cs (C3c wires production callers and residence-retirement fan-out; that is explicitly out of C3a's scope). One consequence documented for C3c: because RuntimeInitialCreateResidenceState.BindRetirementNotification is a single- subscriber seam already bound to InitialCreateExecution.DiscardProgress inside RuntimeEntityObjectLifetime's constructor, the conductor built here does NOT receive a push notification on external residence retirement; it relies on lazy re-validation at the top of every Advance call plus an explicit Forget(key) a future host can call. C3c will need to either fan the single notification out to both subscribers or route it through the conductor.

Implementation complete — gates passed

Final design (RuntimeLocalPlayerFirstEntryState.cs, 423 lines) — a five-stage resumable machine (AwaitingMoverPreparation -> MoverPrepared -> PublicationCommitted -> ActivationCommitted -> Acknowledged), one Advance(record, residenceToken, options, activationPreparation, collisionSource, gameTime, inputs, out receipt) entry point, an _executing HashSet reentrancy guard mirroring the executor's own, a Progress class (LeaseId + Stage + the exact token/receipt/projection structs) keyed by RuntimeEntityKey, and a Discard/Forget pair that unconditionally calls Publication.Discard/DiscardActivation (both harmless no-ops against a default/unreached-stage token).

Bugs found and fixed during test-driven verification (all via a temporary diagnostic build with Console.WriteLine probes, removed before the final commit-ready state):

  1. EvaluateActivation's overloaded DeferredCell status. Once a PRIOR CommitActivation call has registered a lease as awaiting a specific cell (IsDormantLocalActivationAwaitingCell), a REPEATED EvaluateActivation call that is still not ready returns DeferredCell WITHOUT populating its receipt (stays default/invalid) — confirmed against DeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWake in the publication suite, which asserts exactly waiting.IsValid == false on that repeat and never calls CommitActivation with it. The conductor now checks evalReceipt.IsValid before ever calling CommitActivation, short-circuiting straight to AwaitingActivation when it is false, instead of blindly forwarding an invalid receipt (which CommitActivation's own !receipt.IsValid guard would reject as RejectedAuthority).
  2. Re-acknowledging an already-consumed projection token. An earlier draft used a single "Acknowledged" stage value to mean both "just committed, ack not yet attempted" and "ack already succeeded", causing a retry AFTER a successful acknowledgement (e.g. because Execute yielded AwaitingContinuationPlacement) to call AcknowledgeProjection a second time against a token AcknowledgeProjection had already removed from its FIFO — always failing. Split into two distinct stages (ActivationCommitted = ack not yet attempted; Acknowledged = ack done, only Execute remains) so the ack call only ever runs once per projection.
  3. RejectedToken vs RejectedAuthority at the residence-lookup checks. Mirrored RuntimeInitialCreateResidenceState.Complete's own convention: "nothing was ever tracked for this key" (no Progress entry, residence not found) is RejectedToken; "something WAS in flight and just got invalidated" (Progress entry existed, now stale) is RejectedAuthority — matching the executor's identical split for the analogous case.

Real (not test-only) findings surfaced by writing the tests, documented in the test file itself:

  • RuntimeEntityRecord.Key is computed from a nullable LocalEntityId and becomes null the instant ReleaseLocalId runs (part of delete's teardown in RuntimeEntityObjectLifetime.CompleteProjectionRetirement). Advance's entry check (record.Key is not {} key -> RejectedToken) EXACTLY mirrors RuntimeInitialCreateContinuationExecutor.Execute's own entry check — once Key is null, the conductor cannot compute its own dictionary key to reach stale progress at all. A caller that wants deterministic cleanup after full teardown must capture the RuntimeEntityKey BEFORE deletion and call Forget(key) explicitly; this is a pre-existing convention in the codebase (the executor has the identical limitation), not a defect introduced here.
  • RuntimeLocalPlayerPhysicsPublicationState holds exactly ONE global _candidate/_activation (instance fields, not per-key) — correct, since there is only ever one local player — but it means an orphaned, un-Forgot first-entry attempt for a stale incarnation will structurally block a fresh incarnation's own Publication.Prepare (CanPrepare requires _activation is null) until Forget runs. Proven by DeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlot.
  • Movement.ResetSession() proactively nulls Publication's _activation directly (unlike delete, which only makes it stale via _entities.IsCurrent/epoch checks) — so a retry after ResetSession sees EvaluateActivation report RejectedToken (activation genuinely gone), not RejectedAuthority (activation found but stale) — and, because ResetSession never touches RuntimeEntityRecord.Key, ordinary retry alone (no captured-key Forget) reaches Discard and converges.

Final gate results

  • dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release: 0 errors, 0 warnings.
  • dotnet build AcDream.slnx -c Release: 0 errors, 21 pre-existing warnings (same count/files as the C3-1 checkpoint; none new).
  • Focused filter FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry: 308/308 passed.
  • Complete AcDream.Runtime.Tests: 944/944 passed (932 baseline + 12 new), 0 skips.
  • Complete solution (dotnet test AcDream.slnx -c Release --no-build -m:1): every project reports 0 failed — App 4028/3 skips, Bake 15/0, Cli 4/0, Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime 944/0, UI.Abstractions 543/0.
  • git diff --check: clean (only pre-existing LF/CRLF notices on the same eight paths as every prior checkpoint; AGENTS.md is the only one with a real, pre-existing, untouched content diff; RuntimeSetPositionState.cs shows exactly my own 60/8 insertion/deletion extraction, nothing else).
  • No staging, no commits, HEAD unchanged at 277ef5d0.

Files touched (final)

  • src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs — extracted TryPrepareAuthoredMover (new internal method, Setup-read + PrepareMover only); TryPrepareAndSubmitAuthoredPlacement now delegates to it then submits, byte-identical observable behavior (its own two existing tests pass unmodified).
  • src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs — NEW, 423 lines. Dormant; zero production callers (verified by the focused/ complete/full-solution gates above, which exercise it only from the new test file).
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs — NEW, 12 tests: full-sequence happy path; AwaitingCollisionSource retry +resume; AwaitingActivation retry+resume (generation wake); AwaitingReceipt Acknowledgement retry+resume (FIFO-head contention via a second entity's unacknowledged Place); AwaitingContinuationPlacement propagation+resume; reentrant Advance during a collision callback (Contention, outer call still completes); two retry-idempotency tests (mover-preparation and AwaitingActivation stages never re-create a candidate/duplicate a body); mid-flight delete-during-collision-callback abandonment (no Place/shadow published, full convergence); delete-while-AwaitingActivation requiring an explicit captured-key Forget; ResetSession mid-flight converging through ordinary retry; delete+same-GUID-reincarnation requiring Forget before the fresh incarnation can use Publication's one global slot.

RuntimeLocalPlayerPhysicsPublicationState.cs, RuntimeInitialCreateResidenceState.cs, RuntimeInitialCreateContinuationExecutor.cs, RuntimeEntityObjectLifetime.cs, and GameRuntime.cs are all untouched, exactly as scoped.

================================================================ Review round 2 -- F1 (MAJOR) + F2 (MINOR) fixes

F1 (MAJOR) -- ack-failure authority re-validation

Root cause confirmed: the ActivationCommitted stage treated EVERY AcknowledgeProjection failure as the generic "not yet FIFO head" case. TryAcceptDelete -> CompleteProjectionRetirement -> Physics.SetPosition.Forget -> CancelCore rewrites the SAME pending slot from Place to Discard with a bumped Revision; the RuntimePlacementProjectionToken struct this class already cached in progress.Projection can then never match the FIFO head again, so AcknowledgeProjection would fail forever -- infinite AwaitingReceiptAcknowledgement for a dead entity, Progress retained, IsConverged false forever, exactly as reported.

Fix: added IsAcknowledgementStillPending(record, residenceToken, expected), called on every failed acknowledge before deciding retryable vs abandon. Two checks, either failing means authority moved:

  1. _residences.TryGetCurrent(record, out lease) && lease.Token == residenceToken -- the SAME residence-lookup pattern stage0/stage1 already use.
  2. _physics.SetPosition.TryPeekProjection(out head) -- if the FIFO head belongs to THIS entity (head.Token.Entity == expected.Entity) but is no longer the exact Place token expected (kind changed, or revision bumped), authority for THIS SPECIFIC placement moved even if the residence lookup alone would not have caught it. A head belonging to a DIFFERENT entity is the genuine "not our turn yet" case and stays retryable. Both failing -> Discard(key) + RejectedAuthority. Read RuntimeSetPositionState.TryPeekProjection directly (Runtime-internal-to- internal), not through the public generation-gated RuntimePlacementProjectionChannel -- this class is part of Runtime, not an external host crossing that boundary, exactly like its existing direct AcknowledgeProjection call.

Real discovery made while testing this: FinalizeActivation nulls Publication's OWN tracked _activation the INSTANT CommitActivation's final commit succeeds (RuntimeLocalPlayerPhysicsPublicationState.cs FinalizeActivation, _activation = null; right after TryApplyDormantLocalActivationFinalCommit succeeds) -- the controller is genuinely live/published from that point on, not a discardable in-progress candidate. So once Stage.ActivationCommitted is reached, Publication.DiscardActivation is ALREADY a no-op on the controller/body; abandoning a stuck acknowledgement never retroactively un-publishes an already-live entity -- that is ordinary entity teardown's job, not this class's. Documented on Discard's own doc comment and confirmed empirically (a diagnostic build showed PendingActivationCount == 0 already at the FIRST ack attempt, before any authority change).

New test: DeleteWhileAwaitingReceiptAcknowledgementAbandonsInsteadOfRetryingForeverAndConverges -- reaches ActivationCommitted (blocked behind another entity's own unacknowledged Place, same technique as the existing FIFO-head test), then calls the EXACT narrower mechanism TryAcceptDelete itself uses (Physics.SetPosition.Forget(record, releasePreparedMover: true) + PublishCancellation) directly rather than a full entity delete -- this was deliberate: a FULL delete now ALSO retires the residence, and F2's automatic retirement fan-out (below) would converge everything before a second Advance ever ran, masking whether THIS authority-recheck code path itself works. The narrower call proves the fix independent of F2's wiring. Asserts RejectedAuthority, ActiveCount/PendingActivationCount converge to 0, and the UNRELATED entity's own placement remains unaffected and acknowledgeable.

F2 (MINOR) -- ownership fold + cleanup wiring

(a) RuntimeInitialCreateResidenceState.BindRetirementNotification converted from a single nullable Action field (throw-on-second-bind) to a List<Action<RuntimeEntityKey>> (ordered, registration-order invocation via a new private NotifyRetirement(key) helper). Null-arg throw preserved (ArgumentNullException.ThrowIfNull); the "already bound" throw is gone by design since multiple subscribers are now the point. All 5 existing invocation sites (Forget x2, Clear's two loops, Retire(Entry), Retire(CompletedEntry)) now call NotifyRetirement instead of _retirementNotification?.Invoke.

(b) RuntimeLocalPlayerFirstEntryState's constructor no longer takes RuntimeLocalPlayerPhysicsPublicationState (RuntimeEntityObjectLifetime is constructed BEFORE Publication exists -- GameRuntime builds RuntimeLocalPlayerMovementState and attaches its publication only after the entity-object lifetime). Added the SAME late-bind pattern already used throughout this class family (BindGeneration, BindRetirementNotification, BindLiveInputs, RuntimeLocalPlayerMovementState.PhysicsPublication's own throws-if-unbound accessor): BindPublication(publication) (bind-once, throws on null/double-bind) + a private Publication accessor that throws if unbound. All internal _publication.X call sites became Publication.X. Added DiscardAll() mirroring the executor's own (discards every tracked key's candidate/activation, then clears _progress). RuntimeEntityObjectLifetime now constructs LocalPlayerFirstEntry in all 3 constructors (right after InitialCreateExecution, same pattern), binds a SECOND retirement notification (key => LocalPlayerFirstEntry.Forget(key), alongside the executor's existing one), and BeginSessionClear calls LocalPlayerFirstEntry.DiscardAll() right after InitialCreateExecution.DiscardAll(). RuntimeEntityObjectOwnershipSnapshot gained LocalPlayerFirstEntryActiveCount = 0 (trailing default, matching the file's existing convention), folded into IsConverged and into CaptureOwnership()'s construction. GameRuntime.cs itself was NOT touched (BindPublication is never called in production) -- deliberate: since Advance is never called in production, _progress stays permanently empty, so Forget/DiscardAll never actually dereference Publication regardless of binding state; wiring the production BindPublication call is left as a natural part of C3c's Advance-caller work, not manufactured here.

(c) Updated the two named tests plus my own new delete tests to use Lifetime.LocalPlayerFirstEntry (bound via .BindPublication(Publication)) instead of a separately-constructed conductor instance -- this is what actually exercises the real wiring; a standalone instance would never see the fan-out at all.

  • DeleteWhileAwaitingActivationRequiresForgetOfTheCapturedKeyToDiscardTheDormantActivation renamed to DeleteWhileAwaitingActivationConvergesAutomaticallyThroughTheRetirementFanOut: delete alone now converges ActiveCount/PendingActivationCount to 0 with NO explicit host Forget call; a follow-up Advance is a safe RejectedToken no-op (Key already null).
  • DeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlot renamed to DeleteAndSameGuidReincarnationAutomaticallyFreesThePublicationSlotForTheFreshIncarnation: the fresh incarnation's own Prepare now succeeds immediately after delete, no Forget call in between. No standalone-conductor variant was kept -- delete-triggered convergence IS the production path once RuntimeEntityObjectLifetime owns construction, so an explicit-Forget test would only be meaningful for a conductor built outside the lifetime, which is not a real usage shape this slice needs to cover (the F1 test's narrower Physics.SetPosition.Forget-only scenario already demonstrates the authority-recheck's own logic independent of the fan-out, satisfying that documentation need instead).

Fixture.Dispose ordering bug found and fixed along the way: disposing Movement (which tears down Publication) BEFORE Lifetime (whose Dispose runs BeginSessionClear, which now reaches LocalPlayerFirstEntry.DiscardAll -> Publication.Discard for any still-tracked entity) threw ObjectDisposedException whenever a test left real progress untracked at teardown (e.g. the AwaitingActivation retry-idempotency test, which never completes or deletes within the test body). Fixed by disposing Lifetime FIRST. Documented as a real ordering constraint for whoever eventually disposes GameRuntime in production, since the identical dependency exists there (RuntimeEntityObjectLifetime's conductor holds a bound reference to Publication via BindPublication).

Final gate results (round 2)

  • dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release: 0 errors, 0 warnings.
  • dotnet build AcDream.slnx -c Release: 0 errors, 21 pre-existing warnings (unchanged).
  • Focused filter FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry: 309/309 passed (308 + 1 new F1 test).
  • Complete AcDream.Runtime.Tests: 945/945 passed (932 baseline + 13 new), 0 skips.
  • Complete solution (dotnet test AcDream.slnx -c Release --no-build -m:1): every project reports 0 failed -- App 4028/3 skips, Bake 15/0, Cli 4/0, Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime 945/0, UI.Abstractions 543/0.
  • git diff --check: clean. Modified files now include RuntimeEntityObjectLifetime.cs and RuntimeInitialCreateResidenceState.cs in addition to the round-1 RuntimeSetPositionState.cs -- all three diffs are additive/expected (64, 39, 68 changed lines respectively via git diff --stat); the pre-existing eight dirty paths are otherwise unchanged (line-ending noise only).
  • No staging, no commits, HEAD unchanged at 277ef5d0.

Files touched (round 2 additions)

  • src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs -- now 663 lines (was 540): F1's IsAcknowledgementStillPending; F2's BindPublication/Publication accessor replacing the constructor parameter; DiscardAll().
  • src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs -- BindRetirementNotification multicast conversion.
  • src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs -- LocalPlayerFirstEntry property + construction in all 3 ctors + second retirement-notification bind + BeginSessionClear wiring + RuntimeEntityObjectOwnershipSnapshot field/IsConverged/CaptureOwnership fold.
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs -- now 827 lines (was 758): 1 new F1 test; Fixture now binds/uses Lifetime.LocalPlayerFirstEntry instead of a standalone instance; fixed Dispose ordering; 2 tests renamed and rewritten for automatic convergence per F2(c).

GameRuntime.cs remains untouched (see F2(b) note above for why). No production caller of Advance anywhere.

================================================================ Review round 3 -- H1 + H2 hardening (final verdicts: retail PASS, architecture PASS)

H1 -- NotifyRetirement snapshot before iterating

RuntimeInitialCreateResidenceState.NotifyRetirement iterated the live _retirementNotifications List<> directly. A subscriber binding a NEW notification from inside a retirement callback it is itself receiving (unreachable today -- only 2 subscribers exist, neither rebinds -- but becomes reachable the instant C3c adds a runtime-bound third subscriber) would throw "Collection was modified" on the very next iteration step. Fixed per the reviewer's exact instruction, matching RuntimeEntityObjectEventStream's own copy-on-write dispatch precedent: foreach (... in _retirementNotifications.ToArray()). ToArray() (not a Volatile-guarded array swap like the event stream) was the right granularity here since binding only ever happens a handful of times at construction, never on a hot per-frame path -- documented on the method's own doc comment with that reasoning spelled out.

New test (RuntimeInitialCreateResidenceStateTests.cs): RetirementNotificationBoundReentrantlyDuringDispatchDoesNotCorruptTheCurrentIteration -- binds a notification that, on its first invocation, reentrantly binds a THIRD one; triggers a real retirement via Forget; asserts no exception, that the newly-bound subscriber does NOT see the in-flight retirement (not required to), and that it DOES see the next one.

H2 -- unbound-Publication transactional failure

Root cause confirmed: AdvanceCore had no check for an unbound _publication before mutating anything. An Advance call in the window before a host calls BindPublication would run the FULL authored-mover Setup-read/PrepareMover call (mutating RuntimeSetPositionState's own _preparedMovers) and create/store this class's own Progress entry BEFORE the FIRST Publication dereference (inside the MoverPrepared stage) throws -- leaving a poisoned Progress entry in _progress that a LATER, unrelated Discard/DiscardAll call (from a retirement notification or session-clear fan-out) would ALSO throw on, corrupting someone else's teardown.

Fix: AdvanceCore's very first statement (before _progress.TryGetValue, before the ABA check, before anything) is now _ = Publication; -- the existing throws-if-unbound accessor, referenced purely for its side effect, so the whole call fails transactionally with nothing yet mutated. Also hardened Discard/DiscardAll to tolerate an unbound _publication defensively (if (_publication is null) return; before touching Publication.Discard/DiscardActivation) -- both documented as structurally unreachable post-H2 (a Progress entry can only exist if Advance already ran, which now requires a bound Publication first) and guarded anyway as belt-and-suspenders so no future caller shape can turn an already-surfaced Advance failure into a SECOND throw from inside an unrelated fan-out.

New tests (RuntimeLocalPlayerFirstEntryStateTests.cs):

  • AdvanceWithUnboundPublicationThrowsTransactionallyBeforeAnyStateMutation -- constructs a bare RuntimeEntityObjectLifetime (its own LocalPlayerFirstEntry is naturally unbound, since only this test file's own Fixture calls BindPublication), registers a residence, calls Advance with no bound Publication, asserts the throw AND that the residence lease/ActiveCount are completely untouched, THEN binds a real Publication and confirms the SAME token still drives correctly to AwaitingActivation -- proving nothing was corrupted by the failed attempt.
  • BindPublicationTwiceThrows -- the standard bind-once guard test, matching every other BindX method in this class family.

Final gate results (round 3, last of the slice)

  • dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release: 0 errors, 0 warnings.
  • dotnet build AcDream.slnx -c Release: 0 errors (warning count reported as 0 on this incremental rebuild since no other project's files changed and MSBuild skipped re-analyzing them as up-to-date; the prior two rounds already confirmed 21 pre-existing warnings, all in untouched test files, with a from-scratch build).
  • Focused filter FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry: 312/312 passed (309 + 3 new: 1 H1 + 2 H2).
  • Complete AcDream.Runtime.Tests: 948/948 passed (932 baseline + 16 new across the whole C3a slice), 0 skips.
  • Complete solution (dotnet test AcDream.slnx -c Release --no-build -m:1): every project reports 0 failed -- App 4028/3 skips, Bake 15/0, Cli 4/0, Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime 948/0, UI.Abstractions 543/0.
  • git diff --check: clean. Modified files now additionally include tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs (+47/-0, the new H1 test) alongside RuntimeInitialCreateResidenceState.cs (54 changed lines, up from 39 in round 2 -- the ToArray snapshot + doc comment) and the unchanged round-1/2 files. The pre-existing eight dirty paths remain line-ending noise only.
  • No staging, no commits, HEAD unchanged at 277ef5d0.

Files touched (round 3 additions)

  • src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs -- NotifyRetirement now snapshots via ToArray().
  • src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs -- now 690 lines (was 663): upfront unbound-Publication check in AdvanceCore; Discard/DiscardAll unbound-Publication tolerance.
  • tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs -- +1 new H1 test.
  • tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs -- now 922 lines (was 827): +2 new H2 tests.

This is the last round of code changes for C3a per the coordinator's message. GameRuntime.cs remains untouched throughout the whole slice; no production caller of Advance anywhere.

C3b implementer progress (remote body construction at Create)

  • Verified HEAD d62b9950 on codex/port-claude-agents; 8 protected dirty paths untouched.
  • Read: plan (C3b scope), float-gates doc (3 byte-certain gates), retail-notes.md (CreateObject 0x00558870 order; set_description 0x00514F40 order; PhysicsDesc::UnPack), writer map (6 canonical SetPhysicsBody writers), PhysicsBody.cs, RuntimeInitialCreateResidenceState.cs, RuntimeLocalPlayerFirstEntryState.cs (C3a shape), RuntimeSetPositionState mover/submit/ack/park/retry paths, RuntimePhysicsState bind sites, route classifier, C3a test harness.
  • Resolved SetMotionTableID(0) semantics from pseudo-C: CPhysicsObj::SetMotionTableID 0x00512780 (pc:280528) fails ONLY when part_array==0 (005127da) or MotionTableManager::Create fails for a NONZERO id (CPartArray::SetMotionTableID 0x005186E0, pc:286732, 0051872f); id==0 skips manager creation (0051871f) and returns 1 -> gate PASSES for zero id. CPhysicsObj skips MakeMovementManager for INVALID_DID (005127ca).
  • Recovered PhysicsDesc ctor defaults 0x0051D4D0 (pc:292056): friction "33s?" = 0x3F733333 = 0.95f; elasticity 0.05f; translucency 0 (memset); scale 1; state 0x400c08.
  • Recovered set_elasticity 0x0050FD40 (pc:277817): <0 -> 0; <=0.1 -> value; >0.1 -> 0.1. Cross-checked ACE PhysicsGlobals.MaxElasticity = 0.1f (PhysicsObj.cs:3586-3599).
  • Design: RuntimeRemoteFirstEntryState (Entities/, no publication chain) with stages MoverPreparation -> BodyConstruction (via canonical RuntimePhysicsState.GetOrCreatePhysicsBody factory, retail set_description order) -> Submit (lease.Placement) -> Withdraw/Place ack (TryPeekProjection loop for deferred parks) -> Execute. Pure builder RuntimeRemoteBodyDescription + construction receipt for gate proofs.
  • IMPLEMENTED: RuntimeRemoteBodyDescription.cs (288 L, pure set_description-ordered construction + gated receipt), RuntimeRemoteFirstEntryState.cs (620 L, six-stage resumable conductor, no publication chain), lifetime wiring (+51/-2: construction in all 3 ctors, third multicast retirement binding, BeginSessionClear DiscardAll, RemoteFirstEntryActiveCount snapshot field + IsConverged clause), tests (879 L, 29 tests, all pass first run).
  • GATES: Runtime build 0W/0E; solution build 0 errors, 21 pre-existing warnings none in changed files; focused filter (Residence|Executor|SetPositionState|FirstEntry| RemoteEntry) 247/247; complete Runtime 977/977; git diff --check clean; 8 protected dirty paths + AGENTS.md untouched; nothing staged/committed.
  • No conflicts stopped on: motion-table zero-id semantics resolved from pseudo-C (gate passes for id 0); elasticity clamp recovered (0..0.1) + ACE cross-check; PhysicsDesc ctor defaults recovered for absent-wire fields.
  • REVIEW ROUND (arch M1/M2 + retail R1/R2) closed: construction receipt now rides the terminal Advance out-param (M1); shared RuntimeFirstEntryAcknowledgement.IsStillPending used by BOTH conductors + new delete-after-commit-before-ack abandonment test (M2); movement branch re-gated on buffer non-emptiness per UnPack's buff_length!=0 assignment (R1, both-ways tests; parser's empty-buffer wrapper confirmed at CreateObject.cs:597); elasticity NaN -> 0f per retail's first-arm unordered (R2a, ACE divergence noted); friction NaN skip kept + commented per gates-doc quirk (R2b; doc addendum text in final report); translucency NaN matches retail apply-bucket already (noted).
  • FINAL GATES: focused 252/252; complete Runtime 982/982; build 0 errors, no warnings in changed files; git diff --check exit 0; nothing staged; protected paths untouched.

============================================================

C3c — THE HOST FLIP (new session)

============================================================

Reading phase (complete)

  • Verified worktree HEAD 78f1eb18 on codex/port-claude-agents; 8 dirty files = protected list.
  • Read: c3c-contract.md, plan doc C0-C3b notes, route inventory routes 1+8 + cross-cutting, canonical-body-writer-map, both conductors, executor surface, channel + subscription + retry slot, both sinks, both session routes, GameRuntime, RuntimeLocalPlayerMovementState, PlayerModeController (protected-noise), LiveEntityRuntime placement family, materializer, HeadlessSessionWorldProjection, RuntimeLiveEntitySessionController, publication Prepare, RuntimeFirstEntryAcknowledgement, synchronous event-stream dispatch, AcknowledgeProjection (strict FIFO-head, non-idempotent), PublishExecutorCompletion (snapshot carries body position/orientation + token.ExactCellId; residence released before publish).

Design conclusions

  1. One acknowledger per receipt: conductors consume their own initial Place/Withdraw (proven by AwaitingReceiptAcknowledgement tests). Both host sinks must return false (leave-at-head) for Place/Withdraw of an entity with an ACTIVE initial-create residence (TryGetInitialCreateResidence discriminator).
  2. ExecutorCompleted = the presentation-binding receipt (C3-1's purpose). Graphical sink applies Place-shaped presentation from its snapshot (celless => ack-and-ignore); returns false until sidecar+backend ready (existing FIFO-head retry semantics).
  3. Drive points: graphical = post-hydration in the Create flow + wrap the per-frame retry slot callback (drive conductors then RetryPending). Continuation placements completed via C0 fused TryPrepareAndSubmitAuthoredPlacement + AcknowledgeProjection.
  4. GameRuntime first act: LocalPlayerFirstEntry.BindPublication(Movement.PhysicsPublication) after AttachPhysicsPublication (GameRuntime.cs:260-265).

C3c implementation state at session end

  • ALL FIVE SCOPE ITEMS IMPLEMENTED in production code; complete solution builds 0 errors.
  • Runtime tests 982/982 GREEN (3 direct-sink tests rewritten to started-generation + driven-conductor form).
  • Headless tests 74/77 (3 failures: WorldProjectionHydratesCanonicalMovementAndTeleportState + 2 others — old SynchronizeLocalPlayer expectations; fixtures need conductor-driven rework).
  • App tests 3,865/4,031 passing, 163 failing after central fixture repair (LiveEntityRuntimeFixture now binds generation 1). Remaining classes: (a) ~90 fixtures with private lifetimes and no generation bound ("cannot acquire a structurally valid initial residence lease"); (b) ~40 hand-built spawns failing HasConsistentCreateIdentityAndParent ("inconsistent instance or parent projections"); (c) ~30 behavior-expectation updates (suppressed-until-receipt visibility, FullCellId staying 0 for undriven residences).
  • New contract-required integration tests NOT yet written; connected gates NOT reached (automated gates not green — per gate order, stopped and reported).
  • Nothing staged or committed. Protected dirty paths untouched except the sanctioned surgical PlayerModeController.cs touch (flagged).

Continuation session (fixture repair)

  • App: 443 -> 163 -> 93 -> 50 failing (3,978/4,031 passing). Repairs, all mechanical, NO assertion changes: (1) generation binds: LiveEntityRuntimeFixture (all 5 overloads), LiveEntityHydrationControllerTests fixture, EquippedChildProjectionWithdrawalTests fixture (own lifetime + production drive controller + landblock collision generation); (2) consistent-spawn repair: new shared tests/AcDream.App.Tests/LiveEntitySpawnFixture.cs (WithConsistentPhysics extension deriving the nested PhysicsDesc from flattened fields), applied to CreateSupersessionRecovery, Vfx light, DeferredLifecycle, LocalPlayerTeleport, LiveAppearanceAnimation, StreamingFrame builders; per-file physics blocks for LiveEntityPhysicsHostOwnershipTests (19->0) and EquippedChildProjectionWithdrawalTests child/embedded-parent spawns (23->0); (3) conductor-completion where legacy direct application now requires a released residence (NoPositionCreateParent test: CompleteFirstEntry before TryApplyCreateParent).
  • Headless: 77/77 GREEN. The 3 SynchronizeLocalPlayer-era tests rewritten CONDUCTOR-DRIVEN with the real host wiring (host.Start -> RegisterEntityWithInitialResidence -> drive controller constructed BEFORE registration -> HeadlessSessionWorldProjection pump). All original assertions preserved verbatim (controller identity, LocalEntityId, positions, PortalSpace/InWorld, CenterCount 3/2, receipt-validation no-authority invariants). Coverage not shrunk; WorldProjectionHydratesCanonicalMovementAndTeleportState now doubles as a headless first-entry integration flow.
  • Runtime: 982/982 GREEN (incl. probe-restoration build).
  • Autowalk probes RESTORED at the Runtime site (diagnostic-owner pattern, PhysicsDiagnostics.ProbeAutoWalkEnabled): [autowalk-target] + [autowalk-end reason=interrupt] in RuntimeLocalPlayerPhysicsPublicationState.Prepare's host/motion closures; [autowalk-end reason=complete] retained App-side on the approach-lifetime MoveToComplete.

Expectation changes

(Continuation 4 — the logged pass. Clause key: (1) suppressed-until-receipt visibility; (2) undriven-residence semantics; (3) residence-gated Place/Withdraw with ExecutorCompleted as the presentation-binding receipt; (4) sealed-setter lifecycle routing. Where the true mechanism is committed C3a/C3b/C3-1 residence design the clause labels do not literally name — SameIncarnationCreate FIFO staging (AD-59), conductor-built bodies at Create (C3b), no create-authority advance outside the residence transaction — the line cites the closest clause PLUS the design mechanism; all such lines are flagged [interp] for coordinator veto.)

  1. CurrentGameRuntimeAdapterTests.DirectAndGraphicalHosts_ProduceIdenticalEntityObjectTrace → old: both hosts register + hand-driven apply chain produce identical entity-object traces → new: at the deliberate zero generation BOTH hosts refuse the initial Create transactionally with the identical structural error ("cannot acquire a structurally valid initial residence lease"), identical EMPTY traces, zero entity/object counts → clause (2). Full driven-flow parity moved to the new C3c integration tests.
  2. UpdateFrameOrchestratorTests.GameplayInputOwnersUseTypedSeamsWithoutGameWindowBackReferences → old pinned PlayerModeController source order: PreparePositionForCommit → EnterChaseMode → SelectStableHostWithoutRebind → SyncPose → InstallOrRebind → SetPosition(initial) → CommitPreparedPosition → _controllerSlot.Controller = controller → IsPlayerMode=true → new pinned order: controller.IsRuntimePublished gate → EnterChaseMode → SyncPose → _hostSlot.Host = playerHost → IsPlayerMode=true → clause (4) (the deleted markers were exactly the App-side controller construction+commit).
  3. LiveEntityHydrationControllerTests.CompletedSpatialRecovery_DetectsCreateVersionDriftWithoutNestedHydrationRequest → old: PositionSequences [1,1,2], last purpose CreateSupersessionRecovery, InstalledCreateIntegrationVersion 2UL → new: [1,1], SpatialRecovery, 1UL → clause (3) [interp]: a post-residence same-generation Create is description-only at registration; its position churn flows through the freshness-gated events tail; create authority advances only inside the residence transaction, so no drift-replay fires.
  4. LiveEntityHydrationControllerTests.TimestampCallbackFresherSameGeneration_StopsOuterCreateVersion → old: single materialize from the nested newer spawn (PositionSequences[0] == 2) → new: single materialize from the admission-frozen seq-1 create (== 1); the seq-2 facts commit at the executor drain in array order → clause (3) [interp, AD-59 FIFO staging].
  5. LiveEntityHydrationControllerTests.ObjectRemovalCallbackFresherSameGeneration_WinsOuterReplacement → old: last materialize seq 3 + DoesNotContain(2, Skip(1)) → new: last materialize is the admission-frozen seq-2 replacement + DoesNotContain(3) (the seq-3 facts commit at the drain and never re-materialize) → clause (3) [interp, AD-59].
  6. LiveEntityHydrationControllerTests.FailedCompletedSupersession_RemainsPendingUntilExactRetry (both rows) → old: InstalledCreateIntegrationVersion == (retryFromLandblock ? 2 : 3) → new: == 2 in both rows (the retry retransmit no longer advances create authority) → clause (3) [interp]. Drift probe swapped from a nested OnCreate to record.Canonical.AdvanceCreateAuthority() (the drain-stage advance) so the retained-obligation/exact-retry machinery under test still fires; every other assertion verbatim.
  7. LiveEntityInboundAuthorityGateTests.Position_CanonicalInventoryObjectIsAcceptedBeforeProjectionExists → old: accepted.PositionAuthorityVersion == 2 (post-apply) → new: == 1 (admission-time; the merge is queued behind the pending residence and its authority advance commits at the drain) → clause (2).
  8. Probe swaps with ALL assertions verbatim (logged for transparency, mechanism = same-generation Creates no longer advance create authority at registration; modeled by record.Canonical.AdvanceCreateAuthority(), the exact drain-stage advance) [interp, clause (3)]:
    • LiveEntityHydrationControllerTests.CompletedSupersessionReadyFailure_RetriesWholeCommitBoundary (both rows)
    • LiveEntityHydrationControllerTests.ReadyPublisher_RevalidatesExactRecordBetweenEveryStage (3 rows)
    • LiveEntityHydrationControllerTests.ReadyPublisher_RevalidatesAfterReentrantRenderProjectionCallback
    • LiveEntityHydrationControllerTests.EquippedChildReadyCandidate_RejectsVersionAdvancedByPoseCallback
    • LiveEntityCreateSupersessionRecoveryTests.Recovery_CreateVersionAdvanceDuringAppearance_StopsLaterOwners
  9. LiveEntityHydrationControllerTests.ParentSupersession_CompletesAtExactAttachedReadyBoundary (both rows) → scenario repairs: (a) parent registered up front (retail queues a child Create under an unaddressable parent — R5-1 QueueBlobForObject — instead of applying it); (b) drift probe as in item 8; (c) the OnSpawnAction hook now mirrors the production relationship owner's sticky-residence conversion (AwaitRuntimePlacement → LegacyImmediate) at the world→attached kind transition. Assertions verbatim → [interp, clause (3)].
  10. LiveEntityHydrationControllerTests.PositionAfterPickup_ReentersWithSameEntityBodyAndResources → body scaffolding changed from seeding a fresh PhysicsBody to capturing the conductor-built canonical body (C3b bodies-at-Create; factory now throws if missing — a STRONGER assertion). Identity-preservation assertions verbatim → [interp, C3b].
  11. LiveEntityRuntimeTests.PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder → old: seeded RemoteMotionRuntime bodies observe state sync across bind/state arrival orders → new: the conductor-built canonical bodies are observed (C3b never-clobber forbids seeding a replacement); "arrival order" is now SetState-FIFO'd-before-the-drain vs SetState-after-completion; the two expected state-flag transforms are UNCHANGED → [interp, C3b + clause (3)].

Remaining at session end (App 50)

  • LiveEntityHydrationControllerTests 21 (supersession/recovery semantics + 5 "publication owner not bound" = the fixture drive needs a bound publication chain for isLocalPlayer leases: add RuntimeLocalPlayerMovementState+Identity+PublicationState+BindPublication to that fixture);
  • RuntimePlacementPresentationSinkTests 4 (sink behavior changed by design - residence gate + ExecutorCompleted presentation; these need logged expectation updates per contract clauses);
  • LiveEntityRuntimeTests 4, LiveEntityPresentationControllerTests 4, remaining consistent-spawn stragglers in builders that already carry physics blocks (field-level mismatches), CurrentGameRuntimeAdapterTests 2 (deliberate zero-generation binds), misc singles.
  • NOT STARTED: the five contract integration tests; Release build; complete solution (-m:1, ACDREAM_PAK_PATH); connected gates (blocked behind green automated gates per the pinned gate order).
  • No production regression found: the one suspicious trace (FullCellId=wire-cell in a pickup-supersession test) was ruled out against ApplyAcceptedSpawn (object-table only, no cell write) - it is a fixture-flow artifact.
  • Post-log continuation: hydration fixture publication chain bound (Movement/Identity/PublicationState + BindPublication + ServerGuid) -> hydration 21 -> 18; App total 50 -> 47 (3,981/4,031). Runtime 982/982 and Headless 77/77 re-verified green after all repairs. Nothing staged; protected paths still untouched (PlayerModeController touch remains the one sanctioned exception).

Continuation 2 (guard-zone stop)

  • Hydration fixture now also carries the REAL RuntimePlacementProjectionSubscription (ack-only sink mirroring production rules: Discard/ExecutorCompleted ack, Place/Withdraw left for conductors) - fixture is now the full production wiring shape (generation + collision generation + publication chain + drive pump + subscription). Hydration failures unchanged at 18 => the FIFO-wedge hypothesis is ruled OUT; the 18 supersession/recovery failures are NOT a simple fixture gap.
  • STOP under instruction-1 guard: the observed symptom (nested same-generation Create during RecoverProjection no longer triggers CreateSupersessionRecovery; Assert.Throws sees no throw at line 1247) means the initial residence is STILL ACTIVE when the nested create arrives, i.e. the drive after the first OnCreate did not complete the conductor in this fixture. Before editing ANY of these 18 tests' assertions I need to establish WHY the conductor yields here (candidate: something in the fixture's stub materializer / spawn shape leaves the placement deferred or the executor pending) - because if the same yield can happen in PRODUCTION's composed path, this is a real flip defect (login supersession burst leaving residences pending), not an expectation update. That determination requires a focused diagnostic run I could not complete in the remaining session budget.
  • Expectation changes so far: STILL ZERO.
  • Totals at stop: Runtime 982/982, Headless 77/77, App 3,981/4,031 (47 failed, 3 skipped).
  • Items 2-4 (zero-generation adapter tests, physics-block mismatch stragglers, five integration tests, Release/solution/connected gates) not reached.

Continuation 3 — DIAGNOSTIC + CLASSIFICATION (coordinator-directed)

  • Instrumented RuntimeFirstEntryDriveController.DriveOne (yield status + FIFO head + residence-active + FullCellId per step, file-logged); ran FailedCompletedSupersession_RemainsPendingUntilExactRetry.
  • YIELD CHAIN (before fix): first and only drive step yielded RejectedToken with residenceActive=False and fullCell=0x01010001 ALREADY COMMITTED — the residence was retired out-of-band before the pump ever ran.
  • ROOT CAUSE: the hydration fixture's RecordingMaterializer called the internal LiveEntityRuntime.MaterializeLiveEntity overload WITHOUT the residence parameter -> LegacyImmediate -> legacy RebucketLiveEntity fall-through (LiveEntityRuntime.cs:777) -> CommitRebucket committed the wire cell and advanced placement/spatial authority -> residence IsCurrent detected the out-of-band commit and retired the lease -> conductor correctly RejectedToken.
  • CLASSIFICATION: FIXTURE ARTIFACT. Production's route-1 materializer (src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs, MaterializeProjection: residence = retainedRecord?.MaterializationResidence ?? AwaitRuntimePlacement) never takes LegacyImmediate for an initial world create, so production cannot reach this retirement path. Post-fix diagnostic re-run: status=Completed, fullCell committed by the CONDUCTOR, FIFO drained — the drive converges.
  • Fixture fixed at the choke point (stub materializer now passes AwaitRuntimePlacement; subscription upgraded to a production-mirroring FixturePlacementSink: Discard ack, ExecutorCompleted -> TryApplyInitialCreateCompletionPresentation, Place/Withdraw residence-gated -> TryApplyRuntimePlacementProjection). Diagnostics stripped; Runtime rebuilt 0 errors.
  • Result: hydration failures 18 -> 19 (group did NOT shrink; composition changed — the flows now complete their conductors and fail on POST-flip semantic assertions: supersession/recovery expectations against suppressed-until-receipt visibility + conductor-committed cells). These 19 + the rest of the inventory are now genuinely the logged-expectation-update pass (guard rules unchanged, zero changes logged so far).
  • Totals at stop: Runtime 982/982, Headless 77/77, App 3,980/4,031 (48 failed, 3 skipped). No staging/commits.

Continuation 4 — expectation-update pass + integration tests + gate ladder

  • Session start state re-verified: HEAD 78f1eb18, protected dirty paths intact, nothing staged.
  • Full App run (Release): 47 failed / 3,981 passed / 3 skipped of 4,031 (one fewer than the Continuation-3 count of 48; the inventory below is the current truth).
  • Classified inventory: Hydration 19; Withdrawal 6; Sink 4; LiveEntityRuntime 4; Presentation 4; RemotePhysicsUpdater 2; CurrentGameRuntimeAdapter 2; singles 6 (LifecycleStress, UpdateFrameOrchestrator, LiveSessionResetPlan, InboundAuthorityGate, CreateSupersessionRecovery, LiveAppearanceAnimation).
  • CLASSIFICATION POLICY DECLARED (auditable): where a required change's true mechanism is committed, dual-reviewed C3a/C3b/C3-1 residence design that the four clause labels do not literally name (SameIncarnationCreate FIFO staging per AD-59; conductor-constructed bodies at Create per C3b; no create-authority advance outside the residence transaction), the change is logged under the closest clause WITH an explicit mechanism note and flagged in the final report for coordinator veto. Verbatim-assertion fixture repairs get no clause line per the guard's own exemption, but are listed below.

Fixture repairs (assertions verbatim, no expectation-change lines)

  1. RuntimePlacementPresentationSinkTests.Fixture.Materialize — normalize the stale residence (legacy-immediate materialization commits the wire cell out-of-band; the query performs lazy retirement) BEFORE tests capture ownership snapshots; previously the retirement happened inside the sink's own first residence query and shifted SetPositionOperationCount/ AwaitingSetPositionPreparationCount 1->0 mid-TryApply. 14/14 green.
  2. LiveEntityProjectionWithdrawalControllerTests.Fixture.Spawn — spawn had no Physics block but non-null Position/Setup/Scale + InstanceSequence!=0; wrapped with the shared WithConsistentPhysics (sanctioned for block-less builders). 6/6 green.
  3. LiveEntityPresentationControllerTests.Fixture.Spawn — builder ALREADY carries a physics block; field-level fix only (Timestamps.Instance was hardcoded 1 vs the instanceSequence parameter used by guid-reuse tests). NO blind wrap; RawState untouched. 12/12 green.
  4. RemotePhysicsUpdaterTests boundary Spawn(ushort instanceSequence, ...) — same field-level Timestamps.Instance fix; RawState untouched. Both green.
  5. LiveAppearanceAnimationTests.Capture_... — block-less cellless spawn with InstanceSequence 1; WithConsistentPhysics wrap (file's other builder already used it). Green.

Continuation 4 — production changes beyond the inherited diff (both

flagged for coordinator review; each is one revertible hunk)

  1. src/AcDream.App/Rendering/EquippedChildRenderController.cs (TryAttach, before MaterializeLiveEntity): converts a retained residence-managed child's sticky residence AwaitRuntimePlacement → LegacyImmediate at the world→attached kind transition. WITHOUT this, equipping a world-created (cut-over) item throws at LiveEntityRuntime.cs:655's residence-change guard ("cannot change its materialization residence from AwaitRuntimePlacement to LegacyImmediate") because the attach path passes the default LegacyImmediate — while the flip's own materializer comment (DatLiveEntityProjectionMaterializer.cs:712-722) states equipped children must carry LegacyImmediate so a later drop-to-world stays legacy "by construction". Found via ParentSupersession_CompletesAtExactAttachedReadyBoundary; production-reachable (pickup → CreateParent → TryAttach with a retained sidecar). This completes the flip's documented design, not new invention.
  2. src/AcDream.App/World/LiveEntityRuntime.cs (RebucketLiveEntity, residence-managed branch): while the initial-create residence is still ACTIVE, the presentation-only move now refuses (returns false) — the completion receipt is the entity's first world-visible moment (clauses 1/3). The inherited flip had made the presentation-only move unconditional, re-opening the mid-registration reentrancy hazard pinned by RuntimePlacementPendingMaterialization_OwnsResourcesWithoutPublishingResidence (a resource-registration observer could install a bucket for a suppressed record before its placement committed). A STALE residence lazily retires inside the same query, so post-residence legacy moves are unaffected; the completion-receipt path calls RebucketLiveEntityPresentationOnly directly and never crosses this gate.

Continuation 4 — additional fixture repairs (assertions verbatim)

  1. LiveEntityLifecycleStressTests fixture — generation bind on its private lifetime. 7. LiveSessionResetPlanTests.GraphicalResetHost — entity seeded through the legacy direct RegisterEntity (session-less GameRuntime has generation 0; the subject is reset/teardown retry, not the create flow).
  2. CurrentGameRuntimeAdapterTests.GraphicalObserverFailure — session started first (its siblings' existing pattern). 9. Hydration RecordingMaterializer — mirrors the production materializer's self-projection branch (committed cell + no active residence → presentation-only rebucket); fixed PositionAfterPickup/PositionAfterInventoryOnlyCreate spatial-projection truth. 10. PartialProjection_IsRetriedInsteadOfMistakenForCompletedHydration — models the production per-frame pump (FirstEntry.DriveAll) before the streaming recovery (the failed Create unwound before OnCreateCore's own pump; an undriven residence leaves FullCellId 0 and streaming candidates key off the committed cell). 11. LiveEntityRuntimeFixture.CreateDriven — NEW driven variant (collision generation + engine landblock + production RuntimeFirstEntryDriveController + ack-only subscription mirroring host rules); applied to InitialChildCreate_PreservesParentEventQueued..., PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp, and PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder.

Checkpoint: FULL App suite GREEN — 4,028 passed / 0 failed / 3 skipped

of 4,031 (Release). Next: Runtime + Headless re-verification, then the

five contract integration tests.

Continuation 4 — five contract integration tests (all green, real host

wiring: registration -> subscription -> RuntimeFirstEntryDriveController;

no hand-called conductor sequences)

NEW tests/AcDream.App.Tests/World/RuntimeFirstEntryHostIntegrationTests.cs (fixture: real RuntimeEntityObjectLifetime + generation + committed collision generation + LiveEntityHydrationController.OnCreate route + production RuntimePlacementPresentationSink behind the real RuntimePlacementProjectionSubscription + real drive controller + local-player publication chain; materializer is the production-mirroring double):

  1. InitialCreate_ResidenceConductorReceipt_BindsWorldVisibilityExactlyOnce — residence begins once, sidecar provably suppressed at materialize time, conductor completes inside the Create transaction, exactly one visibility edge, world snapshot at the committed pose, all ledgers drained.
  2. DeferredParentCreate_StaysInvisibleUntilParentReplay — unaddressable-parent child: no canonical/sidecar/presentation, queued under the parent GUID; parent Create replays it (real registration delegate), next-frame pump completes the parented conductor; child celless + presentation-suppressed, only the parent world-visible.
  3. LocalLogin_PresentationAttachFailure_RetriesWithoutRuntimeRollback — the camera/shadow-analog App attach failure (first visibility binding throws) AFTER the Runtime commit: published PlayerMovementController (IsRuntimePublished) + canonical body + committed cell all survive, the completion receipt stays pending, RetryPending() binds presentation with the SAME controller instance. NOTE: the literal PlayerModeController camera object is not constructed here (its ~20-dependency graph has no focused harness); its presentation-only rollback is pinned by the updated UpdateFrameOrchestrator source assertions + this receipt-level analog.
  4. GraphicalAndDirectHosts_CommitIdenticalFirstEntryRuntimeFacts — the same spawn through the full graphical wiring vs the no-window direct host shape (RegisterEntityWithInitialResidence + pump + ack-only subscription) commits byte-identical canonical first-entry facts (cell, versions, body pose/state/InWorld, snapshot sequence, local id). NEW in tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs:
  5. MissingPreparedCollisionYieldsTypedRetryAndCompletesWhenAvailable — flaky IPreparedCollisionSource (Missing -> Loaded) through the REAL HeadlessSessionWorldProjection.ProjectSpawn pump: no exception escapes, entity stays tracked/pending with no controller and cell 0, the session tick's retry pump completes placement + publishes the controller. Totals after the tests: Headless 78/78 (77+1); App integration class 4/4.

Continuation 4 — gate ladder

  1. Complete test projects (Release): App 4,032 passed / 0 failed / 3 skipped of 4,035 (4,031 + 4 new integration tests); Runtime 982/982; Headless 78/78 (77 + 1 new). GREEN.
  2. dotnet build AcDream.slnx -c Release --nologo: 0 errors, 18 warnings (pre-existing test-project warnings; none in files this session touched). GREEN.
  3. Complete solution (-m:1, ACDREAM_PAK_PATH=C:\Users\erikn\Documents
    Asheron's Call\acdream.pak): IN FLIGHT (background).
  4. Connected gates: ACE confirmed listening on UDP 9000 (PID 22100), C:\ACE\Server\ACE_Log.txt present, no AcDream.App/acclient processes running. Will run after gate 3.

Continuation 4 — gate ladder RESULT (stopped at gate 4 per pinned order)

  1. Complete solution (Release, -m:1, ACDREAM_PAK_PATH): GREEN — App 4,032/3 skips (4,035), Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,242/1 skip (4,243), Headless 78, Runtime 982, UI.Abstractions 543 = 10,782 passed / 0 failed / 4 skipped of 10,786.
  2. CONNECTED tools/run-connected-world-lifecycle-gate.ps1: FAIL — STOPPED HERE. Artifacts: logs/connected-world-gate-20260802-122749/ (report.json Passed=false; capped/ + uncapped-reconnect/ each with stdout.log, stderr.log, artifacts/). Both sessions connected, entered world as 0x5000000A, ran the 54-command UI probe, requested AND received graceful logout confirmation, then CRASHED identically with an unhandled System.InvalidOperationException: "A sealed, retired, or discarded Runtime movement controller cannot be mutated." EXACT CHAIN (identical in both sessions): PlayerMovementController.EnsureConfigurationMutable (src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:859) <- SetCharacterSkills(:1248) <- RuntimeMovementSkillProjection.ApplyTo( src/AcDream.Runtime/Gameplay/RuntimeMovementSkillProjection.cs:21) <- LiveSessionRuntimeFactory.ApplyMovementStats( src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:327; recompute callback registered at :300 in CreateCharacterBindings) <- LiveSessionEventRouter.RecomputePlayerQualities( src/AcDream.Runtime/Session/LiveSessionEventRouter.cs:406) <- ClientObjectTable.Ingest(WeenieData) <- ObjectTableWiring.ApplyEntitySpawn <- RuntimeEntityObjectLifetime.ApplyAcceptedSpawn(:926) <- LiveEntityHydrationController.OnCreateCore(:298) — an ordinary inbound Create datagram, processed AFTER the graceful-logout confirmation (stdout timeline: probe -> logout confirmed -> post-logout stat-chain recomputes -> crash). DIAGNOSIS (report-only, no fix attempted): the flip made the local movement controller Runtime-published with a SEALED configuration lifecycle (mutable only pre-publication; retired at teardown). The character-bindings quality-recompute subscription (LiveSessionRuntimeFactory.CreateCharacterBindings -> ApplyMovementStats -> controller.SetCharacterSkills) still mutates the controller directly on EVERY player-quality recompute; once the controller is sealed (published) or retired (logout teardown), that mutation throws. This is exactly the review-focus item "Sealed Controller setter: audit every compile break's fix — each must route through the publication lifecycle" — this site was missed because it is a RUNTIME mutation (EnsureConfigurationMutable), not a compile break. A fix must decide where server skill updates route post-flip (e.g. the Runtime movement-state seam / construction options at first entry + a Runtime-owned live-skill channel), which is a production design decision outside this session's mechanical remit — per the pinned gate order the session STOPS at this failure and reports. Secondary observation from the same logs (likely the same defect class, recorded for the fixer): after the unhandled exception the shutdown path reported "status=AbandonedIncomplete, blocked=native window" with Silk.NET "You cannot call Reset inside of the render loop!" — a crash-path artifact, not an independent bug. The nine-stop soak (tools/run-connected-r6-soak.ps1) was NOT run (gated behind the lifecycle gate's pass).
  • Nothing staged, no commits at any point; HEAD remains 78f1eb18; the 7 protected dirty paths + AGENTS.md carry only their inherited state (AGENTS.md the sole real pre-existing content diff; PlayerModeController touched only by the inherited sanctioned flip surgery — this session changed only the marker TEST for it, not the file).

C3c-F1 — movement-stat application through the Runtime seam

Candidate-window investigation (resolved with evidence)

  • The crash state was RuntimeOwnedDormant, not retired: the failing gate run's stdout shows the login world-reveal never completed (collision=False at every readiness line; event=cancel at logout with completed=False) — first-entry activation stayed DeferredCell for the whole session, so RuntimeLocalPlayerMovementState held the dormant controller when the post-logout ingest recompute fired. EnsureConfigurationMutable throws for dormant (PlayerMovementController.cs:848-861: only Standalone/ CandidatePreparing/RuntimePublished/dormant+groundPhase return).
  • CandidateSealed is UNREACHABLE through the seam: the sealed candidate is never installed into the movement owner (CanPrepare requires _movement.Controller is null, RuntimeLocalPlayerPhysicsPublicationState.cs:908; IsCurrent requires CanCommitRuntimeOwnedController(epoch, null), :937-939), and Prepare→Commit runs back-to-back inside one synchronous Advance step (RuntimeLocalPlayerFirstEntryState.cs:395-431) with no callback dispatch between (Prepare's construction is callback-free by design, publication state :319-334).
  • RuntimeOwnedDormant IS reachable across pump iterations: Commit installs the dormant controller (publication state :436) and Evaluate/CommitActivation DeferredCell yields AwaitingActivation with stage kept (RuntimeLocalPlayerFirstEntryState.cs:493-497) — inbound quality events pump between Advance calls. DECISION: apply-immediately-to-dormant (not defer-at-commit) — the dormant instance IS the controller that goes live (ActivateRuntimePublication at RuntimeSetPositionState.cs:2464 flips the same object), the writes touch only PlayerWeenie fields + the mover-flag latch (nothing the activation envelope validates), and the precedent is the existing dormant channels RefreshDormantRuntimePhysicsState/Vector (PlayerMovementController.cs:735-759) that land accepted server facts on the dormant owner mid-window. Deferring would need an activation hook and would leave the weenie stale for the activation ground-phase dispatch.

Implementation

  • PlayerMovementController: internal ApplyCharacterMovementStats(in snapshot) — lifecycle switch (live/dormant apply, terminal typed drop) + private ApplyCharacterMovementStatsCore (verbatim body of the deleted RuntimeMovementSkillProjection.ApplyTo, field writes not gated setters) + internal ReportExhaustionAtMovementBoundary (live-only exhaustion dispatch).
  • RuntimeLocalPlayerMovementState: public ApplyCharacterMovementStats( RuntimeMovementSkillState) + public ReportExhaustion() + public enum RuntimeMovementStatsApplication {AppliedLive, AppliedDormant, DroppedNoController, DroppedIncompleteSnapshot, DroppedDisplacedController}. Disposed-owner tolerant (typed drop, not ObjectDisposedException) per J3.6.
  • DELETED src/AcDream.Runtime/Gameplay/RuntimeMovementSkillProjection.cs (zero remaining consumers).
  • NEW src/AcDream.App/Net/LiveMovementStatsApplier.cs — owns the StaminaExhaustionEdgeTracker + logging; observes the exhaustion edge for both applied outcomes, dispatches only AppliedLive (dormant: no in-flight movement; activation reads the current stamina gate).
  • LiveSessionRuntimeFactory: constructs the applier; OnSkillsUpdated/ OnMovementStatsUpdated route through it; ApplyMovementStats + _staminaExhaustion deleted; ResetPlayerPresentation resets the applier. App keeps zero direct configuration mutations on the stats path.

Tests (all green at write time)

  • Runtime (7 new in RuntimeLocalPlayerMovementStateTests): live byte-identity vs the old direct path (InqRunRate/InqJumpVelocity/CanJump/JumpStaminaCost/ OwnPvpFlags), dormant-window write lands on the instance that goes live, terminal typed drop with unchanged observables + setter-still-throws, sealed-candidate defensive row, absent/incomplete drops, disposed-owner tolerance, ReportExhaustion lifecycle gating. 19/19 file total.
  • App (3 new in LiveMovementStatsApplierTests): the REAL crash chain (real WorldSession + LiveSessionEventRouter + ClientObjectTable.Ingest of the player row + real applier callback) against a retired-installed controller — no throw, typed displaced drop logged; dormant-window ingest applies + values current at activation; absent/incomplete silent skips.

Connected gate run 1 (post stats-fix): FAIL — second site of the same class

  • logs/connected-world-gate-20260802-125907: BOTH sessions confirmed graceful logout, then crashed on the SAME EnsureConfigurationMutable throw via the OTHER App-side mutation my sweep had already classified residual-unsafe: LiveEntityNetworkUpdateController.OnState:1006 → PlayerMovementController.ApplyPhysicsState(:366) at the dormant controller (post-logout inbound SetState; the login reveal never completes in this gate profile so the first-entry controller stays dormant all session — same as the original 122749 failure). The stats seam itself WORKED: the stdout shows repeated "player: applied server movement stats run=10205..." dormant applications with no ingest crash.
  • Disposition per the sweep clause ("route each through the seam or report why it is already lifecycle-safe" — this site cannot be reported safe): routed through the same owner seam pattern. NOT a guard at the throw site: the lifecycle decision moved into the owner.

Second routing (inbound local-player SetState)

  • PlayerMovementController.ApplyServerPhysicsState (internal, typed): live → exact ApplyPhysicsState body; dormant → DroppedDormantActivationOwned (the activation transaction re-reads the canonical FinalPhysicsState itself via RefreshDormantRuntimePhysicsState at both activation phases, and while the accepted SetState is queued behind the initial residence the App push carries that same UNCHANGED record value — RuntimeEntityObjectLifetime.TryApplyState:1351-1378 queues without advancing the record — so the drop is value-preserving by construction); terminal → DroppedDisplacedController. New enum RuntimeServerPhysicsStateApplication beside the stats enum.
  • LiveEntityNetworkUpdateController.cs:1005-1018 routes through the typed entry (result discarded; comment records both gate crash chains).
  • Tests: Runtime ApplyServerPhysicsState_DormantDropsForActivationAndLiveAppliesExactly (990/990 Runtime); App source pin C3cF1ProductionWiringTests. LocalPlayerInboundSetState_RoutesThroughTheTypedOwnerEntry (no direct ApplyPhysicsState caller remains in the file). App 4,036/3 skips of 4,039; Headless 78/78.

Connected gate run 2 (130455) — FAIL, root cause classified INHERITED; STOPPED per directive

  • Both sessions ran CRASH-FREE (0 unhandled exceptions; capped alive 7+ min until my graceful WM_CLOSE, uncapped alive the full 420 s timeout) — the F1 crash-class fixes hold live. 418 login entities + 10,384 total ingested cleanly with 158 dormant stat applications.
  • Harness failures: capped "client exited waiting for probe complete" (my directed close), uncapped "timed out after 420 s waiting for probe complete".
  • Wedge mechanism (link-by-link):
    1. First-entry conductor Prepares+Commits the DORMANT controller within the first frames (stats lines from stdout line 62).
    2. Activation stays DeferredCell; even after streaming collision readiness completes (~40 s, reveal line 223 collision=True ready=True) the activation/publication never commits — PlayerModeController logs ONE "Runtime first-entry controller ... not committed yet" (line 224, PlayerModeController.cs:247-250) and player mode never enters.
    3. Reveal (kind=Login) completes on readiness alone with materialized=False (RuntimeWorldTransitState.Complete:685-720 requires Materialized only for Portal kind), then per-frame readiness re-acknowledgements spam event=rejected reason=readiness-after-terminal (9,000+ lines).
    4. AcknowledgeWorldViewportVisible never fires (visible=False forever) → probe line 5 "timed out waiting for normal world viewport" (RetailUiAutomationScriptRunner.cs:308-313; route line 4-5 tools/connected-world-lifecycle.route.txt) → probe never prints complete → harness fails.
  • NOT an F1 regression — three proofs:
    1. 122749 (zero F1 changes) fails with the IDENTICAL harness failure string ("client exited waiting for probe complete", report.json), has NO probe-complete/checkpoint/player-mode lines, and its own crash (SetCharacterSkills throwing) proves the controller was never published there either.
    2. 122749's login was WORSE pre-F1: the per-Create ingest-recompute throw killed the process 26 s in (StartedUtc→FinishedUtc) with only 1 entity seen vs 418/10,384 under F1 — the coordinator's "122749 entered world normally" premise is contradicted by its own artifacts.
    3. The F1 write set cannot affect activation currency: the stats core touches only PlayerWeenie fields + the OwnPvpFlags latch; the activation envelope checks lifecycle/epochs/body identity only (RuntimeLocalPlayerPhysicsPublicationState.cs:961-992, :747-775); the DeferredCell decision is RuntimeSetPositionState cell-residency machinery untouched by this slice.
  • Residual root cause home: the C3c first-entry activation never resolves its deferred destination cell in the graphical host (or its pending drive entry stops being re-armed) — RuntimeLocalPlayerFirstEntryState.cs:433-501 (PublicationCommitted → EvaluateActivation/CommitActivation DeferredCell loop) against RuntimeSetPositionState's dormant-activation cell gate. That is conductor/publication semantics OUTSIDE the F1 contract → STOPPED, no fix attempted, per coordinator directive 3.
  • Client terminated via graceful-close discipline (WM_CLOSE → logout confirmed by ACE, clean [session] lines, no crash); harness concluded and recorded FAIL; artifacts preserved at logs/connected-world-gate-20260802-130455/.

C3c-F2 — the login DeferredCell activation wedge

Step 1 — artifact re-verification (the pinned contract's evidence chain is

WRONG on run 122749; both prior classifications were partly unsound)

Read directly from the primary artifacts, not from either prior report:

  • 122749 (flip WITHOUT F1), capped/stdout.log is 67 lines TOTAL. It shows [UI-PROBE] running 54 UI probe command(s) (L52) — that line is the probe ANNOUNCING its command count, not 54 commands completing. The reveal shows ONLY event=begin (collision=False) and ONE event=readiness (collision=False, ready=False, materialized=False, visible=False), then event=cancel ... visible=False. The smoke plugin saw 1 entity total. report.json StartedUtc -> FinishedUtc = 26 SECONDS for BOTH sessions. => the contract's "world became visible / probe's wait world-visible passed / 54-command probe ran" is falsified. The world was NEVER visible in 122749; the process died ~9 s after entering world from the F1 crash, i.e. BEFORE the ~40 s collision-readiness edge where the wedge manifests.
  • Consequence: 122749 proves NOTHING either way about post-readiness activation (it never got there). The F1 agent's "identical harness failure string" proof is equally weak (identical string, different causes: crash vs timeout). Both prior attributions are unsound; attribution has to come from CODE, not from these two logs.
  • 130455 (flip WITH F1) is the only run that reaches the readiness edge: L223 readiness collision=True ready=True; L224 not committed yet (ONE line); L225 event=complete ... materialized=False; then 5,577+ readiness-after-terminal; probe L1193 wait world-visible 30000 timeout.

Step 2 — the contract's primary suspect is STRUCTURALLY disproven

F1's sweep change (LiveEntityNetworkUpdateController.cs:1005-1018) passes record.FinalPhysicsState into the typed owner entry. The activation cell gate reads that SAME canonical value directly off the record — RuntimeSetPositionState.cs:1747-1752 builds canonicalRequest with MoverPhysicsState = record.FinalPhysicsState. The activation path never reads the controller's copy. A drop on the CONTROLLER therefore cannot deprive the activation of anything, "value-preserving" or not. The primary suspect cannot produce a DeferredCell wedge. Contract constraint 3's "retained-to-activation admission" is not the fix; the real defect is below and is entirely inside C3c-flip code that F1 never touched.

L1. Login: the first-entry conductor prepares the authored mover and Prepare+Commits the publication in one synchronous step (RuntimeLocalPlayerFirstEntryState.cs:395-431). The controller is now RuntimeOwnedDormant (RuntimeLocalPlayerPhysicsPublicationState.cs:433-436) — confirmed live by the 158 player: applied server movement stats dormant lines in 130455. L2. Stage PublicationCommitted -> EvaluateActivation -> engine SetPosition (RuntimeSetPositionState.cs:1770). The destination landblock's collision is still being published (the reveal reports collision=False for ~40 s), so the result is DEFERRED. L3. CommitActivation -> TryApplyDormantLocalActivationCommit PARKS the operation (RuntimeSetPositionState.cs:2066-2107): Stage=AwaitingCell, WakeableLostCell=true, CollisionGenerationReady=false, and CollisionGeneration = prepared.DeferredCollisionGeneration, computed at :1939-1940 as _physics.ExpectedCollisionGeneration(cellId) = the IN-FLIGHT admission's generation G (RuntimePhysicsState.cs:2934-2939). Bucket key = (cell, prefix, G). L4. ~40 s later the landblock's collision generation commits. RuntimePhysicsState.cs:2503 calls SetPosition.CommitCollisionGeneration(lb, G, ready:true), which finds bucket (cell, prefix, G), verifies IsSpawnCellReady, and sets operation.CollisionGenerationReady = true (RuntimeSetPositionState.cs:3886). The push-side RetryDeferred is deliberately a no-op for this operation — it returns immediately when operation.DormantLocalActivation is set (:4044-4045) by design: the local-player lease must re-enter through the sealed evaluation/commit path. So the ONLY door left is the PULL-side rearm on the conductor's next Advance. L5. Immediately after :2503, AdvanceCommittedActivation REMOVES the admission from _collisionAdmissions (RuntimePhysicsState.cs:2552-2558) while leaving _collisionGenerations[lb] == G (set at BeginCollisionAdmission, :2066). From this instant on, ExpectedCollisionGeneration(cellId) no longer returns G — with no admission it returns _collisionGenerations[lb] + 1 = G+1 (RuntimePhysicsState.cs:2940-2944). L6. THE DEFECT. The next Advance reaches TryRearmDeferredDormantLocalActivation (RuntimeSetPositionState.cs:1851-1882), whose gate includes operation.CollisionGeneration != _physics.ExpectedCollisionGeneration( operation.ExactCellId) (:1870-1871) -> G != G+1 -> rearm refused, permanently. ExpectedCollisionGeneration means "the generation that will make me ready" at PARK time and "the next, not-yet-begun generation" at WAKE time; the rearm compares against the wrong one. Nothing else ever clears WakeableLostCell, so the operation is wedged for the session. L7. EvaluateActivation's fallback then reports DeferredCell forever (publication state :469-476 -> IsDormantLocalActivationAwaitingCell true), the conductor yields AwaitingActivation and keeps its pending entry (first-entry state :493-497), the controller never activates, and IsRuntimePublished stays false -> PlayerModeController.cs:243-251 logs "not committed yet". L8. SECOND LINK (flip-introduced, independent). PlayerModeAutoEntry.TryEnter sets _armed = false BEFORE invoking EnterPlayerMode (PlayerModeAutoEntry.cs:227-228) — a one-shot. Its IsPlayerControllerReady precondition in the PRODUCTION context is the constant true (PlayerModeAutoEntry.cs:86). That was harmless pre-flip because TryEnter CONSTRUCTED the controller and could not fail on it (deleted block, git diff src/AcDream.App/Input/PlayerModeController.cs); post-flip TryEnter returns false when the conductor has not committed. So the single shot is burned on the exact frame readiness flips, and LivePlayerModeAutoEntryContext.EnterPlayerMode calls _worldReveal.Complete() UNCONDITIONALLY (PlayerModeAutoEntry.cs:97-101) — sealing the reveal (materialized=False) and producing the 5,577 readiness-after-terminal rejections. The flip's own comment ("auto-entry retries on a later frame", PlayerModeController.cs:242) is false as written. Ordering note: UpdateFrameOrchestrator.cs:201-207 runs streaming -> live frame (DriveAll) -> auto-entry, so with L6 fixed the same frame would usually succeed — but "usually" is a race, and the log proves a failed attempt burns the shot and seals the reveal.

Why the existing rearm test never caught L6

RuntimeLocalPlayerPhysicsPublicationStateTests .DeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWake (:337-383) wakes the operation by calling SetPosition.BeginCollisionGeneration / CommitCollisionGeneration DIRECTLY, bypassing RuntimePhysicsState's admission ledger. _collisionAdmissions and _collisionGenerations therefore stay EMPTY, so ExpectedCollisionGeneration returns 1 at both park and wake and the identity check accidentally holds. Production always goes through BeginCollisionAdmission -> CommitCollisionGeneration, which is exactly the path that breaks it.

The fix (2 hunks)

F2-1 (root cause, Runtime): the rearm's generation identity check compares against the LIVE collision generation authority (_physics.CollisionGenerationAuthority, RuntimePhysicsState.cs:2953-2962 = _collisionGenerations[lb]), not ExpectedCollisionGeneration. Post-commit that is exactly G, so the parked lease rearms. Every stale case still refuses: a superseding admission or a cancel bumps _collisionGenerations away from G. Park-side semantics (:1939) untouched — "park against the generation that will make me ready" is the established convention shared with the remote ParkDeferred path (:3974). No latch is loosened: WakeableLostCell, CollisionGenerationReady and IsSpawnCellReady remain mandatory. F2-2 (second link, App): LivePlayerModeAutoEntryContext .IsPlayerControllerReady stops lying — it reports the exact precondition PlayerModeController.TryEnter enforces (Runtime-published controller + committed EntityPhysicsHost). The one-shot latch, the reveal latch, and the readiness-after-terminal rejection are all UNCHANGED; the trigger simply cannot burn its shot before the conductor has committed, so _worldReveal.Complete() runs only on a real entry. PlayerModeController.cs is NOT touched by this fix.

Step 4 — LIVE PROBE CORRECTION (temporary attributed probe, since stripped)

The Step-3 analysis was right about the parts but wrong about which link fires first. A temporary change-only probe on the rearm gate terms and the drive controller's local status (env-gated, stripped before the gate) was run against ACE. Evidence, run logs/c3c-f2-probe.out.log (F2-1 only, no admissibility term):

L61  [c3cf2-rearm] ... ready=False ... gen=1 auth=0 expected=1 spawnReady=True
L220 [c3cf2-rearm] ... ready=False ... gen=1 auth=1 expected=1 spawnReady=True
L221 [c3cf2-rearm] ... ready=True  ... gen=1 auth=1 expected=1 spawnReady=True
L222 [c3cf2-drive] local status=RejectedAuthority step=0 pending=59
  • L61: the lease parks at generation 1 with NO admission and NO committed generation yet (auth=0), i.e. ExpectedCollisionGeneration's 1UL default.
  • L221: the collision-generation commit marks it ready — and expected is STILL 1, proving the admission is still registered at that instant: the commit reenters the host's first-entry pump between RuntimePhysicsState.cs:2503 and the retirement at :2552-2558.
  • L222: the rearm succeeds inside that window, the immediately following evaluation fails TrySealCollisionEvaluationAuthority on the still- registered admission, and because the lease is no longer AwaitingCell, EvaluateActivation answers RejectedAuthority — TERMINAL. The conductor discards and the drive entry is dropped for the session. => LINK 3 (the reentrant-window rearm) is the DOMINANT live blocker, and it fires BEFORE the L6 generation mismatch can. L6 is still real and still load-bearing — see the next run.

Second live run with both terms (logs/c3c-f2-probe2.out.log):

L223 [c3cf2-rearm] ... ready=True gen=1 auth=1 expected=1 -> refused (window)
L224 [c3cf2-rearm] ... ready=True gen=1 auth=1 expected=2 -> REARMED
L225 [c3cf2-drive] local status=Completed step=0 pending=2
L230 live: auto-entered player mode for 0x5000000A
L231 [world-reveal] event=complete ...
L234 [world-reveal] event=world-visible ... visible=True

L224 is the direct proof that F2-1 is load-bearing: at the frame the rearm actually happens the admission is gone, so expected is 2 and only the committed authority still equals the parked generation 1. Zero readiness-after-terminal lines in the whole run.

Final fix (3 hunks, all required)

F2-1 RuntimeSetPositionState.TryRearmDeferredDormantLocalActivation — compare the parked generation against CollisionGenerationAuthority (the generation the collision world HOLDS) instead of ExpectedCollisionGeneration (which means "the next, not-yet-begun generation" once the admission retires). F2-3 same method — refuse the rearm while the destination prefix is not evaluable, using the seal's own predicate, now factored as RuntimePhysicsState.IsCollisionEvaluationPrefixAdmissible and consumed by BOTH the seal and the rearm so they cannot drift. This is the same shape the remote wake path already had via TryGetBlockingQuiescence (:4069-4095). F2-2 LivePlayerModeAutoEntryContext.IsPlayerControllerReady — report the Runtime first-entry commit instead of the constant true, so the one-shot guard cannot burn its single attempt (and unconditionally complete the world reveal) before the conductor has published the controller.

Tests

  • NEW RuntimeLocalPlayerPhysicsPublicationStateTests .DeferredCommitRearmsAfterProductionAdmissionCommitsItsGeneration — pins F2-1; fails pre-fix.
  • NEW RuntimeLocalPlayerPhysicsPublicationStateTests .DeferredCommitStaysParkedWhileTheCommittingAdmissionIsStillRegistered — pins F2-3; pre-fix it fails with the exact live symptom (Expected: DeferredCell / Actual: RejectedAuthority).
  • NEW tests/AcDream.App.Tests/Input/C3cF2AutoEntryWiringTests .ProductionAutoEntryRequiresTheRuntimePublishedController — pins F2-2 (source pin; the production context's ~15-dependency graph has no focused harness, matching the C3c-F1 precedent).
  • CONVERTED to the production wake path (assertions verbatim; only the wake DRIVER changed, from the raw SetPosition seam to the collision admission ledger, because the raw seam leaves the ledger empty — a state production can never reach, and precisely why these tests missed the wedge): RuntimeLocalPlayerPhysicsPublicationStateTests .DeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWake, .DeferredAuthoredActivationSuspendsRowsAndExactWakeRestoresThem, RuntimeLocalPlayerFirstEntryStateTests .AwaitingActivationRetriesWhileCellUnresolvedThenResumesAfterGenerationWake, .DeleteAndSameGuidReincarnationAutomaticallyFreesThePublicationSlotForTheFreshIncarnation. All four fail pre-fix once converted.

Gates

Runtime 992/992; App 4,037/0/3 skips; Headless 78/78; Release solution build 0 errors / 18 pre-existing test-project warnings; complete solution (-m:1, ACDREAM_PAK_PATH) 10,797 passed / 0 failed / 4 skipped of 10,801; git diff --check clean.

CONNECTED GATE: logs/connected-world-gate-20260802-135444 — RESULT=FAIL, but NOT on the wedge, which is gone in both sessions:

  • capped: login reveal reached world-visible, the probe captured checkpoint capped_login AND screenshot capped_login.png, then teleported (old lb=(169,180) new lb=(9,4)), and generation 2 (kind=Portal) reached materialized=True, visible=True, completed=True. 17,043 entities ingested.
  • uncapped-reconnect: login at cell 0x09040008 reached visible=True, completed=True.
  • Zero readiness-after-terminal lines, zero "not committed yet", zero F1-class controller-mutation crashes in either session.

BLOCKER (pre-existing, out of C3c-F2 scope) — map-corner landblock

Both sessions then died identically: System.ArgumentOutOfRangeException (Parameter 'landblockId') at RuntimeSetPositionState.BeginCollisionPrefixQuiescence (src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs:783) <- RuntimePhysicsState.BeginCollisionPrefixQuiescence(:1993) <- RuntimePhysicsState.CommitCollisionGeneration(:2432) <- LandblockPhysicsPublisher.AdvanceCompleteOne (src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs:626) <- LandblockPresentationPipeline.Advance <- StreamingController.Tick. Mechanism: the teleport destination is landblock (9,4). The far streaming radius is 12 and StreamingRegion only SKIPS out-of-range indices (nx < 0 || nx > 0xFF, StreamingRegion.cs:80/117/155/225) — it does not skip (0,0) — so the window includes landblock id (0<<24)|(0<<16)|0xFFFF = 0x0000FFFF. CanonicalLandblock keeps that as 0x0000FFFF, and BeginCollisionPrefixQuiescence computes prefix = landblockId & 0xFFFF0000 = 0x00000000 and throws its prefix == 0u guard (:781-783). Dereth's south-west corner landblock therefore cannot be collision-published, and any position within the far radius of it crashes the client. This code is untouched by the C3c flip, by C3c-F1, and by C3c-F2 (git diff src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs contains no change to BeginCollisionPrefixQuiescence); it was simply unreachable while login itself was wedged — 130455 never left Holtburg and never teleported. NOT fixed here, and deliberately not a one-line guard removal: prefix 0 is also overloaded as an "absent prefix" sentinel in the same class — e.g. ParkDeferred derives operation.CollisionQuiescenceHeld = collisionPrefixOverride != 0u (:3975-3978), so a genuine landblock-(0,0) quiescence override would be read as "no override". A correct fix needs an explicit has-prefix flag (or a nullable prefix), which is a physics-ownership design change outside this contract. Recommend a dedicated slice.

C3c-F3 — corner-landblock prefix-0 sentinel conversion

Chain audit (complete, every prefix-sentinel site classified)

CONVERTED (the has-prefix representation is nullable uint for the override pair + OperationId-based token presence + explicit landblockId==0 absent-id input guards):

  1. RuntimeSetPositionState.cs RuntimeCollisionPrefixQuiescenceToken.IsValid (:112) — dropped LandblockPrefix != 0u; presence now discriminated by OperationId != 0 (monotonic from 1) + CollisionGeneration != 0 (from 1). This was load-bearing beyond the crash site: a real prefix-0 token read as invalid wedged TryGetCurrentQuiescence (:839/:887/:905 callers), permission currency (:886), and TryGetBlockingQuiescence's excluded term (:3617 — RetryDeferred's restoringQuiescence would have blocked itself).
  2. RuntimeSetPositionState.BeginCollisionPrefixQuiescence (:782) — the crash-site prefix == 0u throw replaced by landblockId == 0u (absent id), prefix computed unconditionally.
  3. RuntimeSetPositionState.ParkDeferred (:3977-78, :4013-19) — override params converted ulong collisionGenerationOverride = 0UL, uint collisionPrefixOverride = 0u -> ulong?/uint? = null; CollisionQuiescenceHeld = collisionPrefixOverride.HasValue. Both quiescence-token call sites (:2842, :2893) pass values unchanged (implicit lift); byte-identical for nonzero prefixes.
  4. RuntimeSetPositionState.ParkCollisionResidents (:3414-17) — ?? 0UL / ?? 0u collapse removed; quiescence?.Token.X now flows null/value.
  5. RuntimePhysicsState.BeginCollisionPrefixQuiescence (:1991) — dead canonical == 0u (CanonicalLandblock ORs 0xFFFF, never 0) replaced by a LIVE landblockId == 0u guard.
  6. RuntimePhysicsState.AdvanceCollisionRetirementMutation (:2626) — same dead-guard replacement.
  7. RuntimePhysicsState.BeginCollisionAdmission (:2037) — NEW landblockId==0 guard at the admission entrance: an absent id canonicalizes to 0x0000FFFF (the REAL corner landblock), and the old accidental commit-time protection (the prefix throw) is gone.
  8. ShadowObjectRegistry.DeriveOutdoorSeed (:651, Core) — lbPrefix == 0u -> no seed replaced by landblockId == 0u; corner-block baked statics now derive real seeds 0x0000000N (previously silently dropped from the shadow world). Same sentinel class, exercised by the corner collision publication chain.

KEPT with justification (no collision with prefix 0):

  • Generation-0 "unbound" sentinel (RuntimeSetPositionState :3727/:3741-45/ :3811/:3842/:5135/:5180/:5255): generations allocate from 1 (checked(current+1), Begin throws on 0) — 0 is unreachable as a real generation.
  • ExactCellId==0 "absent cell" (IndexDeferred/IndexUnboundDeferred/ UnindexDeferred): cell-part 0x0000 is not a valid cell; corner cells are 0x00000001+.
  • RuntimePhysicsState dead post-canonicalization zero checks (ExpectedCollisionGeneration :2932, IsCollisionEvaluationPrefixAdmissible :2963, CollisionGenerationAuthority :2977, TrySealCollisionEvaluationAuthority Add :3030): CanonicalLandblock never returns 0; harmless dead defensive terms, prefix-agnostic. (Noted follow-up: CanonicalLandblock(0) aliases cellId 0 -> 0x0000FFFF in these helpers; unreachable with real operation cells today, flagged rather than redesigned.)
  • Core PhysicsEngine bare-id compat (:1355/:1362 SampleTerrainWalkableInCell, :2093 HasCellSurface): requestedPrefix == 0 there means "caller passed a bare pre-#106 test-fixture id", a different semantic; corner cells resolve correctly through the world-position filter. Follow-up cleanup candidate, out of this contract's chain.
  • Convention pinned by test: raw input 0x00000000 remains "absent" everywhere; landblock (0,0) is addressed by canonical 0x0000FFFF (or any cell inside it) — matching what production streaming always passes.

Tests (7 new, all fail pre-fix / pass post-fix; pre-fix run of the

production-chain test reproduced the EXACT 135444 crash signature:

ArgumentOutOfRangeException 'landblockId' at RuntimeSetPositionState.cs:783

<- RuntimePhysicsState.cs:1993 <- :2432)

New partial tests/AcDream.Runtime.Tests/Physics/ RuntimeCollisionPrefixQuiescenceTests.CornerLandblock.cs:

  • CornerLandblockCollisionGenerationCommitsThroughTheProductionAdmissionChain (corner 0x0000FFFF + neighbor 0x0001FFFF, empty engine, full admission->prepare->stage->seal->commit, ownership converged)
  • CornerResidentParksAndRestoresAcrossAnActivationReplacement (SetPosition commit into corner cell; activation replacement parks the resident under the prefix-0 quiescence override, wakes, restores)
  • CornerPrefixQuiescenceHoldsAndReleasesExactlyLikeANonzeroPrefix (contract test 2: identical held-placement script vs PrefixP, step-for-step log parity incl. QuiescenceHeld hold/acquire/cancel/restore)
  • CornerLandblockDemotesAndWithdrawsThroughRetirementMutations
  • AbsentLandblockIdStillCannotBeginQuiescence (id-0 keeps throwing at quiescence AND at the new admission-entrance guard; corner token IsValid) tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs:
  • Register_CornerLandblock_DerivesRealOutdoorSeed
  • Register_AbsentLandblockId_StillKeepsWhenEmpty (passed pre-fix too — pins the preserved keep-when-empty guard) Two in-flight test corrections during TDD: seeded corner Place asserts CommittedHostAcknowledgementPending (the ordinary bound-events commit status — over-strict first draft); parity run addresses the corner by canonical 0x0000FFFF, not raw 0 (which is the absent sentinel by design).

Gate ladder

  1. Focused corner tests: 5/5 Runtime + 39/39 ShadowObjectRegistry suite. Complete projects: Runtime 997/997 (992+5), App 4,037/3 skips of 4,040, Headless 78/78.
  2. Release solution build: 0 errors. Complete solution (-m:1, PAK): running. 2 (cont). Complete solution (-m:1, ACDREAM_PAK_PATH): 10,804 passed / 0 failed / 4 skipped (App 4,037/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,244/1, Headless 78, Runtime 997, UI 543).
  3. Connected gate: run logs/connected-world-gate-20260802-142539 launched against live ACE (UDP 9000 confirmed listening, no stale client); result pending.
  4. git diff --check: clean (line-ending metadata warnings only, matching the known worktree pattern). Nothing staged. 3 (result). Connected gate PASS: logs/connected-world-gate-20260802-142539/ report.json Passed=true, Failures=[], both sessions exit 0, one warning ("capped: 25 expected world-edge landblock miss(es)" — the expected class). The 135444 corner crash is gone: the capped session completed the full route (capped_login, facility_hub, aerlinthe_first, rynthid, holtburg_after_dungeon, aerlinthe_revisit checkpoints + screenshots under capped/artifacts/), uncapped-reconnect completed uncapped_reconnect. Coordinator confirmed and directed the soak.
  5. R6 soak launched (run-connected-r6-soak.ps1, same ACE, detached PID 14036); result pending. 5 (result). R6 soak logs/connected-r6-soak-20260802-143157: RESULT=FAIL per the PRIMARY artifact (report.json Passed=false, 37 failures) — the coordinator's "concluded successfully" summary was based on the markers log (route-complete + graceful close are true) but the pass criterion is report.json. Failure class: streamingWork convergence (deferredCompletions/deferredAdoptedCpuBytes/pendingPublications=1/ farBacklog nonzero) at 8 of 9 canonical checkpoints (aerlinthe clean), plus Caul plateau loadedLandblocks/totalLandblocks 283->189 and mesh cache growth 547->588 / +40.5 MB. No crash, no exception, graceful logout confirmed; 9/9 checkpoints + screenshots captured. ATTRIBUTION ANALYSIS (verified, not guessed):
    • Last passing soak (logs/connected-r6-soak-20260727-004942.report.json, Passed=true, 0 failures) enforced the SAME streamingWork expect-zero criterion and met it — but predates the ENTIRE uncommitted C3c flip+F1+F2+F3 stack, so it separates {branch} from {baseline}, not F3 from the flip.
    • F3's blast radius was provably NOT exercised in the failing run: grep 0x0000FFFF over the soak out.log = 0 hits (the corner landblock never entered any route window), zero exceptions (the new absent-id guards never fired), and every F3 conversion is byte-identical for nonzero prefixes (pinned by the parity test). The failing criterion is streaming/publication convergence — a C3c-flip/F1/F2-era surface.
    • The lifecycle gate 142539 (WITH F3) passed cleanly the same day. STOPPED per contract — no retry, no further code work, nothing staged.

C3c-F4 — soak streaming-convergence regression: DIAGNOSED, STOPPED (no fix landed)

Verdict: NOT a wedge and NOT in the C3c flip/F1/F2/F3 diff. It is a throughput regression in the committed collision-generation atomic-replacement mechanism.

  1. StreamingController.DrainAndApply (src/AcDream.App/Streaming/StreamingController.cs:1662-1690) advances the completion-queue head and breaks when it does not complete — at most ONE landblock publication per frame.
  2. LandblockPresentationPipeline.Advance stage publication-index-physics (src/AcDream.App/Streaming/LandblockPresentationPipeline.cs:612-627) charges EntityOperations: PreparationCursor < Entities.Count ? 1 : 0. A FAR build is Array.Empty<WorldEntity>() (PublishAsFar, :419-447), so every step is FREE and only the 2 ms elapsed-time ceiling bounds it.
  3. LandblockPhysicsPublisher.AdvancePreparationOne (src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs:297-309) gates on RuntimePhysicsState.AdvanceCollisionGenerationPreparation.
  4. That calls PreparedLandblockCollisionGeneration.AdvanceStagingClone (src/AcDream.Runtime/Physics/RuntimePhysicsState.cs:531-560) → PhysicsEngine.CollisionStagingBuilder.Advance (src/AcDream.Core/Physics/PhysicsEngine.cs:842-925): ONE leaf per step of an off-side draft of the COMPLETE collision world minus the target prefix (landblock slots, CellStruct, FlatCellStruct, FlatEnvCell, Buildings, EnvCells, Terrain, OutdoorCells, shadow-owner slots).
  5. PhysicsEngine.CommitLandblockReplacement (:304-321) does stagingCache.CollisionWorld.TransferTo(activeCache.CollisionWorld) — the atomic unit is the WHOLE world, which is why the whole world must be cloned.

Measured (attributed probes, lifecycle-gate route, since stripped)

  • median 19,736 / p90 32,135 / max 38,021 clone leaves per landblock publication; median 3.64 ms CPU each; 1,584 preparations = 8.53 s CPU in one 4-minute capped session.
  • Far-queue drain measured at ~10 landblocks/s; [queue-stale] showed the head seq advancing 234→488→548→603→…→1360 and far count 411→351→296→242→190 — the queue drains, it never catches up. pendingPublications=1 is the one head in flight, not a stuck item.
  • [fifo-wedge] never fired: pendingProjections=0. The placement projection FIFO, the sink's C3c residence gate, and the conductors are NOT involved.

Attribution

git log -S CollisionStagingBuilder → introduced by 6b28ff99 "fix(physics): make collision activation starvation-free" (2026-07-31), on top of be94bc9b (atomic activation, 2026-07-31) / 9b0f59bd (2026-08-01). The last PASSING soak is a9a822f2 (2026-07-27) — before all three. git diff touches no file on this path.

Why no fix landed (contract STOP rule)

Tried the one semantics-preserving lever: batch 256 leaves per metered preparation step. Measured 1.8x (gate far backlog 334→187 / 264→130, loaded landblocks 291→438 / 261→395) — real but NOT convergence. It also fails RuntimePhysicsStateTests.DenseResidentWorldAdmissionIsConstantAndMaterializesOneLeafPerStep (tests/AcDream.Runtime.Tests/Physics/RuntimePhysicsStateTests.cs:983, Assert.InRange(step.WorkUnits, 0, 1)) — one-leaf-per-step is an ASSERTED invariant of the slice that introduced the clone. Reverted; tree is byte-identical to flip+F1+F2+F3.

Convergence requires the clone to become O(changed) instead of O(resident world) — structural sharing in CollisionWorldState, or a per-landblock (not whole-world) atomic replacement unit. That is a semantics change to the C2-era mechanism and belongs to its own slice.

Secondary symptoms — same mechanism, not separate defects

loadedLandblocks 283→189 and visibleLandblocks 30 vs the baseline's 180 are the far ring never converging (baseline held 625 loaded at every checkpoint). Mesh cache 547→588 / +40.5 MB is different far-ring subsets resident at the two visits; retest for a true leak only after convergence is restored.

C3c-F5 — local-player first-entry contact seeding

Research (grep-named-first, complete before design)

  • Retail local-player seeding point: SmartBox::HandleCreateObject 0x00454C80 runs SmartBox::init_player 0x00455010 then CPhysicsObj::enter_world (call site 0x00455095; body 0x00516170) for the LOCAL player — the same enter_world the non-player branch uses at 0x004550EC. enter_world builds SetPositionStruct flags 0x11, calls CPhysicsObj::SetPosition, sets transient_state |= 0x80 (ACTIVE) and HandleEnterWorld — NO contact seeding anywhere in it (pseudo-C 284198-284249). Contact arrives from the first gravity frame (digest #270 section; find_placement_pos validates the spot but records no touch). Local player and remote spawn CONFIRMED to share the retail mechanism.
  • Legacy local path (deleted by the flip): BuildControllerAndCamera ran Resolve(100f drop) + ResolvePlacement then PreparePositionForCommit -> SetPositionCore, which FORCE-seeded Contact | OnWalkable | Active ("Treat as grounded after a server-side position snap", PlayerMovementController.cs:1830-1834) — an unconditional non-retail seed (Contact-without-plane, the state the landing family calls unrepresentable). The flip deleted the call chain without an equivalent.
  • New path today: conductor -> publication -> dormant activation -> PhysicsEngine.SetPosition (faithful port, result.InContact=false for a clean placement) -> TryApplyDormantLocalActivationCommit commits contact=false -> FinalizeActivation activates. Body starts airborne; outbound CanSendPositionEvent (= InContact && OnWalkable, PlayerMovementController.cs:1517) stays false -> MTS contact byte 0 (LocalPlayerOutboundController.cs:203) -> ACE says 'while in the air'.
  • DO-NOT-RETRY compliance: settle passes isOnGround:false + real body (no caller-bool seeding); no ContactPlaneValid gating; no forced transients — contact only from the sweep's real touch; airborne spawn stays airborne.

Design (pinned-contract shape)

  • Move RemoteSpawnPlacementSettler (App) -> Core.Physics SpawnPlacementSettler (public, like PhysicsObjUpdate; Core internals are NOT visible to App). Remote caller + tests updated; semantics byte-identical.
  • Seed at RuntimeLocalPlayerPhysicsPublicationState.FinalizeActivation, after TryApplyDormantLocalActivationFinalCommit + shadow dispatch + IsCommittedActivationSuffixCurrent, before placement dispatch (no reentrant-sink hazard; stale-authority path skips the settle). Inputs: activation Body/Record key/ActivationPreparation radius+height, IsPlayer|EdgeSlide|OwnPvpFlags, Movement.HitGround/Motion.LeaveGround — the same callback pair the per-tick landing path uses.
  • Propagation: body transients (a) are THE controller grounded state (b) (controller reads _body directly) and THE outbound bit (c) (CanSendPositionEvent -> contactByte). No second copy exists.

Implementation (seams touched)

  1. src/AcDream.Core/Physics/SpawnPlacementSettler.cs — NEW (moved from src/AcDream.App/Physics/RemoteSpawnPlacementSettler.cs, deleted; public like PhysicsObjUpdate because Core internals are not visible to App). TrySettle body byte-identical to the #270 shipped version; only the class name/namespace/doc changed.
  2. src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:193 — the one legacy remote caller now calls AcDream.Core.Physics.SpawnPlacementSettler.TrySettle (unchanged args).
  3. src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs — FinalizeActivation now calls new private SettleFirstEntryGroundContact(activation) AFTER TryApplyDormantLocalActivationFinalCommit + shadow dispatch + IsCommittedActivationSuffixCurrent, BEFORE placement dispatch (no reentrant-sink window; stale-authority early return skips the settle). Inputs: activation.Body position/cell, ActivationPreparation radius/height, IsPlayer|EdgeSlide|OwnPvpFlags, Controller.LocalEntityId, Movement.HitGround/Motion.LeaveGround (the per-tick landing pair). try/catch matches the existing post-commit ground-edge dispatch containment (_activationDispatchFailureCount).
  4. Tests: tests/AcDream.Core.Tests/Physics/SpawnPlacementSettlerTests.cs (moved from App.Tests, bodies unchanged, layer rule 6); Issue270ProductionWiringTests source pin updated to the new name; RuntimeLocalPlayerPhysicsPublicationStateTests +2 (CommitActivationOnFlatGroundSeedsRetailFirstGravityFrameContact, CommitActivationOverVoidLeavesFirstEntryGenuinelyAirborne) + fixture moverSphereOriginZ param (default 0 = pre-existing shape); RuntimeFirstEntryHostIntegrationTests +2 (LocalLogin_FlatGround_ReportsGroundedOutboundContactBit, LocalLogin_AirborneSpawn_StaysGenuinelyAirborne) + HostFixture terrainHeight/moverSphereOriginZ params.

Gate ladder

  1. Focused: publication-state 92/92 (90+2), first-entry conductor 15/15, settler 3/3 (Core), App first-entry integration + issue-270 wiring 8/8. Complete projects (Release): Runtime 999/0, App 4,036/3 skips, Headless 78/0, Core 4,247/1 skip. NOTE (pre-existing, not this slice): LandblockBuildOriginTests.FarLoad_StripsEnvCellsAndPhysics... fails in DEBUG only — the test feeds an intentional near payload to a FarLoad and LandblockStreamer.cs:505 Debug.Assert fires; Release (the gate config) compiles it out and it passes. Reproduced 3x isolated in Debug, passes in Release; untouched by this slice's diff.
  2. Release solution build 0 warn/0 err; complete solution (-m:1, ACDREAM_PAK_PATH): 10,808 passed / 0 failed / 4 skips (App 4,036/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,247/1, Headless 78, Runtime 999, UI 543).
  3. Connected lifecycle gate: launched against live ACE (process 22100, UDP 9000); result pending. 3 (result). Connected lifecycle gate PASS: logs/connected-world-gate-20260802-164432/report.json Passed=true, Failures=[], both sessions ExitCode=0, one warning ("capped: 25 expected world-edge landblock miss(es)" — the exact 142539/161138 class). All 6 capped checkpoints + uncapped_reconnect captured with screenshots. "while in the air" grep: 0 in capped/stdout.log, 0 in uncapped-reconnect/stdout.log (0x042C also 0/0). Note: the two prior PASS runs (142539/161138) also contained 0 occurrences — the scripted route never surfaced the rejection string; the behavioral proof of the fix is the outbound-bit integration tests + the settle assertions, and the gate proves no regression.
  4. git diff --check exit 0 (CRLF metadata warnings only, the known worktree pattern); nothing staged; no probes added by this slice.

Register note

No divergence-register row existed for the #270 compressed settle (it is classified as timing compression — it produces exactly the state retail's first gravity frame produces); extending it symmetrically to the local player follows the same classification. The commit that lands C3c should also delete/refresh AD-42 (its cited legacy GameWindow/PlayerModeController resolve path no longer exists in the flipped tree) — flagged for the closeout, not acted on here (report-only bookkeeping, no doc edits in this slice's scope).

Purple-haze note (observation only)

The haze script is the Hidden/UnHide materialization path (EntityEffectController.PlayTypedFromHiddenTransition — retail set_hidden 0x00514C60); nothing in it keys off contact/airborne state, so the F5 contact gap does NOT plausibly drive the re-fire. A re-fired UnHide implies the local player's presentation saw a hidden/visibility edge while standing — consistent with F4's streaming/publication convergence regression re-bucketing the player's surroundings, which remains the plausible driver.

C3c-R1

Fix round for review round 1 (contract c3c-r1-fixes.md). Every finding verified at source before editing; dispositions below.

Verification-at-source results (pre-edit)

  • R1 CONFIRMED: CommitPreparedPosition (PlayerMovementController.cs:1784) had ZERO production callers post-flip (grep); PreparePositionForCommit (called at RuntimeLocalPlayerPhysicsPublicationState.cs:219) uses publishSharedState:false and the PositionManager binds at :318 (after the position seed), so no login path armed the leash. The final commit (RuntimeSetPositionState.cs:2494-2516) is where the accepted position is final, the shared cell is published (:2508), and the controller activates (:2516).
  • R2 CONFIRMED: LiveEntityRuntime.cs:806-828 keyed the presentation-only branch off the sticky enum; post-residence entities skipped CommitRebucket (:882-892) + the prepare_to_enter_world clock edges (:897-924). All six cited unflipped-route callers verified (grep RebucketLiveEntity).
  • R3 CONFIRMED: RuntimeLiveEntitySessionController.OnSpawned opened the residence unconditionally; HeadlessSessionHost builds drive+projection only under _contentLease is { } content (:539-565); content-less is validated-legal (HeadlessConfigurationLoader.ValidateContent :88-98 returns on null). worldProjection==null is exactly "no drive exists" (single production constructor call site).
  • F2 CONFIRMED: SpawnPlacementSettler.cs:61 commits settle.Position; settle.CellId never read.
  • F4 CONFIRMED: F3's landblockId==0 guards (RuntimePhysicsState .BeginCollisionAdmission :2037-area) throw through HeadlessCollisionGenerationTransaction.Begin (:62) reachable from CenterOn; the two cited sites passed the raw wire LandblockId unguarded.
  • F5/F6/F7/F8/F9 confirmed as cited (drive _pending outside every ledger; both route Disposes cleared a SHARED drive unconditionally; far-remote DeferredCell park has no wake outside the 3x3 neighborhood; the three stale comments; the per-pump TryGetWorldEntity+GetSetupCylinder).

F3 — STOPPED, pinned design conflicts with source (file:line evidence)

The pinned probe ("nested production OnCreate, not hand-called AdvanceCreateAuthority") cannot produce create-authority drift in the items-6/8 tests' post-residence window:

  1. RuntimeEntityObjectLifetime.cs:660-665 — the ExistingGeneration branch calls Entities.AdvanceCreateAuthority ONLY when !beginInitialResidence; the graphical route always registers with residence (LiveEntityRuntime.cs:544-548), so a post-residence same-generation Create is description-only.
  2. The FIFO-adoption alternative is closed: ConsumeExecuted removes the completed residence entry at Released (RuntimeInitialCreateResidenceState.cs:1246-1251), and the fixtures complete+release during the initial OnCreate, so TryGetTransaction (:729-748) misses and no continuation can be staged post-release.
  3. The executor drain (RuntimeInitialCreateContinuationExecutor.cs:2424-2429) is the ONLY production site that advances create authority for an existing incarnation - exactly what the hand-call models.
  4. EMPIRICAL: temporarily restoring the nested-OnCreate probe in FailedCompletedSupersession_RemainsPendingUntilExactRetry made BOTH rows fail "Assert.Throws() Failure: No exception was thrown" (no drift, no CreateSupersessionRecovery). Experiment reverted; tree byte-identical for that file except nothing. Restoring the probe requires either accepting dead drift machinery or a test-scenario redesign (drift staged during the ACTIVE-residence window and drained mid-recovery by a reentrant pump) - a design call for the coordinator, not a probe swap. NOT implemented; reported.

R4(c) progress-log correction (retail minor M1)

The C3c-F5 section above says the legacy local force-seed path was "deleted by the flip". CORRECTION: the force-seed at PlayerMovementController.cs SetPositionCore ("Treat as grounded after a server-side position snap", Contact|OnWalkable|Active) still RUNS during publication-candidate preparation (PreparePositionForCommit -> SetPositionCore) and is then OVERWRITTEN by the faithful activation commit (which commits the SetPosition result's contact=false) and the settle. What the flip deleted was the CALLER CHAIN (BuildControllerAndCamera's Resolve/ResolvePlacement + CommitPreparedPosition), not the seed statement. Register row AD-61 records the "overwritten, not deleted" truth.

Implemented (all others)

  • R1: PlayerMovementController.ArmConstraintLeashAtCommittedPlacement (internal, published-guarded) + RuntimeLocalPlayerPhysicsPublicationState .ArmFirstEntryConstraintLeash called in FinalizeActivation after the final commit + shadow dispatch, inside the IsCommittedActivationSuffixCurrent gate, BEFORE the settle; exactly-once via _activation = null preceding the suffix. Ordering justified in the new doc comment with file:line.
  • R2: the public RebucketLiveEntity now suppresses ONLY while HasActiveInitialCreateResidence (exact-token activity view); post-residence falls through to the FULL legacy branch (CommitRebucket + clock edges). RebucketLiveEntityPresentationOnly is now called only from TryApplyInitialCreateCompletionPresentation.
  • R3: content-less direct host (worldProjection null) registers via RegisterEntity + direct accepted-frame commit (exact pre-flip shape), with the C4/C5 revisit note.
  • R4: register AD-61 filed (local-player settle compression, overwritten force-seed, settle-CellId caveat); AD-42 refreshed (repointed at RemoteTeleportController.ResolvePlacement + headless portal resync; login split retired); section count 46->47.
  • F1: LiveEntityRuntime.ConvertMaterializationResidenceToLegacyImmediate (throws while residence active); EquippedChildRenderController uses it.
  • F4: { LandblockId: not 0u } guards at both cited CenterOn sites.
  • F5: RuntimeEntityObjectOwnershipSnapshot.FirstEntryDrivePendingCount (IsConverged-gated) + RegisterFirstEntryDriveOwnership; the drive registers its pending-count provider at construction.
  • F6: RuntimeFirstEntryDriveController.AttachRoute/DetachRoute one-route latch (Clear deleted); GraphicalSessionEventRoute + HeadlessSessionEventRoute attach/detach with this.
  • F7: RuntimeAuthoritativePositionRouteClassifier.ToCellessCreateRoute (exact Parented/PickedUp branch shape, preserving authority/operation/ collision-batch); RuntimeInitialCreateResidenceState.TryConvertToCellessRoute (active+unplaced only: ForgetExactPlacement + lease rewrite + PublishCancellation + retirement fan-out for conductor progress reset, entry retained); RuntimeEntityObjectLifetime .TryConvertInitialResidenceToCellessRoute facade; IHeadlessCollisionNeighborhood.IsWithinServiceWindow (3x3 around the requested center; no-center = within) consumed by HeadlessSessionWorldProjection.ProjectSpawn for far remotes before the pump.
  • F8: both conductor "nothing calls Advance in production" headers corrected; PlayerModeController's two false "auto-entry retries" claims replaced with the actual disarm-before-invoke semantics (verified against PlayerModeAutoEntry.TryEnter :232-248 - _armed=false precedes the invoke, and a throw propagates before the context's reveal Complete()).
  • F9: the graphical activation-preparation provider caches the resolved setup cylinder per incarnation (keyed by LocalEntityId; unresolved results not cached; shadow disposition stays live - it is validated against the exact registry at activation commit and may change between pumps).

New tests

  • Runtime: CommitActivationArmsTheLoginConstraintLeashAtTheCommittedPlacement, CommitActivationNeverRearmsTheLeashOnAStaleRetry (publication suite); ContentLessDirectSink_KeepsPreFlipLegacyRegistration, FirstEntryDriveServesOneRouteAtATimeAndScopesClearToTheOwner (session-controller suite; DirectSinkOwnsCanonicalCreateUpdateDelete... updated to pass a world projection so it keeps exercising the residence route per R3's semantics).
  • App: PostResidenceRebucket_TakesTheFullLegacyPathIncludingTheClockEdge (active-suppression + F5 ledger visibility + retired->legacy CommitRebucket
    • the pending-reentry clock rebase), ResidenceConversionToLegacyImmediate_RefusesWhileTheResidenceIsActive.
  • Headless: FarRemoteCreateCompletesCelllessWithoutPinningItsResidence (+ Spawn(guid, cellId) helper param; FixtureCollisionNeighborhood implements IsWithinServiceWindow).
  • One in-flight test correction during TDD: the leash test's anchor assertions initially assumed the committed placement kept the raw spawn Z=3; the faithful placement transaction already floor-snaps (2.705), so the anchor asserts the committed floor-snapped band + committed cell.

Gate ladder (this round)

  1. Runtime 1,003/1,003; App 4,038/3 skips of 4,041; Headless 79/79.
  2. Release solution build: 0 errors. Complete solution (-m:1, ACDREAM_PAK_PATH): 10,815 passed / 0 failed / 4 skipped of 10,819 (App 4,038/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,247/1, Headless 79, Runtime 1,003, UI 543).
  3. Connected gate run 1: logs/connected-world-gate-20260802-174811 — Passed=false, ATTRIBUTED TO USER INTERFERENCE per the coordinator (the user manually drove the client, including a teleport, during the run). Fingerprint: every failure at the capped_login checkpoint only (transitOwnership.activeTeleportCount=1 at the stable checkpoint, activeRevealCount=1, pendingDestinationReadinessCount=1, hostProjectionCount=1, reveal/viewport/composites/collision not ready, 216 staged mesh uploads + 44 composite warmups mid-stream to the manual teleport destination). No crash, no exception; only the expected world-edge warning class. Coordinator sanctioned exactly ONE clean re-run (external interference, not a blind retry); graceful-close discipline held (no stale client process, ACE endpoint intact).
  4. git diff --check: exit 0 (CRLF metadata warnings only, the known worktree pattern); nothing staged. 3 (result). Sanctioned clean re-run PASS: logs/connected-world-gate-20260802-175401/report.json Passed=true, Failures=[], only the expected world-edge warning — the established passing signature (142539/161138/164432 class). 174811 interference attribution confirmed. Round complete; nothing staged.

P1 — origin-recenter retirement-receipt exception loop

Root cause pinned in p1-retirement-receipt-loop.md. An already-detached landblock can retain live projections in GpuWorldState._pendingByLandblock while its one exact full-cleanup ticket advances. The recenter swap incorrectly promoted that pending-only spatial bucket into a second full presentation receipt. The coordinator rejected the duplicate correctly, but the controller's broad resume catch replayed the already-committed detach 243 times in the captured feel-test session.

Implemented:

  • pending-only live buckets are retained through _projectionLocations but no longer manufacture a landblock retirement receipt;
  • loaded/pending-render/pending-near/tier/bounds owners still receive exact receipts;
  • a genuine receipt-ledger invariant after the spatial commit is surfaced as a committed StreamingMutationException and cannot enter retry work;
  • the existing duplicate-receipt guard remains unchanged.

TDD evidence: the new pending-only regression failed before the source edit (Assert.Empty, one receipt returned) and passes afterward. The production controller/recenter regression and committed-invariant fail-fast regression also pass. Focused OriginRecenter group: 20/20.

Final gates: Release build passed; the complete Release suite passed 10,815/10,815 with 4 skips; the connected lifecycle gate passed at logs/connected-world-gate-20260802-203751/report.json; and the nine-stop soak passed at logs/connected-r6-soak-20260802-204309.report.json with all 9 canonical checkpoints, zero failures, zero wait cues, zero pending landblock retirements, and zero recurrence of the 243x exception signature.

F3 addendum (coordinator resolution accepted, implemented)

Hand-calls KEPT as honest documented models: enriched comments at all six item-6/8 sites (LiveEntityHydrationControllerTests x5 sites incl. the shared item-6/CompletedSupersessionReadyFailure text, LiveEntityCreateSupersessionRecoveryTests x1) citing ApplyWeenieDescriptionAction as the sole production site, RuntimeEntityObjectLifetime :660-665 !beginInitialResidence gate, and ConsumeExecuted. NEW source pin tests/AcDream.App.Tests/World/C3cR1F3DriftModelSourcePinTests .HandCalledDriftProbe_StillModelsTheExecutorDrainAdvance (single _entities.AdvanceCreateAuthority in the executor, inside ApplyWeenieDescriptionAction; registration advance still residence-gated). Focused files 82/82; Runtime 1,003/1,003; App 4,039/3 skips of 4,042; git diff --check exit 0. No staging/commits.