Commit graph

3890 commits

Author SHA1 Message Date
Erik
0ad2ee1cdf test: replace streaming source freezes 2026-08-18 15:14:41 +02:00
Erik
5a33369074 test: replace render source freezes 2026-08-18 14:58:25 +02:00
Erik
c31a9ac411 test: remove dormant panel self-tests 2026-08-18 14:37:42 +02:00
Erik
c5f0fbaaa4 test: audit helper-mediated source reads 2026-08-18 14:18:00 +02:00
Erik
dc94b0fe32 docs: make remaining test decisions exact 2026-08-18 13:55:13 +02:00
Erik
53b6841c5a test: remove final campaign labels 2026-08-18 13:50:31 +02:00
Erik
088add2fac test: own Avalonia application session 2026-08-18 13:41:23 +02:00
Erik
631ecd24e3 test: finish diagnostic classification 2026-08-18 13:41:17 +02:00
Erik
a8cd3e2da2 docs: classify issue test taxonomy 2026-08-18 13:19:30 +02:00
Erik
ce2800a00c docs: reconcile ambiguous test contracts 2026-08-18 13:18:11 +02:00
Erik
79a4489e03 test: replace final fixed-delay oracles 2026-08-18 13:12:26 +02:00
Erik
ad7ebe9425 test: observe landblock worker joins 2026-08-18 13:03:30 +02:00
Erik
fa4bdfe89f test: observe monitor waits without delays 2026-08-18 12:56:25 +02:00
Erik
ea17bc8624 test: remove vacuous diagnostic assertions 2026-08-18 12:48:07 +02:00
Erik
5fa9933636 test: remove exact duplicate coverage 2026-08-18 12:36:41 +02:00
Erik
9c6b143a03 test: replace campaign labels with behavior names 2026-08-18 12:25:00 +02:00
Erik
e6fab96f12 docs: map source-text test debt 2026-08-18 12:17:58 +02:00
Erik
056af276d0 test: classify remaining explicit waits 2026-08-18 12:16:53 +02:00
Erik
6faeb4a103 test: make prerequisite lanes fail honestly 2026-08-18 12:09:41 +02:00
Erik
dfc841b779 test: stabilize load-sensitive release contracts 2026-08-18 11:50:23 +02:00
Erik
c8c764a40e test: remove ambient timing from double-click contracts 2026-08-18 11:38:31 +02:00
Erik
3684e7b5e7 test: classify prerequisite lanes and own Avalonia sessions 2026-08-18 11:30:47 +02:00
Erik
c1a905004a test: separate diagnostic apparatus from release gates 2026-08-18 11:06:08 +02:00
Erik
8f490240d4 test: separate non-hermetic release lanes 2026-08-18 10:49:22 +02:00
Erik
52015f5052 test: remove known tautologies and scaffolds 2026-08-18 10:40:20 +02:00
Erik
8e884679e0 docs: close R2 reproducibility checkpoint 2026-08-18 10:32:37 +02:00
Erik
c38f6b8852 build: make release restore reproducible 2026-08-18 10:29:00 +02:00
Erik
b459e0cf0c docs: record R2 gate checkpoint 2026-08-18 09:10:11 +02:00
Erik
2ac054864d ci: add bounded complete release gate 2026-08-18 09:09:38 +02:00
Erik
0a934cf578 fix: prevent launcher exit disposal deadlock 2026-08-18 08:43:29 +02:00
Erik
15539a22a6 Merge campaign-hover-ui-round: #419 post-mortem record (tunnel fix attempts discarded, apparatus-first protocol)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 23:15:48 +02:00
Erik
dd83fece21 docs #419: record the two discarded fix attempts, durable byte-decoded facts, and the apparatus-first protocol
Both attempts removed from the branch by user direction after live
gates (attempt 1 culled the interior — the 'fixed' exit was a
nothing-drawn false positive; attempt 2's luminosity port produced no
visible change, theory-vs-plumbing unresolved). Patches preserved in
the session scratchpad. Next attempt starts with apparatus: tunnel
freeze probe, RenderDoc capture, ACViewer oracle, retail side-by-side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 23:12:59 +02:00
Erik
1fbdf23597 Merge branch 'campaign-hover-ui-round' 2026-08-17 21:16:26 +02:00
Erik
59f68aecdf docs: file #419 — tunnel rim polygon + exit ring flash, decomp-anchored (fixed CreatureMode camera 0.24/-2.7/0.88, view-plane exit)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 21:16:26 +02:00
Erik
d504a12db5 Merge campaign-hover-ui-round: #418 hold-scoped streaming burst — portal/recall holds at retail-feel
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
While a destination reservation hides the world behind the tunnel, the
frame meter runs on a hold-widened profile (2 ms -> 8 ms destination
lane, non-destination lane absolute caps unchanged), reverting the frame
the reservation ends. Measured: warm-process portal hold gate-ready
3.6 s / total 8.7 s (retail-feel band); login drip 6-7 s -> 3.0 s with
the remaining login floor now attributed to process cold-start warmup
(~8 s concurrent phase), the next #418 lever. Tunnel held 64-66 fps
through the burst; mid-game no-hold path bit-identical (test-pinned).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:57:20 +02:00
Erik
a1d15a82dd perf #418: widen the destination-lane budget only while the reveal hold is active
While a destination reservation hides the world behind the authored
tunnel, the streaming frame meter now runs a hold-widened profile
(StreamingWorkBudget.WidenForDestinationHold): the time ceiling rises
from the authored 2 ms to an absolute 8 ms default
(ACDREAM_STREAM_WORK_HOLD_DEST_MS is a measurement-only override), every
count/byte dimension scales by the same factor so elapsed time stays the
authoritative guard (the measured binder is Time at both ceilings), and
the reserve fraction is re-derived (0.75 -> 0.9375) so the
NON-destination lane's absolute per-frame caps are unchanged. The
widening keys off the existing BeginDestinationReservation/
EndDestinationReservation bracket only, is derived per-Tick from the
CURRENT budget (mid-hold quality swaps compose), and a frame with no
reservation uses the authored budget verbatim (test-pinned). Portal
holds ride the same bracket as login holds by construction - intended,
and pinned by a kind-parity test through the real coordinator plus a
live @telepoi portal hold (kind=portal gate-ready 3589 ms).

Why: issue #418's next-hypothesis (1). Measured result: the ~5 s
publication drip collapsed to ~2 s (loaded 625/625 at ~3.0 s, tunnel at
64-66 fps), the portal-hold gate-ready fell to ~3.6 s - and login
gate-ready/total stayed at 8.4-8.8 s / 12.6-12.7 s, exposing the real
remaining pacer: the login-cold render-thread upload/registration
barrier behind GpuWorldState.IsRenderReady, which ran concurrently under
the old drip. Full attribution appended to docs/ISSUES.md #418; no
divergence-register row (the streamed result and reveal gate are
byte-identical; only the scheduling rate during a hidden hold changed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:55:43 +02:00
Erik
62ccf9b9b9 Merge branch 'campaign-hover-ui-round' 2026-08-17 20:16:10 +02:00
Erik
e94e8e0c61 docs #418: goal closeout — retail-feel done bar + ordered next hypotheses
The user's benchmark (retail ~6 s at a smaller window without our
complete-at-reveal guarantee) is now the issue's done bar. Next levers
in order: hold-scoped destination-lane budget widening (predicted
gate-ready ~2-3 s), then upload/composite overlap; retail's authored
tunnel exit is not a tuning target. Miss-branch closeout per the
session goal: acceptance measured 12689/12734 ms (> 12000), attribution
complete, no blind tuning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:16:10 +02:00
Erik
48de9513ff Merge campaign-hover-ui-round: #418 login speedup — 27.4 s to 12.7 s measured
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
The flat 32 blocks/s was the metered publication arm treating Runtime's
deliberate two-poll collision activation as end-of-frame: one landblock
per 32 Hz streaming tick with the 2 ms budget ~90% idle. The metered arm
now uses the same Runtime continuation gate the synchronous path always
used; the budget is genuinely authoritative, debt semantics preserved,
streamed result byte-identical. Remaining floor fully attributed in #418
(~8 s budgeted readiness + retail's authored tunnel exit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:13:45 +02:00
Erik
11106c70e7 perf #418: publish landblocks under the meter, not one per streaming tick
Phase-1 measurement (new [publish-timing]/[stream-tick] probe surfaces,
ACDREAM_PROBE_REVEAL_TIMING=1) refuted the ~31 ms-per-admission
hypothesis: the hold runs at ~64 fps with the streaming tick at ~32 Hz,
the whole 625-block window costs only ~500 ms of publication CPU (far
blocks ~0.17 ms, near 2-43 ms), and steady state showed ZERO meter
yields with ~0.22 ms of the 2 ms budget used - yet exactly one block
published per tick against a ~400-deep completion queue.

The real limiter: Runtime's collision-generation activation is a
deliberate two-poll transaction (the first
TryAcquireCollisionPrefixMutationPermission poll parks residents and
refuses by design), and LandblockPresentationPipeline.Advance's metered
arm returned Completed=false on ANY nonterminal commit, which
DrainAndApply treats as end-of-frame. One landblock per 32 Hz tick =
the flat 32/s, with the authored budget ~90% idle.

Fix: the metered arm now uses the same Runtime-owned gate the unmetered
arm and the synchronous CompletePublication API always used
(CanContinueMutationSynchronously). The second poll runs in the same
frame under the same meter, so the unchanged 2 ms elapsed-time ceiling
is now genuinely the authoritative per-frame bound; with any real debt
(live residents parked mid-game, pending withdrawals, dispatch backlog)
publication defers to the next frame exactly as before. No budget
values change, no reveal-gate/readiness change, and the streamed result
is byte-identical - only the frame scheduling of identical operations.

Measured A/B (this binary, two runs): totalMs 12689 / 12734 vs baseline
26728/27395/27503; loaded slope 32/s -> bursts of 100-360/s, 625/625 in
~6-7 s vs ~23 s. The remaining ~12.7 s floor is fully attributed in
docs/ISSUES.md: ~8 s of real budgeted readiness work plus retail's
authored tunnel exit (TunnelContinue 2-5 s + two 1 s fades, golden
constants), so the <12 s acceptance needs a lead decision on the
hold-time budget, not another hidden limiter.

New regression pin:
MeteredLoaded_NonterminalCommitWithoutDebt_CompletesInOneMeteredAdvance.
Gates: Release build 0 errors; App tests 5576/3 skips/0 failed;
Runtime tests 1756/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:11:35 +02:00
Erik
27b6c8bc19 Merge campaign-hover-ui-round: #417 logout audio fix, reveal-timing probe, #418 landblock build pool (login speedup in progress)
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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 19:36:56 +02:00
Erik
45f379560a docs #418: lead-review refinement — the ~31 ms period is one landblock's update-thread publication cost
The identical flat 32/s across the serial and pooled binaries points at a
single ~31 ms indivisible per-landblock admission on the update thread
(one admission per frame under the 2 ms floor, frame stretched to ~31 ms,
~32 fps x 1/frame = 32/s); the old serial builder producing at the same
~31 ms/block masked it. Predicts ~31 ms tunnel frame time; fix shape is
splitting/off-threading the publication cost, not budgets (disproved
twice). Verify the frame-time prediction first next session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 19:36:56 +02:00
Erik
39967e78bd perf #418: parallelize landblock builds across a striped worker pool
Login publishes the 25x25 window at a flat 32 blocks/s (~27 s in the
tunnel). The reveal-timing probe A/B (695a27b4) showed the consumer
budget env ceilings change nothing, which was read as producer-limited:
one "acdream.streaming.worker" thread, ~31 ms/block. This replaces the
single worker with min(ProcessorCount-2, 8) workers, floor 1.

Design: striped/affinity dispatch. Each worker owns one unbounded lane
channel plus its own high/low priority queues; jobs route to
lane = ((id >> 16) * 2654435761) % N (the low word of a landblock id is
constant, so the id is mixed before reduction). Striping was chosen
over a shared queue + in-flight conflict tracker because it preserves
the per-landblock contract structurally rather than by bookkeeping:
every job for one id lives on one lane, so per-id enqueue order IS
execution and completion-arrival order, and the same-landblock
supersede rules (PromoteToNear removes queued LoadFar/Unload) keep
seeing every queued job for that id. Contract, point by point:

- Per-landblock ordering: same id -> same lane -> serial FIFO.
- ClearLoads: broadcast to every lane inside the same _inboxGate lock
  that serializes enqueues, so any load enqueued before
  ClearPendingLoads() returns sits ahead of its lane's ClearLoads copy
  in that lane's FIFO and is dropped at read time, exactly like the
  single-thread path. Already-dequeued builds still complete (now up
  to one per worker instead of one total); StreamingController's
  SweepCollapsed already unloads those uniformly.
- Priority: per-lane high/low split unchanged. Cross-lane, priority is
  not globally ordered (a lane cannot run another lane's job), which
  the contract permits; near-tier jobs hash-spread across lanes and
  are preferred within each.
- Outbox: SingleWriter flipped to false; nothing assumed single-writer
  (PublishResult already used TryWrite + an Interlocked backlog, and
  the consumer's peek->read head-stability holds because only the
  single reader ever moves the head). Cross-landblock arrival order
  was verified arbitrary-tolerant before relying on it:
  StreamingController.AdmitCompletions classifies each result
  independently into per-priority FIFOs (generation staleness +
  per-landblock retirement blocking); per-landblock arrival order is
  preserved by striping.
- Crash surface: per-worker. The first real crash publishes
  WorkerCrashed (prefixed "worker N:" in pools > 1), sets
  _workerFailure, completes every lane, and cancels the pool (a crash
  still ends all processing, as before); siblings that merely observe
  the closed lanes (ChannelClosedException) exit quietly instead of
  reporting spurious crashes; the outbox completes only when the LAST
  worker exits so no in-flight completions are dropped.
- Disposal: joins every worker under the same _disposeGate; Start
  stays idempotent and dispose-serialized.

Thread-safety audit of the production build closures
(SessionPlayerComposition), per shared object:

- DatCollection (every read in LandblockBuildFactory.BuildLocked:
  LandblockLoader.Load, SceneryGenerator.Generate, SetupMesh.Flatten,
  CellMesh.Build, GfxObjBounds.Get, GfxObjDegradeResolver): NOT
  thread-safe; already serialized under the shared _datLock, which
  BuildLocked holds for the whole read transaction. Unchanged; the
  probe run measured hold 0-13 ms / wait <= 12 ms during the login
  window, so the lock is not the new bottleneck and the build was NOT
  serialized beyond it.
- PakPreparedAssetSource / PakReader (BuildPreparedCollisionClosure,
  outside the lock): immutable TOC array + read-only
  MemoryMappedViewAccessor random-access reads + ConcurrentDictionary
  verdict caches - safe for N concurrent readers (Slice I3 design;
  the headless SharedPreparedCollisionCache wrapper is fully
  lock-protected).
- LandblockMesh.Build (outside the lock): pure math over the dat
  record + the composition-time height table + the immutable
  TerrainBlendingContext record; the shared SurfaceCache is a
  ConcurrentDictionary and BuildSurface is deterministic, so its
  lookup-or-build race is last-write-wins-benign (the code already
  documented exactly this).
- PhysicsDiagnostics probe statics: read-only bools + thread-safe
  Console writes.

MEASURED OUTCOME (gate 4): the timing acceptance did NOT pass, and per
the task contract that is reported, not tuned around. With 8 workers
on this 16-core machine all 625 builds complete in ~203 ms
(ACDREAM_PROBE_TELEPORT BUILD lines t=3475390..3475593) - the producer
is off the critical path - but loaded= still advances at exactly
+32/1000 ms and SUMMARY totalMs measured 27395 and 27503 across two
runs (baseline 26728). The 32/s pacer is in the consumer
admission/publication path and is not governed by the
StreamingWorkBudgetOptions env ceilings. #418 stays IN-PROGRESS on the
consumer side; see docs/ISSUES.md for the evidence chain.

Tests: per-landblock ordering under 4-worker contention, cross-lane
ClearLoads drop, per-lane near-before-far preference, pool-of-1 serial
equivalence, disposal joining every worker, lane-spread guard, and
worker-count validation (LandblockStreamerPoolTests). Two existing
tests asserted a GLOBAL cross-landblock execution order - a serial
implementation detail, not the contract - and now pin workerCount: 1
with justification comments (LoadNear_OvertakesQueuedFarLoads,
TwoQueuedLoads_RetainTheirDistinctOriginAndGeneration).

Gates: Release build 0 errors; App suite 5575 passed / 3 skipped
(5568 + 7 new); Runtime suite 1756/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 19:34:20 +02:00
Erik
695a27b48a probe: ACDREAM_PROBE_REVEAL_TIMING — wall-clock attribution of the login/portal hold
[reveal-timing] lines from the reveal coordinator: per-dimension
first-ready edges (render neighborhood, composite textures, collision,
gate, materialization), 1 Hz progress with the resident-landblock count
(new GpuWorldState.LoadedLandblockCount), and one SUMMARY line at the
viewport reveal. Measurement-first groundwork for the login-load speedup:
the readiness barrier observes its dimensions serially, so the edges give
each dimension's observed tail while the progress lines expose the
pacing shape (a budget-paced linear drip reads directly off the counts).
Probe-gated in StreamingDiagnostics per Code Structure Rules §5; no
behavior change, one branch per Evaluate poll when unset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 19:04:15 +02:00
Erik
0228876a8d Merge campaign-hover-ui-round: fix #417 — silence world audio at the logout reset
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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 18:46:04 +02:00
Erik
0bb47f2711 fix #417: world ambience kept playing at character select after the in-world logoff
The character-session reset manifest had no audio step: retail's logoff
destroys the world's sound sources with the world, but our OpenAL world
pool and ambient scheduler are process-lifetime — the continuous ambient
beds played on at character select and the scheduler kept RE-FIRING
deadlines against the stale listener (Suspend/StopAll had zero callers;
WorldGenerationQuiescence only cycles around teleport-style generation
replaces).

New WorldAudioSessionGate: the reset manifest's 'world audio' step stops
all sixteen world-pool voices (SuspendWorldAudio) and drops every ambient
deadline (StopAll); the pool reopens at the entered-world edge through the
new default-null LiveSessionEnteredWorldBindings.ResumeWorldAudio binding,
invoked first in ApplyEnteredWorld. The ambient soundscape needs no
explicit resume — the next objcell observation rebuilds it exactly as a
cell change always did. Covers logout, reconnect, and full stop uniformly.
UI-pool sounds (interface bank, portal cues) untouched by design.

App tests 5568/3 skips, Runtime 1756/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 18:46:04 +02:00
Erik
0d5978ca2c Merge campaign-hover-ui-round: fix #416 roster hover highlight + #415 probe wait verbs
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
The retail button state/media machine ported from the decomp
(UpdateState_ authored-state gate, SetState state-0 arm, the
non-empty-media reset rule) replaces the media-keyed approximation that
latched the character-select roster highlight. Live-verified. The probe
wait verbs now bind a facts-only automation runtime in every launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:25:43 +02:00
Erik
91c1962b0d fix #416 #415: the retail button state/media machine — roster hover highlight clears; probe wait verbs bind without an artifact dir
#416 (char-select roster highlight never cleared on hover-leave): three
decomp-grounded mechanisms replace the media-keyed _availableStates
approximation.
- UIElement_Button::UpdateState_ @0x00471CF0: the button machine commits
  ONLY states authored on the button's OWN ElementDesc (AccessStateDesc
  gate); unauthored requests no-op, preserving custom semantic states.
- UIElement::SetState @0x00464E70: an unauthored state id is coerced to
  state 0 (the unnamed base state) and committed — ported into
  UiDatElement.TrySetRetailState with the base-descriptor PassToChildren
  cascade arm.
- The SetState media rule @0x004651c0: a committed state replaces the
  playing media ONLY when its media array is non-empty. UiButton now keeps
  per-face-segment media states under that rule (segments model retail's
  PassToChildren children), and LayoutImporter records the raw MediaCount
  including the File=0 draw-nothing images the drawable filter drops —
  the roster bar children's base state is exactly such an image, and it is
  what clears the bar.
The row template truth (probe, installed DAT): the row authors EMPTY
Normal/rollover/Highlight descriptors with PassToChildren; the three bar
children author rollover/Highlight media, NO Normal state, and a File=0
base image. An empty-media Normal_pressed still never blanks a Normal-art
button (the media rule keeps the previous art — the exact behavior the
old gate approximated), and the Appearance spins' property-only Highlight
now genuinely commits: label recolors, arrow art lingers — the retail
split AP-222 approximated with a requested-keyed label hack, now retired.
Live-verified at char select: hover +alex shows the grey bar, moving off
clears it, the selected row keeps its amber bar.

#415 (probe wait world-* verbs dead): the filed snapshot-reset diagnosis
was wrong — the automation bridge simply never bound without
ACDREAM_AUTOMATION_ARTIFACT_DIR. A facts-only
WorldRevealFactsAutomationRuntime now binds whenever the retained UI
exists; checkpoint/screenshot verbs still require the artifact directory
and now report that instead of a generic timeout.

App tests 5568/3 skips, Runtime 1756/0, UI.Abstractions 926/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:25:36 +02:00
Erik
75c19becca 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
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>
2026-08-17 15:17:56 +02:00
Erik
7aa08045d8 fix #414: cursor disappears at character select after the in-world logoff
Session teardown (PlayerModeController.Exit/ResetSession ->
CameraController.ExitChaseMode) fell back to the dev free-fly camera, and
CameraPointerInputController.ApplyCursorForCameraMode faithfully applies
CursorMode.Raw (GLFW disabled cursor: hidden + captured) for fly mode —
so the character-select screen after an in-world logoff had no mouse.
Fresh boot starts in Orbit and never fires a mode change, which is why
only the post-logout path was affected.

Teardown now 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).

Proven live both directions with a driven logout (UI probe 0x100000FA ->
dialog accept 0x17) under Win32 GetCursorInfo sampling: before, flags
flipped 1->0 exactly at the roster re-push that re-shows character select
and stayed hidden; after, zero hidden samples across the full timeline.
Files #415: the UI-probe 'wait world-visible' verb reads the reset
transit snapshot and is dead after reveal completion (test apparatus
only).

App tests 5564/3 skips (+3), Runtime 1756/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:17:50 +02:00