refactor(input): own pointer and callback lifetime

Move camera pointer, framebuffer resize, and retained/devtools input edges behind focused reversible owners. Preserve input priority while making shutdown deactivate callbacks before live-session retirement and retry physical detach without stranding transport teardown.
This commit is contained in:
Erik 2026-07-22 11:59:33 +02:00
parent d09e246d3a
commit 8b8afeefa3
42 changed files with 4029 additions and 461 deletions

View file

@ -27,11 +27,12 @@ What does NOT go here:
## Current queue — 2026-07-22
- **Active structural work:** `GameWindow` decomposition. Slices 17 are
complete; Slice 8 checkpoints AD now own native callbacks, live-session
composition, and world-environment state outside the shell.
complete; Slice 8 checkpoints AE now own native callbacks, live-session
composition, world-environment state, reversible device input/raw pointer,
and physical framebuffer publication outside the shell.
`GameWindow.OnUpdate` and `GameWindow.OnRender` are typed orchestration
handoffs and the class is 4,330 lines / 192 fields / 67 methods. Remaining
Slice 8 input/settings/composition/shutdown
handoffs and the class is 4,266 lines / 194 fields / 65 methods. Remaining
Slice 8 action-routing/settings/composition/shutdown
cleanup is active in
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
This is the behavior-preserving prerequisite before new M4 feature bodies.

View file

@ -827,9 +827,13 @@ Current movement/collision ownership:
session generation into `LiveSessionController` plus its focused host and
routers; Slice 4 moved hydration, teardown, inbound authority, network
updates, and reusable motion-runtime policy over canonical
`LiveEntityRuntime`. Slice 5 landblock presentation is next. The reconciled
ledger and sequence live in `docs/architecture/code-structure.md`. This decision
does not reclassify the remaining L.2 conformance work or Track LH.
`LiveEntityRuntime`. Slices 57 complete landblock presentation and the
update/render frame graphs. Slice 8 checkpoints AE now own the native
callback shell, live-session composition, world environment, reversible
input/pointer lifetime, and physical framebuffer publication. Gameplay action
routing is the next checkpoint. The reconciled ledger and sequence live in
`docs/architecture/code-structure.md`. This decision does not reclassify the
remaining L.2 conformance work or Track LH.
Full `GameEntity` type aggregation remains a separate, higher-risk target after
the thin-`GameWindow` campaign. The current campaign reuses canonical

View file

@ -690,6 +690,16 @@ slices. `OnClosing` delegates to the existing retryable shutdown transaction.
Silk callbacks become narrow calls into the input, update, render, resize,
focus, and shutdown owners.
Checkpoint E is complete. `CameraPointerInputController` owns raw pointer,
camera cursor/focus/scroll/sensitivity behavior; `FramebufferResizeController`
alone publishes physical framebuffer changes. All Silk, dispatcher, retained
UI, and optional devtools device edges are reversible, transactional, and
terminal after disposal. Logical cutoff precedes live-session retirement;
physical detach follows it, so neither copied callbacks nor a failed event
remove can interfere with graceful transport teardown. `GameWindow` is now
4,266 raw lines / 194 fields / 65 methods. Checkpoints FL remain active, with
gameplay action routing next.
### 4.4 Exit criteria
The campaign is complete when:

View file

@ -218,7 +218,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
| AP-119 | Equal-generation CreateObject refresh applies the packet's complete `PhysicsDesc` to the existing `EntityEffectProfile`, including replacing its network sound/PES-table/default-script description. Retail's equal-`INSTANCE_TS` branch applies the individual ObjDesc/Parent-or-Position/Movement/State/Vector/WeenieDesc tail and does not call `CPhysicsObj::set_description` again. | `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs` (`ILiveEntitySameGenerationUpdateSink.OnDescription`); `LiveEntitySameGenerationUpdateRouter.cs` | Existing spell/projectile/portal VFX tests and connected behavior were accepted with this refresh. Slice 4 records and isolates it rather than silently changing DAT-effect ownership during an architecture extraction. | A same-generation CreateObject whose PeTable/sound/default-script fields differ from the original can replace effect lookup state where retail would retain the original table, producing a different later typed effect. | `SmartBox::HandleCreateObject @ 0x00454C80`; `CPhysicsObj::set_description @ 0x00514F40`; `docs/research/2026-07-13-retail-projectile-vfx-pseudocode.md` |
| AP-120 | `ObjectTableWiring.ApplyEntitySpawn` publishes the CreateObject's WeenieDesc/item state before the same-generation physics update tail. Retail applies WeenieDesc after ObjDesc, Parent-or-Position/Pickup, Movement, State, and Vector. | `src/AcDream.Core.Net/ObjectTableWiring.cs` (`ApplyEntitySpawn`); `src/AcDream.App/World/LiveEntitySameGenerationUpdateRouter.cs` | Core.Net owns item-table ingestion before App callbacks and the current single-thread FIFO prevents a second network packet from interleaving; changing publication order crosses the Core.Net/App ownership boundary and requires a separately tested event transaction. | A synchronous item-table observer can see the refreshed WeenieDesc while the same object's physics/parent/state still reflects the prior snapshot; retail observers see the completed physics tail first. | `SmartBox::HandleCreateObject @ 0x00454C80`; `ACCObjectMaint::CreateObject @ 0x00558870` |
## 4. Temporary stopgap (TS) — 37 active rows + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains)
## 4. Temporary stopgap (TS) — 38 active rows + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains)
| # | Divergence | Where (file:line) | Why it is safe / justified | Risk if assumption breaks | Retail oracle |
|---|---|---|---|---|---|
@ -263,6 +263,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
| TS-53 | acdream advances retained UI time on the draw seam and local teleport/UI-camera presentation after its SmartBox-shaped object → inbound network → CommandInterpreter barrier. Retail `Client::UseTime` calls `UIElementManager::UseTime` first, whose global time message reaches `gmSmartBoxUI::UseTime`, and publishes player-camera work from the physics/player callback rather than one post-network camera tail. Slices 67 preserve the accepted host order as ownership-only extractions. | `src/AcDream.App/Update/UpdateFrameOrchestrator.cs` (post-live-frame teleport/camera phases); `src/AcDream.App/Rendering/PrivatePresentationRenderer.cs` (`RetainedGameplayUiFrame.Render`); `docs/plans/2026-07-21-gamewindow-slice-6-update-frame-orchestration.md`; `docs/plans/2026-07-22-gamewindow-slice-7-render-frame-orchestration.md` | Current retained UI, portal transit, reveal, camera, and connected movement traces are accepted; changing cross-subsystem host order while extracting ownership would combine a behavior change with the structural cutover. | Retained UI, teleport, and camera presentation can observe same-frame object/inbound/player state one host update earlier or later than retail at transition boundaries; a future exact host-order port must prove UI, input, reveal, and camera consequences together. | `Client::UseTime @ 0x00411C40`; `UIElementManager::UseTime`; `gmSmartBoxUI::UseTime @ 0x004D6E30`; `CPhysics::UseTime @ 0x00509950`; retire only with a focused host-order port and connected portal/camera comparison |
| TS-54 | AdminEnvirons sound values `0x65..0x7B` are diagnosed by retail enum name but do not play audio. Retail checks that the local player physics object and UI sound table exist, then calls `SoundManager::PlaySoundFromCenter(Sound_UI_*, table)` for Roar through Thunder6. | `src/AcDream.App/World/WorldEnvironmentController.cs` (`ApplyAdminEnvirons`) | The current audio owner has no typed retail UI-sound-table binding; logging preserves the inbound evidence without inventing wave DIDs or routing the sounds through positional world audio. | Server-authored ambience/thunder packets are silent in acdream while retail plays the centered UI sound. | `CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20` (`0x0055E07F..0x0055E2C7`); `SoundManager::PlaySoundFromCenter @ 0x00550950` |
| TS-55 | AdminEnvirons fog values remain a color-only `WeatherSystem.Override` approximation. Retail values 1..5 install authored ambient color/level plus fog color/max; value 6 also forces transition/min/max and blanks radar; Clear restores all override fields and radar; `0x270F` installs a separate authored override. | `src/AcDream.App/World/WorldEnvironmentController.cs` (`ApplyAdminEnvirons`); `src/AcDream.Core/World/WeatherState.cs` (`EnvironOverrideColor`) | Preserves the already accepted enum bridge while Slice 8 moves ownership; porting the complete environment/radar presentation is a separate behavior change requiring focused visual gates. | Forced-fog hue, density, scene ambient, and radar blanking differ from retail; `0x270F` is ignored. | `CPlayerSystem::Handle_Admin__Environs @ 0x0055DE20` (`0x0055DE2B..0x0055E344`) |
| TS-56 | Chase-camera mouse input retains acdream's invented post-filter yaw/pitch scalars (`0.004`/`0.003` radians per count), and held-key pitch/zoom retain their non-retail integration shapes. Retail mouse look passes `FilterMouseInput(delta) × configured sensitivity × 1/15` as the replacement scale to `CameraSet::Rotate`, which then applies the shared 8° angle; retail held pitch uses the same angle and zoom scales the viewer offset multiplicatively. | `src/AcDream.App/Input/CameraPointerInputController.cs`; `src/AcDream.App/Input/MouseLookController.cs`; `src/AcDream.App/Rendering/CameraFrameController.cs` | Slice 8 is behavior-preserving ownership work. The named-retail audit proves the mismatch but has not yet extracted the configured mouse-sensitivity default or the exact caller flags needed for a complete feel port; changing only one scalar here would create a mixed input model. | RMB/MMB orbit, held pitch, and zoom can feel slower, faster, or differently accelerated than retail even though callback ordering and filtering are correct. | `CameraSet::Rotate @ 0x00458310`; `CameraSet::MouseLookHandler` call at `0x00458EF9`; `CameraSet::Raise @ 0x00457B00`; `CameraSet::Closer @ 0x004586D0`; `docs/research/2026-06-11-holistic-map/wf2-camera-viewer.md` |
---

View file

@ -1,6 +1,6 @@
# acdream — strategic roadmap
**Status:** Living document. Updated 2026-07-22. **M3 landed; M4 is next after the active `GameWindow` structural prerequisite.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. The current program is the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md); Slices 17 and Slice 8 checkpoints AD are complete, with the remaining composition/shutdown checkpoints active. New M4 quest/emote/character-creation feature bodies wait until that campaign is complete. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate.
**Status:** Living document. Updated 2026-07-22. **M3 landed; M4 is next after the active `GameWindow` structural prerequisite.** M3's retail casting/UI, R6 locomotion/collision/projectile/teleport/radar rebaseline, deterministic fresh-login/portal world lifecycle, and final two-client portal observer flow are user-gated. The current program is the behavior-preserving ownership campaign in [`docs/architecture/code-structure.md`](../architecture/code-structure.md); Slices 17 and Slice 8 checkpoints AE are complete, with the remaining composition/shutdown checkpoints active. New M4 quest/emote/character-creation feature bodies wait until that campaign is complete. Issue #225's lifestone/particle alpha comparison remains a separate rendering visual gate.
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
---
@ -79,12 +79,17 @@ second synchronized soak pass with graceful exits, and the six stable PNG
checkpoints preserve Slice 6 presentation. Issue #232 tracks the coarse
process-residency gate's diagnostic blind spot; no tolerance was loosened.
Final Slice 8 composition/shutdown cleanup is active. Checkpoints AD now own
Final Slice 8 composition/shutdown cleanup is active. Checkpoints AE now own
the frozen shell boundary, all nine native callback edges, live-session
reset/selection/entry/route composition, and world clock/sky/day/weather/
AdminEnvirons state. `GameWindow` is 4,330 lines / 192 fields / 67 methods;
the Checkpoint D Release suite passes 7,419 tests / 5 skips. Input, settings,
startup/resource transfer, and shutdown checkpoints EL remain.
AdminEnvirons state. Checkpoint E adds reversible, transactional Silk,
dispatcher, retained-UI, and devtools input edges; a focused raw pointer owner;
and one physical framebuffer-size publisher. Logical callback cutoff and
live-session retirement precede physical removal, so failed device detach
cannot block graceful transport close. `GameWindow` is 4,266 lines / 194
fields / 65 methods; the Checkpoint E Release suite passes 7,477 tests / 5
skips. Gameplay routing, settings, startup/resource transfer, shutdown, soak,
and closeout checkpoints FL remain.
This is a behavior-preserving structural program. Severe regressions still get
root-cause fixes in separate commits; ordinary feature work resumes with M4

View file

@ -39,12 +39,15 @@ diagnostic owners preserve the accepted draw/failure graph. `GameWindow` is
campaign baseline. The full Release suite passes 7,341 tests / 5 skips; the
315.6-second capped/reconnect lifecycle gate and 395.2-second synchronized
nine-destination soak pass with graceful exits, and six stable PNG checkpoints
preserve Slice 6 presentation. Final Slice 8 checkpoints AD are complete:
preserve Slice 6 presentation. Final Slice 8 checkpoints AE are complete:
native callbacks and live-session composition now have explicit retryable
owners. Checkpoint D adds the sole world-environment owner for clock, DAT sky,
day group, weather, and AdminEnvirons state. `GameWindow` is 4,330 lines / 192
fields / 67 methods; 7,419 Release tests pass / 5 skip. Checkpoints EL remain
active. Issue #232 tracks process-residency variance in the soak without
day group, weather, and AdminEnvirons state. Checkpoint E adds terminal,
transactional ownership for Silk/dispatcher/retained/devtools input, the raw
camera pointer, focus, and physical framebuffer size. Graceful live-session
retirement now precedes physical callback removal. `GameWindow` is 4,266 lines
/ 194 fields / 65 methods; 7,477 Release tests pass / 5 skip. Checkpoints FL
remain active. Issue #232 tracks process-residency variance in the soak without
loosening its leak threshold.
Carried:

View file

@ -21,7 +21,7 @@ audit.
- [x] C — extract live-session host/reset/binding callbacks and verify the
embedded skill formula against named retail.
- [x] D — extract retail world-environment/day/weather behavior.
- [ ] E — extract two-phase raw pointer/camera input, focus, and framebuffer
- [x] E — extract two-phase raw pointer/camera input, focus, and framebuffer
resize behind symmetric named subscriptions.
- [ ] F — extract the sole gameplay input-action router plus focused combat and
diagnostic command owners.
@ -497,6 +497,27 @@ this behavior-preserving ownership checkpoint.
- Require rollback after every Nth event add and silence between every shutdown
stage for raw/retained/devtools device events.
Result: `CameraPointerInputController` now owns raw move, camera-mode cursor,
focus-loss, scroll, and per-mode sensitivity policy; its gameplay-frame edge is
late-bound without resubscription. Silk keyboard/mouse sources,
`InputDispatcher`, retained UI bindings, and the devtools
`QuiescentInputContext` are publish-before-attach, terminal after disposal,
transactional on every side-effecting event add, and retry only the physical
edges that remain pending. Mouse-only hosts retain camera/cursor behavior.
`FramebufferResizeController` is the sole physical framebuffer publisher and
seeds viewport/aspect/camera state from `FramebufferSize`, while logical UI and
frame dimensions remain `Window.Size`. Shutdown now performs no-throw logical
input cutoff, retires the live session, then removes physical callbacks; the
post-session mouse-look release therefore cannot send a final movement packet,
and a bad Silk remove accessor cannot block F653/transport teardown. TS-56
records the carried non-retail camera input scalars. Three corrected-diff review
loops are clean; 66 focused App tests, 9 dispatcher-lifetime tests, 5 Core input
integration tests, the warning-free production Release build, and the complete
Release suite (7,477 pass / 5 intentional skips) pass. `GameWindow` is 4,266
raw lines / 194 fields / 65 methods. No connected gate was required for this
behavior-preserving ownership checkpoint; Checkpoint L retains the final
connected lifecycle and visual gates.
### F — gameplay action and command routing
- Move the frozen action-priority graph into the sole Fired subscriber.