5.9 KiB
Code review — NEO-44 gig XP on combat defeat
Date: 2026-05-25
Scope: Branch NEO-44-gig-xp-combat-defeat · commits 30d379c–71a6761 vs origin/main
Base: origin/main
Verdict
Approve with nits
Summary
NEO-44 delivers E5M1-10: when AbilityCastApi accepts a cast with combatResolution.targetDefeated === true, the server grants 25 gig XP to prototype main gig breach via CombatDefeatGigXpGrant and a dedicated IPlayerGigProgressionStore (in-memory + Postgres V007), explicitly not calling SkillProgressionGrantOperations. A read-only GET /game/players/{id}/gig-progression snapshot (NEO-37 gate pattern) exposes mainGigId, fixed level: 1, and accumulating xp. Integration tests cover GET 404/default, grant helper, defeat chain (4th pulse +25, 5th no delta, skill snapshot unchanged), and optional Postgres persistence; Bruno gig-progression/ + docs/manual-qa/NEO-44.md complete verification. Implementation matches the kickoff plan and acceptance checklist. Residual risk is low — server-only slice; Godot gig row remains NEO-86 out of scope.
Documentation checked
| Document | Result |
|---|---|
docs/plans/NEO-44-implementation-plan.md |
Matches — kickoff decisions (breach, 25 XP, GET-only wire, Postgres + in-memory), scope, expected outcomes table, reconciliation, and acceptance checklist align with code. |
docs/plans/E5M1-prototype-backlog.md · E5M1-10 |
Matches — AC checked; landed note and gig-vs-skill invariant documented. |
docs/decomposition/modules/E5_M1_CombatRulesEngine.md |
Matches — Purpose/Responsibilities updated for gig XP via NEO-44 (not E2.M2 skill XP). |
docs/decomposition/modules/E2_M2_XpAwardAndLevelEngine.md |
Matches — NEO-44 landed paragraph added; removed from backlog list. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
Matches — E2.M2 and E5.M1 rows updated with NEO-44 landed + gig progression links. |
docs/decomposition/modules/client_server_authority.md |
Matches — server-authoritative defeat grant; client polls GET when NEO-86 lands. |
docs/game-design/progression.md |
Matches — combat → gig XP invariant referenced in README and manual QA. |
server/README.md |
Matches — gig progression GET section, combat defeat invariant, ability-cast NEO-44 note. |
| Full-stack epic decomposition | Matches — plan documents NEO-85/NEO-86 client counterpart; no Godot change in scope. |
docs/manual-qa/NEO-44.md |
Matches — curl defeat spine + skill control snapshot. |
Blocking issues
None.
Suggestions
-
Stale E5.M1 Purpose / Responsibilities —Done. Purpose, Responsibilities, and E2.M2 dependency bullets updated.E5_M1_CombatRulesEngine.mdline 17 still says combat “feeds XP awards through E2.M2”; line 26 says “Award combat XP via E2.M2 integration.” NEO-44 explicitly routes combat defeat toIPlayerGigProgressionStore. Update those bullets to “gig XP via dedicated store (NEO-44); not E2.M2 skill XP” so module intent matches the landed slice. -
Stale E2.M2 implementation snapshot —Done. NEO-44 landed paragraph added; backlog line trimmed to NEO-42 craft wiring only.E2_M2_XpAwardAndLevelEngine.mdStill backlog paragraph lists NEO-44; alignment register already marks NEO-44 landed. Add a NEO-44 landed (E5.M1) paragraph (cross-link E5.M1 / gig GET) and remove NEO-44 from backlog. -
Best-effort grant observability (optional) —Done.CombatDefeatGigXpGrantdiscardsTryApplyXpDeltareturn value per kickoff; acceptable for prototype. Consider a debug/diagnostic log when apply returnsfalseon Postgres (unknown player, FK miss) before production hardening — cast accept should remain unchanged.LogDebugwhen store write fails;AbilityCastApipasses logger fromILoggerFactory.
Nits
-
Nit: BrunoDone. GET URL usesGet gig progression after defeat spine.bruhardcodesdev-local-1in the GET URL while pre-request usesplayerIdenv — use{{playerId}}(or env var) for consistency if multi-player Bruno runs matter.{{playerId}}. -
Nit: No automated test for
prototype_target_betadefeat grant — plan adopts shared hook; alpha chain + integration test suffice for prototype; optional one-pulse-on-beta smoke if beta HP path diverges later. -
Nit:
Get gig progression.bruassertsxpis a number but not0on fresh server — docs note Postgres may retain prior totals; intentional for shared DB dev; fine for CI in-memory.
Verification
cd /home/don/neon-sprawl/server
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj \
--filter "FullyQualifiedName~Gigs|FullyQualifiedName~PostAbilityCast_ShouldGrantGigXp"
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj \
--filter "FullyQualifiedName~AbilityCastApiTests"
NEO-44 filtered tests: 6/6 passed (local run).
AbilityCastApiTests: 21/21 passed (local run).
Postgres (optional): GigProgressionGrantPersistenceIntegrationTests when ConnectionStrings__NeonSprawl is set.
Bruno (manual): Run bruno/neon-sprawl-server/gig-progression/ — seq 1 fresh GET; seq 2 defeat spine (~13s pre-request) asserts breach.xp === 25 and salvage/refine unchanged.