Merge campaign-hover-ui-round: fix #414 — cursor hidden at character select after the in-world logoff
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / portable-launcher (ubuntu-latest) (push) Waiting to run
Headless portability / portable-launcher (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / portable-launcher (ubuntu-latest) (push) Waiting to run
Headless portability / portable-launcher (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Teardown's dev fly-camera fallback raw-captured the OS cursor on the character-select screen; teardown now lands on the orbit camera (the fresh-boot state) and the pointer controller restores a normal cursor. Live-verified both directions under GetCursorInfo sampling. Files #415 (broken 'wait world-visible' automation verb, apparatus only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
75c19becca
3 changed files with 102 additions and 5 deletions
|
|
@ -24,6 +24,48 @@ What does NOT go here:
|
||||||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
- 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.
|
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||||
|
|
||||||
|
## #415 — UI-probe `wait world-visible` verb is dead after reveal completion (automation bridge reads the reset snapshot)
|
||||||
|
|
||||||
|
**Status:** OPEN (filed 2026-08-17 at the #414 cursor repro). The script
|
||||||
|
runner's `wait world-visible` polls
|
||||||
|
`WorldLifecycleAutomationController.IsWorldViewportVisible`, which reads
|
||||||
|
`_getReveal().WorldViewportObserved` from the LIVE transit snapshot. The
|
||||||
|
reveal's `event=complete` retires that generation and the snapshot resets
|
||||||
|
`WorldViewportObserved: false` (`RuntimeWorldTransitState.cs:583`), so the
|
||||||
|
verb only observes true during the sub-second window between
|
||||||
|
`event=world-visible` and `event=complete` — in practice it times out even
|
||||||
|
though the world revealed (proved by the #414 repro logs: `wait
|
||||||
|
world-visible 60000` timed out with `event=world-visible` present in the
|
||||||
|
same run's `[world-reveal]` stream). Fix shape: the automation controller
|
||||||
|
should latch world-visible per generation (or the verb should accept
|
||||||
|
`IsWorldReady`-style completed state), not read the transient snapshot.
|
||||||
|
Test apparatus only — no player impact.
|
||||||
|
|
||||||
|
## #414 — Mouse cursor disappears at character select after the in-world logoff (teardown fly-mode fallback raw-captures the cursor)
|
||||||
|
|
||||||
|
**Status:** ✅ FIXED 2026-08-17 (entry/exit presentation follow-up; fix +
|
||||||
|
regression tests in the same commit as this entry). **Symptom:** press the
|
||||||
|
indicator bar's X, confirm Yes, land on character select — the OS cursor is
|
||||||
|
gone (and captured). Second Enter still works; the cursor returns once a
|
||||||
|
world login re-enters chase mode. **Evidence:** live Win32 `GetCursorInfo`
|
||||||
|
sampling over the driven logout (scripted UI probe: `0x100000FA` → dialog
|
||||||
|
accept `0x17`) — cursor flags flipped `1 → 0` (hCursor `0x0`) exactly at
|
||||||
|
the roster re-push that re-shows character select, and stayed hidden.
|
||||||
|
**Root cause:** `CameraController.ExitChaseMode` (called from
|
||||||
|
`PlayerModeController.Exit`/`ResetSession` at every session teardown) fell
|
||||||
|
back to `Mode.Fly` — the pre-retail dev free-camera convention — and
|
||||||
|
`CameraPointerInputController.ApplyCursorForCameraMode` faithfully applies
|
||||||
|
`CursorMode.Raw` (GLFW disabled cursor: hidden + captured) for fly mode.
|
||||||
|
Fresh boot never fires a mode change at character select (starts Orbit), so
|
||||||
|
only the post-logout path was affected. **Fix:** teardown lands on
|
||||||
|
`Mode.Orbit` — the exact state a fresh boot presents at character select —
|
||||||
|
and always notifies, so the pointer controller restores `CursorMode.Normal`
|
||||||
|
even when torn down from the dev fly camera. The dev fly↔chase flow is
|
||||||
|
untouched (it rides `ToggleFly`, never `ExitChaseMode`).
|
||||||
|
`CameraControllerTests`: chase→orbit + notify, fly→orbit + notify,
|
||||||
|
orbit no-op no-notify; the old fly-fallback assertion updated to the new
|
||||||
|
contract.
|
||||||
|
|
||||||
## #413 — House tab shows no content (owned-house display, Display* line builders unported)
|
## #413 — House tab shows no content (owned-house display, Display* line builders unported)
|
||||||
|
|
||||||
**Status:** NARROWED 2026-08-17 (House-tab ownership-text closer session);
|
**Status:** NARROWED 2026-08-17 (House-tab ownership-text closer session);
|
||||||
|
|
|
||||||
|
|
@ -87,16 +87,24 @@ public sealed class CameraController
|
||||||
ModeChanged?.Invoke(IsChaseMode);
|
ModeChanged?.Invoke(IsChaseMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Player-mode teardown (logout, session reset). Lands on the neutral
|
||||||
|
/// orbit camera — the same state a fresh boot presents at character
|
||||||
|
/// select — NOT the dev free-fly camera: fly mode raw-captures the OS
|
||||||
|
/// cursor (<c>CameraPointerInputController.ApplyCursorForCameraMode</c>
|
||||||
|
/// sets <c>CursorMode.Raw</c>), and the old <c>Mode.Fly</c> fallback
|
||||||
|
/// left the character-select screen after an in-world logoff with a
|
||||||
|
/// hidden, captured mouse. The dev fly↔chase flow is unaffected — it
|
||||||
|
/// rides <see cref="ToggleFly"/>, never this teardown path.
|
||||||
|
/// </summary>
|
||||||
public void ExitChaseMode()
|
public void ExitChaseMode()
|
||||||
{
|
{
|
||||||
bool wasChaseMode = IsChaseMode;
|
|
||||||
Chase = null;
|
Chase = null;
|
||||||
RetailChase = null;
|
RetailChase = null;
|
||||||
if (_mode == Mode.Orbit)
|
if (_mode == Mode.Orbit)
|
||||||
return;
|
return;
|
||||||
_mode = Mode.Fly;
|
_mode = Mode.Orbit;
|
||||||
if (wasChaseMode)
|
ModeChanged?.Invoke(false);
|
||||||
ModeChanged?.Invoke(IsFlyMode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAspect(float aspect)
|
public void SetAspect(float aspect)
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,58 @@ public class CameraControllerTests
|
||||||
|
|
||||||
ctl.ExitChaseMode();
|
ctl.ExitChaseMode();
|
||||||
|
|
||||||
Assert.True(ctl.IsFlyMode);
|
// Teardown lands on orbit even from the dev fly camera: fly mode
|
||||||
|
// raw-captures the OS cursor, and a session teardown must leave the
|
||||||
|
// character-select screen with a normal visible pointer.
|
||||||
|
Assert.False(ctl.IsFlyMode);
|
||||||
|
Assert.False(ctl.IsChaseMode);
|
||||||
Assert.Null(ctl.Chase);
|
Assert.Null(ctl.Chase);
|
||||||
Assert.Null(ctl.RetailChase);
|
Assert.Null(ctl.RetailChase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExitChaseMode_FromChaseLandsOnOrbitAndNotifies()
|
||||||
|
{
|
||||||
|
var (ctl, _, _) = MakeChaseFixture();
|
||||||
|
int notifications = 0;
|
||||||
|
bool? lastArg = null;
|
||||||
|
ctl.ModeChanged += arg => { notifications++; lastArg = arg; };
|
||||||
|
|
||||||
|
ctl.ExitChaseMode();
|
||||||
|
|
||||||
|
Assert.False(ctl.IsChaseMode);
|
||||||
|
Assert.False(ctl.IsFlyMode);
|
||||||
|
Assert.Equal(1, notifications);
|
||||||
|
Assert.False(lastArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExitChaseMode_FromFlyNotifiesSoCursorCaptureReleases()
|
||||||
|
{
|
||||||
|
var ctl = new CameraController(new OrbitCamera(), new FlyCamera());
|
||||||
|
ctl.ToggleFly();
|
||||||
|
Assert.True(ctl.IsFlyMode);
|
||||||
|
int notifications = 0;
|
||||||
|
ctl.ModeChanged += _ => notifications++;
|
||||||
|
|
||||||
|
ctl.ExitChaseMode();
|
||||||
|
|
||||||
|
Assert.False(ctl.IsFlyMode);
|
||||||
|
Assert.Equal(1, notifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExitChaseMode_FromOrbitDoesNotNotify()
|
||||||
|
{
|
||||||
|
var ctl = new CameraController(new OrbitCamera(), new FlyCamera());
|
||||||
|
int notifications = 0;
|
||||||
|
ctl.ModeChanged += _ => notifications++;
|
||||||
|
|
||||||
|
ctl.ExitChaseMode();
|
||||||
|
|
||||||
|
Assert.Equal(0, notifications);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RestoreState_ReestablishesPriorCameraAfterNotificationFailure()
|
public void RestoreState_ReestablishesPriorCameraAfterNotificationFailure()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue