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

9.1 KiB
Raw Permalink Blame History

NEO-45 — Implementation plan

Story reference

Field Value
Key NEO-45
Title E2.M3: Lock prototype salvage mastery catalog + schemas + CI
Linear https://linear.app/neon-sprawl/issue/NEO-45/e2m3-lock-prototype-salvage-mastery-catalog-schemas-ci
Module E2.M3 — MasteryAndPerkUnlocks · Epic 2 Slice 4
Branch NEO-45-salvage-mastery-catalog-schemas-ci

Kickoff clarifications

Topic Question Answer
PerkDef layout Top-level perks map vs inline on branches? Top-level perks map — tracks reference perkId strings (dedupes metadata; mirrors skill catalog style).
Tier 1 unlocks Perks on branch pick at level 2? Branch pick only — tier-1 branches have empty perkIds; first perks unlock at tier 2 (requiredLevel 4).
Frozen perkId strings Who names tier-2 perks? Agent proposal (below); change displayName only after ship.
WIP decomposition docs Include uncommitted E2.M3 doc edits on this branch? Yes — commit with kickoff/plan work.
NEO-33 dependency Blocker status? Satisfiedsalvage / refine / intrusion frozen in content/skills/prototype_skills.json (NEO-33 Done).

Frozen prototype ids (Slice 4 — salvage flagship)

Kind Value Notes
skillId salvage Only skill with a MasteryTrack in prototype catalog.
Tier 1 branchId scrap_efficiency, bulk_haul Mutually exclusive pick @ requiredLevel 2; no perks yet.
Tier 2 branchId same ids Continuation paths @ requiredLevel 4; one perk each.
Tier 2 perkId salvage_scrap_efficiency_1, salvage_bulk_haul_1 Immutable after ship; tune player text via displayName.

Goal, scope, and out-of-scope

Goal: Lock the salvage flagship mastery catalog under content/mastery/ with JSON Schema and CI validation (scripts/validate_content.py) so tiers, branches, and perk ids are stable before server load (NEO-46).

In scope (from Linear):

  • content/schemas/mastery-catalog.schema.json validates the prototype catalog shape.
  • content/mastery/prototype_salvage_mastery.json — one MasteryTrack for salvage (tier 1 @ level 2 branches; tier 2 @ level 4 perks).
  • scripts/validate_content.py — unknown skillId, duplicate perkId, branch/perk reference integrity.
  • Designer note: mutually exclusive branch picks per tier; no respec in v1 (E2.M3 module + content/README.md).
  • Include WIP E2.M3 decomposition / backlog doc updates on this branch.

Out of scope (from Linear):

  • Server boot load (NEO-46).
  • Perk unlock engine, HTTP, client HUD (NEO-47+).
  • Separate mastery XP bar; gig perks; respec.
  • refine / intrusion mastery rows (CI must not require them).

Acceptance criteria checklist

  • content/schemas/mastery-catalog.schema.json (or equivalent) validates prototype catalog.
  • content/mastery/prototype_salvage_mastery.json ships one MasteryTrack for salvage only (tier 1 @ level 2: scrap_efficiency vs bulk_haul; tier 2 @ level 4 with one perk per branch path).
  • CI fails on unknown skillId, duplicate perk ids, or invalid branch references.
  • Designer note in module doc: mutually exclusive branch picks per tier; no respec in v1.
  • refine / intrusion have no mastery rows yet (CI does not require them).

Technical approach

  1. Schema (mastery-catalog.schema.json): Draft 2020-12 object with schemaVersion: 1, top-level perks map (keys = perk id), and tracks array. Each track: skillId, ordered tiers (tierIndex, requiredLevel, branches). Each branch: branchId, optional displayName, perkIds (array, may be empty). Each perk: id, displayName, optional effectKind stub string. Enforce snake_case patterns for ids; additionalProperties: false on objects.
  2. Prototype catalog: Single file content/mastery/prototype_salvage_mastery.json with exactly one track (salvage), two tiers, two branches per tier (same branchId values tier-to-tier), two perks in perks map referenced only from tier 2. Stub effectKind values (e.g. gather_yield_modifier) for future E3 wiring — no gameplay apply.
  3. validate_content.py: After existing skill + level-curve validation, load skill ids from validated *_skills.json files, then validate content/mastery/*_mastery.json against mastery schema. Post-schema gates:
    • Unknown skillId: track skillId ∉ loaded skill ids.
    • Duplicate perkId: same id in perks map or referenced twice across branches.
    • Branch integrity: every perkIds[] entry exists in perks; tier 2 branchId set equals tier 1 branchId set; requiredLevel strictly increases by tier; unique branchId per tier.
    • Slice 4 gate: exactly one track; its skillId is salvage only (no refine / intrusion tracks).
  4. Docs: Extend content/README.md with mastery/ paths and freeze reminder. Ensure E2.M3 module freeze box lists concrete perkId values. Update CT.M1 CI prototype paragraph. Refresh documentation_and_implementation_alignment.md E2.M3 row. Commit included WIP decomposition/backlog edits.
  5. No server/C#/client changes in this story.

Files to add

Path Purpose
content/schemas/mastery-catalog.schema.json JSON Schema for mastery catalog files (perks + tracks).
content/mastery/prototype_salvage_mastery.json Prototype salvage MasteryTrack, tier/branch tree, and perk definitions.

Files to modify

Path Rationale
scripts/validate_content.py Validate mastery catalogs; cross-check skillId; duplicate perkId; branch/perk integrity; Slice 4 salvage-only gate.
content/README.md Document content/mastery/, schema path, Slice 4 freeze, local validate command.
docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md Confirm designer note + freeze table with concrete perkId values (WIP edits).
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md PR gate paragraph: mastery catalog validation.
docs/decomposition/modules/documentation_and_implementation_alignment.md E2.M3 / NEO-45 row after catalog lands.
docs/decomposition/epics/epic_02_skills_and_progression.md Slice 4 pointer consistency (WIP edits).
docs/decomposition/modules/E2_M2_XpAwardAndLevelEngine.md E2.M3 cross-link (WIP edits).
docs/decomposition/modules/module_dependency_register.md E2.M3 note (WIP edits).
docs/plans/E2M3-pre-production-backlog.md Pre-production backlog (untracked → tracked).

Tests

Item Coverage
CI / PR gate .github/workflows/pr-gate.yml already runs python scripts/validate_content.py — extend script; no workflow change required unless gate message needs updating.
Manual negative cases Temporarily break catalog (unknown skillId, duplicate perkId, orphan perkIds reference, extra refine track) and confirm non-zero exit + actionable stderr.
Manual happy path pip install -r scripts/requirements-content.txt && python scripts/validate_content.py reports mastery + existing catalogs OK.
Dedicated pytest Not required for NEO-45 (same as NEO-33 / NEO-39 content-only pattern); optional follow-up if we want fixture-based script tests later.
Server tests None — server load is NEO-46.

Open questions / risks

  • Tier index convention: Use explicit tierIndex (1-based) in JSON for stable server persistence keys in NEO-47; document in schema description.
  • Catalog growth: Slice 4 gate mirrors NEO-33 — relaxing “salvage only” requires a new Linear issue to change validate_content.py constants.
  • Drift vs NEO-46: Server loader must mirror Python rules; NEO-46 plan should reference this plans gate constants and validate tierIndex uniqueness / sequential 1..N per track (schema allows gaps today).
  • None blocking at kickoff after clarifications above.

Decisions

Decision Rationale
Top-level perks map User kickoff choice; avoids duplicating perk metadata on every branch reference.
Empty tier-1 perkIds User kickoff choice; matches Linear AC (“tier 2 … one perk per branch path”).
Frozen tier-2 perk ids salvage_scrap_efficiency_1, salvage_bulk_haul_1 Prefix with skill + branch semantics; unique globally for future multi-track catalogs.
Include WIP E2.M3 docs on branch User kickoff choice; keeps decomposition aligned with Slice 4 backlog before implementation PR.

Verification (implementation)

  • python3 scripts/validate_content.py — exit 0; reports 1 mastery catalog + 1 salvage track.
  • python3 scripts/check_decomposition_language.py — exit 0.
  • Negative cases (restored catalog after each): unknown skillId, orphan perkId, duplicate perkId reference, Slice 4 multi-track gate — all non-zero exit with actionable stderr.