NEO-65: E3.M2 Slice 3 decomposition backlog and freeze docs
Add E3M2 prototype backlog (NEO-65–NEO-71), eight-recipe freeze table, content/README Slice 3 CI note, and cross-links in alignment/decomposition docs.pull/99/head
parent
0556d5fa0f
commit
458e9b10e8
|
|
@ -8,6 +8,7 @@ Data-driven definitions (skills, items, recipes, quests) validated in CI with **
|
||||||
| [`skills/`](skills/) | Skill catalogs; each row matches [`schemas/skill-def.schema.json`](schemas/skill-def.schema.json) — **stable `id`**, **`allowedXpSourceKinds`** for [E2.M1](../docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md) / [E2.M2](../docs/decomposition/modules/E2_M2_XpAwardAndLevelEngine.md) |
|
| [`skills/`](skills/) | Skill catalogs; each row matches [`schemas/skill-def.schema.json`](schemas/skill-def.schema.json) — **stable `id`**, **`allowedXpSourceKinds`** for [E2.M1](../docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md) / [E2.M2](../docs/decomposition/modules/E2_M2_XpAwardAndLevelEngine.md) |
|
||||||
| [`mastery/`](mastery/) | Mastery catalogs; each file matches [`schemas/mastery-catalog.schema.json`](schemas/mastery-catalog.schema.json) — **stable `branchId` / `perkId`**, tier gates via skill level ([E2.M3](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md)) |
|
| [`mastery/`](mastery/) | Mastery catalogs; each file matches [`schemas/mastery-catalog.schema.json`](schemas/mastery-catalog.schema.json) — **stable `branchId` / `perkId`**, tier gates via skill level ([E2.M3](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md)) |
|
||||||
| [`items/`](items/) | Item catalogs; each row matches [`schemas/item-def.schema.json`](schemas/item-def.schema.json) — **stable `id`**, **`stackMax`**, **`inventorySlotKind`** for [E3.M3](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) |
|
| [`items/`](items/) | Item catalogs; each row matches [`schemas/item-def.schema.json`](schemas/item-def.schema.json) — **stable `id`**, **`stackMax`**, **`inventorySlotKind`** for [E3.M3](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) |
|
||||||
|
| [`recipes/`](recipes/) | Recipe catalogs; each row matches [`schemas/recipe-def.schema.json`](schemas/recipe-def.schema.json) — **stable `id`**, **`recipeKind`**, **`requiredSkillId`**, inputs/outputs for [E3.M2](../docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) |
|
||||||
| [`resource-nodes/`](resource-nodes/) | Resource node + yield catalogs; node rows match [`schemas/resource-node-def.schema.json`](schemas/resource-node-def.schema.json), yield rows match [`schemas/resource-yield-row.schema.json`](schemas/resource-yield-row.schema.json) — **stable `nodeDefId`**, **`gatherLens`**, **`maxGathers`** for [E3.M1](../docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) |
|
| [`resource-nodes/`](resource-nodes/) | Resource node + yield catalogs; node rows match [`schemas/resource-node-def.schema.json`](schemas/resource-node-def.schema.json), yield rows match [`schemas/resource-yield-row.schema.json`](schemas/resource-yield-row.schema.json) — **stable `nodeDefId`**, **`gatherLens`**, **`maxGathers`** for [E3.M1](../docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) |
|
||||||
|
|
||||||
**Prototype Slice 1 (NEO-33):** CI expects **exactly three** skill rows with ids **`salvage`**, **`refine`**, **`intrusion`** (gather + process + tech). Each row must declare **`allowedXpSourceKinds`** (non-empty); see [E2.M1 — Designer note: `allowedXpSourceKinds`](../docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md#designer-note-allowedxpsourcekinds-and-grant-call-sites) for what each kind means for grant wiring.
|
**Prototype Slice 1 (NEO-33):** CI expects **exactly three** skill rows with ids **`salvage`**, **`refine`**, **`intrusion`** (gather + process + tech). Each row must declare **`allowedXpSourceKinds`** (non-empty); see [E2.M1 — Designer note: `allowedXpSourceKinds`](../docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md#designer-note-allowedxpsourcekinds-and-grant-call-sites) for what each kind means for grant wiring.
|
||||||
|
|
@ -16,6 +17,8 @@ Data-driven definitions (skills, items, recipes, quests) validated in CI with **
|
||||||
|
|
||||||
**Prototype Slice 1 — items (NEO-50):** CI expects **exactly six** item rows with ids **`scrap_metal_bulk`**, **`refined_plate_stock`**, **`field_stim_mk0`**, **`survey_drone_kit`**, **`contract_handoff_token`**, **`prototype_armor_shell`** — one row per **`prototypeRole`** (`material` through `equip_stub`). **Do not rename** ids after ship—change **`displayName`** only. See [E3.M3 — Designer note](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md#designer-note-stack-slot-and-v1-scope) and [freeze table](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md#prototype-slice-1-freeze-neo-50).
|
**Prototype Slice 1 — items (NEO-50):** CI expects **exactly six** item rows with ids **`scrap_metal_bulk`**, **`refined_plate_stock`**, **`field_stim_mk0`**, **`survey_drone_kit`**, **`contract_handoff_token`**, **`prototype_armor_shell`** — one row per **`prototypeRole`** (`material` through `equip_stub`). **Do not rename** ids after ship—change **`displayName`** only. See [E3.M3 — Designer note](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md#designer-note-stack-slot-and-v1-scope) and [freeze table](../docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md#prototype-slice-1-freeze-neo-50).
|
||||||
|
|
||||||
|
**Prototype Slice 3 — recipes (E3M2-01):** CI expects **exactly eight** recipe ids aligned to [E3.M2 recipe freeze](../docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md#prototype-slice-3-freeze-e3m2-01) — at least one **`process`** and one **`make`** row; all **`requiredSkillId`** values **`refine`**; every input/output **`itemId`** from the frozen item catalog. **Do not rename** recipe `id` after ship. See [E3M2-prototype-backlog.md](../docs/plans/E3M2-prototype-backlog.md).
|
||||||
|
|
||||||
**Prototype Slice 4 (NEO-45):** CI expects **exactly one** `MasteryTrack` for **`salvage`** only (`refine` / `intrusion` have no mastery rows yet). Tier 1 @ skill level **2** is a **mutually exclusive** branch pick (`scrap_efficiency` vs `bulk_haul`) with **no** perks; tier 2 @ level **4** unlocks one perk per branch path. **Do not rename** `branchId` / `perkId` after ship—change `displayName` only. See [E2.M3 — Designer note](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#designer-note-tiers-branches-and-level-gates) and [freeze table](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#prototype-slice-4-freeze--salvage-flagship-neo-45).
|
**Prototype Slice 4 (NEO-45):** CI expects **exactly one** `MasteryTrack` for **`salvage`** only (`refine` / `intrusion` have no mastery rows yet). Tier 1 @ skill level **2** is a **mutually exclusive** branch pick (`scrap_efficiency` vs `bulk_haul`) with **no** perks; tier 2 @ level **4** unlocks one perk per branch path. **Do not rename** `branchId` / `perkId` after ship—change `displayName` only. See [E2.M3 — Designer note](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#designer-note-tiers-branches-and-level-gates) and [freeze table](../docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md#prototype-slice-4-freeze--salvage-flagship-neo-45).
|
||||||
|
|
||||||
Server load path and hot-reload policy are TBD; keep files versioned here as the source of truth.
|
Server load path and hot-reload policy are TBD; keep files versioned here as the source of truth.
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,6 @@ Template for new epics or major revisions: [epics/_epic_template.md](epics/_epic
|
||||||
|
|
||||||
## Next Actions
|
## Next Actions
|
||||||
|
|
||||||
1. Expand slices into **Linear issues** under epic/module **projects**; tag **module / slice** scope with **labels** (not a duplicate Feature tier). Keep module IDs aligned with the master plan.
|
1. Expand slices into **Linear issues** under epic/module **projects** when writing a **prototype backlog** (`docs/plans/E*M*-prototype-backlog.md`) — **create all issues and `blockedBy` links in the same decomposition pass**, not at story kickoff. Set **estimate 1…N** on each issue to match slug execution order so the project board can sort by **Estimate** (ascending). Tag **module / slice** scope with **labels** (not a duplicate Feature tier). Keep module IDs aligned with the master plan.
|
||||||
2. Populate `modules/module_dependency_register.md` as contracts stabilize.
|
2. Populate `modules/module_dependency_register.md` as contracts stabilize.
|
||||||
3. Refine `milestones/phase_execution_plan.md` with dates and owners when execution starts.
|
3. Refine `milestones/phase_execution_plan.md` with dates and owners when execution starts.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
| **Module ID** | E3.M2 |
|
| **Module ID** | E3.M2 |
|
||||||
| **Epic** | [Epic 3 — Crafting Economy](../epics/epic_03_crafting_economy.md) |
|
| **Epic** | [Epic 3 — Crafting Economy](../epics/epic_03_crafting_economy.md) |
|
||||||
| **Stage target** | Prototype |
|
| **Stage target** | Prototype |
|
||||||
| **Status** | Planned (see [dependency register](module_dependency_register.md)) |
|
| **Status** | Planned — Slice 3 backlog [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md) (see [dependency register](module_dependency_register.md)) |
|
||||||
|
|
||||||
## Implementation snapshot
|
## Implementation snapshot
|
||||||
|
|
||||||
|
|
@ -42,9 +42,39 @@ Implements the processing and crafting pipeline: raw-to-refined flows and recipe
|
||||||
- **E6.M4** — [RewardParityEnforcer](E6_M4_RewardParityEnforcer.md): PvP ↔ PvE/crafting equivalents involving craft outputs.
|
- **E6.M4** — [RewardParityEnforcer](E6_M4_RewardParityEnforcer.md): PvP ↔ PvE/crafting equivalents involving craft outputs.
|
||||||
- **Epic 2 integration** — Callers award crafting XP via [E2.M2](E2_M2_XpAwardAndLevelEngine.md) at activity sites.
|
- **Epic 2 integration** — Callers award crafting XP via [E2.M2](E2_M2_XpAwardAndLevelEngine.md) at activity sites.
|
||||||
|
|
||||||
|
## Designer note: prototype recipe shape
|
||||||
|
|
||||||
|
Each **`RecipeDef`** row under `content/recipes/*.json` declares:
|
||||||
|
|
||||||
|
- **`recipeKind`** — **`process`** (refine / intermediate) or **`make`** (finished good).
|
||||||
|
- **`requiredSkillId`** — prototype uses **`refine`** for all eight rows (no separate Make skill in the skill trio).
|
||||||
|
- **`inputs`** / **`outputs`** — arrays of `{ itemId, quantity }` referencing only frozen [E3.M3](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) item ids.
|
||||||
|
- **`requiredSkillLevel`** — **omitted** in Slice 3 (no level gates until a deliberate content pass).
|
||||||
|
|
||||||
|
**Prototype v1:** no station tags, craft failure RNG, or quality rolls — deterministic success when inputs and bag/equipment capacity allow.
|
||||||
|
|
||||||
|
## Prototype Slice 3 freeze (E3M2-01)
|
||||||
|
|
||||||
|
The **first shipped eight-recipe spine** under `content/recipes/*.json` is **frozen** for downstream references (craft HTTP, Bruno spine, quest objectives) until a deliberate migration or follow-up issue expands the roster.
|
||||||
|
|
||||||
|
| `RecipeDef.id` | `recipeKind` | Primary inputs → output | Demo role |
|
||||||
|
|----------------|--------------|-------------------------|-----------|
|
||||||
|
| **`refine_scrap_standard`** | `process` | 5× **`scrap_metal_bulk`** → 1× **`refined_plate_stock`** | Default refine after gather |
|
||||||
|
| **`refine_scrap_efficient`** | `process` | 10× **`scrap_metal_bulk`** → 2× **`refined_plate_stock`** | Branching refine efficiency |
|
||||||
|
| **`make_field_stim_mk0`** | `make` | 2× **`refined_plate_stock`** + 1× **`scrap_metal_bulk`** → 1× **`field_stim_mk0`** | Primary gather→refine→combat consumable |
|
||||||
|
| **`make_field_stim_batch`** | `make` | 5× **`refined_plate_stock`** + 3× **`scrap_metal_bulk`** → 3× **`field_stim_mk0`** | Batch make variant |
|
||||||
|
| **`make_prototype_armor`** | `make` | 8× **`refined_plate_stock`** + 5× **`scrap_metal_bulk`** → 1× **`prototype_armor_shell`** | Equip-stub output |
|
||||||
|
| **`make_armor_quick`** | `make` | 4× **`refined_plate_stock`** + 3× **`scrap_metal_bulk`** → 1× **`prototype_armor_shell`** | Alternate armor path (branching) |
|
||||||
|
| **`make_survey_drone_kit`** | `make` | 3× **`refined_plate_stock`** + 2× **`scrap_metal_bulk`** → 1× **`survey_drone_kit`** | Utility output |
|
||||||
|
| **`make_contract_token`** | `make` | 2× **`refined_plate_stock`** + 1× **`scrap_metal_bulk`** → 1× **`contract_handoff_token`** | Quest token output |
|
||||||
|
|
||||||
|
**Rules:** Do **not** rename these `id` values—change **`displayName`** only, or add a new `id` in a follow-up issue. CI ([`scripts/validate_content.py`](../../../scripts/validate_content.py)) enforces **exactly** these eight ids, at least one **`process`** and one **`make`** row, and duplicate-`id` rejection across all recipe JSON files.
|
||||||
|
|
||||||
## Related implementation slices
|
## Related implementation slices
|
||||||
|
|
||||||
See Epic 3 **Slice 3 — Recipes and crafting pipeline**: gather → refine → item used in combat or quest; telemetry `item_crafted`, `craft_failed`, time-to-first-craft.
|
Epic 3 **Slice 3** — gather → refine → item used in combat or quest; telemetry `item_crafted`, `craft_failed`, time-to-first-craft.
|
||||||
|
|
||||||
|
**Linear backlog (decomposed):** [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md) — **E3M2-01** [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) (content + CI) through **E3M2-07** [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71) (telemetry hooks).
|
||||||
|
|
||||||
## Risks and telemetry
|
## Risks and telemetry
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +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 |
|
| 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. | [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.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) |
|
| 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) |
|
||||||
|
| E3.M2 | Planned | **NEO-42 landed (prep):** **`RefineActivitySkillXpGrant`** — craft/refine success should award **`refine`** XP via shared NEO-38 grant path; no craft route yet. **Slice 3 backlog in Linear:** [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) → [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md) — **E3M2-01**–**E3M2-07**. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. | [NEO-42](../../plans/NEO-42-implementation-plan.md), [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) … [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71), [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md), [E3_M2](E3_M2_RefinementAndRecipeExecution.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ 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.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.
|
||||||
|
|
||||||
### Epic 4 — World Topology
|
### Epic 4 — World Topology
|
||||||
|
|
||||||
| Module ID | Module Name | Depends On | Contract Needed | Phase Required | Status |
|
| Module ID | Module Name | Depends On | Contract Needed | Phase Required | Status |
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ Working backlog for **Epic 3 — Slice 2** ([gather nodes and outputs](../decomp
|
||||||
|
|
||||||
## After this backlog
|
## After this backlog
|
||||||
|
|
||||||
- Decompose **[E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md)** Slice 3 (recipes + craft HTTP) once E3M1-06+ land and inventory holds gathered materials.
|
- **[E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md)** Slice 3 decomposed — [E3M2-prototype-backlog.md](E3M2-prototype-backlog.md) ([NEO-65](https://linear.app/neon-sprawl/issue/NEO-65)–[NEO-71](https://linear.app/neon-sprawl/issue/NEO-71)).
|
||||||
- Track delivery in Linear; keep `blockedBy` links synchronized if scope changes.
|
- Track delivery in Linear; keep `blockedBy` links synchronized if scope changes.
|
||||||
- For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
|
- For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
|
||||||
- Add `docs/manual-qa/{NEO-XX}.md` when interact + inventory surfaces are testable (E3M1-07+).
|
- Add `docs/manual-qa/{NEO-XX}.md` when interact + inventory surfaces are testable (E3M1-07+).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
# E3.M2 — Prototype story backlog (RefinementAndRecipeExecution)
|
||||||
|
|
||||||
|
Working backlog for **Epic 3 — Slice 3** ([recipes and crafting pipeline](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-3)). Decomposition and contracts: [E3.M2 — RefinementAndRecipeExecution](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md).
|
||||||
|
|
||||||
|
**Labels (Linear):** every issue **`E3.M2`**; add **`server`** / **`Story`** as listed per issue.
|
||||||
|
|
||||||
|
**Precursor (do not re-scope):** [NEO-42](https://linear.app/neon-sprawl/issue/NEO-42) ships **`RefineActivitySkillXpGrant.GrantOnSuccessfulCraftOrRefine`** — **10** **`refine`** XP with **`sourceKind: activity`** on successful craft/refine completion. Slice 3 **calls** that helper from the real **`CraftResult`** success path; NEO-42 does **not** own recipe validation, inventory mutation, or craft HTTP.
|
||||||
|
|
||||||
|
**Upstream (must be landed):** [E3.M3](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) Slice 1 — frozen item catalog, **`IPlayerInventoryStore`**, **`PlayerInventoryOperations`**, inventory HTTP ([NEO-50](https://linear.app/neon-sprawl/issue/NEO-50)–[NEO-56](https://linear.app/neon-sprawl/issue/NEO-56)). [E3.M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) Slice 2 — gather → **`scrap_metal_bulk`** ([NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)–[NEO-64](https://linear.app/neon-sprawl/issue/NEO-64)). **E2.M2** grant path ([NEO-38](https://linear.app/neon-sprawl/issue/NEO-38)).
|
||||||
|
|
||||||
|
**Prototype craft spine (frozen in E3M2-01):** **eight** `RecipeDef` rows proving **gather → process → make** on the frozen six-item catalog — **two** process (**refine**) recipes, **six** make recipes covering every **`prototypeRole`** output except **`material`** / **`intermediate`** (those are inputs). All recipes use **`requiredSkillId: refine`** (no separate **Make** skill row in the prototype skill trio). Station tags, skill level gates, and batch-quantity POST are **out of scope** for Slice 3.
|
||||||
|
|
||||||
|
**Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the same branch as implementation work.
|
||||||
|
|
||||||
|
| Slug | Linear |
|
||||||
|
|------|--------|
|
||||||
|
| E3M2-01 | [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) |
|
||||||
|
| E3M2-02 | [NEO-66](https://linear.app/neon-sprawl/issue/NEO-66) |
|
||||||
|
| E3M2-03 | [NEO-67](https://linear.app/neon-sprawl/issue/NEO-67) |
|
||||||
|
| E3M2-04 | [NEO-68](https://linear.app/neon-sprawl/issue/NEO-68) |
|
||||||
|
| E3M2-05 | [NEO-69](https://linear.app/neon-sprawl/issue/NEO-69) |
|
||||||
|
| E3M2-06 | [NEO-70](https://linear.app/neon-sprawl/issue/NEO-70) |
|
||||||
|
| E3M2-07 | [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71) |
|
||||||
|
|
||||||
|
**Dependency graph in Linear:** E3M2-02 (NEO-66) blocked by E3M2-01 (NEO-65). E3M2-03 (NEO-67) blocked by E3M2-02 (NEO-66). E3M2-04 (NEO-68) blocked by E3M2-03 (NEO-67). E3M2-05 (NEO-69) blocked by E3M2-03 (NEO-67) and E3.M3 inventory store (NEO-54). E3M2-06 (NEO-70) blocked by E3M2-05 (NEO-69). E3M2-07 (NEO-71) blocked by E3M2-05 (NEO-69; may parallel E3M2-06).
|
||||||
|
|
||||||
|
**Board order:** each issue carries **estimate 1–7** matching slug order (E3M2-01 = 1 … E3M2-07 = 7). On the Epic 3 project board, use **Display options → Ordering → Estimate** (ascending) so Todo reads top-to-bottom in delivery order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story order (recommended)
|
||||||
|
|
||||||
|
| Order | Slug | Depends on |
|
||||||
|
|-------|------|------------|
|
||||||
|
| 1 | **E3M2-01** | E3.M3 item catalog (NEO-50) |
|
||||||
|
| 2 | **E3M2-02** | E3M2-01 |
|
||||||
|
| 3 | **E3M2-03** | E3M2-02 |
|
||||||
|
| 4 | **E3M2-04** | E3M2-03 |
|
||||||
|
| 5 | **E3M2-05** | E3M2-03, E3.M3 inventory store (NEO-54+) |
|
||||||
|
| 6 | **E3M2-06** | E3M2-05 |
|
||||||
|
| 7 | **E3M2-07** | E3M2-05 |
|
||||||
|
|
||||||
|
**Downstream (separate modules):** [E7.M1](../decomposition/modules/E7_M1_QuestStateMachine.md) quest steps requiring crafted items; [E3.M4](../decomposition/modules/E3_M4_SinkAndDurabilityLifecycle.md) durability sinks; [E4.M1](../decomposition/modules/E4_M1_ZoneGraphAndTravelRules.md) bench/station placement later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kickoff decisions (decomposition defaults)
|
||||||
|
|
||||||
|
| Topic | Decision | Rationale |
|
||||||
|
|-------|----------|-----------|
|
||||||
|
| Recipe count | **8** frozen defs (epic minimum 8–12) | Two refine paths + six make outputs without expanding the item catalog |
|
||||||
|
| Skill id | **`refine`** on every recipe | Prototype skill trio has no separate **Make** skill; matches [NEO-42](NEO-42-implementation-plan.md) |
|
||||||
|
| Skill level gates | **None** in prototype | `requiredSkillLevel` omitted or **0**; gates are a content follow-up |
|
||||||
|
| Station / bench tags | **None** in prototype | Craft always allowed via HTTP; zone/bench gating defers to [E4.M1](../decomposition/modules/E4_M1_ZoneGraphAndTravelRules.md) |
|
||||||
|
| Craft wire surface | **`POST /game/players/{id}/craft`** | Mirror [NEO-55](https://linear.app/neon-sprawl/issue/NEO-55) inventory POST pattern; no station interact in Slice 3 |
|
||||||
|
| Request shape | **`recipeId`** + optional **`quantity`** default **1** | One craft execution per POST; multi-output comes from recipe **`outputs`** |
|
||||||
|
| Atomic commit | Consume **all** inputs, then grant **all** outputs, then XP | Roll back input removal if output grant fails (mirror gather engine rollback) |
|
||||||
|
| XP | **`RefineActivitySkillXpGrant`** once per successful craft | NEO-42 already landed; E3M2-05 owns the call site |
|
||||||
|
| RNG / quality / failure | **None** — deterministic success when inputs + bag space allow | Soft craft failure and quality tiers are pre-production ([crafting.md](../game-design/crafting.md)) |
|
||||||
|
| Primary demo loop | Gather **`scrap_metal_bulk`** → **`refine_scrap_standard`** → **`make_field_stim_mk0`** | Proves combat-relevant consumable from world nodes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-01 — Prototype RecipeDef catalog + schemas + CI
|
||||||
|
|
||||||
|
**Goal:** Lock content shape and CI validation for **eight** frozen recipes before server load.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- `content/schemas/recipe-def.schema.json` and `content/schemas/recipe-io-row.schema.json` (input/output row shape).
|
||||||
|
- `content/recipes/prototype_recipes.json` with stable **`id`** values (see module doc freeze table).
|
||||||
|
- `scripts/validate_content.py`: schema validation, duplicate ids, exact eight-recipe allowlist, **`recipeKind`** coverage (**`process`** + **`make`**), cross-check that every **`itemId`** in inputs/outputs exists in `content/items/`, every **`requiredSkillId`** exists in `content/skills/`.
|
||||||
|
- Designer note in [E3_M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) + `content/README.md`.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Server loader, craft engine, HTTP.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] PR gate validates recipe JSON against schema.
|
||||||
|
- [ ] Exactly eight prototype recipe ids; duplicate `id` fails CI.
|
||||||
|
- [ ] Every input/output references a valid frozen item id; every recipe references **`refine`**.
|
||||||
|
- [ ] Stable id list documented in module doc freeze box.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-02 — Server recipe catalog load (fail-fast)
|
||||||
|
|
||||||
|
**Goal:** Disk → host: startup load of `content/recipes/*.json` with CI-parity validation.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- Loader + catalog types under `server/NeonSprawl.Server/Game/Crafting/` (path TBD in plan).
|
||||||
|
- Fail-fast on malformed files, duplicate ids, unknown item ids (cross-check **`IItemDefinitionRegistry`**), unknown skill ids (cross-check **`ISkillDefinitionRegistry`**).
|
||||||
|
- Unit tests (AAA) for loader happy path and failure modes.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Per-player craft state, HTTP, inventory mutation.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] Server refuses boot when recipe catalog invalid (mirror E3.M1 / E3.M3 catalog behavior).
|
||||||
|
- [ ] Catalog resolves `RecipeDef` and I/O rows by recipe `id`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-03 — Recipe definition registry + DI
|
||||||
|
|
||||||
|
**Goal:** Injectable `IRecipeDefinitionRegistry` consumed by craft engine and HTTP.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- `RecipeDefinitionRegistry` implementation + DI registration.
|
||||||
|
- Unit tests (AAA): lookup, unknown id, input/output metadata, **`recipeKind`**.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- HTTP, persistence.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] Host resolves registry from DI; unknown `recipeId` distinguishable from valid ids in tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-04 — GET world recipe-definitions
|
||||||
|
|
||||||
|
**Goal:** Versioned read-only catalog for Bruno, client preview, and tooling — mirror [NEO-53](https://linear.app/neon-sprawl/issue/NEO-53) item-definitions pattern.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- `GET /game/world/recipe-definitions` + DTOs + API tests (AAA).
|
||||||
|
- Bruno folder `bruno/neon-sprawl-server/recipe-definitions/`.
|
||||||
|
- `server/README.md` section.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Per-player craft history or cooldown state.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] GET returns all eight prototype recipes with schema version field and input/output summary fields needed for QA.
|
||||||
|
- [ ] Bruno happy path documented.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-05 — CraftResult engine (consume inputs, grant outputs, refine XP)
|
||||||
|
|
||||||
|
**Goal:** Single server operation that resolves **`CraftResult`**: validate recipe + inventory, atomically remove inputs and add outputs via **`PlayerInventoryOperations`**, award **`refine`** XP via **`RefineActivitySkillXpGrant`** on success.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- `CraftOperations` / `CraftResult` types in `server/NeonSprawl.Server/Game/Crafting/`.
|
||||||
|
- Stable deny reason codes: `unknown_recipe`, `insufficient_materials`, `inventory_full`, `invalid_quantity` (if quantity validated).
|
||||||
|
- Rollback input removal when output grant fails.
|
||||||
|
- Unit tests (AAA) with stub inventory + progression stores; NEO-42 grant invoked on success path.
|
||||||
|
- Document **`CraftResult`** shape in module doc (server-internal until promoted to wire).
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Craft HTTP (E3M2-06).
|
||||||
|
- Client HUD.
|
||||||
|
- Skill level gates, station checks, craft failure RNG.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] Successful craft removes configured inputs and adds outputs deterministically.
|
||||||
|
- [ ] Insufficient materials returns stable `reasonCode` with **no** inventory change.
|
||||||
|
- [ ] Full bag on output returns stable `reasonCode`; inputs are **not** consumed (or are rolled back).
|
||||||
|
- [ ] **`refine`** skill XP increases by **10** on success (NEO-42).
|
||||||
|
- [ ] **`make_prototype_armor`** respects equipment slot rules from E3.M3 when bag is full but equip slot empty.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-06 — Craft HTTP + Bruno gather→refine→make spine
|
||||||
|
|
||||||
|
**Goal:** Versioned **`POST /game/players/{id}/craft`** wired to craft engine; Bruno documents full prototype loop from gather through combat-relevant output.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- `POST /game/players/{id}/craft` + DTOs + API tests (AAA).
|
||||||
|
- Known-player gate via `IPositionStateStore` (NEO-37 / NEO-55 pattern).
|
||||||
|
- Bruno `bruno/neon-sprawl-server/craft/` with spine: seed or gather **`scrap_metal_bulk`** → **`refine_scrap_standard`** → **`make_field_stim_mk0`** → GET inventory + GET skill-progression shows **`refine`** XP delta.
|
||||||
|
- Bruno deny paths: unknown recipe, insufficient materials, inventory full on output.
|
||||||
|
- `server/README.md`; integration tests (AAA) where practical.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Godot craft UI (optional follow-up).
|
||||||
|
- Station interact / world bench placement.
|
||||||
|
|
||||||
|
**Acceptance criteria**
|
||||||
|
|
||||||
|
- [ ] POST success returns structured **`CraftResult`** (or HTTP envelope) matching engine outcome.
|
||||||
|
- [ ] Bruno spine completes without manual inventory POST shortcuts (gather interact allowed for scrap).
|
||||||
|
- [ ] At least one deny path documented per major `reasonCode`.
|
||||||
|
- [ ] Epic 3 Slice 3 acceptance: player completes **gather → refine → usable item** (`field_stim_mk0` consumable).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### E3M2-07 — `item_crafted` / `craft_failed` telemetry hook sites
|
||||||
|
|
||||||
|
**Goal:** Comment-only hooks on craft success and structured deny for future E9.M1 catalog events.
|
||||||
|
|
||||||
|
**In scope**
|
||||||
|
|
||||||
|
- Hook placement in **`CraftOperations`** (success + deny before commit — engine-only, mirror NEO-64 gather pattern).
|
||||||
|
- `TODO(E9.M1)` comments; no production logging.
|
||||||
|
- README + module doc pointer; note **`time-to-first-craft`** is a future metric derived from these events + session context (no separate hook in prototype).
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
|
||||||
|
- Telemetry ingest, dashboards.
|
||||||
|
- HTTP-layer duplicate hooks if engine owns the contract.
|
||||||
|
|
||||||
|
**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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## After this backlog
|
||||||
|
|
||||||
|
- Epic 3 prototype **gather→craft** loop is complete for Phase 1 crafting relevance gate; Slice 4 (**E3.M4** + **E3.M5**) remains pre-production.
|
||||||
|
- Track delivery in Linear; keep `blockedBy` links synchronized if scope changes.
|
||||||
|
- For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
|
||||||
|
- Add `docs/manual-qa/{NEO-XX}.md` when craft HTTP lands (E3M2-06+).
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- [E3_M2_RefinementAndRecipeExecution.md](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md)
|
||||||
|
- [E3M1-prototype-backlog.md](E3M1-prototype-backlog.md)
|
||||||
|
- [E3M3-prototype-backlog.md](E3M3-prototype-backlog.md)
|
||||||
|
- [epic_03_crafting_economy.md](../decomposition/epics/epic_03_crafting_economy.md)
|
||||||
|
- [crafting.md](../game-design/crafting.md)
|
||||||
|
- [NEO-42 implementation plan](NEO-42-implementation-plan.md)
|
||||||
|
|
@ -197,6 +197,7 @@ Working backlog for **Epic 3 — Slice 1** ([items and inventory MVP](../decompo
|
||||||
## After this backlog
|
## After this backlog
|
||||||
|
|
||||||
- **[E3.M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md)** Slice 2 decomposed — [E3M1-prototype-backlog.md](E3M1-prototype-backlog.md) ([NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)–[NEO-64](https://linear.app/neon-sprawl/issue/NEO-64)).
|
- **[E3.M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md)** Slice 2 decomposed — [E3M1-prototype-backlog.md](E3M1-prototype-backlog.md) ([NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)–[NEO-64](https://linear.app/neon-sprawl/issue/NEO-64)).
|
||||||
|
- **[E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md)** Slice 3 decomposed — [E3M2-prototype-backlog.md](E3M2-prototype-backlog.md) ([NEO-65](https://linear.app/neon-sprawl/issue/NEO-65)–[NEO-71](https://linear.app/neon-sprawl/issue/NEO-71)).
|
||||||
- Track delivery in Linear; keep `blockedBy` links synchronized if scope changes.
|
- Track delivery in Linear; keep `blockedBy` links synchronized if scope changes.
|
||||||
- For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
|
- For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
|
||||||
- Add `docs/manual-qa/{NEO-XX}.md` when HTTP surfaces land (E3M3-06+).
|
- Add `docs/manual-qa/{NEO-XX}.md` when HTTP surfaces land (E3M3-06+).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue