neon-sprawl/docs/decomposition/modules/E2_M3_MasteryAndPerkUnlocks.md

12 KiB
Raw Blame History

E2.M3 — MasteryAndPerkUnlocks

Summary

Field Value
Module ID E2.M3
Epic Epic 2 — Skills and Progression Framework
Linear (Epic 2 project) Epic 2 — Classless Skill and Progression Framework
Stage target Pre-production
Status In Progress (see dependency register; NEO-45 catalog + CI landed; NEO-46 server load; NEO-47 perk engine + persistence landed)

Linear backlog (Epic 2 Slice 4 — E2.M3)

Issues use label E2.M3. Dependency order: NEO-45 → NEO-46 → NEO-47 → NEO-48; NEO-49 after NEO-47 (may parallel NEO-48). Canonical story text: E2M3-pre-production-backlog.md.

Order Issue Summary
1 NEO-45 Lock prototype salvage mastery catalog + schemas + CI
2 NEO-46 Server loads mastery catalog at startup (fail-fast)
3 NEO-47 Perk unlock engine + PerkState persistence
4 NEO-48 Perk state GET + branch selection POST + Bruno
5 NEO-49 perk_unlock telemetry hook sites

docs/plans/NEO-*-implementation-plan.md files are added at story kickoff (per planning-implementation-docs).

Purpose

Mastery tracks and perk unlock state so skill builds have expressive identity beyond raw level curves from E2.M2. Gig mastery/perks remain a separate system (docs/game-design/gigs.md).

v1 design decisions (frozen for backlog)

Topic v1 choice
Progress into mastery Skill level from E2.M2 (IPlayerSkillProgressionStore) — no separate mastery XP bar
Branch model One MasteryTrack per skill; tiers with mutually exclusive branch picks per tier
Respec Out of scope (late-stage tuning per skills.md)
Gig perks No shared tree with gigs
Gameplay effects State + data only in E2.M3 core; stat/yield hooks are optional follow-ups (e.g. E3.M1)

Data contract (schema)

Artifact Path
JSON Schema content/schemas/mastery-catalog.schema.json
Prototype catalog content/mastery/prototype_salvage_mastery.json

Each catalog file has top-level schemaVersion, a perks map (perkIdPerkDef), and a tracks array. Branches reference perks by id string only (no inline perk objects). CI requires each perks map key to equal the nested PerkDef.id (see mastery-catalog.schema.json and validate_content.py).

tracks[]MasteryTracktiers[]: each tier row has tierIndex (1-based, stable for PerkState), requiredLevel (skill level from E2.M2), and branches[]. Each branch has branchId, optional displayName, and perkIds[] (may be empty at tier 1).

MasteryTrack fields (prototype):

Field Rule
skillId Must exist in E2.M1 SkillDef catalog (salvage for flagship slice).
tiers Ordered list; each tier has requiredLevel (skill level from E2.M2) and branches.
branches Stable branchId; mutually exclusive pick per tier; each branch lists perkIds unlocked when chosen.

PerkDef fields (prototype, embedded or referenced in catalog):

Field Rule
id Lowercase snake_case, immutable after ship.
displayName Player-facing; may change without migrating unlock state.
effectKind Optional stub string for future systems (gather yield, craft efficiency, etc.) — no gameplay apply in E2.M3 core stories.

Designer note: tiers, branches, and level gates

  • Level gates come from E2.M2 only: when a players skill level reaches requiredLevel, that tiers branches become eligible for selection (server-authoritative).
  • One branch per tier: picking scrap_efficiency forecloses bulk_haul for that tier; perks on the chosen branch unlock; the other branchs perks for that tier do not unlock.
  • Other skills (refine, intrusion) have no mastery rows in the prototype catalog until a deliberate expansion issue relaxes CI.
  • Respec is not implemented in v1 — document in content if a perk is “permanent for the character.”
  • displayName on a branch row is optional in schema; tier 1 and tier 2 may both include it for consistent authoring.

NEO-46 handoff (server catalog load)

Landed (NEO-46): Server boot loads content/mastery/*_mastery.json fail-fast under Game/Mastery/, mirroring Python CI gates from NEO-45 implementation plan (PROTOTYPE_SLICE4_SALVAGE_SKILL_ID, exactly one track, unknown skillId, duplicate perkId, branch-set equality tier-to-tier, unreferenced perks entries) plus cross-check against ISkillDefinitionRegistry. tierIndex uniqueness and sequential 1..N per track are enforced at server boot and in validate_content.py (protects NEO-47 PerkState keys). Read-only IMasteryCatalogRegistry is registered for NEO-47+.

NEO-47 handoff (perk unlock engine + PerkState persistence)

Landed (NEO-47): IPlayerPerkStateStore (in-memory + Postgres V004) persists branch picks and unlocked perkIds. PerkUnlockEngine evaluates tier-1 explicit branch picks and path-auto tier-2 unlocks from the tier-1 branchId when skill level meets gates (no second pick at tier 2 for prototype salvage). Level-up re-evaluation runs inside SkillProgressionGrantOperations after XP grants (HTTP POST, gather interact, mission/refine helpers). Stable deny reasonCode values for invalid picks. Internal PerkUnlockEvent returned from engine methods. HTTP read/write for perk state is NEO-48.

NEO-49 handoff (perk_unlock telemetry hook sites)

Landed (NEO-49): Comment-only perk_unlock hook site in PerkUnlockEngine.TryUnlockPerks (authoritative unlock anchor; covers branch-pick and level-up path-auto). TODO(E9.M1); no production ingest. server README — Perk unlock engine and telemetry hooks; plan NEO-49; manual QA NEO-49.md.

Prototype Slice 4 freeze — salvage flagship (NEO-45)

The first shipped mastery track is salvage only:

Tier requiredLevel Branches (pick one) Tier-2 perkId (chosen branch only)
1 2 scrap_efficiency · bulk_haul (none — branch pick only)
2 4 same branch ids salvage_scrap_efficiency_1 · salvage_bulk_haul_1

Rules: Do not rename branchId / perkId values to “fix” wording—change displayName only, or add new ids and migrate content. Expanding mastery to refine / intrusion belongs in a new Linear issue once the catalog gate intentionally grows.

Responsibilities

  • Load and validate mastery catalog from data; expose track/perk metadata via IMasteryCatalogRegistry (name flexible).
  • Persist PerkState per player: unlocked perkId set + chosen branchId per tier.
  • Evaluate eligibility from skill level; apply branch selection; emit PerkUnlockEvent for telemetry and future hooks.
  • Re-evaluate eligibility on skill level-up (after E2.M2 grants).
  • Expose versioned HTTP read/write for perk state (prototype JSON v1).

Key contracts

Contract Kind Role
MasteryTrack Content Per-skill tier/branch/perk tree.
PerkDef Content Stable perk metadata (+ optional effectKind stub).
PerkState Server-internal + HTTP DTO Unlocked perks + branch picks per tier.
PerkUnlockEvent Server-internal Fired when a perk becomes unlocked (UI/telemetry/craft gates later).

Module dependencies

Dependents (by design)

  • E3.M1 / E3.M2 — Optional gates or modifiers on gather/craft when perk effectKind is wired (follow-up integration, not E2.M3 core).
  • E7.M2 — Mission rewards may reference perk prerequisites in content (future).
  • Gameplay systems that gate recipes or abilities on perkId (integration per content).

Acceptance criteria (E2.M3 / Slice 4)

Module-level bullets below span NEO-45 → NEO-49. NEO-45 satisfies data + CI only (catalog, schema, validate_content.py); runtime unlock, HTTP, and telemetry are NEO-46+.

  • At least one salvage mastery path unlocks a perk without blocking progression in refine or intrusion (separate skills / tracks).
  • Branch pick at tier 1 is mutually exclusive; server denies invalid or duplicate picks with stable reasonCode values.
  • Catalog + perk state validated in CI or at server boot (CT.M1).
  • Telemetry hook sites documented for perk_unlock (no E9.M1 ingest until catalog exists).
  • Epic 2 Slice 4 — E2.M3 (this module) + E2.M4 pacing (separate backlog; depends on E9.M2).
  • Epic 2 Slice 3 — skill XP integration remains on E2.M2; NEO-44 (gig XP) is Epic 5, not E2.M3.

Risks and telemetry

  • Risk: Mastery feels mandatory for one skill and blocks alts — Mitigation: flagship is one skill; other prototype skills remain level-only until expanded.
  • Risk: Branch picks without respec frustrate players — Mitigation: defer respec to late-stage tuning; keep v1 tree small.
  • Telemetry: perk_unlock hook site in PerkUnlockEngine.TryUnlockPerks (NEO-49 — comments-only); E9.M1 catalog ingest deferred.

Source anchors