# Code review — NEO-64 gather telemetry hook sites

**Date:** 2026-05-24  
**Scope:** Branch `NEO-64-e3m1-resource-gathered-gather-node-depleted-telemetry-hooks` · commits `f39d12a`–`847cda9` vs `origin/main`  
**Base:** `origin/main`

## Verdict

**Approve with nits**

## Summary

NEO-64 adds **comment-only** E9.M1 telemetry hook anchors in **`GatherOperations.TryGather`** for future catalog events **`resource_gathered`** (every successful gather after inventory + XP + depletion commit) and **`gather_node_depleted`** (when that success drives **`remainingGathers`** to **0**, not on pre-gather **`node_depleted`** deny). The change follows NEO-49/NEO-56 precedent: **`TODO(E9.M1)`** markers, planned payload fields, no **`ILogger`** or ingest, engine-only placement (no duplicate hooks in **`InteractionApi`**). Docs close out Epic 3 Slice 2 / E3.M1 backlog (**Ready**). Risk is very low — no runtime behavior, wire contract, or test changes; **`remainingAfterGather`** local is a readability refactor only.

## Documentation checked

| Document | Result |
|----------|--------|
| [`docs/plans/NEO-64-implementation-plan.md`](../plans/NEO-64-implementation-plan.md) | **Matches** — comments-only, engine-only anchor, success-path timing, kickoff decisions (no interact deny hook, no ILogger), all planned file touches, acceptance checklist complete. |
| [`docs/plans/E3M1-prototype-backlog.md`](../plans/E3M1-prototype-backlog.md) | **Matches** — E3M1-08 checkboxes + landed note; Slice 2 backlog complete. Template line “interact deny branch if applicable” superseded by kickoff (engine-only) — no implementation conflict. |
| [`docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md`](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) | **Matches** — NEO-64 hook bullet, module **Ready**, gather orchestration authority unchanged. |
| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E3.M1 row **Ready** with NEO-64 landed note; E3M1-01–E3M1-08 complete. |
| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E3.M1 row **Ready**; **E3.M1 note** includes NEO-64 / E3M1-08. |
| [`docs/decomposition/epics/epic_03_crafting_economy.md`](../decomposition/epics/epic_03_crafting_economy.md) | **Matches** — Slice 2 vocabulary `resource_gathered`, `gather_node_depleted`. |
| [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **N/A** — no authority boundary change. |
| [`docs/manual-qa/NEO-64.md`](../manual-qa/NEO-64.md) | **Matches** — code-review checklist, regression command, optional Bruno sanity. |
| [`server/README.md`](../../server/README.md) | **Matches** — NEO-64 paragraph under gather engine (NEO-62): event names, timing, NEO-56 `item_created` cross-ref, manual QA + plan links. |

Register/tracking: E3.M1 **Ready** and alignment row updates are appropriate — Slice 2 backlog is complete.

## Blocking issues

None.

## Suggestions

1. ~~**Conditional structure for `gather_node_depleted` (optional)** — NEO-40 nests the **`level_up`** hook inside `if (nextLevel > prevLevel)`. Here **`gather_node_depleted`** documents “when **`remainingAfterGather == 0`**” but sits on the unconditional success path. Consider a brief commented guard (e.g. `// if (remainingAfterGather == 0) { … emit gather_node_depleted … }`) so E9.M1 wiring mirrors the **`level_up`** pattern and avoids accidental unconditional emit. Low priority — comment text is already explicit.~~ **Done.** — **`gather_node_depleted`** hook comments nested in `if (remainingAfterGather == 0)`.

2. ~~**Backlog template wording (optional)** — E3M1-08 in [`E3M1-prototype-backlog.md`](../plans/E3M1-prototype-backlog.md) still lists “interact deny branch if applicable” in **In scope**; kickoff chose engine-only. A one-line “deny branch: out of scope (NEO-64 kickoff)” under E3M1-08 would reduce future misreads.~~ **Done.**

## Nits

- ~~Nit: README NEO-64 content is a bold paragraph under **Gather engine (NEO-62)** rather than a separate `### Gather telemetry hooks (NEO-64)` heading with its own anchor; module doc links to `#gather-engine-neo-62` — acceptable, optional heading if you want parity with NEO-49’s dedicated README subsection.~~ **Done.** — dedicated `### Gather telemetry hooks (NEO-64)` subsection; module doc link updated.

- Nit: **`gather_node_depleted`** comment block could repeat **`TODO(E9.M1): catalog emit`** on its own line (it does — same style as **`resource_gathered`**). No change needed.

## Verification

```bash
cd /home/don/neon-sprawl && dotnet test NeonSprawl.sln
# Passed! Failed: 0, Passed: 292
```

Manual: walk [`docs/manual-qa/NEO-64.md`](../manual-qa/NEO-64.md) code-review checklist; optional Bruno `bruno/neon-sprawl-server/interaction/` against a running server to confirm gather behavior unchanged.
