136 lines
11 KiB
Markdown
136 lines
11 KiB
Markdown
# E1.M3 — InteractionAndTargetingLayer
|
|
|
|
## Summary
|
|
|
|
| Field | Value |
|
|
|--------|--------|
|
|
| **Module ID** | E1.M3 |
|
|
| **Epic** | [Epic 1 — Core Player Runtime](../epics/epic_01_core_player_runtime.md) |
|
|
| **Stage target** | Prototype |
|
|
| **Status** | In Progress (see [dependency register](module_dependency_register.md)) |
|
|
| **Linear** | [NEO-23](https://linear.app/neon-sprawl/issue/NEO-23) through [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27) ([backlog](../../plans/E1M3-prototype-backlog.md)); label **`E1.M3`** per [decomposition README — Linear alignment](../README.md#linear-alignment) |
|
|
|
|
## Purpose
|
|
|
|
**Authority:** [Client vs server — E1.M3](client_server_authority.md#e1m3-interactionandtargetinglayer) — the server decides valid **targets** and **interactable** eligibility (range, faction, phase, alive/dead, etc.); the client sends selection or use **intent** and may show hover/soft focus until the server confirms.
|
|
|
|
Bridges authoritative **position** and reach policy from [E1.M1 — InputAndMovementRuntime](E1_M1_InputAndMovementRuntime.md) into combat, gathering, abilities, and social flows: **who** is locked for gameplay, **what** in the world is selectable, and **when** selection changes—without treating camera pose as gameplay truth (see [E1.M2 — world-anchored semantics](E1_M2_IsometricCameraController.md#mid-project-rotation-policy-practical-compromise)).
|
|
|
|
## Boundary: E1.M1 vs E1.M3
|
|
|
|
| Topic | E1.M1 (today) | E1.M3 (this module) |
|
|
|--------|----------------|---------------------|
|
|
| **Primary job** | Locomotion + authoritative **“use interactable *now*”** attempt via `InteractionRequest` | **Selection model**: lock, swap rules, descriptors, and **change events** for UI and downstream systems |
|
|
| **Wire / HTTP** | `POST …/interact` + `InteractionResponse` with `reasonCode` ([NEO-9](../../plans/NEO-9-implementation-plan.md)) | Future **selection/target** intents and authoritative **`TargetState`** sync (wire shape TBD; see [contracts.md](contracts.md)) |
|
|
| **Registry / catalog** | Static prototype interactable table for range checks | **`InteractableDescriptor`** (and combat target projection) as the **stable** description surface for clients and validators |
|
|
| **Client UX** | Minimal **E** key + proximity **preview** glow (authoritative outcome still from server) | Tab-target, focus cycling, sticky lock, rich hover, prompts—**prototype scope** grows here |
|
|
|
|
**Shared policy (not owned exclusively by either module):** horizontal reach on **X/Z only**, **Y ignored** for threshold distance, inclusive radius boundary—same rule as [NEO-9](../../plans/NEO-9-implementation-plan.md). Both E1.M1 interact validation and E1.M3 eligibility checks **reuse** the same math and content anchors so client preview and server denial stay aligned.
|
|
|
|
## Responsibilities
|
|
|
|
- **Combat target lock:** maintain and expose **`TargetState`** for tab-target flow; integrate with [E1.M4 — AbilityInputScaffold](E1_M4_AbilityInputScaffold.md) and [E5.M1 — CombatRulesEngine](E5_M1_CombatRulesEngine.md) for cast validity (server re-validates; client is a view + intent).
|
|
- **World interactables:** discovery, selection, and use flow beyond the NEO-9 smoke path—multiple kinds, prompts, and data-driven defs when scoped; still grounded in **`InteractableDescriptor`** projections.
|
|
- **Focus swap rules:** **Prototype:** explicit input to cycle targets / interactables in a deterministic order (e.g. tab through eligible list); **Deferred:** nearest-in-cone heuristics, soft-target stickiness under latency—decide with first multiplayer combat vertical slice ([authority doc — lag compensation](client_server_authority.md#what-this-doc-does-not-fix-yet)).
|
|
- **Presentation:** highlights, outlines, and optional **wrong** local preview until server ack; reconcile on snapshot or denial ([authority](client_server_authority.md#e1m3-interactionandtargetinglayer)).
|
|
- **E1.M2 adjacency:** targeting and line-of-interest for **gameplay** use **world geometry + server-known positions**, not `CameraState` or client-only aim ([E1.M2](E1_M2_IsometricCameraController.md)).
|
|
|
|
## Key contracts
|
|
|
|
| Contract | Role |
|
|
|----------|------|
|
|
| `TargetState` | Authoritative (server) view: locked combat/interaction target, validity, swap/clear reasons. |
|
|
| `InteractableDescriptor` | Stable description of a selectable world entity (id, kind, anchor, radius / flags). |
|
|
| `SelectionEvent` | Selection changes for UI and consumers (who/when/cause). |
|
|
|
|
### Contract notes ([contracts.md](contracts.md))
|
|
|
|
- **Kind:** these are **wire** contracts once the game channel exists (Protobuf preferred per [tech stack](../../architecture/tech_stack.md)); until then, versioned **JSON** spikes must be explicitly throwaway and tracked for migration—same discipline as early `MoveCommand` / `InteractionRequest`.
|
|
- **Denials:** reuse the **stable `reasonCode` string** pattern from `InteractionResponse` (NEO-9): whenever the server rejects a selection or use intent, return a **non-empty** machine-oriented code; document new codes alongside E5.M1 / E3.M1 consumers.
|
|
|
|
**Logical fields (illustrative, not a frozen schema):**
|
|
|
|
| Contract | Illustrative fields |
|
|
|----------|---------------------|
|
|
| `TargetState` | Target entity id (or empty); validity (`ok` / `out_of_range` / `invalid_target` / …); optional server tick or sequence for reconciliation |
|
|
| `InteractableDescriptor` | `interactableId`; category/kind; world position or entity ref; `interactionRadius`; flags (e.g. usable-in-combat) |
|
|
| `SelectionEvent` | Previous id, new id, cause (`tab`, `clear`, `target_died`, `zone_change`, `server_correction`, …) |
|
|
|
|
### Intent vs state (reference)
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph clientLayer [Client]
|
|
Hover[HoverHighlight]
|
|
Intent[SelectionIntent]
|
|
end
|
|
subgraph serverLayer [Server]
|
|
Pos[PositionState_E1M1]
|
|
Val[EligibilityValidation]
|
|
TS[TargetState_truth]
|
|
end
|
|
Pos --> Val
|
|
Intent --> Val
|
|
Val --> TS
|
|
TS --> clientLayer
|
|
Hover -.->|"preview_only"| clientLayer
|
|
```
|
|
|
|
## Module dependencies
|
|
|
|
- **E1.M1** — InputAndMovementRuntime: authoritative **`PositionState`** and horizontal reach policy; existing **`InteractionRequest`** path for “use now” attempts.
|
|
|
|
**Logical (not in register “Depends On” column):** [E5.M1](E5_M1_CombatRulesEngine.md) resolves hostile actions and may **deny** casts for reasons beyond selection; [E6.M1](E6_M1_PvPEligibilityAndFlagState.md) gates player targets ([pvp_combat_integration.md](pvp_combat_integration.md)). E1.M3 does **not** list E5.M1 as a hard module dependency in the register so that targeting can ship and test against stubs per [Epic 1 Slice 3](../epics/epic_01_core_player_runtime.md#epic-1-slice-3).
|
|
|
|
## Dependents (by design)
|
|
|
|
- **E1.M4** — AbilityInputScaffold: reads **`TargetState`** (and related validity) to build **`AbilityCastRequest`**.
|
|
- **E3.M1** — ResourceNodeAndGatherLoop: gather **interact** flows use range + selection aligned with **`InteractableDescriptor`**.
|
|
- **E5.M1** — CombatRulesEngine: tab-target resolution, valid actions against locked target; re-validates client intent.
|
|
- **E8.M1** — PartyAndMatchAssembly: party-scoped selection / invite context hooks anchored on player identity from targeting layer.
|
|
|
|
## Related implementation slices
|
|
|
|
[Epic 1 — Slice 3: Interaction, targeting, and ability input wiring](../epics/epic_01_core_player_runtime.md#epic-1-slice-3)
|
|
|
|
- **Scope:** E1.M3 + E1.M4 with stub or live combat consumer for `AbilityCastRequest`.
|
|
- **Dependencies:** E1.M2, E5.M1 (minimum combat hook).
|
|
- **Acceptance criteria:** Players can select targets/interactables and fire hotbar-bound abilities when the combat module accepts casts.
|
|
- **Telemetry hooks:** `target_changed`, `ability_cast_requested`, `ability_cast_denied` with reason codes (align with [E9.M1](E9_M1_TelemetryEventSchema.md) when catalog exists).
|
|
|
|
## Implementation snapshot
|
|
|
|
- **`TargetState` (prototype JSON v1 — NEO-23):** server **`GET /game/players/{id}/target`** and **`POST /game/players/{id}/target/select`** with versioned **`PlayerTargetStateResponse`** / **`TargetSelectRequest`** / **`TargetSelectResponse`**; stub registry + in-memory lock under `server/NeonSprawl.Server/Game/Targeting/`; horizontal reach + soft lock semantics — [NEO-23](../../plans/NEO-23-implementation-plan.md), [server README — Targeting](../../../server/README.md#targeting-neo-23). **Still open on later issues:** **`InteractableDescriptor`**, **`SelectionEvent`**, Godot tab-target ([NEO-24](https://linear.app/neon-sprawl/issue/NEO-24)). **Story backlog:** [E1M3 prototype backlog](../../plans/E1M3-prototype-backlog.md).
|
|
- **Precursor under E1.M1 (NEO-9):** server **`POST /game/players/{id}/interact`** with **`InteractionRequest`** / **`InteractionResponse`**; **`HorizontalReach`** (`server/NeonSprawl.Server/Game/World/HorizontalReach.cs`); prototype registry and API under `server/NeonSprawl.Server/Game/Interaction/`; client **`interaction_request_client.gd`**, **`interaction_radius_indicators.gd`**, **`prototype_interaction_constants.gd`** — see [NEO-9](../../plans/NEO-9-implementation-plan.md) and [server README — Interaction](../../../server/README.md#interaction-neo-9).
|
|
- **Alignment:** [Documentation and implementation alignment](documentation_and_implementation_alignment.md).
|
|
|
|
## Risks and telemetry
|
|
|
|
- **Soft vs authoritative target:** client may highlight a target the server will deny; mitigate with visible deny reasons, fast reconciliation, and logging material mismatch ([authority — debugging](client_server_authority.md#telemetry-and-debugging)).
|
|
- **Occluders vs truth:** camera occlusion and pick-through ([NEO-20](https://linear.app/neon-sprawl/issue/NEO-20)) affect **presentation** and click-to-move; **server** eligibility must not depend on “what the camera showed.”
|
|
- **Lag compensation** for tab-target: deferred per [client vs server authority](client_server_authority.md#what-this-doc-does-not-fix-yet); decide with combat design when E5.M1 networking MVP is scoped.
|
|
|
|
**Telemetry (Slice 3):** `target_changed`, `ability_cast_requested`, `ability_cast_denied` — **TODO(E9.M1)** until event schema exists.
|
|
|
|
## Linear backlog
|
|
|
|
Parent epic: [Epic 1 — Core Player Runtime](https://linear.app/neon-sprawl/project/epic-1-core-player-runtime-client-controls-character-loop-66bd590cd016).
|
|
|
|
**Stories:** full acceptance checklists in **[`docs/plans/E1M3-prototype-backlog.md`](../../plans/E1M3-prototype-backlog.md)**.
|
|
|
|
| Slug | Linear |
|
|
|------|--------|
|
|
| **E1M3-01** | [NEO-23](https://linear.app/neon-sprawl/issue/NEO-23) — TargetState v1 + server selection intent |
|
|
| **E1M3-02** | [NEO-24](https://linear.app/neon-sprawl/issue/NEO-24) — Client tab-target + lock UI synced to server |
|
|
| **E1M3-03** | [NEO-25](https://linear.app/neon-sprawl/issue/NEO-25) — InteractableDescriptor list / projection |
|
|
| **E1M3-04** | [NEO-26](https://linear.app/neon-sprawl/issue/NEO-26) — SelectionEvent surface + consumer |
|
|
| **E1M3-05** | [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27) — Slice 3 telemetry hook sites |
|
|
|
|
Issues should carry label **`E1.M3`** per [Linear alignment](../README.md#linear-alignment).
|
|
|
|
## Source anchors
|
|
|
|
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 1, E1.M3 module line.
|
|
- [Module dependency register](module_dependency_register.md)
|
|
- [PvP and the combat engine](pvp_combat_integration.md) — E5.M1 + E6.M1 gating for player targets
|