# E1.M3 — Prototype story backlog (InteractionAndTargetingLayer)

Working backlog for **Epic 1 — Slice 3** ([interaction, targeting, ability input](../decomposition/epics/epic_01_core_player_runtime.md#epic-1-slice-3)). Decomposition and contracts: [E1.M3 — InteractionAndTargetingLayer](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md).

**Labels (Linear):** every issue **`E1.M3`**; add **`Epic 1`** / Slice 3 as your team prefers.

**Precursor (do not re-scope):** authoritative **`InteractionRequest`** + horizontal reach = **E1.M1** / [NEO-9](NEO-9-implementation-plan.md). These stories **build on** that policy; they do not replace NEO-9 acceptance.

**Linear issues (created):** attach **`docs/plans/NEO-*-implementation-plan.md`** on the same branch as code when each story starts (same pattern as [NEO-18](NEO-18-implementation-plan.md)).

| Slug | Linear |
|------|--------|
| E1M3-01 | [NEO-23](https://linear.app/neon-sprawl/issue/NEO-23) |
| E1M3-02 | [NEO-24](https://linear.app/neon-sprawl/issue/NEO-24) |
| E1M3-03 | [NEO-25](https://linear.app/neon-sprawl/issue/NEO-25) |
| E1M3-04 | [NEO-26](https://linear.app/neon-sprawl/issue/NEO-26) |
| E1M3-05 | [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27) |

**Dependency graph in Linear:** NEO-24, NEO-25 → blocked by **NEO-23**. NEO-26 → blocked by **NEO-23**, **NEO-24**. NEO-27 → blocked by **NEO-24**.

---

## Story order (recommended)

| Order | Slug | Linear | Depends on |
|-------|------|--------|------------|
| 1 | **E1M3-01** | [NEO-23](https://linear.app/neon-sprawl/issue/NEO-23) | E1.M1 (PositionState, HorizontalReach), NEO-9 patterns |
| 2 | **E1M3-02** | [NEO-24](https://linear.app/neon-sprawl/issue/NEO-24) | E1M3-01, E1.M2 (presentation only) |
| 3 | **E1M3-03** | [NEO-25](https://linear.app/neon-sprawl/issue/NEO-25) | E1M3-01 or parallel after NEO-9 registry read |
| 4 | **E1M3-04** | [NEO-26](https://linear.app/neon-sprawl/issue/NEO-26) | E1M3-01, E1M3-02 |
| 5 | **E1M3-05** | [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27) | E1M3-02; [E9.M1](../decomposition/modules/E9_M1_TelemetryEventSchema.md) when schema exists |

---

### E1M3-01 — `TargetState` v1 + server selection intent

**Goal:** Publish a **versioned** server-owned **`TargetState`** and a client→server **selection intent** (name TBD: e.g. `TargetSelectRequest`) over the **existing HTTP JSON** prototype channel, with **`reasonCode`** denials consistent with [NEO-9](NEO-9-implementation-plan.md) / `InteractionResponse`.

**In scope**

- Server: validate intent using authoritative **`PositionState`** + shared **horizontal XZ** reach rules; stub **eligible target** set (static ids or scene-registered entities) until sim entities exist.
- Response: current **`TargetState`** (locked id or empty, validity, optional sequence).
- Unit + integration tests; [server README](../../server/README.md) section for the new route(s).

**Out of scope**

- Protobuf wire; full entity sim; PvP gating ([E6.M1](../decomposition/modules/E6_M1_PvPEligibilityAndFlagState.md)) beyond a single **stub** deny path if needed for API shape.
- Replacing **`POST …/interact`** — remain separate surface ([E1.M3 boundary](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md#boundary-e1m1-vs-e1m3)).

**Acceptance criteria**

- [ ] Client cannot commit a locked target the server would reject: **server** is source of truth for `TargetState`.
- [ ] Denied selection returns **200** (or agreed status) + body with **required** `reasonCode` when selection is rejected (match project conventions from NEO-9).
- [ ] Moving the player with the existing move APIs changes **in range / out of range** outcomes for target lock where range applies.
- [ ] Documented field list matches the **illustrative** table in [E1_M3 — Key contracts](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md#key-contracts) (refine names in the plan PR, not silently in code only).

---

### E1M3-02 — Client tab-target + lock UI synced to server

**Goal:** **Tab** (or agreed binding) cycles **server-eligible** targets in deterministic order; **visual** lock state matches last acknowledged **`TargetState`**.

**In scope**

- Godot: input → emit selection intent → apply server response; optional **optimistic** highlight **must** reconcile on deny ([authority](../decomposition/modules/client_server_authority.md#e1m3-interactionandtargetinglayer)).
- Clear target (e.g. Esc) if product agrees; document binding in `client/project.godot` or module README.

**Out of scope**

- Nearest-in-cone auto-target; sticky soft-target under latency ([deferred in E1.M3](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md#responsibilities)).
- Hotbar / `AbilityCastRequest` — that is [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) once E1M3-01/02 are in place.

**Acceptance criteria**

- [ ] With **≥2** stub targets, tab cycles order **defined in module or plan** (e.g. ascending id) and wraps.
- [ ] UI or debug overlay shows **server** lock id (or “none”) after each successful round-trip.
- [ ] Server denial updates UI to **previous** or **cleared** state per documented policy.

---

### E1M3-03 — `InteractableDescriptor` list / projection (multi-entity)

**Goal:** Server exposes a **small, versioned** list (or per-zone snapshot) of **`InteractableDescriptor`** records so the client can tab/hover **interactables** beyond the single NEO-9 row **without** duplicating registry truth in GDScript long-term.

**In scope**

- Extend or complement `PrototypeInteractableRegistry` (or successor) so **multiple** entries are addressable; single JSON **GET** or embedded list on an existing player/session endpoint — pick one and document.
- Client: consume list for proximity preview (optional) or selection id set; **constants in GDScript** must stay **documented** as duplicate-until-codegen ([NEO-9](NEO-9-implementation-plan.md) precedent) or generated from server in this story if you add a trivial export path.

**Out of scope**

- Full data-driven content pipeline ([contracts.md](../decomposition/modules/contracts.md) content kind).
- Gather loop rewards ([E3.M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md)).

**Acceptance criteria**

- [ ] **≥2** interactables in server registry; client can issue **`InteractionRequest`** against each id when in range (NEO-9 path still works).
- [ ] Descriptor payload includes at least: **id**, **world anchor**, **`interactionRadius`**, **kind** string or enum for future branching.

---

### E1M3-04 — `SelectionEvent` surface + consumer

**Goal:** Any **change** to effective target or primary interactable selection is observable as a **`SelectionEvent`** (wire or client-local mirror of server events for prototype).

**In scope**

- Define minimal event shape: **previous**, **next**, **cause** (`tab`, `clear`, `server_correction`, …).
- One **consumer**: debug panel, `print`, or thin HUD widget listing last N events.

**Out of scope**

- Full UI prompt system; party flows ([E8.M1](../decomposition/modules/E8_M1_PartyAndMatchAssembly.md)).

**Acceptance criteria**

- [ ] Tab, clear, and forced server correction (if testable) each emit distinguishable **cause** values.
- [ ] [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) can subscribe to the same bus in a follow-up without reshaping the event (document subscription point).

---

### E1M3-05 — Slice 3 telemetry hook sites

**Goal:** Align with Epic 1 Slice 3 hooks: **`target_changed`**, and reserve call sites for **`ability_cast_requested`** / **`ability_cast_denied`** (latter two may no-op until E1.M4 + E5.M1).

**In scope**

- Document function names or signals in client/server where events **will** emit; **`TODO(E9.M1)`** until [telemetry schema](../decomposition/modules/E9_M1_TelemetryEventSchema.md) exists.
- Optional: dev-only logging when flag enabled.

**Out of scope**

- Production ingest, dashboards, OpenTelemetry wiring.

**Acceptance criteria**

- [ ] Code or doc comment references **event names** matching [epic_01 Slice 3](../decomposition/epics/epic_01_core_player_runtime.md#epic-1-slice-3).
- [ ] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) updated when **NEO-23** merges (status **In Progress** / contract partial per team rule).

---

## After this backlog

- **E1.M4** stories: hotbar + **`AbilityCastRequest`** to [E5.M1](../decomposition/modules/E5_M1_CombatRulesEngine.md) stub or live consumer (separate plans).
- **Register:** when E1M3-01 merges exported **`TargetState`** (+ intent), update [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) **E1.M3** row / note and E1.M3 **Status** per [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md).

---

## Related docs

- [E1_M3_InteractionAndTargetingLayer.md](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md)
- [NEO-9 — InteractionRequest](NEO-9-implementation-plan.md)
- [client_server_authority.md — E1.M3](../decomposition/modules/client_server_authority.md#e1m3-interactionandtargetinglayer)
