From 194078dd3b3413e0c0228555cd22605296d130d2 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:22:10 -0400 Subject: [PATCH 1/4] NEO-71: add implementation plan for craft telemetry hook sites --- docs/plans/NEO-71-implementation-plan.md | 115 +++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/plans/NEO-71-implementation-plan.md diff --git a/docs/plans/NEO-71-implementation-plan.md b/docs/plans/NEO-71-implementation-plan.md new file mode 100644 index 0000000..b29baaa --- /dev/null +++ b/docs/plans/NEO-71-implementation-plan.md @@ -0,0 +1,115 @@ +# NEO-71 — Implementation plan + +## Story reference + +| Field | Value | +|--------|--------| +| **Key** | NEO-71 | +| **Title** | E3.M2: item_crafted / craft_failed telemetry hook sites | +| **Linear** | https://linear.app/neon-sprawl/issue/NEO-71/e3m2-item-crafted-craft-failed-telemetry-hook-sites | +| **Module** | [E3.M2 — RefinementAndRecipeExecution](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) · Epic 3 Slice 3 · backlog **E3M2-07** | +| **Branch** | `NEO-71-item-crafted-craft-failed-telemetry-hooks` | +| **Blocked by** | [NEO-69](https://linear.app/neon-sprawl/issue/NEO-69) — `CraftOperations.TryCraft` + `CraftResult` (**Done** on `main`); [NEO-70](https://linear.app/neon-sprawl/issue/NEO-70) — craft HTTP + Bruno spine (**Done** on `main`; may parallel). | +| **Pattern** | [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64) — gather telemetry hooks in engine; [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56) — centralized **`Deny`** hook for structured denies | +| **Client counterpart** | None — comment-only server engine story; craft UI is [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74) (Slice 5). | + +## Kickoff clarifications + +| Topic | Question | Agent recommendation | Answer | +|--------|----------|----------------------|--------| +| **Manual QA doc** | Add `docs/manual-qa/NEO-71.md`? User asked whether anything is testable in the UI. | **No** — no Godot/UI surface in this story (comment-only server hooks; craft UI is NEO-74). Verify via code review + `dotnet test` + optional Bruno craft regression (NEO-70 waiver for E3.M2 server-only). | **User:** No UI to test; **no manual QA doc** (NEO-70 pattern). | +| **Runtime behavior** | Comments-only vs dev `ILogger`? | **Comments-only** — `TODO(E9.M1)`; no production logging (NEO-49/56/64 precedent). | **Adopted** (repo precedent). | +| **Hook anchor** | Engine only vs engine + `PlayerCraftApi`? | **Engine only** in **`CraftOperations.TryCraft`** — HTTP delegates to engine; no duplicate API-layer hooks (NEO-56/64 precedent; Linear out-of-scope). | **Adopted** (repo precedent). | +| **`craft_failed` placement** | Central **`Deny`** vs inline at each early return? | **Central `Deny`** — one hook site covers all **`CraftReasonCodes`** (NEO-56 **`PlayerInventoryOperations.Deny`** pattern). | **Adopted** (repo precedent). | +| **`craft_failed` scope** | All structured denies vs pre-commit rule denies only? | **All** **`Deny`** outcomes including store-missing and post-mutation rollback paths — stable **`reasonCode`** on every craft failure (NEO-56 all-deny precedent). | **Adopted** (repo precedent). | +| **`time-to-first-craft`** | Separate hook in prototype? | **No separate hook** — derived metric from **`item_crafted`** + session context at E9.M1 (E3M2-07 backlog note). | **Adopted** (backlog default). | + +## Goal, scope, and out-of-scope + +**Goal:** Document and place **comment-only hook sites** on the server-authoritative craft path for future E9.M1 catalog events **`item_crafted`** and **`craft_failed`**. Align with Epic 3 Slice 3 telemetry vocabulary ([epic_03 — Slice 3](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-3)). + +**In scope (from Linear + [E3M2-07](E3M2-prototype-backlog.md#e3m2-07--item_crafted--craft_failed-telemetry-hook-sites)):** + +- Hook placement in **`CraftOperations.TryCraft`** (success + structured deny via **`Deny`**). +- **`TODO(E9.M1)`** comments; no production logging. +- **`server/README.md`** craft telemetry subsection; [E3_M2 module](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) hook note. +- E3M2 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 **`PlayerCraftApi`** (engine-only precedent). +- Godot craft UI — **[NEO-74](https://linear.app/neon-sprawl/issue/NEO-74)**. +- **`docs/manual-qa/NEO-71.md`** (kickoff decision — no player-visible or UI verification). + +## Acceptance criteria checklist + +- [ ] Hook sites documented in code and `server/README.md`. +- [ ] Matches Epic 3 Slice 3 telemetry vocabulary (`item_crafted`, `craft_failed` with reason; **`time-to-first-craft`** noted as derived, no separate hook). + +## Technical approach + +1. **Authoritative surface:** **`CraftOperations.TryCraft`** — all craft side effects (recipe resolve → inventory pre-flight → input removal → output grant → refine XP) funnel here; **`PlayerCraftApi`** delegates without duplicating hooks. + +2. **Hook site A — `item_crafted`:** Immediately before the successful **`CraftResult`** return (after refine XP grant succeeds): + - Comment block names future E9.M1 event **`item_crafted`**. + - **`TODO(E9.M1): catalog emit`** (NEO-64 / NEO-56 style). + - Planned payload fields: `playerId`, `recipeId`, `quantity` (batch), `inputsConsumed`, `outputsGranted`, `xpGrantSummary` (`skillId`, `amount`, `sourceKind`). + +3. **Hook site B — `craft_failed`:** In private **`Deny(string reasonCode)`** (centralized, NEO-56 pattern): + - Comment block names future E9.M1 event **`craft_failed`**. + - **`TODO(E9.M1): catalog emit`** — on every structured craft deny with stable **`reasonCode`** from **`CraftReasonCodes`**. + - Planned payload fields: `playerId`, `recipeId`, `quantity`, `reasonCode`; note **`playerId` / `recipeId` / `quantity`** are in enclosing **`TryCraft`** scope (same pattern as NEO-56 **`Deny`** noting fields at call sites). + +4. **Cross-references (minimal):** + - Update **`CraftOperations`** class summary → NEO-71 telemetry hook anchor. + - **`CraftResult.cs`** summary already points to **`TryCraft`** — confirm wording after hooks land. + - **`server/README.md`:** new **NEO-71 craft telemetry hooks** subsection under craft engine (replace “Telemetry hook sites are **NEO-71**” deferral with landed doc). + - **`E3_M2_RefinementAndRecipeExecution.md`:** NEO-71 landed line; remove “Telemetry hooks deferred to NEO-71”. + - **`documentation_and_implementation_alignment.md`** + **`module_dependency_register.md`:** E3.M2 row — E3M2-07 complete; module **Ready** when all Slice 3 backlog lands. + - **`E3M2-prototype-backlog.md`:** E3M2-07 checkboxes + landed note. + +5. **NEO-56 interaction:** Successful craft output grants already trigger **`item_created`** inside **`PlayerInventoryOperations.TryAddStack`**; **`item_crafted`** is the craft-funnel catalog event at the orchestration layer (analogous to **`resource_gathered`** + **`item_created`** on gather). + +6. **`time-to-first-craft`:** Document in README that E9.M1 derives this from first **`item_crafted`** per player/session — no prototype hook. + +## Files to add + +| Path | Purpose | +|------|---------| +| `docs/plans/NEO-71-implementation-plan.md` | This plan. | + +## Files to modify + +| Path | Rationale | +|------|-----------| +| `server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs` | **NEO-71:** comment-only **`item_crafted`** hook on success path + **`craft_failed`** hook in **`Deny`**. | +| `server/NeonSprawl.Server/Game/Crafting/CraftResult.cs` | Confirm type summary cross-references NEO-71 hook anchor (wording tweak if needed). | +| `server/README.md` | Document craft telemetry hook placement (NEO-71); remove deferral note. | +| `docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md` | Implementation snapshot: NEO-71 telemetry hooks landed; Slice 3 telemetry vocabulary. | +| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E3.M2 row: NEO-71 hook sites. | +| `docs/decomposition/modules/module_dependency_register.md` | E3.M2 note — E3M2-07 complete; module status **Ready**. | +| `docs/plans/E3M2-prototype-backlog.md` | E3M2-07 checkboxes + landed note; Slice 3 server spine complete. | + +## 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 **`CraftOperationsTests`** + **`PlayerCraftApiTests`** unchanged). Optional Bruno: `bruno/neon-sprawl-server/craft/` spine against running dev server (NEO-70; behavior unchanged). | + +## Open questions / risks + +| Question / risk | Agent recommendation | Status | +|-----------------|----------------------|--------| +| **Overlap with NEO-56 `item_created`** | Keep both — **`item_created`** at inventory layer; **`item_crafted`** at craft orchestration (E9.M1 can correlate). | **adopted** | +| **No UI / manual QA doc** | User confirmed no Godot verification; README + code review + existing test suite sufficient (NEO-70 E3.M2 server-only pattern). | **adopted** (kickoff) | +| **Slice 3 module closure** | Landing NEO-71 completes E3.M2 prototype **server** backlog; client loop remains NEO-72–NEO-75 (Slice 5). | **adopted** | + +## Decisions (kickoff) + +- **No `docs/manual-qa/NEO-71.md`** — no UI surface; server comment-only (user confirmed via UI question). +- **Engine-only hooks** in **`CraftOperations`**; no **`PlayerCraftApi`** duplicates. +- **Central `Deny` for `craft_failed`** — all **`CraftReasonCodes`** paths. +- **Comments + `TODO(E9.M1)` only** — no ingest or logging. +- **`time-to-first-craft`** — derived at E9.M1; no separate prototype hook. From 2d4751021b4b600694720183b2bf7b7fcb0242ee Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:23:35 -0400 Subject: [PATCH 2/4] NEO-71: add craft telemetry hook comment sites and docs Place item_crafted and craft_failed TODO(E9.M1) anchors in CraftOperations.TryCraft; document in server README and mark E3.M2 Slice 3 server backlog complete. --- .../E3_M2_RefinementAndRecipeExecution.md | 6 +++--- ...umentation_and_implementation_alignment.md | 2 +- .../modules/module_dependency_register.md | 4 ++-- docs/plans/E3M2-prototype-backlog.md | 6 ++++-- docs/plans/NEO-71-implementation-plan.md | 4 ++-- .../Game/Crafting/CraftOperations.cs | 19 ++++++++++++++++--- server/README.md | 6 +++++- 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md b/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md index 9f2f90a..966e69e 100644 --- a/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md +++ b/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md @@ -7,13 +7,13 @@ | **Module ID** | E3.M2 | | **Epic** | [Epic 3 — Crafting Economy](../epics/epic_03_crafting_economy.md) | | **Stage target** | Prototype | -| **Status** | In Progress — Slice 3 backlog [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md): E3M2-01 [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) landed; E3M2-02–E3M2-07 open (see [dependency register](module_dependency_register.md)) | +| **Status** | Ready — Slice 3 server backlog [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md): E3M2-01 [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) through E3M2-07 [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71) landed (see [dependency register](module_dependency_register.md)); client craft UI [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74) is Slice 5 | ## Implementation snapshot **Prep (NEO-42):** Server ships **`RefineActivitySkillXpGrant.GrantOnSuccessfulCraftOrRefine`** + **`RefineSkillXpConstants`** under `server/NeonSprawl.Server/Game/Skills/` — delegates to **`SkillProgressionGrantOperations.TryApplyGrant`** with **`refine`** + **`activity`** (prototype **10** XP). **`CraftOperations.TryCraft`** (NEO-69) invokes the same grant path on craft success ([NEO-42 implementation plan](../../plans/NEO-42-implementation-plan.md); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69)). -**E3M2-01 (NEO-65):** Frozen eight-recipe catalog in [`content/recipes/prototype_recipes.json`](../../../content/recipes/prototype_recipes.json); [`recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) + [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) Slice 3 gates ([NEO-65 plan](../../plans/NEO-65-implementation-plan.md)). **E3M2-02 (NEO-66):** fail-fast server load under `server/NeonSprawl.Server/Game/Crafting/` — `RecipeDefinitionCatalogLoader`, `RecipeDefinitionCatalog`, cross-check vs item + skill catalogs, Slice 3 gate; [NEO-66 plan](../../plans/NEO-66-implementation-plan.md); [server README — Recipe catalog](../../../server/README.md#recipe-catalog-contentrecipes-neo-66). **E3M2-03 (NEO-67):** injectable **`IRecipeDefinitionRegistry`** + DI; [NEO-67 plan](../../plans/NEO-67-implementation-plan.md). **E3M2-04 (NEO-68):** **`GET /game/world/recipe-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`recipes`**) backed by **`IRecipeDefinitionRegistry`**; Bruno `bruno/neon-sprawl-server/recipe-definitions/`. Plan: [NEO-68 implementation plan](../../plans/NEO-68-implementation-plan.md). **E3M2-05 (NEO-69):** **`CraftOperations.TryCraft`** + **`CraftResult`** — pre-flight inputs/outputs, inventory mutation, refine XP; [NEO-69 plan](../../plans/NEO-69-implementation-plan.md); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69-and-craft-http-neo-70). **E3M2-06 (NEO-70):** **`POST /game/players/{id}/craft`** — wire **`CraftResponse`** backed by **`CraftOperations`**; Bruno gather→refine→make spine; [NEO-70 plan](../../plans/NEO-70-implementation-plan.md). +**E3M2-01 (NEO-65):** Frozen eight-recipe catalog in [`content/recipes/prototype_recipes.json`](../../../content/recipes/prototype_recipes.json); [`recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) + [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) Slice 3 gates ([NEO-65 plan](../../plans/NEO-65-implementation-plan.md)). **E3M2-02 (NEO-66):** fail-fast server load under `server/NeonSprawl.Server/Game/Crafting/` — `RecipeDefinitionCatalogLoader`, `RecipeDefinitionCatalog`, cross-check vs item + skill catalogs, Slice 3 gate; [NEO-66 plan](../../plans/NEO-66-implementation-plan.md); [server README — Recipe catalog](../../../server/README.md#recipe-catalog-contentrecipes-neo-66). **E3M2-03 (NEO-67):** injectable **`IRecipeDefinitionRegistry`** + DI; [NEO-67 plan](../../plans/NEO-67-implementation-plan.md). **E3M2-04 (NEO-68):** **`GET /game/world/recipe-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`recipes`**) backed by **`IRecipeDefinitionRegistry`**; Bruno `bruno/neon-sprawl-server/recipe-definitions/`. Plan: [NEO-68 implementation plan](../../plans/NEO-68-implementation-plan.md). **E3M2-05 (NEO-69):** **`CraftOperations.TryCraft`** + **`CraftResult`** — pre-flight inputs/outputs, inventory mutation, refine XP; [NEO-69 plan](../../plans/NEO-69-implementation-plan.md); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69-and-craft-http-neo-70). **E3M2-06 (NEO-70):** **`POST /game/players/{id}/craft`** — wire **`CraftResponse`** backed by **`CraftOperations`**; Bruno gather→refine→make spine; [NEO-70 plan](../../plans/NEO-70-implementation-plan.md). **E3M2-07 (NEO-71):** comment-only **`item_crafted`** / **`craft_failed`** telemetry hook sites in **`CraftOperations.TryCraft`**; [NEO-71 plan](../../plans/NEO-71-implementation-plan.md); [server README — Craft telemetry hooks (NEO-71)](../../../server/README.md#craft-telemetry-hooks-neo-71). ### `CraftResult` / wire `CraftResponse` (NEO-69 engine, NEO-70 HTTP) @@ -25,7 +25,7 @@ | **`OutputsGranted`** | Scaled `{ itemId, quantity }` rows added on success. | | **`XpGrantSummary`** | Compact refine XP summary (`refine`, **10**, `activity`) on success. | -Promoted to wire JSON as **`CraftResponse`** on **`POST /game/players/{id}/craft`** (NEO-70). Telemetry hooks deferred to NEO-71. +Promoted to wire JSON as **`CraftResponse`** on **`POST /game/players/{id}/craft`** (NEO-70). Telemetry hooks: **`item_crafted`** / **`craft_failed`** in **`CraftOperations.TryCraft`** (NEO-71). ## Purpose diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md index d8a498e..9d2d226 100644 --- a/docs/decomposition/modules/documentation_and_implementation_alignment.md +++ b/docs/decomposition/modules/documentation_and_implementation_alignment.md @@ -55,7 +55,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not | 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 | 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. **Slice 5 client (planned):** gather feedback HUD [NEO-73](https://linear.app/neon-sprawl/issue/NEO-73) — [E3S5 backlog](../../plans/E3S5-client-prototype-backlog.md). | [NEO-41](../../plans/NEO-41-implementation-plan.md) … [NEO-64](../../plans/NEO-64-implementation-plan.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.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. **Slice 5 client (planned):** inventory HUD [NEO-72](https://linear.app/neon-sprawl/issue/NEO-72) — [E3S5 backlog](../../plans/E3S5-client-prototype-backlog.md). | [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), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3M3-prototype-backlog](../../plans/E3M3-prototype-backlog.md), [E3_M3](E3_M3_ItemizationAndInventorySchema.md) | -| E3.M2 | In Progress | **NEO-65 landed:** frozen prototype eight-recipe catalog in [`content/recipes/prototype_recipes.json`](../../../content/recipes/prototype_recipes.json); [`recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) + [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) Slice 3 gates ([NEO-65](../../plans/NEO-65-implementation-plan.md)). **NEO-66 landed:** fail-fast server load of `content/recipes/*_recipes.json` at startup — `server/NeonSprawl.Server/Game/Crafting/` ([NEO-66](../../plans/NEO-66-implementation-plan.md)); [server README — Recipe catalog](../../../server/README.md#recipe-catalog-contentrecipes-neo-66). **NEO-67 landed:** injectable **`IRecipeDefinitionRegistry`** + lookup tests ([NEO-67](../../plans/NEO-67-implementation-plan.md)). **NEO-68 landed:** **`GET /game/world/recipe-definitions`** — `RecipeDefinitionsWorldApi` + DTOs in `Game/Crafting/` ([NEO-68](../../plans/NEO-68-implementation-plan.md), [`NEO-68` manual QA](../../manual-qa/NEO-68.md)); [server README — Recipe definitions (NEO-68)](../../../server/README.md#recipe-definitions-neo-68); Bruno `bruno/neon-sprawl-server/recipe-definitions/`. **NEO-69 landed:** **`CraftOperations.TryCraft`** + **`CraftResult`** — pre-flight inputs/outputs, inventory mutation, refine XP ([NEO-69](../../plans/NEO-69-implementation-plan.md)); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69-and-craft-http-neo-70). **NEO-70 landed:** **`POST /game/players/{id}/craft`** — `PlayerCraftApi` + wire **`CraftResponse`**; Bruno gather→refine→make spine ([NEO-70](../../plans/NEO-70-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/craft/`. **NEO-42 landed (prep):** **`RefineActivitySkillXpGrant`** / **`RefineSkillXpConstants`** — wired from craft engine success path. **Slice 3 backlog in Linear:** [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md) — **E3M2-07** open. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. **Slice 5 client (planned):** craft UI [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74); capstone playable loop [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75) — [E3S5 backlog](../../plans/E3S5-client-prototype-backlog.md). | [NEO-42](../../plans/NEO-42-implementation-plan.md), [NEO-65](../../plans/NEO-65-implementation-plan.md), [NEO-66](../../plans/NEO-66-implementation-plan.md), [NEO-67](../../plans/NEO-67-implementation-plan.md), [NEO-68](../../plans/NEO-68-implementation-plan.md), [NEO-69](../../plans/NEO-69-implementation-plan.md), [NEO-70](../../plans/NEO-70-implementation-plan.md) … [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75), [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3_M2](E3_M2_RefinementAndRecipeExecution.md) | +| E3.M2 | Ready | **NEO-65 landed:** frozen prototype eight-recipe catalog in [`content/recipes/prototype_recipes.json`](../../../content/recipes/prototype_recipes.json); [`recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) + [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) Slice 3 gates ([NEO-65](../../plans/NEO-65-implementation-plan.md)). **NEO-66 landed:** fail-fast server load of `content/recipes/*_recipes.json` at startup — `server/NeonSprawl.Server/Game/Crafting/` ([NEO-66](../../plans/NEO-66-implementation-plan.md)); [server README — Recipe catalog](../../../server/README.md#recipe-catalog-contentrecipes-neo-66). **NEO-67 landed:** injectable **`IRecipeDefinitionRegistry`** + lookup tests ([NEO-67](../../plans/NEO-67-implementation-plan.md)). **NEO-68 landed:** **`GET /game/world/recipe-definitions`** — `RecipeDefinitionsWorldApi` + DTOs in `Game/Crafting/` ([NEO-68](../../plans/NEO-68-implementation-plan.md), [`NEO-68` manual QA](../../manual-qa/NEO-68.md)); [server README — Recipe definitions (NEO-68)](../../../server/README.md#recipe-definitions-neo-68); Bruno `bruno/neon-sprawl-server/recipe-definitions/`. **NEO-69 landed:** **`CraftOperations.TryCraft`** + **`CraftResult`** — pre-flight inputs/outputs, inventory mutation, refine XP ([NEO-69](../../plans/NEO-69-implementation-plan.md)); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69-and-craft-http-neo-70). **NEO-70 landed:** **`POST /game/players/{id}/craft`** — `PlayerCraftApi` + wire **`CraftResponse`**; Bruno gather→refine→make spine ([NEO-70](../../plans/NEO-70-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/craft/`. **NEO-71 landed:** comment-only **`item_crafted`** / **`craft_failed`** telemetry hook sites in **`CraftOperations.TryCraft`** ([NEO-71](../../plans/NEO-71-implementation-plan.md)); [server README — Craft telemetry hooks (NEO-71)](../../../server/README.md#craft-telemetry-hooks-neo-71). **NEO-42 landed (prep):** **`RefineActivitySkillXpGrant`** / **`RefineSkillXpConstants`** — wired from craft engine success path. Epic 3 Slice 3 server backlog **E3M2-01**–**E3M2-07** complete. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. **Slice 5 client (planned):** craft UI [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74); capstone playable loop [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75) — [E3S5 backlog](../../plans/E3S5-client-prototype-backlog.md). | [NEO-42](../../plans/NEO-42-implementation-plan.md), [NEO-65](../../plans/NEO-65-implementation-plan.md), [NEO-66](../../plans/NEO-66-implementation-plan.md), [NEO-67](../../plans/NEO-67-implementation-plan.md), [NEO-68](../../plans/NEO-68-implementation-plan.md), [NEO-69](../../plans/NEO-69-implementation-plan.md), [NEO-70](../../plans/NEO-70-implementation-plan.md), [NEO-71](../../plans/NEO-71-implementation-plan.md) … [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75), [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3_M2](E3_M2_RefinementAndRecipeExecution.md) | --- diff --git a/docs/decomposition/modules/module_dependency_register.md b/docs/decomposition/modules/module_dependency_register.md index 074fb85..10ac879 100644 --- a/docs/decomposition/modules/module_dependency_register.md +++ b/docs/decomposition/modules/module_dependency_register.md @@ -43,7 +43,7 @@ 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 | Ready | -| E3.M2 | RefinementAndRecipeExecution | E3.M1, E3.M3 | RecipeDef, CraftRequest, CraftResult | Prototype | In Progress | +| E3.M2 | RefinementAndRecipeExecution | E3.M1, E3.M3 | RecipeDef, CraftRequest, CraftResult | Prototype | Ready | | 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 | @@ -52,7 +52,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen **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. -**E3.M2 note:** Epic 3 **Slice 3** 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-65](https://linear.app/neon-sprawl/issue/NEO-65) → [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); label **`E3.M2`**. See [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md), [E3_M2_RefinementAndRecipeExecution.md](E3_M2_RefinementAndRecipeExecution.md). **NEO-42** (refine XP prep helper) is **Done**; **NEO-65**–**NEO-71** created at decomposition. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. +**E3.M2 note:** Epic 3 **Slice 3** 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-65](https://linear.app/neon-sprawl/issue/NEO-65) → [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); label **`E3.M2`**. See [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md), [E3_M2_RefinementAndRecipeExecution.md](E3_M2_RefinementAndRecipeExecution.md). **NEO-42** (refine XP prep helper) is **Done**; **NEO-65**–**NEO-71** complete — **NEO-71** (comment-only **`item_crafted`** / **`craft_failed`** telemetry hook sites in `CraftOperations.TryCraft`) — E3M2-01–**E3M2-07** complete; register row **Ready**. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. **E3 Slice 5 (client) note:** Epic 3 **client economy integration** — [NEO-72](https://linear.app/neon-sprawl/issue/NEO-72) → [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75); labels **`client`** + module ids. See [E3S5-client-prototype-backlog.md](../../plans/E3S5-client-prototype-backlog.md), [epic_03 Slice 5](../epics/epic_03_crafting_economy.md#epic-3-slice-5). Required for prototype **playable** gather→craft loop (not Bruno-only). diff --git a/docs/plans/E3M2-prototype-backlog.md b/docs/plans/E3M2-prototype-backlog.md index 8e6b33b..7160c85 100644 --- a/docs/plans/E3M2-prototype-backlog.md +++ b/docs/plans/E3M2-prototype-backlog.md @@ -231,8 +231,10 @@ Working backlog for **Epic 3 — Slice 3** ([recipes and crafting pipeline](../d **Acceptance criteria** -- [ ] Hook sites documented in code and `server/README.md`. -- [ ] Matches Epic 3 Slice 3 telemetry vocabulary in [epic_03](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-3). +- [x] Hook sites documented in code and `server/README.md`. +- [x] Matches Epic 3 Slice 3 telemetry vocabulary in [epic_03](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-3). + +**Landed ([NEO-71](https://linear.app/neon-sprawl/issue/NEO-71)):** comment-only **`item_crafted`** / **`craft_failed`** hook sites in **`CraftOperations.TryCraft`**; plan [NEO-71-implementation-plan.md](NEO-71-implementation-plan.md); [server README — Craft telemetry hooks (NEO-71)](../../server/README.md#craft-telemetry-hooks-neo-71). --- diff --git a/docs/plans/NEO-71-implementation-plan.md b/docs/plans/NEO-71-implementation-plan.md index b29baaa..1b7022a 100644 --- a/docs/plans/NEO-71-implementation-plan.md +++ b/docs/plans/NEO-71-implementation-plan.md @@ -45,8 +45,8 @@ ## Acceptance criteria checklist -- [ ] Hook sites documented in code and `server/README.md`. -- [ ] Matches Epic 3 Slice 3 telemetry vocabulary (`item_crafted`, `craft_failed` with reason; **`time-to-first-craft`** noted as derived, no separate hook). +- [x] Hook sites documented in code and `server/README.md`. +- [x] Matches Epic 3 Slice 3 telemetry vocabulary (`item_crafted`, `craft_failed` with reason; **`time-to-first-craft`** noted as derived, no separate hook). ## Technical approach diff --git a/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs b/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs index c9dca41..903eecd 100644 --- a/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs +++ b/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs @@ -6,7 +6,7 @@ namespace NeonSprawl.Server.Game.Crafting; /// /// Orchestrates craft side effects: recipe resolve, inventory pre-flight, input removal, output grant, refine XP (NEO-69). -/// Craft HTTP: (NEO-70). +/// Craft HTTP: (NEO-70). NEO-71 telemetry hook sites: success path and . /// public static class CraftOperations { @@ -147,6 +147,12 @@ public static class CraftOperations return Deny(xpReason); } + // --- Telemetry hook site (NEO-71): future E9.M1 catalog event `item_crafted` --- + // TODO(E9.M1): catalog emit — on every successful craft (inputs removed, outputs granted, refine XP applied). + // Planned payload fields: playerId, recipeId, quantity (batch), inputsConsumed, outputsGranted, xpGrantSummary + // (skillId, amount, sourceKind). No ingest or ILogger here (comments-only). Successful output grants also pass + // through PlayerInventoryOperations (`item_created` hook, NEO-56). PlayerCraftApi delegates here (no duplicate hooks). + return new CraftResult( Success: true, ReasonCode: null, @@ -211,11 +217,18 @@ public static class CraftOperations } } - private static CraftResult Deny(string reasonCode) => - new( + private static CraftResult Deny(string reasonCode) + { + // --- Telemetry hook site (NEO-71): future E9.M1 catalog event `craft_failed` --- + // TODO(E9.M1): catalog emit — on every structured craft deny with stable reasonCode (CraftReasonCodes). + // Planned payload fields: playerId, recipeId, quantity, reasonCode — from enclosing TryCraft scope at call sites. + // No ingest or ILogger here (comments-only). PlayerCraftApi delegates here (no duplicate hooks). + + return new CraftResult( Success: false, ReasonCode: reasonCode, InputsConsumed: EmptyIo, OutputsGranted: EmptyIo, XpGrantSummary: null); + } } diff --git a/server/README.md b/server/README.md index 11addb2..65fa74a 100644 --- a/server/README.md +++ b/server/README.md @@ -115,7 +115,11 @@ Sample **200** success body (after seeding **5×** **`scrap_metal_bulk`** via ga {"schemaVersion":1,"success":true,"inputsConsumed":[{"itemId":"scrap_metal_bulk","quantity":5}],"outputsGranted":[{"itemId":"refined_plate_stock","quantity":1}],"xpGrantSummary":{"skillId":"refine","amount":10,"sourceKind":"activity"}} ``` -Plan: [NEO-69 implementation plan](../../docs/plans/NEO-69-implementation-plan.md); [NEO-70 implementation plan](../../docs/plans/NEO-70-implementation-plan.md). Bruno: `bruno/neon-sprawl-server/craft/`. Telemetry hook sites (**`item_crafted`**, **`craft_failed`**) are **NEO-71**. +Plan: [NEO-69 implementation plan](../../docs/plans/NEO-69-implementation-plan.md); [NEO-70 implementation plan](../../docs/plans/NEO-70-implementation-plan.md); [NEO-71 implementation plan](../../docs/plans/NEO-71-implementation-plan.md). Bruno: `bruno/neon-sprawl-server/craft/`. + +### Craft telemetry hooks (NEO-71) + +Comment-only hook sites in **`CraftOperations.TryCraft`** for future E9.M1 catalog events — **`item_crafted`** on every successful craft (inputs removed, outputs granted, refine XP applied); **`craft_failed`** with stable **`reasonCode`** on every structured deny via private **`Deny`** (all **`CraftReasonCodes`**, including store-missing and post-mutation rollback paths). **`TODO(E9.M1)`** — no production ingest. Successful craft output grants also pass through **`PlayerInventoryOperations`** (**`item_created`** hook, [NEO-56](#player-inventory-neo-54-store-neo-55-http)). HTTP **`POST …/craft`** calls the engine only (no duplicate API-layer hooks). Epic 3 Slice 3 metric **`time-to-first-craft`** is derived at E9.M1 from **`item_crafted`** + session context (no separate prototype hook). Plan: [NEO-71 implementation plan](../../docs/plans/NEO-71-implementation-plan.md). ## Resource node definitions (NEO-60) From 2be35c164060b45b6c746c4af027448f638560e9 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:30:46 -0400 Subject: [PATCH 3/4] NEO-71: Add code review for craft telemetry hook sites. --- docs/reviews/2026-05-24-NEO-71.md | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/reviews/2026-05-24-NEO-71.md diff --git a/docs/reviews/2026-05-24-NEO-71.md b/docs/reviews/2026-05-24-NEO-71.md new file mode 100644 index 0000000..1fe76f2 --- /dev/null +++ b/docs/reviews/2026-05-24-NEO-71.md @@ -0,0 +1,51 @@ +# Code review — NEO-71 craft telemetry hook sites + +**Date:** 2026-05-24 +**Scope:** Branch `NEO-71-item-crafted-craft-failed-telemetry-hooks` · commits `194078d`–`2d47510` vs `origin/main` +**Base:** `origin/main` + +## Verdict + +**Approve with nits** — comment-only hook sites and documentation match the adopted plan and E3M2-07 acceptance criteria; ready to merge after optional `CraftResult.cs` wording tweak. + +## Summary + +The branch adds **comment-only E9.M1 telemetry hook sites** in **`CraftOperations.TryCraft`**: **`item_crafted`** immediately before the successful return (after refine XP grant), and **`craft_failed`** in the centralized private **`Deny`** helper covering all **`CraftReasonCodes`** paths including store-missing and post-mutation rollback denies. Hook style, payload field notes, and **`TODO(E9.M1): catalog emit`** markers follow NEO-64 / NEO-56 precedent. **`PlayerCraftApi`** correctly has no duplicate hooks. Docs (implementation plan, **`server/README.md`** craft telemetry subsection, E3_M2 module snapshot, E3M2-07 backlog, alignment register, dependency register **Ready** bump) are updated. No runtime or wire contract change; existing craft tests pass. Risk is negligible. + +## Documentation checked + +| Document | Result | +|----------|--------| +| [`docs/plans/NEO-71-implementation-plan.md`](../plans/NEO-71-implementation-plan.md) | **Matches** — engine-only hooks, central **`Deny`**, comments-only, no manual QA doc, **`time-to-first-craft`** derived at E9.M1, acceptance checklist complete. | +| [`docs/plans/E3M2-prototype-backlog.md`](../plans/E3M2-prototype-backlog.md) · **E3M2-07** | **Matches** — acceptance criteria checked; landed note present. | +| [`docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md`](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) | **Matches** — NEO-71 landed line; telemetry deferral removed; module **Ready** (Slice 3 server backlog complete). | +| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E3.M2 row notes NEO-71; E3M2-01–07 complete. | +| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E3.M2 **Status** → **Ready**; E3M2-07 note updated. | +| [`docs/decomposition/epics/epic_03_crafting_economy.md`](../decomposition/epics/epic_03_crafting_economy.md) · Slice 3 | **Matches** — **`item_crafted`**, **`craft_failed`** with reason documented; **`time-to-first-craft`** noted as derived (no separate hook). | +| NEO-64 / NEO-56 reference — [`GatherOperations`](../../server/NeonSprawl.Server/Game/Gathering/GatherOperations.cs), [`PlayerInventoryOperations`](../../server/NeonSprawl.Server/Game/Items/PlayerInventoryOperations.cs) | **Matches** — same comment block structure, **`TODO(E9.M1)`**, central **`Deny`** for structured denies, engine-only anchor. | +| Full-stack epic decomposition | **Matches** — no client counterpart (server comment-only); NEO-74 cited as Slice 5 craft UI; no claim of player-visible prototype completion. | + +Register/tracking: E3.M2 **Ready** bump is appropriate — Slice 3 server backlog E3M2-01–E3M2-07 complete per plan. + +## Blocking issues + +None. + +## Suggestions + +1. **`CraftResult.cs` summary tense** — Plan item 4 asked to confirm cross-reference wording after hooks land. File was not updated; summary still reads “NEO-71 telemetry hook sites **will live** in **`TryCraft`**”. Update to present tense (e.g. “live in **`CraftOperations.TryCraft`** (success path + **`Deny`**)”) so it matches landed hooks and the class summary on **`CraftOperations`**. + +## Nits + +- Nit: README craft telemetry subsection is a single dense paragraph; a two-bullet list mirroring the code hook sites (`item_crafted` / `craft_failed`) would match NEO-64 gather README scanability — optional only. + +## Verification + +```bash +cd server +dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj --filter "FullyQualifiedName~PlayerCraftApiTests|FullyQualifiedName~CraftOperationsTests" +``` + +**Results (2026-05-24):** **21** craft-related tests pass (`PlayerCraftApiTests` + `CraftOperationsTests`). + +Optional Bruno regression (behavior unchanged): run `bruno/neon-sprawl-server/craft/Post craft gather refine make spine.bru` against a running dev server. From 5f0f7035551b5cca0d55201c9714136251b33510 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:32:08 -0400 Subject: [PATCH 4/4] =?UTF-8?q?NEO-71:=20address=20code=20review=20?= =?UTF-8?q?=E2=80=94=20CraftResult=20tense=20and=20README=20bullets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reviews/2026-05-24-NEO-71.md | 6 +++--- server/NeonSprawl.Server/Game/Crafting/CraftResult.cs | 2 +- server/README.md | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/reviews/2026-05-24-NEO-71.md b/docs/reviews/2026-05-24-NEO-71.md index 1fe76f2..dccc5b6 100644 --- a/docs/reviews/2026-05-24-NEO-71.md +++ b/docs/reviews/2026-05-24-NEO-71.md @@ -6,7 +6,7 @@ ## Verdict -**Approve with nits** — comment-only hook sites and documentation match the adopted plan and E3M2-07 acceptance criteria; ready to merge after optional `CraftResult.cs` wording tweak. +**Approve with nits** — comment-only hook sites and documentation match the adopted plan and E3M2-07 acceptance criteria; ready to merge. ## Summary @@ -33,11 +33,11 @@ None. ## Suggestions -1. **`CraftResult.cs` summary tense** — Plan item 4 asked to confirm cross-reference wording after hooks land. File was not updated; summary still reads “NEO-71 telemetry hook sites **will live** in **`TryCraft`**”. Update to present tense (e.g. “live in **`CraftOperations.TryCraft`** (success path + **`Deny`**)”) so it matches landed hooks and the class summary on **`CraftOperations`**. +1. ~~**`CraftResult.cs` summary tense** — Plan item 4 asked to confirm cross-reference wording after hooks land. File was not updated; summary still reads “NEO-71 telemetry hook sites **will live** in **`TryCraft`**”. Update to present tense (e.g. “live in **`CraftOperations.TryCraft`** (success path + **`Deny`**)”) so it matches landed hooks and the class summary on **`CraftOperations`**.~~ **Done.** ## Nits -- Nit: README craft telemetry subsection is a single dense paragraph; a two-bullet list mirroring the code hook sites (`item_crafted` / `craft_failed`) would match NEO-64 gather README scanability — optional only. +- ~~Nit: README craft telemetry subsection is a single dense paragraph; a two-bullet list mirroring the code hook sites (`item_crafted` / `craft_failed`) would match NEO-64 gather README scanability — optional only.~~ **Done.** ## Verification diff --git a/server/NeonSprawl.Server/Game/Crafting/CraftResult.cs b/server/NeonSprawl.Server/Game/Crafting/CraftResult.cs index b226f1b..fe69884 100644 --- a/server/NeonSprawl.Server/Game/Crafting/CraftResult.cs +++ b/server/NeonSprawl.Server/Game/Crafting/CraftResult.cs @@ -2,7 +2,7 @@ namespace NeonSprawl.Server.Game.Crafting; /// /// Server-internal craft resolution envelope (NEO-69); promoted to wire JSON via (NEO-70). -/// NEO-71 telemetry hook sites will live in . +/// NEO-71 telemetry hook sites live in (success path + centralized Deny helper). /// /// Scaled inputs removed on success; empty when denied. /// Scaled outputs added on success; empty when denied. diff --git a/server/README.md b/server/README.md index 65fa74a..d8321d1 100644 --- a/server/README.md +++ b/server/README.md @@ -119,7 +119,12 @@ Plan: [NEO-69 implementation plan](../../docs/plans/NEO-69-implementation-plan.m ### Craft telemetry hooks (NEO-71) -Comment-only hook sites in **`CraftOperations.TryCraft`** for future E9.M1 catalog events — **`item_crafted`** on every successful craft (inputs removed, outputs granted, refine XP applied); **`craft_failed`** with stable **`reasonCode`** on every structured deny via private **`Deny`** (all **`CraftReasonCodes`**, including store-missing and post-mutation rollback paths). **`TODO(E9.M1)`** — no production ingest. Successful craft output grants also pass through **`PlayerInventoryOperations`** (**`item_created`** hook, [NEO-56](#player-inventory-neo-54-store-neo-55-http)). HTTP **`POST …/craft`** calls the engine only (no duplicate API-layer hooks). Epic 3 Slice 3 metric **`time-to-first-craft`** is derived at E9.M1 from **`item_crafted`** + session context (no separate prototype hook). Plan: [NEO-71 implementation plan](../../docs/plans/NEO-71-implementation-plan.md). +Comment-only hook sites in **`CraftOperations.TryCraft`** for future E9.M1 catalog events. **`TODO(E9.M1)`** — no production ingest. HTTP **`POST …/craft`** calls the engine only (no duplicate API-layer hooks). + +- **`item_crafted`** — on every successful craft (inputs removed, outputs granted, refine XP applied). +- **`craft_failed`** — with stable **`reasonCode`** on every structured deny via private **`Deny`** (all **`CraftReasonCodes`**, including store-missing and post-mutation rollback paths). + +Successful craft output grants also pass through **`PlayerInventoryOperations`** (**`item_created`** hook, [NEO-56](#player-inventory-neo-54-store-neo-55-http)). Epic 3 Slice 3 metric **`time-to-first-craft`** is derived at E9.M1 from **`item_crafted`** + session context (no separate prototype hook). Plan: [NEO-71 implementation plan](../../docs/plans/NEO-71-implementation-plan.md). ## Resource node definitions (NEO-60)