diff --git a/docs/ISSUES.md b/docs/ISSUES.md index f69864bf..49d2212d 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -121,6 +121,51 @@ than a REJECT-review fix batch; CH5-or-later. **Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH, CH4 REJECT-review). +## #365 — Headless host cannot move at head: session quarantines on the first advance tick; world never hydrates + +**Status:** OPEN — filed 2026-08-10 during the Campaign CH jump-probe +reproduction. Distinct from #330 (no collision) and #332 (no remote DR): +this is the LOCAL player. + +**Symptom:** a headless session (`jump-probe` policy, real ACE connect, +`testaccount`/`+Acdream`) reaches `InWorld`, `live: in world — CreateObject +stream active` prints, but `entityCount` stays **0** for the whole run and +the local movement controller is never published. The first +`SetIntent(Jump: true)` → next `AdvanceBeforeNetwork` tick quarantined the +session. + +**Evidence chain (2026-08-10):** +1. First quarantine: `RuntimeLocalPlayerFrameController.AdvanceBeforeNetwork:93` + unconditionally re-assigned `controller.LocalEntityId` — a sealed + configuration property — on a still-dormant controller. FIXED in the + probing session: a same-value re-assertion is now a no-op (a different + id while sealed still throws). +2. Second quarantine (one layer deeper): the frame controller takes the + `ObjectClockDisposition == Suspend` branch and calls + `SuspendObjectUpdate` → `EnsurePublishedForRuntimeOperation` throws — + `_host.CanAdvancePlayer` is true while the controller is unpublished. +3. Root condition: the session's world never hydrates (`entities: 0` + despite the active CreateObject stream), so dormant local activation + never completes and the controller never publishes. K3/K4 ran full + connected multi-session gates with movement — this regressed somewhere + in the many Runtime/placement/CH landings since 2026-07-27. + +**Also observed in the same run:** `[weenie-error] unmapped code=0x051D` — +an ACE-only id outside retail's 344-case `HandleFailureEvent` switch; +CH2's silent-toward-player + diagnostics-line fallback handled it as +designed (no action needed, noted for completeness). + +**Repro:** `dotnet run --project src/AcDream.Headless -c Release -- run +--config ` with a `jump-probe` policy session against local ACE +(config shape: version 1, endpoint 127.0.0.1:9000, credential provider +Environment). `HeadlessDiagnosticWriter.Failure` now emits `errorDetail` +(full exception) — added during this diagnosis. + +**Next step:** bisect the headless hydration path (why zero entities admit +headless when the graphical host hydrates fine) BEFORE touching +`CanAdvancePlayer` — the #357 closeout warns that dormant-activation +classification changes have a strict test matrix. + ## #364 — Three `/help` group topics still partial: HelpStupidChannelHack unresolved **Status:** OPEN — filed 2026-08-09, Campaign CH user-gate round 2, item 3. diff --git a/src/AcDream.Headless/Diagnostics/HeadlessDiagnosticWriter.cs b/src/AcDream.Headless/Diagnostics/HeadlessDiagnosticWriter.cs index 8290a2d2..4efc41f3 100644 --- a/src/AcDream.Headless/Diagnostics/HeadlessDiagnosticWriter.cs +++ b/src/AcDream.Headless/Diagnostics/HeadlessDiagnosticWriter.cs @@ -58,6 +58,9 @@ internal sealed class HeadlessDiagnosticWriter sessionId, phase, errorType = error.GetType().FullName, + // Full detail: a quarantine that reports only the exception TYPE + // costs a whole diagnosis round-trip (jump-probe, 2026-08-10). + errorDetail = error.ToString(), }); } diff --git a/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs b/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs index b18ab208..b4daa174 100644 --- a/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs +++ b/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs @@ -381,6 +381,14 @@ public sealed class PlayerMovementController get => _localEntityId; set { + // A same-value re-assertion is not a mutation: + // RuntimeLocalPlayerFrameController.AdvanceBeforeNetwork + // re-asserts the resolved id every advance tick, which on the + // headless host reached a still-dormant/sealed controller and + // quarantined the whole session (jump-probe repro, 2026-08-10). + // A DIFFERENT id while sealed remains the error it always was. + if (value == _localEntityId) + return; EnsureConfigurationMutable(); _localEntityId = value; }