# E3.M2 — RefinementAndRecipeExecution ## Summary | Field | Value | |--------|--------| | **Module ID** | E3.M2 | | **Epic** | [Epic 3 — Crafting Economy](../epics/epic_03_crafting_economy.md) | | **Stage target** | Prototype | | **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-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) | Field | Role | |-------|------| | **`Success`** | `true` when inputs removed, outputs granted, and refine XP applied. | | **`ReasonCode`** | Stable deny string when **`Success`** is `false`; omitted on success. | | **`InputsConsumed`** | Scaled `{ itemId, quantity }` rows removed on success. | | **`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: **`item_crafted`** / **`craft_failed`** in **`CraftOperations.TryCraft`** (NEO-71). ## Purpose Implements the processing and crafting pipeline: raw-to-refined flows and recipe execution with server-authoritative inventory mutations. Bridges gathered materials ([E3.M1](E3_M1_ResourceNodeAndGatherLoop.md)) and item definitions ([E3.M3](E3_M3_ItemizationAndInventorySchema.md)) into usable outputs for combat, quests, and economy loops. ## Responsibilities - Validate `CraftRequest` against `RecipeDef`, costs, and player inventory state. - Produce `CraftResult` (success/failure, outputs, failures with reason codes). - Support prototype recipe set (e.g. 8–12 starter recipes per epic slice minimums). ## Key contracts | Contract | Role | |----------|------| | `RecipeDef` | Inputs, outputs, skill gates, station requirements (as scoped for prototype). | | `CraftRequest` | Actor, recipe, quantities, optional targeting for UI/server sync. | | `CraftResult` | Deterministic outcome for inventory and telemetry. | ## Module dependencies - **E3.M1** — [ResourceNodeAndGatherLoop](E3_M1_ResourceNodeAndGatherLoop.md): refined materials from gather outputs into crafting inputs. - **E3.M3** — [ItemizationAndInventorySchema](E3_M3_ItemizationAndInventorySchema.md): `ItemDef`, `ItemInstance`, inventory slots for costs and outputs. ## Dependents (by design) - **E7.M1** — [QuestStateMachine](E7_M1_QuestStateMachine.md): quest steps may require crafted items or craft completion. - **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. ## 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, **`requiredSkillId: refine`** on every row, input/output **`itemId`** cross-check against the frozen item catalog, and duplicate-`id` rejection across all recipe JSON files. Implementation: [NEO-65 plan](../../plans/NEO-65-implementation-plan.md). ## Related implementation slices 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). **Client (Slice 5):** craft UI — [E3S5-client-prototype-backlog.md](../../plans/E3S5-client-prototype-backlog.md) **E3S5-03** [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74). ## Risks and telemetry - Duplication or negative stacks: server-authoritative craft and inventory; integrity signals via E9.M4 when live. - Quest/economy parity: lint reward bundles against E6.M4 and E3.M5 policies in later phases. ## Source anchors - Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Core Epic Map, Epic 3. - [Module dependency register](module_dependency_register.md)