# Code Review — NEO-39

- **Date:** 2026-05-06
- **Scope:** Working tree / unstaged changes for NEO-39 (`Data-driven skill level curve + CI validation`)
- **Base:** `main` (inferred)

## Verdict

Request changes.

## Summary

The change successfully introduces data-driven skill level thresholds, adds CI validation for `*_skills.json` and `*_level_curve.json`, and wires runtime level resolution through `ISkillLevelCurve`. API behavior for progression GET/POST remains stable and test coverage is healthy overall, with full server test suite passing.  
The main risk is a startup-validation gap: runtime curve loading currently performs custom checks instead of schema evaluation and therefore does not enforce schema constraints such as `additionalProperties: false`. That allows schema-invalid curve content to pass startup, which conflicts with the story’s fail-fast validation intent.

## Documentation checked

- `docs/plans/NEO-39-implementation-plan.md` — **partially matches** (most acceptance criteria are implemented; startup validation is not schema-equivalent to declared contract strictness).
- `docs/decomposition/modules/E2_M2_XpAwardAndLevelEngine.md` — **matches** (module snapshot updated for NEO-39 landed behavior).
- `docs/decomposition/modules/module_dependency_register.md` — **matches** (E2.M2 remains In Progress; dependencies/contract shape unchanged).
- `docs/decomposition/modules/documentation_and_implementation_alignment.md` — **partially matches** (E2.M2 tracking row still lists NEO-39 as follow-on; should be updated after merge to avoid stale status narrative).

## Blocking issues

1. ~~Runtime level-curve validation is not schema-equivalent, so schema-invalid files can still boot successfully.~~
   - ~~Evidence: `server/NeonSprawl.Server/Game/Skills/ContentBackedSkillLevelCurve.cs` validates only selected fields manually and does not evaluate `content/schemas/level-curve.schema.json` constraints like `additionalProperties: false`.~~
   - ~~Impact: Violates fail-fast startup validation intent for invalid curve content and can permit config drift between CI and runtime.~~
   - ~~Recommendation: Validate the loaded JSON against `level-curve.schema.json` at startup (or implement strict parity checks for all schema constraints, including additional properties and structural limits), then keep existing domain checks (strictly increasing rows, first row = level 1 / xp 0).~~
   Done. Startup load now evaluates `level-curve.schema.json` via `JsonSchema.Net` in `ContentBackedSkillLevelCurve.Load(...)` before domain checks, and `ContentBackedSkillLevelCurveTests.Load_ShouldThrow_WhenCurveHasUnexpectedTopLevelProperty` verifies `additionalProperties: false` enforcement.

## Suggestions

1. ~~Update `docs/decomposition/modules/documentation_and_implementation_alignment.md` E2.M2 snapshot text to reflect that NEO-39 is landed (currently phrased as a follow-on item).~~  
   Done. E2.M2 tracking row now records NEO-39 as landed (data-driven `LevelCurve` + CI + fail-fast startup checks) and links `docs/plans/NEO-39-implementation-plan.md` plus `docs/manual-qa/NEO-39.md`.
2. ~~Refresh stale placeholder wording in `server/NeonSprawl.Server.Tests/Game/Skills/SkillProgressionGrantApiTests.cs` comments (the threshold behavior is now content-backed, not placeholder-based).~~  
   Done. Updated the stale arrange comment in `PostSkillProgression_ShouldReportLevelUps_WhenThresholdCrossed` to reference content-backed thresholds from `prototype_level_curve.json`.

## Nits

- ~~Nit: `SkillDefinitionCatalogLoader` XML summary still says `content/skills/*.json`; implementation now intentionally scopes to `*_skills.json`.~~  
  Done. Updated XML summary in `server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs` to `content/skills/*_skills.json`.

## Verification

- `python3 scripts/validate_content.py`
- `dotnet test server/NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj --no-restore`
- Manual negative check: add an extra top-level property to `content/skills/prototype_level_curve.json` and verify server startup fails (expected once runtime schema validation parity is implemented).
