meta { name: GET skill progression type: http seq: 1 } docs { NEO-37: per-player read model; join with GET /game/world/skill-definitions for display names. NEO-39: XP persists (in-memory/Postgres) and levels are derived from content/skills/prototype_level_curve.json. These checks do not assume a fresh server. Restart server (or truncate DB in Postgres mode) to see all-zero defaults again. } get { url: {{baseUrl}}/game/players/dev-local-1/skill-progression body: none auth: none } tests { test("returns 200 JSON with schema v1 and skills array", function () { expect(res.getStatus()).to.equal(200); expect(res.getHeader("content-type")).to.contain("application/json"); const body = res.getBody(); expect(body.schemaVersion).to.equal(1); expect(body.playerId).to.equal("dev-local-1"); expect(body.skills).to.be.an("array"); expect(body.skills.length).to.equal(3); }); test("frozen prototype trio with levels consistent with content-backed curve", function () { const body = res.getBody(); const ids = new Set(body.skills.map((x) => x.id)); expect(ids.has("salvage")).to.equal(true); expect(ids.has("refine")).to.equal(true); expect(ids.has("intrusion")).to.equal(true); /** Matches prototype_level_curve.json (NEO-39). */ const thresholds = [ { level: 1, requiredXp: 0 }, { level: 2, requiredXp: 100 }, { level: 3, requiredXp: 250 }, { level: 4, requiredXp: 450 }, { level: 5, requiredXp: 700 }, ]; const expectedLevel = (xp) => { const clamped = Math.max(0, xp); let level = 1; for (const row of thresholds) { if (clamped < row.requiredXp) { break; } level = row.level; } return level; }; for (const id of ["salvage", "refine", "intrusion"]) { const row = body.skills.find((x) => x.id === id); expect(row).to.be.an("object"); expect(row.xp).to.be.a("number"); expect(row.level).to.be.a("number"); expect(row.xp).to.be.at.least(0); expect(row.level).to.be.at.least(1); expect(row.level).to.equal(expectedLevel(row.xp)); } }); }