NEO-67: add implementation plan for recipe definition registry
parent
565906ff8c
commit
7188c5b558
|
|
@ -0,0 +1,104 @@
|
|||
# NEO-67 — Implementation plan
|
||||
|
||||
## Story reference
|
||||
|
||||
| Field | Value |
|
||||
|--------|--------|
|
||||
| **Key** | NEO-67 |
|
||||
| **Title** | E3.M2: Recipe definition registry + DI |
|
||||
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-67/e3m2-recipe-definition-registry-di |
|
||||
| **Module** | [E3.M2 — RefinementAndRecipeExecution](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) · Epic 3 Slice 3 · backlog **E3M2-03** |
|
||||
| **Branch** | `NEO-67-recipe-definition-registry-di` |
|
||||
| **Precursor** | [NEO-66](https://linear.app/neon-sprawl/issue/NEO-66) — fail-fast `RecipeDefinitionCatalog` load (**Done** on `main`) |
|
||||
| **Pattern** | [NEO-52](https://linear.app/neon-sprawl/issue/NEO-52) / [NEO-59](https://linear.app/neon-sprawl/issue/NEO-59) — thin registry adapter over startup catalog + DI; strict split after NEO-66 loader |
|
||||
| **Blocks** | [NEO-68](https://linear.app/neon-sprawl/issue/NEO-68) — GET world recipe-definitions; [NEO-69](https://linear.app/neon-sprawl/issue/NEO-69) — CraftResult engine |
|
||||
|
||||
## Kickoff clarifications
|
||||
|
||||
| Topic | Question | Agent recommendation | Answer |
|
||||
|--------|----------|----------------------|--------|
|
||||
| **Enumeration API** | Include `GetDefinitionsInIdOrder()` now? | **Yes** — mirror `IItemDefinitionRegistry` / `ISkillDefinitionRegistry`; [NEO-68](https://linear.app/neon-sprawl/issue/NEO-68) `GET /game/world/recipe-definitions` will need ordered defs without reaching into `RecipeDefinitionCatalog`. | **User:** include enumeration. |
|
||||
| **Lookup naming** | `TryGetDefinition` vs `TryGetRecipe`? | **`TryGetDefinition(string? recipeId, …)`** — same naming as item/skill registries; catalog keeps `TryGetRecipe` for direct catalog access. | **User:** `TryGetDefinition`. |
|
||||
| **NEO-66 vs NEO-67 split** | Combine registry with catalog load? | **Strict split** (decided on NEO-66 kickoff) — NEO-66 ships loader + catalog; this story adds injectable registry only. | **Adopted** — NEO-66 plan. |
|
||||
| **Program.cs eager resolve** | Eager-resolve `IRecipeDefinitionRegistry` at boot? | **Omit** — `RecipeDefinitionCatalog` is already eager-resolved in `Program.cs` (NEO-66); registry is a thin adapter (NEO-52 default). | **Adopted** |
|
||||
| **I/O + `recipeKind` exposure** | Separate TryGet methods for inputs/outputs? | **No** — embedded on `RecipeDefRow` returned by `TryGetDefinition`; backlog tests cover metadata + `recipeKind` via one lookup (unlike node+yield on NEO-59). | **Adopted** |
|
||||
| **Runtime validation** | Re-validate recipes in registry? | **No** — catalog load is fail-fast (NEO-66); registry delegates to loaded rows only. | **Adopted** |
|
||||
|
||||
## Goal, scope, and out-of-scope
|
||||
|
||||
**Goal:** Provide **`IRecipeDefinitionRegistry`** backed by the startup-loaded **`RecipeDefinitionCatalog`**: resolve by stable **`recipeId`**, enumerate definitions in id order, expose prototype metadata (`displayName`, `recipeKind`, `requiredSkillId`, embedded `inputs` / `outputs`). Register in DI so **NEO-68+** (HTTP), **NEO-69** (craft engine), and future callers depend on the interface instead of the catalog type.
|
||||
|
||||
**In scope (from Linear + [E3M2-03](E3M2-prototype-backlog.md#e3m2-03--recipe-definition-registry--di)):**
|
||||
|
||||
- `RecipeDefinitionRegistry` thin adapter over `RecipeDefinitionCatalog`.
|
||||
- DI registration alongside existing catalog singleton.
|
||||
- Unit tests (AAA): known prototype id lookup + `recipeKind` + I/O metadata; unknown `recipeId` returns false without throwing; enumeration order; host resolves registry from DI.
|
||||
|
||||
**Out of scope (from Linear + backlog):**
|
||||
|
||||
- HTTP ([NEO-68](https://linear.app/neon-sprawl/issue/NEO-68)).
|
||||
- Persistence, per-player craft state, inventory mutation ([NEO-69+](E3M2-prototype-backlog.md)).
|
||||
- Changing loader, Slice 3 gate, or catalog load semantics (NEO-66).
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [ ] DI resolves `IRecipeDefinitionRegistry` at host startup (test via `InMemoryWebApplicationFactory`).
|
||||
- [ ] Unit tests (AAA): lookup for known prototype `recipeId` (e.g. `refine_scrap_standard`, `make_field_stim_mk0`) with expected `recipeKind`, `requiredSkillId`, and input/output rows.
|
||||
- [ ] Unit tests (AAA): unknown `recipeId` returns false / absent without throwing.
|
||||
- [ ] `GetDefinitionsInIdOrder` returns all eight loaded rows ordered by `id` (ordinal).
|
||||
|
||||
## Technical approach
|
||||
|
||||
1. **`IRecipeDefinitionRegistry`** — mirror [`IItemDefinitionRegistry`](../../server/NeonSprawl.Server/Game/Items/IItemDefinitionRegistry.cs):
|
||||
- `TryGetDefinition(string? recipeId, [NotNullWhen(true)] out RecipeDefRow? definition)` — null and unknown ids return false without throwing.
|
||||
- `GetDefinitionsInIdOrder()` — all rows ordered by `RecipeDefRow.Id` (ordinal).
|
||||
- XML remarks: craft engine / inventory callers (NEO-69+) should use this interface; HTTP read model (NEO-68) should not reach into `RecipeDefinitionCatalog`.
|
||||
|
||||
2. **`RecipeDefinitionRegistry`** — primary-constructor adapter over `RecipeDefinitionCatalog` (same pattern as [`ItemDefinitionRegistry`](../../server/NeonSprawl.Server/Game/Items/ItemDefinitionRegistry.cs)).
|
||||
|
||||
3. **DI** — extend [`AddRecipeDefinitionCatalog`](../../server/NeonSprawl.Server/Game/Crafting/RecipeCatalogServiceCollectionExtensions.cs) to register `IRecipeDefinitionRegistry` → `RecipeDefinitionRegistry` after catalog registration. No change to `Program.cs` eager-resolve (catalog only).
|
||||
|
||||
4. **Comments** — update `RecipeDefinitionCatalog` summary to point at `IRecipeDefinitionRegistry`; update [server README](../../server/README.md) recipe-catalog section (currently says “until NEO-67”).
|
||||
|
||||
5. **Tests** — new `RecipeDefinitionRegistryTests.cs` mirroring [`ItemDefinitionRegistryTests`](../../server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionRegistryTests.cs): in-memory catalog helper, loader-backed prototype fixture via `RecipeCatalogTestPaths` + item/skill id stubs, host DI test.
|
||||
|
||||
## Files to add
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `server/NeonSprawl.Server/Game/Crafting/IRecipeDefinitionRegistry.cs` | Public contract: lookup, enumerate, remarks for NEO-68/69 callers. |
|
||||
| `server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionRegistry.cs` | Singleton adapter over `RecipeDefinitionCatalog`. |
|
||||
| `server/NeonSprawl.Server.Tests/Game/Crafting/RecipeDefinitionRegistryTests.cs` | AAA unit + host DI tests (mirror `ItemDefinitionRegistryTests`). |
|
||||
| `docs/plans/NEO-67-implementation-plan.md` | This plan. |
|
||||
|
||||
## Files to modify
|
||||
|
||||
| Path | Rationale |
|
||||
|------|-----------|
|
||||
| `server/NeonSprawl.Server/Game/Crafting/RecipeCatalogServiceCollectionExtensions.cs` | Register `IRecipeDefinitionRegistry` → `RecipeDefinitionRegistry` after catalog singleton. |
|
||||
| `server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionCatalog.cs` | Tighten summary comment to reference `IRecipeDefinitionRegistry` (replace “NEO-67 adds…” placeholder). |
|
||||
| `server/README.md` | Recipe catalog section: document `IRecipeDefinitionRegistry` as preferred lookup surface. |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E3.M2 row — note NEO-67 registry when landed. |
|
||||
|
||||
## Tests
|
||||
|
||||
| Test file | What it covers |
|
||||
|-----------|----------------|
|
||||
| `server/NeonSprawl.Server.Tests/Game/Crafting/RecipeDefinitionRegistryTests.cs` | **Unit:** `TryGetDefinition` for `refine_scrap_standard` returns true with `RecipeKind` `process`, `RequiredSkillId` `refine`, expected input/output `ItemId`/`Quantity`; `make_field_stim_mk0` returns `make` with multi-input rows; null and unknown id return false without throw; `GetDefinitionsInIdOrder` count (8) and ordinal order for prototype fixture; loader-backed prototype fixture matches catalog. **Host:** `InMemoryWebApplicationFactory` resolves `IRecipeDefinitionRegistry` and finds `refine_scrap_standard`. |
|
||||
|
||||
**Manual QA:** Skipped — no player-visible HTTP; acceptance is fully covered by AAA unit/host tests (same as NEO-52 / NEO-59).
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
| Question / risk | Agent recommendation | Status |
|
||||
|-----------------|----------------------|--------|
|
||||
| **Catalog vs registry for game code** | New callers inject `IRecipeDefinitionRegistry`; leave `RecipeDefinitionCatalog` eager-resolve in `Program.cs` for fail-fast only. | **adopted** |
|
||||
| **Duplicate API on catalog** | Keep `RecipeDefinitionCatalog.TryGetRecipe` for loader/tests; registry is the game-facing surface (NEO-52 precedent). | **adopted** |
|
||||
| **NEO-68 HTTP projection** | API layer maps `RecipeDefRow` fields to DTOs via registry enumeration + lookup; no combined read model in NEO-67. | **adopted** |
|
||||
|
||||
## Decisions (kickoff)
|
||||
|
||||
- **`GetDefinitionsInIdOrder()` included** — enumeration for NEO-68 HTTP without catalog coupling (user confirmed).
|
||||
- **`TryGetDefinition` naming** — consistent with item/skill registries; parameter is `recipeId` (user confirmed).
|
||||
- **No `Program.cs` registry eager-resolve** — catalog fail-fast only (NEO-52 pattern).
|
||||
- **I/O and `recipeKind` on `RecipeDefRow`** — single lookup surface; no separate TryGet methods.
|
||||
Loading…
Reference in New Issue