fix(input): focus loss no longer faults on an unpublished movement controller (#356)
Losing window focus calls CameraPointerInputController.HandleFocusChanged -> MouseLookController.EndForLifecycle -> PlayerMovementController .EndMouseLook, and EnsurePublishedForRuntimeOperation throws when the controller exists but is not yet published (mid-login) or already retired (post-logout). A focus callback can land in either window, so a simple alt-tab during the login stream took the whole process down with an unhandled InvalidOperationException. Hit live during the Campaign A listening-session launches. EndAndRestoreCursor already guarded 'no controller'; publication state is the finer-grained form of the same condition, so the guard is completed with the new CanExecuteLiveMovement predicate (the exact lifecycle set EnsurePublishedForRuntimeOperation accepts) rather than wrapping the call in a catch. Cursor restore still runs unconditionally - presentation is always safe. Published-controller behaviour is unchanged. Also files issue #357: the login placement stall this session exposed (reveal ready=True, player Place edge never executes, world never opens). That one is NOT fixed here - full evidence chain, wire capture, and probe output are in the issue. It is a placement-domain bug and blocks the Campaign A listening gate. Suite: green except the known load-dependent measurement flake (RuntimeCollisionReportingStateTests allocation pin), which passes in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2914e43aa9
commit
972c7ab3b8
3 changed files with 78 additions and 1 deletions
|
|
@ -24,6 +24,65 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #357 — Login stalls: reveal reaches ready=True but the player is never placed; UI + sky render, world never opens
|
||||
|
||||
**Status:** OPEN — filed 2026-08-08 during the Campaign A listening session,
|
||||
which it blocks. **This is a placement/streaming bug, not an audio bug** —
|
||||
read `docs/research/2026-08-05-c4-closeout-handoff.md` and
|
||||
`claude-memory/project_physics_collision_digest.md` before touching it.
|
||||
|
||||
**Symptom:** login proceeds normally (handshake, CharacterList, EnterWorld,
|
||||
~6,670 CreateObjects streamed, first player position received, streaming
|
||||
recentered to (9,4) @0x09040008, all reveal domains converge —
|
||||
`render=True composites=True collision=True ready=True`) — and then nothing.
|
||||
`materialized=False completed=False visible=False` forever. The user sees the
|
||||
retail UI and the sky/background; the world viewport never opens. Client is
|
||||
healthy: no stderr, frame loop ticking (~15% of one core), graceful close
|
||||
works.
|
||||
|
||||
**Evidence chain (all from 2026-08-08, exact binary `aa82ff7b` + rebuilds):**
|
||||
|
||||
1. **Nondeterministic on the SAME binary.** Two launches at ~08:51/08:53
|
||||
reached `auto-entered player mode` and `event=complete`; every launch from
|
||||
~08:56 onward stalls identically. No code change flips it: a control run
|
||||
without `ACDREAM_RETAIL_UI`, a run with the uncommitted focus-crash fix
|
||||
reverted (pure committed tree), and post-ACE-restart runs all stall.
|
||||
2. **Not the server.** Loopback capture (35 s, `login-capture.pcap` in the
|
||||
session scratchpad): ACE sends `PlayerCreate` (0xF746, from :9001) and the
|
||||
player guid `0x5000000A` appears in 48 payloads. A retail client logs into
|
||||
the same ACE fine (user-confirmed).
|
||||
3. **`ACDREAM_PROBE_PARK=1` shows a park storm:** 19 bodies (remotes
|
||||
0x8xxxxxxx + generated statics 0x709xxxxx in landblocks 0x0904/0x0905) all
|
||||
park with `cause=unplaceable`, `eligible=True captured=True`, and **zero
|
||||
restores** over 30+ s. `ACDREAM_PROBE_PLACEMENT_FAIL=1` emits nothing.
|
||||
4. **The player guid appears in ZERO park lines** — the local player's route-1
|
||||
Place edge never executes at all, so `PlayerMovementController` never
|
||||
publishes, `PlayerModeAutoEntry.IsPlayerControllerReady` never becomes
|
||||
true, auto-entry never fires, and the reveal never completes. In the
|
||||
working 08:51 run, `[step-h]` player resolves appear BEFORE the final
|
||||
readiness event and auto-entry lands immediately after it.
|
||||
|
||||
**Working hypothesis (unverified):** a pre-existing streaming/placement race
|
||||
in the login path — collision reveal-readiness and the placement pipeline's
|
||||
placeability read different convergence points, and a timing shift (machine
|
||||
warmth / page cache) made the losing interleaving consistent. The
|
||||
restore-pump silence (19 eligible parks, zero restores) suggests whatever
|
||||
re-drives parked placements after collision publication is not firing for
|
||||
this interleaving; the same mechanism failing globally would also explain the
|
||||
player's Place edge never arming. Smells adjacent to the
|
||||
`feedback_streaming_residence_race` class (#168/#169) and the C4 route-1
|
||||
machinery, but NOT confirmed — nobody has read the route-1 executor against
|
||||
this trace yet.
|
||||
|
||||
**Repro:** launch Release live against local ACE (standard env) on a warm
|
||||
machine; stall reproduces every time as of filing. Probes:
|
||||
`ACDREAM_PROBE_PARK=1 ACDREAM_PROBE_PLACEMENT_FAIL=1`.
|
||||
|
||||
**Next step:** read the C4 handoff's route-1 recipe, then instrument the
|
||||
route-1 accepted-position drive (what arms the login Place edge, and what
|
||||
re-drives parked operations when a collision generation publishes) against a
|
||||
stalled run. Do NOT band-aid with a retry loop — find the missed edge.
|
||||
|
||||
## #355 — Sound probability was never applied: every gated cue played on every trigger
|
||||
|
||||
**Status:** CLOSED 2026-08-08 (Campaign A slice A1) — user gate finding
|
||||
|
|
|
|||
|
|
@ -196,8 +196,14 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
|
|||
bool stateWasActive = _state.Active;
|
||||
_state.Release();
|
||||
|
||||
// The null check alone is not enough: a controller can also be present
|
||||
// but UNPUBLISHED (mid-login) or retired (post-logout), and a focus-loss
|
||||
// callback can land in either window. EndMouseLook faults on both, so a
|
||||
// simple alt-tab during login used to take the process down. Cursor
|
||||
// restore below still runs — that is presentation and always safe.
|
||||
PlayerMovementController? controller = _playerController.Controller;
|
||||
if (controller is not null && controller.EndMouseLook(_movementInput.Capture()))
|
||||
if (controller is { CanExecuteLiveMovement: true }
|
||||
&& controller.EndMouseLook(_movementInput.Capture()))
|
||||
{
|
||||
_outbound.TrySendMovement(
|
||||
_session.CurrentSession,
|
||||
|
|
|
|||
|
|
@ -764,6 +764,18 @@ public sealed class PlayerMovementController
|
|||
internal bool IsRuntimePublished => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.RuntimePublished;
|
||||
|
||||
/// <summary>
|
||||
/// True when this controller may execute a live movement operation — the
|
||||
/// same predicate <see cref="EnsurePublishedForRuntimeOperation"/> throws
|
||||
/// on. Exposed so LIFECYCLE callers (window focus loss, session teardown)
|
||||
/// can skip the operation instead of faulting: a focus change can arrive at
|
||||
/// any moment, including before the controller is published during login
|
||||
/// and after it is retired on logout, and neither is an error.
|
||||
/// </summary>
|
||||
public bool CanExecuteLiveMovement => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.StandalonePublished
|
||||
or PlayerMovementControllerPublicationLifecycle.RuntimePublished;
|
||||
|
||||
private bool _dormantSetPositionGroundPhase;
|
||||
|
||||
internal void BeginDormantSetPositionGroundPhase()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue