neon-sprawl/docs/plans/NEO-28-implementation-plan.md

7.8 KiB

NEO-28 — Implementation plan

Story reference

Field Value
Key NEO-28
Title E1M4-03: Combat accept/deny integration + reason-code UX
Linear NEO-28
Slug E1M4-03
Git branch NEO-28-combat-accept-deny-reason-ux
Parent context E1.M4 prototype backlog · E1.M4 — AbilityInputScaffold
Related modules E1.M3 — InteractionAndTargetingLayer, E5.M1 — CombatRulesEngine

Kickoff clarifications

  • UX surface: Asked which prototype surface should render denied cast reasons. User chose a HUD/debug label: add a small on-screen cast feedback/reason line near the existing target/hotbar debug UI.
  • Validation scope: Asked how strict the prototype server-side cast denial contract should be. Recommendation was accepted: use the stricter lock contract. POST /game/players/{id}/ability-cast should validate the request target against the server-acknowledged lock, prototype target registry, and current server position; return invalid_target for missing/unknown/mismatch and out_of_range when the locked target is too far.
  • Linear blocker: Linear showed NEO-28 blocked by NEO-31. User confirmed NEO-31 is merged and Done; kickoff removed the blockedBy: NEO-31 relation from NEO-28 and left NEO-28 In Progress.

Goal, scope, and out-of-scope

Goal: Consume authoritative cast accept/deny responses in the client cast path and make rejected casts visible through stable reason-code UX.

In scope

  • Extend the current prototype ability-cast response contract with target-related denial reasons needed by E1.M4/E5.M1 integration.
  • Server-side cast validation for target presence, registry membership, server lock match, and range against the authoritative position snapshot.
  • Client-side handling for accepted and denied cast responses, including HUD/debug feedback for reason codes.
  • Documentation of the denial contract and reason list so E1.M4 and E5.M1 do not drift.

Out of scope

  • Rich combat VFX, animation, final combat log styling, damage/healing resolution, cooldown presentation, and final E5.M1 CombatResolution semantics.
  • Production telemetry ingestion; keep existing TODO(E9.M1) hook notes.

Acceptance criteria checklist

  • Denied casts render a player-visible reason path for at least invalid_target and out_of_range.
  • Successful casts transition to post-request state without conflicting local rollback behavior.
  • Denial contract and reason list are documented in plan/module docs to prevent drift.

Technical approach

  1. Server contract — Extend AbilityCastApi from NEO-31 rather than adding a parallel endpoint. Keep the v1 JSON shape (schemaVersion, accepted, optional reasonCode) and add stable target denial constants: invalid_target and out_of_range.

  2. Server validation order — Preserve existing request/loadout/ability validation first. After the loadout match succeeds, validate target authority: non-empty request targetId, known PrototypeTargetRegistry id, request target equals IPlayerTargetLockStore current lock, and target is within horizontal range of the authoritative IPositionStateStore snapshot. Return a JSON deny response with accepted: false for target denials; reserve transport status codes for malformed/missing-player cases already established by NEO-31.

  3. Client response handling — Make AbilityCastClient emit a signal for completed cast responses so main.gd can update a HUD/debug cast feedback label. Denies should display ability_cast_denied: <reasonCode> or equivalent player-visible text; accepts should clear or replace stale deny text with a lightweight accepted/post-request message.

  4. Optimistic state reconciliation — Keep the current prototype behavior conservative: ability_cast_requested only logs once the POST starts, and no local cooldown/rollback state is introduced in this story. On deny, the feedback label becomes the reconciliation surface; on accept, the client avoids any rollback behavior that would conflict with later cooldown work in NEO-32.

  5. Documentation — Update E1.M4 and E5.M1 docs with the prototype accept/deny contract, including the reason list and which reasons belong to hotbar/loadout validation versus combat target validation.

Files to add

Path Rationale
docs/manual-qa/NEO-28.md Manual QA checklist for accepted cast, invalid_target, out_of_range, and HUD/debug feedback visibility.

Files to modify

Path Rationale
server/NeonSprawl.Server/Game/AbilityInput/AbilityCastApi.cs Add target authority validation, reason constants, and IPlayerTargetLockStore / PrototypeTargetRegistry use.
server/NeonSprawl.Server/Game/AbilityInput/AbilityCastDtos.cs Update summary/comments if needed to describe accept/deny v1 target semantics.
server/NeonSprawl.Server.Tests/Game/AbilityInput/AbilityCastApiTests.cs Cover invalid_target, out_of_range, and strict lock-match behavior.
client/scripts/ability_cast_client.gd Surface parsed accept/deny responses via signal instead of only push_warning.
client/scripts/main.gd Add/update HUD/debug cast feedback label wiring and reconcile accepted/denied response display.
client/README.md Document cast deny feedback behavior for manual prototype use.
server/README.md Document the extended ability-cast denial contract and reason codes.
docs/decomposition/modules/E1_M4_AbilityInputScaffold.md Update implementation snapshot, Linear row, and denial contract notes for E1M4-03.
docs/decomposition/modules/E5_M1_CombatRulesEngine.md Document the prototype accept/deny handoff and deferred final CombatResolution scope.
bruno/neon-sprawl-server/ability-cast/Post cast happy.bru Ensure existing happy path still asserts accepted response after target validation setup.
bruno/neon-sprawl-server/ability-cast/Post cast loadout mismatch.bru Keep non-target denial regression aligned with response contract.

Tests

Suite What it covers
server/NeonSprawl.Server.Tests/Game/AbilityInput/AbilityCastApiTests.cs Accepted cast with locked in-range target; invalid_target for missing/unknown/mismatched target; out_of_range when authoritative position is outside prototype lock radius; existing loadout denies remain stable.
client/test/ability_cast_client_test.gd Response parsing and signal emission for accepted and denied JSON bodies, including invalid_target / out_of_range.
client/test/hotbar_cast_slot_resolver_test.gd No required change expected unless target payload rules change; keep as regression for request formation from server-ack target state.
docs/manual-qa/NEO-28.md Human verification of HUD/debug feedback, accepted cast display, and denial reason visibility.
Bruno ability-cast/*.bru Add or adjust requests/tests if needed for target validation preconditions and stable response assertions.

Open questions / risks

  • Server lock setup in tests / Bruno: Existing happy-path requests may need a target-select setup step before cast because the stricter contract requires a server lock.
  • Out-of-range reproduction: Current target selection rejects out-of-range locks, so out_of_range cast tests may need to lock while in range, then move the authoritative position out of range before casting.
  • UI scene edit size: Adding a HUD/debug label may touch client/main.tscn; keep it minimal and aligned with existing debug labels.