acdream/docs/plans/2026-07-21-gamewindow-slice-1-selection-interaction.md
Erik 9ad8113c79 docs(architecture): close selection extraction slice
Record the measured GameWindow reduction, final review and test gates, durable selection/inventory ownership rules, and live animation presentation as Slice 2.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-21 09:04:31 +02:00

15 KiB

GameWindow Slice 1 — selection and interaction ownership

Status: COMPLETE — landed 2026-07-21. Parent program: docs/architecture/code-structure.md, Slice 1. Baseline: cf50ee3d; GameWindow.cs is 15,723 lines before this slice. Behavior rule: Existing retail behavior moves unchanged. Any defect found by the extraction audit is fixed in a separately identified commit before the mechanical cutover.

Landing record

  • Plan/oracle: c2713837.
  • Exact-incarnation lifetime hardening: 047a4c83.
  • Retail pending keyboard-pickup presentation: 52dbb574.
  • Read-only query extraction: e74f2ca9.
  • Stateful interaction owner: fa8d5232.
  • GameWindow cutover: d2bb5af4.
  • Three-review correction pass: 5acc3f01.

The correction pass unified retail's one-inventory-request-at-a-time owner across every mutation surface, separated optimistic/rollback/authoritative placement notices, made response completion atomic and reentrancy-safe, and bound pending work to exact object identities and placement tokens.

Final metrics: GameWindow.cs 15,723 → 14,912 lines and 205 → 191 methods; fields remained 278. Release build passed, the focused selection/inventory gate passed, the full suite passed 6,558 tests with five intentional skips, and the retail-conformance, architecture/integration, and adversarial reviews all finished with no actionable findings.

1. Outcome

GameWindow stops owning world selection and selection-driven interaction. After this slice it retains only composition and narrow lifecycle calls:

  • construct the query/controller;
  • feed the completed render-selection frame;
  • drain outbound interactions at the existing retail frame boundary;
  • forward input actions and MoveTo completion;
  • forward entity hidden/removal and session-reset edges.

Two App owners replace the current body:

  1. WorldSelectionQuery owns read-only world selection, eligibility, classification, naming, closest-hostile lookup, target bounds, and range queries over canonical live state.
  2. SelectionInteractionController owns selection intents, click routing, Use/PickUp ordering, speculative facing/movement, deferred close-range requests, and the input-action boundary.

Core SelectionState remains the sole selected-object truth. ItemInteractionController remains the sole retail ItemHolder policy owner. LiveEntityRuntime remains the sole GUID/incarnation/visibility owner.

2. Retail and project oracles

No AC algorithm is invented in this slice. Move the existing ports with these references attached:

Existing registered adaptations remain adaptations; this structural slice does not silently “improve” them:

  • IA-19: automatic acquisition is narrowed to hostile non-player monsters.
  • AD-11: ACE null-useability fallback.
  • AD-27: natural MoveToComplete is the client completion seam.
  • AP-23: speculative local Use movement and 3/2/0.6-unit radius buckets.

Their file pointers move in the same commit as their implementation bodies.

3. Invariants

3.1 Render selection

  • The renderer builds a private list, then publishes it only at CompleteFrame; input reads the previous complete frame.
  • A hit carries server GUID and local entity ID. The query accepts it only if LiveEntityRuntime still resolves the same current, visible top-level incarnation.
  • Plain selection excludes self. Active target mode includes self.
  • A successful hit starts the HIGH click-light pulse before target-mode or selection routing.
  • Target-mode success or rejection consumes the click and never falls through to ordinary selection/use.
  • A miss does not clear selection. A miss during target mode leaves target mode active.
  • Drag release may pulse the target, but does not select it.
  • Vivid target markers deliberately query materialized state so selected objects remain indicated through walls and long distances. Picking and new combat acquisition use visible interaction state only.

3.2 Input and outbound ordering

  • Keep the one InputDispatcher.Fired subscription in GameWindow.
  • Combat attack handling, Press/DoubleClick filtering, and retained-UI capture occur before delegation exactly as today.
  • The controller handles only the currently implemented semantic actions: previous selection, closest monster, left selection, double-left selection, Use selected, and PickUp selected.
  • Input callbacks enqueue Use/PickUp. The queue drains after local movement output and before inbound packet dispatch.
  • The queue drains only its boundary snapshot; reentrant enqueue waits until the next frame.

3.3 Use/PickUp and movement

  • ItemInteractionController decides Use/open/target mode. The new controller does not copy ItemHolder policy.
  • Far target: install speculative MoveTo and send exactly one wire request immediately. ACE owns its arrival callback.
  • Close target: install TurnToObject and defer the first request until natural MoveToComplete(None).
  • Cancel/error completion never sends.
  • The movement intent preserves target cylinder radius/height, the AP-23 use radius, and the 7.5-unit CanCharge prediction.
  • Every queued/deferred request is bound to the captured live incarnation and revalidated before sending.
  • Pickup preserves destination container and placement. Retail pending-slot presentation occurs before the request.

3.4 Lifecycle

  • Hidden, logical removal, session reset, and GUID replacement invalidate old hits and pending actions immediately.
  • Token-specific pending cleanup is allowed even after GUID replacement; GUID-only selection cleanup retains the existing incarnation guard so old teardown cannot clear a newly selected replacement.
  • Session reset atomically clears selection history, outbound queue, deferred action, item target mode/busy state, published selection frame, and click pulse.
  • CombatTargetController must not auto-acquire during SessionReset.
  • No new GUID map, selection store, visibility store, or retry timer is added.

4. Target interfaces

4.1 Render hit identity

RetailSelectionPart and RetailSelectionHit carry LocalEntityId alongside ServerGuid. RetailSelectionScene.Pick returns the complete hit. Its lighting source is also keyed by both identities so a reused GUID cannot inherit an old click pulse.

RetailSelectionScene.Reset clears building/published lists, frame keys, frustum state, and pulse state. Geometry resolution receives a narrow internal source interface so publication behavior is testable without installed DAT.

4.2 WorldSelectionQuery

The query receives:

  • canonical LiveEntityRuntime;
  • ClientObjectTable;
  • the shared RetailSelectionScene;
  • player GUID and current player position providers;
  • camera/viewport and cursor providers;
  • a DAT selection-sphere resolver;
  • the existing Setup-cylinder resolver.

It exposes read-only operations for:

  • pick at cursor/screen point with exact-incarnation validation;
  • current interaction record/entity lookup;
  • item type, name, and description;
  • creature/hostile/health eligibility;
  • closest visible hostile result without mutating selection;
  • combat camera target;
  • vivid target information and selection sphere;
  • useability/pickupability;
  • AP-23 use radius, close-range, CanCharge, and target movement data;
  • external-container cylinder range.

The query never mutates selection, queues work, sends packets, or starts movement.

4.3 SelectionInteractionController

The controller receives:

  • SelectionState and WorldSelectionQuery;
  • ItemInteractionController provider;
  • a typed Use/PickUp transport;
  • a narrow movement-intent sink;
  • toast/system diagnostic sinks;
  • Auto Target setting provider.

It owns OutboundInteractionQueue and an incarnation-bound PendingPostArrivalAction. Public methods form the complete host seam:

HandleInputAction
PickAtCursor / PlaceDraggedItem
SelectClosestCombatTarget
GetSelectedOrClosestCombatTarget
SendUse / SendPickUp
DrainOutbound
OnNaturalMoveToComplete
OnEntityHidden / OnEntityRemoved
ResetSession

The controller does not subscribe directly to Silk input and does not own rendering, combat state, the session, or item policy.

5. Execution sequence

Commit 0 — plan and defect registration

  • Land this plan.
  • Register #230 for GUID-incarnation/session-reset leakage.
  • Register #231 for F-key pickup missing the pending destination reservation.
  • No runtime behavior change.

Commit 1 — lifecycle identity hardening (#230)

  • Add local-entity identity to render parts/hits and selection lighting.
  • Add scene reset and deterministic publication tests.
  • Validate published hits against current visible incarnation.
  • Bind pending post-arrival actions to a live record/local identity.
  • Clear only the matching pending token on teardown, including GUID reuse.
  • Make session reset clear scene and item interaction state.
  • Prevent combat Auto Target during SessionReset.
  • Keep this root-cause correction independently bisectable.

Commit 2 — retail pending pickup presentation (#231)

  • Add one ItemInteractionController entry point for a validated world pickup.
  • Publish PendingBackpackPlacementRequested before calling the existing placement delegate.
  • Route F-key pickup through it with destination/placement unchanged.
  • Do not optimistically move ownership; server confirmation/failure remains authoritative.

Commit 3 — extract WorldSelectionQuery

  • Move every read-only selection/classification/range body from GameWindow.
  • Preserve materialized-versus-visible semantics.
  • Extend the live animation read seam with current motion so dead eligibility does not depend on GameWindow.AnimatedEntity.
  • Rewire cursor, vivid target, toolbar health, combat camera, external-container range, drag target, and door-name reads through the query.
  • Delete the corresponding GameWindow methods.

Commit 4 — extract SelectionInteractionController

  • Move world click routing, selection mutation, closest-target selection, outbound queue, Use/PickUp, movement intent, deferred completion, and hidden/removal/reset handling.
  • Compose ItemInteractionController; do not duplicate its policies.
  • Update IA-19/AD-11/AD-27/AP-23 file pointers.
  • Add focused controller tests for every invariant in §3.

Commit 5 — input cutover and host cleanup

  • Replace the six selection/interaction switch cases with one controller delegation call at the same ordered position.
  • Forward render-frame, update-frame, MoveTo completion, hidden/removal, session-reset, cursor, and drag hooks.
  • Delete old fields/records/wrappers from GameWindow.
  • Prove GameWindow no longer owns a selection algorithm or interaction request body.

Commit 6 — review, gate, and ledger close

  • Run retail-conformance, architecture/integration, and adversarial read-only reviews against the complete slice diff.
  • Fix confirmed findings and repeat review until clean.
  • Run focused Core/App tests, Release solution build, and full Release suite.
  • Update code-structure ownership status, roadmap/current queue if necessary, both session instruction files, divergence pointers, and durable interaction memory.
  • Record the new GameWindow line/field/method counts.

6. Automated acceptance matrix

Render/query

  • Building frame is invisible until CompleteFrame.
  • Last complete frame remains queryable while the next frame builds.
  • Empty completion clears prior hits.
  • Hidden, pending, deleted, and replaced incarnations reject stale hits.
  • A replacement GUID is not pickable until its own part is published.
  • Pulse is identity-bound and cannot color a replacement.
  • Plain pick excludes self; target-mode pick includes self.
  • Vivid target can use materialized state while combat candidates cannot.
  • Closest hostile excludes self, players, pets, friendly NPCs, dead, Hidden, pending, and deleted objects.

Interaction

  • Pulse precedes target-mode and selection routing.
  • Target-mode success/rejection consumes the click without selection drift.
  • Single click selects; double click captures the clicked GUID and queues one activation.
  • Miss preserves selection and target mode.
  • Previous selection preserves current SelectionState semantics.
  • Queue drains after movement and only once per captured boundary.
  • Far Use/PickUp sends once; close sends only on natural completion.
  • Cancel/error/Hidden/delete/reset sends zero.
  • Delete then same-GUID create sends zero to the replacement.
  • Useability and pickupability fallbacks remain byte-for-byte behaviorally equivalent.
  • Creature and Stuck pickup rejection messages remain exact.
  • Corpse double-click/Use opens it and never attempts to pick up the corpse.
  • F pickup reserves the first destination slot before wire send.
  • Pickup destination/placement survive the deferred path.

Lifecycle/input

  • Session reset clears previous selection, target mode, busy state, queue, pending completion, published picks, and pulse; repeated reset is safe.
  • SessionReset cannot trigger combat auto-target.
  • Modal/retained UI capture still prevents controller invocation upstream.
  • Escape target-mode cancellation remains ahead of window-close behavior.

7. Connected gate

Run one normal capped Release client and verify:

  1. select a nearby door/NPC and see the click pulse and target corners;
  2. double-click/Use the door or NPC;
  3. select a distant corpse, press R, approach, and open it;
  4. double-click one loot item and use F on another; both reserve the expected backpack slot and settle after ACE confirmation;
  5. enter combat, select a hostile, use closest-target and previous-selection;
  6. kill the selected target with Auto Target enabled and observe only a hostile replacement;
  7. portal or relog and confirm no old selection/pulse/action survives.

Any failure returns to the owning commit. No retry, grace period, or GUID-only suppression is acceptable.

8. Subagent policy for this slice

The implementation is tightly coupled across GameWindow, live lifetime, render publication, input order, and player MoveTo. One primary agent performs all edits. Independent subagents are useful for read-only retail, architecture, and adversarial reviews; they do not edit the shared slice concurrently.