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-castshould validate the request target against the server-acknowledged lock, prototype target registry, and current server position; returninvalid_targetfor missing/unknown/mismatch andout_of_rangewhen 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-31relation 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
CombatResolutionsemantics. - 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_targetandout_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
-
Server contract — Extend
AbilityCastApifrom NEO-31 rather than adding a parallel endpoint. Keep the v1 JSON shape (schemaVersion,accepted, optionalreasonCode) and add stable target denial constants:invalid_targetandout_of_range. -
Server validation order — Preserve existing request/loadout/ability validation first. After the loadout match succeeds, validate target authority: non-empty request
targetId, knownPrototypeTargetRegistryid, request target equalsIPlayerTargetLockStorecurrent lock, and target is within horizontal range of the authoritativeIPositionStateStoresnapshot. Return a JSON deny response withaccepted: falsefor target denials; reserve transport status codes for malformed/missing-player cases already established by NEO-31. -
Client response handling — Make
AbilityCastClientemit a signal for completed cast responses somain.gdcan update a HUD/debug cast feedback label. Denies should displayability_cast_denied: <reasonCode>or equivalent player-visible text; accepts should clear or replace stale deny text with a lightweight accepted/post-request message. -
Optimistic state reconciliation — Keep the current prototype behavior conservative:
ability_cast_requestedonly 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. -
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_rangecast 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.