# 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; Slice 5 client [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74) craft UI + capstone [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75) playable loop landed |

## 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) (**landed** — `recipe_definitions_client.gd`, `craft_client.gd`, `craft_recipe_panel.gd`, [`NEO-74` manual QA](../../manual-qa/NEO-74.md)).

## 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)
