Define the read-only query and stateful interaction ownership boundaries, pin retail ordering and lifecycle invariants, and isolate the GUID-incarnation and pending-pickup defects as #230 and #231 before the mechanical cutover. Co-authored-by: OpenAI Codex <codex@openai.com>
14 KiB
GameWindow Slice 1 — selection and interaction ownership
Status: Approved for execution 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.
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:
WorldSelectionQueryowns read-only world selection, eligibility, classification, naming, closest-hostile lookup, target bounds, and range queries over canonical live state.SelectionInteractionControllerowns 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:
2026-07-17-retail-world-selection-pseudocode.md— render-coupled part selection, selection sphere, vivid target, click pulse.2026-07-10-retail-toolbar-interaction-pseudocode.md— target-mode precedence, ItemHolder Use, selection notices.2026-07-17-retail-external-container-looting-pseudocode.md— corpse/container open, range, pending loot placement.2026-07-12-death-and-auto-target-pseudocode.md— synchronous selection clear and reentrant Auto Target.Render::GfxObjUnderSelectionRay @ 0x0054C740.ACCWeenieObject::SetSelectedObject @ 0x0058C2E0.CPlayerSystem::OnAction @ 0x00561890.CPlayerSystem::PlaceInBackpack @ 0x0055D8C0.ItemHolder::UseObject @ 0x00588A80.UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0.
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
MoveToCompleteis 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
LiveEntityRuntimestill 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.Firedsubscription inGameWindow. - 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
ItemInteractionControllerdecides 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.
CombatTargetControllermust not auto-acquire duringSessionReset.- 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:
SelectionStateandWorldSelectionQuery;ItemInteractionControllerprovider;- 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
PendingBackpackPlacementRequestedbefore 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
GameWindowmethods.
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
GameWindowno 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
GameWindowline/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:
- select a nearby door/NPC and see the click pulse and target corners;
- double-click/Use the door or NPC;
- select a distant corpse, press R, approach, and open it;
- double-click one loot item and use F on another; both reserve the expected backpack slot and settle after ACE confirmation;
- enter combat, select a hostile, use closest-target and previous-selection;
- kill the selected target with Auto Target enabled and observe only a hostile replacement;
- 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.