Root cause: pending-only live projection buckets were misclassified as landblock presentation owners during origin recentering. That manufactured a second full cleanup receipt for a generation whose first receipt was still advancing; the duplicate guard threw and the broad retry path replayed the already-committed detach 243 times. Keep pending live projections through the spatial identity map without issuing another receipt, and fail fast when a receipt-ledger invariant occurs after detachment. Evidence: docs/research/2026-08-02-collision-throughput-handoff/p1-retirement-receipt-loop.md. Release suite, lifecycle gate, and nine-stop soak pass.
4549 lines
280 KiB
Markdown
4549 lines
280 KiB
Markdown
# Implementer progress — Runtime initial-placement continuation executor
|
|
|
|
## Reading phase (complete)
|
|
- Read contract, runtime-surface.md, retail-notes.md in full.
|
|
- Read source: RuntimeInitialCreateResidenceState.cs (full, 960 lines),
|
|
InboundPhysicsStateController.cs (full, 903 lines),
|
|
RuntimeAuthoritativePositionRouteClassifier.cs (full, 580 lines),
|
|
RuntimeEntityObjectLifetime.cs (full, 2134 lines),
|
|
RuntimeEntityDirectory.cs (full), RuntimeEntityRecord.cs (full),
|
|
ParentAttachmentState.cs (full), RuntimeInitialCreateAdmissionFreezer.cs (full),
|
|
RuntimeSetPositionState.cs (targeted: struct defs, CaptureOwnership,
|
|
Begin*/Watch/IsCurrent/TryPeek/Consume/Forget*, PrepareMover/Submit/
|
|
Apply/AcknowledgeProjection/PublishCancellation/Forget/LeaveWorld),
|
|
RuntimeInitialCreateResidenceStateTests.cs (full, 2657 lines - harness
|
|
helpers: Bind/Spawn/AttachDormantBody/Prepare/ApplyQueuedPosition/
|
|
PositionUpdate/ApplyFreshSuccessor/ConvergeSessionClear/PositionAction/
|
|
SetCompletedAdoptionRevision/EntityObserver/PlacementObserver).
|
|
|
|
## Key design decisions made during reading
|
|
1. AdoptCompletedPlacement resolves runtime-surface.md 3.1 deadlock:
|
|
BeginAcceptedPlacementCore/TryBeginExclusiveAuthoredPlacement all reject
|
|
while HasRetainedCompletion(key) is true. Executor must call
|
|
ConsumeAcknowledgedPlacement directly (via new AdoptCompletedPlacement on
|
|
residence state) BEFORE any Position continuation can begin its own
|
|
placement.
|
|
2. Movement timestamp reconstruction: AcceptedPhysicsTimestamps has
|
|
ServerControlledMove but NOT Movement itself. Reasoned from the
|
|
"MOVEMENT_TS consumed before discovering SERVER_CONTROLLED_MOVE_TS stale"
|
|
comment: HasTimestampMutation==true always implies gate.MovementTimestamp
|
|
advanced to update.MovementSequence (movement gate checked first). So
|
|
ApplyAcceptedMotion sets MovementSequence=update.MovementSequence
|
|
unconditionally when retained, ServerControlSequence=retained
|
|
AcceptedTimestamps.ServerControlledMove exactly. Judgment call - documented
|
|
in code comment.
|
|
3. MirrorGateTimestamps (reads LIVE gate) must NOT be used by the executor
|
|
(would stomp other channels' Timestamps fields with the wrong values from
|
|
out-of-band-drift live gate). Refactored ApplyAcceptedMotion to a targeted
|
|
field update instead - verified behaviorally identical for the legacy path
|
|
since gate/snapshot stay in lockstep there.
|
|
4. Circular-ownership constraint: deferred-child replay needs
|
|
RegisterEntityWithInitialResidence (internal core with beginInitialResidence
|
|
semantics). Contract forbids passing RuntimeEntityObjectLifetime itself.
|
|
Resolution: lifetime passes a bound delegate
|
|
(WorldSession.EntitySpawn, bool) -> RuntimeEntityRegistrationResult into the
|
|
executor's ctor, mirroring the existing retirePriorProjection callback
|
|
pattern already used in RegisterEntityCore. Narrow single-purpose seam,
|
|
not a reference to the owning type.
|
|
5. Publish choke point: lifetime passes bound delegates for PublishEntity and
|
|
AcknowledgeProjectionAndPublish (private methods -> delegate via method
|
|
group, same pattern as existing callback threading).
|
|
|
|
## Implementation phase - STARTING NOW
|
|
Next: refactor InboundPhysicsStateController.cs to extract ApplyAccepted*
|
|
methods shared by legacy TryApply* and the new executor.
|
|
|
|
## Implementation progress (continued)
|
|
- InboundPhysicsStateController.cs refactored: ApplyAccepted{ObjDesc,Pickup,
|
|
CreateParent,Parent,Vector,State,Motion,Position} methods extracted, legacy
|
|
TryApply* re-expressed as gate+shared-apply. Removed now-unused
|
|
MirrorGateTimestamps. Build clean, 89-test focused gate green after refactor.
|
|
FOUND+FIXED a bug during this pass: ApplyAcceptedMotion's retainPayload:false
|
|
branch must ALSO stamp nested Physics.Timestamps.Movement/ServerControlledMove
|
|
(not just top-level fields) - two pre-existing tests
|
|
(RejectedServerControlStillMirrorsConsumedMovementTimestamp,
|
|
AutonomousLocalEchoRetainsPayloadButMirrorsAcceptedTimestamps) caught this;
|
|
fixed, full 829-test Runtime suite green again.
|
|
- RuntimeInitialCreateResidenceState.cs: added CompletedEntry.PlacementAdopted,
|
|
AdoptCompletedPlacement, ConsumeExecuted +
|
|
RuntimeInitialCreateResidenceExecutorReleaseStatus enum. Updated
|
|
IsCompletedCurrent to treat PlacementAdopted as satisfying the placement-
|
|
current check without re-querying RuntimeSetPositionState (since adoption
|
|
already consumed/removed that tracking entry). Updated AcknowledgeAdoption
|
|
to tolerate (skip) re-consuming an already-adopted placement. Build clean,
|
|
48/48 residence tests green.
|
|
- New file src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
written (full algorithm: reentrancy latch, per-key Progress with LeaseId
|
|
staleness discard, initial tail (adopt+hook+deferred-child replay), FIFO
|
|
drain for all 8 continuation kinds incl. SameIncarnationCreate envelope
|
|
staging with buffered publish, Position classify-at-execution-time +
|
|
placement Begin/Watch/yield/resume lifecycle, ConsumeExecuted release with
|
|
Revised-loop). Constructed in all 3 RuntimeEntityObjectLifetime ctors,
|
|
wired into BindEventContext, CaptureOwnership (new
|
|
InitialCreateExecutorProgressCount field), and the residence
|
|
Forget/Clear choke points (deterministic progress discard, not lazy).
|
|
Build clean, full 829-test baseline still green after wiring.
|
|
|
|
## Judgment calls made (to report)
|
|
1. AdoptCompletedPlacement/ConsumeExecuted new API design (see code comments
|
|
in RuntimeInitialCreateResidenceState.cs) - resolves the 3.1 deadlock per
|
|
contract's exact prescription.
|
|
2. Registration callback threaded as a bound delegate
|
|
(WorldSession.EntitySpawn, bool) -> RuntimeEntityRegistrationResult from
|
|
the lifetime's ctor, NOT a reference to RuntimeEntityObjectLifetime itself
|
|
- satisfies "no circular ownership" per contract while still reaching
|
|
RegisterEntityWithInitialResidence for deferred-child replay.
|
|
3. Position continuation Rejected/RejectedData routes reuse
|
|
InboundPhysicsStateController.ApplyAcceptedPosition by passing
|
|
PositionTimestampDisposition.Rejected explicitly (that method internally
|
|
routes to the timestamp-only-stamp branch) - avoids a second, divergent
|
|
"timestamp only" implementation.
|
|
4. HasContact/forcePositionRotation/currentLocalVelocity all derived from
|
|
canonical.PhysicsBody when attached, falling back to Inputs.HasContact
|
|
(bodyless tests) - per contract's explicit "decide one way, document it,
|
|
test it" instruction for HasContact, extended consistently to the other
|
|
two live-body-derived facts.
|
|
5. ParentAttachmentState.Resolve/CommitProjection has zero callers anywhere
|
|
in AcDream.Runtime (grep-verified) - confirmed host-driven, not wired.
|
|
Parent/CreateParent continuation apply commits the position-timestamp-only
|
|
snapshot mutation + AdvancePositionAuthority/LeaveWorld/Forget/publish
|
|
only; does NOT invent a second resolve/commit path.
|
|
6. ObjectTableWiring.ApplyEntitySpawn (via RuntimeEntityObjectLifetime.
|
|
ApplyAcceptedSpawn) has zero callers anywhere in AcDream.Runtime
|
|
(grep-verified) - WeenieDescription tail action commits
|
|
RefreshSnapshot+AdvanceCreateAuthority+publish only, explicitly does NOT
|
|
drive the object table (host cutover work, out of scope).
|
|
7. ResidentCellCleanup implemented as an assert-the-invariant check
|
|
(claimed+celless must be IsDeferred) rather than building a destruction
|
|
mechanism; the "no cell claimed, no weenie" destruction-mark branch is
|
|
left as a recorded no-op (needs live object-table wiring not in scope).
|
|
8. Reentrant Execute() for the same RuntimeEntityKey while one is already on
|
|
the call stack fails closed via a HashSet<RuntimeEntityKey> latch.
|
|
9. Stale Progress (LeaseId mismatch, e.g. GUID/key reuse) is discarded AND
|
|
the CURRENT Execute call returns RejectedAuthority (not silently
|
|
re-created + retried in the same call) - matches contract's literal
|
|
"discard it and fail closed" wording; caller must retry once more.
|
|
|
|
## Major design fix discovered during test-driven debugging
|
|
Found a genuine architecture conflict: RuntimeInitialCreateResidenceState's
|
|
IsCompletedCurrent staleness check compares LIVE record.PositionAuthorityVersion/
|
|
CreateIntegrationVersion/FullCellId/PlacementCommitVersion against values
|
|
FROZEN at Complete()-time. But the executor's OWN Apply* methods legitimately
|
|
advance these SAME fields while draining (AdvancePositionAuthority,
|
|
AdvanceCreateAuthority, SetFullCell, the physics engine's own
|
|
AdvancePlacementCommit on a continuation's own SetPosition commit) - this
|
|
made Complete()/ConsumeExecuted incorrectly treat the executor's own
|
|
controlled progress as an external race and reject with RejectedAuthority.
|
|
Root-caused via a debug bisection test (temporarily added, then removed).
|
|
|
|
Fix: added CompletedEntry.Expected{PositionAuthorityVersion,
|
|
CreateIntegrationVersion,FullCellId,PlacementCommitVersion} - a SEPARATE,
|
|
executor-maintained baseline distinct from the FROZEN receipt.Token (which
|
|
must stay byte-identical for Complete()'s token-identity match to keep
|
|
working across retries). IsCompletedCurrent now compares against
|
|
entry.Expected* instead of receipt.Token.*/receipt.FullCellId/
|
|
receipt.PlacementCommitVersion directly. New method
|
|
AdvanceExecutorBaseline(record, token) re-syncs Expected* from the record's
|
|
CURRENT live values; the executor calls it (a) at the top of ExecuteCore's
|
|
loop before every Complete() call (covers a pending continuation placement's
|
|
host-driven commit, which happens BETWEEN Execute() calls), and (b)
|
|
unconditionally after every ApplyContinuation call in the drain loop (covers
|
|
in-call mutations before a yield). Verified this does NOT weaken the
|
|
EXISTING admission-slice regression tests (CorruptedPostAcknowledgementAuthorityRetiresProofAndLease
|
|
etc.) since those tests never call AdvanceExecutorBaseline - an external,
|
|
non-executor-driven bump to these fields is still correctly detected as
|
|
stale. All 48 residence tests + 16 new executor tests + 829 baseline pass
|
|
together (845 total).
|
|
|
|
## Test suite status: 16/16 new tests green, 845/845 total Runtime tests green
|
|
Covered: A (2 tests: basic completion + hook; mixed simple continuation
|
|
order), C (2: envelope atomicity/buffered-publish/stage-order incl.
|
|
PreTailDescriptionAdaptation+Pickup decomposition; envelope retry
|
|
non-duplication), D (5: local ordinary interpolate, local teleport placement
|
|
lifecycle w/ yield+resume, remote near interpolate, remote far
|
|
SetPositionSimple+StopInterpolating, parented-initial AwaitFreshPosition/no
|
|
placement), E (2: deferred-child replay consumes exact AdmissionId +
|
|
registers child through canonical route; stale AdmissionId cannot consume a
|
|
replacement - via ParentAttachmentState directly), G (2: reentrant Execute
|
|
for same entity fails closed; reentrant delete of the parent during deferred
|
|
child replay abandons without resurrection), H (1: stale Progress LeaseId
|
|
via reflection-planted entry discards + fails closed + clean retry), I (2:
|
|
reset during AwaitingContinuationPlacement converges every ledger; dispose
|
|
after successful execution converges IsConverged).
|
|
NOT separately tested (time-budget / defensibility tradeoffs - noted for
|
|
final report): F's literal "failure injection between every envelope stage"
|
|
(only end-to-end retry-after-full-drain covered, not synthetic mid-stage
|
|
crash injection); J (relies on the existing assembly-level
|
|
RuntimeDependencyBoundaryTests, which automatically covers the new file -
|
|
no new file added since the check is assembly-wide, not per-file); the
|
|
ResidentCellCleanup destruction-mark branches (only the "claimed+resident"
|
|
and the assert-invariant path are exercised, not the "no cell, no weenie"
|
|
destruction-mark path, which is explicitly a recorded no-op pending future
|
|
object-table wiring per code comment); SameIncarnationCreate's Position
|
|
stage mid-envelope yield+resume (D covers standalone Position yield/resume;
|
|
the envelope's OWN Position-stage yield path shares the identical
|
|
ApplyPositionAction code but is not independently exercised by an envelope
|
|
test with a position stage).
|
|
|
|
## ROUND 2: Reviewer feedback (Finding 1, Gaps 2-4, Finding 5 amendment)
|
|
Coordinator sent review findings after round-1 delivery. Working through:
|
|
- FINDING 5 (production regression, addressed FIRST since flagged most
|
|
urgent): ApplyAcceptedMotion's legacy caller was stamping
|
|
update.MovementSequence (wire's own stale/rejected value) instead of
|
|
gate.MovementTimestamp (post-call gate value) for the timestamp-only
|
|
branch. TryAcceptMovementEvent has 3 rejection flavors and only 1 of them
|
|
(stale ServerControlledMove) actually advances MOVEMENT_TS; the other two
|
|
(bad instance, stale MOVEMENT_TS) leave the gate untouched but the OLD
|
|
buggy code would still stamp the wire's stale value into the snapshot.
|
|
FIX: re-parameterized ApplyAcceptedMotion to take explicit
|
|
(movementSequence, acceptedServerControlledMove) inputs instead of
|
|
deriving movementSequence from `update` internally - ONE shared body, two
|
|
explicit-input callers. Legacy caller now passes
|
|
gate.MovementTimestamp/gate.ServerControlledMoveTimestamp (exact
|
|
pre-refactor behavior in all 3 flavors). Executor caller passes
|
|
action.Movement.Value.MovementSequence/action.AcceptedTimestamps.ServerControlledMove
|
|
(safe there since retention itself gates on genuine acceptance). Added 2
|
|
regression tests to InboundPhysicsStateControllerTests.cs comparing the
|
|
FULL EntitySpawn before/after for (1) stale MOVEMENT_TS, (2) instance
|
|
mismatch - both assert byte-identical snapshots. 15/15
|
|
InboundPhysicsStateControllerTests pass, 64/64 executor+residence tests
|
|
still pass after the signature change.
|
|
|
|
- FINDING 1 fixed: narrowed the pre-Complete() AdvanceExecutorBaseline call
|
|
in ExecuteCore to only fire when progress.PendingContinuationPlacement.IsValid
|
|
(the only legitimate between-calls mutation window). Added 2 regression
|
|
tests (ExternalPositionAuthorityMutation.../ExternalFullCellMutation...)
|
|
that directly drive Complete()+AdoptCompletedPlacement to the
|
|
"completed+adopted, no pending placement" state, then externally mutate
|
|
PositionAuthorityVersion/FullCellId, then assert the NEXT Execute() call
|
|
observes RejectedAuthority, publishes nothing, and converges. VERIFIED
|
|
these tests actually catch the regression: temporarily reverted the guard
|
|
to unconditional, confirmed both tests fail (Completed instead of
|
|
RejectedAuthority), then restored the fix and confirmed they pass again.
|
|
18/18 executor tests pass (16 + 2 new).
|
|
|
|
- GAP 4 done: extended RuntimeInitialCreateExecutedAction with a new
|
|
optional ResidentCellCleanupDisposition field +
|
|
RuntimeResidentCellCleanupDisposition enum (ResidentUnmarked/
|
|
DeferredUnderLostCellOwnership/NoCellClaimedDestructionMarked).
|
|
ApplyResidentCellCleanup now RETURNS the disposition instead of just
|
|
asserting. (a) ResidentCellCleanupUnmarksWhenCellClaimedAndAlreadyResident
|
|
- engine-backed, real initial placement, same-create Position stage
|
|
classifies Interpolate (SameIncarnationCreate source forces
|
|
effectiveContact=true, entity already resident so not cellless) -> no
|
|
placement needed, ResidentUnmarked recorded. (b)
|
|
ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership
|
|
- PickedUp initial (no placement), same-create Position with
|
|
UsePositionFromServer=false classifies NoPositionOperation (no
|
|
SetPosition begins at all) -> claimed+celless+not-deferred -> throws
|
|
InvalidOperationException, verified via Assert.Throws + message
|
|
content. (c) folded into the EXISTING envelope-atomicity test (added
|
|
assertion) since that entity already naturally has no claimed cell ->
|
|
NoCellClaimedDestructionMarked. 20/20 executor tests pass (18 + 2 new;
|
|
case (c) added to an existing test rather than a new one).
|
|
|
|
- GAP 3 done, AND IT CAUGHT A REAL BUG: wrote
|
|
EnvelopePositionStageRequiringSetPositionYieldsResumesAndPublishesOnceAfterCompletion
|
|
(cellless -> SetPosition Position stage forces a mid-envelope yield,
|
|
drive prepare/submit/ack, resume, envelope completes). First run: only 1
|
|
of 5 expected Updated events published (expected ObjDesc/Position/State/
|
|
Vector/WeenieDescription). ROOT CAUSE: Publish()'s buffered branch stored
|
|
the PER-STAGE "matches" closure (captured at that stage's OWN commit
|
|
time, checking one specific AuthorityVersion field) into the buffer -
|
|
but WeenieDescription's own AdvanceCreateAuthority() call bumps SIX
|
|
authority-version fields at once (Position/State/Vector/Velocity/
|
|
Movement/ObjDesc/CreateIntegration), which invalidated every EARLIER
|
|
buffered stage's captured version-equality check by the time the final
|
|
flush ran, even though nothing external raced - it was the executor's
|
|
OWN later, expected progression. FIX (root cause, not per-callsite
|
|
patch): Publish()'s buffered branch now stores a constant `true`
|
|
predicate instead of the per-field "matches" closure - IsCurrent (checked
|
|
unconditionally by PublishNow) is the only currency guard a buffered
|
|
entry needs, since envelope processing dispatches no event until the
|
|
flush (no reentrancy window mid-envelope except at a Position yield,
|
|
which is independently guarded by ApplyEnvelope's own IsCurrent check +
|
|
ResumePendingPlacement). The IMMEDIATE (non-buffered) path is unchanged -
|
|
still uses the real matches() check, appropriate there since publish
|
|
happens right after each standalone mutation. 21/21 executor tests pass
|
|
after the fix (20 + 1 new).
|
|
|
|
- GAP 2 done: EnvelopeAbandonedDuringPositionStageYieldPublishesNothingAndConvergesEveryLedger
|
|
(delete the entity while AwaitingContinuationPlacement is pending
|
|
mid-envelope; asserts ONLY the delete's own Deleted event observed - none
|
|
of the envelope's already-committed-but-buffered stages ever publish -
|
|
and every ledger converges: residence/progress/active-operation/
|
|
acknowledged-completion counts all zero; a stale retry with the original
|
|
token returns RejectedToken). Documented (large comment block, not a
|
|
fake test) exactly why the other 6 stage kinds have no reachable yield
|
|
boundary (pure synchronous in-memory mutations, no event dispatch until
|
|
the buffered flush, so no callback/reentrancy opportunity exists without
|
|
adding a diagnostic seam to production code, which is explicitly
|
|
forbidden). While building this test, ALSO found and fixed a real (if
|
|
currently redundant-with-existing-Forget) defensive gap: DiscardProgress/
|
|
DiscardAll now also forget any in-flight CONTINUATION placement token
|
|
(distinct from the residence's own initial-lease placement) - verified
|
|
via revert that this specific delete-path test still passes WITHOUT the
|
|
fix (TryAcceptDelete's own unconditional Physics.SetPosition.Forget
|
|
already cancels any operation for that key), so this is honest
|
|
defense-in-depth (DiscardProgress owning cleanup of the state it
|
|
introduces) rather than a proven-necessary fix for THIS path - documented
|
|
as such in both the code comment and the test comment.
|
|
22/22 executor tests pass (21 + 1 new).
|
|
|
|
## ALL 4 REVIEW ITEMS (Finding 1, Gaps 2/3/4) COMPLETE, plus Finding 5
|
|
(production regression) fixed first.
|
|
|
|
## ROUND 2 FINAL GATES: ALL GREEN
|
|
1. Release build (AcDream.slnx): 0 errors, 21 pre-existing warnings.
|
|
2. Focused filter (Residence+Classifier+Executor): 111 passed (89 + 22).
|
|
3. Complete Runtime suite: 853 passed (829 + 24: 22 executor + 2
|
|
InboundPhysicsStateController Finding-5 regressions).
|
|
4. git diff --check: exit 0, clean. Nothing staged.
|
|
Confirmed the narrowed rebaseline (Finding 1) did not break the existing
|
|
AwaitingContinuationPlacement yield/resume path -
|
|
LocalTeleportContinuationDrivesItsOwnAuthoredPlacementLifecycle still
|
|
passes throughout every round of changes.
|
|
|
|
## FINAL STATUS (Round 2): ALL GATES GREEN
|
|
1. dotnet build AcDream.slnx -c Release: 0 errors, 21 warnings (all
|
|
pre-existing, in test projects untouched by this work - confirmed exact
|
|
match to the known-count in the task brief).
|
|
2. Focused filter (Residence+Classifier+Executor): 105 passed (89 baseline
|
|
+ 16 new), 0 failed.
|
|
3. Complete AcDream.Runtime.Tests: 845 passed (829 baseline + 16 new), 0
|
|
failed.
|
|
4. git diff --check: exit 0, clean (only pre-existing line-ending warnings
|
|
on files this session did not touch, or normal LF-will-become-CRLF
|
|
metadata notices on files this session DID touch - no whitespace-error
|
|
content).
|
|
Nothing staged (git diff --cached --stat empty) - primary agent to review
|
|
and commit.
|
|
|
|
## ROUND 3: both independent reviews returned FAIL (3 blockers, 12 majors,
|
|
## mandated test-completion list). Combined directive at round3-fixes.md.
|
|
## Followed the section-D work order: A1/A2 first, then B1/B2/B3/B4, then
|
|
## A3/B5/B6/B9/B10, then B7/B8/B12, then B11, Section C tests throughout.
|
|
|
|
### A1 (blocker) — snapshot lockstep. FIXED.
|
|
Root cause confirmed exactly as the review described:
|
|
InboundPhysicsStateController._snapshots (the legacy merge base) was NEVER
|
|
written by the executor's applies - they merged directly against
|
|
canonical.Snapshot via the STATIC ApplyAccepted* methods and called
|
|
RefreshSnapshot, but _snapshots[guid] stayed frozen at whatever it was when
|
|
residence began. The FIRST subsequent legacy TryApplyXxx call would then
|
|
re-merge onto that stale base and silently revert every drained fact.
|
|
Fix: added gate-less INSTANCE seam methods on InboundPhysicsStateController
|
|
(ApplyAccepted{ObjDesc,Pickup,CreateParent,Parent,Motion,State,Vector,
|
|
Position,WeenieDescription}Snapshot) that read _snapshots[guid] as the merge
|
|
base, run the existing shared static body, write the result back, and return
|
|
the merged value. Delegated through RuntimeEntityDirectory (new
|
|
ApplyAccepted*Snapshot 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<DeferredParentCreate>: atomically removes and returns the
|
|
ENTIRE queued bucket for one parent (retail: PartArray::add_child-owning
|
|
CreateObject handler detaches the whole netblob list before dispatching,
|
|
pseudo-C ~93617 - detach IS the consume, no separate peek-then-remove).
|
|
ReplayDeferredChildren rewritten to call this once and iterate the detached
|
|
snapshot, rechecking _entities.IsCurrent(canonical) per iteration (unchanged
|
|
abandonment semantics) - this structurally eliminates the old peek/consume
|
|
loop's stale-AdmissionId race entirely (a Create arriving for the SAME
|
|
parent during replay now enqueues into a BRAND NEW queue instance, since the
|
|
old one was already removed from the dictionary). Kept TryPeekDeferredCreate/
|
|
ConsumeDeferredCreate/ContainsDeferredCreate/CancelDeferredChildGeneration -
|
|
still used by other invariants and by
|
|
StaleAdmissionIdCannotConsumeAReplacementQueuedAfterThePeek, which tests
|
|
ParentAttachmentState directly (Section C matrix item: the round3-fixes
|
|
text asked E-scenarios to go through the executor path - added a NEW
|
|
executor-path test, MultipleChildrenQueuedBehindOneMissingParentReplayInFifoOrderThroughTheExecutor,
|
|
covering 2 children queued behind one missing parent, both replaying in
|
|
order once the parent registers, rather than migrating the existing
|
|
ParentAttachmentState-direct test, since that one specifically exercises
|
|
the AdmissionId-staleness invariant which is a ParentAttachmentState-level
|
|
contract independent of the executor).
|
|
|
|
### B8 (major) — rename unreachable ResidentCellCleanup disposition. FIXED.
|
|
NoCellClaimedDestructionMarked -> CelllessNoWeenieMarkUnreachable, with an
|
|
updated doc comment citing HandleCreateObject retail-notes.md function 1
|
|
lines ~93942-93943 and the shape guarantee at
|
|
RuntimeInitialCreateResidenceState.cs:277-284
|
|
(RuntimeInitialCreateResidenceContinuation.HasValidShape enforces
|
|
Actions[^2].Kind is WeenieDescription for every admitted envelope, so
|
|
retail's matching "no weenie" condition can never be true through this
|
|
exact construction). Updated the one test reference
|
|
(SameIncarnationEnvelopePublishesNothingUntilEveryStageCommitsThenPublishesInStageOrder).
|
|
|
|
### B9 (major) — Parent continuation execution-time revalidation. FIXED.
|
|
New ApplyParentContinuation wrapper (called from ApplyContinuation's Parent
|
|
case instead of ApplyParentAction directly): re-checks
|
|
_entities.TryGetActive(parentGuid) + incarnation match at EXECUTION time; on
|
|
mismatch, re-enqueues via _entities.ParentAttachments.Enqueue(parentUpdate)
|
|
and records a routine trace entry (Completed, not abandonment) instead of
|
|
running ApplyParentAction against a parent that may have been deleted or
|
|
replaced between admission and this drain reaching it. Added
|
|
ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch:
|
|
admits a Parent continuation while the parent is still active, deletes the
|
|
parent before the drain runs, asserts the drain still converges cleanly and
|
|
the update lands back in ParentAttachmentState's unresolved queue
|
|
(UnresolvedRelationCount == 1) rather than crashing or committing against a
|
|
gone parent.
|
|
(Envelope-side note: RuntimeInitialCreateTailActionKind.Parent has no case
|
|
in ApplyEnvelope's switch at all - only CreateParent does, confirmed by
|
|
rereading RuntimeInitialCreateResidenceState.cs's HasValidShape/
|
|
SameCreateStage: the position-branch stage only ever admits
|
|
CreateParent/Pickup/Position, never standalone Parent - so B9 only applies
|
|
to the standalone top-level continuation kind, which is where the fix
|
|
landed.)
|
|
|
|
### B10 (major) — new stamp variant for execution-time-rejected retained
|
|
### Position. FIXED.
|
|
Added InboundPhysicsStateController.ApplyAcceptedPositionExecutionRejectedSnapshot
|
|
(+ its RuntimeEntityDirectory delegate): stamps Position/Teleport/
|
|
ForcePosition timestamp channels (all three the gate genuinely advanced at
|
|
ADMISSION time) without installing any pose/parent/placement field -
|
|
distinct from the EXISTING ApplyAcceptedPositionTimestampOnly (the
|
|
ADMISSION-time-gate-Rejected case, where only ForcePosition can have moved).
|
|
ApplyPositionAction's `!route.Accepted` branch now branches on
|
|
action.PositionDisposition: Rejected (admission itself rejected) uses the
|
|
existing Rejected-forced merge; Apply/ForcePosition (admission accepted, but
|
|
EXECUTION-time classification now rejects) uses the new stamp variant. Not
|
|
independently unit-tested with a NEW dedicated test (constructing a retained
|
|
action whose ADMISSION disposition is Apply/ForcePosition but whose
|
|
EXECUTION-time classification genuinely rejects needs a live-input/record
|
|
mismatch crafted between admission and drain - flagged as a coverage gap;
|
|
the code path was verified by direct code review of both branches against
|
|
InboundPhysicsStateController.ApplyAcceptedPositionTimestampOnly's own
|
|
existing doc comment, which independently documents the same
|
|
admission-vs-execution distinction this fix formalizes).
|
|
|
|
### B11 (major) — Progress creation timing. FIXED.
|
|
Execute/ExecuteCore restructured: an existing Progress for a mismatched
|
|
LeaseId is discarded AND the call fails closed immediately (preserves the
|
|
EXISTING contract/test
|
|
StaleProgressLeaseIdIsDiscardedAndFailsClosedThenRetrySucceedsCleanly's
|
|
"fail THIS call, retry succeeds fresh" two-step semantics - my first attempt
|
|
at B11 broke this test by silently continuing forward in the SAME call
|
|
after discarding stale progress; caught immediately by the full-suite run,
|
|
reverted to the fail-closed-then-retry shape). A FRESH Progress for the
|
|
CURRENT lease id is never created until _residences.Complete(...) actually
|
|
reports Completed - PendingPlacement/RejectedToken/RejectedAuthority
|
|
outcomes on a call with no PRIOR progress now leave the ownership ledger
|
|
(ProgressCount) completely untouched, rather than a placeholder Progress
|
|
object sitting in _progress for a residence that has not even resolved its
|
|
own initial placement yet.
|
|
|
|
## Test suite status after Round 3
|
|
- 5 NEW tests added (all Round 3): DrainedAppearanceAndPoseSurviveTheNextLegacyWireApply
|
|
(A1), SameIncarnationWeenieDescriptionMergePreservesRetainedMotionTableId
|
|
(A2), WeenieDescriptionStageWiresTheObjectTableExactlyOnce (B12),
|
|
MultipleChildrenQueuedBehindOneMissingParentReplayInFifoOrderThroughTheExecutor
|
|
(B7), ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch
|
|
(B9).
|
|
- 1 EXISTING test rewritten from Assert.Throws to typed-abandonment
|
|
assertions (ResidentCellCleanupFailsClosedWhenClaimedCelllessOutcomeIsNotUnderLostCellOwnership,
|
|
covers B1's ResidentCellCleanup-abandonment path).
|
|
- 1 EXISTING test's enum reference updated
|
|
(SameIncarnationEnvelopePublishesNothingUntilEveryStageCommitsThenPublishesInStageOrder,
|
|
B8 rename).
|
|
- 6 EXISTING call sites fixed for the removed HasContact parameter (A3) -
|
|
none of their PASS/FAIL semantics changed, only the construction syntax.
|
|
- Complete AcDream.Runtime.Tests: 858 passed (853 Round-2 baseline + 5 new),
|
|
0 failed.
|
|
|
|
## Known gaps NOT covered by a new dedicated test (time-budget /
|
|
## defensibility tradeoffs, reported honestly rather than papered over):
|
|
- B1's operation-slot-contention retry path (transient
|
|
TryBeginExclusiveAuthoredPlacement failure while the record stays
|
|
current) - needs a second concurrent SetPosition consumer occupying the
|
|
same key's slot; none of the harness helpers construct that scenario.
|
|
Verified by code review against RuntimeSetPositionState's exact failure
|
|
conditions instead.
|
|
- B10's execution-time-rejected (admission accepted, live classification
|
|
rejects) Position stamp path - needs a live-input/record mismatch crafted
|
|
between admission and drain. Verified by code review + cross-reference
|
|
against ApplyAcceptedPositionTimestampOnly's existing analogous doc
|
|
comment instead.
|
|
- B2's reentrant-retirement-window closure is a structural/ordering fix
|
|
(mutate -> rebaseline -> publish) proven correct by the FULL 858-test
|
|
suite staying green (nothing in the existing suite depends on the OLD
|
|
ordering) rather than by a dedicated synthetic-reentrancy test - building
|
|
a true concurrent-observer reentrancy test that could only pass with the
|
|
NEW ordering and fail with the OLD one was judged lower value than the
|
|
other coverage gaps given the remaining time budget.
|
|
- Section C's full 9-sub-matrix enumeration from round3-fixes.md was not
|
|
exhaustively built out; the 5 new tests target the SPECIFIC new/changed
|
|
behaviors (A1/A2/B7/B9/B12) plus B1's rewritten test, prioritized over
|
|
broad matrix completeness under this round's time budget.
|
|
|
|
## ROUND 4: combined re-review findings (round4-fixes.md, R4-1..R4-15).
|
|
## Smaller than Round 3; same bar. Implemented in this order: R4-4 (field-
|
|
## masked baseline enum/method - foundational, everything else built on it),
|
|
## R4-1 (deferred-child replay containment/restore), R4-5+R4-6 (Parent
|
|
## discard + trace enums), R4-2 (ResumePendingPlacement forget-before-abandon),
|
|
## R4-3 (WeenieDescription apply-window reorder + result check), R4-7/R4-8
|
|
## (test-only trace-flag/wire-vs-body fixes), R4-9 (two mandated tests),
|
|
## R4-10 (captured-key threading), R4-11 (ApplyStateAction bool fix),
|
|
## R4-12/R4-13 (doc comments + fallback + structural test pin), R4-14 (doc
|
|
## comment), R4-15 (register-rows-draft.md rewrite).
|
|
|
|
### R4-1 (deferred-child replay containment). FIXED.
|
|
ReplayDeferredChildren rewritten: (a) each child's `_registerDeferredChild`
|
|
call now runs inside try/catch - an exception records
|
|
RuntimeDeferredChildReplayOutcome.Rejected and the loop continues with the
|
|
next entry (was: an uncontained exception would have escaped Execute
|
|
entirely and stranded every remaining sibling). (b) mid-loop abandonment
|
|
(entity no longer current, e.g. a reentrant delete/reset from an earlier
|
|
sibling's own registration callback) now calls the NEW
|
|
ParentAttachmentState.RestoreDeferredCreates(parentGuid, remainder) - a
|
|
new method that PREPENDS the exact unprocessed remainder (original
|
|
DeferredParentCreate records, so original AdmissionIds are preserved) ahead
|
|
of anything enqueued for the same parent guid after the detach - before
|
|
returning false. Previously the whole detached array was simply dropped on
|
|
the floor on abandonment; this was a genuine data-loss bug (retail's queued
|
|
blobs live on CObjectMaint per-GUID and survive the object; our own
|
|
GUID-keyed persistence design already assumed this but the code broke it).
|
|
Tests: DeferredChildReplayContainsOneChildsThrowingRegistrationAndContinuesWithSiblings
|
|
(3 children behind one parent; child 2's registration is forced to throw
|
|
via a reflection-swapped _registerDeferredChild delegate - the standard
|
|
fault-injection pattern this file already used for
|
|
SetCompletedAdoptionRevision; child 1 and 3 still register, trace shows
|
|
Rejected for child 2, no exception escapes Execute) and
|
|
DeferredChildReplayRestoresTheUnprocessedRemainderWhenTheParentIsDeletedReentrantlyMidReplay
|
|
(2 children; child 1's registration callback reentrantly deletes the
|
|
PARENT; child 2's raw Create is confirmed back in the bucket via
|
|
ContainsDeferredCreate; asserted residence-lease-count == 1, matching the
|
|
EXISTING DeleteDuringDeferredChildReplayAbandonsExecutionWithoutResurrection
|
|
precedent - that "1" is child 1's own never-executed residence, not a
|
|
leak; first draft of this test wrongly asserted 0 and had to be corrected
|
|
after tracing the precedent test's own comment).
|
|
|
|
### R4-2 (ResumePendingPlacement leaked the acknowledged completion on
|
|
### both failure arms). FIXED.
|
|
Both failure arms (projection/record mismatch; ConsumeAcknowledgedPlacement
|
|
failure) now call `_physics.SetPosition.ForgetExactPlacement(placementToken)`
|
|
+ `PublishCancellation` BEFORE clearing progress.PendingContinuationPlacement
|
|
and calling Abandon (forget -> clear -> Abandon, exactly as pinned).
|
|
Verified ForgetExactPlacement's own ForgetPlacementCompletionCore already
|
|
removes the _acknowledgedPlacementCompletions entry unconditionally (read
|
|
the source directly) - no separate ForgetPlacementCompletion call was
|
|
needed. Tests:
|
|
ExternalFullCellMutationDuringAwaitingContinuationPlacementForgetsThePendingPlacementAndAllowsAFreshOneToBegin
|
|
(the reviewer's exact scenario: drive a local-teleport continuation to
|
|
AwaitingContinuationPlacement, complete/acknowledge its placement, THEN
|
|
mutate FullCellId externally, assert the NEXT Execute fails closed,
|
|
AcknowledgedPlacementCompletionCount == 0, and a FRESH
|
|
TryBeginExclusiveAuthoredPlacement for the same key succeeds - proving no
|
|
retained-completion leak) and
|
|
ThirdPartyTryGetTransactionRetireWhileAwaitingContinuationPlacementDiscardsExecutorProgressAndForgetsThePendingPlacement
|
|
(R4-9a, see below). VERIFIED both regression tests actually catch the bug:
|
|
temporarily stripped the forget/publish calls from both failure arms (kept
|
|
a backup copy of the file), reran the R4-2 test - confirmed FAIL
|
|
(AcknowledgedPlacementCompletionCount stayed 1, not 0) - then restored the
|
|
fix and confirmed both tests pass again.
|
|
|
|
### R4-3 (ApplyWeenieDescriptionAction object-table apply window/result).
|
|
### FIXED.
|
|
Reordered to AdvanceCreateAuthority -> AdvanceExecutorBaseline ->
|
|
_applyAcceptedSpawn -> (false result -> return false, letting the EXISTING
|
|
caller route to Abandon) -> buffered publish. Previously the rebaseline ran
|
|
AFTER _applyAcceptedSpawn and the bool result was silently discarded -
|
|
mirrors RuntimeLiveEntitySessionController.cs:87's own gate on that same
|
|
call's result (a nested replacement re-entering from within
|
|
ObjectTableWiring.ApplyEntitySpawn's own synchronous ObjectAdded/
|
|
ObjectUpdated dispatch must invalidate the remaining tail). Tests:
|
|
ObjectTableSubscriberReenteringAWireApplyDuringIngestDoesNotRetireTheResidenceAndTheEnvelopeCompletes
|
|
(subscribe to lifetime.Objects.ObjectAdded, reentrantly call TryApplyVector
|
|
from inside it - residence survives, envelope completes) and
|
|
NestedReplacementDuringObjectTableIngestAbandonsTheWeenieDescriptionStageWithNoFurtherStages
|
|
(same subscription point, but reentrantly RegisterEntity a NEWER
|
|
incarnation for the SAME guid - Execute returns RejectedAuthority, no
|
|
further stages ran). Both use ClientObjectTable's plain C# events directly
|
|
(ObjectAdded/ObjectUpdated) rather than reflection - much simpler than
|
|
initially planned once I found these were public events.
|
|
|
|
### R4-4 (field-masked executor baseline precision). FIXED - foundational,
|
|
### done FIRST since every apply method's shape changed.
|
|
Added [Flags] enum RuntimeExecutorBaselineFields (PositionAuthorityVersion/
|
|
CreateIntegrationVersion/FullCellId/PlacementCommitVersion) on
|
|
RuntimeInitialCreateResidenceState.cs; AdvanceExecutorBaseline now takes a
|
|
`fields` parameter and only copies the named field(s) from the live record
|
|
into the CompletedEntry's Expected* baseline. Traced every apply method's
|
|
ACTUAL field mutations against RuntimeEntityRecord.cs's own method bodies
|
|
before assigning masks (not guessed): ObjDesc/Movement(both branches)/
|
|
State/Vector move NONE of the four tracked fields (their own
|
|
AdvanceXxxAuthority methods only bump ObjDescAuthorityVersion/
|
|
MovementAuthorityVersion+MovementCommitVersion/StateAuthorityVersion+
|
|
PhysicsStateMutationVersion/VectorAuthorityVersion+VelocityAuthorityVersion
|
|
respectively - VelocityAuthorityVersion is NOT one of the four tracked
|
|
fields) - AdvanceExecutorBaseline calls REMOVED entirely at these 4 sites.
|
|
Position/Parent/CreateParent (applied branch) move PositionAuthorityVersion
|
|
only. Pickup moves PositionAuthorityVersion + FullCellId (SetFullCell(0,0)).
|
|
WeenieDescription's AdvanceCreateAuthority moves PositionAuthorityVersion +
|
|
CreateIntegrationVersion (confirmed against its exact body). The pre-
|
|
Complete pending-placement-window rebaseline in ExecuteCore now masks
|
|
FullCellId + PlacementCommitVersion only (the sole legitimate between-calls
|
|
mutation, RuntimeSetPositionState's own commit machinery). R4-5's NEW
|
|
Parent-discard branch (ApplyParentPositionTimestampOnly/
|
|
ApplyCreateParentPositionTimestampOnly) correctly calls NO
|
|
AdvanceExecutorBaseline at all - traced that ApplyPositionTimestampOnly
|
|
(the shared static body both go through) only writes PositionSequence/
|
|
nested Physics.Timestamps.Position, never any AdvanceXxxAuthority method.
|
|
Test: FieldMaskedBaselinePrecisionDetectsAnExternalPositionRaceDuringAnUnrelatedObjDescPublish
|
|
(FIFO = ObjDesc then Vector on a parented/no-placement residence; an
|
|
observer bumps PositionAuthorityVersion externally during ObjDesc's OWN
|
|
publish; asserts the drain detects this at ConsumeExecuted - RejectedAuthority,
|
|
not Released - proving ObjDesc's own apply correctly did NOT blanket-rebaseline
|
|
and silently absorb the race).
|
|
|
|
### R4-5 (stale-parent DISCARD, not re-Enqueue) + R4-6 (trace enums). FIXED.
|
|
Added RuntimeParentRelationOutcome{Applied, DiscardedStaleParent} and
|
|
RuntimeDeferredChildReplayOutcome{Registered, ReDeferred, Rejected}, both
|
|
threaded onto RuntimeInitialCreateExecutedAction (renamed the old
|
|
`bool DeferredChildRegistered` field to `RuntimeDeferredChildReplayOutcome?
|
|
DeferredChildOutcome`, added a new `RuntimeParentRelationOutcome?
|
|
ParentRelationOutcome` field). ApplyParentContinuation's stale-parent
|
|
branch (standalone Parent) no longer calls ParentAttachments.Enqueue -
|
|
instead calls the NEW ApplyParentPositionTimestampOnly (the SAME
|
|
ApplyAcceptedParentSnapshot->ApplyPositionTimestampOnly merge body
|
|
ApplyParentAction's own first step already used, but stops there - no
|
|
AdvancePositionAuthority/LeaveWorld/Forget/publish) and traces
|
|
DiscardedStaleParent. Added the ANALOGOUS revalidation to the envelope's
|
|
CreateParent stage too (this never existed before at all - Round 3 B9 only
|
|
touched the standalone Parent kind, explicitly noting the envelope path
|
|
had no revalidation) via a new ApplyCreateParentContinuation wrapper +
|
|
ApplyCreateParentPositionTimestampOnly helper; CreateParentUpdate carries
|
|
no ParentInstanceSequence at all (confirmed from its own record definition
|
|
and the TryApplyCreateParent doc comment: "unlike standalone ParentEvent it
|
|
carries no parent INSTANCE_TS"), so only addressability is revalidated
|
|
there, never incarnation match. UPDATED the existing Round 3 B9 test
|
|
(renamed ParentContinuationRevalidatesLiveParentAtExecutionAndReDefersOnMismatch
|
|
-> ...AndDiscardsOnMismatch): asserts ParentRelationOutcome.DiscardedStaleParent
|
|
in the trace and UnresolvedRelationCount == 0 (was asserting 1, i.e. the
|
|
OLD re-defer semantics) - this was the ONE pre-existing test that broke
|
|
after the R4-5 rewrite, exactly as expected, and was updated per the
|
|
directive's explicit instruction.
|
|
|
|
### R4-7 (test-only: wire-IsGrounded-vs-body-contact). FIXED.
|
|
Parameterized the PositionUpdate test helper with `isGrounded = true`
|
|
(default preserves every existing call site's behavior). Deleted the 3
|
|
stale `ForceContact(canonical, inContact: true)` calls + their misleading
|
|
"matching this test's ... premise" comments at LocalOrdinaryPosition.../
|
|
RemoteNearContactPosition.../RemoteFarPosition... - none of them affect
|
|
routing anymore since Round 3 A3 (HasContact reads ONLY wire IsGrounded).
|
|
Added 2 new disagree-both-ways tests:
|
|
LocalOrdinaryPositionRouteFollowsWireGroundedTrueWhenBodyContactIsFalse
|
|
(wire true / body forced false -> Interpolate, i.e. route follows wire) and
|
|
RemotePositionRouteFollowsWireGroundedFalseWhenBodyContactIsTrue (wire
|
|
false / body forced true -> NoPositionOperation). Kept the ForceContact
|
|
helper itself (still used by these 2 new tests to construct the
|
|
disagreement).
|
|
|
|
### R4-8 (D-matrix trace-flag assertions). FIXED.
|
|
Added the missing ConstrainPhase/UnparentBeforeRouting/HookPhase/
|
|
ZeroVelocity/StopInterpolating assertions to the 5 existing D-matrix tests
|
|
(local ordinary, local teleport, remote near, remote far, projectile) per
|
|
each route's own classified values (cross-checked against
|
|
RuntimeAuthoritativePositionRouteClassifier's exact returned route structs,
|
|
not guessed).
|
|
|
|
### R4-9 (two missing mandated tests). FIXED.
|
|
(a) ThirdPartyTryGetTransactionRetireWhileAwaitingContinuationPlacementDiscardsExecutorProgressAndForgetsThePendingPlacement:
|
|
drives a local-teleport continuation to AwaitingContinuationPlacement
|
|
(placement begun+watched, NOT yet acknowledged), externally bumps
|
|
PositionAuthorityVersion, then calls
|
|
lifetime.InitialCreateResidences.TryGetTransaction(canonical, out _)
|
|
directly (the third-party path, not through Execute) - asserts it returns
|
|
false (staleness detected), then asserts executor progress/residence-lease/
|
|
active-operation/watch/acknowledged-completion counts are ALL zero (B3's
|
|
notification edge correctly forgot the pending continuation placement, not
|
|
just the residence's own initial-lease placement) and a fresh placement
|
|
can begin. (b) is the ExternalFullCellMutationDuringAwaitingContinuationPlacement...
|
|
test already covered under R4-2 above.
|
|
|
|
### R4-10 (captured-key threading through every Abandon call site). FIXED.
|
|
Threaded `RuntimeEntityKey key` as an explicit parameter through
|
|
ApplyContinuation/ApplyParentContinuation/ApplyEnvelope/ApplyPositionAction/
|
|
ResumePendingPlacement (all captured ONCE in ExecuteCore from
|
|
`canonical.Key is not { } key` at the very top). Replaced all 19
|
|
`canonical.Key ?? default` occurrences (18 Abandon call sites + the
|
|
ExecuteCore Released-branch receipt construction) with the threaded `key`.
|
|
This is not purely cosmetic: `canonical.Key ?? default` re-derives the key
|
|
from the LIVE record at each call site, which produces `default` (WRONG -
|
|
does not match the Progress dictionary's actual key) if canonical.Key has
|
|
already gone null (e.g. LocalEntityId released) by the time Abandon runs -
|
|
DiscardProgress(default) would silently fail to clean up the REAL stale
|
|
Progress entry. The threaded key is trusted/stable for the whole Execute
|
|
call. ApplyPositionAction's own top guard (`canonical.Key is not {} key`)
|
|
was simplified to `canonical.Key != key` since key is now a parameter, not
|
|
a fresh pattern-bind.
|
|
|
|
### R4-11 (ApplyStateAction BecameHidden currency-failure now returns
|
|
### false, not true). FIXED.
|
|
Changed `return true;` to `return false;` in the BecameHidden branch's
|
|
currency-failure check - the EXISTING caller (`if (!ApplyStateAction(...))
|
|
return Abandon(...)`) already converts false -> Abandon correctly, so no
|
|
other change was needed. Documented via a new doc comment on the method
|
|
explaining WHY this specific path is not independently unit-tested with a
|
|
live reentrancy seam: traced RuntimeCollisionReportingState.LeaveWorld ->
|
|
ForceEnd -> EndExpiredObjectCollisions and confirmed it returns immediately
|
|
whenever `_owners` has no established collision record for this key (line
|
|
~1115-1119) - which is ALWAYS true for a residence-fresh entity that has
|
|
never run a real collision batch, so no observer callback can ever fire
|
|
from this call site through this harness. Did not fake a seam; documented
|
|
per the round's explicit escape valve for this exact finding.
|
|
|
|
### R4-12 (ForcePosition parent-retention doc + structural test pin). FIXED.
|
|
Added a code comment at InboundPhysicsStateController.ApplyAcceptedPosition's
|
|
`parentGuid` computation citing retail Gate A (retail-notes.md function 3,
|
|
"GATE A: local-player force-position self-echo shortcut" - the early return
|
|
before CPhysicsObj::unset_parent). Extended the EXISTING ForcePosition test
|
|
(ForcePositionContinuationRecordsSetPositionSimpleWithPreservedHeadingAndNoParentClear):
|
|
attaches a parent via lifetime.Entities.TryCommitParent BEFORE the
|
|
ForcePosition update (gate-satisfying positionSequence match verified
|
|
against Spawn()'s own default), then asserts BOTH Position and
|
|
ParentGuid/ParentLocation are non-null after the drain - pinning the
|
|
deliberate combined shape deliberately, per the directive.
|
|
|
|
### R4-13 (HasAnimations Physics?.MotionTableId fallback). FIXED.
|
|
`hasAnimations` in ApplyPositionAction now reads
|
|
`(canonical.Snapshot.MotionTableId ?? canonical.Snapshot.Physics?.MotionTableId)
|
|
is {} m && m != 0u` - exact pinned formula. No dedicated new test (NOTE
|
|
priority per the directive); existing HasAnimations-adjacent tests
|
|
(ForcePosition non-animated route etc.) continue to pass unchanged since
|
|
top-level MotionTableId is populated in every existing test fixture.
|
|
|
|
### R4-14 (AwaitingContinuationPlacement doc comment). FIXED.
|
|
Added a doc comment on the enum value explaining the two distinct flavors
|
|
sharing this one status (ordinary token-available case vs Round 3 B1's
|
|
operation-slot-contention case where TryGetPendingContinuationPlacement
|
|
returns false) and the correct caller action for each.
|
|
|
|
### R4-15 (register-rows-draft.md rewrite). FIXED - scratchpad-only, no
|
|
### docs/ edits.
|
|
Row A: dropped the MoveOrTeleport co-anchor (confirmed retail-notes.md
|
|
never decompiled MoveOrTeleport's own internals; the ONLY confirmed
|
|
HasAnims call site is inside HandleReceivedPosition itself, line ~92992),
|
|
reworded "running cycle" -> "non-empty animation queue (anim_list.head_ !=
|
|
0)", updated the divergence formula text to match R4-13's new fallback.
|
|
Row C: broadened from "the 3 no-placement routes" to "NO route runs a live
|
|
ConstrainTo, including SetPosition routes" per the directive, citing the
|
|
3 ConstrainTo call sites at retail-notes.md lines ~93007/~93024/~93041.
|
|
Row D: rewrote across the THREE ApplyResidentCellCleanup branches -
|
|
claimed+celless+undeferred is now correctly described as a typed
|
|
ABANDONMENT (not "a recorded fact" - that was wrong even before Round 4,
|
|
since Round 3 B1 already changed this to Abandon; the register draft just
|
|
hadn't caught up), the deferred flavor correctly delegates retail's
|
|
AddObjectToBeDestroyed (93933) to the lost-cell/deferred owners, and the
|
|
claimedCell==0 branch is explicitly marked NOT a divergence (structural
|
|
shape guarantee, nothing to diverge from). Row E: softened the "no retail
|
|
per-step notice" claim - retail DOES emit exactly ONE notice
|
|
(ECM_Physics::SendNotice_CreateObject, ACCObjectMaint::CreateObject
|
|
0x00558870 step 11) per Create, just not N per-internal-step; kept the
|
|
consumer-facing risk warning unchanged. Row F: REMOVED from the register
|
|
draft entirely (internal refactor debt between two not-yet-unified
|
|
Position-apply paths, not a retail divergence) - replaced with (a) a code
|
|
comment on InboundPhysicsStateController.TryApplyPosition's doc comment and
|
|
(b) a new "ISSUES draft" section in the scratchpad file for eventual
|
|
docs/ISSUES.md inclusion. Added Row G (new): the executor's canonical-cell
|
|
semantics (refreshPosition: false), previously only a code comment, now
|
|
also a proper register row citing the classifier comment + retail
|
|
HandleReceivedPosition-never-writes-a-resident-cell fact.
|
|
|
|
## ROUND 4 FINAL GATES: see final report for exact totals.
|
|
|
|
---
|
|
|
|
# CUTOVER SLICE C0 — Runtime bridge + live inputs (new implementer session)
|
|
|
|
Worked from c0-contract.md (pinned), 2026-08-02-placement-cutover.md,
|
|
2026-08-02-cutover-route-inventory.md, 2026-08-02-runtime-continuation-
|
|
executor-handoff.md. HEAD at start 27e05b99.
|
|
|
|
## Discovery that changed my design: the observer/sink pipe already exists
|
|
Before designing C0-1, grepped for `IRuntimePlacementObserver`/
|
|
`IRuntimePlacementProjectionSink` production usage - found
|
|
`RuntimePlacementProjectionSubscription` (Runtime-owned,
|
|
`IRuntimePlacementObserver`), `RuntimePlacementPresentationSink` (App),
|
|
`HeadlessRuntimePlacementProjectionSink` (Headless) ALL already exist and
|
|
are production-wired (`GraphicalSessionEventRoute`/`HeadlessSessionHost`).
|
|
The cutover-route-inventory.md's "zero production IRuntimePlacementObserver"
|
|
claim is stale - superseded by a slice landed after that doc. What's still
|
|
genuinely dormant: nothing ever PUBLISHES into the channel in production,
|
|
because `Execute`/`RegisterEntityWithInitialResidence` still have zero
|
|
production callers. This meant C0-1 could NOT touch App/Headless sink
|
|
implementations (hard rule anyway) - the new `ExecutorCompleted` Kind will
|
|
sit unhandled by those sinks (return false) until a LATER cutover slice
|
|
updates them, but since Execute has no production caller today this never
|
|
fires in production. Documented this explicitly in code comments.
|
|
|
|
## C0-1: executor → placement-channel completion bridge. DONE.
|
|
Design (pinned contract's suggested shape, exactly): extended the
|
|
vocabulary, not the plumbing. New `RuntimePlacementProjectionKind.
|
|
ExecutorCompleted` (append-only, no exhaustive-switch breaks found anywhere
|
|
in src/tests via grep). New `RuntimeSetPositionState.PublishExecutorCompletion
|
|
(record, portal=default)`: builds a FRESH token via the SAME
|
|
`_nextProjectionSequence` counter every other publish uses (preserves
|
|
temporal/exact-head ordering), but derived from the CANONICAL RECORD's
|
|
current authority/version/cell facts (`_entities.SessionLifetimeVersion`,
|
|
`record.FullCellId`, `_physics.ExpectedCollisionGeneration(record.FullCellId)`)
|
|
rather than an Operation snapshot - correct because by the time Execute
|
|
reaches `Released`, the residence's/continuation's own operation is already
|
|
gone (adopted/acknowledged earlier in the SAME drain). `AcknowledgeProjection`
|
|
gained one extra Kind in its existing `Discard`-only fast path
|
|
(`Discard or ExecutorCompleted` -> remove + retire quiescence, no Operation
|
|
lookup) - there is no operation backing an ExecutorCompleted receipt, exactly
|
|
like Discard.
|
|
Correlation (the "reachable from/correlated with" ask): did NOT put the rich
|
|
internal `RuntimeInitialCreateExecutionReceipt` on the PUBLIC
|
|
`RuntimePlacementProjectionSnapshot` (would need public exposure of an
|
|
entire internal enum/struct family, or risk CS0053 inconsistent-accessibility
|
|
if done wrong) - instead the executor keeps a private
|
|
`Dictionary<RuntimeEntityKey, (ulong Sequence, Receipt)>` overwritten
|
|
per-key on each completion (bounded by live entity count, never
|
|
accumulates), queried via `TryGetCompletionReceipt(in RuntimePlacementProjectionToken)`
|
|
which verifies BOTH Entity and Sequence match before returning true - a
|
|
host/test correlates purely through the PUBLIC token identity every other
|
|
Kind already uses. The executor calls `PublishExecutorCompletion` exactly
|
|
once, at `ExecuteCore`'s `Released` exit (canonical still provably current
|
|
there).
|
|
Tests: `PublishExecutorCompletion_PublishesAcknowledgeOnlyReceiptAndConverges`,
|
|
`PublishExecutorCompletion_RespectsExactHeadOrderingAcrossEntities`
|
|
(RuntimeSetPositionStateTests.cs, isolated unit level);
|
|
`ExecutorCompletion_PublishesOnTheSamePlacementStreamCorrelatedWithTheFullReceipt`,
|
|
`ExecutorCompletion_ObservedOnlyAfterAnyContinuationPlacementInFifoOrder`
|
|
(RuntimeInitialCreateContinuationExecutorTests.cs, full Execute-drain
|
|
integration, the second proving continuation-Place-then-ExecutorCompleted
|
|
FIFO ordering).
|
|
|
|
## C0-2: Runtime-side live inputs. DONE.
|
|
UsePositionFromServer: grepped named-retail decomp for
|
|
`CommandInterpreter::UsePositionFromServer`/`SetAutonomyLevel`/
|
|
`autonomy_level` - found `result = this->autonomy_level != 2` (pseudo-C
|
|
699510), default `autonomy_level = 2` at construction (699752) AND at
|
|
`command_line_autonomy_level` (1088429, itself `0x2` by default) - autonomy
|
|
is a STARTUP/command-line-only knob in retail; no in-game caller of
|
|
SetAutonomyLevel exists anywhere in the decomp. Added the exact mirror to
|
|
`RuntimeCharacterState` (the "character-option owner" the contract named):
|
|
`FullAutonomyLevel=2u` const, `AutonomyLevel` (Volatile-read uint,
|
|
default 2), `UsePositionFromServer => AutonomyLevel != FullAutonomyLevel`,
|
|
`TrySetAutonomyLevel(level)` (rejects >2, exact retail rule). Reset in both
|
|
`ResetSession`/`Dispose`; added `AutonomyIsDefault` to
|
|
`RuntimeCharacterOwnershipSnapshot`/`IsConverged`.
|
|
PlayerDistance: grepped `LiveEntityNetworkUpdateController.cs` (App,
|
|
read-only) for the legacy remote path's own distance basis (cutover-routes.md
|
|
route 4) - confirmed `Vector3.Distance(worldPos, localPlayerPos)` where
|
|
`localPlayerPos = _playerController?.Position ?? Vector3.Zero` (the live
|
|
PHYSICS-CONTROLLER position, never a record snapshot). Bound source:
|
|
`RuntimeLocalPlayerMovementState.Controller?.Position ?? Vector3.Zero` -
|
|
same `PlayerMovementController` type.
|
|
Executor: `RuntimeInitialCreateContinuationExecutor.BindLiveInputs(Func<bool>,
|
|
Func<Vector3>)` (nullable seams, throws on double-bind matching
|
|
`BindGeneration`'s convention), `ResolveInputs(canonical, inputs)` computes
|
|
the EFFECTIVE `RuntimeInitialCreateExecutionInputs` ONCE per `Execute` call
|
|
(bound source wins; unbound falls back to the caller struct field-by-field) -
|
|
PlayerDistance uses THIS entity's own currently-accepted position
|
|
(`Snapshot.Physics?.Position ?? Snapshot.Position`, the same field
|
|
`CanonicalSetupTableId`-adjacent code already trusts) vs the bound live
|
|
player position. Documented the one-shot-per-Execute-call granularity as
|
|
inherited from the EXISTING `inputs` parameter shape, not a new limitation
|
|
I introduced - out of C0-2's scope to refine to per-continuation freshness.
|
|
`RuntimeEntityObjectLifetime.BindLiveInputs` forwards to the executor
|
|
(mirrors `BindEventContext`'s existing fan-out shape). `GameRuntime.cs` wires
|
|
the REAL sources right after `BindEventContext`, since `RuntimeCharacterState`/
|
|
`RuntimeLocalPlayerMovementState` are constructed AFTER `RuntimeEntityObjectLifetime`
|
|
in `GameRuntime`'s own sequence (verified exact construction order first).
|
|
Tests: `RuntimeCharacterStateTests.cs` (`AutonomyLevel_DefaultsToFullAndMirrorsRetailUsePositionFromServer`,
|
|
`ResetSession_RestoresAutonomyLevelToFull`);
|
|
`RuntimeInitialCreateContinuationExecutorTests.cs`
|
|
(`BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct`
|
|
- proves bound-wins AND live-read-not-cached-at-bind-time by flipping the
|
|
captured bool between two entities' drains;
|
|
`BindLiveInputs_ThrowsOnASecondBindAndUnboundExecutorsUseTheCallerStructUnchanged`).
|
|
|
|
## C0-3: exact-Setup mover chain end-to-end. DONE.
|
|
New `RuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement(record,
|
|
token, operationKind, flags, IPreparedCollisionSource, gameTime, out outcome,
|
|
placementClass=Ordinary, portal=default, ...scatter/shadow-offset params)`:
|
|
reads the CANONICAL Setup table id via the EXISTING private
|
|
`CanonicalSetupTableId(record)` (same field `CapturePreparationAuthority`
|
|
already trusts - never a caller-supplied id), takes the retail "genuine no
|
|
Setup" dummy path (`RuntimeSetPositionMoverSetup.ResolvedAbsent`) when that id
|
|
is 0, else calls `collisionSource.ReadSetupCollision(setupTableId)` and maps
|
|
Missing/Corrupt -> `RetrySetupUnavailable` (per `RuntimeSetPositionMoverSetup`'s
|
|
own doc-comment distinction between "not arrived yet" and "resolved absent" -
|
|
never manufactures a fallback while a real read is in flight) or Loaded ->
|
|
`RuntimeSetPositionMoverSetup.Resolved(id, data)`, then chains straight into
|
|
the EXISTING `PrepareMover` -> `SubmitPreparedPlacement`. Pure wiring - zero
|
|
changes to `PrepareMover`/`RuntimeSetPositionMoverPreparer.TryBuild`/
|
|
`SubmitPreparedPlacement`'s own validation semantics (per the contract's
|
|
explicit "wiring, not behavior change" constraint) - confirmed by re-reading
|
|
both untouched.
|
|
Tests (RuntimeSetPositionStateTests.cs, new `FakeCollisionSource :
|
|
IPreparedCollisionSource` test double, only `ReadSetupCollision` implemented
|
|
- others throw `NotSupportedException` since C0-3 exercises only that one):
|
|
`TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit`
|
|
(authored two-sphere Setup reaches `SubmitPreparedPlacement` and
|
|
`TryGetPreparedMoverSphereCount` byte-exactly == 2, matching the existing
|
|
preparer tests' own expectations) and
|
|
`TryPrepareAndSubmitAuthoredPlacement_YieldsRetryOnAMissingSetupReadWithoutMutatingStage`
|
|
(Missing status -> `RetrySetupUnavailable`, operation stays
|
|
`AwaitingPreparation`/`IsPlacementCurrent` true, no prepared-mover sphere
|
|
count recorded - genuinely retryable, not a dead token).
|
|
|
|
## C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries. DONE,
|
|
## both confirmed at source exactly as the inventory claimed.
|
|
(a) `RuntimeEntityObjectLifetime.TryCommitParent` (944-974 pre-fix) had
|
|
NEITHER `ForgetInitialCreateResidence` NOR `Physics.SetPosition.Forget` -
|
|
confirmed by direct read, contrasted against the sibling
|
|
`CommitPositionChannelUpdate` (used by `TryApplyParent`/`TryApplyCreateParent`)
|
|
which has BOTH. Fixed: added the identical
|
|
`ForgetInitialCreateResidence` -> `Physics.SetPosition.Forget` ->
|
|
`PreferCancellation` -> pass to `AcknowledgeProjectionAndPublish` sequence.
|
|
Deliberately did NOT add `Physics.CollisionReports.LeaveWorld` (present in
|
|
`CommitPositionChannelUpdate` but outside the contract's explicit
|
|
"residence/placement-family cancellation" scope, and I have no retail
|
|
citation that a STAGED parent-attach commit should also force a collision
|
|
leave-world at this exact point) - flagging this as a considered, deliberate
|
|
non-addition rather than an oversight.
|
|
(b) `CommitWithdrawal` (1401-1422 pre-fix) called `ForgetInitialCreateResidence`
|
|
but not `Physics.SetPosition.Forget` - confirmed by direct read, contrasted
|
|
against `TryApplyPickup`/`CommitAcceptedParentCellless`/`TryAcceptDelete`
|
|
which all cancel both. Fixed symmetrically (added the ordinary Forget +
|
|
PreferCancellation into the existing `cancellation` variable already threaded
|
|
to `AcknowledgeProjectionAndPublish`).
|
|
Tests, three total, each begins an ACTUAL in-flight SetPosition operation
|
|
that has already reached `SubmitPreparedPlacement`'s pending-Place stage (a
|
|
still-`AwaitingPreparation`, never-submitted operation produces NO Discard
|
|
receipt at all when cancelled - `CancelCoreDeferred` only converts an
|
|
EXISTING pending projection into a Discard; there is nothing to discard if
|
|
nothing was ever published - this cost one debugging round, see below):
|
|
`RuntimeInitialCreateResidenceStateTests.
|
|
TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement`
|
|
(residence's OWN placement, still unacknowledged, is the thing cancelled -
|
|
both Forget calls fire but only one finds anything, `PreferCancellation`
|
|
picks it, exactly one Discard observed); `RuntimeSetPositionStateTests.
|
|
TryCommitParent_CancelsASeparateActiveOrdinaryPendingPlacement` and
|
|
`CommitWithdrawal_CancelsAnActiveOrdinaryPendingPlacementSymmetricallyWithPickup`
|
|
(plain `RegisterEntity`, no residence at all - isolates the SECOND,
|
|
previously-missing Forget call specifically). All three assert the Discard
|
|
sits at the SAME sequence as the original Place (Revision bumped), then
|
|
explicitly acknowledge it and assert `PendingProjectionCount == 0` - a
|
|
cancelled-but-unacknowledged receipt stays IN the pending set (replaced, not
|
|
removed) until a host consumes it, same as every other in-flight-cancel path
|
|
in this codebase.
|
|
|
|
## Debugging round (all 4 caught by the focused-filter run, all root-caused
|
|
## and fixed, not worked around):
|
|
1. Three C0-4 tests initially asserted a Discard would be published from
|
|
cancelling a placement operation still in `AwaitingPreparation`
|
|
(never submitted) - traced `CancelCoreDeferred` and confirmed it only
|
|
converts an EXISTING `_pendingProjection` entry to Discard
|
|
(`operation.ProjectionSequence != 0UL` gate); an unpublished operation
|
|
just disappears from `_operations` with no receipt, which is CORRECT
|
|
(nothing was ever promised to a host). Fixed the TESTS to reach
|
|
`SubmitPreparedPlacement`'s pending-ack stage first, not the production
|
|
code.
|
|
2. Two of those same tests then asserted `PendingProjectionCount == 0`
|
|
immediately after cancellation - wrong; a Discard REPLACES the pending
|
|
entry at the same sequence (Revision+1), it does not remove it. Fixed the
|
|
assertions to expect 1, then explicitly acknowledge, then expect 0.
|
|
3. `TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement`'s
|
|
`Prepare(..., RuntimeSetPositionMoverSetup.ResolvedAbsent)` failed with
|
|
`InvalidData` because `Spawn(guid, 1)` in that file defaults
|
|
`setupId: 0x02000001u` (nonzero), mismatching `ResolvedAbsent`'s claimed
|
|
"no Setup at all". Fixed by passing `setupId: null` explicitly (matching
|
|
the file's OWN existing convention for this exact scenario, e.g.
|
|
`ResetSnapshotsAllResidenceOwnersBeforeReentrantDiscardObserver`).
|
|
4. `BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct`
|
|
failed `AcknowledgeProjection` on a SECOND entity's placement - not exact
|
|
head. Root cause: the FIRST entity's full drain published its own
|
|
`ExecutorCompleted` receipt (C0-1) which I never acknowledged before
|
|
moving on to the second entity - this is CORRECT exact-head behavior
|
|
(great incidental proof C0-1's ordering guarantee holds), not a bug.
|
|
Fixed the test to peek+acknowledge the first completion before
|
|
proceeding.
|
|
|
|
## Final gates (all green)
|
|
1. `dotnet build AcDream.slnx -c Release`: 0 errors, 21 warnings (all
|
|
pre-existing, identical set to the executor handoff's baseline - zero
|
|
new warnings from this slice).
|
|
2. Focused filter (RuntimeInitialCreateContinuationExecutorTests|
|
|
RuntimeInitialCreateResidenceStateTests|RuntimeSetPositionStateTests|
|
|
RuntimePlacementProjectionSubscriptionTests|RuntimeCharacterStateTests|
|
|
RuntimeEntityObjectLifetimeTests): 247/247 passed.
|
|
3. Complete `AcDream.Runtime.Tests`: 916/916 passed (903 baseline + 13 new:
|
|
2 C0-1 unit + 2 C0-1 integration + 2 C0-2 executor + 2 C0-2 character-state
|
|
+ 2 C0-3 + 1 C0-4 residence + 2 C0-4 set-position-state).
|
|
4. Complete solution (`dotnet test AcDream.slnx -c Release -m:1`): every
|
|
project passed - App 4027/3 skip, Bake 15, Cli 4, Content 124, Core.Net
|
|
762, Core 4242/1 skip, Headless 76, Runtime 916, UI.Abstractions 543.
|
|
0 failed anywhere.
|
|
5. `git diff --check`: clean (only pre-existing LF-will-become-CRLF
|
|
metadata notices, no whitespace-error content).
|
|
6. `git status`: exactly the 9 files this slice touched, plus the 8
|
|
pre-existing protected dirty paths untouched (never staged/committed).
|
|
|
|
## Files changed (Runtime + Runtime.Tests only, no App/Headless production,
|
|
## no staging/commits)
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (C0-1 Kind+publish+
|
|
ack branch; C0-3 chain method; +`using AcDream.Content;`)
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
(C0-1 completion-receipt correlation + publish call site; C0-2
|
|
BindLiveInputs/ResolveInputs)
|
|
- src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (C0-2
|
|
BindLiveInputs forwarder; C0-4 TryCommitParent/CommitWithdrawal fixes;
|
|
+`using System.Numerics;`)
|
|
- src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs (C0-2 autonomy
|
|
level/UsePositionFromServer)
|
|
- src/AcDream.Runtime/GameRuntime.cs (C0-2 wiring the real sources;
|
|
+`using System.Numerics;`)
|
|
- tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (C0-1,
|
|
C0-3, C0-4 tests + FakeCollisionSource; +`using AcDream.Content;`/
|
|
`AcDream.Content.Pak;`)
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
(C0-1, C0-2 tests + PlacementObserver fake)
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs
|
|
(C0-4 residence-side test)
|
|
- tests/AcDream.Runtime.Tests/Gameplay/RuntimeCharacterStateTests.cs (C0-2
|
|
autonomy tests)
|
|
|
|
## Still dormant / no production caller flipped (per pinned scope)
|
|
Nothing in App/Headless was touched; `Execute`/`RegisterEntityWithInitialResidence`
|
|
still have zero production callers (unchanged from the executor handoff).
|
|
`RuntimePlacementPresentationSink`/`HeadlessRuntimePlacementProjectionSink`
|
|
will need an `ExecutorCompleted`-handling branch added when a LATER cutover
|
|
slice (C1+) actually starts calling `Execute` in production - flagging this
|
|
explicitly as the next slice's concern, not a gap in C0.
|
|
|
|
**SUPERSEDED by the review fix round below**: the App/Headless sink
|
|
untouched-claim above no longer holds - F1 sanctioned a scoped exception
|
|
(exactly 3 sink files). See the fix-round section for the full disposition.
|
|
|
|
---
|
|
|
|
# C0 REVIEW FIX ROUND (F1-F5)
|
|
|
|
Both independent reviews returned FAIL with converging findings. Per the
|
|
coordinator: all five retail semantic questions verified CLEAN (autonomy_level
|
|
!= 2 derivation exact; distance basis matches retail + legacy path fallback;
|
|
mover chain preserves prerequisite-B exactness; ExecutorCompleted
|
|
unambiguously acknowledge-only; the LeaveWorld omission in TryCommitParent is
|
|
REQUIRED per retail set_parent 0x00515A90:283832-283833's single gated
|
|
leave_world - a second one would double-leave-world with no retail
|
|
counterpart).
|
|
|
|
## F1 (MAJOR, arch) - sink acknowledge-and-ignore. FIXED. SCOPE EXPANSION
|
|
## SANCTIONED for exactly 3 files, no route flips, no other App/Headless changes.
|
|
Root cause: `HeadlessRuntimePlacementProjectionSink.cs:51` (`is not Place ->
|
|
false`), `RuntimePlacementPresentationSink.cs:89-96` (`_ -> false`),
|
|
`LiveEntityRuntime.cs:969-976` (`_ -> false`) all silently reject
|
|
ExecutorCompleted, and `RuntimePlacementProjectionSubscription` treats a
|
|
false return on the FIFO head as "leave pending" - the first ExecutorCompleted
|
|
reaching a production sink (at a future cutover slice) would permanently wedge
|
|
the entire ordered placement stream behind it. Fixed all three: added an
|
|
explicit `if (Kind is Discard or ExecutorCompleted) return true;`-shaped
|
|
early return BEFORE each file's record-lookup/portal-shape gate (never
|
|
letting ExecutorCompleted depend on a lookup that can legitimately fail for
|
|
unrelated reasons). Provably inert today - `PublishExecutorCompletion` has
|
|
zero production callers (`Execute`/`RegisterEntityWithInitialResidence` are
|
|
both unreached) - documented in both the code comments and the new tests.
|
|
Tests: `RuntimePlacementPresentationSinkTests.ExecutorCompleted_IsAckOnlyNoOpEvenWhenTokenIsStaleOrSidecarIsGone`
|
|
(App, mirrors the existing Discard test exactly, proves ack regardless of a
|
|
completely bogus/stale token) and
|
|
`HeadlessSessionHostTests.ExecutorCompletedReceiptIsAcknowledgeOnlyRegardlessOfRecordValidity`
|
|
(Headless, mirrors `PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly`'s
|
|
stale-incarnation half).
|
|
|
|
## F2 (MAJOR, both reviewers) - completion-receipt lifecycle. FIXED.
|
|
Four sub-fixes, all landed:
|
|
1. **Register-before-publish**: `PublishExecutorCompletion` gained a
|
|
`beforePublish: Action<RuntimePlacementProjectionToken>?` callback invoked
|
|
AFTER the token is added to `_pendingProjection` but BEFORE
|
|
`PublishPlacement`'s synchronous observer dispatch. The executor's
|
|
`ExecuteCore` Released case now registers `_completionReceipts[key]`
|
|
inside that callback (capturing `receipt` via a local `completedReceipt`
|
|
- an `out` parameter cannot be captured by a lambda) - a subscriber
|
|
reading the correlation back from inside its OWN `OnPlacement` now always
|
|
finds it.
|
|
2. **Ack-driven removal**: new `RuntimeSetPositionState.BindExecutorCompletionAcknowledgement(Action<RuntimeEntityKey,ulong>)`
|
|
(mirrors `RuntimeInitialCreateResidenceState.BindRetirementNotification`'s
|
|
existing one-bound-delegate shape), invoked from `AcknowledgeProjection`'s
|
|
ExecutorCompleted branch the moment a host acknowledges. Bound in all 3
|
|
`RuntimeEntityObjectLifetime` constructors to
|
|
`InitialCreateExecution.ForgetCompletionReceipt(key, sequence)` - a new
|
|
executor method that removes exactly the matching (key, sequence) entry
|
|
(exact-sequence-checked, so a NEWER completion under a reused key survives).
|
|
3. **DiscardProgress/DiscardAll**: both now unconditionally reap
|
|
`_completionReceipts` (Remove/Clear respectively) - DiscardProgress
|
|
removes it EVEN WHEN `_progress` no longer tracks the key (the drain
|
|
already removed its own Progress entry before publishing the completion),
|
|
proven by a dedicated test.
|
|
4. **Ownership/convergence**: added `PendingCompletionReceiptCount` to the
|
|
executor and folded it into
|
|
`RuntimeEntityObjectOwnershipSnapshot`/`IsConverged` (appended as the last
|
|
positional field with a `= 0` default, following this record's own
|
|
established extension convention) - chosen semantics: non-zero while
|
|
unacknowledged, zero exactly at acknowledge, mirroring
|
|
`RuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount`'s
|
|
existing "unacknowledged receipt is outstanding debt, gated by
|
|
IsConverged" shape (documented as such, in contrast with the adjacent
|
|
diagnostic-only `ReplayFailureCount`).
|
|
Tests (`RuntimeInitialCreateContinuationExecutorTests.cs`):
|
|
`ExecutorCompletion_ReceiptIsReadableFromWithinTheSameSynchronousOnPlacementDispatch`,
|
|
`ExecutorCompletion_ConvergenceLedgerCountsAnUnacknowledgedReceiptAsOutstandingDebtUntilAcknowledged`,
|
|
`ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgress`,
|
|
`ExecutorCompletion_CorrelationEntryIsReapedByDiscardAll` (the latter two
|
|
split into single-entity tests after discovering combining them with a
|
|
second entity in the SAME lifetime hit exact-head contention - see debugging
|
|
notes below).
|
|
|
|
## F3 (MAJOR, arch) - nullable local-player-position fallback. FIXED.
|
|
`GameRuntime.cs:280`'s `context.Movement.Controller?.Position ?? Vector3.Zero`
|
|
fabricated a distance basis of literal (0,0,0) whenever the login-window
|
|
drain ran before the local player's own controller existed - a nearby remote
|
|
entity could misclassify as >96m and hard-snap where retail would
|
|
interpolate. Fixed per the contract's own fallback rule: `_localPlayerPosition`
|
|
is now `Func<Vector3?>?` (was `Func<Vevtor3>?`), `BindLiveInputs`'s parameter
|
|
type updated to match, `ResolveInputs` now does
|
|
`_localPlayerPosition?.Invoke() is { } localPlayerPosition` (both "unbound"
|
|
AND "bound-but-returns-null" fall back to the caller struct's PlayerDistance
|
|
identically), and `GameRuntime.cs` now binds
|
|
`() => context.Movement.Controller?.Position` directly (a `PlayerMovementController?.Position`
|
|
already yields `Vector3?` via null-conditional propagation - no `?? Vector3.Zero`
|
|
needed or wanted). Test (both directions, per the ask):
|
|
`BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull` -
|
|
entity 1 has a bound NON-null near position (proves the bound value, not the
|
|
caller struct's far value, wins -> Interpolate); entity 2 flips the SAME
|
|
bound source to null (proves it falls back to the caller struct's far value,
|
|
not Vector3.Zero -> SetPositionSimple/StopInterpolating).
|
|
|
|
## F4 (MINOR) - TryCommitParent comment narrowed. FIXED.
|
|
Rewrote the C0-4(a) comment: no longer claims "the SAME flow every sibling
|
|
relation commit uses" wholesale (which would imply LeaveWorld too) - now
|
|
states the Forget/PreferCancellation sequence is shared for THIS part of the
|
|
job, and explicitly documents the deliberate LeaveWorld omission citing
|
|
retail `set_parent` (0x00515A90, lines 283832-283833)'s single gated
|
|
leave_world call, which this method's staged/deferred-replay commit already
|
|
represents - a second LeaveWorld here would double-leave-world with no
|
|
retail counterpart.
|
|
|
|
## F5 (NOTEs). FIXED.
|
|
(a) `TrySetAutonomyLevel`'s doc comment now notes retail's setter ALSO sends
|
|
`SendAutonomyLevelEvent` (pseudo-C 699550) and that any FUTURE host exposure
|
|
of this setter must carry the equivalent outbound event, not just the field
|
|
write.
|
|
(b) `FullAutonomyLevel`'s doc comment corrected from "No in-game caller of
|
|
CommandInterpreter::SetAutonomyLevel exists" (implying zero callers anywhere)
|
|
to the precise claim: exactly ONE retail caller exists, the startup
|
|
construction path at pseudo-C 94102 (the constructor's own default at 699752
|
|
is a direct field write, not a SetAutonomyLevel call, so it doesn't count as
|
|
a second caller).
|
|
|
|
## Debugging round for the F2/F3 tests (both caught by the test run, both
|
|
## root-caused, not worked around)
|
|
1. `ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgressAndDiscardAll`
|
|
(combined, single lifetime, two entities) failed at the SECOND entity's
|
|
`CompleteInitialPlacement` - root cause: the FIRST entity's
|
|
ExecutorCompleted receipt was left UNACKNOWLEDGED in `_pendingProjection`
|
|
(by design, to prove DiscardProgress reaps the correlation cache
|
|
independent of the normal ack path) - but that means it permanently sat
|
|
at the exact head, blocking ANY later entity's Place receipt from ever
|
|
being acknowledged (DiscardProgress only touches the correlation cache,
|
|
never `_pendingProjection` itself - a deliberate, narrow scope). Fixed by
|
|
splitting into two single-entity tests (`...ReapedByDiscardProgress`,
|
|
`...ReapedByDiscardAll`), each with its own fresh lifetime - eliminates
|
|
the exact-head contention entirely rather than working around it.
|
|
2. `BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull`
|
|
hit the SAME class of bug for the SAME reason (entity 1's completed drain
|
|
left an unacknowledged ExecutorCompleted blocking entity 2). Fixed by
|
|
inserting an explicit peek+acknowledge of entity 1's completion between
|
|
the two entities (matching the pattern already established in
|
|
`BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct`
|
|
from the prior round).
|
|
|
|
## Fix-round final gates (all green)
|
|
1. `dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release`: 0/0.
|
|
2. `dotnet build tests/AcDream.Runtime.Tests -c Release`: 0/0.
|
|
3. Complete `AcDream.Runtime.Tests`: 921/921 passed (916 + 5 new: 4 F2 + 1 F3;
|
|
see above for exact names).
|
|
4. `dotnet build tests/AcDream.App.Tests -c Release`: 0 errors, 3 pre-existing
|
|
warnings (CS8767, unrelated to this change).
|
|
5. `dotnet build tests/AcDream.Headless.Tests -c Release`: 0/0.
|
|
6. `dotnet test tests/AcDream.App.Tests -c Release --no-build`: 4028/4028
|
|
passed, 3 skips (4027 baseline + 1 new: the F1 App-sink test).
|
|
7. `dotnet test tests/AcDream.Headless.Tests -c Release --no-build`: 77/77
|
|
passed (76 baseline + 1 new: the F1 Headless-sink test).
|
|
8. `dotnet build AcDream.slnx -c Release --no-incremental` (clean rebuild for
|
|
an authoritative count): 0 errors, EXACTLY 21 warnings (matching the
|
|
documented baseline precisely - zero new warnings across the whole fix
|
|
round, including the 3 sanctioned sink-file edits).
|
|
9. Complete solution (`dotnet test AcDream.slnx -c Release -m:1`): every
|
|
project green - App 4028/3 skip, Bake 15, Cli 4, Content 124, Core.Net
|
|
762, Core 4242/1 skip, Headless 77, Runtime 921, UI.Abstractions 543. 0
|
|
failed anywhere.
|
|
10. `git diff --check`: clean (only pre-existing LF/CRLF metadata notices;
|
|
confirmed by grepping the raw output for anything OTHER than that
|
|
pattern - zero matches).
|
|
11. `git status`: exactly the C0 file set plus 5 NEW files from this fix
|
|
round (the 3 sanctioned sink files + their 2 test files), plus the 8
|
|
pre-existing protected dirty paths untouched. Nothing staged.
|
|
|
|
## Files touched in THIS fix round (in addition to the C0 file set above)
|
|
- src/AcDream.App/World/LiveEntityRuntime.cs (F1)
|
|
- src/AcDream.App/World/RuntimePlacementPresentationSink.cs (F1)
|
|
- src/AcDream.Headless/Hosting/HeadlessRuntimePlacementProjectionSink.cs (F1)
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (F2: beforePublish
|
|
param + BindExecutorCompletionAcknowledgement + ack-branch notification)
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
(F2: ForgetCompletionReceipt/PendingCompletionReceiptCount/DiscardProgress+
|
|
DiscardAll reap/register-before-publish call site; F3: nullable field/
|
|
ResolveInputs)
|
|
- src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (F2: ownership
|
|
snapshot field + CaptureOwnership wiring + acknowledgement binding; F3:
|
|
BindLiveInputs signature; F4: comment)
|
|
- src/AcDream.Runtime/GameRuntime.cs (F3: nullable binding call site)
|
|
- src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs (F5a/F5b: comments)
|
|
- tests/AcDream.App.Tests/World/RuntimePlacementPresentationSinkTests.cs (F1
|
|
test)
|
|
- tests/AcDream.Headless.Tests/HeadlessSessionHostTests.cs (F1 test)
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
(F2 x4 + F3 x1 tests)
|
|
|
|
---
|
|
|
|
# C2 — placement allocation budget (new implementer session)
|
|
|
|
Worktree: C:\Users\erikn\.codex\worktrees\af5e\acdream (branch codex/port-claude-agents)
|
|
HEAD verified at session start: 6460596b56cd72a2c6d96e757b33da879a805b6d
|
|
|
|
## Baseline reproduction
|
|
|
|
- `dotnet build AcDream.slnx -c Release` green, 21 pre-existing warnings in
|
|
unrelated files (not introduced by this session).
|
|
- Ran `WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover`
|
|
(tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs:253).
|
|
Baseline via temporary forced-failure instrumentation (reverted before any
|
|
real edits; git diff was clean after revert): **2032 B/op**, stable across
|
|
3 repeat runs. NOT 1880 as the stale research doc
|
|
(docs/research/2026-07-31-canonical-set-position.md lines ~326-333)
|
|
states -- the number drifted upward since C0 landed additional
|
|
bookkeeping (ExecutorCompleted receipt plumbing etc). This is already
|
|
within 16 bytes of tripping the 2048 cap on its own -- confirms urgency.
|
|
|
|
## Bisection (temporary instrumentation, reverted before real edits)
|
|
|
|
Added static accumulator fields + GC.GetAllocatedBytesForCurrentThread()
|
|
brackets around: Apply() -> BeginAcceptedPlacementCore (BEGIN) and
|
|
SubmitPreparedPlacementCore (SUBMIT); inside SubmitPreparedPlacementCore:
|
|
prefix-to-SetPosition-call (PREFIX), the `_physics.Engine.SetPosition(...)`
|
|
call itself (SETPOSITION), CommitCanonical (COMMIT), PublishProjection
|
|
(PUBLISH), tail/Outcome (TAIL); AcknowledgeProjection (ACK, wrapped core in
|
|
try/finally).
|
|
|
|
Result (per-op, averaged over 1000 measured iterations, 64 warmups):
|
|
```
|
|
TOTAL=2032 BEGIN=752 SUBMIT=1160 SETPOSITION=584 COMMIT=0 PUBLISH=208 ACK=120
|
|
PREFIX=144 MID=0 TAIL=0
|
|
```
|
|
Outer brackets are self-consistent to the byte: BEGIN+SUBMIT+ACK =
|
|
752+1160+120 = 2032 = TOTAL exactly. The inner SUBMIT subdivision only sums
|
|
to 936 (144+584+0+208), leaving ~224 B/op I could not pin down further
|
|
within budget (possibly SortedDictionary rebalancing spillover attributed
|
|
oddly across adjacent brackets, possibly a measurement-granularity artifact
|
|
of many small brackets in one method -- the coarse brackets are trustworthy,
|
|
the finest subdivision is not). Decided not to chase further since the two
|
|
big, well-understood root causes below match the project's established
|
|
"pool the envelope, cache the delegate" pattern and account for the
|
|
majority of the budget.
|
|
|
|
## Root causes identified (confirmed by direct code reading + the
|
|
bisection above)
|
|
|
|
1. **BEGIN (752 B, `BeginAcceptedPlacementCore`)**: `new Operation { ... }`
|
|
(private sealed class Operation, ~25 properties incl. embedded
|
|
RuntimeSetPositionCommand / PhysicsSetPositionResult structs) allocated
|
|
FRESH on every accepted-placement call; the old Operation for the same
|
|
entity key is simply dropped (`_operations[key] = replacement;`) and
|
|
becomes garbage every single call.
|
|
|
|
2. **SETPOSITION (584 B, two call sites: SubmitPreparedPlacementCore
|
|
~line 2402 and RetryDeferred ~line 3536)**:
|
|
`report => _physics.HandleSetPositionCollisions(operation.Record, ...,
|
|
canonicalCommand.GameTime, ...)` is a closure capturing `this` +
|
|
`operation` (+ `canonicalCommand` at the first site) -- a fresh
|
|
compiler-generated display-class allocated on EVERY call. Confirmed
|
|
every field the closure reads is already reachable from `operation`
|
|
alone (`operation.Command.GameTime == canonicalCommand.GameTime` because
|
|
`operation.Command = canonicalCommand;` runs earlier in the same
|
|
method) -- the closure captures nothing that isn't already sitting on
|
|
`operation`. Fixable with ONE delegate cached for the RuntimeSetPositionState
|
|
instance's lifetime, reading the "current operation" off a small
|
|
reusable Stack<Operation> pushed/popped around the SetPosition call
|
|
(defends against theoretical re-entrant/nested SetPosition calls inside
|
|
PhysicsEngine -- TransitionScratchArena's ActiveDepth/Capacity gate
|
|
implies nesting is possible at that layer, even though
|
|
HandleSetPositionCollisions itself never calls back into
|
|
RuntimeSetPositionState).
|
|
|
|
## Plan
|
|
1. Fix the closure (lowest risk, no behavior change) -- both call sites.
|
|
2. Pool the Operation object (`_operationPool`, convert `required {get;init;}`
|
|
to settable + add a `Reset(...)`, return retired Operations to the pool
|
|
at every site that currently discards one for good). Audit every
|
|
`_operations.Remove(...)` / displacement site so nothing else still
|
|
holds the recycled instance (per "no workarounds": a pool that
|
|
resurrects stale state is worse than the allocation it replaces).
|
|
3. Re-measure; tighten the gate to the new number with justified headroom.
|
|
|
|
## Fixes implemented
|
|
|
|
1. Cached collision-report delegate (fixes the closure at both
|
|
`_physics.Engine.SetPosition` call sites: SubmitPreparedPlacementCore and
|
|
RetryDeferred). Added `CollisionCallbackContext` (readonly record struct)
|
|
plus `Stack<CollisionCallbackContext> _collisionCallbackContexts` plus
|
|
`Func<PhysicsSetPositionCollisionReport, bool>
|
|
_handleSetPositionCollisionsCallback` (built ONCE in the constructor,
|
|
bound to instance method `HandleSetPositionCollisionsCallback` which
|
|
reads `_collisionCallbackContexts.Peek()`). Each call site now does
|
|
Push(context) / try { SetPosition(request, cached delegate) } / finally
|
|
{ Pop() }. The stack (not a single field) defends nested/re-entrant
|
|
SetPosition calls at the PhysicsEngine layer.
|
|
|
|
2. Operation pooling (fixes `new Operation` in
|
|
`BeginAcceptedPlacementCore`). Converted every `Operation` property from
|
|
`required ... { get; init; }` to plain `{ get; set; }`, added
|
|
`ResetAllFieldsToDefault()`, added `_operationPool` (`Stack<Operation>`,
|
|
capped at 64), `RentOperation()` / `RetireOperationToPool(Operation)`.
|
|
`BeginAcceptedPlacementCore` now rents+field-sets instead of
|
|
`new Operation {...}`. Operations are retired at the 3 places an
|
|
Operation is permanently removed from `_operations`: both branches inside
|
|
`AcknowledgeProjection` Place/non-lost-cell paths, and inside
|
|
`CancelCoreDeferred` (the single removal chokepoint every `CancelCore`
|
|
overload funnels through).
|
|
|
|
CRITICAL BUG FOUND AND FIXED during verification: the first pass reset
|
|
fields at RETIREMENT time (inside RetireOperationToPool). This broke
|
|
`ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation` -
|
|
`CommitCanonical` remote branch builds a `ContactCommitGuard` that
|
|
captures the live `Operation`, then invokes `remote.HitGround()` /
|
|
`LeaveGround()` - and retail lets that callback synchronously call BACK
|
|
INTO `BeginAcceptedPlacement` for the SAME entity, which displaces and
|
|
retires the very operation the guard is holding, mid-guard.
|
|
`guard.IsCurrent()` (`IsCanonicalPlacementCommitCurrent`) reads that
|
|
operation ORIGINAL PositionAuthorityVersion/SpatialAuthorityVersion AFTER
|
|
the callback returns - resetting those fields at retirement time zeroed
|
|
them out from under the still-executing outer frame, making an in-flight
|
|
valid commit look stale and silently dropping its shadow update (test
|
|
caught it: shadow.Position stayed at the pre-spawn 10,20,7 instead of the
|
|
committed 13,18,~6.95). Fix: move the reset from RetireOperationToPool to
|
|
RentOperation (reset happens the moment an instance is about to be
|
|
handed out for reuse, not the moment it is taken out of `_operations`).
|
|
A retired-but-not-yet-rented instance now keeps its true last-known field
|
|
values until something actually reuses it - any outer frame with a
|
|
captured reference gets a brief, safe, read-only window on stale-but-
|
|
correct data instead of zeroed garbage. Full 921/921 Runtime tests pass
|
|
after this fix (before the fix: 920/921, this exact test failing).
|
|
|
|
Lesson worth carrying into memory: reentrant displaced-operation pooling
|
|
must reset at RENT time, never at RETIREMENT time, whenever a captured
|
|
reference (guard/closure/local) might still read the object fields after
|
|
retirement but before the next real use.
|
|
|
|
3. Eliminated LINQ `.First()` boxing on `_pendingProjection`
|
|
(SortedDictionary<ulong, RuntimePlacementProjectionSnapshot>). Added
|
|
`FirstPendingProjection()` using a plain `foreach` (resolves to the
|
|
concrete struct-returning `GetEnumerator()`, not the boxing
|
|
`IEnumerable<T>` interface one that `Enumerable.First<T>`
|
|
forces). Replaced all 3 call sites (`TryPeekProjection`,
|
|
`AcknowledgeProjection` guard, `HasPendingProjectionThrough`).
|
|
|
|
## Root cause: SortedDictionary Add allocations (about 208 B/op) and
|
|
AcDream.Core PhysicsEngine internals (about 520 B/op) - NOT fixed
|
|
|
|
Confirmed via targeted diagnostic instrumentation (added, measured, fully
|
|
reverted before finalizing - git diff was clean after each revert):
|
|
- `_pendingProjection.Add(sequence, snapshot)` (PublishProjection) costs
|
|
about 208 B/op - SortedDictionary red-black tree node allocation, inherent
|
|
to the BCL type with no pooling hook. Replacing `_pendingProjection` data
|
|
structure to chase this would touch 10+ methods relying on its ordering
|
|
and FIFO-peek semantics (quiescence tracking, withdrawal acknowledgement,
|
|
etc.) - judged too invasive and risky for the remaining reward given the
|
|
result is already well under the 2048 cap.
|
|
- `_physics.Engine.SetPosition` (AcDream.Core/Physics/PhysicsEngine.cs)
|
|
costs about 520 B/op split as INIT=144 (InitializeSetPositionTransition),
|
|
INNER=344 (SetPositionInternal/scatter solve), FINAL=32 (the
|
|
`queryFootprint.OrderedIds.ToImmutableArray()` call - a genuine single-
|
|
element ImmutableArray materialization from the outdoor-adjustment query
|
|
footprint, not an artifact). RENT=0 (Transition pooling already zero-
|
|
alloc). This lives entirely inside AcDream.Core, shared physics
|
|
infrastructure used far beyond RuntimeSetPositionState - out of this
|
|
slice Runtime-only hard-rule scope, and not touched.
|
|
- Confirmed `HandleSetPositionCollisionReports` /
|
|
`RuntimeCollisionReportingState.HandleReports` (Runtime-side) do NOT
|
|
allocate in the test steady state (no collisions ever occur -
|
|
collidedObjectIds stays empty, no OwnerState ever gets created for this
|
|
entity) - ruled out as a contributor.
|
|
|
|
## Final verification
|
|
|
|
- `dotnet build AcDream.slnx -c Release`: 0 errors, 21 pre-existing warnings
|
|
(all in files untouched by this session - confirmed identical to the
|
|
pre-session baseline build).
|
|
- `WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover`: passes
|
|
at the new `Assert.InRange(allocated / iterations, 1L, 1_536L)` gate.
|
|
Measured value stable at exactly 944 B/op across 5+ repeat runs (down
|
|
from 2032 B/op baseline, 53.5% reduction). 1,536 keeps about 60% headroom.
|
|
- Complete `AcDream.Runtime.Tests`: 921/921 pass (0 skips).
|
|
- Complete solution (`dotnet test AcDream.slnx -c Release -m:1`): every
|
|
project green - App 4028/3 skip, Bake 15, Cli 4, Content 124, Core.Net
|
|
762, Core 4242/1 skip, Headless 77, Runtime 921, UI.Abstractions 543.
|
|
10,716 total, 0 failed, 4 skipped (all pre-existing skips).
|
|
- `git diff --check`: clean (only the same pre-existing LF/CRLF metadata
|
|
notices on the 8 protected dirty files from before this session; grepped
|
|
for anything else - zero matches).
|
|
- `git status`: exactly 2 files changed by this session
|
|
(src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs,
|
|
tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs) plus
|
|
the 8 pre-existing protected dirty paths, untouched, exactly as they were
|
|
at session start. Nothing staged, nothing committed, HEAD unchanged at
|
|
6460596b56cd72a2c6d96e757b33da879a805b6d.
|
|
|
|
## Files touched this session
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (net +340/-69 lines
|
|
vs the C0 baseline: Operation class made poolable plus
|
|
ResetAllFieldsToDefault, CollisionCallbackContext plus cached delegate
|
|
plus stack, RentOperation/RetireOperationToPool, FirstPendingProjection,
|
|
BeginAcceptedPlacementCore restructure, both SetPosition call sites, both
|
|
AcknowledgeProjection removal branches, CancelCoreDeferred retirement
|
|
call site)
|
|
- tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs
|
|
(26 lines: only the regression gate comment and threshold, from 2_048L to
|
|
1_536L - zero other test changes; the fix required no test edits beyond
|
|
the gate itself, confirming the pooling/delegate/LINQ changes are fully
|
|
behavior-preserving)
|
|
|
|
# C2 review-fix round (F1-F4)
|
|
|
|
Both independent reviews FAILED the original C2 landing with four findings.
|
|
Fixed all four; re-verified B/op unchanged (944, same as before this round)
|
|
and the complete Runtime + solution suites green.
|
|
|
|
## F1 (MAJOR, retail) - CommitCanonical post-callback reads/writes
|
|
|
|
Root cause: CommitCanonical read operation.PositionAuthorityVersion/
|
|
SpatialAuthorityVersion/Command.GameTime/PreviousContact/PreviousOnWalkable/
|
|
Key/Command.ShadowWorldOffsetX/Y AFTER invoking the ground-edge HitGround/
|
|
LeaveGround callbacks (via PhysicsObjUpdate.CommitSetPositionContactTransition).
|
|
A synchronous cancel-then-begin (or begin-twice) chain for the SAME entity
|
|
retires-then-rents (LIFO) the SAME Operation instance mid-callback, so those
|
|
post-callback reads could observe a reset-or-repurposed operation.
|
|
|
|
Fix: hoisted every scalar CommitCanonical still needs into locals BEFORE the
|
|
callback (operationKey, positionAuthorityVersion, spatialAuthorityVersion,
|
|
sourceVelocityAuthorityVersion, commandGameTime, previousContact,
|
|
previousOnWalkable, shadowWorldOffsetX/Y). ContactCommitGuard now captures
|
|
positionAuthorityVersion/spatialAuthorityVersion as plain values instead of
|
|
holding an Operation reference. IsCanonicalPlacementCommitCurrent's Operation
|
|
parameter was replaced with the two explicit scalar parameters.
|
|
|
|
FALSE START (caught by full-suite regression, not left in): first pass ALSO
|
|
added an operationToken identity comparison inside
|
|
IsCanonicalPlacementCommitCurrent, intending to detect the exact repurposing.
|
|
This broke two EXISTING tests
|
|
(ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation and,
|
|
after fixing that, exposed a status regression in the same test) because
|
|
retail's OWN contract is that an in-flight ground-edge commit for a
|
|
DISPLACED operation must still complete its physical settle (contact
|
|
transition + shadow sync) - adding identity-based rejection there
|
|
incorrectly aborted a commit the test explicitly requires to succeed.
|
|
Reverted the token check from IsCanonicalPlacementCommitCurrent entirely
|
|
(doc comment there now explains why identity must NOT be checked at that
|
|
layer). The REAL self-aliasing hazard was two levels up: `BeginAcceptedPlacementCore`'s
|
|
final `IsCurrent(replacement)` check (after PublishPlacement can reentrantly
|
|
retire+rent the SAME `replacement` instance for an inner Begin) and
|
|
`SubmitPreparedPlacementCore`/`RetryDeferred`'s post-CommitCanonical decision
|
|
of Cancelled-vs-Committed (CommitCanonical can legitimately succeed for a
|
|
displaced operation - the OUTER caller's own invocation still must not
|
|
publish a Place projection nobody will ever acknowledge). Fixed both by
|
|
comparing a LOCAL, pre-reentrancy token/parameter (`token` already a
|
|
parameter in SubmitPreparedPlacementCore; hoisted `operationToken` added to
|
|
RetryDeferred; the `token` local already existed in
|
|
BeginAcceptedPlacementCore) against a FRESH `_operations` lookup - never
|
|
against the potentially-repurposed operation reference's own fields.
|
|
|
|
Regression test: ReentrantCancelThenBeginRecyclesInstanceButCollisionReportUsesPreCallbackValues.
|
|
Drives Cancel(publishWithdrawal:false) then BeginAcceptedPlacement from
|
|
inside OnHitGround - LIFO pool guarantees the SAME instance is retired then
|
|
immediately rented for the new ("recycled") operation, a strictly more
|
|
adversarial recycle than the pre-existing test. Asserts (via
|
|
CollisionReportObserver subscribed to CollisionReports) that the
|
|
environment-collision report's RecipientWasInContact reflects the ORIGINAL
|
|
pre-callback PreviousContact, and that the report fires at all (proving
|
|
PreviousOnWalkable also carried through correctly - the report only fires
|
|
when !previousOnWalkable && body.OnWalkable, so a corrupted
|
|
PreviousOnWalkable would silently suppress it). Verified DISCRIMINATING: reverted
|
|
the hoist locally, confirmed the test fails (empty report collection), then
|
|
restored the fix.
|
|
|
|
## F2 (MAJOR arch + MINOR retail) - pool invisible to reset/dispose ledger
|
|
|
|
_operationPool was never cleared by ClearOwnedState (called from both
|
|
ResetSession and Dispose), so up to 64 pooled instances could retain full
|
|
previous-generation entity graphs across a session boundary. Added
|
|
`_operationPool.Clear()` inside ClearOwnedState (comment explains why this
|
|
is safe unlike RetireOperationToPool: a session clear cannot be reentered
|
|
from inside itself). Added `PooledOperationCount` to
|
|
RuntimeSetPositionOwnershipSnapshot (new trailing field, single
|
|
construction site updated), deliberately EXCLUDED from IsConverged (doc
|
|
comment explains pooled idle capacity is legitimate mid-session).
|
|
|
|
Regression tests: OperationPoolClearsOnResetSession,
|
|
OperationPoolClearsOnDispose - both drive an Apply+Acknowledge cycle to get
|
|
>=1 pooled operation, assert PooledOperationCount >= 1, then call
|
|
ResetSession/Dispose and assert it drops to exactly 0. Verified
|
|
DISCRIMINATING: commented out the `_operationPool.Clear()` line, confirmed
|
|
both tests fail (1 instead of 0), restored the fix.
|
|
|
|
## F3 (MINOR arch) - no-self-aliasing invariant + InPool guard
|
|
|
|
Reordered BeginAcceptedPlacementCore: RentOperation() now happens AFTER the
|
|
displaced operation's CancelCoreDeferred retire (previously rent happened
|
|
first). This lets a single-entity churn cycle legitimately reuse the exact
|
|
retired instance (LIFO) instead of drawing a different one - safe because
|
|
every field this method needs from `displaced` was already captured into
|
|
locals before the retire point (unchanged from the original C2 landing).
|
|
Added `Operation.InPool` (bool, defaults false): set true in
|
|
RetireOperationToPool right before pushing, cleared in
|
|
ResetAllFieldsToDefault (called from RentOperation right after popping).
|
|
RetireOperationToPool now throws InvalidOperationException if called on an
|
|
instance that is already InPool (double-retire without an intervening rent
|
|
would silently duplicate the instance in the pool stack).
|
|
|
|
This reorder, on its own, reintroduced a DIFFERENT self-aliasing hazard at
|
|
BeginAcceptedPlacementCore's own final line (`IsCurrent(replacement) ? token
|
|
: default`) - caught by the EXISTING test
|
|
ReentrantBeginDuringDiscardCannotBeOverwrittenByOuterBegin (a
|
|
PublishPlacement-triggered reentrant Begin during a Discard notification can
|
|
retire+rent the SAME `replacement` instance for an inner operation, making
|
|
`IsCurrent(replacement)` a self-referential tautology that wrongly reports
|
|
"still current"). Fixed by comparing `_operations[key].Token` against the
|
|
LOCAL `token` (captured at function entry, immune to reentrant corruption)
|
|
instead of calling `IsCurrent(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 6460596b56cd72a2c6d96e757b33da879a805b6d.
|
|
|
|
## Files touched this fix round
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs (net diff vs C2
|
|
landing: 800 lines changed - Operation.InPool + doc, ResetAllFieldsToDefault
|
|
InPool line, CommitCanonical hoisting rewrite, ContactCommitGuard/
|
|
IsCanonicalPlacementCommitCurrent signature change, IsVelocityCurrent
|
|
overload, BeginAcceptedPlacementCore reorder + final-check fix,
|
|
SubmitPreparedPlacementCore + RetryDeferred post-commit currency checks,
|
|
RuntimeSetPositionOwnershipSnapshot.PooledOperationCount +
|
|
ClearOwnedState pool clear, ParkCollisionResidents + CreateWithdrawalOperation
|
|
routed through RentOperation)
|
|
- tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs (net
|
|
diff: 255 lines - 4 new tests + System.Reflection using)
|
|
|
|
# C2 round 3 (rent-after-retire regression) + addendum (A1/A2)
|
|
|
|
The F3 reorder (rent AFTER retire, so a single-entity churn cycle reuses
|
|
the exact retired instance) makes `IsCurrent(Operation)` (ReferenceEquals +
|
|
six field comparisons read off the SAME instance) a tautology once a
|
|
reentrant cancel-then-begin recycles that instance for a different logical
|
|
operation at the same key. Every one of the ~20 `IsCurrent(operation)` call
|
|
sites needed auditing: either convert to a captured-token-vs-fresh-lookup
|
|
check, or prove (with a per-site comment) that no reentrancy point
|
|
intervenes between the last fresh lookup and the check.
|
|
|
|
## The fix
|
|
|
|
Extracted `IsOperationStateConsistent(Operation)` from `IsCurrent`'s six
|
|
non-identity field comparisons. `IsCurrent(Operation)` is now
|
|
`_operations.TryGetValue(operation.Key, out current) &&
|
|
ReferenceEquals(current, operation) && IsOperationStateConsistent(operation)`
|
|
- same behavior as before, just factored so the new helper below can share
|
|
the state check. Added:
|
|
|
|
```
|
|
private bool IsCurrentByToken(
|
|
RuntimeEntityKey key,
|
|
in RuntimeEntityPlacementToken capturedToken,
|
|
[NotNullWhen(true)] out Operation? operation)
|
|
```
|
|
|
|
Does a FRESH `_operations.TryGetValue` + `current.Token == capturedToken` +
|
|
`IsOperationStateConsistent(current)`, and hands back the fresh (possibly
|
|
different-instance) `Operation` on success. `[NotNullWhen(true)]` lets
|
|
callers reuse a single `out` binding across an `if (!IsCurrentByToken(...))
|
|
return ...;` guard without a separate null-forgiving cast.
|
|
|
|
`CancelCore(Operation expected, ...)` (ReferenceEquals shape - the OTHER
|
|
regression source the reviewer named, "shares the shape with a worse
|
|
outcome: cancelling the newer operation") became
|
|
`CancelCore(RuntimeEntityKey key, in RuntimeEntityPlacementToken
|
|
expectedToken, bool preserveLostFamily = false)`: fresh
|
|
`_operations.TryGetValue(key, ...)` + `current.Token != expectedToken` ->
|
|
no-op. All 5 callers converted (ForgetExactPlacement,
|
|
RetireDormantLocalActivation, RetireDormantLocalActivationToken,
|
|
SubmitPreparedPlacementCore's post-CommitCanonical-failure branch,
|
|
RetryDeferred's equivalent) - each now passes its own captured
|
|
`(key, token)` instead of an `Operation` reference.
|
|
|
|
## The exact bug the reviewer traced (Cancel path) - FIXED
|
|
|
|
`Cancel(record, bool)` creates a withdrawal operation, installs it at the
|
|
key, then calls `PublishPlacement(cancelledOld)` (reentrancy point: an
|
|
`IRuntimePlacementObserver` subscriber can call Begin/Cancel for the same
|
|
entity from inside this synchronous dispatch), then previously checked
|
|
`IsCurrent(operation)` against the now-possibly-recycled reference. Fixed:
|
|
capture `operation.Token` into a local BEFORE `PublishPlacement`, then
|
|
`IsCurrentByToken(key, capturedToken, out operation)` afterward.
|
|
|
|
## Subtler hazard found during the audit, not named by the reviewer:
|
|
## RebindQuiescedDeferredOperations
|
|
|
|
`foreach (Operation operation in _operations.Values.ToArray())` snapshots
|
|
live REFERENCES. An earlier iteration's `RetryDeferred` call (itself
|
|
reentrancy-exposed) can recycle a LATER iteration's not-yet-reached
|
|
Operation instance before the loop reaches it - a stale-reference bug
|
|
independent of the Cancel/CancelCore ones. Fixed by snapshotting
|
|
`(Key, Token)` VALUE pairs first, then resolving fresh via
|
|
`IsCurrentByToken` at the top of each iteration instead of trusting the
|
|
snapshotted reference.
|
|
|
|
## Addendum A1 (retail MINOR) - CommitCanonical's 4 post-callback writes
|
|
|
|
`CommitCanonical`'s tail (`operation.ExactCellId/Result/WakeableLostCell/
|
|
EnteringWorldFromCelllessResidence = ...; CancelLostFamilyDeadlines(operation)`)
|
|
still targeted the poolable instance directly. With rent-after-retire, a
|
|
nested cancel-then-begin recycles the instance before this block runs, and
|
|
a bare Begin never advances PlacementCommitVersion - invisible to the
|
|
settle-layer record-state checks - so the writes could land on the WRONG
|
|
(freshly-begun) operation. Fixed: hoisted `operationToken = operation.Token`
|
|
(already had `operationKey` from F1) at the SAME pre-callback point as
|
|
every other F1 hoist, then gated the whole write block behind a fresh
|
|
`_operations.TryGetValue(operationKey, out currentOperation) &&
|
|
currentOperation.Token == operationToken` check - skip the writes (not the
|
|
whole commit) if the token no longer matches, matching retail's
|
|
already-unconditional physical settle (only Runtime's OWN bookkeeping is
|
|
conditional). The final `IsCanonicalPlacementCommitCurrent(...,
|
|
requireSpatialRoot: true)` return is UNCHANGED - still identity-agnostic,
|
|
per the layer-separation rule below.
|
|
|
|
## Addendum A2 (doc hygiene) - stale comment on RetireOperationToPool
|
|
|
|
The old comment claimed `IsCanonicalPlacementCommitCurrent` "additionally
|
|
compares the operation's Token by value" - that guard was a round-3 FALSE
|
|
START (see the F1 section above) that was reverted before F1 even landed;
|
|
the comment was never updated and contradicted the real mechanism.
|
|
Rewritten to state the true safety contract: hoisted locals (F1) +
|
|
call-site captured-token-vs-fresh-lookup (this round), with the settle
|
|
path deliberately identity-agnostic per retail's unconditional
|
|
SetPositionInternal completion.
|
|
|
|
## Layer separation (unchanged, reaffirmed by the retail reviewer)
|
|
|
|
`IsCanonicalPlacementCommitCurrent` takes NO identity/Token parameter, by
|
|
design - retail's SetPositionInternal settle completes unconditionally for
|
|
a displaced operation (see
|
|
ReentrantGroundEdgePlacementCannotBeCancelledByDisplacedOperation, from the
|
|
F1 false-start above). Identity gates belong ONLY at Runtime-owned
|
|
publication/cancellation/ownership decisions (SubmitPreparedPlacementCore's
|
|
Cancelled-vs-Committed decision, Cancel's withdrawal publish, CancelCore's
|
|
match check, CommitCanonical's A1 bookkeeping-write gate) - never at the
|
|
settle/currency check itself. Did not touch this layer in round 3 beyond
|
|
re-confirming it via the reverted false-start being re-tried and failing
|
|
the same way it did in round 2 (not re-attempted this round - the round-2
|
|
false-start's lesson was already incorporated).
|
|
|
|
## Per-site audit table (all sites this class calls
|
|
`IsCurrent(Operation)`/`ReferenceEquals` on an Operation across a
|
|
reentrancy-exposed span)
|
|
|
|
CONVERTED to captured-token-vs-fresh-lookup (IsCurrentByToken or
|
|
CancelCore(key, token)):
|
|
1. `Cancel(record, bool)` post-`PublishPlacement(cancelledOld)` check - the
|
|
reviewer's named bug.
|
|
2. `SubmitPreparedPlacementCore` post-`_physics.Engine.SetPosition(...)`
|
|
check (first of two, immediately after the collision-callback-bearing
|
|
call).
|
|
3. `SubmitPreparedPlacementCore` post-`SetPosition` second check (after the
|
|
`TryGetBlockingQuiescence`/deferred branch, before `CommitCanonical`).
|
|
4. `SubmitPreparedPlacementCore`'s `CancelCore(token.Entity, token)` call on
|
|
`CommitCanonical` failure - the reviewer's named CancelCore-shape bug.
|
|
5. `RetryDeferred`'s two analogous post-`SetPosition` checks (same shape as
|
|
#2/#3, via hoisted `operationToken`).
|
|
6. `RetryDeferred`'s `CancelCore(operationToken.Entity, operationToken)`
|
|
call on `CommitCanonical` failure (same shape as #4).
|
|
7. `RebindQuiescedDeferredOperations`'s per-entry resolve - converted from a
|
|
snapshotted-REFERENCE loop to a snapshotted-(Key,Token) loop + fresh
|
|
`IsCurrentByToken` (the subtler hazard found during the audit, not named
|
|
by the reviewer).
|
|
8. `ForgetExactPlacement`'s `CancelCore(token.Entity, token)` call.
|
|
9. `RetireDormantLocalActivation`'s `CancelCore` call.
|
|
10. `RetireDormantLocalActivationToken`'s `CancelCore` call.
|
|
11. `CommitCanonical`'s A1 bookkeeping-write gate (fresh lookup +
|
|
`currentOperation.Token == operationToken` before the
|
|
ExactCellId/Result/WakeableLostCell/EnteringWorldFromCelllessResidence/
|
|
CancelLostFamilyDeadlines writes) - addendum A1.
|
|
|
|
PROVEN SAFE WITH COMMENT (fresh lookup + Token/state check on the same line
|
|
or immediately prior, nothing reentrant intervenes before the check runs):
|
|
12. `IsPlacementCurrent` - fresh lookup + Token check inline, no
|
|
reentrancy between them.
|
|
13. `PrepareDormantLocalActivationOwnership` - fresh lookup + Token check
|
|
earlier in the same method, dormant-family code with no production
|
|
callers.
|
|
14. `PrepareMover` - same shape as #13.
|
|
15. `IsExactPreparedPlacementCurrent` - same shape.
|
|
16. `TryEvaluateDormantLocalActivation` - ReferenceEquals variant, safe
|
|
because `handleCollisions: null` on this call means nothing reentrant
|
|
can run before the check.
|
|
17. `IsDormantLocalActivationPrephaseCurrent` - fresh lookup + Token check
|
|
earlier in the same method.
|
|
18. `IsDormantLocalActivationResponseCurrent` - same shape.
|
|
19. `CommitDormantLocalActivationPostCollision` - entry AND final-return
|
|
checks, one comment block covering both; dormant family, no production
|
|
callers.
|
|
20. `IsDormantLocalActivationCommitCurrent` - same shape as #17/#18.
|
|
21. `IsExactDormantLocalActivationCurrent` - same shape.
|
|
22. `SubmitPreparedPlacementCore`'s own entry `ownsToken` check - the
|
|
function's own validation, nothing reentrant between the fresh lookup
|
|
and this check.
|
|
23. `AcknowledgeProjection`'s entry check - fresh lookup +
|
|
ProjectionSequence check, no reentrancy in between.
|
|
24. `CommitCanonical`'s entry check (`!result.IsCommitted ||
|
|
!IsCurrent(operation)`) - every caller (SubmitPreparedPlacementCore,
|
|
RetryDeferred) passes an operation freshly re-verified immediately
|
|
before calling CommitCanonical.
|
|
25. `CommitCollisionGeneration`'s per-entity loop - added during THIS pass
|
|
(not flagged by the reviewer, found while double-checking every
|
|
`_operations.TryGetValue` site in the file for completeness). Iterates
|
|
an array of KEYS (never Operation references), fresh lookup + full
|
|
WakeableLostCell/ExactCellId/CollisionPrefix/CollisionGeneration shape
|
|
check every iteration - immune to the snapshotted-reference class of
|
|
bug even though it calls the reentrancy-exposed `RetryDeferred`
|
|
per-entity.
|
|
|
|
OUT OF SCOPE (different class entirely, not an Operation-identity site):
|
|
26. `ParkCollisionResidentsForQuiescence`'s `ReferenceEquals(current,
|
|
state)` - checks `CollisionPrefixQuiescence` identity (a class that is
|
|
never pooled), unrelated to the Operation pool this round's regression
|
|
lives in.
|
|
|
|
## Round 3 tests
|
|
|
|
`ReentrantBeginDuringCancelPublishCannotBeOverwrittenByOuterWithdraw` -
|
|
the reviewer's named Cancel-path scenario: Apply (pending Place
|
|
projection) -> observer reentrantly Begins on the Discard notification
|
|
during Cancel's PublishPlacement -> asserts only the Discard delta
|
|
published (a stale-reference bug would add a second Withdraw delta
|
|
stamped with the inner operation's state) and the inner token is still
|
|
`IsPlacementCurrent`. VERIFIED DISCRIMINATING: reverted Cancel's
|
|
`IsCurrentByToken` check back to `IsCurrent(operation)`, reran - failed
|
|
exactly as predicted (`Assert.Single` saw 2 deltas, the second a Withdraw
|
|
carrying the inner operation's PlacementCommitVersion=2/Sequence=2) -
|
|
restored the fix.
|
|
|
|
`ReentrantCancelThenBeginDuringCommitFailureLeavesInnerOperationUncancelled`
|
|
- the CancelCore-shape scenario: ground-edge HitGround callback does
|
|
cancel-then-begin (recycling the instance for `inner`) AND calls
|
|
`lifetime.Entities.AdvancePlacementCommit(record)` a second time BEFORE
|
|
creating `inner` (so `inner` snapshots the already-advanced value and
|
|
stays internally self-consistent, while the OUTER commit's
|
|
`canonicalCommitVersion`, captured before the callback, now mismatches) -
|
|
this makes `CommitCanonical`'s post-callback `IsCanonicalPlacementCommitCurrent`
|
|
check fail for the outer commit without needing a full nested SetPosition
|
|
round-trip, driving `SubmitPreparedPlacementCore` into
|
|
`PublishCancellation(CancelCore(token.Entity, token))`. Asserts
|
|
`outcome.Status == Cancelled` and `inner` is still `IsPlacementCurrent`
|
|
afterward. VERIFIED DISCRIMINATING: reverted `CancelCore(key, token)`'s
|
|
Token check to accept any match by key alone (simulating the old
|
|
ReferenceEquals-without-identity shape), reran - failed exactly as
|
|
predicted (`IsPlacementCurrent(inner)` false, the sabotaged check retired
|
|
`inner`'s instance out from under it) - restored the fix.
|
|
|
|
## Final verification (round 3 + addendum)
|
|
|
|
- Release build (`dotnet build AcDream.slnx -c Release`): 0 errors, 21
|
|
pre-existing warnings, all in test files this session did not touch (App/
|
|
Core test projects) - unchanged from the F1-F4 round.
|
|
- `WarmedImmediateCommitAllocationIsMeasuredBeforeProductionCutover`:
|
|
passes at the existing 1,536L gate - token captures added this round are
|
|
all stack-only locals/struct fields, no new heap allocation.
|
|
- Complete AcDream.Runtime.Tests: 927/927 (925 F1-F4 baseline + 2 new: the
|
|
Cancel-path and CancelCore-shape regressions).
|
|
- Complete solution build (`dotnet build AcDream.slnx -c Release`): 0
|
|
errors.
|
|
- `git diff --check`: exit 0, clean (same pre-existing LF/CRLF metadata
|
|
notices on the same pre-existing dirty files, RuntimeSetPositionState.cs
|
|
included - no whitespace-error content).
|
|
- `git status`/`git rev-parse HEAD`: still exactly the 2 files this session
|
|
owns (RuntimeSetPositionState.cs, RuntimeSetPositionStateTests.cs) plus
|
|
the same pre-existing dirty paths (AGENTS.md,
|
|
PlayerModeController.cs, PlayerInteractionMovementSink.cs,
|
|
LiveAnimationPresentationContext.cs, RuntimeRemotePhysicsUpdater.cs,
|
|
CellTransitTests.cs, Issue133DungeonTeleportPrefixTests.cs,
|
|
A8CellAudit.csproj) untouched by this session. Nothing staged, nothing
|
|
committed, HEAD unchanged at 6460596b56cd72a2c6d96e757b33da879a805b6d.
|
|
|
|
## 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:
|
|
a32aba35d1d945b9d3194a84e70facf74a7d7608. Read in full: c3-contract.md,
|
|
docs/plans/2026-08-02-placement-cutover.md, docs/research/
|
|
2026-08-02-cutover-route-inventory.md (routes 1+8 + all cross-cutting
|
|
sections), docs/research/2026-08-02-canonical-body-writer-map.md, docs/
|
|
research/2026-08-02-runtime-continuation-executor-handoff.md.
|
|
|
|
## C3-1 — DONE, tested, gated. (Runtime-only prerequisite a)
|
|
|
|
Design: rather than widen any internal enum's accessibility (the contract's
|
|
explicit preference), added a small PUBLIC projection surface next to the
|
|
existing public placement-receipt types (`RuntimePlacementProjectionKind`/
|
|
`Token`/`Snapshot` are already public; the executor's own receipt/trace types
|
|
are `internal`):
|
|
|
|
- `RuntimeInitialCreateTeleportHookPhase`, `RuntimeInitialCreatePositionDisposition`,
|
|
`RuntimeInitialCreatePositionConstrainPhase` — public 1:1 projections of the
|
|
internal `RuntimeTeleportHookPhase`/`RuntimeAuthoritativePositionDisposition`/
|
|
`RuntimePositionConstrainPhase` enums (`RuntimeAuthoritativePositionRouteClassifier.cs:38,49,63`).
|
|
- `RuntimeInitialCreatePositionRouteFact` — one Position continuation's route
|
|
facts (Sequence/Disposition/HookPhase/ConstrainPhase/StopInterpolating/
|
|
ZeroVelocity/PreserveHeading/SendPositionImmediately), projected from
|
|
`RuntimeInitialCreateExecutedAction` trace entries where
|
|
`Kind == Position` only (every other action kind stays internal-only —
|
|
widening the full action-kind vocabulary was explicitly what the contract
|
|
said to avoid).
|
|
- `RuntimeInitialCreatePlacementCompletion` — the top-level public shape
|
|
(Entity, FullCellId, TeleportHookPhase, PositionRouteFacts,
|
|
ReplayedDeferredChildCount), built ONCE by a new private
|
|
`RuntimeInitialCreateContinuationExecutor.ProjectCompletion` at the exact
|
|
completion site (`ExecuteCore`'s `Released` case, where `completedReceipt`
|
|
is built) and cached in `_completionReceipts`'s tuple (extended from
|
|
`(Sequence, Receipt)` to `(Sequence, Receipt, Public)`) — so a host polling/
|
|
retrying `TryGetInitialCreateCompletion` never re-allocates
|
|
(the "allocation-conscious" requirement).
|
|
- New internal `RuntimeInitialCreateContinuationExecutor.TryGetCompletion`
|
|
reads the cached projection by exact token identity (same Entity/Sequence
|
|
correlation rule as the existing `TryGetCompletionReceipt`).
|
|
- New PUBLIC `RuntimePlacementProjectionChannel.TryGetInitialCreateCompletion(
|
|
RuntimeGenerationToken, in RuntimePlacementProjectionToken, out
|
|
RuntimeInitialCreatePlacementCompletion)` — generation-gated like every
|
|
other channel method, thin passthrough to the executor. The channel now
|
|
takes the executor as a 3rd internal ctor parameter; all 3
|
|
`RuntimeEntityObjectLifetime` constructors updated to pass
|
|
`InitialCreateExecution`.
|
|
|
|
Files touched:
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
(+219/-0 net insertions: new public types, ProjectCompletion + 3 enum
|
|
mappers, TryGetCompletion, _completionReceipts tuple widened, completion
|
|
site wired).
|
|
- src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs (+32/-1:
|
|
new ctor param + field, TryGetInitialCreateCompletion).
|
|
- src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs (+9/-6: all 3
|
|
ctors pass InitialCreateExecution into the channel).
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
(+178: 4 new tests — ProjectsHookPhaseCellAndReplayCount [local-player
|
|
login correctly reports AfterEnterWorld hook phase, empty route facts],
|
|
ProjectsPositionRouteFactsForConstrainInterpolationBinding [teleport-
|
|
advanced continuation's route facts round-trip through the public
|
|
projection], RejectsWrongGeneration, ReturnsFalseAfterAcknowledgeReapsTheCorrelationEntry).
|
|
|
|
Gates run for C3-1: focused Residence|Classifier|Executor|SetPositionState|
|
|
PlacementProjectionChannel filter 237/237; complete AcDream.Runtime.Tests
|
|
931/931 (927 baseline + 4 new); Release build of the full solution 0
|
|
errors/21 pre-existing warnings; complete solution
|
|
`dotnet test AcDream.slnx -c Release -m:1` with
|
|
`ACDREAM_PAK_PATH=/c/Users/erikn/Documents/Asheron's Call/acdream.pak` —
|
|
every project green (App 4028/3 skips, Bake 15/0, Cli 4/0, Content 124/0,
|
|
Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime 931/0,
|
|
UI.Abstractions 543/0 — zero failures anywhere). `git diff --check` clean
|
|
(only the same pre-existing LF/CRLF notices on pre-existing dirty files).
|
|
Nothing staged, nothing committed. `git status` confirms only the 4 files
|
|
above are newly dirty beyond the 8 pre-existing protected paths (which I
|
|
did not touch — I read PlayerModeController.cs for C3-2 investigation but
|
|
made ZERO edits to it).
|
|
|
|
## C3-2/C3-3/C3-4 — NOT implemented this session. Stopped with evidence
|
|
## after investigation surfaced a materially larger scope than the four
|
|
## input docs describe. Full findings below for whoever picks this up.
|
|
|
|
I read (in full or targeted-full) beyond the four input docs:
|
|
`PlayerModeController.cs` (all 623 lines), `RuntimeLocalPlayerPhysicsPublicationState.cs`
|
|
(all 1033 lines), `RuntimeLocalPlayerMovementState.cs` (all 374 lines),
|
|
`EntityPhysicsHostComposition.cs` (all 82 lines), `RuntimeInitialCreateResidenceState.Begin`/
|
|
`Own` (full), `RuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement`/
|
|
`SubmitPreparedPlacementCore` (targeted), `RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate`
|
|
(full), `DatLiveEntityProjectionMaterializer.RegisterAnimation` (full), plus
|
|
targeted greps across `RuntimeInitialCreateContinuationExecutorTests.cs` and
|
|
`RuntimeLocalPlayerPhysicsPublicationStateTests.cs` for the test harness's
|
|
own "intended recipe" (tests are the only place the FULL local-player
|
|
publication recipe is exercised end-to-end today).
|
|
|
|
### Finding A — confirmed: no accessibility blocker, no accidental
|
|
pre-built orchestrator (cross-check of the route-inventory's own claim)
|
|
|
|
Re-verified independently: `RuntimeLocalPlayerPhysicsPublicationState`'s
|
|
Prepare/Commit/EvaluateActivation/CommitActivation/FinalizeActivation chain
|
|
has ZERO production callers (only
|
|
`tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerPhysicsPublicationStateTests.cs`).
|
|
Unlike C1 ("satisfied by existing mechanism" — a pleasant surprise the plan
|
|
doc recorded), there is no similar hidden orchestrator tying
|
|
`RegisterEntityWithInitialResidence`'s residence lease to the publication
|
|
lifecycle automatically. The wiring described by C3-2/C3-3's bullets
|
|
genuinely does not exist anywhere, dormant or otherwise.
|
|
|
|
### Finding B — the local-player initial-placement circular dependency
|
|
(confirmed by direct read, not previously named in any of the 4 docs)
|
|
|
|
- `RuntimeEntityObjectLifetime.RegisterEntityWithInitialResidence` →
|
|
`InitializeAcceptedCreateResidence` → `InitialCreateResidences.Begin`
|
|
(`RuntimeInitialCreateResidenceState.cs:556-607`) → `Own`
|
|
(`:731-...`) — `Own` synchronously calls
|
|
`_setPosition.TryBeginExclusiveAuthoredPlacement(record, ..., route.OperationKind)`
|
|
(`:746-751`) whenever `route.PerformsSetPosition` is true. This runs
|
|
**at wire CreateObject time**, inside `LiveEntityRuntime.RegisterLiveEntity`
|
|
today's call site (once flipped) — i.e. it opens a `RuntimeEntityPlacementToken`
|
|
operation in stage `AwaitingPreparation` immediately, for EVERY entity
|
|
including the local player, and the lease (`lease.Placement`) sits open
|
|
until something completes it.
|
|
- `RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate`
|
|
(`RuntimeAuthoritativePositionRouteClassifier.cs:205-273`, full read)
|
|
gives EVERY TopLevel Create with a valid wire position — local player,
|
|
remote, projectile alike — `Disposition.SetPosition` uniformly
|
|
(`route.PerformsSetPosition` is true for all of them; only Parented/
|
|
PickedUp Creates get `AwaitFreshPosition`, which does NOT open a
|
|
SetPosition operation). So this circular dependency is not local-player-
|
|
specific in its trigger — it applies to the SAME `Own()` call for every
|
|
top-level Create.
|
|
- For the LOCAL PLAYER specifically, that open placement operation can only
|
|
be completed by driving `RuntimeLocalPlayerPhysicsPublicationState`'s full
|
|
chain against the EXACT SAME `lease.Placement` token (confirmed by reading
|
|
`RuntimeLocalPlayerPhysicsPublicationStateTests.cs:2263-2300`'s `Fixture.RepreparePlacement`/
|
|
`Prepare` helpers: `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 generic `SubmitPreparedPlacement`/`TryPrepareAndSubmitAuthoredPlacement`
|
|
path (the one C3-2's bullet 3 names for `AwaitingContinuationPlacement`'s
|
|
pending-token flavor) **cannot** be used for the local player's OWN initial
|
|
placement: `SubmitPreparedPlacementCore` requires
|
|
`operation.Record.PhysicsBody is not { } body` to already be non-null
|
|
(`RuntimeSetPositionState.cs:2610`) — there is no local-player body yet at
|
|
Create time, so this path structurally rejects it. Only the publication
|
|
lifecycle can attach the FIRST body to an already-open placement token.
|
|
- TODAY, `PlayerModeController.BuildControllerAndCamera` runs LATER than
|
|
Create-time (gated by `PlayerModeAutoEntry.cs:214-230`'s
|
|
`IsPlayerEntityPresent && IsWorldReady` per-frame check) and never touches
|
|
`lease.Placement`/the publication state at all — it does its own unrelated
|
|
`_physics.Resolve`/`ResolvePlacement` (PlayerModeController.cs:409-430).
|
|
So after the flip, the residence lease's placement (and therefore the
|
|
ENTIRE executor drain — deferred-child replay, the AfterEnterWorld
|
|
teleport-hook request, every continuation) stays stuck in `PendingPlacement`
|
|
from Create time until whatever replaces `BuildControllerAndCamera` drives
|
|
the publication chain AND separately calls `InitialCreateExecution.Execute(...)`
|
|
a SECOND time afterward to actually drain the FIFO. This second `Execute`
|
|
call is not optional — `Execute`'s own doc comment and the executor
|
|
handoff both say a caller re-invokes `Execute` after the placement token
|
|
in the trace is acknowledged; nothing does this automatically.
|
|
- Practically workable simplification found: `RuntimeLocalPlayerPhysicsPublicationState.Prepare`
|
|
calls `DiscardCurrent()` on entry (`:305`) before installing a new
|
|
candidate, so a full "retry the whole Prepare→Commit chain from scratch
|
|
next frame" is safe UNTIL `Commit()` succeeds (at which point `_activation`
|
|
is populated and a subsequent `Prepare` call correctly rejects via
|
|
`CanPrepare`'s `_activation is null` guard — the retry loop must switch to
|
|
re-driving `EvaluateActivation`/`CommitActivation` on the SAME
|
|
`activationToken`, not re-`Prepare`ing). This means `PlayerModeAutoEntry`'s
|
|
EXISTING per-frame "keep calling TryEnter until it returns true" loop can
|
|
likely be reused rather than inventing a wholly new scheduler — but
|
|
`PlayerModeController` needs a small piece of cross-frame state (at least
|
|
the pending `activationToken` when Commit succeeded but Evaluate/Commit-
|
|
Activation hasn't finished) that does not exist today. This is a genuinely
|
|
new resumable mini-state-machine, not a one-line change.
|
|
- The animation-sequencer hook attachment
|
|
(`AttachCycleVelocityAccessor`/`ObjectScale`/`AttachAnimationRootMotionSource`/
|
|
`Motion.RemoveLinkAnimations`/`InitializeMotionTables`/`CheckForCompletedMotions`/
|
|
`DefaultSink`, `PlayerModeController.cs:378-396`) currently happens on the
|
|
controller BEFORE placement resolve, via the existing
|
|
`RuntimeLocalPlayerMovementState.BeginMotionPreparation` lease
|
|
(`:102-117`). `RuntimeLocalPlayerPhysicsPublicationState.Prepare` builds
|
|
its OWN controller internally and does not return a reference to it before
|
|
`Commit()` — but since `BeginMotionPreparation` only needs a controller
|
|
REFERENCE (no ordering requirement relative to the publication state's own
|
|
internal stages), it appears safe to call `BeginMotionPreparation` on
|
|
`_movement.Controller` immediately AFTER `Commit()` succeeds (controller
|
|
is `RuntimeOwnedDormant` at that point, not yet `RuntimePublished`) and
|
|
BEFORE `EvaluateActivation`/`CommitActivation` — this avoids needing any
|
|
NEW Runtime API to expose the candidate controller pre-Commit. Flagged
|
|
as "appears safe" (not "verified safe") — needs a dedicated conformance
|
|
test before trusting it.
|
|
|
|
### Finding C — a SECOND, previously-unstated capability gap: no production
|
|
path constructs a NON-local entity's FIRST PhysicsBody for a residence-driven
|
|
Create (this blocks Route 1/8 for remote/projectile Creates just as much as
|
|
the local-player gap blocks it for the local player)
|
|
|
|
- `SubmitPreparedPlacementCore` (`RuntimeSetPositionState.cs:2584-2629`)
|
|
requires `operation.Record.PhysicsBody is not { } body` (`:2610`) for
|
|
EVERY entity, not just the local player — confirmed by reading the full
|
|
method; there is no branch that constructs a body when none exists.
|
|
- `TryPrepareAndSubmitAuthoredPlacement` (`RuntimeSetPositionState.cs:1562-1625`,
|
|
the "C0-3" mover-chain call the contract names for continuation
|
|
placements) is a thin wrapper around `PrepareMover` + `SubmitPreparedPlacement`
|
|
— it inherits the SAME pre-existing-body requirement. Its own doc comment
|
|
says "a residence lease's own Placement/Route.OperationKind/
|
|
Route.SetPositionFlags are exactly the token/kind/flags this takes" —
|
|
true for the TOKEN shape, but silent on the body precondition.
|
|
`ClassifyCreate` gives Remote/Projectile TopLevel Creates the exact same
|
|
`Disposition.SetPosition` as the local player (see Finding B) — so a
|
|
fresh remote humanoid/monster Create's residence placement would ALSO
|
|
reject via this same guard today.
|
|
- The ONLY production body-construction call I found for a NEWLY-CREATED
|
|
(non-local) entity is `DatLiveEntityProjectionMaterializer.RegisterAnimation`'s
|
|
`_runtime.GetOrCreatePhysicsBody(spawn.Guid, incarnation => new PhysicsBody{...})`
|
|
(`DatLiveEntityProjectionMaterializer.cs:1003-1016`) — but this is gated
|
|
behind `physicsStatic` (`FinalPhysicsState & PhysicsStateFlags.Static`,
|
|
line 961) AND a resolved animation sequencer (`animation.Sequencer is {}`)
|
|
— i.e. it is a narrow special case for STATIC decorative animated objects
|
|
(banners, torches), never reached for an ordinary moving humanoid/monster
|
|
spawn. `RuntimePhysicsState.GetOrCreatePhysicsBody` (public,
|
|
`RuntimePhysicsState.cs:1623`) is presumably the right general-purpose
|
|
tool to reuse, but nobody calls it for the general case, and none of the 4
|
|
input docs name this as a Route-1 capability gap (the closest hits —
|
|
route-inventory's "Gap 2" and body-writer-map's summary — are both scoped
|
|
explicitly to the LOCAL PLAYER controller/body atomicity problem, not to
|
|
ordinary remote entities).
|
|
- Building this out requires retail-fidelity decisions (what a fresh
|
|
non-static remote/projectile body's default orientation/scale/friction/
|
|
elasticity/velocity should be at Create time, mirroring whatever retail's
|
|
`enter_world`/object-creation path does) that none of the 4 docs specify
|
|
and that I should not invent without the grep-named-first workflow this
|
|
project mandates for AC-specific behavior.
|
|
|
|
### Why I stopped here rather than pushing an implementation
|
|
|
|
Both findings B and C are genuine, evidence-backed (file:line cited) gaps
|
|
in the CONTRACT's own assumed shape, not just "this is a lot of code."
|
|
Landing C3-2+C3-3 correctly needs, at minimum: (1) a new resumable
|
|
mini-state-machine in PlayerModeController/PlayerModeAutoEntry driving
|
|
Prepare→Commit→EvaluateActivation→CommitActivation→(second) Execute across
|
|
frames; (2) the equivalent for headless (which has its own per-tick
|
|
`TryCompletePortal`-shaped loop already, per route-inventory's route 8
|
|
section, that a similar chain would need to extend); (3) a NEW general
|
|
first-body-construction step for non-local residence-driven Creates,
|
|
requiring retail research this session did not do; (4) deletion of the
|
|
now-superseded duplicate authorities across ~6 files; (5) new App.Tests/
|
|
Headless.Tests integration tests; (6) the connected lifecycle/reconnect
|
|
gate against a live ACE, which is itself explicitly one of this project's
|
|
few "stop and get user verification" events. Given the project's own
|
|
standing rules — no workarounds, no guessing at retail behavior, dual-
|
|
reviewed shape for anything this load-bearing, and "stop and brainstorm
|
|
when the observed scope diverges from the plan's assumed shape" — pushing
|
|
a rushed implementation of the single most sensitive path in the client
|
|
(both hosts' login/placement) within this session's remaining budget was
|
|
judged higher-risk than landing C3-1 clean and handing back precise,
|
|
citable findings for a properly scoped follow-up session (likely its own
|
|
C3-2a "local-player initial-placement orchestration" + C3-2b "first-body
|
|
construction for residence-driven Creates" split, each with its own
|
|
dual-review pass, mirroring how C0/C1/C2 were each already run).
|
|
|
|
No files under C3-2/C3-3/C3-4's scope were edited: PlayerModeController.cs,
|
|
LiveEntityRuntime.cs, DatLiveEntityProjectionMaterializer.cs,
|
|
RuntimeLiveEntitySessionController.cs, HeadlessSessionWorldProjection.cs,
|
|
and RuntimeLocalPlayerMovementState.cs (the C3-4 Controller-setter seal)
|
|
are all untouched by this session (confirmed via `git status`).
|
|
|
|
## Gate 4 (connected) — not reached, not skipped/faked
|
|
|
|
The exact lifecycle/reconnect harness is real and located per the route
|
|
inventory: `tools/run-connected-world-lifecycle-gate.ps1` (drives capped +
|
|
uncapped-reconnect sessions against local ACE on 127.0.0.1:9000) and
|
|
`tools/run-connected-r6-soak.ps1` (canonical nine-stop route). Both require
|
|
an already-listening local ACE. I did not attempt to launch/verify ACE
|
|
reachability because there is no production code change from C3-2/C3-3/C3-4
|
|
to gate yet — running the connected harness against C3-1's Runtime-only
|
|
addition would exercise nothing new (C3-1 has no host caller in this
|
|
session) and would misrepresent the gate as having validated the cutover.
|
|
Whoever lands C3-2/C3-3/C3-4 must run this gate for real, with a live ACE,
|
|
per the contract's gate 4 and the project's own "visual verification is the
|
|
one thing that requires stopping for the user" rule.
|
|
|
|
## Files touched this session (C3-1 only)
|
|
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
- src/AcDream.Runtime/Physics/RuntimePlacementProjectionChannel.cs
|
|
- src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
|
|
Nothing staged, nothing committed, HEAD unchanged at a32aba35d1d945b9d3194a84e70facf74a7d7608.
|
|
|
|
## C3-1 review-fix round (same session, 2026-08-02)
|
|
|
|
Coordinator relayed two review passes on C3-1's diff:
|
|
|
|
1. Architecture PASS with one MINOR: `MapHookPhase`/`MapDisposition`/
|
|
`MapConstrainPhase`'s catch-all `_ =>` arms silently folded an unmapped
|
|
future internal enum value into `None`/`NoPositionOperation` instead of
|
|
failing loudly. Fixed: every declared value now has an explicit arm
|
|
(added the previously-implicit `None`/`NoPositionOperation` cases) and
|
|
the catch-all now `throw new ArgumentOutOfRangeException(...)` with a
|
|
message naming both the mapper method and the public enum to update.
|
|
Also fixed `ProjectCompletion`'s `action.PositionDisposition ??
|
|
RuntimeAuthoritativePositionDisposition.NoPositionOperation` fallback:
|
|
verified (grep-confirmed `BuildPositionTrace` is the sole producer of
|
|
`Kind.Position` trace entries, always passing `route.Disposition`, a
|
|
non-nullable enum) that null is NOT a legitimate state at that call
|
|
site specifically (it legitimately IS null for non-Position action
|
|
kinds elsewhere in the trace, per the field's own doc comment - just
|
|
not reachable here since the loop already filters to
|
|
`Kind == Position`) - replaced the silent fallback with an explicit
|
|
`InvalidOperationException` throw + comment explaining why.
|
|
2. Retail-conformance PASS with two documentation-only addenda (no code
|
|
changes): (a) `RuntimeInitialCreatePositionRouteFact`'s doc comment now
|
|
states explicitly that `UnparentBeforeRouting`/
|
|
`ApplyPlacementFrameBeforeRouting` ("unset_parent"/"SetPlacementFrame")
|
|
are NOT projected because the executor's own merge
|
|
(`ApplyAcceptedPositionSnapshot`'s `clearParent`/`installPlacementFrame`
|
|
params, confirmed by direct read at the `ApplyPositionAction`/envelope
|
|
call sites) already applies both to the canonical snapshot before the
|
|
trace entry is built - a host must not re-apply them; the struct only
|
|
carries facts still DEFERRED to the host. (b)
|
|
`RuntimeInitialCreatePlacementCompletion`'s doc comment now states
|
|
`PositionRouteFacts`'s ARRAY ORDER (not `Sequence`) is authoritative -
|
|
confirmed multiple Position trace entries from one same-incarnation
|
|
envelope share one continuation `Sequence` (only `Stage`, not
|
|
projected, distinguishes them internally), and `ProjectCompletion`
|
|
preserves trace/FIFO-drain order by construction.
|
|
|
|
New test per the reviewer's ask ("same guard shape as
|
|
`OperationResetAllFieldsToDefaultTouchesEveryDeclaredField`"):
|
|
`EnumProjectionMapsHaveEqualArityAndEveryInternalValueRoundTripsByName` in
|
|
`RuntimeInitialCreateContinuationExecutorTests.cs` - reflection-invokes the
|
|
three private static `Map*` methods against every declared value of their
|
|
internal source enum, asserting (a) equal arity between the internal and
|
|
public enum, and (b) every mapped public value's `.ToString()` name equals
|
|
the internal value's name (the mappers are literal 1:1 name mirrors by
|
|
design). Sabotage-verified twice, both reverted after confirming failure:
|
|
(1) added a member ONLY to internal `RuntimeTeleportHookPhase` - failed on
|
|
the arity assertion ("RuntimeTeleportHookPhase has 5 values but
|
|
RuntimeInitialCreateTeleportHookPhase has 4"); (2) added the SAME member to
|
|
BOTH the internal and public enum (arity equal) without adding a mapping
|
|
arm - failed via the reflection-invoked `MapHookPhase` throwing
|
|
`ArgumentOutOfRangeException` exactly as designed. Both sabotage edits
|
|
fully reverted; confirmed clean via `git diff` on the touched file showing
|
|
no residual change.
|
|
|
|
Gates re-run after the fix: focused
|
|
Residence|Classifier|Executor|SetPositionState|PlacementProjectionChannel
|
|
filter 242/242 (241 + 1 new); complete AcDream.Runtime.Tests 932/932 (931 +
|
|
1 new); Release build of the full solution 0 errors/21 pre-existing
|
|
warnings; `git diff --check` clean (only pre-existing LF/CRLF notices, same
|
|
files as before). `git status` shows the same files as the prior C3-1
|
|
checkpoint dirty, PLUS one file I did NOT touch:
|
|
`docs/plans/2026-08-02-placement-cutover.md` now shows a diff decomposing
|
|
C3 into C3a/C3b/C3c based on my earlier Finding B/C report - this was made
|
|
externally (not by this session; I never opened that file for editing this
|
|
round) and is left exactly as found, unstaged. Nothing staged by me,
|
|
nothing committed, HEAD unchanged at
|
|
a32aba35d1d945b9d3194a84e70facf74a7d7608.
|
|
|
|
Files touched this round (all within C3-1's original scope, no new files):
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
(Map* explicit arms + throws, ProjectCompletion null-check, doc-comment
|
|
addenda on RuntimeInitialCreatePositionRouteFact and
|
|
RuntimeInitialCreatePlacementCompletion)
|
|
- tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
(+1 new test, +using System.Reflection)
|
|
|
|
================================================================
|
|
C3a -- Runtime first-entry conductor (dormant) -- implementer session
|
|
================================================================
|
|
|
|
## Mandatory first step: full reads completed
|
|
|
|
Read in full before writing any code:
|
|
- src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs
|
|
(1,033 lines).
|
|
- tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerPhysicsPublicationStateTests.cs
|
|
(2,698 lines, all ~35 tests).
|
|
- docs/research/2026-07-31-remaining-physics-campaign-handoff.md (497 lines,
|
|
full) -- route-1's own required order, lines 280-292.
|
|
- docs/plans/2026-08-02-placement-cutover.md (166 lines, full).
|
|
- docs/research/2026-08-02-canonical-body-writer-map.md (681 lines, full).
|
|
- docs/research/2026-08-02-runtime-continuation-executor-handoff.md (211
|
|
lines, full).
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs (1,260
|
|
lines, full).
|
|
- src/AcDream.Runtime/Entities/RuntimeInitialCreateContinuationExecutor.cs
|
|
(Execute/ExecuteCore, lines 1-250 and 860-1080).
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs targeted sections:
|
|
RuntimeEntityPlacementStage enum (30-39), TryBeginExclusiveAuthoredPlacement/
|
|
PrepareDormantLocalActivationOwnership/BeginAcceptedPlacementCore
|
|
(1170-1467), PrepareMover/TryPrepareAndSubmitAuthoredPlacement/
|
|
IsExactPreparedPlacementCurrent (1480-1652), SubmitPreparedPlacementCore
|
|
(2575-2700), RetryDeferred (3986-4020), AcknowledgeProjection (2913-3006).
|
|
- Existing test fixtures for reuse patterns:
|
|
tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateContinuationExecutorTests.cs
|
|
(EngineLifetime/Bind/Spawn/AttachDormantBody/CompleteInitialPlacement,
|
|
lines 1-130 and 4963-5150) and
|
|
tests/AcDream.Runtime.Tests/Physics/RuntimeSetPositionStateTests.cs
|
|
(FakeCollisionSource, TryPrepareAndSubmitAuthoredPlacement tests,
|
|
lines 2660-2760, 3296+).
|
|
|
|
## Step-graph (states x transitions x owning method)
|
|
|
|
[residence Begin -- ALREADY DONE at registration, outside conductor scope]
|
|
RuntimeEntityObjectLifetime.RegisterEntityWithInitialResidence
|
|
-> RuntimeInitialCreateResidenceState.Begin
|
|
-> RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement
|
|
(opens Operation, stage=AwaitingPreparation)
|
|
-> RuntimeSetPositionState.WatchPlacementCompletion(placement)
|
|
yields: RuntimeInitialCreateResidenceLease { Token, Route, Placement }
|
|
|
|
Stage.AwaitingMoverPreparation (conductor entry point)
|
|
precondition: RuntimeInitialCreateResidenceState.TryGetCurrent(record, out
|
|
lease) && lease.Token == residenceToken
|
|
if !lease.Route.PerformsSetPosition (Parented/PickedUp -- never true for a
|
|
real login, kept for structural completeness):
|
|
-> Stage.Acknowledged (skip straight to Execute)
|
|
else:
|
|
RuntimeSetPositionState.TryPrepareAuthoredMover <- NEW extracted method
|
|
(Setup-read via IPreparedCollisionSource, then PrepareMover; stage
|
|
stays AwaitingPreparation; sets authority.Prepared=true)
|
|
RetrySetupUnavailable -> yield AwaitingCollisionSource (retry same stage)
|
|
Prepared -> Stage.MoverPrepared, holds RuntimeSetPositionCommand
|
|
|
|
Stage.MoverPrepared
|
|
RuntimeLocalPlayerPhysicsPublicationState.Prepare(record, lease.Placement,
|
|
command, options, activationPreparation, out pubToken)
|
|
(validates IsExactPreparedPlacementCurrent -- REQUIRES mover already
|
|
prepared; off-canonical body+controller build against scratch clock)
|
|
RejectedAuthority -> abandon (Discard progress; nothing to undo, Prepare
|
|
never mutates on failure)
|
|
Prepared -> Stage.PublicationPrepared, holds pubToken
|
|
|
|
Stage.PublicationPrepared
|
|
RuntimeLocalPlayerPhysicsPublicationState.Commit(pubToken, out
|
|
activationToken)
|
|
-> internally: RuntimeSetPositionState.PrepareDormantLocalActivationOwnership
|
|
(the designed seam -- binds Operation.Body, sets DormantLocalActivation
|
|
=true; requires stage STILL AwaitingPreparation + record.PhysicsBody
|
|
still null)
|
|
-> candidate.Controller.CommitRuntimeOwnership +
|
|
candidate.Record.SetPhysicsBody(body) (record now has a body)
|
|
RejectedToken/RejectedAuthority -> abandon (Publication.Commit already
|
|
self-discards its own candidate on RejectedAuthority; conductor drops
|
|
its own progress entry)
|
|
Committed -> Stage.PublicationCommitted, holds activationToken
|
|
|
|
Stage.PublicationCommitted / Stage.Evaluated (single combined retry point --
|
|
see "CommitActivation resume safety" note below)
|
|
RuntimeLocalPlayerPhysicsPublicationState.EvaluateActivation(activationToken,
|
|
out receipt)
|
|
RejectedToken/RejectedAuthority -> abandon
|
|
Evaluated/DeferredCell/RejectedPlacement -> receipt.IsValid in all three;
|
|
proceed to CommitActivation in the SAME Advance call (mirrors every
|
|
publication test: Evaluate and CommitActivation are always chained,
|
|
never yielded between)
|
|
RuntimeLocalPlayerPhysicsPublicationState.CommitActivation(receipt, out
|
|
projection)
|
|
(internally drives: ground phase -> HitGround/LeaveGround -> post-ground
|
|
-> collision dispatch -> post-collision -> FinalizeActivation, which is
|
|
the SAME retail-staged commit already tested)
|
|
Committed -> Stage.ActivationCommitted, holds projection
|
|
DeferredCell / RejectedPlacement -> yield AwaitingActivation, stage stays
|
|
PublicationCommitted (retry re-runs BOTH EvaluateActivation AND
|
|
CommitActivation next Advance call -- safe even for the internal
|
|
AwaitingFinalShadowPreparation resume path, see note below)
|
|
RejectedAuthority -> abandon
|
|
|
|
Stage.ActivationCommitted
|
|
RuntimeSetPositionState.AcknowledgeProjection(projection.Token)
|
|
("Place receipt -> acknowledgement" -- the SAME ack any host uses; moves
|
|
the watched placement token into _acknowledgedPlacementCompletions,
|
|
operation.Stage -> AwaitingCommitAcknowledgement, operation removed from
|
|
_operations)
|
|
false -> yield AwaitingReceiptAcknowledgement (retry same stage -- only
|
|
fails if not the exact FIFO head; single-entity tests never hit this,
|
|
documented for completeness)
|
|
true -> Stage.Acknowledged
|
|
|
|
Stage.Acknowledged (or skipped-straight-here for a non-SetPosition route)
|
|
RuntimeInitialCreateContinuationExecutor.Execute(record, residenceToken,
|
|
inputs, out executionReceipt)
|
|
(internally: RuntimeInitialCreateResidenceState.Complete -- now succeeds
|
|
because IsPlacementCurrent(lease.Placement)==false and
|
|
TryPeekAcknowledgedPlacement succeeds -- -> AdoptCompletedPlacement ->
|
|
AfterEnterWorld hook -> deferred replay -> FIFO drain -> ConsumeExecuted)
|
|
Completed -> conductor Stage.Completed (progress removed) -> terminal
|
|
PendingPlacement -> yield AwaitingReceiptAcknowledgement (residence still
|
|
sees the operation as unacknowledged/current -- should not normally
|
|
recur once we've truly acknowledged, kept as a defensive yield)
|
|
AwaitingContinuationPlacement -> yield AwaitingContinuationPlacement
|
|
(a LATER Position continuation needs its own placement -- entirely the
|
|
EXECUTOR's own concern from here; conductor just passes the status
|
|
through, mirroring "Execute (FIFO drain) -> ExecutorCompleted receipt"
|
|
being the conductor's LAST step, not something it re-implements)
|
|
RejectedToken/RejectedAuthority -> abandon
|
|
|
|
Typed yields exposed (RuntimeLocalPlayerFirstEntryStatus): Completed,
|
|
AwaitingCollisionSource, AwaitingActivation, AwaitingReceiptAcknowledgement,
|
|
AwaitingContinuationPlacement, Contention (reentrancy-guard-only -- mirrors
|
|
the executor's own `_executing.Add(key)` fail-closed pattern), RejectedToken,
|
|
RejectedAuthority. "awaiting-preparation" from the contract's five named
|
|
yields is folded into AwaitingCollisionSource / the MoverPrepared ->
|
|
PublicationPrepared span (see reconciliation below) rather than kept as an
|
|
eighth separate value -- documented at the enum declaration.
|
|
|
|
## Reconciliation against route-1 + CONTRADICTION FOUND (resolved, did not
|
|
## silently redesign)
|
|
|
|
Campaign handoff route-1 order (2026-07-31-remaining-physics-campaign-handoff.md:280-292):
|
|
1. register identity cellless
|
|
2. begin initial/remote-create placement before hydration
|
|
3. load exact Setup mover
|
|
4. prepare the atomic Runtime controller/body relationship
|
|
5. submit canonical SetPosition
|
|
6. publish presentation only from Place
|
|
7. acknowledge, then enable player mode/simulation
|
|
|
|
Step 3 (mover) precedes step 4 (controller/body prepare) here.
|
|
|
|
The C3a contract's OWN restated PURPOSE-section order instead reads:
|
|
"... atomic Commit binding the body to the residence's EXACT placement
|
|
token (PrepareDormantLocalActivationOwnership is the designed seam)
|
|
-> authored-mover preparation + submission (C0's
|
|
TryPrepareAndSubmitAuthoredPlacement ...)"
|
|
i.e. it places mover-prep AFTER Publication Prepare/Commit -- the OPPOSITE of
|
|
route-1's own order 3-then-4.
|
|
|
|
Verified against the actual staged semantics that this restated order is
|
|
IMPOSSIBLE and additionally that the named mechanism cannot be reused as
|
|
worded:
|
|
1. RuntimeLocalPlayerPhysicsPublicationState.CanPrepare
|
|
(RuntimeLocalPlayerPhysicsPublicationState.cs:886-889) requires
|
|
_physics.SetPosition.IsExactPreparedPlacementCurrent(record, placement,
|
|
command) to ALREADY be true. IsExactPreparedPlacementCurrent
|
|
(RuntimeSetPositionState.cs:1627-1651) requires
|
|
authority.Prepared && authority.PreparedCommand == command -- i.e.
|
|
PrepareMover MUST have already succeeded for this exact command BEFORE
|
|
Publication.Prepare can even be called. Mover-prep cannot happen after
|
|
Commit; it structurally gates entry into Prepare.
|
|
2. TryPrepareAndSubmitAuthoredPlacement (RuntimeSetPositionState.cs:1562-1625)
|
|
is PrepareMover followed unconditionally by SubmitPreparedPlacement.
|
|
SubmitPreparedPlacementCore (RuntimeSetPositionState.cs:2584-2630)
|
|
requires operation.Record.PhysicsBody is not {} body -- i.e. a body must
|
|
ALREADY exist. Before Commit, the record has no body (Commit is what
|
|
attaches one); calling this fused method before Commit would reject.
|
|
Calling it AFTER Commit (as the contract's restated order implies) would
|
|
NOT reject -- the body now exists -- but RetryDeferred's own comment
|
|
(RuntimeSetPositionState.cs:3988-3993) states explicitly: "The local-
|
|
player activation lease owns its dormant body/controller and must
|
|
re-enter through the same sealed evaluation/commit path ... it must
|
|
never bypass that path through the ordinary remote CommitCanonical
|
|
tail." SubmitPreparedPlacementCore has no DormantLocalActivation
|
|
exclusion check, so calling it post-Commit would silently route the
|
|
operation through the wrong (ordinary) commit tail in parallel with the
|
|
dormant Evaluate/Commit/FinalizeActivation chain -- corrupting state.
|
|
3. Confirmed empirically: the EXISTING executor test suite
|
|
(RuntimeInitialCreateContinuationExecutorTests.cs's
|
|
AttachDormantBody/CompleteInitialPlacement helpers, lines 5007-5073)
|
|
treats even isLocalPlayer: true fixtures via a direct
|
|
Entities.SetPhysicsBody + ordinary SubmitPreparedPlacement -- NEVER
|
|
through RuntimeLocalPlayerPhysicsPublicationState -- because for a
|
|
record that never sets DormantLocalActivation, the ordinary tail is
|
|
exactly correct. This is a test-only substitute for what C3a's conductor
|
|
now performs for real; it is not evidence that the ordinary tail is ever
|
|
valid for a DormantLocalActivation operation.
|
|
|
|
Resolution: route-1's own order (mover-prep BEFORE the controller/body
|
|
prepare step) is correct and consistent with every tested invariant; the
|
|
C3a contract's restated PURPOSE-section prose transposed the two steps.
|
|
Per the contract's own instruction ("STOP with file:line evidence" rather
|
|
than silently redesigning the PUBLICATION CHAIN), this is flagged here with
|
|
full citations; the conductor is implemented using route-1's order because
|
|
(a) that is what the contract explicitly told me to reconcile against, and
|
|
(b) it is the only order that satisfies the publication chain's own
|
|
staged/tested preconditions without changing a single line of already-
|
|
tested code. The publication chain itself (Prepare/Commit/EvaluateActivation/
|
|
CommitActivation/FinalizeActivation) is NOT modified or reinterpreted -- only
|
|
the CONDUCTOR's call order was corrected relative to the contract's prose.
|
|
|
|
Mechanism correction: "C0's TryPrepareAndSubmitAuthoredPlacement" as named in
|
|
the contract is the WRONG vehicle for the local-player dormant path for the
|
|
reason in point 2 above (it ends in SubmitPreparedPlacement, forbidden
|
|
once DormantLocalActivation is set). RuntimeSetPositionState.cs gained one
|
|
new internal method, TryPrepareAuthoredMover, extracted verbatim from
|
|
TryPrepareAndSubmitAuthoredPlacement's FIRST HALF (Setup-read + PrepareMover
|
|
call only, no Submit) -- a pure, behavior-preserving refactor.
|
|
TryPrepareAndSubmitAuthoredPlacement itself now calls this shared helper
|
|
then submits, unchanged in every observable respect (its own two existing
|
|
tests, TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit
|
|
and ..._YieldsRetryOnAMissingSetupReadWithoutMutatingStage, stay green
|
|
unmodified). The conductor calls ONLY the new TryPrepareAuthoredMover half.
|
|
|
|
## CommitActivation resume safety note (why one retry stage suffices)
|
|
|
|
Verified that re-running EvaluateActivation before every CommitActivation
|
|
retry -- rather than adding a THIRD stage that resumes CommitActivation alone
|
|
for the AwaitingFinalShadowPreparation internal resumption path -- is safe:
|
|
CommitActivation's own top-of-method resume check
|
|
(activation.PendingFinalCommit.Status is AwaitingFinalShadowPreparation,
|
|
RuntimeSetPositionState.cs:498-506) fires only AFTER re-validating
|
|
activation.Receipt == receipt; a fresh EvaluateActivation call sets
|
|
activation.Receipt to match whatever it just returned, so passing that
|
|
same fresh receipt back into CommitActivation satisfies the equality check
|
|
and the stored PendingFinalCommit (untouched by the extra Evaluate call)
|
|
still drives the correct resume via FinalizeActivation. Confirmed
|
|
DeferredCell/RejectedPlacement from CommitActivation clear
|
|
activation.Receipt to default (RuntimeSetPositionState.cs:546,628) -- so a
|
|
fresh Evaluate is REQUIRED, not just tolerated, on those two outcomes. The
|
|
one cost is a redundant extra Engine.SetPosition resolve in the (rare,
|
|
contention-only) AwaitingFinalShadowPreparation case -- not a correctness
|
|
issue, and simpler than tracking a fourth stage.
|
|
|
|
## Files this session will add/touch
|
|
|
|
- src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs -- extract
|
|
TryPrepareAuthoredMover (new internal method; TryPrepareAndSubmitAuthoredPlacement
|
|
now delegates to it, unchanged behavior).
|
|
- src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs -- NEW,
|
|
the conductor. Dormant; no production caller; constructed only in tests.
|
|
- tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs
|
|
-- NEW.
|
|
|
|
No changes to RuntimeLocalPlayerPhysicsPublicationState.cs,
|
|
RuntimeInitialCreateResidenceState.cs, RuntimeInitialCreateContinuationExecutor.cs,
|
|
RuntimeEntityObjectLifetime.cs, or GameRuntime.cs (C3c wires production
|
|
callers and residence-retirement fan-out; that is explicitly out of C3a's
|
|
scope). One consequence documented for C3c: because
|
|
RuntimeInitialCreateResidenceState.BindRetirementNotification is a single-
|
|
subscriber seam already bound to InitialCreateExecution.DiscardProgress
|
|
inside RuntimeEntityObjectLifetime's constructor, the conductor built here
|
|
does NOT receive a push notification on external residence retirement; it
|
|
relies on lazy re-validation at the top of every Advance call plus an
|
|
explicit Forget(key) a future host can call. C3c will need to either fan the
|
|
single notification out to both subscribers or route it through the
|
|
conductor.
|
|
|
|
## Implementation complete — gates passed
|
|
|
|
Final design (RuntimeLocalPlayerFirstEntryState.cs, 423 lines) — a five-stage
|
|
resumable machine (AwaitingMoverPreparation -> MoverPrepared ->
|
|
PublicationCommitted -> ActivationCommitted -> Acknowledged), one
|
|
`Advance(record, residenceToken, options, activationPreparation,
|
|
collisionSource, gameTime, inputs, out receipt)` entry point, an
|
|
`_executing` HashSet reentrancy guard mirroring the executor's own, a
|
|
`Progress` class (LeaseId + Stage + the exact token/receipt/projection
|
|
structs) keyed by `RuntimeEntityKey`, and a `Discard`/`Forget` pair that
|
|
unconditionally calls `Publication.Discard`/`DiscardActivation` (both
|
|
harmless no-ops against a default/unreached-stage token).
|
|
|
|
Bugs found and fixed during test-driven verification (all via a temporary
|
|
diagnostic build with Console.WriteLine probes, removed before the final
|
|
commit-ready state):
|
|
1. **EvaluateActivation's overloaded DeferredCell status.** Once a PRIOR
|
|
CommitActivation call has registered a lease as awaiting a specific cell
|
|
(`IsDormantLocalActivationAwaitingCell`), a REPEATED EvaluateActivation
|
|
call that is still not ready returns DeferredCell WITHOUT populating its
|
|
receipt (stays default/invalid) — confirmed against
|
|
`DeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWake` in the
|
|
publication suite, which asserts exactly `waiting.IsValid == false` on
|
|
that repeat and never calls CommitActivation with it. The conductor now
|
|
checks `evalReceipt.IsValid` before ever calling CommitActivation,
|
|
short-circuiting straight to AwaitingActivation when it is false, instead
|
|
of blindly forwarding an invalid receipt (which CommitActivation's own
|
|
`!receipt.IsValid` guard would reject as RejectedAuthority).
|
|
2. **Re-acknowledging an already-consumed projection token.** An earlier
|
|
draft used a single "Acknowledged" stage value to mean both "just
|
|
committed, ack not yet attempted" and "ack already succeeded", causing a
|
|
retry AFTER a successful acknowledgement (e.g. because Execute yielded
|
|
AwaitingContinuationPlacement) to call AcknowledgeProjection a second
|
|
time against a token AcknowledgeProjection had already removed from its
|
|
FIFO — always failing. Split into two distinct stages
|
|
(ActivationCommitted = ack not yet attempted; Acknowledged = ack done,
|
|
only Execute remains) so the ack call only ever runs once per projection.
|
|
3. **RejectedToken vs RejectedAuthority at the residence-lookup checks.**
|
|
Mirrored `RuntimeInitialCreateResidenceState.Complete`'s own convention:
|
|
"nothing was ever tracked for this key" (no Progress entry, residence not
|
|
found) is RejectedToken; "something WAS in flight and just got
|
|
invalidated" (Progress entry existed, now stale) is RejectedAuthority —
|
|
matching the executor's identical split for the analogous case.
|
|
|
|
Real (not test-only) findings surfaced by writing the tests, documented in
|
|
the test file itself:
|
|
- `RuntimeEntityRecord.Key` is computed from a nullable `LocalEntityId` and
|
|
becomes `null` the instant `ReleaseLocalId` runs (part of delete's
|
|
teardown in `RuntimeEntityObjectLifetime.CompleteProjectionRetirement`).
|
|
`Advance`'s entry check (`record.Key is not {} key -> RejectedToken`)
|
|
EXACTLY mirrors `RuntimeInitialCreateContinuationExecutor.Execute`'s own
|
|
entry check — once Key is null, the conductor cannot compute its own
|
|
dictionary key to reach stale progress at all. A caller that wants
|
|
deterministic cleanup after full teardown must capture the
|
|
`RuntimeEntityKey` BEFORE deletion and call `Forget(key)` explicitly; this
|
|
is a pre-existing convention in the codebase (the executor has the
|
|
identical limitation), not a defect introduced here.
|
|
- `RuntimeLocalPlayerPhysicsPublicationState` holds exactly ONE global
|
|
`_candidate`/`_activation` (instance fields, not per-key) — correct, since
|
|
there is only ever one local player — but it means an orphaned, un-Forgot
|
|
first-entry attempt for a stale incarnation will structurally block a
|
|
fresh incarnation's own `Publication.Prepare` (CanPrepare requires
|
|
`_activation is null`) until `Forget` runs. Proven by
|
|
`DeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlot`.
|
|
- `Movement.ResetSession()` proactively nulls Publication's `_activation`
|
|
directly (unlike delete, which only makes it stale via
|
|
`_entities.IsCurrent`/epoch checks) — so a retry after ResetSession sees
|
|
EvaluateActivation report RejectedToken (activation genuinely gone), not
|
|
RejectedAuthority (activation found but stale) — and, because
|
|
ResetSession never touches `RuntimeEntityRecord.Key`, ordinary retry alone
|
|
(no captured-key Forget) reaches Discard and converges.
|
|
|
|
### Final gate results
|
|
|
|
- `dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release`:
|
|
0 errors, 0 warnings.
|
|
- `dotnet build AcDream.slnx -c Release`: 0 errors, 21 pre-existing warnings
|
|
(same count/files as the C3-1 checkpoint; none new).
|
|
- Focused filter
|
|
`FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry`:
|
|
308/308 passed.
|
|
- Complete `AcDream.Runtime.Tests`: 944/944 passed (932 baseline + 12 new),
|
|
0 skips.
|
|
- Complete solution (`dotnet test AcDream.slnx -c Release --no-build -m:1`):
|
|
every project reports 0 failed — App 4028/3 skips, Bake 15/0, Cli 4/0,
|
|
Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime
|
|
944/0, UI.Abstractions 543/0.
|
|
- `git diff --check`: clean (only pre-existing LF/CRLF notices on the same
|
|
eight paths as every prior checkpoint; AGENTS.md is the only one with a
|
|
real, pre-existing, untouched content diff; `RuntimeSetPositionState.cs`
|
|
shows exactly my own 60/8 insertion/deletion extraction, nothing else).
|
|
- No staging, no commits, HEAD unchanged at `277ef5d0`.
|
|
|
|
### Files touched (final)
|
|
|
|
- `src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs` — extracted
|
|
`TryPrepareAuthoredMover` (new internal method, Setup-read + PrepareMover
|
|
only); `TryPrepareAndSubmitAuthoredPlacement` now delegates to it then
|
|
submits, byte-identical observable behavior (its own two existing tests
|
|
pass unmodified).
|
|
- `src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs` — NEW,
|
|
423 lines. Dormant; zero production callers (verified by the focused/
|
|
complete/full-solution gates above, which exercise it only from the new
|
|
test file).
|
|
- `tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs`
|
|
— NEW, 12 tests: full-sequence happy path; AwaitingCollisionSource retry
|
|
+resume; AwaitingActivation retry+resume (generation wake); AwaitingReceipt
|
|
Acknowledgement retry+resume (FIFO-head contention via a second entity's
|
|
unacknowledged Place); AwaitingContinuationPlacement propagation+resume;
|
|
reentrant Advance during a collision callback (Contention, outer call
|
|
still completes); two retry-idempotency tests (mover-preparation and
|
|
AwaitingActivation stages never re-create a candidate/duplicate a body);
|
|
mid-flight delete-during-collision-callback abandonment (no Place/shadow
|
|
published, full convergence); delete-while-AwaitingActivation requiring an
|
|
explicit captured-key Forget; ResetSession mid-flight converging through
|
|
ordinary retry; delete+same-GUID-reincarnation requiring Forget before the
|
|
fresh incarnation can use Publication's one global slot.
|
|
|
|
RuntimeLocalPlayerPhysicsPublicationState.cs, RuntimeInitialCreateResidenceState.cs,
|
|
RuntimeInitialCreateContinuationExecutor.cs, RuntimeEntityObjectLifetime.cs, and
|
|
GameRuntime.cs are all untouched, exactly as scoped.
|
|
|
|
================================================================
|
|
Review round 2 -- F1 (MAJOR) + F2 (MINOR) fixes
|
|
================================================================
|
|
|
|
## F1 (MAJOR) -- ack-failure authority re-validation
|
|
|
|
Root cause confirmed: the ActivationCommitted stage treated EVERY
|
|
AcknowledgeProjection failure as the generic "not yet FIFO head" case.
|
|
TryAcceptDelete -> CompleteProjectionRetirement -> Physics.SetPosition.Forget
|
|
-> CancelCore rewrites the SAME pending slot from Place to Discard with a
|
|
bumped Revision; the RuntimePlacementProjectionToken struct this class
|
|
already cached in progress.Projection can then never match the FIFO head
|
|
again, so AcknowledgeProjection would fail forever -- infinite
|
|
AwaitingReceiptAcknowledgement for a dead entity, Progress retained,
|
|
IsConverged false forever, exactly as reported.
|
|
|
|
Fix: added IsAcknowledgementStillPending(record, residenceToken, expected),
|
|
called on every failed acknowledge before deciding retryable vs abandon.
|
|
Two checks, either failing means authority moved:
|
|
1. _residences.TryGetCurrent(record, out lease) && lease.Token ==
|
|
residenceToken -- the SAME residence-lookup pattern stage0/stage1
|
|
already use.
|
|
2. _physics.SetPosition.TryPeekProjection(out head) -- if the FIFO head
|
|
belongs to THIS entity (head.Token.Entity == expected.Entity) but is no
|
|
longer the exact Place token expected (kind changed, or revision
|
|
bumped), authority for THIS SPECIFIC placement moved even if the
|
|
residence lookup alone would not have caught it. A head belonging to a
|
|
DIFFERENT entity is the genuine "not our turn yet" case and stays
|
|
retryable.
|
|
Both failing -> Discard(key) + RejectedAuthority. Read
|
|
RuntimeSetPositionState.TryPeekProjection directly (Runtime-internal-to-
|
|
internal), not through the public generation-gated
|
|
RuntimePlacementProjectionChannel -- this class is part of Runtime, not an
|
|
external host crossing that boundary, exactly like its existing direct
|
|
AcknowledgeProjection call.
|
|
|
|
Real discovery made while testing this: FinalizeActivation nulls
|
|
Publication's OWN tracked `_activation` the INSTANT CommitActivation's
|
|
final commit succeeds (RuntimeLocalPlayerPhysicsPublicationState.cs
|
|
FinalizeActivation, `_activation = null;` right after
|
|
TryApplyDormantLocalActivationFinalCommit succeeds) -- the controller is
|
|
genuinely live/published from that point on, not a discardable in-progress
|
|
candidate. So once Stage.ActivationCommitted is reached,
|
|
Publication.DiscardActivation is ALREADY a no-op on the controller/body;
|
|
abandoning a stuck acknowledgement never retroactively un-publishes an
|
|
already-live entity -- that is ordinary entity teardown's job, not this
|
|
class's. Documented on Discard's own doc comment and confirmed empirically
|
|
(a diagnostic build showed PendingActivationCount == 0 already at the FIRST
|
|
ack attempt, before any authority change).
|
|
|
|
New test:
|
|
`DeleteWhileAwaitingReceiptAcknowledgementAbandonsInsteadOfRetryingForeverAndConverges`
|
|
-- reaches ActivationCommitted (blocked behind another entity's own
|
|
unacknowledged Place, same technique as the existing FIFO-head test), then
|
|
calls the EXACT narrower mechanism TryAcceptDelete itself uses
|
|
(`Physics.SetPosition.Forget(record, releasePreparedMover: true)` +
|
|
`PublishCancellation`) directly rather than a full entity delete -- this
|
|
was deliberate: a FULL delete now ALSO retires the residence, and F2's
|
|
automatic retirement fan-out (below) would converge everything before a
|
|
second Advance ever ran, masking whether THIS authority-recheck code path
|
|
itself works. The narrower call proves the fix independent of F2's
|
|
wiring. Asserts RejectedAuthority, ActiveCount/PendingActivationCount
|
|
converge to 0, and the UNRELATED entity's own placement remains
|
|
unaffected and acknowledgeable.
|
|
|
|
## F2 (MINOR) -- ownership fold + cleanup wiring
|
|
|
|
(a) RuntimeInitialCreateResidenceState.BindRetirementNotification converted
|
|
from a single nullable Action field (throw-on-second-bind) to a
|
|
`List<Action<RuntimeEntityKey>>` (ordered, registration-order invocation via
|
|
a new private NotifyRetirement(key) helper). Null-arg throw preserved
|
|
(ArgumentNullException.ThrowIfNull); the "already bound" throw is gone by
|
|
design since multiple subscribers are now the point. All 5 existing
|
|
invocation sites (Forget x2, Clear's two loops, Retire(Entry),
|
|
Retire(CompletedEntry)) now call NotifyRetirement instead of
|
|
`_retirementNotification?.Invoke`.
|
|
|
|
(b) RuntimeLocalPlayerFirstEntryState's constructor no longer takes
|
|
RuntimeLocalPlayerPhysicsPublicationState (RuntimeEntityObjectLifetime is
|
|
constructed BEFORE Publication exists -- GameRuntime builds
|
|
RuntimeLocalPlayerMovementState and attaches its publication only after the
|
|
entity-object lifetime). Added the SAME late-bind pattern already used
|
|
throughout this class family (BindGeneration, BindRetirementNotification,
|
|
BindLiveInputs, RuntimeLocalPlayerMovementState.PhysicsPublication's own
|
|
throws-if-unbound accessor): `BindPublication(publication)` (bind-once,
|
|
throws on null/double-bind) + a private `Publication` accessor that throws
|
|
if unbound. All internal `_publication.X` call sites became `Publication.X`.
|
|
Added `DiscardAll()` mirroring the executor's own (discards every tracked
|
|
key's candidate/activation, then clears `_progress`).
|
|
RuntimeEntityObjectLifetime now constructs `LocalPlayerFirstEntry` in all 3
|
|
constructors (right after InitialCreateExecution, same pattern), binds a
|
|
SECOND retirement notification (`key => LocalPlayerFirstEntry.Forget(key)`,
|
|
alongside the executor's existing one), and `BeginSessionClear` calls
|
|
`LocalPlayerFirstEntry.DiscardAll()` right after
|
|
`InitialCreateExecution.DiscardAll()`. `RuntimeEntityObjectOwnershipSnapshot`
|
|
gained `LocalPlayerFirstEntryActiveCount = 0` (trailing default, matching
|
|
the file's existing convention), folded into `IsConverged` and into
|
|
`CaptureOwnership()`'s construction. GameRuntime.cs itself was NOT touched
|
|
(BindPublication is never called in production) -- deliberate: since
|
|
Advance is never called in production, `_progress` stays permanently empty,
|
|
so Forget/DiscardAll never actually dereference Publication regardless of
|
|
binding state; wiring the production BindPublication call is left as a
|
|
natural part of C3c's Advance-caller work, not manufactured here.
|
|
|
|
(c) Updated the two named tests plus my own new delete tests to use
|
|
`Lifetime.LocalPlayerFirstEntry` (bound via `.BindPublication(Publication)`)
|
|
instead of a separately-constructed conductor instance -- this is what
|
|
actually exercises the real wiring; a standalone instance would never see
|
|
the fan-out at all.
|
|
- `DeleteWhileAwaitingActivationRequiresForgetOfTheCapturedKeyToDiscardTheDormantActivation`
|
|
renamed to `DeleteWhileAwaitingActivationConvergesAutomaticallyThroughTheRetirementFanOut`:
|
|
delete alone now converges ActiveCount/PendingActivationCount to 0 with
|
|
NO explicit host Forget call; a follow-up Advance is a safe RejectedToken
|
|
no-op (Key already null).
|
|
- `DeleteAndSameGuidReincarnationRequiresForgetBeforeTheFreshIncarnationCanUseThePublicationSlot`
|
|
renamed to `DeleteAndSameGuidReincarnationAutomaticallyFreesThePublicationSlotForTheFreshIncarnation`:
|
|
the fresh incarnation's own Prepare now succeeds immediately after delete,
|
|
no Forget call in between.
|
|
No standalone-conductor variant was kept -- delete-triggered convergence
|
|
IS the production path once RuntimeEntityObjectLifetime owns construction,
|
|
so an explicit-Forget test would only be meaningful for a conductor built
|
|
outside the lifetime, which is not a real usage shape this slice needs to
|
|
cover (the F1 test's narrower Physics.SetPosition.Forget-only scenario
|
|
already demonstrates the authority-recheck's own logic independent of the
|
|
fan-out, satisfying that documentation need instead).
|
|
|
|
Fixture.Dispose ordering bug found and fixed along the way: disposing
|
|
Movement (which tears down Publication) BEFORE Lifetime (whose Dispose runs
|
|
BeginSessionClear, which now reaches LocalPlayerFirstEntry.DiscardAll ->
|
|
Publication.Discard for any still-tracked entity) threw
|
|
ObjectDisposedException whenever a test left real progress untracked at
|
|
teardown (e.g. the AwaitingActivation retry-idempotency test, which never
|
|
completes or deletes within the test body). Fixed by disposing Lifetime
|
|
FIRST. Documented as a real ordering constraint for whoever eventually
|
|
disposes GameRuntime in production, since the identical dependency exists
|
|
there (RuntimeEntityObjectLifetime's conductor holds a bound reference to
|
|
Publication via BindPublication).
|
|
|
|
## Final gate results (round 2)
|
|
|
|
- `dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release`:
|
|
0 errors, 0 warnings.
|
|
- `dotnet build AcDream.slnx -c Release`: 0 errors, 21 pre-existing warnings
|
|
(unchanged).
|
|
- Focused filter
|
|
`FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry`:
|
|
309/309 passed (308 + 1 new F1 test).
|
|
- Complete `AcDream.Runtime.Tests`: 945/945 passed (932 baseline + 13 new),
|
|
0 skips.
|
|
- Complete solution (`dotnet test AcDream.slnx -c Release --no-build -m:1`):
|
|
every project reports 0 failed -- App 4028/3 skips, Bake 15/0, Cli 4/0,
|
|
Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime
|
|
945/0, UI.Abstractions 543/0.
|
|
- `git diff --check`: clean. Modified files now include
|
|
`RuntimeEntityObjectLifetime.cs` and `RuntimeInitialCreateResidenceState.cs`
|
|
in addition to the round-1 `RuntimeSetPositionState.cs` -- all three
|
|
diffs are additive/expected (64, 39, 68 changed lines respectively via
|
|
`git diff --stat`); the pre-existing eight dirty paths are otherwise
|
|
unchanged (line-ending noise only).
|
|
- No staging, no commits, HEAD unchanged at `277ef5d0`.
|
|
|
|
## Files touched (round 2 additions)
|
|
|
|
- `src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs` --
|
|
now 663 lines (was 540): F1's IsAcknowledgementStillPending; F2's
|
|
BindPublication/Publication accessor replacing the constructor
|
|
parameter; DiscardAll().
|
|
- `src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs` --
|
|
BindRetirementNotification multicast conversion.
|
|
- `src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs` --
|
|
LocalPlayerFirstEntry property + construction in all 3 ctors + second
|
|
retirement-notification bind + BeginSessionClear wiring +
|
|
RuntimeEntityObjectOwnershipSnapshot field/IsConverged/CaptureOwnership
|
|
fold.
|
|
- `tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs`
|
|
-- now 827 lines (was 758): 1 new F1 test; Fixture now binds/uses
|
|
`Lifetime.LocalPlayerFirstEntry` instead of a standalone instance; fixed
|
|
Dispose ordering; 2 tests renamed and rewritten for automatic
|
|
convergence per F2(c).
|
|
|
|
GameRuntime.cs remains untouched (see F2(b) note above for why). No
|
|
production caller of Advance anywhere.
|
|
|
|
================================================================
|
|
Review round 3 -- H1 + H2 hardening (final verdicts: retail PASS,
|
|
architecture PASS)
|
|
================================================================
|
|
|
|
## H1 -- NotifyRetirement snapshot before iterating
|
|
|
|
`RuntimeInitialCreateResidenceState.NotifyRetirement` iterated the live
|
|
`_retirementNotifications` List<> directly. A subscriber binding a NEW
|
|
notification from inside a retirement callback it is itself receiving
|
|
(unreachable today -- only 2 subscribers exist, neither rebinds -- but
|
|
becomes reachable the instant C3c adds a runtime-bound third subscriber)
|
|
would throw "Collection was modified" on the very next iteration step.
|
|
Fixed per the reviewer's exact instruction, matching
|
|
`RuntimeEntityObjectEventStream`'s own copy-on-write dispatch precedent:
|
|
`foreach (... in _retirementNotifications.ToArray())`. `ToArray()` (not a
|
|
`Volatile`-guarded array swap like the event stream) was the right
|
|
granularity here since binding only ever happens a handful of times at
|
|
construction, never on a hot per-frame path -- documented on the method's
|
|
own doc comment with that reasoning spelled out.
|
|
|
|
New test (`RuntimeInitialCreateResidenceStateTests.cs`):
|
|
`RetirementNotificationBoundReentrantlyDuringDispatchDoesNotCorruptTheCurrentIteration`
|
|
-- binds a notification that, on its first invocation, reentrantly binds a
|
|
THIRD one; triggers a real retirement via `Forget`; asserts no exception,
|
|
that the newly-bound subscriber does NOT see the in-flight retirement (not
|
|
required to), and that it DOES see the next one.
|
|
|
|
## H2 -- unbound-Publication transactional failure
|
|
|
|
Root cause confirmed: `AdvanceCore` had no check for an unbound
|
|
`_publication` before mutating anything. An Advance call in the window
|
|
before a host calls `BindPublication` would run the FULL authored-mover
|
|
Setup-read/PrepareMover call (mutating `RuntimeSetPositionState`'s own
|
|
`_preparedMovers`) and create/store this class's own `Progress` entry
|
|
BEFORE the FIRST `Publication` dereference (inside the `MoverPrepared`
|
|
stage) throws -- leaving a poisoned `Progress` entry in `_progress` that a
|
|
LATER, unrelated `Discard`/`DiscardAll` call (from a retirement
|
|
notification or session-clear fan-out) would ALSO throw on, corrupting
|
|
someone else's teardown.
|
|
|
|
Fix: `AdvanceCore`'s very first statement (before `_progress.TryGetValue`,
|
|
before the ABA check, before anything) is now `_ = Publication;` -- the
|
|
existing throws-if-unbound accessor, referenced purely for its side
|
|
effect, so the whole call fails transactionally with nothing yet mutated.
|
|
Also hardened `Discard`/`DiscardAll` to tolerate an unbound `_publication`
|
|
defensively (`if (_publication is null) return;` before touching
|
|
`Publication.Discard`/`DiscardActivation`) -- both documented as
|
|
structurally unreachable post-H2 (a `Progress` entry can only exist if
|
|
`Advance` already ran, which now requires a bound `Publication` first) and
|
|
guarded anyway as belt-and-suspenders so no future caller shape can turn
|
|
an already-surfaced `Advance` failure into a SECOND throw from inside an
|
|
unrelated fan-out.
|
|
|
|
New tests (`RuntimeLocalPlayerFirstEntryStateTests.cs`):
|
|
- `AdvanceWithUnboundPublicationThrowsTransactionallyBeforeAnyStateMutation`
|
|
-- constructs a bare `RuntimeEntityObjectLifetime` (its own
|
|
`LocalPlayerFirstEntry` is naturally unbound, since only this test file's
|
|
own `Fixture` calls `BindPublication`), registers a residence, calls
|
|
`Advance` with no bound Publication, asserts the throw AND that the
|
|
residence lease/`ActiveCount` are completely untouched, THEN
|
|
binds a real Publication and confirms the SAME token still drives
|
|
correctly to `AwaitingActivation` -- proving nothing was corrupted by the
|
|
failed attempt.
|
|
- `BindPublicationTwiceThrows` -- the standard bind-once guard test,
|
|
matching every other `BindX` method in this class family.
|
|
|
|
## Final gate results (round 3, last of the slice)
|
|
|
|
- `dotnet build src/AcDream.Runtime/AcDream.Runtime.csproj -c Release`:
|
|
0 errors, 0 warnings.
|
|
- `dotnet build AcDream.slnx -c Release`: 0 errors (warning count reported
|
|
as 0 on this incremental rebuild since no other project's files changed
|
|
and MSBuild skipped re-analyzing them as up-to-date; the prior two
|
|
rounds already confirmed 21 pre-existing warnings, all in untouched test
|
|
files, with a from-scratch build).
|
|
- Focused filter
|
|
`FullyQualifiedName~Publication|~Residence|~Executor|~SetPositionState|~FirstEntry`:
|
|
312/312 passed (309 + 3 new: 1 H1 + 2 H2).
|
|
- Complete `AcDream.Runtime.Tests`: 948/948 passed (932 baseline + 16 new
|
|
across the whole C3a slice), 0 skips.
|
|
- Complete solution (`dotnet test AcDream.slnx -c Release --no-build -m:1`):
|
|
every project reports 0 failed -- App 4028/3 skips, Bake 15/0, Cli 4/0,
|
|
Content 124/0, Core.Net 762/0, Core 4242/1 skip, Headless 77/0, Runtime
|
|
948/0, UI.Abstractions 543/0.
|
|
- `git diff --check`: clean. Modified files now additionally include
|
|
`tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs`
|
|
(+47/-0, the new H1 test) alongside
|
|
`RuntimeInitialCreateResidenceState.cs` (54 changed lines, up from 39 in
|
|
round 2 -- the ToArray snapshot + doc comment) and the unchanged round-1/2
|
|
files. The pre-existing eight dirty paths remain line-ending noise only.
|
|
- No staging, no commits, HEAD unchanged at `277ef5d0`.
|
|
|
|
## Files touched (round 3 additions)
|
|
|
|
- `src/AcDream.Runtime/Entities/RuntimeInitialCreateResidenceState.cs` --
|
|
`NotifyRetirement` now snapshots via `ToArray()`.
|
|
- `src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerFirstEntryState.cs` --
|
|
now 690 lines (was 663): upfront unbound-Publication check in
|
|
`AdvanceCore`; `Discard`/`DiscardAll` unbound-Publication tolerance.
|
|
- `tests/AcDream.Runtime.Tests/Entities/RuntimeInitialCreateResidenceStateTests.cs`
|
|
-- +1 new H1 test.
|
|
- `tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerFirstEntryStateTests.cs`
|
|
-- now 922 lines (was 827): +2 new H2 tests.
|
|
|
|
This is the last round of code changes for C3a per the coordinator's
|
|
message. GameRuntime.cs remains untouched throughout the whole slice; no
|
|
production caller of Advance anywhere.
|
|
|
|
## C3b implementer progress (remote body construction at Create)
|
|
|
|
- Verified HEAD d62b9950 on codex/port-claude-agents; 8 protected dirty paths untouched.
|
|
- Read: plan (C3b scope), float-gates doc (3 byte-certain gates), retail-notes.md
|
|
(CreateObject 0x00558870 order; set_description 0x00514F40 order; PhysicsDesc::UnPack),
|
|
writer map (6 canonical SetPhysicsBody writers), PhysicsBody.cs,
|
|
RuntimeInitialCreateResidenceState.cs, RuntimeLocalPlayerFirstEntryState.cs (C3a shape),
|
|
RuntimeSetPositionState mover/submit/ack/park/retry paths, RuntimePhysicsState bind sites,
|
|
route classifier, C3a test harness.
|
|
- Resolved SetMotionTableID(0) semantics from pseudo-C: CPhysicsObj::SetMotionTableID
|
|
0x00512780 (pc:280528) fails ONLY when part_array==0 (005127da) or
|
|
MotionTableManager::Create fails for a NONZERO id (CPartArray::SetMotionTableID
|
|
0x005186E0, pc:286732, 0051872f); id==0 skips manager creation (0051871f) and
|
|
returns 1 -> gate PASSES for zero id. CPhysicsObj skips MakeMovementManager for
|
|
INVALID_DID (005127ca).
|
|
- Recovered PhysicsDesc ctor defaults 0x0051D4D0 (pc:292056): friction "33s?" =
|
|
0x3F733333 = 0.95f; elasticity 0.05f; translucency 0 (memset); scale 1; state 0x400c08.
|
|
- Recovered set_elasticity 0x0050FD40 (pc:277817): <0 -> 0; <=0.1 -> value; >0.1 -> 0.1.
|
|
Cross-checked ACE PhysicsGlobals.MaxElasticity = 0.1f (PhysicsObj.cs:3586-3599).
|
|
- Design: RuntimeRemoteFirstEntryState (Entities/, no publication chain) with stages
|
|
MoverPreparation -> BodyConstruction (via canonical RuntimePhysicsState.GetOrCreatePhysicsBody
|
|
factory, retail set_description order) -> Submit (lease.Placement) -> Withdraw/Place ack
|
|
(TryPeekProjection loop for deferred parks) -> Execute. Pure builder
|
|
RuntimeRemoteBodyDescription + construction receipt for gate proofs.
|
|
- IMPLEMENTED: RuntimeRemoteBodyDescription.cs (288 L, pure set_description-ordered
|
|
construction + gated receipt), RuntimeRemoteFirstEntryState.cs (620 L, six-stage
|
|
resumable conductor, no publication chain), lifetime wiring (+51/-2: construction in
|
|
all 3 ctors, third multicast retirement binding, BeginSessionClear DiscardAll,
|
|
RemoteFirstEntryActiveCount snapshot field + IsConverged clause), tests (879 L, 29
|
|
tests, all pass first run).
|
|
- GATES: Runtime build 0W/0E; solution build 0 errors, 21 pre-existing warnings none in
|
|
changed files; focused filter (Residence|Executor|SetPositionState|FirstEntry|
|
|
RemoteEntry) 247/247; complete Runtime 977/977; git diff --check clean; 8 protected
|
|
dirty paths + AGENTS.md untouched; nothing staged/committed.
|
|
- No conflicts stopped on: motion-table zero-id semantics resolved from pseudo-C
|
|
(gate passes for id 0); elasticity clamp recovered (0..0.1) + ACE cross-check;
|
|
PhysicsDesc ctor defaults recovered for absent-wire fields.
|
|
- REVIEW ROUND (arch M1/M2 + retail R1/R2) closed: construction receipt now rides the
|
|
terminal Advance out-param (M1); shared RuntimeFirstEntryAcknowledgement.IsStillPending
|
|
used by BOTH conductors + new delete-after-commit-before-ack abandonment test (M2);
|
|
movement branch re-gated on buffer non-emptiness per UnPack's buff_length!=0 assignment
|
|
(R1, both-ways tests; parser's empty-buffer wrapper confirmed at CreateObject.cs:597);
|
|
elasticity NaN -> 0f per retail's first-arm unordered (R2a, ACE divergence noted);
|
|
friction NaN skip kept + commented per gates-doc quirk (R2b; doc addendum text in
|
|
final report); translucency NaN matches retail apply-bucket already (noted).
|
|
- FINAL GATES: focused 252/252; complete Runtime 982/982; build 0 errors, no warnings in
|
|
changed files; git diff --check exit 0; nothing staged; protected paths untouched.
|
|
|
|
# ============================================================
|
|
# C3c — THE HOST FLIP (new session)
|
|
# ============================================================
|
|
|
|
## Reading phase (complete)
|
|
- Verified worktree HEAD 78f1eb18 on codex/port-claude-agents; 8 dirty files = protected list.
|
|
- Read: c3c-contract.md, plan doc C0-C3b notes, route inventory routes 1+8 + cross-cutting,
|
|
canonical-body-writer-map, both conductors, executor surface, channel + subscription +
|
|
retry slot, both sinks, both session routes, GameRuntime, RuntimeLocalPlayerMovementState,
|
|
PlayerModeController (protected-noise), LiveEntityRuntime placement family, materializer,
|
|
HeadlessSessionWorldProjection, RuntimeLiveEntitySessionController, publication Prepare,
|
|
RuntimeFirstEntryAcknowledgement, synchronous event-stream dispatch, AcknowledgeProjection
|
|
(strict FIFO-head, non-idempotent), PublishExecutorCompletion (snapshot carries body
|
|
position/orientation + token.ExactCellId; residence released before publish).
|
|
|
|
## Design conclusions
|
|
1. One acknowledger per receipt: conductors consume their own initial Place/Withdraw
|
|
(proven by AwaitingReceiptAcknowledgement tests). Both host sinks must return false
|
|
(leave-at-head) for Place/Withdraw of an entity with an ACTIVE initial-create
|
|
residence (TryGetInitialCreateResidence discriminator).
|
|
2. ExecutorCompleted = the presentation-binding receipt (C3-1's purpose). Graphical sink
|
|
applies Place-shaped presentation from its snapshot (celless => ack-and-ignore);
|
|
returns false until sidecar+backend ready (existing FIFO-head retry semantics).
|
|
3. Drive points: graphical = post-hydration in the Create flow + wrap the per-frame retry
|
|
slot callback (drive conductors then RetryPending). Continuation placements completed
|
|
via C0 fused TryPrepareAndSubmitAuthoredPlacement + AcknowledgeProjection.
|
|
4. GameRuntime first act: LocalPlayerFirstEntry.BindPublication(Movement.PhysicsPublication)
|
|
after AttachPhysicsPublication (GameRuntime.cs:260-265).
|
|
|
|
## C3c implementation state at session end
|
|
- ALL FIVE SCOPE ITEMS IMPLEMENTED in production code; complete solution builds 0 errors.
|
|
- Runtime tests 982/982 GREEN (3 direct-sink tests rewritten to started-generation + driven-conductor form).
|
|
- Headless tests 74/77 (3 failures: WorldProjectionHydratesCanonicalMovementAndTeleportState + 2 others — old
|
|
SynchronizeLocalPlayer expectations; fixtures need conductor-driven rework).
|
|
- App tests 3,865/4,031 passing, 163 failing after central fixture repair (LiveEntityRuntimeFixture now binds
|
|
generation 1). Remaining classes: (a) ~90 fixtures with private lifetimes and no generation bound
|
|
("cannot acquire a structurally valid initial residence lease"); (b) ~40 hand-built spawns failing
|
|
HasConsistentCreateIdentityAndParent ("inconsistent instance or parent projections");
|
|
(c) ~30 behavior-expectation updates (suppressed-until-receipt visibility, FullCellId staying 0 for
|
|
undriven residences).
|
|
- New contract-required integration tests NOT yet written; connected gates NOT reached (automated gates
|
|
not green — per gate order, stopped and reported).
|
|
- Nothing staged or committed. Protected dirty paths untouched except the sanctioned surgical
|
|
PlayerModeController.cs touch (flagged).
|
|
|
|
## Continuation session (fixture repair)
|
|
- App: 443 -> 163 -> 93 -> 50 failing (3,978/4,031 passing). Repairs, all mechanical, NO assertion changes:
|
|
(1) generation binds: LiveEntityRuntimeFixture (all 5 overloads), LiveEntityHydrationControllerTests fixture,
|
|
EquippedChildProjectionWithdrawalTests fixture (own lifetime + production drive controller + landblock
|
|
collision generation);
|
|
(2) consistent-spawn repair: new shared tests/AcDream.App.Tests/LiveEntitySpawnFixture.cs
|
|
(WithConsistentPhysics extension deriving the nested PhysicsDesc from flattened fields), applied to
|
|
CreateSupersessionRecovery, Vfx light, DeferredLifecycle, LocalPlayerTeleport, LiveAppearanceAnimation,
|
|
StreamingFrame builders; per-file physics blocks for LiveEntityPhysicsHostOwnershipTests (19->0) and
|
|
EquippedChildProjectionWithdrawalTests child/embedded-parent spawns (23->0);
|
|
(3) conductor-completion where legacy direct application now requires a released residence
|
|
(NoPositionCreateParent test: CompleteFirstEntry before TryApplyCreateParent).
|
|
- Headless: 77/77 GREEN. The 3 SynchronizeLocalPlayer-era tests rewritten CONDUCTOR-DRIVEN with the real
|
|
host wiring (host.Start -> RegisterEntityWithInitialResidence -> drive controller constructed BEFORE
|
|
registration -> HeadlessSessionWorldProjection pump). All original assertions preserved verbatim
|
|
(controller identity, LocalEntityId, positions, PortalSpace/InWorld, CenterCount 3/2, receipt-validation
|
|
no-authority invariants). Coverage not shrunk; WorldProjectionHydratesCanonicalMovementAndTeleportState now
|
|
doubles as a headless first-entry integration flow.
|
|
- Runtime: 982/982 GREEN (incl. probe-restoration build).
|
|
- Autowalk probes RESTORED at the Runtime site (diagnostic-owner pattern, PhysicsDiagnostics.ProbeAutoWalkEnabled):
|
|
[autowalk-target] + [autowalk-end reason=interrupt] in RuntimeLocalPlayerPhysicsPublicationState.Prepare's
|
|
host/motion closures; [autowalk-end reason=complete] retained App-side on the approach-lifetime MoveToComplete.
|
|
|
|
## Expectation changes
|
|
(Continuation 4 — the logged pass. Clause key: (1) suppressed-until-receipt
|
|
visibility; (2) undriven-residence semantics; (3) residence-gated
|
|
Place/Withdraw with ExecutorCompleted as the presentation-binding receipt;
|
|
(4) sealed-setter lifecycle routing. Where the true mechanism is committed
|
|
C3a/C3b/C3-1 residence design the clause labels do not literally name —
|
|
SameIncarnationCreate FIFO staging (AD-59), conductor-built bodies at Create
|
|
(C3b), no create-authority advance outside the residence transaction — the
|
|
line cites the closest clause PLUS the design mechanism; all such lines are
|
|
flagged [interp] for coordinator veto.)
|
|
|
|
1. CurrentGameRuntimeAdapterTests.DirectAndGraphicalHosts_ProduceIdenticalEntityObjectTrace
|
|
→ old: both hosts register + hand-driven apply chain produce identical
|
|
entity-object traces → new: at the deliberate zero generation BOTH hosts
|
|
refuse the initial Create transactionally with the identical structural
|
|
error ("cannot acquire a structurally valid initial residence lease"),
|
|
identical EMPTY traces, zero entity/object counts → clause (2). Full
|
|
driven-flow parity moved to the new C3c integration tests.
|
|
2. UpdateFrameOrchestratorTests.GameplayInputOwnersUseTypedSeamsWithoutGameWindowBackReferences
|
|
→ old pinned PlayerModeController source order: PreparePositionForCommit
|
|
→ EnterChaseMode → SelectStableHostWithoutRebind → SyncPose →
|
|
InstallOrRebind → SetPosition(initial) → CommitPreparedPosition →
|
|
`_controllerSlot.Controller = controller` → IsPlayerMode=true → new pinned
|
|
order: controller.IsRuntimePublished gate → EnterChaseMode → SyncPose →
|
|
`_hostSlot.Host = playerHost` → IsPlayerMode=true → clause (4) (the
|
|
deleted markers were exactly the App-side controller construction+commit).
|
|
3. LiveEntityHydrationControllerTests.CompletedSpatialRecovery_DetectsCreateVersionDriftWithoutNestedHydrationRequest
|
|
→ old: PositionSequences [1,1,2], last purpose CreateSupersessionRecovery,
|
|
InstalledCreateIntegrationVersion 2UL → new: [1,1], SpatialRecovery, 1UL
|
|
→ clause (3) [interp]: a post-residence same-generation Create is
|
|
description-only at registration; its position churn flows through the
|
|
freshness-gated events tail; create authority advances only inside the
|
|
residence transaction, so no drift-replay fires.
|
|
4. LiveEntityHydrationControllerTests.TimestampCallbackFresherSameGeneration_StopsOuterCreateVersion
|
|
→ old: single materialize from the nested newer spawn (PositionSequences[0]
|
|
== 2) → new: single materialize from the admission-frozen seq-1 create
|
|
(== 1); the seq-2 facts commit at the executor drain in array order →
|
|
clause (3) [interp, AD-59 FIFO staging].
|
|
5. LiveEntityHydrationControllerTests.ObjectRemovalCallbackFresherSameGeneration_WinsOuterReplacement
|
|
→ old: last materialize seq 3 + DoesNotContain(2, Skip(1)) → new: last
|
|
materialize is the admission-frozen seq-2 replacement + DoesNotContain(3)
|
|
(the seq-3 facts commit at the drain and never re-materialize) →
|
|
clause (3) [interp, AD-59].
|
|
6. LiveEntityHydrationControllerTests.FailedCompletedSupersession_RemainsPendingUntilExactRetry (both rows)
|
|
→ old: InstalledCreateIntegrationVersion == (retryFromLandblock ? 2 : 3)
|
|
→ new: == 2 in both rows (the retry retransmit no longer advances create
|
|
authority) → clause (3) [interp]. Drift probe swapped from a nested
|
|
OnCreate to record.Canonical.AdvanceCreateAuthority() (the drain-stage
|
|
advance) so the retained-obligation/exact-retry machinery under test
|
|
still fires; every other assertion verbatim.
|
|
7. LiveEntityInboundAuthorityGateTests.Position_CanonicalInventoryObjectIsAcceptedBeforeProjectionExists
|
|
→ old: accepted.PositionAuthorityVersion == 2 (post-apply) → new: == 1
|
|
(admission-time; the merge is queued behind the pending residence and its
|
|
authority advance commits at the drain) → clause (2).
|
|
8. Probe swaps with ALL assertions verbatim (logged for transparency,
|
|
mechanism = same-generation Creates no longer advance create authority at
|
|
registration; modeled by record.Canonical.AdvanceCreateAuthority(), the
|
|
exact drain-stage advance) [interp, clause (3)]:
|
|
- LiveEntityHydrationControllerTests.CompletedSupersessionReadyFailure_RetriesWholeCommitBoundary (both rows)
|
|
- LiveEntityHydrationControllerTests.ReadyPublisher_RevalidatesExactRecordBetweenEveryStage (3 rows)
|
|
- LiveEntityHydrationControllerTests.ReadyPublisher_RevalidatesAfterReentrantRenderProjectionCallback
|
|
- LiveEntityHydrationControllerTests.EquippedChildReadyCandidate_RejectsVersionAdvancedByPoseCallback
|
|
- LiveEntityCreateSupersessionRecoveryTests.Recovery_CreateVersionAdvanceDuringAppearance_StopsLaterOwners
|
|
9. LiveEntityHydrationControllerTests.ParentSupersession_CompletesAtExactAttachedReadyBoundary (both rows)
|
|
→ scenario repairs: (a) parent registered up front (retail queues a child
|
|
Create under an unaddressable parent — R5-1 QueueBlobForObject — instead
|
|
of applying it); (b) drift probe as in item 8; (c) the OnSpawnAction hook
|
|
now mirrors the production relationship owner's sticky-residence
|
|
conversion (AwaitRuntimePlacement → LegacyImmediate) at the
|
|
world→attached kind transition. Assertions verbatim → [interp, clause (3)].
|
|
10. LiveEntityHydrationControllerTests.PositionAfterPickup_ReentersWithSameEntityBodyAndResources
|
|
→ body scaffolding changed from seeding a fresh PhysicsBody to capturing
|
|
the conductor-built canonical body (C3b bodies-at-Create; factory now
|
|
throws if missing — a STRONGER assertion). Identity-preservation
|
|
assertions verbatim → [interp, C3b].
|
|
11. LiveEntityRuntimeTests.PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder
|
|
→ old: seeded RemoteMotionRuntime bodies observe state sync across
|
|
bind/state arrival orders → new: the conductor-built canonical bodies
|
|
are observed (C3b never-clobber forbids seeding a replacement); "arrival
|
|
order" is now SetState-FIFO'd-before-the-drain vs
|
|
SetState-after-completion; the two expected state-flag transforms are
|
|
UNCHANGED → [interp, C3b + clause (3)].
|
|
|
|
## Remaining at session end (App 50)
|
|
- LiveEntityHydrationControllerTests 21 (supersession/recovery semantics + 5 "publication owner not bound"
|
|
= the fixture drive needs a bound publication chain for isLocalPlayer leases: add
|
|
RuntimeLocalPlayerMovementState+Identity+PublicationState+BindPublication to that fixture);
|
|
- RuntimePlacementPresentationSinkTests 4 (sink behavior changed by design - residence gate + ExecutorCompleted
|
|
presentation; these need logged expectation updates per contract clauses);
|
|
- LiveEntityRuntimeTests 4, LiveEntityPresentationControllerTests 4, remaining consistent-spawn stragglers in
|
|
builders that already carry physics blocks (field-level mismatches), CurrentGameRuntimeAdapterTests 2
|
|
(deliberate zero-generation binds), misc singles.
|
|
- NOT STARTED: the five contract integration tests; Release build; complete solution (-m:1, ACDREAM_PAK_PATH);
|
|
connected gates (blocked behind green automated gates per the pinned gate order).
|
|
- No production regression found: the one suspicious trace (FullCellId=wire-cell in a pickup-supersession test)
|
|
was ruled out against ApplyAcceptedSpawn (object-table only, no cell write) - it is a fixture-flow artifact.
|
|
- Post-log continuation: hydration fixture publication chain bound (Movement/Identity/PublicationState +
|
|
BindPublication + ServerGuid) -> hydration 21 -> 18; App total 50 -> 47 (3,981/4,031). Runtime 982/982 and
|
|
Headless 77/77 re-verified green after all repairs. Nothing staged; protected paths still untouched
|
|
(PlayerModeController touch remains the one sanctioned exception).
|
|
|
|
## Continuation 2 (guard-zone stop)
|
|
- Hydration fixture now also carries the REAL RuntimePlacementProjectionSubscription (ack-only sink mirroring
|
|
production rules: Discard/ExecutorCompleted ack, Place/Withdraw left for conductors) - fixture is now the
|
|
full production wiring shape (generation + collision generation + publication chain + drive pump +
|
|
subscription). Hydration failures unchanged at 18 => the FIFO-wedge hypothesis is ruled OUT; the 18
|
|
supersession/recovery failures are NOT a simple fixture gap.
|
|
- STOP under instruction-1 guard: the observed symptom (nested same-generation Create during
|
|
RecoverProjection no longer triggers CreateSupersessionRecovery; Assert.Throws sees no throw at line 1247)
|
|
means the initial residence is STILL ACTIVE when the nested create arrives, i.e. the drive after the first
|
|
OnCreate did not complete the conductor in this fixture. Before editing ANY of these 18 tests' assertions I
|
|
need to establish WHY the conductor yields here (candidate: something in the fixture's stub materializer /
|
|
spawn shape leaves the placement deferred or the executor pending) - because if the same yield can happen in
|
|
PRODUCTION's composed path, this is a real flip defect (login supersession burst leaving residences pending),
|
|
not an expectation update. That determination requires a focused diagnostic run I could not complete in the
|
|
remaining session budget.
|
|
- Expectation changes so far: STILL ZERO.
|
|
- Totals at stop: Runtime 982/982, Headless 77/77, App 3,981/4,031 (47 failed, 3 skipped).
|
|
- Items 2-4 (zero-generation adapter tests, physics-block mismatch stragglers, five integration tests,
|
|
Release/solution/connected gates) not reached.
|
|
|
|
## Continuation 3 — DIAGNOSTIC + CLASSIFICATION (coordinator-directed)
|
|
- Instrumented RuntimeFirstEntryDriveController.DriveOne (yield status + FIFO head + residence-active +
|
|
FullCellId per step, file-logged); ran FailedCompletedSupersession_RemainsPendingUntilExactRetry.
|
|
- YIELD CHAIN (before fix): first and only drive step yielded RejectedToken with residenceActive=False and
|
|
fullCell=0x01010001 ALREADY COMMITTED — the residence was retired out-of-band before the pump ever ran.
|
|
- ROOT CAUSE: the hydration fixture's RecordingMaterializer called the internal
|
|
LiveEntityRuntime.MaterializeLiveEntity overload WITHOUT the residence parameter -> LegacyImmediate ->
|
|
legacy RebucketLiveEntity fall-through (LiveEntityRuntime.cs:777) -> CommitRebucket committed the wire cell
|
|
and advanced placement/spatial authority -> residence IsCurrent detected the out-of-band commit and retired
|
|
the lease -> conductor correctly RejectedToken.
|
|
- CLASSIFICATION: FIXTURE ARTIFACT. Production's route-1 materializer
|
|
(src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs, MaterializeProjection: residence =
|
|
retainedRecord?.MaterializationResidence ?? AwaitRuntimePlacement) never takes LegacyImmediate for an
|
|
initial world create, so production cannot reach this retirement path. Post-fix diagnostic re-run:
|
|
status=Completed, fullCell committed by the CONDUCTOR, FIFO drained — the drive converges.
|
|
- Fixture fixed at the choke point (stub materializer now passes AwaitRuntimePlacement; subscription upgraded
|
|
to a production-mirroring FixturePlacementSink: Discard ack, ExecutorCompleted ->
|
|
TryApplyInitialCreateCompletionPresentation, Place/Withdraw residence-gated ->
|
|
TryApplyRuntimePlacementProjection). Diagnostics stripped; Runtime rebuilt 0 errors.
|
|
- Result: hydration failures 18 -> 19 (group did NOT shrink; composition changed — the flows now complete
|
|
their conductors and fail on POST-flip semantic assertions: supersession/recovery expectations against
|
|
suppressed-until-receipt visibility + conductor-committed cells). These 19 + the rest of the inventory are
|
|
now genuinely the logged-expectation-update pass (guard rules unchanged, zero changes logged so far).
|
|
- Totals at stop: Runtime 982/982, Headless 77/77, App 3,980/4,031 (48 failed, 3 skipped). No staging/commits.
|
|
|
|
## Continuation 4 — expectation-update pass + integration tests + gate ladder
|
|
- Session start state re-verified: HEAD 78f1eb18, protected dirty paths intact, nothing staged.
|
|
- Full App run (Release): 47 failed / 3,981 passed / 3 skipped of 4,031 (one fewer
|
|
than the Continuation-3 count of 48; the inventory below is the current truth).
|
|
- Classified inventory: Hydration 19; Withdrawal 6; Sink 4; LiveEntityRuntime 4;
|
|
Presentation 4; RemotePhysicsUpdater 2; CurrentGameRuntimeAdapter 2; singles 6
|
|
(LifecycleStress, UpdateFrameOrchestrator, LiveSessionResetPlan,
|
|
InboundAuthorityGate, CreateSupersessionRecovery, LiveAppearanceAnimation).
|
|
- CLASSIFICATION POLICY DECLARED (auditable): where a required change's true
|
|
mechanism is committed, dual-reviewed C3a/C3b/C3-1 residence design that the
|
|
four clause labels do not literally name (SameIncarnationCreate FIFO staging
|
|
per AD-59; conductor-constructed bodies at Create per C3b; no create-authority
|
|
advance outside the residence transaction), the change is logged under the
|
|
closest clause WITH an explicit mechanism note and flagged in the final
|
|
report for coordinator veto. Verbatim-assertion fixture repairs get no
|
|
clause line per the guard's own exemption, but are listed below.
|
|
|
|
### Fixture repairs (assertions verbatim, no expectation-change lines)
|
|
1. RuntimePlacementPresentationSinkTests.Fixture.Materialize — normalize the
|
|
stale residence (legacy-immediate materialization commits the wire cell
|
|
out-of-band; the query performs lazy retirement) BEFORE tests capture
|
|
ownership snapshots; previously the retirement happened inside the sink's
|
|
own first residence query and shifted SetPositionOperationCount/
|
|
AwaitingSetPositionPreparationCount 1->0 mid-TryApply. 14/14 green.
|
|
2. LiveEntityProjectionWithdrawalControllerTests.Fixture.Spawn — spawn had no
|
|
Physics block but non-null Position/Setup/Scale + InstanceSequence!=0;
|
|
wrapped with the shared WithConsistentPhysics (sanctioned for block-less
|
|
builders). 6/6 green.
|
|
3. LiveEntityPresentationControllerTests.Fixture.Spawn — builder ALREADY
|
|
carries a physics block; field-level fix only (Timestamps.Instance was
|
|
hardcoded 1 vs the instanceSequence parameter used by guid-reuse tests).
|
|
NO blind wrap; RawState untouched. 12/12 green.
|
|
4. RemotePhysicsUpdaterTests boundary Spawn(ushort instanceSequence, ...) —
|
|
same field-level Timestamps.Instance fix; RawState untouched. Both green.
|
|
5. LiveAppearanceAnimationTests.Capture_... — block-less cellless spawn with
|
|
InstanceSequence 1; WithConsistentPhysics wrap (file's other builder
|
|
already used it). Green.
|
|
|
|
### Continuation 4 — production changes beyond the inherited diff (both
|
|
### flagged for coordinator review; each is one revertible hunk)
|
|
1. src/AcDream.App/Rendering/EquippedChildRenderController.cs (TryAttach,
|
|
before MaterializeLiveEntity): converts a retained residence-managed
|
|
child's sticky residence AwaitRuntimePlacement → LegacyImmediate at the
|
|
world→attached kind transition. WITHOUT this, equipping a world-created
|
|
(cut-over) item throws at LiveEntityRuntime.cs:655's residence-change
|
|
guard ("cannot change its materialization residence from
|
|
AwaitRuntimePlacement to LegacyImmediate") because the attach path passes
|
|
the default LegacyImmediate — while the flip's own materializer comment
|
|
(DatLiveEntityProjectionMaterializer.cs:712-722) states equipped children
|
|
must carry LegacyImmediate so a later drop-to-world stays legacy "by
|
|
construction". Found via ParentSupersession_CompletesAtExactAttachedReadyBoundary;
|
|
production-reachable (pickup → CreateParent → TryAttach with a retained
|
|
sidecar). This completes the flip's documented design, not new invention.
|
|
2. src/AcDream.App/World/LiveEntityRuntime.cs (RebucketLiveEntity,
|
|
residence-managed branch): while the initial-create residence is still
|
|
ACTIVE, the presentation-only move now refuses (returns false) — the
|
|
completion receipt is the entity's first world-visible moment (clauses
|
|
1/3). The inherited flip had made the presentation-only move
|
|
unconditional, re-opening the mid-registration reentrancy hazard pinned
|
|
by RuntimePlacementPendingMaterialization_OwnsResourcesWithoutPublishingResidence
|
|
(a resource-registration observer could install a bucket for a suppressed
|
|
record before its placement committed). A STALE residence lazily retires
|
|
inside the same query, so post-residence legacy moves are unaffected; the
|
|
completion-receipt path calls RebucketLiveEntityPresentationOnly directly
|
|
and never crosses this gate.
|
|
|
|
### Continuation 4 — additional fixture repairs (assertions verbatim)
|
|
6. 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).
|
|
8. CurrentGameRuntimeAdapterTests.GraphicalObserverFailure — session started
|
|
first (its siblings' existing pattern). 9. Hydration RecordingMaterializer
|
|
— mirrors the production materializer's self-projection branch (committed
|
|
cell + no active residence → presentation-only rebucket); fixed
|
|
PositionAfterPickup/PositionAfterInventoryOnlyCreate spatial-projection
|
|
truth. 10. PartialProjection_IsRetriedInsteadOfMistakenForCompletedHydration
|
|
— models the production per-frame pump (FirstEntry.DriveAll) before the
|
|
streaming recovery (the failed Create unwound before OnCreateCore's own
|
|
pump; an undriven residence leaves FullCellId 0 and streaming candidates
|
|
key off the committed cell). 11. LiveEntityRuntimeFixture.CreateDriven —
|
|
NEW driven variant (collision generation + engine landblock + production
|
|
RuntimeFirstEntryDriveController + ack-only subscription mirroring host
|
|
rules); applied to InitialChildCreate_PreservesParentEventQueued...,
|
|
PositionAfterPickup_RequiresTeleportHookEvenWithEqualTeleportStamp, and
|
|
PhysicsStateAndRemoteBodyStaySynchronizedAcrossEitherArrivalOrder.
|
|
|
|
### Checkpoint: FULL App suite GREEN — 4,028 passed / 0 failed / 3 skipped
|
|
### of 4,031 (Release). Next: Runtime + Headless re-verification, then the
|
|
### five contract integration tests.
|
|
|
|
### Continuation 4 — five contract integration tests (all green, real host
|
|
### wiring: registration -> subscription -> RuntimeFirstEntryDriveController;
|
|
### no hand-called conductor sequences)
|
|
NEW tests/AcDream.App.Tests/World/RuntimeFirstEntryHostIntegrationTests.cs
|
|
(fixture: real RuntimeEntityObjectLifetime + generation + committed collision
|
|
generation + LiveEntityHydrationController.OnCreate route + production
|
|
RuntimePlacementPresentationSink behind the real
|
|
RuntimePlacementProjectionSubscription + real drive controller + local-player
|
|
publication chain; materializer is the production-mirroring double):
|
|
1. InitialCreate_ResidenceConductorReceipt_BindsWorldVisibilityExactlyOnce —
|
|
residence begins once, sidecar provably suppressed at materialize time,
|
|
conductor completes inside the Create transaction, exactly one visibility
|
|
edge, world snapshot at the committed pose, all ledgers drained.
|
|
2. DeferredParentCreate_StaysInvisibleUntilParentReplay — unaddressable-parent
|
|
child: no canonical/sidecar/presentation, queued under the parent GUID;
|
|
parent Create replays it (real registration delegate), next-frame pump
|
|
completes the parented conductor; child celless + presentation-suppressed,
|
|
only the parent world-visible.
|
|
3. LocalLogin_PresentationAttachFailure_RetriesWithoutRuntimeRollback — the
|
|
camera/shadow-analog App attach failure (first visibility binding throws)
|
|
AFTER the Runtime commit: published PlayerMovementController
|
|
(IsRuntimePublished) + canonical body + committed cell all survive, the
|
|
completion receipt stays pending, RetryPending() binds presentation with
|
|
the SAME controller instance. NOTE: the literal PlayerModeController
|
|
camera object is not constructed here (its ~20-dependency graph has no
|
|
focused harness); its presentation-only rollback is pinned by the updated
|
|
UpdateFrameOrchestrator source assertions + this receipt-level analog.
|
|
5. 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:
|
|
4. MissingPreparedCollisionYieldsTypedRetryAndCompletesWhenAvailable — flaky
|
|
IPreparedCollisionSource (Missing -> Loaded) through the REAL
|
|
HeadlessSessionWorldProjection.ProjectSpawn pump: no exception escapes,
|
|
entity stays tracked/pending with no controller and cell 0, the session
|
|
tick's retry pump completes placement + publishes the controller.
|
|
Totals after the tests: Headless 78/78 (77+1); App integration class 4/4.
|
|
|
|
### Continuation 4 — gate ladder
|
|
1. Complete test projects (Release): App 4,032 passed / 0 failed / 3 skipped
|
|
of 4,035 (4,031 + 4 new integration tests); Runtime 982/982; Headless
|
|
78/78 (77 + 1 new). GREEN.
|
|
2. dotnet build AcDream.slnx -c Release --nologo: 0 errors, 18 warnings
|
|
(pre-existing test-project warnings; none in files this session touched).
|
|
GREEN.
|
|
3. Complete solution (-m:1, ACDREAM_PAK_PATH=C:\Users\erikn\Documents\
|
|
Asheron's Call\acdream.pak): IN FLIGHT (background).
|
|
4. Connected gates: ACE confirmed listening on UDP 9000 (PID 22100),
|
|
C:\ACE\Server\ACE_Log.txt present, no AcDream.App/acclient processes
|
|
running. Will run after gate 3.
|
|
|
|
### Continuation 4 — gate ladder RESULT (stopped at gate 4 per pinned order)
|
|
3. 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.
|
|
4. CONNECTED tools/run-connected-world-lifecycle-gate.ps1: **FAIL — STOPPED
|
|
HERE.** Artifacts: logs/connected-world-gate-20260802-122749/
|
|
(report.json Passed=false; capped/ + uncapped-reconnect/ each with
|
|
stdout.log, stderr.log, artifacts/). Both sessions connected, entered
|
|
world as 0x5000000A, ran the 54-command UI probe, requested AND received
|
|
graceful logout confirmation, then CRASHED identically with an unhandled
|
|
System.InvalidOperationException: "A sealed, retired, or discarded
|
|
Runtime movement controller cannot be mutated."
|
|
EXACT CHAIN (identical in both sessions):
|
|
PlayerMovementController.EnsureConfigurationMutable
|
|
(src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:859)
|
|
<- SetCharacterSkills(:1248)
|
|
<- RuntimeMovementSkillProjection.ApplyTo(
|
|
src/AcDream.Runtime/Gameplay/RuntimeMovementSkillProjection.cs:21)
|
|
<- LiveSessionRuntimeFactory.ApplyMovementStats(
|
|
src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:327; recompute
|
|
callback registered at :300 in CreateCharacterBindings)
|
|
<- LiveSessionEventRouter.RecomputePlayerQualities(
|
|
src/AcDream.Runtime/Session/LiveSessionEventRouter.cs:406)
|
|
<- ClientObjectTable.Ingest(WeenieData)
|
|
<- ObjectTableWiring.ApplyEntitySpawn
|
|
<- RuntimeEntityObjectLifetime.ApplyAcceptedSpawn(:926)
|
|
<- LiveEntityHydrationController.OnCreateCore(:298) — an ordinary
|
|
inbound Create datagram, processed AFTER the graceful-logout
|
|
confirmation (stdout timeline: probe -> logout confirmed ->
|
|
post-logout stat-chain recomputes -> crash).
|
|
DIAGNOSIS (report-only, no fix attempted): the flip made the local
|
|
movement controller Runtime-published with a SEALED configuration
|
|
lifecycle (mutable only pre-publication; retired at teardown). The
|
|
character-bindings quality-recompute subscription
|
|
(LiveSessionRuntimeFactory.CreateCharacterBindings ->
|
|
ApplyMovementStats -> controller.SetCharacterSkills) still mutates the
|
|
controller directly on EVERY player-quality recompute; once the
|
|
controller is sealed (published) or retired (logout teardown), that
|
|
mutation throws. This is exactly the review-focus item "Sealed
|
|
Controller setter: audit every compile break's fix — each must route
|
|
through the publication lifecycle" — this site was missed because it is
|
|
a RUNTIME mutation (EnsureConfigurationMutable), not a compile break.
|
|
A fix must decide where server skill updates route post-flip (e.g. the
|
|
Runtime movement-state seam / construction options at first entry +
|
|
a Runtime-owned live-skill channel), which is a production design
|
|
decision outside this session's mechanical remit — per the pinned gate
|
|
order the session STOPS at this failure and reports.
|
|
Secondary observation from the same logs (likely the same defect class,
|
|
recorded for the fixer): after the unhandled exception the shutdown path
|
|
reported "status=AbandonedIncomplete, blocked=native window" with
|
|
Silk.NET "You cannot call `Reset` inside of the render loop!" — a
|
|
crash-path artifact, not an independent bug.
|
|
The nine-stop soak (tools/run-connected-r6-soak.ps1) was NOT run (gated
|
|
behind the lifecycle gate's pass).
|
|
- Nothing staged, no commits at any point; HEAD remains 78f1eb18; the 7
|
|
protected dirty paths + AGENTS.md carry only their inherited state
|
|
(AGENTS.md the sole real pre-existing content diff; PlayerModeController
|
|
touched only by the inherited sanctioned flip surgery — this session
|
|
changed only the marker TEST for it, not the file).
|
|
|
|
## C3c-F1 — movement-stat application through the Runtime seam
|
|
### Candidate-window investigation (resolved with evidence)
|
|
- The crash state was RuntimeOwnedDormant, not retired: the failing gate run's
|
|
stdout shows the login world-reveal never completed (collision=False at every
|
|
readiness line; event=cancel at logout with completed=False) — first-entry
|
|
activation stayed DeferredCell for the whole session, so
|
|
RuntimeLocalPlayerMovementState held the dormant controller when the
|
|
post-logout ingest recompute fired. EnsureConfigurationMutable throws for
|
|
dormant (PlayerMovementController.cs:848-861: only Standalone/
|
|
CandidatePreparing/RuntimePublished/dormant+groundPhase return).
|
|
- CandidateSealed is UNREACHABLE through the seam: the sealed candidate is
|
|
never installed into the movement owner (CanPrepare requires
|
|
`_movement.Controller is null`, RuntimeLocalPlayerPhysicsPublicationState.cs:908;
|
|
IsCurrent requires CanCommitRuntimeOwnedController(epoch, null), :937-939),
|
|
and Prepare→Commit runs back-to-back inside one synchronous Advance step
|
|
(RuntimeLocalPlayerFirstEntryState.cs:395-431) with no callback dispatch
|
|
between (Prepare's construction is callback-free by design, publication
|
|
state :319-334).
|
|
- RuntimeOwnedDormant IS reachable across pump iterations: Commit installs
|
|
the dormant controller (publication state :436) and Evaluate/CommitActivation
|
|
DeferredCell yields AwaitingActivation with stage kept
|
|
(RuntimeLocalPlayerFirstEntryState.cs:493-497) — inbound quality events pump
|
|
between Advance calls. DECISION: apply-immediately-to-dormant (not
|
|
defer-at-commit) — the dormant instance IS the controller that goes live
|
|
(ActivateRuntimePublication at RuntimeSetPositionState.cs:2464 flips the
|
|
same object), the writes touch only PlayerWeenie fields + the mover-flag
|
|
latch (nothing the activation envelope validates), and the precedent is the
|
|
existing dormant channels RefreshDormantRuntimePhysicsState/Vector
|
|
(PlayerMovementController.cs:735-759) that land accepted server facts on
|
|
the dormant owner mid-window. Deferring would need an activation hook and
|
|
would leave the weenie stale for the activation ground-phase dispatch.
|
|
### Implementation
|
|
- PlayerMovementController: internal ApplyCharacterMovementStats(in snapshot)
|
|
— lifecycle switch (live/dormant apply, terminal typed drop) + private
|
|
ApplyCharacterMovementStatsCore (verbatim body of the deleted
|
|
RuntimeMovementSkillProjection.ApplyTo, field writes not gated setters) +
|
|
internal ReportExhaustionAtMovementBoundary (live-only exhaustion dispatch).
|
|
- RuntimeLocalPlayerMovementState: public ApplyCharacterMovementStats(
|
|
RuntimeMovementSkillState) + public ReportExhaustion() + public enum
|
|
RuntimeMovementStatsApplication {AppliedLive, AppliedDormant,
|
|
DroppedNoController, DroppedIncompleteSnapshot, DroppedDisplacedController}.
|
|
Disposed-owner tolerant (typed drop, not ObjectDisposedException) per J3.6.
|
|
- DELETED src/AcDream.Runtime/Gameplay/RuntimeMovementSkillProjection.cs
|
|
(zero remaining consumers).
|
|
- NEW src/AcDream.App/Net/LiveMovementStatsApplier.cs — owns the
|
|
StaminaExhaustionEdgeTracker + logging; observes the exhaustion edge for
|
|
both applied outcomes, dispatches only AppliedLive (dormant: no in-flight
|
|
movement; activation reads the current stamina gate).
|
|
- LiveSessionRuntimeFactory: constructs the applier; OnSkillsUpdated/
|
|
OnMovementStatsUpdated route through it; ApplyMovementStats +
|
|
_staminaExhaustion deleted; ResetPlayerPresentation resets the applier.
|
|
App keeps zero direct configuration mutations on the stats path.
|
|
### Tests (all green at write time)
|
|
- Runtime (7 new in RuntimeLocalPlayerMovementStateTests): live byte-identity
|
|
vs the old direct path (InqRunRate/InqJumpVelocity/CanJump/JumpStaminaCost/
|
|
OwnPvpFlags), dormant-window write lands on the instance that goes live,
|
|
terminal typed drop with unchanged observables + setter-still-throws,
|
|
sealed-candidate defensive row, absent/incomplete drops, disposed-owner
|
|
tolerance, ReportExhaustion lifecycle gating. 19/19 file total.
|
|
- App (3 new in LiveMovementStatsApplierTests): the REAL crash chain
|
|
(real WorldSession + LiveSessionEventRouter + ClientObjectTable.Ingest of
|
|
the player row + real applier callback) against a retired-installed
|
|
controller — no throw, typed displaced drop logged; dormant-window ingest
|
|
applies + values current at activation; absent/incomplete silent skips.
|
|
### Connected gate run 1 (post stats-fix): FAIL — second site of the same class
|
|
- logs/connected-world-gate-20260802-125907: BOTH sessions confirmed graceful
|
|
logout, then crashed on the SAME EnsureConfigurationMutable throw via the
|
|
OTHER App-side mutation my sweep had already classified residual-unsafe:
|
|
LiveEntityNetworkUpdateController.OnState:1006 →
|
|
PlayerMovementController.ApplyPhysicsState(:366) at the dormant controller
|
|
(post-logout inbound SetState; the login reveal never completes in this
|
|
gate profile so the first-entry controller stays dormant all session —
|
|
same as the original 122749 failure). The stats seam itself WORKED: the
|
|
stdout shows repeated "player: applied server movement stats run=10205..."
|
|
dormant applications with no ingest crash.
|
|
- Disposition per the sweep clause ("route each through the seam or report
|
|
why it is already lifecycle-safe" — this site cannot be reported safe):
|
|
routed through the same owner seam pattern. NOT a guard at the throw site:
|
|
the lifecycle decision moved into the owner.
|
|
### Second routing (inbound local-player SetState)
|
|
- PlayerMovementController.ApplyServerPhysicsState (internal, typed):
|
|
live → exact ApplyPhysicsState body; dormant →
|
|
DroppedDormantActivationOwned (the activation transaction re-reads the
|
|
canonical FinalPhysicsState itself via RefreshDormantRuntimePhysicsState
|
|
at both activation phases, and while the accepted SetState is queued
|
|
behind the initial residence the App push carries that same UNCHANGED
|
|
record value — RuntimeEntityObjectLifetime.TryApplyState:1351-1378 queues
|
|
without advancing the record — so the drop is value-preserving by
|
|
construction); terminal → DroppedDisplacedController. New enum
|
|
RuntimeServerPhysicsStateApplication beside the stats enum.
|
|
- LiveEntityNetworkUpdateController.cs:1005-1018 routes through the typed
|
|
entry (result discarded; comment records both gate crash chains).
|
|
- Tests: Runtime ApplyServerPhysicsState_DormantDropsForActivationAndLiveAppliesExactly
|
|
(990/990 Runtime); App source pin C3cF1ProductionWiringTests.
|
|
LocalPlayerInboundSetState_RoutesThroughTheTypedOwnerEntry (no direct
|
|
ApplyPhysicsState caller remains in the file). App 4,036/3 skips of 4,039;
|
|
Headless 78/78.
|
|
### Connected gate run 2 (130455) — FAIL, root cause classified INHERITED; STOPPED per directive
|
|
- Both sessions ran CRASH-FREE (0 unhandled exceptions; capped alive 7+ min
|
|
until my graceful WM_CLOSE, uncapped alive the full 420 s timeout) — the
|
|
F1 crash-class fixes hold live. 418 login entities + 10,384 total ingested
|
|
cleanly with 158 dormant stat applications.
|
|
- Harness failures: capped "client exited waiting for probe complete" (my
|
|
directed close), uncapped "timed out after 420 s waiting for probe
|
|
complete".
|
|
- Wedge mechanism (link-by-link):
|
|
1. First-entry conductor Prepares+Commits the DORMANT controller within
|
|
the first frames (stats lines from stdout line 62).
|
|
2. Activation stays DeferredCell; even after streaming collision readiness
|
|
completes (~40 s, reveal line 223 collision=True ready=True) the
|
|
activation/publication never commits — PlayerModeController logs ONE
|
|
"Runtime first-entry controller ... not committed yet" (line 224,
|
|
PlayerModeController.cs:247-250) and player mode never enters.
|
|
3. Reveal (kind=Login) completes on readiness alone with
|
|
materialized=False (RuntimeWorldTransitState.Complete:685-720 requires
|
|
Materialized only for Portal kind), then per-frame readiness
|
|
re-acknowledgements spam event=rejected reason=readiness-after-terminal
|
|
(9,000+ lines).
|
|
4. AcknowledgeWorldViewportVisible never fires (visible=False forever) →
|
|
probe line 5 "timed out waiting for normal world viewport"
|
|
(RetailUiAutomationScriptRunner.cs:308-313; route line 4-5
|
|
tools/connected-world-lifecycle.route.txt) → probe never prints
|
|
complete → harness fails.
|
|
- NOT an F1 regression — three proofs:
|
|
1. 122749 (zero F1 changes) fails with the IDENTICAL harness failure
|
|
string ("client exited waiting for probe complete", report.json), has
|
|
NO probe-complete/checkpoint/player-mode lines, and its own crash
|
|
(SetCharacterSkills throwing) proves the controller was never published
|
|
there either.
|
|
2. 122749's login was WORSE pre-F1: the per-Create ingest-recompute throw
|
|
killed the process 26 s in (StartedUtc→FinishedUtc) with only 1 entity
|
|
seen vs 418/10,384 under F1 — the coordinator's "122749 entered world
|
|
normally" premise is contradicted by its own artifacts.
|
|
3. The F1 write set cannot affect activation currency: the stats core
|
|
touches only PlayerWeenie fields + the OwnPvpFlags latch; the
|
|
activation envelope checks lifecycle/epochs/body identity only
|
|
(RuntimeLocalPlayerPhysicsPublicationState.cs:961-992, :747-775); the
|
|
DeferredCell decision is RuntimeSetPositionState cell-residency
|
|
machinery untouched by this slice.
|
|
- Residual root cause home: the C3c first-entry activation never resolves
|
|
its deferred destination cell in the graphical host (or its pending drive
|
|
entry stops being re-armed) — RuntimeLocalPlayerFirstEntryState.cs:433-501
|
|
(PublicationCommitted → EvaluateActivation/CommitActivation DeferredCell
|
|
loop) against RuntimeSetPositionState's dormant-activation cell gate.
|
|
That is conductor/publication semantics OUTSIDE the F1 contract →
|
|
STOPPED, no fix attempted, per coordinator directive 3.
|
|
- Client terminated via graceful-close discipline (WM_CLOSE → logout
|
|
confirmed by ACE, clean [session] lines, no crash); harness concluded and
|
|
recorded FAIL; artifacts preserved at
|
|
logs/connected-world-gate-20260802-130455/.
|
|
|
|
## C3c-F2 — the login DeferredCell activation wedge
|
|
|
|
### Step 1 — artifact re-verification (the pinned contract's evidence chain is
|
|
### WRONG on run 122749; both prior classifications were partly unsound)
|
|
Read directly from the primary artifacts, not from either prior report:
|
|
- 122749 (flip WITHOUT F1), capped/stdout.log is 67 lines TOTAL. It shows
|
|
`[UI-PROBE] running 54 UI probe command(s)` (L52) — that line is the probe
|
|
ANNOUNCING its command count, not 54 commands completing. The reveal shows
|
|
ONLY `event=begin` (collision=False) and ONE `event=readiness`
|
|
(collision=False, ready=False, materialized=False, visible=False), then
|
|
`event=cancel ... visible=False`. The smoke plugin saw 1 entity total.
|
|
report.json StartedUtc -> FinishedUtc = 26 SECONDS for BOTH sessions.
|
|
=> the contract's "world became visible / probe's `wait world-visible`
|
|
passed / 54-command probe ran" is falsified. The world was NEVER visible in
|
|
122749; the process died ~9 s after entering world from the F1 crash, i.e.
|
|
BEFORE the ~40 s collision-readiness edge where the wedge manifests.
|
|
- Consequence: 122749 proves NOTHING either way about post-readiness
|
|
activation (it never got there). The F1 agent's "identical harness failure
|
|
string" proof is equally weak (identical string, different causes: crash vs
|
|
timeout). Both prior attributions are unsound; attribution has to come from
|
|
CODE, not from these two logs.
|
|
- 130455 (flip WITH F1) is the only run that reaches the readiness edge:
|
|
L223 readiness collision=True ready=True; L224 `not committed yet` (ONE
|
|
line); L225 `event=complete ... materialized=False`; then 5,577+
|
|
`readiness-after-terminal`; probe L1193 `wait world-visible 30000` timeout.
|
|
|
|
### Step 2 — the contract's primary suspect is STRUCTURALLY disproven
|
|
F1's sweep change (LiveEntityNetworkUpdateController.cs:1005-1018) passes
|
|
`record.FinalPhysicsState` into the typed owner entry. The activation cell
|
|
gate reads that SAME canonical value directly off the record —
|
|
RuntimeSetPositionState.cs:1747-1752 builds `canonicalRequest` with
|
|
`MoverPhysicsState = record.FinalPhysicsState`. The activation path never
|
|
reads the controller's copy. A drop on the CONTROLLER therefore cannot
|
|
deprive the activation of anything, "value-preserving" or not. The primary
|
|
suspect cannot produce a DeferredCell wedge. Contract constraint 3's
|
|
"retained-to-activation admission" is not the fix; the real defect is below
|
|
and is entirely inside C3c-flip code that F1 never touched.
|
|
|
|
### 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 `expected` is
|
|
STILL 1, proving the admission is still registered at that instant: the
|
|
commit reenters the host's first-entry pump between
|
|
RuntimePhysicsState.cs:2503 and the retirement at :2552-2558.
|
|
- L222: the rearm succeeds inside that window, the immediately following
|
|
evaluation fails `TrySealCollisionEvaluationAuthority` on the still-
|
|
registered admission, and because the lease is no longer AwaitingCell,
|
|
EvaluateActivation answers RejectedAuthority — TERMINAL. The conductor
|
|
discards and the drive entry is dropped for the session.
|
|
=> LINK 3 (the reentrant-window rearm) is the DOMINANT live blocker, and it
|
|
fires BEFORE the L6 generation mismatch can. L6 is still real and still
|
|
load-bearing — see the next run.
|
|
|
|
Second live run with both terms (`logs/c3c-f2-probe2.out.log`):
|
|
```
|
|
L223 [c3cf2-rearm] ... ready=True gen=1 auth=1 expected=1 -> refused (window)
|
|
L224 [c3cf2-rearm] ... ready=True gen=1 auth=1 expected=2 -> REARMED
|
|
L225 [c3cf2-drive] local status=Completed step=0 pending=2
|
|
L230 live: auto-entered player mode for 0x5000000A
|
|
L231 [world-reveal] event=complete ...
|
|
L234 [world-reveal] event=world-visible ... visible=True
|
|
```
|
|
L224 is the direct proof that F2-1 is load-bearing: at the frame the rearm
|
|
actually happens the admission is gone, so `expected` is 2 and only the
|
|
committed authority still equals the parked generation 1. Zero
|
|
readiness-after-terminal lines in the whole run.
|
|
|
|
### Final fix (3 hunks, all required)
|
|
F2-1 RuntimeSetPositionState.TryRearmDeferredDormantLocalActivation — compare
|
|
the parked generation against `CollisionGenerationAuthority` (the generation
|
|
the collision world HOLDS) instead of `ExpectedCollisionGeneration` (which
|
|
means "the next, not-yet-begun generation" once the admission retires).
|
|
F2-3 same method — refuse the rearm while the destination prefix is not
|
|
evaluable, using the seal's own predicate, now factored as
|
|
`RuntimePhysicsState.IsCollisionEvaluationPrefixAdmissible` and consumed by
|
|
BOTH the seal and the rearm so they cannot drift. This is the same shape the
|
|
remote wake path already had via `TryGetBlockingQuiescence` (:4069-4095).
|
|
F2-2 LivePlayerModeAutoEntryContext.IsPlayerControllerReady — report the
|
|
Runtime first-entry commit instead of the constant `true`, so the one-shot
|
|
guard cannot burn its single attempt (and unconditionally complete the world
|
|
reveal) before the conductor has published the controller.
|
|
|
|
### Tests
|
|
- NEW RuntimeLocalPlayerPhysicsPublicationStateTests
|
|
.DeferredCommitRearmsAfterProductionAdmissionCommitsItsGeneration — pins
|
|
F2-1; fails pre-fix.
|
|
- NEW RuntimeLocalPlayerPhysicsPublicationStateTests
|
|
.DeferredCommitStaysParkedWhileTheCommittingAdmissionIsStillRegistered —
|
|
pins F2-3; pre-fix it fails with the exact live symptom
|
|
(`Expected: DeferredCell / Actual: RejectedAuthority`).
|
|
- NEW tests/AcDream.App.Tests/Input/C3cF2AutoEntryWiringTests
|
|
.ProductionAutoEntryRequiresTheRuntimePublishedController — pins F2-2
|
|
(source pin; the production context's ~15-dependency graph has no focused
|
|
harness, matching the C3c-F1 precedent).
|
|
- CONVERTED to the production wake path (assertions verbatim; only the wake
|
|
DRIVER changed, from the raw SetPosition seam to the collision admission
|
|
ledger, because the raw seam leaves the ledger empty — a state production
|
|
can never reach, and precisely why these tests missed the wedge):
|
|
RuntimeLocalPlayerPhysicsPublicationStateTests
|
|
.DeferredCommitWaitsThenRearmsSameLeaseAfterExactGenerationWake,
|
|
.DeferredAuthoredActivationSuspendsRowsAndExactWakeRestoresThem,
|
|
RuntimeLocalPlayerFirstEntryStateTests
|
|
.AwaitingActivationRetriesWhileCellUnresolvedThenResumesAfterGenerationWake,
|
|
.DeleteAndSameGuidReincarnationAutomaticallyFreesThePublicationSlotForTheFreshIncarnation.
|
|
All four fail pre-fix once converted.
|
|
|
|
### Gates
|
|
Runtime 992/992; App 4,037/0/3 skips; Headless 78/78; Release solution build
|
|
0 errors / 18 pre-existing test-project warnings; complete solution (-m:1,
|
|
ACDREAM_PAK_PATH) 10,797 passed / 0 failed / 4 skipped of 10,801;
|
|
`git diff --check` clean.
|
|
|
|
CONNECTED GATE: logs/connected-world-gate-20260802-135444 — RESULT=FAIL, but
|
|
NOT on the wedge, which is gone in both sessions:
|
|
- capped: login reveal reached world-visible, the probe captured checkpoint
|
|
`capped_login` AND screenshot `capped_login.png`, then teleported
|
|
(`old lb=(169,180) new lb=(9,4)`), and generation 2 (kind=Portal) reached
|
|
materialized=True, visible=True, completed=True. 17,043 entities ingested.
|
|
- uncapped-reconnect: login at cell 0x09040008 reached visible=True,
|
|
completed=True.
|
|
- Zero `readiness-after-terminal` lines, zero "not committed yet", zero
|
|
F1-class controller-mutation crashes in either session.
|
|
|
|
### BLOCKER (pre-existing, out of C3c-F2 scope) — map-corner landblock
|
|
Both sessions then died identically:
|
|
`System.ArgumentOutOfRangeException (Parameter 'landblockId')` at
|
|
RuntimeSetPositionState.BeginCollisionPrefixQuiescence
|
|
(src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs:783)
|
|
<- RuntimePhysicsState.BeginCollisionPrefixQuiescence(:1993)
|
|
<- RuntimePhysicsState.CommitCollisionGeneration(:2432)
|
|
<- LandblockPhysicsPublisher.AdvanceCompleteOne
|
|
(src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs:626)
|
|
<- LandblockPresentationPipeline.Advance <- StreamingController.Tick.
|
|
Mechanism: the teleport destination is landblock (9,4). The far streaming
|
|
radius is 12 and StreamingRegion only SKIPS out-of-range indices
|
|
(`nx < 0 || nx > 0xFF`, StreamingRegion.cs:80/117/155/225) — it does not skip
|
|
(0,0) — so the window includes landblock id `(0<<24)|(0<<16)|0xFFFF` =
|
|
0x0000FFFF. `CanonicalLandblock` keeps that as 0x0000FFFF, and
|
|
BeginCollisionPrefixQuiescence computes `prefix = landblockId & 0xFFFF0000`
|
|
= 0x00000000 and throws its `prefix == 0u` guard (:781-783). Dereth's
|
|
south-west corner landblock therefore cannot be collision-published, and any
|
|
position within the far radius of it crashes the client.
|
|
This code is untouched by the C3c flip, by C3c-F1, and by C3c-F2 (`git diff
|
|
src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs` contains no change to
|
|
BeginCollisionPrefixQuiescence); it was simply unreachable while login itself
|
|
was wedged — 130455 never left Holtburg and never teleported.
|
|
NOT fixed here, and deliberately not a one-line guard removal: prefix 0 is
|
|
also overloaded as an "absent prefix" sentinel in the same class — e.g.
|
|
ParkDeferred derives `operation.CollisionQuiescenceHeld = collisionPrefixOverride
|
|
!= 0u` (:3975-3978), so a genuine landblock-(0,0) quiescence override would be
|
|
read as "no override". A correct fix needs an explicit has-prefix flag (or a
|
|
nullable prefix), which is a physics-ownership design change outside this
|
|
contract. Recommend a dedicated slice.
|
|
|
|
## C3c-F3 — corner-landblock prefix-0 sentinel conversion
|
|
|
|
### Chain audit (complete, every prefix-sentinel site classified)
|
|
CONVERTED (the has-prefix representation is nullable uint for the override
|
|
pair + OperationId-based token presence + explicit landblockId==0 absent-id
|
|
input guards):
|
|
1. RuntimeSetPositionState.cs RuntimeCollisionPrefixQuiescenceToken.IsValid
|
|
(:112) — dropped `LandblockPrefix != 0u`; presence now discriminated by
|
|
OperationId != 0 (monotonic from 1) + CollisionGeneration != 0 (from 1).
|
|
This was load-bearing beyond the crash site: a real prefix-0 token read
|
|
as invalid wedged TryGetCurrentQuiescence (:839/:887/:905 callers),
|
|
permission currency (:886), and TryGetBlockingQuiescence's `excluded`
|
|
term (:3617 — RetryDeferred's restoringQuiescence would have blocked
|
|
itself).
|
|
2. RuntimeSetPositionState.BeginCollisionPrefixQuiescence (:782) — the
|
|
crash-site `prefix == 0u` throw replaced by `landblockId == 0u` (absent
|
|
id), prefix computed unconditionally.
|
|
3. RuntimeSetPositionState.ParkDeferred (:3977-78, :4013-19) — override
|
|
params converted `ulong collisionGenerationOverride = 0UL, uint
|
|
collisionPrefixOverride = 0u` -> `ulong?/uint? = null`;
|
|
CollisionQuiescenceHeld = collisionPrefixOverride.HasValue. Both
|
|
quiescence-token call sites (:2842, :2893) pass values unchanged
|
|
(implicit lift); byte-identical for nonzero prefixes.
|
|
4. RuntimeSetPositionState.ParkCollisionResidents (:3414-17) — `?? 0UL` /
|
|
`?? 0u` collapse removed; `quiescence?.Token.X` now flows null/value.
|
|
5. RuntimePhysicsState.BeginCollisionPrefixQuiescence (:1991) — dead
|
|
`canonical == 0u` (CanonicalLandblock ORs 0xFFFF, never 0) replaced by a
|
|
LIVE `landblockId == 0u` guard.
|
|
6. RuntimePhysicsState.AdvanceCollisionRetirementMutation (:2626) — same
|
|
dead-guard replacement.
|
|
7. RuntimePhysicsState.BeginCollisionAdmission (:2037) — NEW landblockId==0
|
|
guard at the admission entrance: an absent id canonicalizes to
|
|
0x0000FFFF (the REAL corner landblock), and the old accidental
|
|
commit-time protection (the prefix throw) is gone.
|
|
8. ShadowObjectRegistry.DeriveOutdoorSeed (:651, Core) — `lbPrefix == 0u ->
|
|
no seed` replaced by `landblockId == 0u`; corner-block baked statics now
|
|
derive real seeds 0x0000000N (previously silently dropped from the
|
|
shadow world). Same sentinel class, exercised by the corner collision
|
|
publication chain.
|
|
|
|
KEPT with justification (no collision with prefix 0):
|
|
- Generation-0 "unbound" sentinel (RuntimeSetPositionState :3727/:3741-45/
|
|
:3811/:3842/:5135/:5180/:5255): generations allocate from 1
|
|
(checked(current+1), Begin throws on 0) — 0 is unreachable as a real
|
|
generation.
|
|
- ExactCellId==0 "absent cell" (IndexDeferred/IndexUnboundDeferred/
|
|
UnindexDeferred): cell-part 0x0000 is not a valid cell; corner cells are
|
|
0x00000001+.
|
|
- RuntimePhysicsState dead post-canonicalization zero checks
|
|
(ExpectedCollisionGeneration :2932, IsCollisionEvaluationPrefixAdmissible
|
|
:2963, CollisionGenerationAuthority :2977, TrySealCollisionEvaluationAuthority
|
|
Add :3030): CanonicalLandblock never returns 0; harmless dead defensive
|
|
terms, prefix-agnostic. (Noted follow-up: CanonicalLandblock(0) aliases
|
|
cellId 0 -> 0x0000FFFF in these helpers; unreachable with real
|
|
operation cells today, flagged rather than redesigned.)
|
|
- Core PhysicsEngine bare-id compat (:1355/:1362 SampleTerrainWalkableInCell,
|
|
:2093 HasCellSurface): `requestedPrefix == 0` there means "caller passed a
|
|
bare pre-#106 test-fixture id", a different semantic; corner cells resolve
|
|
correctly through the world-position filter. Follow-up cleanup candidate,
|
|
out of this contract's chain.
|
|
- Convention pinned by test: raw input 0x00000000 remains "absent"
|
|
everywhere; landblock (0,0) is addressed by canonical 0x0000FFFF (or any
|
|
cell inside it) — matching what production streaming always passes.
|
|
|
|
### Tests (7 new, all fail pre-fix / pass post-fix; pre-fix run of the
|
|
### production-chain test reproduced the EXACT 135444 crash signature:
|
|
### ArgumentOutOfRangeException 'landblockId' at RuntimeSetPositionState.cs:783
|
|
### <- RuntimePhysicsState.cs:1993 <- :2432)
|
|
New partial tests/AcDream.Runtime.Tests/Physics/
|
|
RuntimeCollisionPrefixQuiescenceTests.CornerLandblock.cs:
|
|
- CornerLandblockCollisionGenerationCommitsThroughTheProductionAdmissionChain
|
|
(corner 0x0000FFFF + neighbor 0x0001FFFF, empty engine, full
|
|
admission->prepare->stage->seal->commit, ownership converged)
|
|
- CornerResidentParksAndRestoresAcrossAnActivationReplacement (SetPosition
|
|
commit into corner cell; activation replacement parks the resident under
|
|
the prefix-0 quiescence override, wakes, restores)
|
|
- CornerPrefixQuiescenceHoldsAndReleasesExactlyLikeANonzeroPrefix (contract
|
|
test 2: identical held-placement script vs PrefixP, step-for-step log
|
|
parity incl. QuiescenceHeld hold/acquire/cancel/restore)
|
|
- CornerLandblockDemotesAndWithdrawsThroughRetirementMutations
|
|
- AbsentLandblockIdStillCannotBeginQuiescence (id-0 keeps throwing at
|
|
quiescence AND at the new admission-entrance guard; corner token IsValid)
|
|
tests/AcDream.Core.Tests/Physics/ShadowObjectRegistryTests.cs:
|
|
- Register_CornerLandblock_DerivesRealOutdoorSeed
|
|
- Register_AbsentLandblockId_StillKeepsWhenEmpty (passed pre-fix too —
|
|
pins the preserved keep-when-empty guard)
|
|
Two in-flight test corrections during TDD: seeded corner Place asserts
|
|
CommittedHostAcknowledgementPending (the ordinary bound-events commit
|
|
status — over-strict first draft); parity run addresses the corner by
|
|
canonical 0x0000FFFF, not raw 0 (which is the absent sentinel by design).
|
|
|
|
### Gate ladder
|
|
1. Focused corner tests: 5/5 Runtime + 39/39 ShadowObjectRegistry suite.
|
|
Complete projects: Runtime 997/997 (992+5), App 4,037/3 skips of 4,040,
|
|
Headless 78/78.
|
|
2. Release solution build: 0 errors. Complete solution (-m:1, PAK): running.
|
|
2 (cont). Complete solution (-m:1, ACDREAM_PAK_PATH): 10,804 passed / 0
|
|
failed / 4 skipped (App 4,037/3, Bake 15, Cli 4, Content 124, Core.Net
|
|
762, Core 4,244/1, Headless 78, Runtime 997, UI 543).
|
|
3. Connected gate: run logs/connected-world-gate-20260802-142539 launched
|
|
against live ACE (UDP 9000 confirmed listening, no stale client); result
|
|
pending.
|
|
4. git diff --check: clean (line-ending metadata warnings only, matching the
|
|
known worktree pattern). Nothing staged.
|
|
3 (result). Connected gate PASS: logs/connected-world-gate-20260802-142539/
|
|
report.json Passed=true, Failures=[], both sessions exit 0, one warning
|
|
("capped: 25 expected world-edge landblock miss(es)" — the expected
|
|
class). The 135444 corner crash is gone: the capped session completed the
|
|
full route (capped_login, facility_hub, aerlinthe_first, rynthid,
|
|
holtburg_after_dungeon, aerlinthe_revisit checkpoints + screenshots
|
|
under capped/artifacts/), uncapped-reconnect completed
|
|
uncapped_reconnect. Coordinator confirmed and directed the soak.
|
|
5. R6 soak launched (run-connected-r6-soak.ps1, same ACE, detached PID
|
|
14036); result pending.
|
|
5 (result). R6 soak logs/connected-r6-soak-20260802-143157: RESULT=FAIL per
|
|
the PRIMARY artifact (report.json Passed=false, 37 failures) — the
|
|
coordinator's "concluded successfully" summary was based on the markers
|
|
log (route-complete + graceful close are true) but the pass criterion is
|
|
report.json. Failure class: streamingWork convergence
|
|
(deferredCompletions/deferredAdoptedCpuBytes/pendingPublications=1/
|
|
farBacklog nonzero) at 8 of 9 canonical checkpoints (aerlinthe clean),
|
|
plus Caul plateau loadedLandblocks/totalLandblocks 283->189 and mesh
|
|
cache growth 547->588 / +40.5 MB. No crash, no exception, graceful
|
|
logout confirmed; 9/9 checkpoints + screenshots captured.
|
|
ATTRIBUTION ANALYSIS (verified, not guessed):
|
|
- Last passing soak (logs/connected-r6-soak-20260727-004942.report.json,
|
|
Passed=true, 0 failures) enforced the SAME streamingWork expect-zero
|
|
criterion and met it — but predates the ENTIRE uncommitted C3c
|
|
flip+F1+F2+F3 stack, so it separates {branch} from {baseline}, not F3
|
|
from the flip.
|
|
- F3's blast radius was provably NOT exercised in the failing run:
|
|
grep 0x0000FFFF over the soak out.log = 0 hits (the corner landblock
|
|
never entered any route window), zero exceptions (the new absent-id
|
|
guards never fired), and every F3 conversion is byte-identical for
|
|
nonzero prefixes (pinned by the parity test). The failing criterion is
|
|
streaming/publication convergence — a C3c-flip/F1/F2-era surface.
|
|
- The lifecycle gate 142539 (WITH F3) passed cleanly the same day.
|
|
STOPPED per contract — no retry, no further code work, nothing staged.
|
|
|
|
## C3c-F4 — soak streaming-convergence regression: DIAGNOSED, STOPPED (no fix landed)
|
|
|
|
**Verdict: NOT a wedge and NOT in the C3c flip/F1/F2/F3 diff.** It is a
|
|
throughput regression in the committed collision-generation atomic-replacement
|
|
mechanism.
|
|
|
|
### Named mechanism (link by link)
|
|
1. `StreamingController.DrainAndApply` (src/AcDream.App/Streaming/StreamingController.cs:1662-1690)
|
|
advances the completion-queue head and `break`s when it does not complete —
|
|
at most ONE landblock publication per frame.
|
|
2. `LandblockPresentationPipeline.Advance` stage `publication-index-physics`
|
|
(src/AcDream.App/Streaming/LandblockPresentationPipeline.cs:612-627) charges
|
|
`EntityOperations: PreparationCursor < Entities.Count ? 1 : 0`. A FAR build is
|
|
`Array.Empty<WorldEntity>()` (PublishAsFar, :419-447), so every step is FREE
|
|
and only the 2 ms elapsed-time ceiling bounds it.
|
|
3. `LandblockPhysicsPublisher.AdvancePreparationOne`
|
|
(src/AcDream.App/Streaming/LandblockPhysicsPublisher.cs:297-309) gates on
|
|
`RuntimePhysicsState.AdvanceCollisionGenerationPreparation`.
|
|
4. That calls `PreparedLandblockCollisionGeneration.AdvanceStagingClone`
|
|
(src/AcDream.Runtime/Physics/RuntimePhysicsState.cs:531-560) →
|
|
`PhysicsEngine.CollisionStagingBuilder.Advance`
|
|
(src/AcDream.Core/Physics/PhysicsEngine.cs:842-925): ONE leaf per step of an
|
|
off-side draft of the COMPLETE collision world minus the target prefix
|
|
(landblock slots, CellStruct, FlatCellStruct, FlatEnvCell, Buildings,
|
|
EnvCells, Terrain, OutdoorCells, shadow-owner slots).
|
|
5. `PhysicsEngine.CommitLandblockReplacement` (:304-321) does
|
|
`stagingCache.CollisionWorld.TransferTo(activeCache.CollisionWorld)` — the
|
|
atomic unit is the WHOLE world, which is why the whole world must be cloned.
|
|
|
|
### Measured (attributed probes, lifecycle-gate route, since stripped)
|
|
- median 19,736 / p90 32,135 / max 38,021 clone leaves per landblock
|
|
publication; median 3.64 ms CPU each; 1,584 preparations = 8.53 s CPU in one
|
|
4-minute capped session.
|
|
- Far-queue drain measured at ~10 landblocks/s; `[queue-stale]` showed the head
|
|
seq advancing 234→488→548→603→…→1360 and far count 411→351→296→242→190 —
|
|
the queue drains, it never catches up. `pendingPublications=1` is the one
|
|
head in flight, not a stuck item.
|
|
- `[fifo-wedge]` never fired: `pendingProjections=0`. The placement projection
|
|
FIFO, the sink's C3c residence gate, and the conductors are NOT involved.
|
|
|
|
### Attribution
|
|
`git log -S CollisionStagingBuilder` → introduced by `6b28ff99`
|
|
"fix(physics): make collision activation starvation-free" (2026-07-31), on top
|
|
of `be94bc9b` (atomic activation, 2026-07-31) / `9b0f59bd` (2026-08-01). The
|
|
last PASSING soak is `a9a822f2` (2026-07-27) — before all three. `git diff`
|
|
touches no file on this path.
|
|
|
|
### Why no fix landed (contract STOP rule)
|
|
Tried the one semantics-preserving lever: batch 256 leaves per metered
|
|
preparation step. Measured 1.8x (gate far backlog 334→187 / 264→130, loaded
|
|
landblocks 291→438 / 261→395) — real but NOT convergence. It also fails
|
|
`RuntimePhysicsStateTests.DenseResidentWorldAdmissionIsConstantAndMaterializesOneLeafPerStep`
|
|
(tests/AcDream.Runtime.Tests/Physics/RuntimePhysicsStateTests.cs:983,
|
|
`Assert.InRange(step.WorkUnits, 0, 1)`) — one-leaf-per-step is an ASSERTED
|
|
invariant of the slice that introduced the clone. Reverted; tree is byte-identical
|
|
to flip+F1+F2+F3.
|
|
|
|
Convergence requires the clone to become O(changed) instead of O(resident world)
|
|
— structural sharing in `CollisionWorldState`, or a per-landblock (not
|
|
whole-world) atomic replacement unit. That is a semantics change to the C2-era
|
|
mechanism and belongs to its own slice.
|
|
|
|
### Secondary symptoms — same mechanism, not separate defects
|
|
`loadedLandblocks` 283→189 and `visibleLandblocks` 30 vs the baseline's 180 are
|
|
the far ring never converging (baseline held 625 loaded at every checkpoint).
|
|
Mesh cache 547→588 / +40.5 MB is different far-ring subsets resident at the two
|
|
visits; retest for a true leak only after convergence is restored.
|
|
|
|
## C3c-F5 — local-player first-entry contact seeding
|
|
|
|
### Research (grep-named-first, complete before design)
|
|
- Retail local-player seeding point: `SmartBox::HandleCreateObject` 0x00454C80
|
|
runs `SmartBox::init_player` 0x00455010 then `CPhysicsObj::enter_world`
|
|
(call site 0x00455095; body 0x00516170) for the LOCAL player — the same
|
|
enter_world the non-player branch uses at 0x004550EC. enter_world builds
|
|
SetPositionStruct flags 0x11, calls CPhysicsObj::SetPosition, sets
|
|
`transient_state |= 0x80` (ACTIVE) and HandleEnterWorld — NO contact
|
|
seeding anywhere in it (pseudo-C 284198-284249). Contact arrives from the
|
|
first gravity frame (digest #270 section; find_placement_pos validates the
|
|
spot but records no touch). Local player and remote spawn CONFIRMED to
|
|
share the retail mechanism.
|
|
- Legacy local path (deleted by the flip): BuildControllerAndCamera ran
|
|
Resolve(100f drop) + ResolvePlacement then PreparePositionForCommit ->
|
|
SetPositionCore, which FORCE-seeded `Contact | OnWalkable | Active`
|
|
("Treat as grounded after a server-side position snap",
|
|
PlayerMovementController.cs:1830-1834) — an unconditional non-retail seed
|
|
(Contact-without-plane, the state the landing family calls
|
|
unrepresentable). The flip deleted the call chain without an equivalent.
|
|
- New path today: conductor -> publication -> dormant activation ->
|
|
PhysicsEngine.SetPosition (faithful port, result.InContact=false for a
|
|
clean placement) -> TryApplyDormantLocalActivationCommit commits
|
|
contact=false -> FinalizeActivation activates. Body starts airborne;
|
|
outbound CanSendPositionEvent (= InContact && OnWalkable,
|
|
PlayerMovementController.cs:1517) stays false -> MTS contact byte 0
|
|
(LocalPlayerOutboundController.cs:203) -> ACE says 'while in the air'.
|
|
- DO-NOT-RETRY compliance: settle passes isOnGround:false + real body (no
|
|
caller-bool seeding); no ContactPlaneValid gating; no forced transients —
|
|
contact only from the sweep's real touch; airborne spawn stays airborne.
|
|
|
|
### Design (pinned-contract shape)
|
|
- Move RemoteSpawnPlacementSettler (App) -> Core.Physics
|
|
`SpawnPlacementSettler` (public, like PhysicsObjUpdate; Core internals are
|
|
NOT visible to App). Remote caller + tests updated; semantics byte-identical.
|
|
- Seed at RuntimeLocalPlayerPhysicsPublicationState.FinalizeActivation,
|
|
after TryApplyDormantLocalActivationFinalCommit + shadow dispatch +
|
|
IsCommittedActivationSuffixCurrent, before placement dispatch (no
|
|
reentrant-sink hazard; stale-authority path skips the settle). Inputs:
|
|
activation Body/Record key/ActivationPreparation radius+height,
|
|
IsPlayer|EdgeSlide|OwnPvpFlags, Movement.HitGround/Motion.LeaveGround —
|
|
the same callback pair the per-tick landing path uses.
|
|
- Propagation: body transients (a) are THE controller grounded state (b)
|
|
(controller reads _body directly) and THE outbound bit (c)
|
|
(CanSendPositionEvent -> contactByte). No second copy exists.
|
|
|
|
### Implementation (seams touched)
|
|
1. src/AcDream.Core/Physics/SpawnPlacementSettler.cs — NEW (moved from
|
|
src/AcDream.App/Physics/RemoteSpawnPlacementSettler.cs, deleted; public
|
|
like PhysicsObjUpdate because Core internals are not visible to App).
|
|
TrySettle body byte-identical to the #270 shipped version; only the
|
|
class name/namespace/doc changed.
|
|
2. src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:193 — the
|
|
one legacy remote caller now calls
|
|
AcDream.Core.Physics.SpawnPlacementSettler.TrySettle (unchanged args).
|
|
3. src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs
|
|
— FinalizeActivation now calls new private
|
|
SettleFirstEntryGroundContact(activation) AFTER
|
|
TryApplyDormantLocalActivationFinalCommit + shadow dispatch +
|
|
IsCommittedActivationSuffixCurrent, BEFORE placement dispatch (no
|
|
reentrant-sink window; stale-authority early return skips the settle).
|
|
Inputs: activation.Body position/cell, ActivationPreparation
|
|
radius/height, IsPlayer|EdgeSlide|OwnPvpFlags,
|
|
Controller.LocalEntityId, Movement.HitGround/Motion.LeaveGround (the
|
|
per-tick landing pair). try/catch matches the existing post-commit
|
|
ground-edge dispatch containment (_activationDispatchFailureCount).
|
|
4. Tests: tests/AcDream.Core.Tests/Physics/SpawnPlacementSettlerTests.cs
|
|
(moved from App.Tests, bodies unchanged, layer rule 6);
|
|
Issue270ProductionWiringTests source pin updated to the new name;
|
|
RuntimeLocalPlayerPhysicsPublicationStateTests +2
|
|
(CommitActivationOnFlatGroundSeedsRetailFirstGravityFrameContact,
|
|
CommitActivationOverVoidLeavesFirstEntryGenuinelyAirborne) + fixture
|
|
moverSphereOriginZ param (default 0 = pre-existing shape);
|
|
RuntimeFirstEntryHostIntegrationTests +2
|
|
(LocalLogin_FlatGround_ReportsGroundedOutboundContactBit,
|
|
LocalLogin_AirborneSpawn_StaysGenuinelyAirborne) + HostFixture
|
|
terrainHeight/moverSphereOriginZ params.
|
|
|
|
### Gate ladder
|
|
1. Focused: publication-state 92/92 (90+2), first-entry conductor 15/15,
|
|
settler 3/3 (Core), App first-entry integration + issue-270 wiring 8/8.
|
|
Complete projects (Release): Runtime 999/0, App 4,036/3 skips,
|
|
Headless 78/0, Core 4,247/1 skip.
|
|
NOTE (pre-existing, not this slice):
|
|
LandblockBuildOriginTests.FarLoad_StripsEnvCellsAndPhysics... fails in
|
|
DEBUG only — the test feeds an intentional near payload to a FarLoad and
|
|
LandblockStreamer.cs:505 Debug.Assert fires; Release (the gate config)
|
|
compiles it out and it passes. Reproduced 3x isolated in Debug, passes
|
|
in Release; untouched by this slice's diff.
|
|
2. Release solution build 0 warn/0 err; complete solution (-m:1,
|
|
ACDREAM_PAK_PATH): 10,808 passed / 0 failed / 4 skips
|
|
(App 4,036/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,247/1,
|
|
Headless 78, Runtime 999, UI 543).
|
|
3. Connected lifecycle gate: launched against live ACE (process 22100,
|
|
UDP 9000); result pending.
|
|
3 (result). Connected lifecycle gate PASS:
|
|
logs/connected-world-gate-20260802-164432/report.json Passed=true,
|
|
Failures=[], both sessions ExitCode=0, one warning ("capped: 25 expected
|
|
world-edge landblock miss(es)" — the exact 142539/161138 class). All 6
|
|
capped checkpoints + uncapped_reconnect captured with screenshots.
|
|
"while in the air" grep: 0 in capped/stdout.log, 0 in
|
|
uncapped-reconnect/stdout.log (0x042C also 0/0). Note: the two prior
|
|
PASS runs (142539/161138) also contained 0 occurrences — the scripted
|
|
route never surfaced the rejection string; the behavioral proof of the
|
|
fix is the outbound-bit integration tests + the settle assertions, and
|
|
the gate proves no regression.
|
|
4. git diff --check exit 0 (CRLF metadata warnings only, the known worktree
|
|
pattern); nothing staged; no probes added by this slice.
|
|
|
|
### Register note
|
|
No divergence-register row existed for the #270 compressed settle (it is
|
|
classified as timing compression — it produces exactly the state retail's
|
|
first gravity frame produces); extending it symmetrically to the local
|
|
player follows the same classification. The commit that lands C3c should
|
|
also delete/refresh AD-42 (its cited legacy GameWindow/PlayerModeController
|
|
resolve path no longer exists in the flipped tree) — flagged for the
|
|
closeout, not acted on here (report-only bookkeeping, no doc edits in this
|
|
slice's scope).
|
|
|
|
### Purple-haze note (observation only)
|
|
The haze script is the Hidden/UnHide materialization path
|
|
(EntityEffectController.PlayTypedFromHiddenTransition — retail set_hidden
|
|
0x00514C60); nothing in it keys off contact/airborne state, so the F5
|
|
contact gap does NOT plausibly drive the re-fire. A re-fired UnHide implies
|
|
the local player's presentation saw a hidden/visibility edge while standing
|
|
— consistent with F4's streaming/publication convergence regression
|
|
re-bucketing the player's surroundings, which remains the plausible driver.
|
|
|
|
## C3c-R1
|
|
Fix round for review round 1 (contract c3c-r1-fixes.md). Every finding
|
|
verified at source before editing; dispositions below.
|
|
|
|
### Verification-at-source results (pre-edit)
|
|
- R1 CONFIRMED: CommitPreparedPosition (PlayerMovementController.cs:1784)
|
|
had ZERO production callers post-flip (grep); PreparePositionForCommit
|
|
(called at RuntimeLocalPlayerPhysicsPublicationState.cs:219) uses
|
|
publishSharedState:false and the PositionManager binds at :318 (after the
|
|
position seed), so no login path armed the leash. The final commit
|
|
(RuntimeSetPositionState.cs:2494-2516) is where the accepted position is
|
|
final, the shared cell is published (:2508), and the controller activates
|
|
(:2516).
|
|
- R2 CONFIRMED: LiveEntityRuntime.cs:806-828 keyed the presentation-only
|
|
branch off the sticky enum; post-residence entities skipped CommitRebucket
|
|
(:882-892) + the prepare_to_enter_world clock edges (:897-924). All six
|
|
cited unflipped-route callers verified (grep RebucketLiveEntity).
|
|
- R3 CONFIRMED: RuntimeLiveEntitySessionController.OnSpawned opened the
|
|
residence unconditionally; HeadlessSessionHost builds drive+projection only
|
|
under `_contentLease is { } content` (:539-565); content-less is
|
|
validated-legal (HeadlessConfigurationLoader.ValidateContent :88-98
|
|
returns on null). worldProjection==null is exactly "no drive exists"
|
|
(single production constructor call site).
|
|
- F2 CONFIRMED: SpawnPlacementSettler.cs:61 commits settle.Position;
|
|
settle.CellId never read.
|
|
- F4 CONFIRMED: F3's landblockId==0 guards (RuntimePhysicsState
|
|
.BeginCollisionAdmission :2037-area) throw through
|
|
HeadlessCollisionGenerationTransaction.Begin (:62) reachable from
|
|
CenterOn; the two cited sites passed the raw wire LandblockId unguarded.
|
|
- F5/F6/F7/F8/F9 confirmed as cited (drive `_pending` outside every ledger;
|
|
both route Disposes cleared a SHARED drive unconditionally; far-remote
|
|
DeferredCell park has no wake outside the 3x3 neighborhood; the three
|
|
stale comments; the per-pump TryGetWorldEntity+GetSetupCylinder).
|
|
|
|
### F3 — STOPPED, pinned design conflicts with source (file:line evidence)
|
|
The pinned probe ("nested production OnCreate, not hand-called
|
|
AdvanceCreateAuthority") cannot produce create-authority drift in the
|
|
items-6/8 tests' post-residence window:
|
|
1. RuntimeEntityObjectLifetime.cs:660-665 — the ExistingGeneration branch
|
|
calls Entities.AdvanceCreateAuthority ONLY when !beginInitialResidence;
|
|
the graphical route always registers with residence
|
|
(LiveEntityRuntime.cs:544-548), so a post-residence same-generation
|
|
Create is description-only.
|
|
2. The FIFO-adoption alternative is closed: ConsumeExecuted removes the
|
|
completed residence entry at Released
|
|
(RuntimeInitialCreateResidenceState.cs:1246-1251), and the fixtures
|
|
complete+release during the initial OnCreate, so TryGetTransaction
|
|
(:729-748) misses and no continuation can be staged post-release.
|
|
3. The executor drain (RuntimeInitialCreateContinuationExecutor.cs:2424-2429)
|
|
is the ONLY production site that advances create authority for an
|
|
existing incarnation - exactly what the hand-call models.
|
|
4. EMPIRICAL: temporarily restoring the nested-OnCreate probe in
|
|
FailedCompletedSupersession_RemainsPendingUntilExactRetry made BOTH rows
|
|
fail "Assert.Throws() Failure: No exception was thrown" (no drift, no
|
|
CreateSupersessionRecovery). Experiment reverted; tree byte-identical
|
|
for that file except nothing.
|
|
Restoring the probe requires either accepting dead drift machinery or a
|
|
test-scenario redesign (drift staged during the ACTIVE-residence window and
|
|
drained mid-recovery by a reentrant pump) - a design call for the
|
|
coordinator, not a probe swap. NOT implemented; reported.
|
|
|
|
### R4(c) progress-log correction (retail minor M1)
|
|
The C3c-F5 section above says the legacy local force-seed path was
|
|
"deleted by the flip". CORRECTION: the force-seed at
|
|
PlayerMovementController.cs SetPositionCore ("Treat as grounded after a
|
|
server-side position snap", Contact|OnWalkable|Active) still RUNS during
|
|
publication-candidate preparation (PreparePositionForCommit ->
|
|
SetPositionCore) and is then OVERWRITTEN by the faithful activation commit
|
|
(which commits the SetPosition result's contact=false) and the settle. What
|
|
the flip deleted was the CALLER CHAIN (BuildControllerAndCamera's
|
|
Resolve/ResolvePlacement + CommitPreparedPosition), not the seed statement.
|
|
Register row AD-61 records the "overwritten, not deleted" truth.
|
|
|
|
### Implemented (all others)
|
|
- R1: PlayerMovementController.ArmConstraintLeashAtCommittedPlacement
|
|
(internal, published-guarded) + RuntimeLocalPlayerPhysicsPublicationState
|
|
.ArmFirstEntryConstraintLeash called in FinalizeActivation after the
|
|
final commit + shadow dispatch, inside the IsCommittedActivationSuffixCurrent
|
|
gate, BEFORE the settle; exactly-once via `_activation = null` preceding
|
|
the suffix. Ordering justified in the new doc comment with file:line.
|
|
- R2: the public RebucketLiveEntity now suppresses ONLY while
|
|
HasActiveInitialCreateResidence (exact-token activity view); post-residence
|
|
falls through to the FULL legacy branch (CommitRebucket + clock edges).
|
|
RebucketLiveEntityPresentationOnly is now called only from
|
|
TryApplyInitialCreateCompletionPresentation.
|
|
- R3: content-less direct host (worldProjection null) registers via
|
|
RegisterEntity + direct accepted-frame commit (exact pre-flip shape),
|
|
with the C4/C5 revisit note.
|
|
- R4: register AD-61 filed (local-player settle compression, overwritten
|
|
force-seed, settle-CellId caveat); AD-42 refreshed (repointed at
|
|
RemoteTeleportController.ResolvePlacement + headless portal resync;
|
|
login split retired); section count 46->47.
|
|
- F1: LiveEntityRuntime.ConvertMaterializationResidenceToLegacyImmediate
|
|
(throws while residence active); EquippedChildRenderController uses it.
|
|
- F4: `{ LandblockId: not 0u }` guards at both cited CenterOn sites.
|
|
- F5: RuntimeEntityObjectOwnershipSnapshot.FirstEntryDrivePendingCount
|
|
(IsConverged-gated) + RegisterFirstEntryDriveOwnership; the drive
|
|
registers its pending-count provider at construction.
|
|
- F6: RuntimeFirstEntryDriveController.AttachRoute/DetachRoute one-route
|
|
latch (Clear deleted); GraphicalSessionEventRoute + HeadlessSessionEventRoute
|
|
attach/detach with `this`.
|
|
- F7: RuntimeAuthoritativePositionRouteClassifier.ToCellessCreateRoute
|
|
(exact Parented/PickedUp branch shape, preserving authority/operation/
|
|
collision-batch); RuntimeInitialCreateResidenceState.TryConvertToCellessRoute
|
|
(active+unplaced only: ForgetExactPlacement + lease rewrite +
|
|
PublishCancellation + retirement fan-out for conductor progress reset,
|
|
entry retained); RuntimeEntityObjectLifetime
|
|
.TryConvertInitialResidenceToCellessRoute facade;
|
|
IHeadlessCollisionNeighborhood.IsWithinServiceWindow (3x3 around the
|
|
requested center; no-center = within) consumed by
|
|
HeadlessSessionWorldProjection.ProjectSpawn for far remotes before the
|
|
pump.
|
|
- F8: both conductor "nothing calls Advance in production" headers
|
|
corrected; PlayerModeController's two false "auto-entry retries" claims
|
|
replaced with the actual disarm-before-invoke semantics (verified against
|
|
PlayerModeAutoEntry.TryEnter :232-248 - _armed=false precedes the
|
|
invoke, and a throw propagates before the context's reveal Complete()).
|
|
- F9: the graphical activation-preparation provider caches the resolved
|
|
setup cylinder per incarnation (keyed by LocalEntityId; unresolved
|
|
results not cached; shadow disposition stays live - it is validated
|
|
against the exact registry at activation commit and may change between
|
|
pumps).
|
|
|
|
### New tests
|
|
- Runtime: CommitActivationArmsTheLoginConstraintLeashAtTheCommittedPlacement,
|
|
CommitActivationNeverRearmsTheLeashOnAStaleRetry (publication suite);
|
|
ContentLessDirectSink_KeepsPreFlipLegacyRegistration,
|
|
FirstEntryDriveServesOneRouteAtATimeAndScopesClearToTheOwner
|
|
(session-controller suite; DirectSinkOwnsCanonicalCreateUpdateDelete...
|
|
updated to pass a world projection so it keeps exercising the residence
|
|
route per R3's semantics).
|
|
- App: PostResidenceRebucket_TakesTheFullLegacyPathIncludingTheClockEdge
|
|
(active-suppression + F5 ledger visibility + retired->legacy CommitRebucket
|
|
+ the pending-reentry clock rebase),
|
|
ResidenceConversionToLegacyImmediate_RefusesWhileTheResidenceIsActive.
|
|
- Headless: FarRemoteCreateCompletesCelllessWithoutPinningItsResidence
|
|
(+ Spawn(guid, cellId) helper param; FixtureCollisionNeighborhood
|
|
implements IsWithinServiceWindow).
|
|
- One in-flight test correction during TDD: the leash test's anchor
|
|
assertions initially assumed the committed placement kept the raw spawn
|
|
Z=3; the faithful placement transaction already floor-snaps (2.705), so
|
|
the anchor asserts the committed floor-snapped band + committed cell.
|
|
|
|
### Gate ladder (this round)
|
|
1. Runtime 1,003/1,003; App 4,038/3 skips of 4,041; Headless 79/79.
|
|
2. Release solution build: 0 errors. Complete solution (-m:1,
|
|
ACDREAM_PAK_PATH): 10,815 passed / 0 failed / 4 skipped of 10,819
|
|
(App 4,038/3, Bake 15, Cli 4, Content 124, Core.Net 762, Core 4,247/1,
|
|
Headless 79, Runtime 1,003, UI 543).
|
|
3. Connected gate run 1: logs/connected-world-gate-20260802-174811 —
|
|
Passed=false, ATTRIBUTED TO USER INTERFERENCE per the coordinator
|
|
(the user manually drove the client, including a teleport, during the
|
|
run). Fingerprint: every failure at the capped_login checkpoint only
|
|
(transitOwnership.activeTeleportCount=1 at the stable checkpoint,
|
|
activeRevealCount=1, pendingDestinationReadinessCount=1,
|
|
hostProjectionCount=1, reveal/viewport/composites/collision not ready,
|
|
216 staged mesh uploads + 44 composite warmups mid-stream to the manual
|
|
teleport destination). No crash, no exception; only the expected
|
|
world-edge warning class. Coordinator sanctioned exactly ONE clean
|
|
re-run (external interference, not a blind retry); graceful-close
|
|
discipline held (no stale client process, ACE endpoint intact).
|
|
4. git diff --check: exit 0 (CRLF metadata warnings only, the known
|
|
worktree pattern); nothing staged.
|
|
3 (result). Sanctioned clean re-run PASS:
|
|
logs/connected-world-gate-20260802-175401/report.json Passed=true,
|
|
Failures=[], only the expected world-edge warning — the established
|
|
passing signature (142539/161138/164432 class). 174811 interference
|
|
attribution confirmed. Round complete; nothing staged.
|
|
|
|
## P1 — origin-recenter retirement-receipt exception loop
|
|
|
|
Root cause pinned in
|
|
`p1-retirement-receipt-loop.md`. An already-detached landblock can retain
|
|
live projections in `GpuWorldState._pendingByLandblock` while its one exact
|
|
full-cleanup ticket advances. The recenter swap incorrectly promoted that
|
|
pending-only spatial bucket into a second full presentation receipt. The
|
|
coordinator rejected the duplicate correctly, but the controller's broad
|
|
resume catch replayed the already-committed detach 243 times in the captured
|
|
feel-test session.
|
|
|
|
Implemented:
|
|
|
|
- pending-only live buckets are retained through `_projectionLocations` but
|
|
no longer manufacture a landblock retirement receipt;
|
|
- loaded/pending-render/pending-near/tier/bounds owners still receive exact
|
|
receipts;
|
|
- a genuine receipt-ledger invariant after the spatial commit is surfaced as
|
|
a committed `StreamingMutationException` and cannot enter retry work;
|
|
- the existing duplicate-receipt guard remains unchanged.
|
|
|
|
TDD evidence: the new pending-only regression failed before the source edit
|
|
(`Assert.Empty`, one receipt returned) and passes afterward. The production
|
|
controller/recenter regression and committed-invariant fail-fast regression
|
|
also pass. Focused `OriginRecenter` group: 20/20.
|
|
|
|
Final gates: Release build passed; the complete Release suite passed
|
|
10,815/10,815 with 4 skips; the connected lifecycle gate passed at
|
|
`logs/connected-world-gate-20260802-203751/report.json`; and the nine-stop
|
|
soak passed at `logs/connected-r6-soak-20260802-204309.report.json` with all
|
|
9 canonical checkpoints, zero failures, zero wait cues, zero pending
|
|
landblock retirements, and zero recurrence of the 243x exception signature.
|
|
|
|
### F3 addendum (coordinator resolution accepted, implemented)
|
|
Hand-calls KEPT as honest documented models: enriched comments at all six
|
|
item-6/8 sites (LiveEntityHydrationControllerTests x5 sites incl. the
|
|
shared item-6/CompletedSupersessionReadyFailure text,
|
|
LiveEntityCreateSupersessionRecoveryTests x1) citing
|
|
ApplyWeenieDescriptionAction as the sole production site,
|
|
RuntimeEntityObjectLifetime :660-665 !beginInitialResidence gate, and
|
|
ConsumeExecuted. NEW source pin
|
|
tests/AcDream.App.Tests/World/C3cR1F3DriftModelSourcePinTests
|
|
.HandCalledDriftProbe_StillModelsTheExecutorDrainAdvance (single
|
|
_entities.AdvanceCreateAuthority in the executor, inside
|
|
ApplyWeenieDescriptionAction; registration advance still residence-gated).
|
|
Focused files 82/82; Runtime 1,003/1,003; App 4,039/3 skips of 4,042;
|
|
git diff --check exit 0. No staging/commits.
|