From 0b40ae35984ee0cbab34fb128fe973d8bfe46349 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Mon, 25 May 2026 10:44:02 -0400 Subject: [PATCH] NEO-82: Add code review for cast combat wiring. --- docs/reviews/2026-05-25-NEO-82.md | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/reviews/2026-05-25-NEO-82.md diff --git a/docs/reviews/2026-05-25-NEO-82.md b/docs/reviews/2026-05-25-NEO-82.md new file mode 100644 index 0000000..0e51b7a --- /dev/null +++ b/docs/reviews/2026-05-25-NEO-82.md @@ -0,0 +1,63 @@ +# Code review — NEO-82 Wire ability-cast into combat engine + cast response + +**Date:** 2026-05-25 +**Scope:** Branch `NEO-82-wire-ability-cast-combat-resolution` · commits `4126ac9`–`e8be100` vs `origin/main` +**Base:** `origin/main` + +## Verdict + +**Approve with nits** + +## Summary + +NEO-82 delivers **E5M1-07**: after E1.M4 cast gates pass, `AbilityCastApi` invokes `CombatOperations.TryResolve`, returns nested wire `combatResolution` on accept, maps combat denies (notably `target_defeated`) to JSON cast denies without cooldown commit, and replaces the global prototype cooldown with per-ability catalog `cooldownSeconds` on successful resolve. Handler flow, DTO shape, kickoff decisions (nested block, no cooldown on defeated deny), Bruno happy + defeat spine, and README updates align with the implementation plan. Four new integration tests cover zero-damage accept, four-pulse defeat chain, and guard-vs-pulse catalog cooldown; existing cast tests assert resolution fields. Residual risk is low — server-only HTTP extension with clear deny vocabulary; module tracking docs should sync on merge (same follow-up pattern as NEO-81). + +## Documentation checked + +| Document | Result | +|----------|--------| +| [`docs/plans/NEO-82-implementation-plan.md`](../plans/NEO-82-implementation-plan.md) | **Matches** — kickoff decisions, scope, acceptance checklist, expected cast table, and reconciliation align with code. | +| [`docs/plans/E5M1-prototype-backlog.md`](../plans/E5M1-prototype-backlog.md) · **E5M1-07** | **Matches** — AC checked, landed note added. | +| [`docs/decomposition/modules/E5_M1_CombatRulesEngine.md`](../decomposition/modules/E5_M1_CombatRulesEngine.md) | **Partially matches** — prototype notes paragraph updated for NEO-82; **Status** still reads “E5M1-07+ pending”; no **Related implementation slices** entry for E5M1-07 (NEO-78–NEO-81 pattern). | +| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Partially matches** — E5.M1 note cites NEO-81; **NEO-82 landed** (cast → `combatResolution`, catalog cooldown) not yet added. | +| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Partially matches** — E5.M1 row still ends with “accepts without **`CombatResolution`** yet”; should cite NEO-82 landed after merge. | +| [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **Matches** — server validates and computes all combat outcomes; client unchanged (NEO-85). | +| [`docs/decomposition/modules/pvp_combat_integration.md`](../decomposition/modules/pvp_combat_integration.md) | **N/A** — prototype NPC targets only. | +| [`server/README.md`](../../server/README.md) | **Matches** — cast `combatResolution` table, `target_defeated` deny, catalog cooldown policy, combat engine cross-link. | +| Full-stack epic decomposition | **Matches** — plan documents NEO-85 client counterpart; no Godot change in scope. | +| Manual QA | **N/A** — kickoff skip; Bruno + README sufficient. | + +## Blocking issues + +None. + +## Suggestions + +1. **Module doc status + slice entry** — In `docs/decomposition/modules/E5_M1_CombatRulesEngine.md`, update **Status** to cite **E5M1-07 NEO-82 landed** (cast POST returns `combatResolution`), and add **E5M1-07 (NEO-82)** under **Related implementation slices** linking the plan and README ability-cast section (mirror NEO-81 landed pattern). + +2. **Dependency register + alignment table** — Extend the **E5.M1 note** in `module_dependency_register.md` and the E5.M1 row in `documentation_and_implementation_alignment.md` with **NEO-82 landed:** `AbilityCastApi` → `CombatOperations.TryResolve`, nested `combatResolution`, per-ability catalog cooldown, `target_defeated` cast deny. + +3. **Bruno defeat spine HP stepping** — Plan §6 calls for asserting HP 75 → 50 → 25 → 0 on casts 1–4; `Post cast lock pulse defeat spine.bru` only captures/asserts the fourth cast. Consider storing intermediate cast bodies in pre-request and asserting `targetRemainingHp` on each pulse for stronger manual lock-in (non-blocking — fifth deny + integration test cover the critical path). + +## Nits + +- Nit: `PostAbilityCast_ShouldDefeatTargetAfterFourPulses_ThenDenyTargetDefeated` runs pulses 1–3 inside **Act**; move them to **Arrange** for stricter AAA (same note as NEO-81 four-pulse pattern). + +- Nit: After successful `TryResolve`, `TryGetDefinition` runs again solely for `CooldownSeconds` — `CombatOperations` already loaded the definition internally. Acceptable for thin handler clarity; optional future refactor if catalog lookups become hot-path. + +- Nit: `AbilityPrototypeCooldown.GlobalDuration` is `[Obsolete]` but retained — matches plan; no remaining runtime references in tests or accept path. + +## Verification + +```bash +cd /home/don/neon-sprawl/server +dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj \ + --filter "FullyQualifiedName~AbilityCastApiTests|FullyQualifiedName~CooldownSnapshotApiTests" +dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj +``` + +**AbilityCast + CooldownSnapshot tests:** 22/22 passed (local run). + +**Full suite:** 394/394 passed (local run). + +**Bruno (manual):** Run `bruno/neon-sprawl-server/ability-cast/` folder — happy cast + defeat spine (seq 23, ~10s pre-request cooldown waits).