neon-sprawl/docs/plans/NEO-50-implementation-plan.md

7.9 KiB

NEO-50 — Implementation plan

Story reference

Field Value
Key NEO-50
Title E3.M3: Lock prototype ItemDef starter set + schemas + CI
Linear https://linear.app/neon-sprawl/issue/NEO-50/e3m3-lock-prototype-itemdef-starter-set-schemas-ci
Module E3.M3 — ItemizationAndInventorySchema · Epic 3 Slice 1 · backlog E3M3-01
Branch NEO-50-itemdef-starter-set-schemas-ci

Kickoff clarifications

Topic Question Answer
Prototype role field prototypeRole vs designBucket vs tags only? Agent recommendation (accepted): prototypeRole enum — one value per frozen archetype (material | intermediate | consumable | utility | quest_token | equip_stub). CI requires exactly one row per role (same pattern as skill category coverage). User asked for suggestion; design buckets in items.md overlap and are not 1:1 with the six-id spine.
Stack + slot metadata Explicit fields vs deferred? Agent recommendation (accepted): Explicit per row: stackMax (int ≥ 1) and inventorySlotKind (bag | equipment). Locks stack/slot rules in content before NEO-51 server load and E3M3-05 inventory engine.
Forward-compat stubs Optional rarity / bindPolicy / durabilityMax on schema? Yes — optional fields on ItemDef; omit on all six prototype rows (documented unused in v1). User confirmed.
Catalog envelope File shape? Mirror skills: { "schemaVersion": 1, "items": [ … ] } in content/items/prototype_items.json; each row validates against content/schemas/item-def.schema.json.
Base branch Branch from main after PR #84? Yesmain includes E3M3-prototype-backlog and README freeze pointers.

Frozen prototype ids (Slice 1 — item spine)

id prototypeRole inventorySlotKind stackMax (initial)
scrap_metal_bulk material bag 999
refined_plate_stock intermediate bag 999
field_stim_mk0 consumable bag 20
survey_drone_kit utility bag 1
contract_handoff_token quest_token bag 1
prototype_armor_shell equip_stub equipment 1

Rules: Do not rename id values after ship — change displayName only, or add a new id in a follow-up issue. Relaxing the six-id or six-role CI gate requires a new Linear issue.

Goal, scope, and out-of-scope

Goal: Lock the six-item prototype catalog under content/items/ with JSON Schema and CI validation (scripts/validate_content.py) so ids, stack rules, and slot metadata are stable before server load (NEO-51+).

In scope (from Linear / E3M3-prototype-backlog):

  • content/schemas/item-def.schema.json — single-row ItemDef contract.
  • content/items/prototype_items.json — six frozen ids (table above).
  • scripts/validate_content.py — schema validation, duplicate id, prototypeRole coverage, exact six-id allowlist.
  • Designer note in E3.M3 module doc + content/README.md: stack rules, bag vs equipment slot, v1 scope (no durability mutation).

Out of scope (from Linear):

  • Server runtime load, inventory store, HTTP, client HUD (NEO-51+).

Acceptance criteria checklist

  • PR gate / validate_content.py fails on invalid rows, schema mismatch, or duplicate item id.
  • Exactly six prototype item ids; wrong id set fails CI.
  • Each prototypeRole appears exactly once across the catalog.
  • Six prototype ids documented as frozen in E3.M3 module doc + content/README.md.

Technical approach

  1. Row schema (item-def.schema.json): Draft 2020-12 object; additionalProperties: false. Required: id, displayName, prototypeRole, stackMax, inventorySlotKind. Enums: prototypeRole (six values above), inventorySlotKind (bag | equipment). id pattern ^[a-z][a-z0-9_]*$ (same as skills). Optional stubs: rarity (common | uncommon | rare | epic), bindPolicy (none | bind_on_acquire | bind_on_equip), durabilityMax (integer ≥ 0) — all optional, unused in prototype rows.
  2. Catalog file: content/items/prototype_items.json with schemaVersion: 1 and six rows matching the freeze table; placeholder displayName strings acceptable.
  3. validate_content.py: After existing skill / level-curve / mastery validation (or in parallel before final success):
    • Discover content/items/*_items.json.
    • Validate each row against item-def.schema.json.
    • Track seen_ids across files; reject duplicates.
    • _prototype_slice1_item_gate: ids must equal PROTOTYPE_SLICE1_ITEM_IDS; roles must cover each prototypeRole exactly once; equip_stub row must use inventorySlotKind: equipment and stackMax: 1 (post-schema sanity).
  4. Docs: E3.M3 — Prototype Slice 1 freeze subsection + designer note (bag vs equipment, stackMax, no durability mutation in v1). content/README.md — promote items path from “planned” to active with freeze list. CT.M1 — add item catalog paragraph. documentation_and_implementation_alignment.md — E3.M3 row → In Progress / NEO-50.
  5. No server/C#/client changes in this story.

Files to add

Path Purpose
content/schemas/item-def.schema.json JSON Schema for a single ItemDef row.
content/items/prototype_items.json Prototype six-item catalog (schemaVersion + items array).

Files to modify

Path Rationale
scripts/validate_content.py Load/validate item catalogs; duplicate id; Slice 1 six-id + six-role gates; update success summary line.
content/README.md Document content/items/, schema path, Slice 1 freeze (active, not “planned”).
docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md Freeze table, designer note (stack/slot/v1 scope), link to plan.
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md PR gate paragraph for item catalog validation.
docs/decomposition/modules/documentation_and_implementation_alignment.md E3.M3 / NEO-50 status after catalog lands.

Tests

Item Coverage
CI / PR gate .github/workflows/pr-gate.yml already runs python scripts/validate_content.py — extended script is the regression signal.
Manual negative cases From repo root after implementation: (1) duplicate id in catalog → non-zero exit; (2) remove one frozen id → gate error; (3) break schema (invalid stackMax) → schema error; (4) swap two prototypeRole values → role coverage error.
Manual happy path pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — reports item catalog + existing catalogs OK.

No dedicated pytest module in-repo today (same as NEO-33 / NEO-45).

Open questions / risks

  • Catalog growth: Slice 1 gate requires exactly the six frozen ids; expanding the roster needs a follow-up issue to change PROTOTYPE_SLICE1_ITEM_IDS and role gate.
  • C# vs Python drift: None until NEO-51; document “keep in sync” constant names in both places when server load lands.
  • None otherwise at kickoff.

Decisions

Decision Rationale
prototypeRole enum + per-role CI User requested agent recommendation; maps 1:1 to six-id vertical slice.
Explicit stackMax + inventorySlotKind User requested agent recommendation; backlog requires stack/slot metadata before server load.
Optional rarity / bindPolicy / durabilityMax on schema User chose yes_stubs for forward-compat without populating prototype rows.