9.9 KiB
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) |
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 (perkId → PerkDef), 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[] → MasteryTrack → tiers[]: 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 player’s skill level reaches
requiredLevel, that tier’s branches become eligible for selection (server-authoritative). - One branch per tier: picking
scrap_efficiencyforeclosesbulk_haulfor that tier; perks on the chosen branch unlock; the other branch’s 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.”
displayNameon 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)
When NEO-46 kicks off, mirror the Python CI gates documented in 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). Consider tierIndex uniqueness and sequential 1..N per track at server boot (schema allows gaps today).
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
PerkStateper player: unlockedperkIdset + chosenbranchIdper tier. - Evaluate eligibility from skill level; apply branch selection; emit
PerkUnlockEventfor 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
- E2.M2 — XpAwardAndLevelEngine: skill level source for tier gates; level-up hook site for re-evaluation.
- E2.M1 — SkillDefinitionRegistry: validate catalog
skillIdreferences.
Dependents (by design)
- E3.M1 / E3.M2 — Optional gates or modifiers on gather/craft when perk
effectKindis 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
refineorintrusion(separate skills / tracks). - Branch pick at tier 1 is mutually exclusive; server denies invalid or duplicate picks with stable
reasonCodevalues. - 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).
Related implementation slices
- 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_unlockhook sites (NEO-49); align with E9.M1 when catalog exists.
Source anchors
- Master plan:
neon_sprawl_vision.plan.md— Epic 2, E2.M3. - Game design:
docs/game-design/skills.md— Mastery and perks. - Module dependency register