acdream/docs/research/2026-08-10-365-headless-hydration-diagnosis.md
Erik 326a186897 docs(issues): close #368 (fixed in b7f59923); file #370 — the headless jump-airborne timeout survives single-threading, so it was never a threading artifact
#368's entry now records the fix mechanism (dedicated
acdream-headless-update thread owning Start + every scheduler turn;
synchronous TimeProvider-timer scheduler loop; guard untouched, zero
shared Runtime changes) and the 3/3 live-ACE verification vs the 3/3
pre-fix quarantines. The #365 entry and diagnosis doc get dated
pointers: their open question is answered — the airborne residual
persists with threads provably single, refuting the
unsynchronized-thread hypothesis — and is split off as #370 with the
evidence and starting points.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 18:24:01 +02:00

26 KiB
Raw Blame History

#365 headless hydration — Opus diagnosis (2026-08-10, read-only pass)

Verbatim agent output. The fix agent works solely from this document.

0. Executive summary

Three distinct defects layered on top of each other. Only the middle one is the hydration root cause.

# Layer Verdict
A HeadlessLocalPlayerFrameHost.CanAdvancePlayer accepts a dormant (unpublished) controller; the graphical host does not. This is the crash. CONFIRMED by source. Real host-asymmetry bug.
B The headless collision neighborhood publishes collision as a reaction to the local-player Create, interleaved with the Create burst. Every TrySealCollisionEvaluationAuthority during that window is structurally refused, and (hypothesis) the mutual HasOldPrefixPlacementDebt ↔ seal dependency between remote first-entry operations and the landblock commit closes a circular wait that never opens. This is why the controller never publishes. Mechanism CONFIRMED; the "never opens" closure is HIGH-CONFIDENCE HYPOTHESIS. One existing probe answers it in one line.
C HeadlessStaticStateAudit unconditionally refuses to start when any PhysicsDiagnostics.Probe*/Dump* flag is set — including single-session runs. The exact probe built to diagnose this stall class (ACDREAM_PROBE_PARK=1) cannot be run headless at all. CONFIRMED. Why #365 arrived with no [rearm]/[pump] evidence.

Fix C first (two-line predicate change), then use it to confirm B, then fix B, then fix A. Fixing A alone is the forbidden workaround shape: it converts a hard crash into a silent never-moves bot.

1. Correction to the ISSUES.md evidence chain

entities: 0 in the headless JSON is NOT evidence of failed hydration. HeadlessDiagnosticWriter.Lifecycle (HeadlessDiagnosticWriter.cs:22-45) is called at exactly four points (HeadlessSessionHost.cs:289, 463, 551, 588): constructed (pre-connect), start-result (inside StartLive, BEFORE the first Runtime.Session.Tick() drains a packet), reconnect-deferred, and stopped (AFTER teardown emptied the directory). A perfectly healthy run also prints entities: 0 on every lifecycle line. live: in world — CreateObject stream active (LiveSessionController.cs:593) prints before any CreateObject routes. The periodic resources sampler sums real counts but defaults to a 30 s period the quarantined run never reached.

Do NOT hunt for "CreateObjects never admit." The record almost certainly IS registered: RuntimeLocalPlayerPhysicsPublicationState.Prepare dereferences record.Key!.Value.LocalEntityId (:214), CanPrepare requires _entities.IsCurrent(record) (:1052), and the crash proves a controller was constructed. The entityCount datum is an artifact.

2. Q1 — inbound route and the fork points

Headless inbound route: WorldSession.EntitySpawnedLiveSessionEventRouterRuntimeLiveEntitySessionController.OnSpawned (:129) → RegisterEntityWithInitialResidence (:147-152) → ApplyAcceptedSpawn (:158) → HeadlessSessionWorldProjection.ProjectSpawn (:627): local player → _collision.CenterOn(position.LandblockId) (:644-648) then _firstEntry.DriveAll() (:666) → RuntimeFirstEntryDriveController.DriveOne (:200) → RuntimeLocalPlayerFirstEntryState.Advance (:247).

Per-tick pump (HeadlessSessionHost.Tick:333-362): Clock.AdvanceAdvanceBeforeNetworkSession.Tick()PumpFirstEntry()PumpPortalCompletion()RetryPending()RunPostNetworkCommandPhase()policy.Tick.

No presentation-gated admission step, no missing subscription, no reveal/streaming gate on the headless admission path — C3c (529e0e9d) unified them. The fork is downstream:

Fork point 1 (the crash) — HeadlessLocalPlayerFrameHost.cs:42-44

public bool CanAdvancePlayer =>
    _runtime.Session.IsInWorld
    && _runtime.MovementOwner.Controller is not null;

Graphical host: LocalPlayerFrameRuntime.cs:24-25CanPresentPlayerIsPlayerMode, entered only when PlayerModeAutoEntry.cs:100 Controller is { IsRuntimePublished: true } (the C3c-F2 fix). Controller becomes non-null at publication Commit (RuntimeLocalPlayerPhysicsPublicationState.Commit:472) in RuntimeOwnedDormant; RuntimePublished only arrives inside TryApplyDormantLocalActivationFinalCommit (RuntimeSetPositionState.cs:2823 controller.ActivateRuntimePublication()). So headless runs RuntimeLocalPlayerFrameController.AdvanceBeforeNetwork (:75) against a dormant controller: :93 LocalEntityId write (quarantine #1, neutralized by ab82347d) and :96-99 Suspend branch → SuspendObjectUpdateEnsurePublishedForRuntimeOperation throw (quarantine #2, current).

Fork point 2 (the hydration stall) — HeadlessSessionWorldProjection.cs:471-548

HeadlessCollisionNeighborhood.AdvanceWork, called from PumpFirstEntry (:736-742) and ProjectSpawn (:644-648). The headless host's ONLY collision publisher is a 3×3 landblock plan built and started by the local player's own CreateObject. The graphical publisher (LandblockPhysicsPublisher.cs) runs on the streaming cadence, independent of and ahead of the Create burst. That timing difference is the whole asymmetry.

3. Q2 — the never-satisfied precondition

The stall state

Conductor parked at Stage.PublicationCommitted in RuntimeLocalPlayerFirstEntryState.AdvanceCore (:437-506), cycling EvaluateActivationAwaitingActivation forever. MovementOwner.Controller non-null (Commit ran) but IsRuntimeOwnedDormant; record.PhysicsBody set, body.InWorld == false (Publication.Prepare:358-359); record.FullCellId == 0ObjectClockDisposition (:78-96) returns Suspend; RuntimeFirstEntryDriveController.DriveOne hits :264-270 and returns every tick. 1:1 match for every symptom.

The precondition

EvaluateActivation (:496-541) fails because RuntimeSetPositionState.TryEvaluateDormantLocalActivation (:1989-2094) returns false at the seal (:2061:2088). TrySealCollisionEvaluationAuthority (RuntimePhysicsState.cs:2411-2469) requires for EVERY prefix the placement's ring search queried:

// RuntimePhysicsState.cs:2365-2371
internal bool IsCollisionEvaluationPrefixAdmissible(uint exactCellId)
{
    uint landblockId = CanonicalLandblock(exactCellId);
    return landblockId != 0u
        && !_collisionAdmissions.ContainsKey(landblockId)
        && !SetPosition.IsCollisionPrefixQuiescing(landblockId);
}

Why headless cannot satisfy it (structural half — CONFIRMED)

AdvanceWork drains a 9-entry plan one commit poll per call, yielding on every non-completed CommitCollisionGeneration (:540-543, HeadlessCollisionGenerationTransaction.Advance:142-156). While it holds _pendingPublication, that landblock's RuntimeCollisionAdmission IS registered, and once CommitCollisionGeneration opens BeginCollisionPrefixQuiescence (RuntimePhysicsState.cs:1866-1889) the prefix IS quiescing — both admissibility terms false for the duration. PumpFirstEntry calls DriveAll() immediately after IsReady (:739-741), so the conductor evaluates exactly while the admission is open. Same in ProjectSpawn (:648:666). The ring search touches the center + its neighbours — every one in the 3×3 plan. The graphical host has the identical seal but long admission-free windows. Same class #357 named; headless is strictly worse because publication is STARTED by the Create.

Why it may never open (HIGH-CONFIDENCE HYPOTHESIS — must be confirmed)

Circular wait between the collision commit and remote first-entry operations:

  1. Committing landblock L requires TryAcquireCollisionPrefixMutationPermission (RuntimeSetPositionState.cs:925-972) → requires !HasOldPrefixPlacementDebt(current).
  2. HasOldPrefixPlacementDebt (:4076-4100) walks every _operations entry, skipping only WakeableLostCell || DormantLocalActivation, true if the operation's command/result/mover-preparation accepted position or its record's collision residency touches L.
  3. RuntimeInitialCreateResidenceState.Own (:750-780) opens an exclusive authored placement operation for EVERY remote Create whose route PerformsSetPosition. Those sit in AwaitingPreparation, not exempt (they gain WakeableLostCell only after successfully submitting and parking; 0934a121 gives them a real body in prefix L, which also trips IsAffectedCollisionResident).
  4. A remote's own placement submission seals against the same prefix — refused while L is admitted/quiescing — so it never reaches the park that would exempt it.

→ L never commits; remotes never park; the player's dormant activation never wakes. The local player IS exempt from step 2 from publication-Commit onward (PrepareDormantLocalActivationOwnership sets operation.DormantLocalActivation = true, RuntimeSetPositionState.cs:1526) — exactly why the local controller gets built and then freezes.

Other blocking terms checked by the same probe: HasPendingProjectionThrough(ProjectionBarrierSequence) (:4059-4062) — note HeadlessRuntimePlacementProjectionSink.TryApply deliberately returns FALSE (leaves at FIFO head) for Place/Withdraw of an entity still holding an initial-create residence (HeadlessRuntimePlacementProjectionSink.cs:63-81) — and HasCollisionDispatchDebt (:4064-4074).

The one line that settles it

TryRearmDeferredDormantLocalActivation prints a purpose-built verdict enumerating every term (RuntimeSetPositionState.cs:~2200-2225), behind PhysicsDiagnostics.ProbeParkEnabled:

[rearm] guid=... verdict=<not-current | stage=X | not-dormant | not-wakeable
        | gen-not-ready | proj-seq | gen-mismatch(a!=b) | spawn-not-ready
        | prefix-inadmissible | OK>
[rearm] guid=... seal-refused (transient; lease retained)
[pump]  DriveAll #N pending=K
[wake]  begin lb=0x... gen=... unboundCells=... buckets=...

And it is unrunnable headless — defect C.

4. Q3 — when it broke

Commit Date Headless impact
529e0e9d — C3c production placement cutover (routes 1+8) 2026-08-02 Prime culprit. Rewrote the headless local-player path wholesale; deleted SynchronizeLocalPlayer/CreateController/ApplySetupStepHeights; controller now only publishable through the residence→publication→activation→seal chain. Gate list: unit tests + a GRAPHICAL connected gate. No connected headless gate.
175ad6b0 2026-08-02 Same seam, same window.
9966b531/2e8e09ac/e0f96a55 08-03→05 Touched HeadlessSessionWorldProjection; graphical-gated only.
6921a027 C5a, 3aab05b0 #280 08-05/06 Comment-only / shape — not causal.
78b981cc — #357 2026-08-08 Symptom-shape culprit. Seal failure reclassified terminal→DeferredCell: pre-#357 headless failed SILENTLY (controller discarded, no crash); post-#357 the dormant controller persists → crash. Correct fix graphically; DO NOT revert.
ab82347d 2026-08-10 Fixed quarantine #1; filed #365.

Coverage gap: the one headless hydration test (HeadlessSessionHostTests.WorldProjectionHydratesCanonicalMovementAndTeleportState, :347-475) uses a FixtureCollisionNeighborhood fake and pre-commits collision by calling SetPosition.BeginCollisionGeneration/ CommitCollisionGeneration DIRECTLY (:372-375) — bypassing admission registry, seal, and quiescence. The production HeadlessCollisionNeighborhood has never been exercised against the first-entry conductor in any test.

5. Q4 — Suspend / CanAdvancePlayer contract

  • Suspend is correct for a cell-less pre-hydration player (retail's parent/cell-less/Frozen early gate, PlayerMovementController.cs:1036-1041). The crash is calling ANY live-movement op on an unpublished controller — the Advance branch would throw identically.
  • CanAdvancePlayer must require publication. Three confirmations: the graphical host gates on IsRuntimePublished (C3c-F2, PlayerModeAutoEntry.cs:100); #356 (972c7ab3) established CanExecuteLiveMovement (PlayerMovementController.cs:797-805) as the lifecycle-caller idiom ("including before the controller is published during login… neither is an error"; MouseLookController.cs:205 is the consumer); and IsActivationOwnershipEnvelopeCurrent (RuntimeLocalPlayerPhysicsPublicationState.cs:900-928) asserts the dormant controller is untouched across activation — advancing it is semantically wrong, not merely fatal. No dormant-activation classification changes needed; the #357 test matrix stays untouched.
  • There is NO #357 closeout doc in docs/research; its record is commit 78b981cc's message + source comments (RuntimeSetPositionState.cs:2073-2088, RuntimeLocalPlayerPhysicsPublicationState.cs:513-534).

6. Q5 — fix plan (ORDERING IS LOAD-BEARING)

Step 1 (enabler, ~10 lines) — make the diagnosis runnable headless

HeadlessStaticStateAudit.cs:15 + call site HeadlessProcessHost.cs:45: the audit's stated rationale is multi-root isolation; give ValidateProcessIsolation a sessionCount parameter and skip the refusal for sessionCount == 1 (emit a diagnostic Message naming enabled probes). Tests: single-session + probe ⇒ starts (and logs); two sessions + probe ⇒ still throws naming the probe. Not a workaround — a correctness fix to a guard whose rationale does not hold for its condition.

Step 2 (measurement, no code) — confirm which term is stuck

Run the repro with ACDREAM_PROBE_PARK=1; read [rearm] verdict=:

verdict Meaning Fix target
prefix-inadmissible persisting structural claim — admission/quiescence never clears Step 3
gen-not-ready / gen-mismatch neighborhood commits a generation the lease isn't parked against narrower fix in HeadlessCollisionNeighborhood
spawn-not-ready 3×3 plan never published the destination cell BuildPublicationPlan / CreatePublication
proj-seq unacknowledged placement projection — the projection-sink residence gate wedge drain rule in RuntimeFirstEntryDriveController
seal-refused repeating, no verdict line operation never re-parks; abort in pre-park evaluation TryEvaluateDormantLocalActivation

Also capture [pump] DriveAll #N pending=K. This step is mandatory (the C4 closeout's "inferring a fact you can observe" finding applies exactly).

Step 3 (root cause) — headless collision publication quiescent-before-drive

Do NOT relax the seal, add a retry budget, or special-case headless inside Runtime. The host's publication cadence violates the (correct) precondition.

3a. Never drive the conductor while the neighborhood holds an open admission or in-flight quiescence. Add read-only IHeadlessCollisionNeighborhood.IsQuiescent (_pendingPublication is null && _publicationQueue.Count == 0 && !_pendingPublicationCancellation); gate PumpFirstEntry (and ProjectSpawn:666, ProjectPosition:690):

if (!_collision.IsQuiescent)
    return;                       // publication owns the authority this tick
_firstEntry?.DriveAll();
_acceptedPositionDrive?.Advance();

3b. If Step 2 shows HasOldPrefixPlacementDebt (remote first-entry operations) is the blocker, 3a alone won't close it: hoist CenterOn off ProjectSpawn onto the accepted local-player position observed at RegisterEntityCore's Physics.ObserveLocalPlayerCreate seam, publish the login window to completion BEFORE the first Create is projected, hold conductor driving until IsQuiescent — restoring the graphical ordering (world published, then entities placed).

Explicitly rejected shapes (workarounds): retry budget on AwaitingActivation; forcing IsCollisionEvaluationPrefixAdmissible to ignore the headless admission; nulling the controller on stall.

Step 4 (crash guard, AFTER step 3) — CanAdvancePlayer requires publication

HeadlessLocalPlayerFrameHost.cs:42-44:

public bool CanAdvancePlayer =>
    _runtime.Session.IsInWorld
    && _runtime.MovementOwner.Controller is { CanExecuteLiveMovement: true };

Use the PUBLIC CanExecuteLiveMovement (#356 idiom), not internal IsRuntimePublished. Optionally harden the shared owner: RuntimeLocalPlayerFrameController.AdvanceBeforeNetwork/RunPostNetworkCommandPhase/ TryGetPresentationAfterNetwork (:75, :135, :166) add || !controller.CanExecuteLiveMovement to the null checks (contract-preserving for the graphical host).

Test list

  1. Runtime: RuntimeLocalPlayerFrameControllerTests — host reporting CanAdvancePlayer:true with a DORMANT controller must not throw in either Suspend or Advance branch (sabotage-verify both directions).
  2. RuntimeLocalPlayerPhysicsPublicationStateTests — assert UNCHANGED (the seven #357 DeferredCell tests + two terminal tests). If any needs editing, the fix is wrong.
  3. The missing headless test: sibling of WorldProjectionHydratesCanonicalMovementAndTeleportState driving the REAL HeadlessCollisionNeighborhood (or a faithful fake registering a RuntimeCollisionAdmission + opening prefix quiescence across ticks); asserts first-entry reaches Completed and MovementOwner.Controller.IsRuntimePublished within a bounded tick count. Must FAIL on the current tree — the acceptance criterion.
  4. PumpFirstEntry does not call DriveAll while non-quiescent; calls it on the first tick after quiescence.
  5. CanAdvancePlayer: false dormant, true published, false retired.
  6. Audit: single-session+probe allowed (logged); multi-session+probe refused.
  7. Keep HeadlessCollisionNeighborhoodServiceWindowTests, HeadlessSessionEventRouteRetryPendingTests green.

End-to-end verification recipe

Config on disk (content-bearing, so the content-less branch at RuntimeLiveEntitySessionController.cs:147 is NOT in play): <scratchpad>/jump-probe-config.json (testaccount/+Acdream, policy jump-probe, credential env ACDREAM_HEADLESS_PASS).

$env:ACDREAM_HEADLESS_PASS = "testpassword"
$env:ACDREAM_PROBE_PARK    = "1"      # requires Step 1
dotnet run --project src\AcDream.Headless -c Release -- run --config <cfg> 2>&1 |
  Tee-Object -FilePath headless-365.log

Cautions: testaccount must not be held by a running graphical client; terminate with Ctrl-C/SIGINT (never Stop-Process — graceful Stop() clears the ACE session in ~3-5 s; a hard kill costs ~3 min of exit-29).

Pass criteria: (1) no kind:"failure"; (2) [jump-probe] local player present; charging jump; (3) [pump] reaches pending=0 and [rearm] verdict=OK (or clean first-pass seal); (4) [jump-probe] airborne-transition False -> True — the bot actually moves (the real acceptance test); (5) graceful exit, converged final disposed sample. Second gate: run the observer-movement policy ~60 s and watch from the graphical/retail client that +Acdream walks — the K3/K4 gate unrun since 2026-08-02.

7. Confidence summary

Claim Confidence Falsify cheaply
Crash = dormant controller reaching SuspendObjectUpdate via headless CanAdvancePlayer Certain
Graphical gates on IsRuntimePublished; headless doesn't Certain PlayerModeAutoEntry.cs:100
entities:0 is a logging artifact Certain HeadlessDiagnosticWriter call sites
Conductor parked at PublicationCommitted/AwaitingActivation Very high [pump] probe
Blocked precondition = IsCollisionEvaluationPrefixAdmissible in the seal High [rearm] verdict=
Circular HasOldPrefixPlacementDebt ↔ seal wait makes it permanent Hypothesis, well-supported [rearm] verdict= + log on TryAcquireCollisionPrefixMutationPermission's four early returns
529e0e9d root cause; 78b981cc changed the symptom High git show 529e0e9d -- src/AcDream.Headless/
Probes unrunnable headless Certain HeadlessProcessHost.cs:45
Existing hydration test bypasses admission/seal Certain HeadlessSessionHostTests.cs:372-375

8. OUTCOME (2026-08-10, fix session)

Steps 1, 3a, and 4 landed exactly as specified; 3b was not needed.

Step 2 measurement (mandatory, run before any Step-3 code): the ACDREAM_PROBE_PARK=1 jump-probe repro against local ACE produced

[pump] DriveAll #1 pending=0
...
[wake] begin lb=0x0904FFFF gen=1 unboundCells=0 buckets=0
[rearm] guid=0x5000000A seal-refused (transient; lease retained)
[rearm] guid=0x5000000A seal-refused (transient; lease retained)
[jump-probe] local player present; charging jump

seal-refused repeating with no preceding [rearm] verdict= line. Per this doc's own Step-2 table, that shape means the operation never even reached the AwaitingCell park: IsExactDormantLocalActivationCurrent already reports "current" on every attempt (the op sits in AwaitingPreparation the whole time), so TryRearmDeferredDormantLocalActivation is never called, and every attempt fails at TrySealCollisionEvaluationAuthority on the SAME still-open admission. This is the doc's §3 "structural half — CONFIRMED" mechanism. No prefix-inadmissible rearm verdict was ever observed, so there is no direct evidence the §3 "why it may never open" HasOldPrefixPlacementDebt circular-wait hypothesis is in play for this repro — 3a alone was measured sufficient.

Fix shape: 3a only. IHeadlessCollisionNeighborhood.IsQuiescent (_pendingPublication is null && _publicationQueue.Count == 0 && !_pendingPublicationCancellation) gates ProjectSpawn/ProjectPosition/ PumpFirstEntry's trailing _firstEntry?.DriveAll() / _acceptedPositionDrive?.Advance() calls exactly as specified. Step 4 landed as specified (CanAdvancePlayer requires CanExecuteLiveMovement, plus the three shared RuntimeLocalPlayerFrameController entry points hardened the same way). Step 1 landed as specified (ValidateProcessIsolation(sessionCount), single session + probe logs and proceeds, multi-session + probe still refuses, naming the probe).

Verification that the new test (item 3) actually discriminates: temporarily reverting the three IsQuiescent gates (commented out, never committed) made both RealAdmissionNeverDrivesTheConductorWhileOpenAndHydratesOnceReleased and PumpFirstEntryWithholdsDriveAllUntilQuiescentThenDrivesImmediately fail — Assert.Null(runtime.MovementOwner.Controller) failed because the controller was ALREADY built and published (CanExecuteLiveMovement = True) while the neighborhood's admission was still held open, exactly the pre-fix race. Both pass cleanly on the real, fixed tree. The gate was then restored and git diff confirmed the file matches the shipped Step-3a diff exactly (no residual simulation code).

A design note for the test: the first attempt at test item 3 opened the held admission on the PLAYER'S OWN landblock via the full HeadlessCollisionGenerationTransaction commit cycle. That hit a genuine, separate settlement question in CommitCollisionGenerationTryAcquireCollisionPrefixMutationPermission (never resolved within 200 ticks in that configuration) — worth a future look if it turns out to matter in production, but not needed to prove Step 3a. The shipped test instead holds a real admission open on a DIFFERENT (neighbor) landblock the player does not target, cancelling rather than committing it — a faithful, simpler proof of "an open admission anywhere in the plan blocks driving" without touching that separate question.

End-to-end (live ACE, jump-probe policy, ACDREAM_PROBE_PARK=1), three runs, all consistent: hydration now succeeds — entityCount reaches 136 at the running-stop resource sample (previously 0, permanently), [jump-probe] local player present; charging jump fires promptly, seal-refused spam is gone, and the original fork-1 crash (SuspendObjectUpdate on a dormant controller) never recurs. Full pass criterion 4 ([jump-probe] airborne-transition False -> True) was NOT independently observed on the unmodified tree — every real run hit a SEPARATE, newly-discovered, pre-existing defect first (filed as issue #368: the headless scheduler's await Task.Delay(...).ConfigureAwait(false) loop can resume ticks on a different ThreadPool thread than the one that opened a collision generation, tripping RuntimePhysicsState.EnsureCollisionMutationThread). #368 is explicitly out of scope for this fix — orthogonal mechanism, no mention anywhere in this diagnosis, and a proper fix needs verification against the graphical host, which this session was constrained not to launch. A throwaway, never-committed diagnostic run with #368's guard neutralized (verified via git diff to have zero residual footprint) DID reach [jump-probe] releasing jump (fire) with a clean exit (code 0, graceful logout, entityCount=136), confirming the #365 mechanism itself is sound; it timed out waiting for airborne-transition True in THAT run, plausibly a downstream artifact of the same unsynchronized-thread condition the neutralized guard exists to catch (racing collision/physics state across threads) rather than a second #365-scope defect — flagged as an open question in #368, not claimed as resolved.

Every real (unmodified) run's session tore down gracefully ([session] graceful logout confirmed, zero entities/leases at the final disposed sample) regardless of which way it exited — testaccount was never left in a stuck state by this work.

9. ADDENDUM (2026-08-10, #368 fix session)

#368 is CLOSED at b7f59923: one dedicated headless update thread now owns Start, every scheduler turn, and the post-loop captures; the scheduler loop is synchronous with TimeProvider-timer event waits (no Task.Delay resumption migration; zero shared Runtime changes). Three live jump-probe runs on the fixed tree each crossed [wake] begin gen=1 cleanly with 204205 hydrated entities and graceful exits. The §8 open question is now answered: the airborne-transition True timeout PERSISTS 3/3 with threading provably single, so the "downstream artifact of the unsynchronized-thread condition" hypothesis is refuted — the residual is a distinct pre-existing defect, filed as #370.