diff --git a/docs/decomposition/modules/CT_M1_ContentValidationPipeline.md b/docs/decomposition/modules/CT_M1_ContentValidationPipeline.md index 5848fe0..4aeeaf0 100644 --- a/docs/decomposition/modules/CT_M1_ContentValidationPipeline.md +++ b/docs/decomposition/modules/CT_M1_ContentValidationPipeline.md @@ -47,7 +47,9 @@ Content tooling **Slice 1** — schemas + CI; **Slice 4** — server parity hard **Item catalogs ([NEO-50](https://linear.app/neon-sprawl/issue/NEO-50)):** the same script validates each row in `content/items/*_items.json` against [`content/schemas/item-def.schema.json`](../../../content/schemas/item-def.schema.json), rejects **duplicate `id`** across files, and (Slice 1) enforces the **frozen six-item** id set plus **exactly one row per `prototypeRole`** (`material` through `equip_stub`). -**Resource node catalogs ([NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)):** the same script validates `content/resource-nodes/*_resource_nodes.json` rows against [`content/schemas/resource-node-def.schema.json`](../../../content/schemas/resource-node-def.schema.json) and `*_resource_yields.json` rows against [`content/schemas/resource-yield-row.schema.json`](../../../content/schemas/resource-yield-row.schema.json); rejects **duplicate `nodeDefId`**; (Slice 2) enforces the **frozen four-node** id set, **one row per `gatherLens`**, exactly **one yield row per node**, yield **`itemId`** cross-check against item catalogs, and **only `scrap_metal_bulk`** as yield item in prototype. Extend the script when additional catalogs and schemas ship. +**Resource node catalogs ([NEO-57](https://linear.app/neon-sprawl/issue/NEO-57)):** the same script validates `content/resource-nodes/*_resource_nodes.json` rows against [`content/schemas/resource-node-def.schema.json`](../../../content/schemas/resource-node-def.schema.json) and `*_resource_yields.json` rows against [`content/schemas/resource-yield-row.schema.json`](../../../content/schemas/resource-yield-row.schema.json); rejects **duplicate `nodeDefId`**; (Slice 2) enforces the **frozen four-node** id set, **one row per `gatherLens`**, exactly **one yield row per node**, yield **`itemId`** cross-check against item catalogs, and **only `scrap_metal_bulk`** as yield item in prototype. + +**Recipe catalogs ([NEO-65](https://linear.app/neon-sprawl/issue/NEO-65)):** the same script validates each row in `content/recipes/*_recipes.json` against [`content/schemas/recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) (I/O rows via [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json)), rejects **duplicate `id`** across files, cross-checks every input/output **`itemId`** against item catalogs and every **`requiredSkillId`** against skill catalogs, and (Slice 3) enforces the **frozen eight-recipe** id set, at least one **`process`** and one **`make`** row, and **`requiredSkillId: refine`** on every row. Extend the script when additional catalogs and schemas ship. **Decomposition vocabulary:** the same workflow runs [`scripts/check_decomposition_language.py`](../../../scripts/check_decomposition_language.py) (stdlib only) over `docs/decomposition/**/*.md` to catch wording that treats **combat** as a leveled **`SkillDef`** line instead of **gig** progression (see script docstring for patterns). Adjust the script if a future doc needs a documented exception. diff --git a/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md b/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md index 2aca9fc..9645058 100644 --- a/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md +++ b/docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md @@ -68,7 +68,7 @@ The **first shipped eight-recipe spine** under `content/recipes/*.json` is **fro | **`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. +**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 diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md index 63ec1ba..ce80e62 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 | 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. **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](https://linear.app/neon-sprawl/issue/NEO-65) … [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 | 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-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-66](https://linear.app/neon-sprawl/issue/NEO-66) → [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md) — **E3M2-02**–**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](https://linear.app/neon-sprawl/issue/NEO-66) … [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/plans/E3M2-prototype-backlog.md b/docs/plans/E3M2-prototype-backlog.md index cb6bac7..f0575fa 100644 --- a/docs/plans/E3M2-prototype-backlog.md +++ b/docs/plans/E3M2-prototype-backlog.md @@ -78,10 +78,12 @@ Working backlog for **Epic 3 — Slice 3** ([recipes and crafting pipeline](../d **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. +- [x] PR gate validates recipe JSON against schema. +- [x] Exactly eight prototype recipe ids; duplicate `id` fails CI. +- [x] Every input/output references a valid frozen item id; every recipe references **`refine`**. +- [x] Stable id list documented in module doc freeze box. + +**Landed ([NEO-65](https://linear.app/neon-sprawl/issue/NEO-65)):** [`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), CI gates in [`validate_content.py`](../../scripts/validate_content.py); plan [NEO-65-implementation-plan.md](NEO-65-implementation-plan.md). --- diff --git a/docs/plans/NEO-65-implementation-plan.md b/docs/plans/NEO-65-implementation-plan.md index c14da8c..70625d0 100644 --- a/docs/plans/NEO-65-implementation-plan.md +++ b/docs/plans/NEO-65-implementation-plan.md @@ -42,10 +42,10 @@ ## Acceptance criteria checklist -- [ ] 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. +- [x] PR gate validates recipe JSON against schema. +- [x] Exactly eight prototype recipe ids; duplicate `id` fails CI. +- [x] Every input/output references a valid frozen item id; every recipe references **`refine`**. +- [x] Stable id list documented in module doc freeze box. ## Technical approach @@ -124,6 +124,6 @@ No dedicated pytest module in-repo today (same as NEO-50 / NEO-57). |-----------------|----------------------|--------| | **Catalog growth** | Slice 3 gate requires **exactly** the eight frozen ids; expanding the roster needs a follow-up issue to change `PROTOTYPE_SLICE3_RECIPE_IDS`. | **adopted** | | **C# vs Python drift** | None until NEO-66; document “keep in sync” constant names in both places when server load lands. | **adopted** | -| **Branch carries E3M2 decomposition prep** | Commit decomposition doc deltas (freeze table, backlog, README) with implementation or as first doc batch on this branch. | **pending** | +| **Branch carries E3M2 decomposition prep** | Commit decomposition doc deltas (freeze table, backlog, README) with implementation or as first doc batch on this branch. | **done** — prep landed in prior commits; catalog + CI in this story. | None blocking.