8.7 KiB
NEO-59 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-59 |
| Title | E3.M1: Resource node definition registry + DI |
| Linear | https://linear.app/neon-sprawl/issue/NEO-59/e3m1-resource-node-definition-registry-di |
| Module | E3.M1 — ResourceNodeAndGatherLoop · Epic 3 Slice 2 · backlog E3M1-03 |
| Branch | NEO-59-e3m1-resource-node-definition-registry-di |
| Precursor | NEO-58 — fail-fast ResourceNodeCatalog load (merged to main via PR #93) |
| Pattern | NEO-52 — thin registry adapter over startup catalog + DI; strict split after NEO-58 loader |
Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| Enumeration API | Include GetDefinitionsInIdOrder() now? |
Yes — mirror IItemDefinitionRegistry / ISkillDefinitionRegistry; NEO-60 GET /game/world/resource-node-definitions will need ordered defs without reaching into ResourceNodeCatalog. |
User: include enumeration. |
| Yield access | How should yield rows be exposed? | Separate TryGetYield(nodeDefId) alongside TryGetDefinition — mirrors ResourceNodeCatalog.TryGetNode / TryGetYield; backlog E3M1-03 tests call for yield metadata + capacity (maxGathers) fields. |
User: separate TryGetYield. |
| Lookup naming | TryGetDefinition vs TryGetNode? |
TryGetDefinition(string? nodeDefId, …) — same naming as item/skill registries; catalog keeps TryGetNode for direct catalog access. |
Adopted — NEO-52 / NEO-58 precedent. |
| Program.cs eager resolve | Eager-resolve IResourceNodeDefinitionRegistry at boot? |
Omit — ResourceNodeCatalog is already eager-resolved in Program.cs (NEO-58); registry is a thin adapter (NEO-52 default). |
Adopted |
| Runtime validation | Re-validate nodes/yields in registry? | No — catalog load is fail-fast (NEO-58); registry delegates to loaded rows only. | Adopted |
Goal, scope, and out-of-scope
Goal: Provide IResourceNodeDefinitionRegistry backed by the startup-loaded ResourceNodeCatalog: resolve node defs and yield rows by stable nodeDefId, enumerate node defs in id order, expose prototype metadata (displayName, gatherLens, maxGathers, yield itemId / quantity). Register in DI so NEO-60+ (HTTP), NEO-61 (depletion), NEO-62 (gather engine), and interact wiring depend on the interface instead of the catalog type.
In scope (from Linear + E3M1-03):
ResourceNodeDefinitionRegistrythin adapter overResourceNodeCatalog.- DI registration alongside existing catalog singleton.
- Unit tests (AAA): known prototype id lookup + node metadata + yield metadata; unknown
nodeDefIdreturns false without throwing; enumeration order; host resolves registry from DI.
Out of scope (from Linear + backlog):
- HTTP (NEO-60).
- Persistence of depletion state (NEO-61).
- Gather engine / inventory mutation (NEO-62).
- Changing loader, Slice 2 gate, or catalog load semantics (NEO-58).
Acceptance criteria checklist
- DI resolves
IResourceNodeDefinitionRegistryat host startup (test viaInMemoryWebApplicationFactory). - Unit tests (AAA): lookup for known prototype
nodeDefId(e.g.prototype_resource_node_alpha) with expected node metadata (gatherLens,maxGathers) and yield metadata (itemId,quantity). - Unit tests (AAA): unknown
nodeDefIdreturns false / absent without throwing (both node and yield lookups). GetDefinitionsInIdOrderreturns all loaded node defs ordered bynodeDefId(ordinal).
Technical approach
-
IResourceNodeDefinitionRegistry— mirrorIItemDefinitionRegistry:TryGetDefinition(string? nodeDefId, [NotNullWhen(true)] out ResourceNodeDefRow? definition)— null and unknown ids return false without throwing.TryGetYield(string? nodeDefId, [NotNullWhen(true)] out ResourceYieldRow? yield)— same contract for yield rows.GetDefinitionsInIdOrder()— all node defs ordered byResourceNodeDefRow.NodeDefId(ordinal).- XML remarks: gather engine / depletion / interact callers (NEO-61+) should use this interface; HTTP read model (NEO-60) should not reach into
ResourceNodeCatalog.
-
ResourceNodeDefinitionRegistry— primary-constructor adapter overResourceNodeCatalog(same pattern asItemDefinitionRegistry). -
DI — extend
AddResourceNodeCatalogto registerIResourceNodeDefinitionRegistry→ResourceNodeDefinitionRegistryafter catalog registration. No change toProgram.cseager-resolve (catalog only). -
Comments — update
ResourceNodeCatalogsummary to point atIResourceNodeDefinitionRegistry; update server README resource-node catalog section (currently says “lands in NEO-59”). -
Tests — new
ResourceNodeDefinitionRegistryTests.csmirroringItemDefinitionRegistryTests: in-memory catalog helper, loader-backed prototype fixture viaResourceNodeCatalogTestPaths, host DI test.
Files to add
| Path | Purpose |
|---|---|
server/NeonSprawl.Server/Game/Gathering/IResourceNodeDefinitionRegistry.cs |
Public contract: node lookup, yield lookup, enumerate, remarks for NEO-60/61/62 callers. |
server/NeonSprawl.Server/Game/Gathering/ResourceNodeDefinitionRegistry.cs |
Singleton adapter over ResourceNodeCatalog. |
server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeDefinitionRegistryTests.cs |
AAA unit + host DI tests (mirror ItemDefinitionRegistryTests). |
Files to modify
| Path | Rationale |
|---|---|
server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalogServiceCollectionExtensions.cs |
Register IResourceNodeDefinitionRegistry → ResourceNodeDefinitionRegistry after catalog singleton. |
server/NeonSprawl.Server/Game/Gathering/ResourceNodeCatalog.cs |
Tighten summary comment to reference IResourceNodeDefinitionRegistry (replace “Prefer registry in NEO-59” placeholder). |
server/README.md |
Resource-node catalog section: document IResourceNodeDefinitionRegistry as preferred lookup surface. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E3.M1 row — note NEO-59 registry when landed. |
Tests
| Test file | What it covers |
|---|---|
server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeDefinitionRegistryTests.cs |
Unit: TryGetDefinition for prototype_resource_node_alpha returns true with GatherLens, MaxGathers, DisplayName; TryGetYield for same id returns true with ItemId (scrap_metal_bulk) and Quantity; null and unknown id return false without throw for both methods; GetDefinitionsInIdOrder count (4) and ordinal order for multi-node fixture; loader-backed prototype fixture matches catalog. Host: InMemoryWebApplicationFactory resolves IResourceNodeDefinitionRegistry and finds prototype_resource_node_alpha with expected yield. |
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
| Catalog vs registry for game code | New callers inject IResourceNodeDefinitionRegistry; leave ResourceNodeCatalog eager-resolve in Program.cs for fail-fast only. |
adopted |
| Duplicate API on catalog | Keep ResourceNodeCatalog.TryGetNode / TryGetYield for loader/tests; registry is the game-facing surface (NEO-52 precedent). |
adopted |
| NEO-60 HTTP projection | API layer joins node + yield via registry’s two TryGet methods when building DTOs; no combined read model in NEO-59. | adopted |
Decisions (kickoff)
GetDefinitionsInIdOrder()included — enumeration for NEO-60 HTTP without catalog coupling.- Separate
TryGetYield— yield metadata available to gather engine / tests without a combined DTO. TryGetDefinitionnaming — consistent with item/skill registries; parameter isnodeDefId.- No
Program.csregistry eager-resolve — catalog fail-fast only (NEO-52 pattern).