Merge pull request #98 from ViPro-Technologies/NEO-64-e3m1-resource-gathered-gather-node-depleted-telemetry-hooks
NEO-64: gather telemetry hook sites (resource_gathered / gather_node_depleted)pull/100/head
commit
fa7bcce78b
|
|
@ -7,7 +7,7 @@
|
|||
| **Module ID** | E3.M1 |
|
||||
| **Epic** | [Epic 3 — Crafting Economy](../epics/epic_03_crafting_economy.md) |
|
||||
| **Stage target** | Prototype |
|
||||
| **Status** | In Progress — Slice 2 backlog [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)–[NEO-64](https://linear.app/neon-sprawl/issue/NEO-64) ([E3M1-prototype-backlog](../../plans/E3M1-prototype-backlog.md); NEO-41 anchor below; see [dependency register](module_dependency_register.md)) |
|
||||
| **Status** | Ready — Slice 2 backlog [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)–[NEO-64](https://linear.app/neon-sprawl/issue/NEO-64) complete ([E3M1-prototype-backlog](../../plans/E3M1-prototype-backlog.md); NEO-41 anchor below; see [dependency register](module_dependency_register.md)) |
|
||||
| **Linear** | Label **`E3.M1`** per [decomposition README — Linear alignment](../README.md#linear-alignment) |
|
||||
|
||||
## Purpose
|
||||
|
|
@ -96,6 +96,8 @@ Epic 3 **Slice 2** — [gather nodes and outputs](../epics/epic_03_crafting_econ
|
|||
|
||||
**Gather engine (NEO-62):** **`GatherOperations.TryGather`** → **`GatherResult`** (`success`, `reasonCode`, `grantsApplied[]`, `remainingGathers`, optional `xpGrantSummary`) — inventory + **`salvage`** XP + depletion commit in one orchestrated path; server-internal until promoted to wire. Plan: [NEO-62 implementation plan](../../plans/NEO-62-implementation-plan.md); [server README — Gather engine (NEO-62)](../../../server/README.md#gather-engine-neo-62).
|
||||
|
||||
**Gather telemetry hooks (NEO-64):** comment-only **`resource_gathered`** + **`gather_node_depleted`** hook sites in **`GatherOperations.TryGather`** (`TODO(E9.M1)`; no ingest). **`gather_node_depleted`** fires on success when **`remainingGathers`** reaches **0** — not on pre-gather depletion deny. Plan: [NEO-64 implementation plan](../../plans/NEO-64-implementation-plan.md); [server README — Gather telemetry hooks (NEO-64)](../../../server/README.md#gather-telemetry-hooks-neo-64); manual QA: [`NEO-64`](../../manual-qa/NEO-64.md).
|
||||
|
||||
**Resource node definitions HTTP (NEO-60):** **`GET /game/world/resource-node-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`nodes`**) backed by **`IResourceNodeDefinitionRegistry`**; each row includes nested **`yield`** summary. Bruno `bruno/neon-sprawl-server/resource-node-definitions/`. Plan: [NEO-60 implementation plan](../../plans/NEO-60-implementation-plan.md).
|
||||
|
||||
**Resource node instance depletion store (NEO-61):** **`IResourceNodeInstanceStore`** + **`ResourceNodeInstanceOperations`** — lazy-init remaining gathers from catalog **`maxGathers`**, atomic decrement, **`node_depleted`** / **`unknown_node`** reason codes; Postgres **`V006`** or in-memory fallback. Plan: [NEO-61 implementation plan](../../plans/NEO-61-implementation-plan.md).
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not
|
|||
| E2.M1 | In Progress | **NEO-33 landed:** frozen prototype trio **`salvage`** / **`refine`** / **`intrusion`** in [`content/skills/prototype_skills.json`](../../../content/skills/prototype_skills.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) enforce schema, duplicate `id`, exact trio ids, and category coverage; designer note on **`allowedXpSourceKinds`** in [E2_M1](E2_M1_SkillDefinitionRegistry.md) + [`content/README.md`](../../../content/README.md). **NEO-34 landed:** fail-fast server load of `content/skills/*.json` at startup — `server/NeonSprawl.Server/Game/Skills/` ([NEO-34](../../plans/NEO-34-implementation-plan.md)); config + discovery in [server README — Skill catalog](../../../server/README.md#skill-catalog-contentskills-neo-34). **NEO-35 landed:** `ISkillDefinitionRegistry` / `SkillDefinitionRegistry` + DI ([NEO-35](../../plans/NEO-35-implementation-plan.md)). **NEO-36 landed:** versioned **`GET /game/world/skill-definitions`** ([NEO-36](../../plans/NEO-36-implementation-plan.md)) — `SkillDefinitionsWorldApi` + `SkillDefinitionsListDtos` in `server/NeonSprawl.Server/Game/Skills/`; Bruno `bruno/neon-sprawl-server/skill-definitions/`; manual QA [`NEO-36`](../../manual-qa/NEO-36.md). **E2.M2 consumer (NEO-38 landed):** grant path validates `skillId` + `sourceKind` vs catalog **`allowedXpSourceKinds`** on **`POST …/skill-progression`** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); see [server README — Skill progression grant](../../../server/README.md#skill-progression-grant-neo-38) and [E2_M2](E2_M2_XpAwardAndLevelEngine.md). | [NEO-33](../../plans/NEO-33-implementation-plan.md), [NEO-34](../../plans/NEO-34-implementation-plan.md), [NEO-35](../../plans/NEO-35-implementation-plan.md), [NEO-36](../../plans/NEO-36-implementation-plan.md); [E2_M1](E2_M1_SkillDefinitionRegistry.md); [module_dependency_register.md](module_dependency_register.md) **E2.M1 note**; `server/NeonSprawl.Server/Game/Skills/`; [`docs/manual-qa/NEO-36.md`](../../manual-qa/NEO-36.md); [server README — Skill definitions (NEO-36)](../../../server/README.md#skill-definitions-neo-36) |
|
||||
| E2.M3 | In Progress | **NEO-45 landed:** prototype **`salvage`** mastery catalog + CI gates (see [NEO-45 plan](../../plans/NEO-45-implementation-plan.md)). **NEO-46 landed:** fail-fast server load under `server/NeonSprawl.Server/Game/Mastery/` — `MasteryCatalogLoader`, `IMasteryCatalogRegistry`, cross-check vs `ISkillDefinitionRegistry`, Slice 4 + **`tierIndex`** 1..N gate; see [NEO-46 plan](../../plans/NEO-46-implementation-plan.md). **NEO-47 landed:** `PerkUnlockEngine`, `IPlayerPerkStateStore` + **`V004`**, level-up hook in skill XP grants; see [NEO-47 plan](../../plans/NEO-47-implementation-plan.md). **NEO-49 landed:** comment-only **`perk_unlock`** telemetry hook site in [`PerkUnlockEngine.TryUnlockPerks`](../../../server/NeonSprawl.Server/Game/Mastery/PerkUnlockEngine.cs) ([NEO-49 plan](../../plans/NEO-49-implementation-plan.md), [`NEO-49` manual QA](../../manual-qa/NEO-49.md)); [server README — Perk unlock telemetry (NEO-49)](../../../server/README.md#perk-unlock-engine-and-telemetry-hooks-neo-47-neo-49). **NEO-48 landed:** **`GET`/`POST /game/players/{id}/perk-state`** — `PerkStateApi` + DTOs in `Game/Mastery/` ([NEO-48](../../plans/NEO-48-implementation-plan.md), [`NEO-48` manual QA](../../manual-qa/NEO-48.md)); [server README — Perk state (NEO-48)](../../../server/README.md#perk-state-neo-48); Bruno `bruno/neon-sprawl-server/perk-state/`. | [NEO-45](../../plans/NEO-45-implementation-plan.md), [NEO-46](../../plans/NEO-46-implementation-plan.md), [NEO-47](../../plans/NEO-47-implementation-plan.md), [NEO-48](../../plans/NEO-48-implementation-plan.md), [NEO-49](../../plans/NEO-49-implementation-plan.md), [E2M3-pre-production-backlog](../../plans/E2M3-pre-production-backlog.md), [E2_M3](E2_M3_MasteryAndPerkUnlocks.md) |
|
||||
| E2.M2 | In Progress | **NEO-37 landed:** versioned **`GET /game/players/{id}/skill-progression`** ([NEO-37](../../plans/NEO-37-implementation-plan.md)) — read model for every registered skill; known-player gate via `IPositionStateStore`; [server README — Skill progression snapshot (NEO-37)](../../../server/README.md#skill-progression-snapshot-neo-37); manual QA [`NEO-37`](../../manual-qa/NEO-37.md). **NEO-38 landed:** **`POST`** same path — grant apply, persistence (`IPlayerSkillProgressionStore`, `V003` migration), structured denies + **`levelUps`** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); manual QA [`NEO-38`](../../manual-qa/NEO-38.md); Bruno `bruno/neon-sprawl-server/skill-progression/`; [server README — Skill progression grant (NEO-38)](../../../server/README.md#skill-progression-grant-neo-38). **NEO-39 landed:** data-driven `LevelCurve` content + schema + CI validation (`*_level_curve.json`) with fail-fast startup schema checks; progression GET/POST level resolution now uses `ISkillLevelCurve` content-backed thresholds ([NEO-39](../../plans/NEO-39-implementation-plan.md)); manual QA [`NEO-39`](../../manual-qa/NEO-39.md). **NEO-40 landed:** comment-only telemetry hook sites in [`SkillProgressionSnapshotApi.cs`](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotApi.cs) on **`POST …/skill-progression`** for future **`xp_grant`** / **`level_up`** ([NEO-40](../../plans/NEO-40-implementation-plan.md), [`NEO-40` manual QA](../../manual-qa/NEO-40.md)). **NEO-41 landed:** gather prototype — **`POST …/interact`** with **`kind: resource_node`** grants **`salvage`** + **`activity`** (10 XP) via [`SkillProgressionGrantOperations`](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantOperations.cs) ([NEO-41](../../plans/NEO-41-implementation-plan.md), [`NEO-41` manual QA](../../manual-qa/NEO-41.md)); [server README — Interaction](../../../server/README.md#interaction-neo-9). **NEO-42 landed (prep):** **`RefineActivitySkillXpGrant`** / **`RefineSkillXpConstants`** + deny-registry factory + tests — same NEO-38 grant stack; **E3.M2** must invoke on craft/refine success ([NEO-42](../../plans/NEO-42-implementation-plan.md), [`NEO-42` manual QA](../../manual-qa/NEO-42.md)); [server README — Craft / refine hook (NEO-42)](../../../server/README.md#craft--refine-hook--skill-xp-neo-42). **NEO-43 landed (prep):** **`MissionRewardSkillXpGrant`** / **`MissionRewardSkillXpConstants`** + deny-registry factory + tests — same NEO-38 grant stack with fixed **`sourceKind: mission_reward`**; **E7.M2** must invoke from quest hand-in ([NEO-43](../../plans/NEO-43-implementation-plan.md), [`NEO-43` manual QA](../../manual-qa/NEO-43.md)); [server README — Mission / quest reward (NEO-43)](../../../server/README.md#mission--quest-reward--skill-xp-neo-43). **Slice 3 still open:** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [epic_02 — E2.M2 + Slice 3](../epics/epic_02_skills_and_progression.md). | [NEO-37](../../plans/NEO-37-implementation-plan.md), [NEO-38](../../plans/NEO-38-implementation-plan.md), [NEO-39](../../plans/NEO-39-implementation-plan.md), [NEO-40](../../plans/NEO-40-implementation-plan.md), [NEO-41](../../plans/NEO-41-implementation-plan.md), [NEO-42](../../plans/NEO-42-implementation-plan.md), [NEO-43](../../plans/NEO-43-implementation-plan.md), [E2_M2](E2_M2_XpAwardAndLevelEngine.md); label **`E2.M2`** on NEO-37–NEO-41, NEO-42–NEO-44 |
|
||||
| E3.M1 | In Progress | **NEO-41 landed (prototype, superseded on interact by NEO-63):** inline XP on **`resource_node`** interact. **NEO-57–NEO-61** — content, registry, HTTP defs, depletion store (see prior alignment). **NEO-62 landed:** **`GatherOperations`** + **`GatherResult`**. **NEO-63 landed:** **`POST …/interact`** **`resource_node`** → gather engine; **four** **`PrototypeInteractableRegistry`** anchors; Bruno gather → inventory + depletion deny ([NEO-63](../../plans/NEO-63-implementation-plan.md), [`NEO-63` manual QA](../../manual-qa/NEO-63.md)); [server README — Gather via interact (NEO-63)](../../../server/README.md#gather-via-interact-neo-63). **Still planned:** telemetry hooks ([NEO-64](https://linear.app/neon-sprawl/issue/NEO-64)). | [NEO-41](../../plans/NEO-41-implementation-plan.md) … [NEO-63](../../plans/NEO-63-implementation-plan.md), [E3_M1](E3_M1_ResourceNodeAndGatherLoop.md); `server/NeonSprawl.Server/Game/Gathering/`, `Game/Interaction/` |
|
||||
| E3.M1 | Ready | **NEO-41 landed (prototype, superseded on interact by NEO-63):** inline XP on **`resource_node`** interact. **NEO-57–NEO-61** — content, registry, HTTP defs, depletion store (see prior alignment). **NEO-62 landed:** **`GatherOperations`** + **`GatherResult`**. **NEO-63 landed:** **`POST …/interact`** **`resource_node`** → gather engine; **four** **`PrototypeInteractableRegistry`** anchors; Bruno gather → inventory + depletion deny ([NEO-63](../../plans/NEO-63-implementation-plan.md), [`NEO-63` manual QA](../../manual-qa/NEO-63.md)); [server README — Gather via interact (NEO-63)](../../../server/README.md#gather-via-interact-neo-63). **NEO-64 landed:** comment-only **`resource_gathered`** / **`gather_node_depleted`** hook sites in **`GatherOperations.TryGather`** ([NEO-64](../../plans/NEO-64-implementation-plan.md), [`NEO-64` manual QA](../../manual-qa/NEO-64.md)); Epic 3 Slice 2 backlog **E3M1-01**–**E3M1-08** complete. | [NEO-41](../../plans/NEO-41-implementation-plan.md) … [NEO-64](../../plans/NEO-64-implementation-plan.md), [E3_M1](E3_M1_ResourceNodeAndGatherLoop.md); `server/NeonSprawl.Server/Game/Gathering/`, `Game/Interaction/` |
|
||||
| E3.M3 | In Progress | **NEO-50 landed:** frozen prototype six-item catalog in [`content/items/prototype_items.json`](../../../content/items/prototype_items.json); [`item-def.schema.json`](../../../content/schemas/item-def.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py). **NEO-51 landed:** fail-fast server load of `content/items/*_items.json` at startup — `server/NeonSprawl.Server/Game/Items/` ([NEO-51](../../plans/NEO-51-implementation-plan.md)); [server README — Item catalog](../../../server/README.md#item-catalog-contentitems-neo-51). **NEO-52 landed:** injectable **`IItemDefinitionRegistry`** + lookup tests ([NEO-52](../../plans/NEO-52-implementation-plan.md)). **NEO-53 landed:** **`GET /game/world/item-definitions`** — `ItemDefinitionsWorldApi` + DTOs in `Game/Items/` ([NEO-53](../../plans/NEO-53-implementation-plan.md), [`NEO-53` manual QA](../../manual-qa/NEO-53.md)); [server README — Item definitions (NEO-53)](../../../server/README.md#item-definitions-neo-53); Bruno `bruno/neon-sprawl-server/item-definitions/`. **NEO-54 landed:** **`IPlayerInventoryStore`** + **`PlayerInventoryOperations`** — 24 bag + 1 equipment slots, stack rules, `V005` migration ([NEO-54](../../plans/NEO-54-implementation-plan.md)). **NEO-55 landed:** **`GET`/`POST /game/players/{id}/inventory`** — `PlayerInventoryApi` + DTOs in `Game/Items/` ([NEO-55](../../plans/NEO-55-implementation-plan.md)); [server README — Player inventory (NEO-54 store, NEO-55 HTTP)](../../../server/README.md#player-inventory-neo-54-store-neo-55-http); Bruno `bruno/neon-sprawl-server/inventory/`. **NEO-56 landed:** comment-only **`item_created`** / **`inventory_transfer_denied`** telemetry hook sites in [`PlayerInventoryOperations`](../../../server/NeonSprawl.Server/Game/Items/PlayerInventoryOperations.cs) ([NEO-56](../../plans/NEO-56-implementation-plan.md), [`NEO-56` manual QA](../../manual-qa/NEO-56.md)); no E9.M1 ingest. | [NEO-50](../../plans/NEO-50-implementation-plan.md), [NEO-51](../../plans/NEO-51-implementation-plan.md), [NEO-52](../../plans/NEO-52-implementation-plan.md), [NEO-53](../../plans/NEO-53-implementation-plan.md), [NEO-54](../../plans/NEO-54-implementation-plan.md), [NEO-55](../../plans/NEO-55-implementation-plan.md), [NEO-56](../../plans/NEO-56-implementation-plan.md), [E3M3-prototype-backlog](../../plans/E3M3-prototype-backlog.md), [E3_M3](E3_M3_ItemizationAndInventorySchema.md) |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
|
|||
|
||||
| Module ID | Module Name | Depends On | Contract Needed | Phase Required | Status |
|
||||
|---|---|---|---|---|---|
|
||||
| E3.M1 | ResourceNodeAndGatherLoop | E1.M3, E2.M2 | ResourceNodeDef, GatherResult, ResourceYieldTable | Prototype | In Progress |
|
||||
| E3.M1 | ResourceNodeAndGatherLoop | E1.M3, E2.M2 | ResourceNodeDef, GatherResult, ResourceYieldTable | Prototype | Ready |
|
||||
| E3.M2 | RefinementAndRecipeExecution | E3.M1, E3.M3 | RecipeDef, CraftRequest, CraftResult | Prototype | Planned |
|
||||
| E3.M3 | ItemizationAndInventorySchema | None | ItemDef, ItemInstance, InventorySlot | Prototype | In Progress |
|
||||
| E3.M4 | SinkAndDurabilityLifecycle | E3.M3, E8.M3 | DurabilityState, ItemSinkEvent, RepairCostRule | Pre-production | Planned |
|
||||
| E3.M5 | EconomyBalancePolicy | E3.M4, E9.M2 | EconomyPolicy, PriceBandRule, FaucetSinkRatio | Pre-production | Planned |
|
||||
|
||||
**E3.M1 note:** Epic 3 **Slice 2** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57) → [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64); label **`E3.M1`**. See [E3M1-prototype-backlog.md](../../plans/E3M1-prototype-backlog.md), [E3_M1_ResourceNodeAndGatherLoop.md](E3_M1_ResourceNodeAndGatherLoop.md). **NEO-57** (content + CI), **NEO-58** (server fail-fast load), **NEO-59** (`IResourceNodeDefinitionRegistry` + DI), **NEO-60** (`GET /game/world/resource-node-definitions`), **NEO-61** (`IResourceNodeInstanceStore` + depletion operations, `V006`), **NEO-62** (`GatherOperations` + `GatherResult`), **NEO-63** (interact → gather engine + four registry anchors + Bruno) — E3M1-01–**E3M1-07** complete; register row **In Progress** until telemetry slice lands. Remaining Slice 2 work: **`resource_gathered`** / **`gather_node_depleted`** hook sites ([NEO-64](https://linear.app/neon-sprawl/issue/NEO-64)).
|
||||
**E3.M1 note:** Epic 3 **Slice 2** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57) → [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64); label **`E3.M1`**. See [E3M1-prototype-backlog.md](../../plans/E3M1-prototype-backlog.md), [E3_M1_ResourceNodeAndGatherLoop.md](E3_M1_ResourceNodeAndGatherLoop.md). **NEO-57** (content + CI), **NEO-58** (server fail-fast load), **NEO-59** (`IResourceNodeDefinitionRegistry` + DI), **NEO-60** (`GET /game/world/resource-node-definitions`), **NEO-61** (`IResourceNodeInstanceStore` + depletion operations, `V006`), **NEO-62** (`GatherOperations` + `GatherResult`), **NEO-63** (interact → gather engine + four registry anchors + Bruno), **NEO-64** (comment-only **`resource_gathered`** / **`gather_node_depleted`** telemetry hook sites in `GatherOperations.TryGather`) — E3M1-01–**E3M1-08** complete; register row **Ready**.
|
||||
|
||||
**E3.M3 note:** Epic 3 **Slice 1** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) → [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56); label **`E3.M3`**. See [E3M3-prototype-backlog.md](../../plans/E3M3-prototype-backlog.md), [E3_M3_ItemizationAndInventorySchema.md](E3_M3_ItemizationAndInventorySchema.md). **NEO-50** (content + CI), **NEO-51** (server fail-fast load), **NEO-52** (`IItemDefinitionRegistry` + DI), **NEO-53** (`GET /game/world/item-definitions`), **NEO-54** (inventory store + stack/slot rules engine), **NEO-55** (`GET`/`POST /game/players/{id}/inventory`), and **NEO-56** (comment-only `item_created` / `inventory_transfer_denied` telemetry hook sites in `PlayerInventoryOperations`) — Epic 3 Slice 1 backlog **E3M3-01**–**E3M3-07** complete; register row **In Progress** until Slice 2+; later slices update the alignment table as they land.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# Manual QA — NEO-64 (gather telemetry hook sites)
|
||||
|
||||
Reference: [implementation plan](../plans/NEO-64-implementation-plan.md), [NEO-62 implementation plan](../plans/NEO-62-implementation-plan.md) (gather engine), [NEO-63 implementation plan](../plans/NEO-63-implementation-plan.md) (interact wiring).
|
||||
|
||||
## Preconditions
|
||||
|
||||
- Run `NeonSprawl.Server` (`dotnet run` from `server/NeonSprawl.Server`; default `http://localhost:5253`) if exercising Bruno; not required for code review only.
|
||||
- This story adds **comments only**; gather behavior matches NEO-62/NEO-63.
|
||||
|
||||
## Code review check
|
||||
|
||||
- [ ] Open `server/NeonSprawl.Server/Game/Gathering/GatherOperations.cs`.
|
||||
- [ ] Confirm **`resource_gathered`** comment block on the successful gather path (after depletion commit, before **`GatherResult`** return).
|
||||
- [ ] Confirm **`gather_node_depleted`** comment block is inside `if (remainingAfterGather == 0)` (not on pre-gather **`node_depleted`** deny).
|
||||
- [ ] Confirm **`TODO(E9.M1)`** markers and planned payload fields; **no** new `ILogger` or metrics calls.
|
||||
- [ ] Confirm **no** duplicate hook comments in `InteractionApi.cs`.
|
||||
- [ ] Open `GatherResult.cs` — summary cross-references NEO-64 hook anchor in **`TryGather`**.
|
||||
- [ ] Open [server README — Gather telemetry hooks (NEO-64)](../../server/README.md#gather-telemetry-hooks-neo-64) — NEO-64 subsection present.
|
||||
|
||||
## Regression (optional)
|
||||
|
||||
```bash
|
||||
cd server && dotnet test NeonSprawl.sln
|
||||
```
|
||||
|
||||
Expect all tests pass (comments-only change).
|
||||
|
||||
## Bruno (optional sanity)
|
||||
|
||||
From `bruno/neon-sprawl-server/interaction/` against a running server:
|
||||
|
||||
- **Post interact prototype resource node gather** — `allowed: true` unchanged.
|
||||
- **Post interact gather then get inventory** — scrap quantity increases unchanged.
|
||||
- **Post interact depleted node deny** — `allowed: false`, `reasonCode: node_depleted` unchanged.
|
||||
|
|
@ -234,18 +234,21 @@ Working backlog for **Epic 3 — Slice 2** ([gather nodes and outputs](../decomp
|
|||
|
||||
**In scope**
|
||||
|
||||
- Hook placement in gather engine (and interact deny branch if applicable).
|
||||
- Hook placement in gather engine (and interact deny branch if applicable — **out of scope per NEO-64 kickoff:** engine-only; no **`InteractionApi`** hooks).
|
||||
- `TODO(E9.M1)` comments; no production logging.
|
||||
- README + module doc pointer.
|
||||
|
||||
**Out of scope**
|
||||
|
||||
- Telemetry ingest, dashboards.
|
||||
- Interact deny-branch hook sites (**NEO-64 kickoff** — pre-gather **`node_depleted`** deny uses interact **`reasonCode`** only).
|
||||
|
||||
**Acceptance criteria**
|
||||
|
||||
- [ ] Hook sites documented in code and `server/README.md`.
|
||||
- [ ] Matches Epic 3 Slice 2 telemetry vocabulary in [epic_03](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-2).
|
||||
- [x] Hook sites documented in code and `server/README.md`.
|
||||
- [x] Matches Epic 3 Slice 2 telemetry vocabulary in [epic_03](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-2).
|
||||
|
||||
**Landed:** [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64) — comment-only **`resource_gathered`** / **`gather_node_depleted`** hooks in **`GatherOperations.TryGather`**; Epic 3 Slice 2 / E3.M1 backlog complete.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
# NEO-64 — Implementation plan
|
||||
|
||||
## Story reference
|
||||
|
||||
| Field | Value |
|
||||
|--------|--------|
|
||||
| **Key** | NEO-64 |
|
||||
| **Title** | E3.M1: resource_gathered / gather_node_depleted telemetry hook sites |
|
||||
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-64/e3m1-resource-gathered-gather-node-depleted-telemetry-hook-sites |
|
||||
| **Module** | [E3.M1 — ResourceNodeAndGatherLoop](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) · Epic 3 Slice 2 · backlog **E3M1-08** |
|
||||
| **Branch** | `NEO-64-e3m1-resource-gathered-gather-node-depleted-telemetry-hooks` |
|
||||
| **Blocked by** | [NEO-62](https://linear.app/neon-sprawl/issue/NEO-62) — `GatherOperations` + `GatherResult` (**Done** on `main`); [NEO-63](https://linear.app/neon-sprawl/issue/NEO-63) — interact gather wiring (**Done** on `main`). |
|
||||
| **Pattern** | [NEO-49](https://linear.app/neon-sprawl/issue/NEO-49) / [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56) — comment-only engine anchors; [NEO-30](https://linear.app/neon-sprawl/issue/NEO-30) — event name + `TODO(E9.M1)` |
|
||||
|
||||
## Kickoff clarifications
|
||||
|
||||
| Topic | Question | Agent recommendation | Answer |
|
||||
|--------|----------|----------------------|--------|
|
||||
| **Runtime behavior** | Comments-only vs dev `ILogger`? | **Comments-only** — `TODO(E9.M1)`; no production logging (NEO-49/56 precedent). | **Adopted** (repo precedent; not asked). |
|
||||
| **Hook anchor** | Engine only vs engine + `InteractionApi`? | **Engine only** in **`GatherOperations.TryGather`** — HTTP calls engine; no duplicate API-layer hooks (NEO-56 precedent). | **Adopted** (repo precedent; not asked). |
|
||||
| **`gather_node_depleted` timing** | Success transition vs also pre-gather deny? | **Success transition only** — hook when a successful gather drives **`remainingGathers`** to **0** after depletion commit; not on pre-gather **`node_depleted`** deny. | **User:** success transition only. |
|
||||
| **Manual QA doc** | `docs/manual-qa/NEO-64.md`? | **Yes** — NEO-49/56 verification pattern (comments + no behavior change). | **Adopted** (repo convention). |
|
||||
|
||||
## Goal, scope, and out-of-scope
|
||||
|
||||
**Goal:** Document and place **comment-only hook sites** on the server-authoritative gather path for future E9.M1 catalog events **`resource_gathered`** and **`gather_node_depleted`**. Align with Epic 3 Slice 2 telemetry vocabulary ([epic_03 — Slice 2](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-2)).
|
||||
|
||||
**In scope (from Linear + [E3M1-08](E3M1-prototype-backlog.md#e3m1-08--resource_gathered--gather_node_depleted-telemetry-hook-sites)):**
|
||||
|
||||
- Hook placement in **`GatherOperations.TryGather`** with **`TODO(E9.M1)`** comments.
|
||||
- **`server/README.md`** gather subsection + [E3_M1 module](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) hook note.
|
||||
- **`docs/manual-qa/NEO-64.md`** — verify comments + no behavior/API change.
|
||||
- E3M1 backlog + alignment register updates when implementation completes.
|
||||
|
||||
**Out of scope (from Linear):**
|
||||
|
||||
- Telemetry ingest, dashboards (**E9.M1**).
|
||||
- `ILogger` / metrics / dev-only log lines.
|
||||
- Duplicate hook comments in **`InteractionApi`** (NEO-56 engine-only precedent).
|
||||
- **`gather_node_depleted`** on pre-gather depletion deny or interact deny passthrough (kickoff decision).
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [x] Hook sites documented in code and `server/README.md`.
|
||||
- [x] Matches Epic 3 Slice 2 telemetry vocabulary (`resource_gathered`, `gather_node_depleted`).
|
||||
|
||||
## Technical approach
|
||||
|
||||
1. **Authoritative surface:** **`GatherOperations.TryGather`** — all gather side effects (inventory → XP → depletion) funnel here; **`InteractionApi`** delegates **`resource_node`** interact without duplicating hooks.
|
||||
|
||||
2. **Hook site A — `resource_gathered`:** Immediately before the successful **`GatherResult`** return (after depletion commit succeeds):
|
||||
- Comment block names future E9.M1 event **`resource_gathered`**.
|
||||
- **`TODO(E9.M1): catalog emit`** (NEO-49 / NEO-56 style).
|
||||
- Planned payload fields: `playerId`, `interactableId` (node instance key), `itemId`, `quantity` (from **`GatherGrantApplied`**), `skillId` + `xpAmount` + `sourceKind` (from **`GatherXpGrantSummary`**), `remainingGathers` after commit.
|
||||
|
||||
3. **Hook site B — `gather_node_depleted`:** On the same success path, inside `if (remainingAfterGather == 0)` after depletion commit (NEO-40 `level_up` conditional pattern):
|
||||
- Comment block names future E9.M1 event **`gather_node_depleted`**.
|
||||
- **`TODO(E9.M1): catalog emit`** — emit **once** when capacity transitions to zero via successful gather (not on pre-gather **`node_depleted`** deny).
|
||||
- Planned payload fields: `interactableId`, optional `playerId` (last gatherer), `maxGathers` from definition if needed at E9.M1 wiring time.
|
||||
|
||||
4. **Cross-references (minimal):**
|
||||
- Update **`GatherResult.cs`** xml summary → hook sites in **`TryGather`**.
|
||||
- **`GatherOperations`** class summary → NEO-64 telemetry hook anchor.
|
||||
- **`server/README.md`:** new **NEO-64 telemetry hooks** subsection under gather engine (after NEO-62 block).
|
||||
- **`E3_M1_ResourceNodeAndGatherLoop.md`:** NEO-64 landed line when complete.
|
||||
- **`documentation_and_implementation_alignment.md`** + **`module_dependency_register.md`:** E3.M1 row — E3M1-08 complete; module **Ready** when all Slice 2 backlog lands.
|
||||
- **`E3M1-prototype-backlog.md`:** E3M1-08 checkboxes + landed note.
|
||||
|
||||
5. **NEO-56 interaction:** Successful gather already triggers **`item_created`** inside **`PlayerInventoryOperations.TryAddStack`**; **`resource_gathered`** is the gather-funnel catalog event at the orchestration layer (analogous to cast accept vs ability-specific events).
|
||||
|
||||
## Files to add
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `docs/manual-qa/NEO-64.md` | Manual QA: verify comment blocks + no behavior/API change. |
|
||||
| `docs/plans/NEO-64-implementation-plan.md` | This plan. |
|
||||
|
||||
## Files to modify
|
||||
|
||||
| Path | Rationale |
|
||||
|------|-----------|
|
||||
| `server/NeonSprawl.Server/Game/Gathering/GatherOperations.cs` | **NEO-64:** comment-only **`resource_gathered`** + **`gather_node_depleted`** hook sites on success path. |
|
||||
| `server/NeonSprawl.Server/Game/Gathering/GatherResult.cs` | Cross-reference hook anchor in type summary (no behavior change). |
|
||||
| `server/README.md` | Document gather telemetry hook placement (NEO-64). |
|
||||
| `docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md` | Implementation snapshot: NEO-64 telemetry hooks landed. |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E3.M1 row: NEO-64 hook sites. |
|
||||
| `docs/decomposition/modules/module_dependency_register.md` | E3.M1 note — E3M1-08 complete; module status **Ready**. |
|
||||
| `docs/plans/E3M1-prototype-backlog.md` | E3M1-08 checkboxes + landed note. |
|
||||
|
||||
## Tests
|
||||
|
||||
| Test file | What it covers |
|
||||
|-----------|----------------|
|
||||
| *(none added or changed)* | Comments-only change; no behavior or wire contract change. Regression: `dotnet test NeonSprawl.sln` (existing **`GatherOperationsTests`** + **`InteractionResourceNodeGatherIntegrationTests`** unchanged). Manual verification: **`docs/manual-qa/NEO-64.md`**. |
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
| Question / risk | Agent recommendation | Status |
|
||||
|-----------------|----------------------|--------|
|
||||
| **Overlap with NEO-56 `item_created`** | Keep both — **`item_created`** at inventory layer; **`resource_gathered`** at gather orchestration (E9.M1 can correlate or sample). | **adopted** |
|
||||
| **Pre-gather depletion deny telemetry** | No **`gather_node_depleted`** hook on deny; interact **`node_depleted`** **`reasonCode`** suffices until E9.M1 defines a separate deny funnel event. | **adopted** (kickoff) |
|
||||
|
||||
None blocking.
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# 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.
|
||||
|
|
@ -7,6 +7,7 @@ namespace NeonSprawl.Server.Game.Gathering;
|
|||
/// <summary>
|
||||
/// Orchestrates gather side effects: capacity pre-check, inventory grant, skill XP, depletion commit (NEO-62).
|
||||
/// Wired from <see cref="NeonSprawl.Server.Game.Interaction.InteractionApi"/> on <c>resource_node</c> interact (NEO-63).
|
||||
/// NEO-64 telemetry hook sites: <see cref="TryGather"/> (<c>resource_gathered</c>, <c>gather_node_depleted</c>).
|
||||
/// </summary>
|
||||
public static class GatherOperations
|
||||
{
|
||||
|
|
@ -111,11 +112,27 @@ public static class GatherOperations
|
|||
depletedRemaining);
|
||||
}
|
||||
|
||||
var remainingAfterGather = depletionOutcome.Snapshot!.Value.RemainingGathers;
|
||||
|
||||
// --- Telemetry hook site (NEO-64): future E9.M1 catalog event `resource_gathered` ---
|
||||
// TODO(E9.M1): catalog emit — on every successful gather (inventory + XP + depletion committed).
|
||||
// Planned payload fields: playerId, interactableId (node key), itemId, quantity (GatherGrantApplied),
|
||||
// skillId, xpAmount, sourceKind (GatherXpGrantSummary), remainingGathers after commit.
|
||||
// No ingest or ILogger here (comments-only). InteractionApi delegates here (no duplicate hooks).
|
||||
|
||||
if (remainingAfterGather == 0)
|
||||
{
|
||||
// --- Telemetry hook site (NEO-64): future E9.M1 catalog event `gather_node_depleted` ---
|
||||
// TODO(E9.M1): catalog emit — once when this successful gather drives remainingGathers to 0
|
||||
// (not on pre-gather node_depleted deny). Planned payload: interactableId, optional playerId
|
||||
// (last gatherer), maxGathers from definition when wiring E9.M1.
|
||||
}
|
||||
|
||||
return new GatherResult(
|
||||
Success: true,
|
||||
ReasonCode: null,
|
||||
GrantsApplied: [new GatherGrantApplied(yield.ItemId, yield.Quantity)],
|
||||
RemainingGathers: depletionOutcome.Snapshot!.Value.RemainingGathers,
|
||||
RemainingGathers: remainingAfterGather,
|
||||
XpGrantSummary: new GatherXpGrantSummary(
|
||||
GatherSkillXpConstants.SalvageSkillId,
|
||||
GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ namespace NeonSprawl.Server.Game.Gathering;
|
|||
/// <summary>
|
||||
/// Server-internal gather resolution envelope (NEO-62). May promote to wire JSON when the client needs explicit
|
||||
/// gather payloads; until then prototype verification uses inventory GET + interact response (NEO-63).
|
||||
/// NEO-64 telemetry hook sites live in <see cref="GatherOperations.TryGather"/>.
|
||||
/// </summary>
|
||||
/// <param name="GrantsApplied">Item grants on success; empty when denied.</param>
|
||||
/// <param name="RemainingGathers">Authoritative capacity after success, or current capacity on depletion deny.</param>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@ World-wide **remaining gathers** per **`interactableId`** live in **`Game/Gather
|
|||
|
||||
**Interact wiring** (replace NEO-41 inline XP, map denies to **`InteractionResponse`**) is **[NEO-63](#gather-via-interact-neo-63)**; plan: [NEO-63 implementation plan](../../docs/plans/NEO-63-implementation-plan.md).
|
||||
|
||||
### Gather telemetry hooks (NEO-64)
|
||||
|
||||
Comment-only hook sites in **`GatherOperations.TryGather`** for future E9.M1 catalog events — **`resource_gathered`** on every successful gather (inventory + XP + depletion committed); **`gather_node_depleted`** when that success drives **`remainingGathers`** to **0** (not on pre-gather **`node_depleted`** deny). **`TODO(E9.M1)`** — no production ingest. Successful gathers also pass through **`PlayerInventoryOperations`** (**`item_created`** hook, [NEO-56](#player-inventory-neo-54-store-neo-55-http)). HTTP **`POST …/interact`** calls the engine only (no duplicate API-layer hooks). Manual QA: [`docs/manual-qa/NEO-64.md`](../../docs/manual-qa/NEO-64.md); plan: [NEO-64 implementation plan](../../docs/plans/NEO-64-implementation-plan.md).
|
||||
|
||||
## Item definitions (NEO-53)
|
||||
|
||||
**`GET /game/world/item-definitions`** returns a versioned JSON body (`schemaVersion` **1**, **`items`**) backed by **`IItemDefinitionRegistry`** — the same prototype rows loaded at startup (no second source of truth). Plan: [NEO-53 implementation plan](../../docs/plans/NEO-53-implementation-plan.md); manual QA: [`docs/manual-qa/NEO-53.md`](../../docs/manual-qa/NEO-53.md); Bruno: `bruno/neon-sprawl-server/item-definitions/`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue