282 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
- 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.
- 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.
- 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.
- 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.
- 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)
- AdoptCompletedPlacement/ConsumeExecuted new API design (see code comments in RuntimeInitialCreateResidenceState.cs) - resolves the 3.1 deadlock per contract's exact prescription.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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).
- Reentrant Execute() for the same RuntimeEntityKey while one is already on the call stack fails closed via a HashSet latch.
- 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
updateinternally - 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
truepredicate 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
- Release build (AcDream.slnx): 0 errors, 21 pre-existing warnings.
- Focused filter (Residence+Classifier+Executor): 111 passed (89 + 22).
- Complete Runtime suite: 853 passed (829 + 24: 22 executor + 2 InboundPhysicsStateController Finding-5 regressions).
- 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
- 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).
- Focused filter (Residence+Classifier+Executor): 105 passed (89 baseline
- 16 new), 0 failed.
- Complete AcDream.Runtime.Tests: 845 passed (829 baseline + 16 new), 0 failed.
- 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)
PublishCancellationBEFORE 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
NotSupportedExceptionsince C0-3 exercises only that one):TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit(authored two-sphere Setup reachesSubmitPreparedPlacementandTryGetPreparedMoverSphereCountbyte-exactly == 2, matching the existing preparer tests' own expectations) andTryPrepareAndSubmitAuthoredPlacement_YieldsRetryOnAMissingSetupReadWithoutMutatingStage(Missing status ->RetrySetupUnavailable, operation staysAwaitingPreparation/IsPlacementCurrenttrue, 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):
- Three C0-4 tests initially asserted a Discard would be published from
cancelling a placement operation still in
AwaitingPreparation(never submitted) - tracedCancelCoreDeferredand confirmed it only converts an EXISTING_pendingProjectionentry to Discard (operation.ProjectionSequence != 0ULgate); an unpublished operation just disappears from_operationswith no receipt, which is CORRECT (nothing was ever promised to a host). Fixed the TESTS to reachSubmitPreparedPlacement's pending-ack stage first, not the production code. - Two of those same tests then asserted
PendingProjectionCount == 0immediately 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. TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement'sPrepare(..., RuntimeSetPositionMoverSetup.ResolvedAbsent)failed withInvalidDatabecauseSpawn(guid, 1)in that file defaultssetupId: 0x02000001u(nonzero), mismatchingResolvedAbsent's claimed "no Setup at all". Fixed by passingsetupId: nullexplicitly (matching the file's OWN existing convention for this exact scenario, e.g.ResetSnapshotsAllResidenceOwnersBeforeReentrantDiscardObserver).BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStructfailedAcknowledgeProjectionon a SECOND entity's placement - not exact head. Root cause: the FIRST entity's full drain published its ownExecutorCompletedreceipt (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)
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).- Focused filter (RuntimeInitialCreateContinuationExecutorTests| RuntimeInitialCreateResidenceStateTests|RuntimeSetPositionStateTests| RuntimePlacementProjectionSubscriptionTests|RuntimeCharacterStateTests| RuntimeEntityObjectLifetimeTests): 247/247 passed.
- 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).
- 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. git diff --check: clean (only pre-existing LF-will-become-CRLF metadata notices, no whitespace-error content).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:
- Register-before-publish:
PublishExecutorCompletiongained abeforePublish: Action<RuntimePlacementProjectionToken>?callback invoked AFTER the token is added to_pendingProjectionbut BEFOREPublishPlacement's synchronous observer dispatch. The executor'sExecuteCoreReleased case now registers_completionReceipts[key]inside that callback (capturingreceiptvia a localcompletedReceipt- an
outparameter cannot be captured by a lambda) - a subscriber reading the correlation back from inside its OWNOnPlacementnow always finds it.
- an
- Ack-driven removal: new
RuntimeSetPositionState.BindExecutorCompletionAcknowledgement(Action<RuntimeEntityKey,ulong>)(mirrorsRuntimeInitialCreateResidenceState.BindRetirementNotification's existing one-bound-delegate shape), invoked fromAcknowledgeProjection's ExecutorCompleted branch the moment a host acknowledges. Bound in all 3RuntimeEntityObjectLifetimeconstructors toInitialCreateExecution.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). - DiscardProgress/DiscardAll: both now unconditionally reap
_completionReceipts(Remove/Clear respectively) - DiscardProgress removes it EVEN WHEN_progressno longer tracks the key (the drain already removed its own Progress entry before publishing the completion), proven by a dedicated test. - Ownership/convergence: added
PendingCompletionReceiptCountto the executor and folded it intoRuntimeEntityObjectOwnershipSnapshot/IsConverged(appended as the last positional field with a= 0default, following this record's own established extension convention) - chosen semantics: non-zero while unacknowledged, zero exactly at acknowledge, mirroringRuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount's existing "unacknowledged receipt is outstanding debt, gated by IsConverged" shape (documented as such, in contrast with the adjacent diagnostic-onlyReplayFailureCount). 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)
ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgressAndDiscardAll(combined, single lifetime, two entities) failed at the SECOND entity'sCompleteInitialPlacement- 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_pendingProjectionitself - 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.BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNullhit 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 inBindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStructfrom the prior round).
Fix-round final gates (all green)
dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release: 0/0.dotnet build tests/AcDream.Runtime.Tests -c Release: 0/0.- Complete
AcDream.Runtime.Tests: 921/921 passed (916 + 5 new: 4 F2 + 1 F3; see above for exact names). dotnet build tests/AcDream.App.Tests -c Release: 0 errors, 3 pre-existing warnings (CS8767, unrelated to this change).dotnet build tests/AcDream.Headless.Tests -c Release: 0/0.dotnet test tests/AcDream.App.Tests -c Release --no-build: 4028/4028 passed, 3 skips (4027 baseline + 1 new: the F1 App-sink test).dotnet test tests/AcDream.Headless.Tests -c Release --no-build: 77/77 passed (76 baseline + 1 new: the F1 Headless-sink test).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).- 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. 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).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 Releasegreen, 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)
-
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. -
SETPOSITION (584 B, two call sites: SubmitPreparedPlacementCore ~line 2402 and RetryDeferred ~line 3536):
report => _physics.HandleSetPositionCollisions(operation.Record, ..., canonicalCommand.GameTime, ...)is a closure capturingthis+operation(+canonicalCommandat the first site) -- a fresh compiler-generated display-class allocated on EVERY call. Confirmed every field the closure reads is already reachable fromoperationalone (operation.Command.GameTime == canonicalCommand.GameTimebecauseoperation.Command = canonicalCommand;runs earlier in the same method) -- the closure captures nothing that isn't already sitting onoperation. 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
- Fix the closure (lowest risk, no behavior change) -- both call sites.
- Pool the Operation object (
_operationPool, convertrequired {get;init;}to settable + add aReset(...), 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). - Re-measure; tighten the gate to the new number with justified headroom.
Fixes implemented
-
Cached collision-report delegate (fixes the closure at both
_physics.Engine.SetPositioncall sites: SubmitPreparedPlacementCore and RetryDeferred). AddedCollisionCallbackContext(readonly record struct) plusStack<CollisionCallbackContext> _collisionCallbackContextsplusFunc<PhysicsSetPositionCollisionReport, bool> _handleSetPositionCollisionsCallback(built ONCE in the constructor, bound to instance methodHandleSetPositionCollisionsCallbackwhich 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. -
Operation pooling (fixes
new OperationinBeginAcceptedPlacementCore). Converted everyOperationproperty fromrequired ... { get; init; }to plain{ get; set; }, addedResetAllFieldsToDefault(), added_operationPool(Stack<Operation>, capped at 64),RentOperation()/RetireOperationToPool(Operation).BeginAcceptedPlacementCorenow rents+field-sets instead ofnew Operation {...}. Operations are retired at the 3 places an Operation is permanently removed from_operations: both branches insideAcknowledgeProjectionPlace/non-lost-cell paths, and insideCancelCoreDeferred(the single removal chokepoint everyCancelCoreoverload funnels through).CRITICAL BUG FOUND AND FIXED during verification: the first pass reset fields at RETIREMENT time (inside RetireOperationToPool). This broke
ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation-CommitCanonicalremote branch builds aContactCommitGuardthat captures the liveOperation, then invokesremote.HitGround()/LeaveGround()- and retail lets that callback synchronously call BACK INTOBeginAcceptedPlacementfor 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.
-
Eliminated LINQ
.First()boxing on_pendingProjection(SortedDictionary<ulong, RuntimePlacementProjectionSnapshot>). AddedFirstPendingProjection()using a plainforeach(resolves to the concrete struct-returningGetEnumerator(), not the boxingIEnumerable<T>interface one thatEnumerable.First<T>forces). Replaced all 3 call sites (TryPeekProjection,AcknowledgeProjectionguard,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_pendingProjectiondata 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 (thequeryFootprint.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 newAssert.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 at6460596b56.
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 SAMEreplacementinstance 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)):
Cancel(record, bool)post-PublishPlacement(cancelledOld)check - the reviewer's named bug.SubmitPreparedPlacementCorepost-_physics.Engine.SetPosition(...)check (first of two, immediately after the collision-callback-bearing call).SubmitPreparedPlacementCorepost-SetPositionsecond check (after theTryGetBlockingQuiescence/deferred branch, beforeCommitCanonical).SubmitPreparedPlacementCore'sCancelCore(token.Entity, token)call onCommitCanonicalfailure - the reviewer's named CancelCore-shape bug.RetryDeferred's two analogous post-SetPositionchecks (same shape as #2/#3, via hoistedoperationToken).RetryDeferred'sCancelCore(operationToken.Entity, operationToken)call onCommitCanonicalfailure (same shape as #4).RebindQuiescedDeferredOperations's per-entry resolve - converted from a snapshotted-REFERENCE loop to a snapshotted-(Key,Token) loop + freshIsCurrentByToken(the subtler hazard found during the audit, not named by the reviewer).ForgetExactPlacement'sCancelCore(token.Entity, token)call.RetireDormantLocalActivation'sCancelCorecall.RetireDormantLocalActivationToken'sCancelCorecall.CommitCanonical's A1 bookkeeping-write gate (fresh lookup +currentOperation.Token == operationTokenbefore 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 callslifetime.Entities.AdvancePlacementCommit(record)a second time BEFORE creatinginner(soinnersnapshots the already-advanced value and stays internally self-consistent, while the OUTER commit'scanonicalCommitVersion, captured before the callback, now mismatches) - this makesCommitCanonical's post-callbackIsCanonicalPlacementCommitCurrentcheck fail for the outer commit without needing a full nested SetPosition round-trip, drivingSubmitPreparedPlacementCoreintoPublishCancellation(CancelCore(token.Entity, token)). Assertsoutcome.Status == Cancelledandinneris stillIsPlacementCurrentafterward. VERIFIED DISCRIMINATING: revertedCancelCore(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 retiredinner'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 at6460596b56.
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 internalRuntimeTeleportHookPhase/RuntimeAuthoritativePositionDisposition/RuntimePositionConstrainPhaseenums (RuntimeAuthoritativePositionRouteClassifier.cs:38,49,63).RuntimeInitialCreatePositionRouteFact— one Position continuation's route facts (Sequence/Disposition/HookPhase/ConstrainPhase/StopInterpolating/ ZeroVelocity/PreserveHeading/SendPositionImmediately), projected fromRuntimeInitialCreateExecutedActiontrace entries whereKind == Positiononly (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 privateRuntimeInitialCreateContinuationExecutor.ProjectCompletionat the exact completion site (ExecuteCore'sReleasedcase, wherecompletedReceiptis built) and cached in_completionReceipts's tuple (extended from(Sequence, Receipt)to(Sequence, Receipt, Public)) — so a host polling/ retryingTryGetInitialCreateCompletionnever re-allocates (the "allocation-conscious" requirement).- New internal
RuntimeInitialCreateContinuationExecutor.TryGetCompletionreads the cached projection by exact token identity (same Entity/Sequence correlation rule as the existingTryGetCompletionReceipt). - 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 3RuntimeEntityObjectLifetimeconstructors updated to passInitialCreateExecution.
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.RegisterEntityWithInitialResidence→InitializeAcceptedCreateResidence→InitialCreateResidences.Begin(RuntimeInitialCreateResidenceState.cs:556-607) →Own(:731-...) —Ownsynchronously calls_setPosition.TryBeginExclusiveAuthoredPlacement(record, ..., route.OperationKind)(:746-751) wheneverroute.PerformsSetPositionis true. This runs at wire CreateObject time, insideLiveEntityRuntime.RegisterLiveEntitytoday's call site (once flipped) — i.e. it opens aRuntimeEntityPlacementTokenoperation in stageAwaitingPreparationimmediately, 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.SetPositionuniformly (route.PerformsSetPositionis true for all of them; only Parented/ PickedUp Creates getAwaitFreshPosition, which does NOT open a SetPosition operation). So this circular dependency is not local-player- specific in its trigger — it applies to the SAMEOwn()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 SAMElease.Placementtoken (confirmed by readingRuntimeLocalPlayerPhysicsPublicationStateTests.cs:2263-2300'sFixture.RepreparePlacement/Preparehelpers:BeginAuthoredPlacement→PrepareMover→Owner.Prepare(record, placement, command, options, activationPreparation, out token)→Commit(token, out activationToken)→EvaluateActivation→CommitActivation—Commit'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 genericSubmitPreparedPlacement/TryPrepareAndSubmitAuthoredPlacementpath (the one C3-2's bullet 3 names forAwaitingContinuationPlacement's pending-token flavor) cannot be used for the local player's OWN initial placement:SubmitPreparedPlacementCorerequiresoperation.Record.PhysicsBody is not { } bodyto 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.BuildControllerAndCameraruns LATER than Create-time (gated byPlayerModeAutoEntry.cs:214-230'sIsPlayerEntityPresent && IsWorldReadyper-frame check) and never toucheslease.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 inPendingPlacementfrom Create time until whatever replacesBuildControllerAndCameradrives the publication chain AND separately callsInitialCreateExecution.Execute(...)a SECOND time afterward to actually drain the FIFO. This secondExecutecall is not optional —Execute's own doc comment and the executor handoff both say a caller re-invokesExecuteafter the placement token in the trace is acknowledged; nothing does this automatically. - Practically workable simplification found:
RuntimeLocalPlayerPhysicsPublicationState.PreparecallsDiscardCurrent()on entry (:305) before installing a new candidate, so a full "retry the whole Prepare→Commit chain from scratch next frame" is safe UNTILCommit()succeeds (at which point_activationis populated and a subsequentPreparecall correctly rejects viaCanPrepare's_activation is nullguard — the retry loop must switch to re-drivingEvaluateActivation/CommitActivationon the SAMEactivationToken, not re-Prepareing). This meansPlayerModeAutoEntry's EXISTING per-frame "keep calling TryEnter until it returns true" loop can likely be reused rather than inventing a wholly new scheduler — butPlayerModeControllerneeds a small piece of cross-frame state (at least the pendingactivationTokenwhen 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 existingRuntimeLocalPlayerMovementState.BeginMotionPreparationlease (:102-117).RuntimeLocalPlayerPhysicsPublicationState.Preparebuilds its OWN controller internally and does not return a reference to it beforeCommit()— but sinceBeginMotionPreparationonly needs a controller REFERENCE (no ordering requirement relative to the publication state's own internal stages), it appears safe to callBeginMotionPreparationon_movement.Controllerimmediately AFTERCommit()succeeds (controller isRuntimeOwnedDormantat that point, not yetRuntimePublished) and BEFOREEvaluateActivation/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) requiresoperation.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 aroundPrepareMover+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.ClassifyCreategives Remote/Projectile TopLevel Creates the exact sameDisposition.SetPositionas 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 behindphysicsStatic(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:
- Architecture PASS with one MINOR:
MapHookPhase/MapDisposition/MapConstrainPhase's catch-all_ =>arms silently folded an unmapped future internal enum value intoNone/NoPositionOperationinstead of failing loudly. Fixed: every declared value now has an explicit arm (added the previously-implicitNone/NoPositionOperationcases) and the catch-all nowthrow new ArgumentOutOfRangeException(...)with a message naming both the mapper method and the public enum to update. Also fixedProjectCompletion'saction.PositionDisposition ?? RuntimeAuthoritativePositionDisposition.NoPositionOperationfallback: verified (grep-confirmedBuildPositionTraceis the sole producer ofKind.Positiontrace entries, always passingroute.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 toKind == Position) - replaced the silent fallback with an explicitInvalidOperationExceptionthrow + comment explaining why. - Retail-conformance PASS with two documentation-only addenda (no code
changes): (a)
RuntimeInitialCreatePositionRouteFact's doc comment now states explicitly thatUnparentBeforeRouting/ApplyPlacementFrameBeforeRouting("unset_parent"/"SetPlacementFrame") are NOT projected because the executor's own merge (ApplyAcceptedPositionSnapshot'sclearParent/installPlacementFrameparams, confirmed by direct read at theApplyPositionAction/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 statesPositionRouteFacts's ARRAY ORDER (notSequence) is authoritative - confirmed multiple Position trace entries from one same-incarnation envelope share one continuationSequence(onlyStage, not projected, distinguishes them internally), andProjectCompletionpreserves 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):
- register identity cellless
- begin initial/remote-create placement before hydration
- load exact Setup mover
- prepare the atomic Runtime controller/body relationship
- submit canonical SetPosition
- publish presentation only from Place
- 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:
- 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.
- 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.
- 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):
- 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 againstDeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWakein the publication suite, which asserts exactlywaiting.IsValid == falseon that repeat and never calls CommitActivation with it. The conductor now checksevalReceipt.IsValidbefore ever calling CommitActivation, short-circuiting straight to AwaitingActivation when it is false, instead of blindly forwarding an invalid receipt (which CommitActivation's own!receipt.IsValidguard would reject as RejectedAuthority). - 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.
- 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.Keyis computed from a nullableLocalEntityIdand becomesnullthe instantReleaseLocalIdruns (part of delete's teardown inRuntimeEntityObjectLifetime.CompleteProjectionRetirement).Advance's entry check (record.Key is not {} key -> RejectedToken) EXACTLY mirrorsRuntimeInitialCreateContinuationExecutor.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 theRuntimeEntityKeyBEFORE deletion and callForget(key)explicitly; this is a pre-existing convention in the codebase (the executor has the identical limitation), not a defect introduced here.RuntimeLocalPlayerPhysicsPublicationStateholds 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 ownPublication.Prepare(CanPrepare requires_activation is null) untilForgetruns. Proven byDeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlot.Movement.ResetSession()proactively nulls Publication's_activationdirectly (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 touchesRuntimeEntityRecord.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.csshows 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— extractedTryPrepareAuthoredMover(new internal method, Setup-read + PrepareMover only);TryPrepareAndSubmitAuthoredPlacementnow 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:
- _residences.TryGetCurrent(record, out lease) && lease.Token == residenceToken -- the SAME residence-lookup pattern stage0/stage1 already use.
- _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.
DeleteWhileAwaitingActivationRequiresForgetOfTheCapturedKeyToDiscardTheDormantActivationrenamed toDeleteWhileAwaitingActivationConvergesAutomaticallyThroughTheRetirementFanOut: 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).DeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlotrenamed toDeleteAndSameGuidReincarnationAutomaticallyFreesThePublicationSlotForTheFreshIncarnation: 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 includeRuntimeEntityObjectLifetime.csandRuntimeInitialCreateResidenceState.csin addition to the round-1RuntimeSetPositionState.cs-- all three diffs are additive/expected (64, 39, 68 changed lines respectively viagit 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/usesLifetime.LocalPlayerFirstEntryinstead 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 bareRuntimeEntityObjectLifetime(its ownLocalPlayerFirstEntryis naturally unbound, since only this test file's ownFixturecallsBindPublication), registers a residence, callsAdvancewith no bound Publication, asserts the throw AND that the residence lease/ActiveCountare completely untouched, THEN binds a real Publication and confirms the SAME token still drives correctly toAwaitingActivation-- proving nothing was corrupted by the failed attempt.BindPublicationTwiceThrows-- the standard bind-once guard test, matching every otherBindXmethod 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 includetests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs(+47/-0, the new H1 test) alongsideRuntimeInitialCreateResidenceState.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--NotifyRetirementnow snapshots viaToArray().src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs-- now 690 lines (was 663): upfront unbound-Publication check inAdvanceCore;Discard/DiscardAllunbound-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
d62b9950on 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
78f1eb18on 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
- 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).
- 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).
- 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.
- 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.)
- 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.
- 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). - 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.
- 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].
- 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].
- 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.
- 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).
- 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
- 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)].
- 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].
- 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)
- 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.
- 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.
- 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.
- RemotePhysicsUpdaterTests boundary Spawn(ushort instanceSequence, ...) — same field-level Timestamps.Instance fix; RawState untouched. Both green.
- 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)
- 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.
- 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)
- 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).
- 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):
- 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.
- 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.
- 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.
- 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:
- 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
- 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.
- dotnet build AcDream.slnx -c Release --nologo: 0 errors, 18 warnings (pre-existing test-project warnings; none in files this session touched). GREEN.
- Complete solution (-m:1, ACDREAM_PAK_PATH=C:\Users\erikn\Documents
Asheron's Call\acdream.pak): IN FLIGHT (background). - 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)
- 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.
- 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
Resetinside 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):
- First-entry conductor Prepares+Commits the DORMANT controller within the first frames (stats lines from stdout line 62).
- 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.
- 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).
- 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:
- 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.
- 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.
- 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 ONLYevent=begin(collision=False) and ONEevent=readiness(collision=False, ready=False, materialized=False, visible=False), thenevent=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'swait world-visiblepassed / 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); L225event=complete ... materialized=False; then 5,577+readiness-after-terminal; probe L1193wait world-visible 30000timeout.
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.
Step 3 — VERIFIED MECHANISM, link by link
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
expectedis 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
TrySealCollisionEvaluationAuthorityon 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_loginAND screenshotcapped_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-terminallines, 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):
- 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'sexcludedterm (:3617 — RetryDeferred's restoringQuiescence would have blocked itself). - RuntimeSetPositionState.BeginCollisionPrefixQuiescence (:782) — the
crash-site
prefix == 0uthrow replaced bylandblockId == 0u(absent id), prefix computed unconditionally. - 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. - RuntimeSetPositionState.ParkCollisionResidents (:3414-17) —
?? 0UL/?? 0ucollapse removed;quiescence?.Token.Xnow flows null/value. - RuntimePhysicsState.BeginCollisionPrefixQuiescence (:1991) — dead
canonical == 0u(CanonicalLandblock ORs 0xFFFF, never 0) replaced by a LIVElandblockId == 0uguard. - RuntimePhysicsState.AdvanceCollisionRetirementMutation (:2626) — same dead-guard replacement.
- 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.
- ShadowObjectRegistry.DeriveOutdoorSeed (:651, Core) —
lbPrefix == 0u -> no seedreplaced bylandblockId == 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 == 0there 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
- 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.
- 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).
- Connected gate: run logs/connected-world-gate-20260802-142539 launched against live ACE (UDP 9000 confirmed listening, no stale client); result pending.
- 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.
- 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.
Named mechanism (link by link)
StreamingController.DrainAndApply(src/AcDream.App/Streaming/StreamingController.cs:1662-1690) advances the completion-queue head andbreaks when it does not complete — at most ONE landblock publication per frame.LandblockPresentationPipeline.Advancestagepublication-index-physics(src/AcDream.App/Streaming/LandblockPresentationPipeline.cs:612-627) chargesEntityOperations: PreparationCursor < Entities.Count ? 1 : 0. A FAR build isArray.Empty<WorldEntity>()(PublishAsFar, :419-447), so every step is FREE and only the 2 ms elapsed-time ceiling bounds it.LandblockPhysicsPublisher.AdvancePreparationOne(src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs:297-309) gates onRuntimePhysicsState.AdvanceCollisionGenerationPreparation.- 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). PhysicsEngine.CommitLandblockReplacement(:304-321) doesstagingCache.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=1is 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::HandleCreateObject0x00454C80 runsSmartBox::init_player0x00455010 thenCPhysicsObj::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, setstransient_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)
- 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.
- src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:193 — the one legacy remote caller now calls AcDream.Core.Physics.SpawnPlacementSettler.TrySettle (unchanged args).
- 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).
- 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
- 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.
- 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).
- 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.
- 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
_pendingoutside 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:
- 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.
- 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.
- 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.
- 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 = nullpreceding 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)
- Runtime 1,003/1,003; App 4,038/3 skips of 4,041; Headless 79/79.
- 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).
- 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).
- 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
_projectionLocationsbut 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
StreamingMutationExceptionand 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.
2026-08-03 stabilization checkpoint and handoff
Five separately bisectable root-cause fixes followed the O1/O2/O3 checkpoint:
01f4791e— pending-only live projection buckets no longer manufacture a second origin-recenter retirement receipt. Complete Release, lifecycle, and canonical nine-stop soak passed on this binary; the soak had nine checkpoints, zero failures, zero wait cues, and zero pending retirements.670f307c— Runtime converts remote Create frames through the accepted local-player world center and publishes the local physics body's world position. The user accepted monster/static placement, chase, and attacks.1fc529cd— distant Use materializes the canonical minimal static physics host before MoveTo, and presentation attach reconciles the pre-PartArray startup motion suffix. The user accepted near and distant object use.f24532ad— exact-incarnation effect packets wait for canonical presentation binding; projectile and static-animation sidecars retry on the committed visibility edge; effect cells follow rebuckets. The user accepted buffs, recalls, arrows, combat spell projectiles, portals, and statics.175ad6b0— LoginComplete is emitted from the local first-placement terminal edge instead of raw PlayerCreate receipt. The user accepted login materialization haze behavior.
Post-fix focused evidence: 90 App effect/projectile/static-scheduler tests,
two Runtime login tests, the exact live-entity cell tracking test, all 79
Headless tests, and a Release solution build with zero errors passed. The
complete suite and connected nine-stop soak were not rerun after the final
four stabilization commits. A broader selected fixture run exposed five
still-open LiveEntityRuntimeTests failures associated with the placement
cutover and one old remote-first-entry fixture that supplies an empty
collision source. These are campaign work, not evidence to weaken the new
production contracts.
Open campaign finish: classify/fix those six fixtures; finish placement routes 2–7 and delete legacy writers; resolve #276/#277 and portal-prefetch #280; run the final-binary complete suite/lifecycle/nine-stop/two-client gates; perform the #269 slope-glide visual check; retire AP-1/AD-1/AP-131/AD-60 only when the legacy paths are gone; then land AP-22 and AD-10 and close the ledger.