9.6 KiB
NEO-57 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-57 |
| Title | E3.M1: Prototype ResourceNodeDef + yield tables + schemas + CI |
| Linear | https://linear.app/neon-sprawl/issue/NEO-57/e3m1-prototype-resourcenodedef-yield-tables-schemas-ci |
| Module | E3.M1 — ResourceNodeAndGatherLoop · Epic 3 Slice 2 · backlog E3M1-01 |
| Branch | NEO-57-e3m1-prototype-resourcenodedef-yield-schemas-ci |
| Precursor | NEO-50 — frozen six-item catalog (scrap_metal_bulk); NEO-41 — prototype resource_node interact XP anchor (unchanged this story) |
| Pattern | NEO-50 — row JSON Schema + catalog envelope + scripts/validate_content.py Slice gate |
Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| Catalog layout | One file vs two files / schemas? | Two files + two row schemas — prototype_resource_nodes.json + prototype_resource_yields.json; mirrors backlog paths and E3M1-02 loader split. |
User: two files + two row schemas. |
maxGathers in content |
Include on ResourceNodeDef now? |
Yes — depletion contract is documented in module freeze; avoids schema churn before E3M1-04. | User: yes now. |
| Yield quantities | Same vs per-node quantities? | Differentiated (e.g. 1 / 2 / 3 / 5) — proves per-node yield tables without new item ids. | User: differentiated. |
| Decomposition docs | How to land E3M1 backlog + freeze table? | Merge chore/e3m1-slice2-decomposition into story branch first. |
User: merge chore branch. |
Goal, scope, and out-of-scope
Goal: Lock content shape and CI validation for four frozen ResourceNodeDef rows and fixed per-node yield rows before any server catalog load (E3M1-02).
In scope (from Linear + E3M1-01):
- JSON Schemas:
resource-node-def.schema.json,resource-yield-row.schema.json(single-row contracts). content/resource-nodes/prototype_resource_nodes.json— four frozennodeDefIdvalues aligned to interactable ids (freeze table).content/resource-nodes/prototype_resource_yields.json— one yield row per node;itemIdonlyscrap_metal_bulk; differentiatedquantityper node.scripts/validate_content.py— schema validation, duplicatenodeDefId, exact four-node allowlist,gatherLenscoverage (one per lens), cross-check yielditemIdagainst loaded item catalog, exactly one yield row per knownnodeDefId.- Designer note already in E3.M1 module doc +
content/README.mdSlice 2 paragraph (from merged decomposition).
Out of scope (from Linear):
- Server loader, depletion store, inventory grants, HTTP,
PrototypeInteractableRegistrynew anchors (E3M1-05+), Bruno.
Acceptance criteria checklist
- PR gate validates resource-node JSON against schema.
- Exactly four prototype node ids; duplicate
nodeDefIdfails CI. - Every yield references a valid frozen item id (
scrap_metal_bulk). - Stable id list documented in module doc freeze box.
Technical approach
-
Row schema —
ResourceNodeDef(resource-node-def.schema.json): Draft 2020-12 object;additionalProperties: false. Required:nodeDefId,displayName,gatherLens,maxGathers.nodeDefIdpattern^[a-z][a-z0-9_]*$.gatherLensenum:consumer_salvage|subsurface|bio|urban_bulk(matches gathering.md).maxGathersinteger ≥ 1. -
Row schema — yield row (
resource-yield-row.schema.json): Required:nodeDefId,itemId,quantity.quantityinteger ≥ 1.itemIduses same id pattern as items. -
Catalog envelopes (mirror items):
prototype_resource_nodes.json:{ "schemaVersion": 1, "nodes": [ … ] }— four rows from freeze table; placeholderdisplayNamestrings OK.prototype_resource_yields.json:{ "schemaVersion": 1, "yields": [ … ] }— four rows, one pernodeDefId.
-
Frozen content values (prototype):
nodeDefIdgatherLensmaxGathersyield quantityprototype_resource_node_alphaconsumer_salvage101prototype_subsurface_vein_betasubsurface102prototype_bio_mat_gammabio103prototype_urban_bulk_deltaurban_bulk105All yields:
itemIdscrap_metal_bulk.maxGathersis uniform 10 for prototype (depletion behavior lands in E3M1-04; value is content-only until then). -
validate_content.py:- Discover
content/resource-nodes/*_resource_nodes.jsonand*_resource_yields.json(or fixed filenames above). - Validate each node row / yield row against respective schemas.
- Track
seen_node_ids; reject duplicates across node files. _prototype_slice2_resource_node_gate: ids must equalPROTOTYPE_SLICE2_NODE_IDS;gatherLensset must equal four lens enums exactly once each.- Yield pass: reject duplicate
nodeDefIdin yields; every yieldnodeDefIdmust exist in loaded nodes; every yielditemIdmust exist in item catalog (PROTOTYPE_SLICE1_ITEM_IDS/ loaded item ids); prototype gate requires onlyscrap_metal_bulkas yield item id in Slice 2. - Require exactly one yield row per frozen
nodeDefId. - Run resource-node validation after item catalogs succeed (reuse item id set from
_validate_item_catalogs). - Extend success summary line with resource-node file counts.
- Discover
-
Docs (minimal delta beyond merged decomposition):
content/README.md— addresource-nodes/row to table + Slice 2 freeze pointer (if not already complete post-merge).CT_M1_ContentValidationPipeline.md— PR gate paragraph for resource-node catalogs.documentation_and_implementation_alignment.md— E3.M1 row note NEO-57 content landed when implementation completes.
-
No server/C#/client/Bruno changes in this story.
Files to add
| Path | Purpose |
|---|---|
content/schemas/resource-node-def.schema.json |
JSON Schema for a single ResourceNodeDef row. |
content/schemas/resource-yield-row.schema.json |
JSON Schema for a single fixed-yield row. |
content/resource-nodes/prototype_resource_nodes.json |
Four-node prototype catalog (schemaVersion + nodes). |
content/resource-nodes/prototype_resource_yields.json |
Four yield rows (schemaVersion + yields). |
Files to modify
| Path | Rationale |
|---|---|
scripts/validate_content.py |
Load/validate resource-node catalogs; duplicate nodeDefId; Slice 2 four-id + four-lens gates; yield↔item and yield↔node cross-checks; summary line. |
content/README.md |
Document content/resource-nodes/, schema paths, Slice 2 freeze (if merge left gaps). |
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md |
PR gate paragraph for resource-node + yield validation. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E3.M1 / NEO-57 status after catalog lands. |
Already on branch (merge chore/e3m1-slice2-decomposition): docs/plans/E3M1-prototype-backlog.md, docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md (freeze box), docs/decomposition/modules/module_dependency_register.md (E3.M1 note), content/README.md Slice 2 pointer.
Tests
| Item | Coverage |
|---|---|
| CI / PR gate | .github/workflows/pr-gate.yml runs python scripts/validate_content.py — extended script is the regression signal. |
| Manual negative cases | From repo root: (1) duplicate nodeDefId in nodes file → non-zero exit; (2) remove one frozen id → gate error; (3) invalid gatherLens → schema error; (4) yield references not-a-prototype-item → cross-check error; (5) second yield row for same nodeDefId → duplicate error; (6) wrong yield item id (not scrap_metal_bulk) → Slice 2 item gate error. |
| Manual happy path | pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — reports resource-node catalogs + existing catalogs OK. |
No dedicated pytest module (same as NEO-50 / NEO-33). No C# tests until E3M1-02 server loader.
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
| Interactable registry anchors for beta/gamma/delta | Defer to E3M1-05 — content ids frozen now; world X/Z added when interactables list grows. | deferred |
| C# vs Python drift | None until E3M1-02; duplicate PROTOTYPE_SLICE2_* constants in loader + script with “keep in sync” comment (NEO-50 pattern). |
adopted |
| Catalog growth beyond four nodes | New Linear issue to relax PROTOTYPE_SLICE2_NODE_IDS and lens gate. |
adopted |
| Per-lens skill XP | Stay salvage + activity until content pass; not in JSON this story. |
adopted |
Decisions (kickoff)
- Two-file layout with separate row schemas for nodes and yields.
maxGatherson node defs in content now (10for all prototype rows).- Differentiated yield quantities 1 / 2 / 3 / 5 by lens order in freeze table.
- Decomposition docs merged from
chore/e3m1-slice2-decompositionon this branch before implementation commits.