@ -51,7 +51,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not
| E1.M3 | In Progress | **NEO-23 landed:** JSON v1 targeting read + select (`GET`/`POST …/target`, `Game/Targeting/` , [NEO-23 ](../../plans/NEO-23-implementation-plan.md )) — `PlayerTargetStateResponse` / soft lock / `reasonCode` denials; wire name differs from illustrative ** `TargetState` ** in module doc (called out in plan + README). **NEO-24 landed:** client tab-target + lock HUD synced to server ([NEO-24](../../plans/NEO-24-implementation-plan.md)) — `TargetSelectionClient` ([`client/scripts/target_selection_client.gd`](../../../client/scripts/target_selection_client.gd)), Tab / Esc bindings, `TargetLockLabel` HUD, hybrid movement-triggered refresh via new `PositionAuthorityClient.authoritative_ack` signal (fires on every server-confirmed position, boot + `move-stream` 200) with a 250 ms cooldown; manual QA in [`docs/manual-qa/NEO-24.md` ](../../manual-qa/NEO-24.md ). **NEO-25 landed:** versioned ** `GET /game/world/interactables` ** ([NEO-25](../../plans/NEO-25-implementation-plan.md)) — `InteractablesListResponse` / `InteractablesWorldApi` in `server/NeonSprawl.Server/Game/Interaction/` ; Godot `interactables_catalog_client.gd` + `interactable_world_builder.gd` (fetch-driven props + glow); [server README — Interactable descriptors (NEO-25) ](../../../server/README.md#interactable-descriptors-neo-25 ). **NEO-26 landed:** prototype ** `SelectionEvent` ** — ** `TargetSelectionClient.selection_event` ** ([NEO-26](../../plans/NEO-26-implementation-plan.md)) when ** `lockedTargetId` ** changes; optional ** `log_selection_events` **. **NEO-27 landed:** Slice 3 telemetry hook sites documented — `selection_event` maps to product `target_changed` in `target_selection_client.gd` ; server lock transition hook comments in `InMemoryPlayerTargetLockStore` ; reserved `ability_cast_requested` / `ability_cast_denied` comments in `client/scripts/main.gd` (all TODO(E9.M1)); see [NEO-27 ](../../plans/NEO-27-implementation-plan.md ) and [`docs/manual-qa/NEO-27.md` ](../../manual-qa/NEO-27.md ). **Follow-on / still open:** richer ** `InteractableDescriptor` ** consumers beyond list projection + existing `POST …/interact` ; production telemetry ingest/catalog after E9.M1. **Precursor (E1.M1):** [NEO-9 ](../../plans/NEO-9-implementation-plan.md ) `POST …/interact` . | Linear [NEO-24 ](https://linear.app/neon-sprawl/issue/NEO-24 )– [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27); [E1_M3_InteractionAndTargetingLayer.md ](E1_M3_InteractionAndTargetingLayer.md ); [E1M3 prototype backlog ](../../plans/E1M3-prototype-backlog.md ); [server README — Targeting ](../../../server/README.md#targeting-neo-23 ), [Interactable descriptors (NEO-25) ](../../../server/README.md#interactable-descriptors-neo-25 ), [Interaction ](../../../server/README.md#interaction-neo-9 ) |
| E1.M3 | In Progress | **NEO-23 landed:** JSON v1 targeting read + select (`GET`/`POST …/target`, `Game/Targeting/` , [NEO-23 ](../../plans/NEO-23-implementation-plan.md )) — `PlayerTargetStateResponse` / soft lock / `reasonCode` denials; wire name differs from illustrative ** `TargetState` ** in module doc (called out in plan + README). **NEO-24 landed:** client tab-target + lock HUD synced to server ([NEO-24](../../plans/NEO-24-implementation-plan.md)) — `TargetSelectionClient` ([`client/scripts/target_selection_client.gd`](../../../client/scripts/target_selection_client.gd)), Tab / Esc bindings, `TargetLockLabel` HUD, hybrid movement-triggered refresh via new `PositionAuthorityClient.authoritative_ack` signal (fires on every server-confirmed position, boot + `move-stream` 200) with a 250 ms cooldown; manual QA in [`docs/manual-qa/NEO-24.md` ](../../manual-qa/NEO-24.md ). **NEO-25 landed:** versioned ** `GET /game/world/interactables` ** ([NEO-25](../../plans/NEO-25-implementation-plan.md)) — `InteractablesListResponse` / `InteractablesWorldApi` in `server/NeonSprawl.Server/Game/Interaction/` ; Godot `interactables_catalog_client.gd` + `interactable_world_builder.gd` (fetch-driven props + glow); [server README — Interactable descriptors (NEO-25) ](../../../server/README.md#interactable-descriptors-neo-25 ). **NEO-26 landed:** prototype ** `SelectionEvent` ** — ** `TargetSelectionClient.selection_event` ** ([NEO-26](../../plans/NEO-26-implementation-plan.md)) when ** `lockedTargetId` ** changes; optional ** `log_selection_events` **. **NEO-27 landed:** Slice 3 telemetry hook sites documented — `selection_event` maps to product `target_changed` in `target_selection_client.gd` ; server lock transition hook comments in `InMemoryPlayerTargetLockStore` ; reserved `ability_cast_requested` / `ability_cast_denied` comments in `client/scripts/main.gd` (all TODO(E9.M1)); see [NEO-27 ](../../plans/NEO-27-implementation-plan.md ) and [`docs/manual-qa/NEO-27.md` ](../../manual-qa/NEO-27.md ). **Follow-on / still open:** richer ** `InteractableDescriptor` ** consumers beyond list projection + existing `POST …/interact` ; production telemetry ingest/catalog after E9.M1. **Precursor (E1.M1):** [NEO-9 ](../../plans/NEO-9-implementation-plan.md ) `POST …/interact` . | Linear [NEO-24 ](https://linear.app/neon-sprawl/issue/NEO-24 )– [NEO-27](https://linear.app/neon-sprawl/issue/NEO-27); [E1_M3_InteractionAndTargetingLayer.md ](E1_M3_InteractionAndTargetingLayer.md ); [E1M3 prototype backlog ](../../plans/E1M3-prototype-backlog.md ); [server README — Targeting ](../../../server/README.md#targeting-neo-23 ), [Interactable descriptors (NEO-25) ](../../../server/README.md#interactable-descriptors-neo-25 ), [Interaction ](../../../server/README.md#interaction-neo-9 ) |
| E1.M4 | In Progress | **NEO-29 landed:** prototype `HotbarLoadout` v1 server contract (`GET`/`POST /game/players/{id}/hotbar-loadout`) with stable deny reason codes, per-player scope, and persistence policy matching NEO-8/NS-17 (Postgres when configured, in-memory fallback otherwise). Client boot hydration is wired via `hotbar_loadout_client.gd` + `hotbar_state.gd` from `main.gd` ; manual QA checklist created. **NEO-31 landed:** prototype ** `POST /game/players/{id}/ability-cast` **, digit-key cast wiring from `main.gd` , `ability_cast_client.gd` , and `hotbar_cast_slot_resolver.gd` (target id from `lockedTargetId` in cached target state); GdUnit covers resolver + HTTP client; manual QA [NEO-31 ](../../manual-qa/NEO-31.md ). **NEO-28 landed:** cast POST validates **lock + registry + range** (`invalid_target`, `out_of_range` ); ** `AbilityCastClient.cast_result_received` ** + ** `CastFeedbackLabel` ** HUD line; manual QA [NEO-28 ](../../manual-qa/NEO-28.md ). **NEO-30 landed:** Slice 3 cast funnel telemetry **hook-site comments** in [`AbilityCastApi.cs` ](../../../server/NeonSprawl.Server/Game/AbilityInput/AbilityCastApi.cs ) (`ability_cast_requested` on authoritative accept, `ability_cast_denied` + non-empty `reasonCode` on each JSON deny branch); client dev hooks unchanged; manual QA [NEO-30 ](../../manual-qa/NEO-30.md ); plan [NEO-30 ](../../plans/NEO-30-implementation-plan.md ). **NEO-32 landed:** `GET /game/players/{id}/cooldown-snapshot` + `on_cooldown` cast deny + prototype global cooldown commit; [`cooldown_snapshot_client.gd` ](../../../client/scripts/cooldown_snapshot_client.gd ), [`cooldown_state.gd` ](../../../client/scripts/cooldown_state.gd ), ** `CooldownSlotsLabel` ** in [`main.gd` ](../../../client/scripts/main.gd ); manual QA [NEO-32 ](../../manual-qa/NEO-32.md ); plan [NEO-32 ](../../plans/NEO-32-implementation-plan.md ). | [NEO-29 ](../../plans/NEO-29-implementation-plan.md ), [NEO-31 ](../../plans/NEO-31-implementation-plan.md ), [NEO-28 ](../../plans/NEO-28-implementation-plan.md ), [NEO-30 ](../../plans/NEO-30-implementation-plan.md ), [NEO-32 ](../../plans/NEO-32-implementation-plan.md ); [E1_M4_AbilityInputScaffold.md ](E1_M4_AbilityInputScaffold.md ); [E1M4 prototype backlog ](../../plans/E1M4-prototype-backlog.md ); `server/NeonSprawl.Server/Game/AbilityInput/` ; [`client/scripts/hotbar_loadout_client.gd` ](../../../client/scripts/hotbar_loadout_client.gd ), [`client/scripts/hotbar_state.gd` ](../../../client/scripts/hotbar_state.gd ), [`client/scripts/ability_cast_client.gd` ](../../../client/scripts/ability_cast_client.gd ), [`client/scripts/hotbar_cast_slot_resolver.gd` ](../../../client/scripts/hotbar_cast_slot_resolver.gd ), [`client/scripts/cooldown_snapshot_client.gd` ](../../../client/scripts/cooldown_snapshot_client.gd ), [`client/scripts/cooldown_state.gd` ](../../../client/scripts/cooldown_state.gd ); [server README — Hotbar loadout ](../../../server/README.md#hotbar-loadout-neo-29 ), [Ability cast (NEO-31) ](../../../server/README.md#ability-cast-neo-31 ), [Cooldown snapshot (NEO-32) ](../../../server/README.md#cooldown-snapshot-neo-32 ) |
| E1.M4 | In Progress | **NEO-29 landed:** prototype `HotbarLoadout` v1 server contract (`GET`/`POST /game/players/{id}/hotbar-loadout`) with stable deny reason codes, per-player scope, and persistence policy matching NEO-8/NS-17 (Postgres when configured, in-memory fallback otherwise). Client boot hydration is wired via `hotbar_loadout_client.gd` + `hotbar_state.gd` from `main.gd` ; manual QA checklist created. **NEO-31 landed:** prototype ** `POST /game/players/{id}/ability-cast` **, digit-key cast wiring from `main.gd` , `ability_cast_client.gd` , and `hotbar_cast_slot_resolver.gd` (target id from `lockedTargetId` in cached target state); GdUnit covers resolver + HTTP client; manual QA [NEO-31 ](../../manual-qa/NEO-31.md ). **NEO-28 landed:** cast POST validates **lock + registry + range** (`invalid_target`, `out_of_range` ); ** `AbilityCastClient.cast_result_received` ** + ** `CastFeedbackLabel` ** HUD line; manual QA [NEO-28 ](../../manual-qa/NEO-28.md ). **NEO-30 landed:** Slice 3 cast funnel telemetry **hook-site comments** in [`AbilityCastApi.cs` ](../../../server/NeonSprawl.Server/Game/AbilityInput/AbilityCastApi.cs ) (`ability_cast_requested` on authoritative accept, `ability_cast_denied` + non-empty `reasonCode` on each JSON deny branch); client dev hooks unchanged; manual QA [NEO-30 ](../../manual-qa/NEO-30.md ); plan [NEO-30 ](../../plans/NEO-30-implementation-plan.md ). **NEO-32 landed:** `GET /game/players/{id}/cooldown-snapshot` + `on_cooldown` cast deny + prototype global cooldown commit; [`cooldown_snapshot_client.gd` ](../../../client/scripts/cooldown_snapshot_client.gd ), [`cooldown_state.gd` ](../../../client/scripts/cooldown_state.gd ), ** `CooldownSlotsLabel` ** in [`main.gd` ](../../../client/scripts/main.gd ); manual QA [NEO-32 ](../../manual-qa/NEO-32.md ); plan [NEO-32 ](../../plans/NEO-32-implementation-plan.md ). | [NEO-29 ](../../plans/NEO-29-implementation-plan.md ), [NEO-31 ](../../plans/NEO-31-implementation-plan.md ), [NEO-28 ](../../plans/NEO-28-implementation-plan.md ), [NEO-30 ](../../plans/NEO-30-implementation-plan.md ), [NEO-32 ](../../plans/NEO-32-implementation-plan.md ); [E1_M4_AbilityInputScaffold.md ](E1_M4_AbilityInputScaffold.md ); [E1M4 prototype backlog ](../../plans/E1M4-prototype-backlog.md ); `server/NeonSprawl.Server/Game/AbilityInput/` ; [`client/scripts/hotbar_loadout_client.gd` ](../../../client/scripts/hotbar_loadout_client.gd ), [`client/scripts/hotbar_state.gd` ](../../../client/scripts/hotbar_state.gd ), [`client/scripts/ability_cast_client.gd` ](../../../client/scripts/ability_cast_client.gd ), [`client/scripts/hotbar_cast_slot_resolver.gd` ](../../../client/scripts/hotbar_cast_slot_resolver.gd ), [`client/scripts/cooldown_snapshot_client.gd` ](../../../client/scripts/cooldown_snapshot_client.gd ), [`client/scripts/cooldown_state.gd` ](../../../client/scripts/cooldown_state.gd ); [server README — Hotbar loadout ](../../../server/README.md#hotbar-loadout-neo-29 ), [Ability cast (NEO-31) ](../../../server/README.md#ability-cast-neo-31 ), [Cooldown snapshot (NEO-32) ](../../../server/README.md#cooldown-snapshot-neo-32 ) |
| E2.M1 | In Progress | **NEO-33 landed:** frozen prototype trio ** `salvage` ** / ** `refine` ** / ** `intrusion` ** in [`content/skills/prototype_skills.json` ](../../../content/skills/prototype_skills.json ); PR gate + [`validate_content.py` ](../../../scripts/validate_content.py ) enforce schema, duplicate `id` , exact trio ids, and category coverage; designer note on ** `allowedXpSourceKinds` ** in [E2_M1 ](E2_M1_SkillDefinitionRegistry.md ) + [`content/README.md` ](../../../content/README.md ). **NEO-34 landed:** fail-fast server load of `content/skills/*.json` at startup — `server/NeonSprawl.Server/Game/Skills/` ([NEO-34](../../plans/NEO-34-implementation-plan.md)); config + discovery in [server README — Skill catalog ](../../../server/README.md#skill-catalog-contentskills-neo-34 ). **NEO-35 landed:** `ISkillDefinitionRegistry` / `SkillDefinitionRegistry` + DI ([NEO-35](../../plans/NEO-35-implementation-plan.md)). **NEO-36 landed:** versioned ** `GET /game/world/skill-definitions` ** ([NEO-36](../../plans/NEO-36-implementation-plan.md)) — `SkillDefinitionsWorldApi` + `SkillDefinitionsListDtos` in `server/NeonSprawl.Server/Game/Skills/` ; Bruno `bruno/neon-sprawl-server/skill-definitions/` ; manual QA [`NEO-36` ](../../manual-qa/NEO-36.md ). **E2.M2 consumer (NEO-38 landed):** grant path validates `skillId` + `sourceKind` vs catalog ** `allowedXpSourceKinds` ** on ** `POST …/skill-progression` ** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); see [server README — Skill progression grant ](../../../server/README.md#skill-progression-grant-neo-38 ) and [E2_M2 ](E2_M2_XpAwardAndLevelEngine.md ). | [NEO-33 ](../../plans/NEO-33-implementation-plan.md ), [NEO-34 ](../../plans/NEO-34-implementation-plan.md ), [NEO-35 ](../../plans/NEO-35-implementation-plan.md ), [NEO-36 ](../../plans/NEO-36-implementation-plan.md ); [E2_M1 ](E2_M1_SkillDefinitionRegistry.md ); [module_dependency_register.md ](module_dependency_register.md ) **E2.M1 note** ; `server/NeonSprawl.Server/Game/Skills/` ; [`docs/manual-qa/NEO-36.md` ](../../manual-qa/NEO-36.md ); [server README — Skill definitions (NEO-36) ](../../../server/README.md#skill-definitions-neo-36 ) |
| E2.M1 | In Progress | **NEO-33 landed:** frozen prototype trio ** `salvage` ** / ** `refine` ** / ** `intrusion` ** in [`content/skills/prototype_skills.json` ](../../../content/skills/prototype_skills.json ); PR gate + [`validate_content.py` ](../../../scripts/validate_content.py ) enforce schema, duplicate `id` , exact trio ids, and category coverage; designer note on ** `allowedXpSourceKinds` ** in [E2_M1 ](E2_M1_SkillDefinitionRegistry.md ) + [`content/README.md` ](../../../content/README.md ). **NEO-34 landed:** fail-fast server load of `content/skills/*.json` at startup — `server/NeonSprawl.Server/Game/Skills/` ([NEO-34](../../plans/NEO-34-implementation-plan.md)); config + discovery in [server README — Skill catalog ](../../../server/README.md#skill-catalog-contentskills-neo-34 ). **NEO-35 landed:** `ISkillDefinitionRegistry` / `SkillDefinitionRegistry` + DI ([NEO-35](../../plans/NEO-35-implementation-plan.md)). **NEO-36 landed:** versioned ** `GET /game/world/skill-definitions` ** ([NEO-36](../../plans/NEO-36-implementation-plan.md)) — `SkillDefinitionsWorldApi` + `SkillDefinitionsListDtos` in `server/NeonSprawl.Server/Game/Skills/` ; Bruno `bruno/neon-sprawl-server/skill-definitions/` ; manual QA [`NEO-36` ](../../manual-qa/NEO-36.md ). **E2.M2 consumer (NEO-38 landed):** grant path validates `skillId` + `sourceKind` vs catalog ** `allowedXpSourceKinds` ** on ** `POST …/skill-progression` ** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); see [server README — Skill progression grant ](../../../server/README.md#skill-progression-grant-neo-38 ) and [E2_M2 ](E2_M2_XpAwardAndLevelEngine.md ). | [NEO-33 ](../../plans/NEO-33-implementation-plan.md ), [NEO-34 ](../../plans/NEO-34-implementation-plan.md ), [NEO-35 ](../../plans/NEO-35-implementation-plan.md ), [NEO-36 ](../../plans/NEO-36-implementation-plan.md ); [E2_M1 ](E2_M1_SkillDefinitionRegistry.md ); [module_dependency_register.md ](module_dependency_register.md ) **E2.M1 note** ; `server/NeonSprawl.Server/Game/Skills/` ; [`docs/manual-qa/NEO-36.md` ](../../manual-qa/NEO-36.md ); [server README — Skill definitions (NEO-36) ](../../../server/README.md#skill-definitions-neo-36 ) |
| E2.M3 | In Progress | **NEO-45 landed:** prototype ** `salvage` ** mastery catalog + CI gates (see [NEO-45 plan ](../../plans/NEO-45-implementation-plan.md )). **NEO-46 landed:** fail-fast server load under `server/NeonSprawl.Server/Game/Mastery/` — `MasteryCatalogLoader` , `IMasteryCatalogRegistry` , cross-check vs `ISkillDefinitionRegistry` , Slice 4 + ** `tierIndex` ** 1..N gate; see [NEO-46 plan ](../../plans/NEO-46-implementation-plan.md ). **NEO-47 landed:** `PerkUnlockEngine` , `IPlayerPerkStateStore` + ** `V004` **, level-up hook in skill XP grants; see [NEO-47 plan ](../../plans/NEO-47-implementation-plan.md ). ** Still planned:** perk HTTP + Bruno ([NEO-48](https://linear.app/neon-sprawl/issue/NEO-48)), telemetry hooks ([NEO-49](https://linear.app/neon-sprawl/issue/NEO-49 )). | [NEO-45 ](../../plans/NEO-45-implementation-plan.md ), [NEO-46 ](../../plans/NEO-46-implementation-plan.md ), [NEO-47 ](../../plans/NEO-47 -implementation-plan.md), [E2M3-pre-production-backlog ](../../plans/E2M3-pre-production-backlog.md ), [E2_M3 ](E2_M3_MasteryAndPerkUnlocks.md ) |
| E2.M3 | In Progress | **NEO-45 landed:** prototype ** `salvage` ** mastery catalog + CI gates (see [NEO-45 plan ](../../plans/NEO-45-implementation-plan.md )). **NEO-46 landed:** fail-fast server load under `server/NeonSprawl.Server/Game/Mastery/` — `MasteryCatalogLoader` , `IMasteryCatalogRegistry` , cross-check vs `ISkillDefinitionRegistry` , Slice 4 + ** `tierIndex` ** 1..N gate; see [NEO-46 plan ](../../plans/NEO-46-implementation-plan.md ). **NEO-47 landed:** `PerkUnlockEngine` , `IPlayerPerkStateStore` + ** `V004` **, level-up hook in skill XP grants; see [NEO-47 plan ](../../plans/NEO-47-implementation-plan.md ). ** NEO-49 landed:** comment-only ** `perk_unlock` ** telemetry hook site in [`PerkUnlockEngine.TryUnlockPerks` ](../../../server/NeonSprawl.Server/Game/Mastery/PerkUnlockEngine.cs ) ([NEO-49 plan](../../plans/NEO-49-implementation-plan.md), [`NEO-49` manual QA ](../../manual-qa/NEO-49.md )); [server README — Perk unlock telemetry (NEO-49) ](../../../server/README.md#perk-unlock-engine-and-telemetry-hooks-neo-47-neo-49 ). **Still planned:** perk HTTP + Bruno ([NEO-48](https://linear.app/neon-sprawl/issue/NEO-48 )). | [NEO-45 ](../../plans/NEO-45-implementation-plan.md ), [NEO-46 ](../../plans/NEO-46-implementation-plan.md ), [NEO-47 ](../../plans/NEO-47 -implementation-plan.md), [NEO-49 ](../../plans/NEO-49 -implementation-plan.md), [E2M3-pre-production-backlog ](../../plans/E2M3-pre-production-backlog.md ), [E2_M3 ](E2_M3_MasteryAndPerkUnlocks.md ) |
| E2.M2 | In Progress | **NEO-37 landed:** versioned ** `GET /game/players/{id}/skill-progression` ** ([NEO-37](../../plans/NEO-37-implementation-plan.md)) — read model for every registered skill; known-player gate via `IPositionStateStore` ; [server README — Skill progression snapshot (NEO-37) ](../../../server/README.md#skill-progression-snapshot-neo-37 ); manual QA [`NEO-37` ](../../manual-qa/NEO-37.md ). **NEO-38 landed:** ** `POST` ** same path — grant apply, persistence (`IPlayerSkillProgressionStore`, `V003` migration), structured denies + ** `levelUps` ** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); manual QA [`NEO-38` ](../../manual-qa/NEO-38.md ); Bruno `bruno/neon-sprawl-server/skill-progression/` ; [server README — Skill progression grant (NEO-38) ](../../../server/README.md#skill-progression-grant-neo-38 ). **NEO-39 landed:** data-driven `LevelCurve` content + schema + CI validation (`*_level_curve.json`) with fail-fast startup schema checks; progression GET/POST level resolution now uses `ISkillLevelCurve` content-backed thresholds ([NEO-39](../../plans/NEO-39-implementation-plan.md)); manual QA [`NEO-39` ](../../manual-qa/NEO-39.md ). **NEO-40 landed:** comment-only telemetry hook sites in [`SkillProgressionSnapshotApi.cs` ](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotApi.cs ) on ** `POST …/skill-progression` ** for future ** `xp_grant` ** / ** `level_up` ** ([NEO-40](../../plans/NEO-40-implementation-plan.md), [`NEO-40` manual QA ](../../manual-qa/NEO-40.md )). **NEO-41 landed:** gather prototype — ** `POST …/interact` ** with ** `kind: resource_node` ** grants ** `salvage` ** + ** `activity` ** (10 XP) via [`SkillProgressionGrantOperations` ](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantOperations.cs ) ([NEO-41](../../plans/NEO-41-implementation-plan.md), [`NEO-41` manual QA ](../../manual-qa/NEO-41.md )); [server README — Interaction ](../../../server/README.md#interaction-neo-9 ). **NEO-42 landed (prep):** ** `RefineActivitySkillXpGrant` ** / ** `RefineSkillXpConstants` ** + deny-registry factory + tests — same NEO-38 grant stack; **E3.M2** must invoke on craft/refine success ([NEO-42](../../plans/NEO-42-implementation-plan.md), [`NEO-42` manual QA ](../../manual-qa/NEO-42.md )); [server README — Craft / refine hook (NEO-42) ](../../../server/README.md#craft--refine-hook--skill-xp-neo-42 ). **NEO-43 landed (prep):** ** `MissionRewardSkillXpGrant` ** / ** `MissionRewardSkillXpConstants` ** + deny-registry factory + tests — same NEO-38 grant stack with fixed ** `sourceKind: mission_reward` **; **E7.M2** must invoke from quest hand-in ([NEO-43](../../plans/NEO-43-implementation-plan.md), [`NEO-43` manual QA ](../../manual-qa/NEO-43.md )); [server README — Mission / quest reward (NEO-43) ](../../../server/README.md#mission--quest-reward--skill-xp-neo-43 ). **Slice 3 still open:** [NEO-44 ](https://linear.app/neon-sprawl/issue/NEO-44 ). See [epic_02 — E2.M2 + Slice 3 ](../epics/epic_02_skills_and_progression.md ). | [NEO-37 ](../../plans/NEO-37-implementation-plan.md ), [NEO-38 ](../../plans/NEO-38-implementation-plan.md ), [NEO-39 ](../../plans/NEO-39-implementation-plan.md ), [NEO-40 ](../../plans/NEO-40-implementation-plan.md ), [NEO-41 ](../../plans/NEO-41-implementation-plan.md ), [NEO-42 ](../../plans/NEO-42-implementation-plan.md ), [NEO-43 ](../../plans/NEO-43-implementation-plan.md ), [E2_M2 ](E2_M2_XpAwardAndLevelEngine.md ); label ** `E2.M2` ** on NEO-37– NEO-41, NEO-42– NEO-44 |
| E2.M2 | In Progress | **NEO-37 landed:** versioned ** `GET /game/players/{id}/skill-progression` ** ([NEO-37](../../plans/NEO-37-implementation-plan.md)) — read model for every registered skill; known-player gate via `IPositionStateStore` ; [server README — Skill progression snapshot (NEO-37) ](../../../server/README.md#skill-progression-snapshot-neo-37 ); manual QA [`NEO-37` ](../../manual-qa/NEO-37.md ). **NEO-38 landed:** ** `POST` ** same path — grant apply, persistence (`IPlayerSkillProgressionStore`, `V003` migration), structured denies + ** `levelUps` ** ([NEO-38](../../plans/NEO-38-implementation-plan.md)); manual QA [`NEO-38` ](../../manual-qa/NEO-38.md ); Bruno `bruno/neon-sprawl-server/skill-progression/` ; [server README — Skill progression grant (NEO-38) ](../../../server/README.md#skill-progression-grant-neo-38 ). **NEO-39 landed:** data-driven `LevelCurve` content + schema + CI validation (`*_level_curve.json`) with fail-fast startup schema checks; progression GET/POST level resolution now uses `ISkillLevelCurve` content-backed thresholds ([NEO-39](../../plans/NEO-39-implementation-plan.md)); manual QA [`NEO-39` ](../../manual-qa/NEO-39.md ). **NEO-40 landed:** comment-only telemetry hook sites in [`SkillProgressionSnapshotApi.cs` ](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotApi.cs ) on ** `POST …/skill-progression` ** for future ** `xp_grant` ** / ** `level_up` ** ([NEO-40](../../plans/NEO-40-implementation-plan.md), [`NEO-40` manual QA ](../../manual-qa/NEO-40.md )). **NEO-41 landed:** gather prototype — ** `POST …/interact` ** with ** `kind: resource_node` ** grants ** `salvage` ** + ** `activity` ** (10 XP) via [`SkillProgressionGrantOperations` ](../../../server/NeonSprawl.Server/Game/Skills/SkillProgressionGrantOperations.cs ) ([NEO-41](../../plans/NEO-41-implementation-plan.md), [`NEO-41` manual QA ](../../manual-qa/NEO-41.md )); [server README — Interaction ](../../../server/README.md#interaction-neo-9 ). **NEO-42 landed (prep):** ** `RefineActivitySkillXpGrant` ** / ** `RefineSkillXpConstants` ** + deny-registry factory + tests — same NEO-38 grant stack; **E3.M2** must invoke on craft/refine success ([NEO-42](../../plans/NEO-42-implementation-plan.md), [`NEO-42` manual QA ](../../manual-qa/NEO-42.md )); [server README — Craft / refine hook (NEO-42) ](../../../server/README.md#craft--refine-hook--skill-xp-neo-42 ). **NEO-43 landed (prep):** ** `MissionRewardSkillXpGrant` ** / ** `MissionRewardSkillXpConstants` ** + deny-registry factory + tests — same NEO-38 grant stack with fixed ** `sourceKind: mission_reward` **; **E7.M2** must invoke from quest hand-in ([NEO-43](../../plans/NEO-43-implementation-plan.md), [`NEO-43` manual QA ](../../manual-qa/NEO-43.md )); [server README — Mission / quest reward (NEO-43) ](../../../server/README.md#mission--quest-reward--skill-xp-neo-43 ). **Slice 3 still open:** [NEO-44 ](https://linear.app/neon-sprawl/issue/NEO-44 ). See [epic_02 — E2.M2 + Slice 3 ](../epics/epic_02_skills_and_progression.md ). | [NEO-37 ](../../plans/NEO-37-implementation-plan.md ), [NEO-38 ](../../plans/NEO-38-implementation-plan.md ), [NEO-39 ](../../plans/NEO-39-implementation-plan.md ), [NEO-40 ](../../plans/NEO-40-implementation-plan.md ), [NEO-41 ](../../plans/NEO-41-implementation-plan.md ), [NEO-42 ](../../plans/NEO-42-implementation-plan.md ), [NEO-43 ](../../plans/NEO-43-implementation-plan.md ), [E2_M2 ](E2_M2_XpAwardAndLevelEngine.md ); label ** `E2.M2` ** on NEO-37– NEO-41, NEO-42– NEO-44 |
| E3.M1 | In Progress | **NEO-41 landed (prototype):** `POST …/interact` success on ** `resource_node` ** (`prototype_resource_node_alpha`) applies ** `salvage` ** skill XP (**`sourceKind: activity`**, 10 XP) via shared NEO-38 grant operations. **Still planned:** `GatherResult` , yields, inventory per [E3_M1 ](E3_M1_ResourceNodeAndGatherLoop.md ). | [NEO-41 ](../../plans/NEO-41-implementation-plan.md ), [E3_M1 ](E3_M1_ResourceNodeAndGatherLoop.md ); `server/NeonSprawl.Server/Game/Interaction/` , `Game/Skills/` |
| E3.M1 | In Progress | **NEO-41 landed (prototype):** `POST …/interact` success on ** `resource_node` ** (`prototype_resource_node_alpha`) applies ** `salvage` ** skill XP (**`sourceKind: activity`**, 10 XP) via shared NEO-38 grant operations. **Still planned:** `GatherResult` , yields, inventory per [E3_M1 ](E3_M1_ResourceNodeAndGatherLoop.md ). | [NEO-41 ](../../plans/NEO-41-implementation-plan.md ), [E3_M1 ](E3_M1_ResourceNodeAndGatherLoop.md ); `server/NeonSprawl.Server/Game/Interaction/` , `Game/Skills/` |