neon-sprawl/docs/plans/E3M1-prototype-backlog.md

12 KiB
Raw Blame History

E3.M1 — Prototype story backlog (ResourceNodeAndGatherLoop)

Working backlog for Epic 3 — Slice 2 (gather nodes and outputs). Decomposition and contracts: E3.M1 — ResourceNodeAndGatherLoop.

Labels (Linear): every issue E3.M1; add server / Story as listed per issue.

Precursor (do not re-scope): NEO-41 grants salvage skill XP on successful POST …/interact when interactable kind is resource_nodeno GatherResult, yields, inventory, or depletion yet. Slice 2 migrates XP into the gather engine and adds item grants + node capacity.

Upstream (must be landed): E3.M3 Slice 1 — frozen item catalog, IPlayerInventoryStore, inventory operations (NEO-50NEO-56). E1.M3 interact + range (NEO-9, NEO-25). E2.M2 grant path (NEO-38).

Prototype gather spine (frozen in E3M1-01): four ResourceNodeDef rows — one per gather lens — each with a fixed ResourceYieldTable entry awarding scrap_metal_bulk (primary material from E3.M3 freeze). World placement stays on PrototypeInteractableRegistry until zone graph (E4.M1) ships.

Linear issues (created): attach docs/plans/NEO-*-implementation-plan.md on the same branch as implementation work.

Slug Linear
E3M1-01 NEO-57
E3M1-02 NEO-58
E3M1-03 NEO-59
E3M1-04 NEO-60
E3M1-05 NEO-61
E3M1-06 NEO-62
E3M1-07 NEO-63
E3M1-08 NEO-64

Dependency graph in Linear: E3M1-02 blocked by E3M1-01. E3M1-03 blocked by E3M1-02. E3M1-04 blocked by E3M1-03. E3M1-05 blocked by E3M1-03. E3M1-06 blocked by E3M1-05 and E3M1-03 (inventory from E3.M3). E3M1-07 blocked by E3M1-06. E3M1-08 blocked by E3M1-06 (may parallel E3M1-07).


Story order (recommended)

Order Slug Depends on
1 E3M1-01 E3.M3 item catalog (NEO-50)
2 E3M1-02 E3M1-01
3 E3M1-03 E3M1-02
4 E3M1-04 E3M1-03
5 E3M1-05 E3M1-03
6 E3M1-06 E3M1-05, E3.M3 inventory store (NEO-54+)
7 E3M1-07 E3M1-06
8 E3M1-08 E3M1-06

Downstream (separate modules): E3.M2 recipes consume gathered materials; NEO-42 refine XP hook runs after craft success; E4.M2 owns respawn/ecology pacing later.


Kickoff decisions (decomposition defaults)

Topic Decision Rationale
Node count 4 frozen defs (epic allows 46) One per gather lens; enough variety without bloating prototype registry
Gather wire surface Keep POST …/interact (E1.M3) No separate gather HTTP in prototype; E3.M1 owns resolution after interact eligibility
Yields Fixed quantities per gather (no RNG) Deterministic tests; probability tables are pre-production
Depleted node Allowed: false + reasonCode: node_depleted Prevents “free” taps; clearer than silent no-op
XP Migrate NEO-41 inline grant into gather engine on successful yield Single call site for gather completion side effects
Skill per node All prototype nodes award salvage + activity (10 XP) Matches NEO-41; per-lens skill mapping is content follow-up

E3M1-01 — Prototype ResourceNodeDef + yield tables + schemas + CI

Goal: Lock content shape and CI validation for four frozen resource nodes and their fixed yield tables before server load.

In scope

  • content/schemas/resource-node-def.schema.json and content/schemas/resource-yield-row.schema.json (two row schemas; catalogs in prototype_resource_nodes.json + prototype_resource_yields.jsonNEO-57).
  • content/resource-nodes/prototype_resource_nodes.json with stable nodeDefId values aligned to interactable ids (see module doc freeze table).
  • content/resource-nodes/prototype_resource_yields.json — one yield row per nodeDefId, referencing only scrap_metal_bulk from the E3.M3 freeze.
  • scripts/validate_content.py: schema validation, duplicate ids, exact four-node allowlist, gatherLens coverage, cross-check that every yield itemId exists in content/items/.
  • Designer note in E3_M1 + content/README.md.

Out of scope

  • Server loader, depletion store, inventory grants, HTTP.

Acceptance criteria

  • PR gate validates resource-node JSON against schema.
  • Exactly four prototype node ids; duplicate nodeDefId fails CI.
  • Every yield references a valid frozen item id.
  • Stable id list documented in module doc freeze box.

Landed: NEO-57 — content + CI only; server load is E3M1-02.


E3M1-02 — Server resource-node catalog load (fail-fast)

Goal: Disk → host: startup load of content/resource-nodes/*.json with CI-parity validation.

In scope

  • Loader + catalog types under server/NeonSprawl.Server/Game/Gathering/ (path TBD in plan).
  • Fail-fast on malformed files, duplicate ids, unknown yield item ids (cross-check IItemDefinitionRegistry at startup).
  • Unit tests (AAA) for loader happy path and failure modes.

Out of scope

  • Per-instance depletion, HTTP, interact wiring.

Acceptance criteria

  • Server refuses boot when resource-node catalog invalid (mirror E3.M3 / E2.M1 catalog behavior).
  • Catalog resolves ResourceNodeDef and yield row by nodeDefId.

E3M1-03 — Resource node definition registry + DI

Goal: Injectable IResourceNodeDefinitionRegistry consumed by gather engine and interact integration.

In scope

  • ResourceNodeDefinitionRegistry implementation + DI registration.
  • Unit tests (AAA): lookup, unknown id, yield metadata, capacity fields.

Out of scope

  • HTTP, persistence of depletion state.

Acceptance criteria

  • Host resolves registry from DI; unknown nodeDefId distinguishable from valid ids in tests.

E3M1-04 — GET world resource-node-definitions

Goal: Versioned read-only catalog for Bruno, client preview, and tooling — mirror NEO-53 item-definitions pattern.

In scope

  • GET /game/world/resource-node-definitions + DTOs + API tests (AAA).
  • Bruno folder bruno/neon-sprawl-server/resource-node-definitions/.
  • server/README.md section.

Out of scope

  • Per-instance remaining capacity (instance state is E3M1-05).

Acceptance criteria

  • GET returns all four prototype defs with schema version field and yield summary fields needed for QA.
  • Bruno happy path documented.

E3M1-05 — World node instance depletion store

Goal: Server-authoritative remaining gathers per node instance (interactableId), with structured deny when depleted.

In scope

  • IResourceNodeInstanceStore (or equivalent) keyed by interactableId (matches NEO-25 descriptor ids).
  • Initialize capacity from ResourceNodeDef.maxGathers (or equivalent field name frozen in E3M1-01).
  • Decrement on successful gather commit; node_depleted when zero.
  • Persistence: in-memory + Postgres + migration (mirror NEO-54 / NEO-8 policy).
  • Unit + integration tests (AAA).

Out of scope

  • Item grants, skill XP, HTTP beyond store tests.

Acceptance criteria

  • First gather on a fresh node succeeds; repeated gathers until capacity returns node_depleted without going negative.
  • Postgres + in-memory parity tests.

E3M1-06 — GatherResult engine (yields + inventory + skill XP)

Goal: Single server operation that resolves GatherResult: validate node + capacity, grant items via PlayerInventoryOperations, award salvage XP via SkillProgressionGrantOperations, commit depletion.

In scope

  • GatherOperations / GatherResult types in server/NeonSprawl.Server/Game/Gathering/.
  • Stable deny reason codes: node_depleted, inventory_full, unknown_node, etc.
  • Unit tests (AAA) with stub inventory + progression stores.
  • Document GatherResult shape in module doc (server-internal until promoted to wire).

Out of scope

  • InteractionApi wiring (E3M1-07).
  • Client HUD.
  • Regen / respawn timers (E4.M2).

Acceptance criteria

  • Successful gather adds configured scrap_metal_bulk quantity to player bag.
  • Full bag returns stable reasonCode without partial silent loss.
  • Skill XP grant uses same rules as NEO-38 / NEO-41 (salvage, activity, 10 XP).
  • Depletion store decrements atomically with successful grant.

E3M1-07 — Interact path integration + registry expansion + Bruno

Goal: Replace NEO-41 inline XP block with gather engine; register four prototype resource nodes in PrototypeInteractableRegistry; extend Bruno/manual QA for gather → inventory.

In scope

  • InteractionApi: on resource_node kind, call gather engine; map engine deny → InteractionResponse with Allowed: false + reasonCode (including node_depleted).
  • Remove duplicate XP grant path once gather engine covers it.
  • Add three new interactable anchors (positions TBD in plan; keep prototype_resource_node_alpha anchor).
  • Bruno: move near node → interact → GET inventory shows scrap_metal_bulk; depleted node deny.
  • server/README.md; integration tests (AAA).

Out of scope

  • Godot gather HUD (optional follow-up).
  • New gather-specific HTTP route.

Acceptance criteria

  • In-range interact on fresh node increases inventory quantity and salvage XP.
  • Depleted node returns node_depleted without inventory or XP change.
  • Terminal interact unchanged (no gather side effects).
  • Bruno documents happy path + depletion deny for at least one node.

E3M1-08 — resource_gathered / gather_node_depleted telemetry hook sites

Goal: Comment-only hooks on gather success and depletion for future E9.M1 catalog events.

In scope

  • Hook placement in gather engine (and interact deny branch if applicable).
  • TODO(E9.M1) comments; no production logging.
  • README + module doc pointer.

Out of scope

  • Telemetry ingest, dashboards.

Acceptance criteria

  • Hook sites documented in code and server/README.md.
  • Matches Epic 3 Slice 2 telemetry vocabulary in epic_03.

After this backlog

  • Decompose E3.M2 Slice 3 (recipes + craft HTTP) once E3M1-06+ land and inventory holds gathered materials.
  • Track delivery in Linear; keep blockedBy links synchronized if scope changes.
  • For each issue kickoff, add docs/plans/{NEO-XX}-implementation-plan.md per story-kickoff.
  • Add docs/manual-qa/{NEO-XX}.md when interact + inventory surfaces are testable (E3M1-07+).