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