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

167 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# NEO-86 — Implementation plan
## Story reference
| Field | Value |
|--------|--------|
| **Key** | NEO-86 |
| **Title** | E5M1-12: Playable tab-target combat capstone (Godot) |
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-86/e5m1-12-playable-tab-target-combat-capstone-godot |
| **Module** | [E5.M1 — CombatRulesEngine](../decomposition/modules/E5_M1_CombatRulesEngine.md) · Epic 5 Slice 1 · backlog **E5M1-12** |
| **Branch** | `NEO-86-playable-combat-capstone` |
| **Server deps** | [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) cast + `combatResolution` (**Done**); [NEO-83](https://linear.app/neon-sprawl/issue/NEO-83) combat-targets GET (**Done**); [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) gig XP on defeat + GET (**Done**) |
| **Client deps** | [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) combat feedback + target HP HUD (**Done**); E1.M4 cast funnel (NEO-28/31/32) (**Done**) |
| **Pattern** | Capstone integration — [NEO-75](https://linear.app/neon-sprawl/issue/NEO-75) docs + manual QA primary; minimal client gap-fill for gig visibility |
| **Server counterpart** | NEO-44 gig progression GET; Bruno-only verification is not prototype-complete per [full-stack epic decomposition](../../.cursor/rules/full-stack-epic-decomposition.md) |
## Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|--------|----------|----------------------|--------|
| **Gig XP visibility** | Include gig XP in the Godot capstone? | **`GigProgressionClient` + `GigXpLabel`** in economy HUD; refresh after defeat cast — NEO-44 landed; NEO-75 capstone is Godot-only (no Bruno/curl). | **User:** Godot gig label + client. |
| **Implementation scope** | Docs-only vs client code? | **Docs + manual QA primary**; add gig client wiring; fix integration gaps only if capstone QA fails on `main`. | **User:** docs-first; code for gig row or QA failures. |
| **Session baseline** | Fresh server vs fixture POST only? | **Fresh server restart** before Godot **F5** — resets in-memory dummy HP + gig XP baseline (NEO-75 / NEO-44 manual QA precedent). | **Adopted** (kickoff default). |
No other blocking decisions — combat loop HUD, hotbar pulse bootstrap, and event-driven combat-target refresh are landed in NEO-85; field names follow NEO-82 / NEO-44 server vocabulary.
## Goal, scope, and out-of-scope
**Goal:** Prove Epic 5 Slice 1 acceptance **in Godot**: tab-target lock, cast abilities, readable deny reasons, defeat **`prototype_target_alpha`**, and visible gig XP after defeat — without Bruno.
**In scope (from Linear + [E5M1-12](E5M1-prototype-backlog.md#e5m1-12--playable-tab-target-combat-capstone-godot)):**
- **`docs/manual-qa/NEO-86.md`**: numbered **single-session** capstone script — fresh dev player, Tab lock alpha, digit cast **`prototype_pulse`** until defeat, verify combat HUD + **`breach`** gig XP — **no Bruno/curl** steps.
- **`client/README.md`**: **End-to-end combat loop** section — integration checklist (boot → Tab lock → cast → defeat → gig XP refresh chain); cross-links NEO-23/28/31/85.
- **`GigProgressionClient`** + **`GigXpLabel`** — mirror **`SkillProgressionClient`** (NEO-37); boot hydrate + refresh when cast accept carries **`targetDefeated: true`**.
- **Module alignment** (on story completion): update [`documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) E5.M1 row + E5M1-12 backlog checkboxes; note Epic 5 Slice 1 client capstone complete.
- **Integration fixes only if capstone QA fails** on current `main` wiring (unexpected deny, refresh gap, etc.).
**Out of scope (from Linear + backlog):**
- NPC AI (E5.M2), encounters/loot (E5.M3), group scaling.
- Final combat VFX, floating damage numbers, player HP bar.
- Full nine-gig catalog, hub swap, gig level curves.
- Bruno-only verification as prototype-complete proof.
## Acceptance criteria checklist
- [x] Human completes **`docs/manual-qa/NEO-86.md`** with server + client; **`prototype_target_alpha`** shows defeated state in HUD.
- [x] Epic 5 Slice 1 AC: combat readable in fixed isometric camera; no silent cast failures on deny.
- [x] **`breach`** gig XP visible in Godot after defeat (≥ **25** from fresh-server baseline).
- [x] Re-read [epic_05 Definition of Done](../decomposition/epics/epic_05_pve_combat.md#epic-5-definition-of-done) for prototype minimums.
## Implementation reconciliation (shipped)
- **Client:** `gig_progression_client.gd` GET snapshot + **`gig_row`**; **`GigXpLabel`** under economy HUD; defeat-triggered refresh in **`_on_cast_result_received`**.
- **Tests:** `gig_progression_client_test.gd`, `gig_feedback_refresh_test.gd` (5 cases pass).
- **Docs:** [`NEO-86` manual QA](../manual-qa/NEO-86.md); `client/README.md` end-to-end combat loop section; E5M1 backlog + module alignment rows updated.
- **Integration:** No capstone QA gaps on `main` beyond gig client wiring.
## Technical approach
### 1. Capstone manual QA script (`docs/manual-qa/NEO-86.md`)
**Preconditions:** Stop any running server; start fresh (`dotnet run`) so in-memory **`dev-local-1`** dummy HP and gig XP reset. Godot **F5** with **no** prior curl/Bruno seeding.
**Combat math** (from NEO-85 / NEO-82):
| Cast # | Expected cast feedback | Target HP label |
|--------|------------------------|-----------------|
| 1 | `Cast: 25 dmg → 75 HP` | `100/100``75/100` |
| 2 | `→ 50 HP` | `50/100` |
| 3 | `→ 25 HP` | `25/100` |
| 4 | `→ 0 HP — defeated!` | `0/100 (defeated)` |
| 5 | `ability_cast_denied: target_defeated` | unchanged defeated |
**Checklist outline:**
1. Start server + client; confirm **`Gig XP:`** row shows **`breach`** at **0** xp (boot hydrate).
2. Walk to orange **Dummy α** near spawn; **Tab** lock **`prototype_target_alpha`** — dummy brightens; **`CombatTargetHpLabel`** shows **`100/100`**.
3. Press **1** four times (respect ~3s pulse cooldown); verify **`CastFeedbackLabel`** + HP label step as above.
4. Verify **`GigXpLabel`** shows **`breach`** **`xp` 25** after lethal hit (event-driven GET refresh).
5. Press **1** again — deny feedback; gig XP unchanged.
6. **Esc** clear lock — HP label **`— (no lock)`**; combat feedback labels remain readable.
7. Regression spot-check: cast without lock → **`invalid_target`** deny on **`CastFeedbackLabel`**.
Document optional **`POST /game/__dev/combat-targets-fixture`** for mid-session dummy-only reset (NEO-85) — capstone baseline still prefers **server restart**.
### 2. Gig progression client (`gig_progression_client.gd`)
Mirror **`skill_progression_client.gd`**:
- **`GET {base}/game/players/{id}/gig-progression`** — injectable HTTP, **`_busy`** guard.
- **`parse_progression_json(text)`** static — require **`schemaVersion` 1**, **`mainGigId`**, **`gigs`** array.
- Signals: **`progression_received(snapshot: Dictionary)`**, **`progression_sync_failed(reason: String)`**.
- **`gig_row(gig_id: String, snapshot: Dictionary = {}) -> Dictionary`** — lookup by **`id`**; uses cached snapshot when second arg omitted.
### 3. Gig XP HUD (`main.gd` + `main.tscn`)
- Add **`GigProgressionClient`** node (scene or runtime like **`CombatTargetsClient`**).
- Add **`GigXpLabel`** under **`UICanvas/EconomyHudSection/Body/`** below **`SkillProgressionLabel`**, above **`CraftRecipePanel`** — respects NEO-75 collapse toggle.
- **`_render_gig_xp_label()`** — format **`Gig XP:\nbreach: L1 · 25 xp`** (prototype fixed level **1** from server); **`Loading…`** / error lines match skill label pattern.
- **Refresh triggers (event-driven):**
- Boot: **`request_sync_from_server()`** in setup (parallel to skill progression).
- **`cast_result_received`** when **`accepted`** and **`resolution.targetDefeated === true`** → gig progression sync (NEO-44 grant is server-side on that accept; GET reconciles HUD).
- Do **not** refresh gig XP on every cast accept — only boot + defeat transition (minimize HTTP chatter).
### 4. README integration checklist
New **`## End-to-end combat loop (NEO-86)`** section after NEO-85 combat HUD subsection:
- Flow in prose: boot hotbar sync (auto pulse bind) → **Tab** lock dummy → digit cast → cast feedback + HP label refresh → defeat → gig XP refresh.
- Cross-links: NEO-23 (targeting), NEO-28/31 (cast), NEO-85 (combat HUD), NEO-44 (server gig grant).
- Pointer to **`docs/manual-qa/NEO-86.md`** as authoritative capstone script.
- Note **`Economy HUD`** toggle hides gig row with other economy labels.
### 5. Module alignment (implementation batch or story end)
When acceptance criteria pass:
- **`documentation_and_implementation_alignment.md`**: E5.M1 — note **NEO-86 landed**, Epic 5 Slice 1 client capstone complete.
- **`E5M1-prototype-backlog.md`**: E5M1-12 checkboxes + landed note.
- **`E5_M1_CombatRulesEngine.md`**: E5M1-12 row if not already marked.
### 6. Integration fixes (conditional)
Run capstone QA on **`main`** wiring before gig client work; if any step fails, fix minimal **`main.gd`** / client bug in this branch and note in plan **Decisions**. Expected path: gig client + docs only.
## Files to add
| Path | Purpose |
|------|---------|
| `docs/plans/NEO-86-implementation-plan.md` | This plan. |
| `docs/manual-qa/NEO-86.md` | Capstone single-session manual QA (zero Bruno/curl). |
| `client/scripts/gig_progression_client.gd` | GET gig-progression snapshot; parse v1; **`gig_row`** helper. |
| `client/scripts/gig_progression_client.gd.uid` | Godot uid companion (tracked). |
| `client/test/gig_progression_client_test.gd` | GdUnit: parse snapshot; **`gig_row("breach")`**; HTTP error path. AAA layout. |
| `client/test/gig_feedback_refresh_test.gd` | GdUnit: cast accept with **`targetDefeated`** triggers gig sync (spy harness). AAA layout. |
## Files to modify
| Path | Rationale |
|------|-----------|
| `client/scripts/main.gd` | Wire **`GigProgressionClient`**; **`_render_gig_xp_label`**; defeat-triggered gig refresh in **`_on_cast_result_received`**. |
| `client/scenes/main.tscn` | Add **`GigProgressionClient`** node + **`GigXpLabel`** under economy HUD body. |
| `client/README.md` | End-to-end combat loop section; gig XP row docs; capstone manual QA link. |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E5.M1 row — NEO-86 landed + Slice 1 client capstone complete (on story completion). |
| `docs/plans/E5M1-prototype-backlog.md` | E5M1-12 acceptance checkboxes + landed note (on story completion). |
## Tests
| Path | Change |
|------|--------|
| `client/test/gig_progression_client_test.gd` | **Add:** GET parse v1 with **`breach`** row; invalid schema → failure signal; 404 path. |
| `client/test/gig_feedback_refresh_test.gd` | **Add:** cast accept with **`targetDefeated: true`** triggers sync; non-defeat accept and deny do not. |
**Manual verification:** [`docs/manual-qa/NEO-86.md`](manual-qa/NEO-86.md) — server + Godot; full defeat spine + gig XP visibility without curl.
## Open questions / risks
| Question or risk | Agent recommendation | Status |
|------------------|----------------------|--------|
| **Gig refresh on every accept** | Refresh only on **`targetDefeated`** + boot — avoids redundant GETs on non-lethal hits. | **adopted** |
| **Gig label inside economy collapse** | Place under **`EconomyHudSection/Body`** — hidden when economy HUD collapsed (NEO-75); combat labels stay visible. | **adopted** |
| **Overlap with NEO-85 manual QA** | NEO-86 supersedes as capstone script; keep NEO-85 as component-level regression reference. | **adopted** |
| **Postgres dev DB retains gig XP** | Capstone preconditions require **server restart**; document that shared Postgres may show prior totals. | **adopted** |
| **Integration gap on `main`** | Run capstone dry-run first; fix only if QA fails. | **adopted** — no logic gaps beyond gig client |