# NEO-27 — Implementation plan

## Story reference

| Field | Value |
|--------|--------|
| **Key** | NEO-27 |
| **Title** | E1.M3: Slice 3 telemetry hook sites (`target_changed`, …) |
| **Linear** | [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27/e1m3-slice-3-telemetry-hook-sites-target-changed) |
| **Slug** | E1M3-05 |
| **Git branch** | `NEO-27-e1m3-slice-3-telemetry-hook-sites` |
| **Parent context** | [Epic 1 — Core Player Runtime](https://linear.app/neon-sprawl/project/epic-1-core-player-runtime-client-controls-character-loop-66bd590cd016) · [E1M3 prototype backlog](E1M3-prototype-backlog.md) |
| **Decomposition** | [Epic 1 Slice 3 — telemetry hooks](../decomposition/epics/epic_01_core_player_runtime.md#epic-1-slice-3); [E1.M3 — InteractionAndTargetingLayer](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md) |

## Kickoff clarifications

- **Q1 (resolved):** Where should reserved `ability_cast_requested` / `ability_cast_denied` hook comments live for this story?
- **Answer:** Keep them in **`client/scripts/main.gd`** (no extra placeholder script in NEO-27).
- **Why no additional blocking questions:** Event names, scope, and out-of-scope were explicit in Linear + backlog docs; this was the only implementation-location decision not already settled.

## Goal, scope, and out-of-scope

**Goal:** Establish **documented hook sites** for Epic 1 Slice 3 product telemetry names: **`target_changed`** (wired to real selection transitions), plus **reserved** (no-op / comment-only) sites for **`ability_cast_requested`** and **`ability_cast_denied`** until [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) + [E5.M1](../decomposition/modules/E5_M1_CombatRulesEngine.md). All real ingest waits on **`TODO(E9.M1)`** per [E9.M1 — TelemetryEventSchema](../decomposition/modules/E9_M1_TelemetryEventSchema.md).

**In scope**

- **Event names** in code or doc comments matching [epic Slice 3](../decomposition/epics/epic_01_core_player_runtime.md#epic-1-slice-3) (`target_changed`, `ability_cast_requested`, `ability_cast_denied`).
- **`TODO(E9.M1)`** at each hook site until a catalog exists.
- **Optional dev logging** when a flag is enabled (reuse or extend the NEO-26 `log_selection_events` pattern on the client; optional `ILogger` / `Debug.WriteLine` guard on server only if we add a clearly dev-gated path—prefer comments + single optional trace line consistent with repo norms).
- **`documentation_and_implementation_alignment.md`:** extend the **E1.M3** snapshot row to mention NEO-27 hook sites (per Linear acceptance; NEO-23 is already on `main`).

**Out of scope**

- Production ingest, dashboards, OpenTelemetry.
- Changing `TargetState` / HTTP contracts (NEO-23).
- Implementing real ability cast requests (E1.M4).

## Acceptance criteria checklist

- [ ] **Documented event names** in-repo matching Slice 3: `target_changed`, `ability_cast_requested`, `ability_cast_denied` (latter two may be comment-only reserves).
- [ ] **`TODO(E9.M1)`** at each hook site referencing schema work.
- [ ] **Optional dev logging** for `target_changed` when a documented flag is on (or explicitly documented that an existing flag covers it, with log line including the product event name).
- [ ] **`documentation_and_implementation_alignment.md`** E1.M3 row updated for NEO-27.

## Technical approach

1. **`target_changed` (client)** — Canonical prototype mirror: when **`TargetSelectionClient`** emits **`selection_event`** (NEO-26), the product telemetry name is **`target_changed`** (same transition: normalized `lockedTargetId` change, with `cause` / `sequence` carrying intent). Add a short class-level or method-level note on **`_maybe_emit_selection_event`** linking **`selection_event`** → **`target_changed`**. Optionally prefix dev **`print`** (when `log_selection_events` is true) with a literal `target_changed` token so grep and QA are unambiguous.

2. **`target_changed` (server)** — Authoritative lock id transitions occur in **`InMemoryPlayerTargetLockStore.ApplySet`** / **`ApplyClear`** when the stored lock id actually changes (sequence bumps). Add **`//` / `///` comments** (and `TODO(E9.M1)`) at those mutation success paths as the **server-side hook site** for `target_changed`. Rationale: single choke point; HTTP handlers stay thin.

3. **`ability_cast_requested` / `ability_cast_denied` (reserve)** — No hotbar or cast route exists yet. Add a compact **comment block** in **`client/scripts/main.gd`** (or the first file E1.M4 will extend for input, if we prefer—**Decision:** `main.gd` next to other prototype wiring) listing the two event names and pointing to E1.M4 / E5.M1 + E9.M1. Optionally mirror a one-line reserve in **`TargetingApi.cs`** near denial responses noting that **`ability_cast_denied`** (with `reasonCode`) will attach to a **future cast POST**, not the current target-select deny path—avoids confusion with selection denials.

4. **Docs alignment** — Append to the E1.M3 **Snapshot** cell: NEO-27 documents Slice 3 telemetry hook sites; link this plan.

## Files to add

| Path | Rationale |
|------|-----------|
| `docs/manual-qa/NEO-27.md` | Ticket-level manual verification checklist for Slice 3 telemetry hook-site behavior and expectations. |

## Files to modify

| Path | Rationale |
|------|-----------|
| `client/scripts/target_selection_client.gd` | Document `target_changed` ↔ `selection_event`; optional dev log prefix; `TODO(E9.M1)`. |
| `server/NeonSprawl.Server/Game/Targeting/InMemoryPlayerTargetLockStore.cs` | Server authoritative `target_changed` hook site on lock id transitions. |
| `client/scripts/main.gd` | Reserved comment block for `ability_cast_requested` / `ability_cast_denied` (E1.M4 + E5.M1). |
| `server/NeonSprawl.Server/Game/Targeting/TargetingApi.cs` | Optional one-line reserve clarifying cast denials vs target-select denials (avoids mis-mapping event names). |
| `client/README.md` | Short “Slice 3 telemetry (NEO-27)” pointer next to SelectionEvent section. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E1.M3 snapshot: NEO-27 telemetry hook sites landed (per team rule). |
| `docs/decomposition/modules/E1_M3_InteractionAndTargetingLayer.md` | Optional one-line under telemetry: code hook pointers (`target_selection_client.gd`, lock store)—only if the module table still says hooks are TODO-only; keep register alignment. |
| `docs/plans/NEO-27-implementation-plan.md` | Keep kickoff clarifications and resolved decisions synchronized with actual implementation choices. |

## Tests

| File | Action |
|------|--------|
| `client/test/selection_event_client_test.gd` | **Change only if** new observable behavior is added (e.g. new export or signal). If implementation is comments + optional `print` format only, **no test change** — manual QA: enable `log_selection_events`, tab/clear, confirm output contains `target_changed`. |
| `docs/manual-qa/NEO-27.md` | Add explicit manual checks for `target_changed` log token, id-change-only emission behavior, and ability telemetry reserve documentation. |

**Server:** No new tests required for comment-only / dev-trace placeholders unless we introduce a testable abstraction (out of scope).

## Open questions / risks

**Resolved (2026-04-26):** Linear no longer lists **NEO-24** as blocking NEO-27 (dependency removed on board).
**Resolved (2026-04-26):** Keep ability telemetry reserve comments in **`client/scripts/main.gd`** for NEO-27; no placeholder script yet.

**None** otherwise.
