6.3 KiB
NEO-37 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-37 |
| Title | Skill progression snapshot (read model) |
| Linear | https://linear.app/neon-sprawl/issue/NEO-37/skill-progression-snapshot-read-model |
| Module | E2.M2 — XpAwardAndLevelEngine |
Kickoff clarifications
Asked (Agent AskQuestion): Lock plan defaults for route, 404 rule, JSON shape (schemaVersion + playerId + skills with id / xp / level), Bruno folder style, and docs/manual-qa/NEO-37.md.
Answer: User chose Proceed — use these defaults in the plan.
Goal, scope, and out-of-scope
Goal: Ship a versioned GET read model for per-player skill XP and level for every SkillDef id from ISkillDefinitionRegistry, bootstrapping xp = 0 and level = 1 before any grants exist. Match NEO-36 envelope patterns (schemaVersion, System.Text.Json property names). skills is semantically unordered — clients key by id (unlike the world skill catalog projection where id order is pinned for discovery).
In scope (from Linear):
- Versioned JSON for the agreed player-scoped path.
- One row per registered skill id; coherent defaults (0 XP, level 1).
- Unit/integration tests (AAA) and Bruno collection folder mirroring NEO-36 style.
- Server README section for the endpoint.
Out of scope (from Linear):
- Applying XP grants (NEO-38).
- Data-driven level curves (separate issue; this slice uses fixed baseline level 1 for all rows).
- Gather/craft/combat integration (Epic 2 Slice 3).
Acceptance criteria checklist
- Versioned JSON response for
GET /game/players/{id}/skill-progression(schemaVersion1). - One row per registered skill id;
xp= 0,level= 1 for all rows in this story; consumers treatskillsas unordered and key byid(wire order not contractual). - Automated tests (AAA); Bruno folder mirroring NEO-36 (
skill-definitionsconventions). server/README.mdsection documenting the endpoint (curl + pointers to plan / manual QA / Bruno).
Technical approach
-
Route:
GET /game/players/{id}/skill-progressionalongside existing/game/players/{id}/…reads (cooldown-snapshot,hotbar-loadout, etc.). -
Player gate:
404when!IPositionStateStore.TryGetPosition(id, …), matchingCooldownSnapshotApi(known player/session only). -
Response: Top-level
schemaVersion(1, const on a response type like NEO-36’sSkillDefinitionsListResponse.CurrentSchemaVersion),playerId(id.Trim(), consistent withHotbarLoadoutResponse), andskills: array of objectsid(skill def id),xp(int, 0),level(int, 1). Array order is not part of the public contract — clients index byid. Do not duplicate catalog fields (displayName, etc.); clients join withGET /game/world/skill-definitionsif needed. -
Data source: For NEO-37, build rows from
ISkillDefinitionRegistry(no persistence). Sort byid(ordinal) before serialization for stable diffs only — semantics remain keyed byid, not by array index. NEO-38 will introduce stored progression and merge/override defaults; until then there is no separate store interface unless implementation discovers a need for testing seams—prefer a small private/static builder method next to the route to keep diff small. -
Bruno:
bruno/neon-sprawl-server/skill-progression/withGet skill progression.bru. Tests assert 200,schemaVersion,skillslength 3, frozen ids present +xp/leveldefaults — do not assert array order. -
README: Add a subsection after the NEO-36 skill definitions block — curl example for
dev-local-1(or generic{id}) and links to this plan,docs/manual-qa/NEO-37.md, and Bruno path.
Files to add
| Path | Purpose |
|---|---|
server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotApi.cs |
Map* extension registering the GET route; position check + registry → JSON. |
server/NeonSprawl.Server/Game/Skills/SkillProgressionSnapshotDtos.cs |
Versioned response + per-skill row DTOs (schemaVersion, playerId, skills). |
server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionSnapshotApiTests.cs |
InMemoryWebApplicationFactory: success for dev-local-1; 404 for unknown player; ReadFromJsonAsync asserts schema, trio by id (map), defaults (order-independent). |
bruno/neon-sprawl-server/skill-progression/Get skill progression.bru |
Manual / Bruno verification (mirror skill-definitions/Get skill definitions.bru style). |
bruno/neon-sprawl-server/skill-progression/folder.bru |
Optional; align with Bruno folder metadata used beside skill-definitions. |
docs/manual-qa/NEO-37.md |
Short checklist: run server, GET with known vs unknown player id. |
Files to modify
| Path | Rationale |
|---|---|
server/NeonSprawl.Server/Program.cs |
Call app.MapSkillProgressionSnapshotApi() (exact name may match class) next to other Map*Api registrations. |
server/README.md |
Document GET /game/players/{id}/skill-progression, curl, and links (plan / manual QA / Bruno). |
Tests
| Test file | What it covers |
|---|---|
server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionSnapshotApiTests.cs |
GET returns 404 for missing-player. GET for dev-local-1 returns 200; body has those three **id**s with xp 0, level 1, without asserting JSON array order. AAA comments per csharp-style. |
Bruno scripts complement automated coverage; they do not replace it.
Open questions / risks
- NEO-38 merge point: When grants land, prefer replacing the pure “zeros builder” with a store-backed projection without changing the public JSON contract (only values change). If
schemaVersionmust bump later, coordinate with clients. - None otherwise.