neon-sprawl/docs/plans/E1M3-prototype-backlog.md

9.0 KiB

E1.M3 — Prototype story backlog (InteractionAndTargetingLayer)

Working backlog for Epic 1 — Slice 3 (interaction, targeting, ability input). Decomposition and contracts: E1.M3 — InteractionAndTargetingLayer.

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. 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).

Slug Linear
E1M3-01 NEO-23
E1M3-02 NEO-24
E1M3-03 NEO-25
E1M3-04 NEO-26
E1M3-05 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 E1.M1 (PositionState, HorizontalReach), NEO-9 patterns
2 E1M3-02 NEO-24 E1M3-01, E1.M2 (presentation only)
3 E1M3-03 NEO-25 E1M3-01 or parallel after NEO-9 registry read
4 E1M3-04 NEO-26 E1M3-01, E1M3-02
5 E1M3-05 NEO-27 E1M3-02; E9.M1 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 / 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 section for the new route(s).

Out of scope

  • Protobuf wire; full entity sim; PvP gating (E6.M1) beyond a single stub deny path if needed for API shape.
  • Replacing POST …/interact — remain separate surface (E1.M3 boundary).

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 (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).
  • 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).
  • Hotbar / AbilityCastRequest — that is E1.M4 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 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 content kind).
  • Gather loop rewards (E3.M1).

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).

Acceptance criteria

  • Tab, clear, and forced server correction (if testable) each emit distinguishable cause values.
  • E1.M4 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 exists.
  • Optional: dev-only logging when flag enabled.

Out of scope

  • Production ingest, dashboards, OpenTelemetry wiring.

Acceptance criteria


After this backlog