NEO-131: Add kickoff implementation plan for quest reward HUD.

pull/170/head
VinPropane 2026-06-07 22:57:23 -04:00
parent a7579776a6
commit 5578de9de3
1 changed files with 171 additions and 0 deletions

View File

@ -0,0 +1,171 @@
# NEO-131 — Implementation plan
## Story reference
| Field | Value |
|--------|--------|
| **Key** | NEO-131 |
| **Title** | E7M2-08: Client quest completion reward HUD (Godot) |
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-131/e7m2-08-client-quest-completion-reward-hud-godot |
| **Module** | [E7.M2 — RewardAndUnlockRouter](../decomposition/modules/E7_M2_RewardAndUnlockRouter.md) · Epic 7 Slice 2 · backlog **E7M2-08** |
| **Branch** | `NEO-131-client-quest-completion-reward-hud` |
| **Server deps** | [NEO-129](https://linear.app/neon-sprawl/issue/NEO-129) **`Done`** on `main``completionRewardSummary` on completed quest-progress rows |
| **Pattern** | [NEO-110](NEO-110-implementation-plan.md) — separate completion grant label + item-def display names; [NEO-122](NEO-122-implementation-plan.md) — `quest_hud_controller.gd` + event-driven GET refresh |
| **Blocks** | [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) — playable quest reward delivery capstone (E7M2-09) |
| **Server counterpart** | [NEO-129](https://linear.app/neon-sprawl/issue/NEO-129) — authoritative GET; Bruno is **not** prototype-complete per [full-stack epic decomposition](../../.cursor/rules/full-stack-epic-decomposition.md) |
## Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|--------|----------|----------------------|--------|
| **HUD component** | Separate reward label vs inline on progress list? | **Separate `QuestRewardDeliveryLabel`** after **`QuestAcceptFeedbackLabel`** — NEO-110 **`EncounterCompleteLabel`** precedent; keeps four-quest progress compact. | **Adopted** — separate label |
| **Reward surfacing** | When to paint grant copy? | **Transition-only** — detect quest status → **`completed`** on refresh; matches Linear AC (“completing a quest shows…”); avoids replaying all summaries on boot/idempotency (NEO-123). | **Adopted** — transition-only |
| **Skill XP copy** | How to label skill lines without a skill-definitions client? | **Title-case `skillId` + amount** (e.g. **`Salvage +25 XP`**) — no skill-def client in repo; item lines use **`ItemDefinitionsClient`** like encounter loot. | **Adopted** (no kickoff question — settled by precedent) |
**Additional defaults (no kickoff question — settled by backlog / landed code):**
- **JSON shape:** nested **`completionRewardSummary`** with **`itemGrants: [{ itemId, quantity }]`** + **`skillXpGrants: [{ skillId, amount }]`** per NEO-129; empty **`itemGrants: []`** included when summary present.
- **Missing summary:** when **`status: completed`** but **`completionRewardSummary`** omitted (no delivery record), reward label stays **`—`** on transition — server omits field per NEO-129 kickoff.
- **Refresh strategy:** reuse NEO-122 triggers (boot hydrate + gather/craft/**`targetDefeated`**/encounter **`completed`**/accept POST) — no new poll or key.
- **Error surfacing:** failed GET continues **`sync error — {reason}`** on **`QuestProgressLabel`** (NEO-122); reward label shows **`—`** while sync error set (NEO-110 encounter loot precedent).
- **Server changes:** none — client-only story.
## Goal, scope, and out-of-scope
**Goal:** Godot shows **server-owned** quest completion reward grants using **`completionRewardSummary`** on **`GET /game/players/{id}/quest-progress`** — player-visible readable copy when a quest newly completes, without Bruno.
**In scope (from Linear + [E7M2-08](E7M2-prototype-backlog.md#e7m2-08--client-quest-completion-reward-hud-godot)):**
- Parse and retain **`completionRewardSummary`** on quest-progress rows (existing top-level parse passes through; add tests).
- **`QuestRewardDeliveryLabel`** under **`HudRoot`** after **`QuestAcceptFeedbackLabel`** — compact grant lines on in-session completion transition.
- Extend **`quest_hud_controller.gd`**: transition detection, reward label render, optional **`ItemDefinitionsClient`** for item display names.
- **`main.gd` / `main.tscn`:** wire reward label + item-defs client into controller **`setup`**.
- GdUnit parse + HUD render tests ([testing-expectations.md](../../.cursor/rules/testing-expectations.md)).
- **`docs/manual-qa/NEO-131.md`** — component checklist (gather intro skill XP; operator chain item + XP).
- **`client/README.md`** quest reward HUD subsection.
**Out of scope (from Linear + backlog):**
- Final quest journal UI; reward VFX.
- Skill-definitions client; server route/DTO changes.
- Full Slice 2 capstone script (**[NEO-132](https://linear.app/neon-sprawl/issue/NEO-132)**).
- Inventory/skills auto-refresh on quest complete (optional follow-up; NEO-132 capstone may assert economy HUD separately).
## Acceptance criteria checklist
- [ ] Completing a quest shows readable reward copy on HUD.
- [ ] Failed GET surfaces visible error (NEO-122 pattern).
## Technical approach
### Server contract (landed — NEO-129)
Per completed quest row when delivery recorded:
```json
{
"questId": "prototype_quest_gather_intro",
"status": "completed",
"completionRewardSummary": {
"itemGrants": [],
"skillXpGrants": [{ "skillId": "salvage", "amount": 25 }]
}
}
```
Operator chain adds item line: **`survey_drone_kit` ×1** + **`salvage` 50** XP.
### 1. Parse layer (`quest_progress_client.gd`)
- No parser change required — **`parse_quest_progress_json`** already accepts arbitrary quest row fields.
- Optional static helper **`completion_reward_summary(quest_id, snapshot) -> Dictionary`** returning **`{}`** when absent — keeps HUD code readable (mirror **`encounter_row`** usage).
- GdUnit: completed row JSON with nested summary preserves **`itemGrants`** / **`skillXpGrants`** through parse + **`quest_row`**.
### 2. Transition detection (`quest_hud_controller.gd`)
- Maintain **`_previous_status_by_quest: Dictionary`** (questId → last known status string).
- On **`_on_progress_received`**, before overwriting snapshot:
- For each quest row in new snapshot, compare **`status`** to previous.
- When previous ∉ **`{completed}`** and new **`status == "completed"`** with non-empty **`completionRewardSummary`**, set **`_last_reward_quest_id`** + **`_last_reward_summary`**.
- On boot first snapshot: seed **`_previous_status_by_quest`** without firing transition (no “replay” grants).
- On sync failure: clear **`_last_reward_summary`** paint to **`—`**; preserve NEO-122 progress error lines.
### 3. Reward label render
**Header:** **`Quest rewards:`** (idle **`Quest rewards:\n—`**).
When **`_last_reward_quest_id`** set and summary present:
```
Quest rewards:
Intro: Salvage Run
Salvage +25 XP
```
Item lines: **`{displayName} ×{quantity}`** via **`ItemDefinitionsClient.display_name_for`**, fallback **`itemId`**.
Skill lines: **`{_title_case_skill_id(skillId)} +{amount} XP`**.
When **`completed`** transition but summary omitted: show quest display name + **`(rewards pending)`** or **`—`** — prefer **`—`** only (no delivery record = no copy; keeps label honest).
### 4. Scene + wiring
- **`main.tscn`:** add **`QuestRewardDeliveryLabel`** after **`QuestAcceptFeedbackLabel`** (same font/theme as quest labels).
- **`main.gd`:** `@onready` reward label; extend **`_setup_quest_progress_sync()`** to pass reward label + **`_item_defs_client`** into controller **`setup`** (new optional params at end for backward-compatible test calls).
### 5. Display copy reference (freeze table)
| Quest id | Expected reward lines |
|----------|----------------------|
| `prototype_quest_gather_intro` | Salvage +25 XP |
| `prototype_quest_refine_intro` | Refine +25 XP |
| `prototype_quest_combat_intro` | Salvage +25 XP |
| `prototype_quest_operator_chain` | Survey Drone Kit ×1; Salvage +50 XP |
### 6. Relationship to adjacent HUD
| HUD element | Story | Role |
|-------------|-------|------|
| **`QuestProgressLabel`** | NEO-122 | Status/step/counters for all four quests |
| **`QuestRewardDeliveryLabel`** | NEO-131 | Grant copy on completion transition |
| **`EncounterCompleteLabel`** | NEO-110 | Encounter loot (separate from quest completion bundles) |
Combat intro completion is **skill XP only** in quest bundle — encounter token loot remains on encounter label (E7M2 freeze).
## Files to add
| Path | Purpose |
|------|---------|
| `docs/plans/NEO-131-implementation-plan.md` | This plan. |
| `docs/manual-qa/NEO-131.md` | Manual QA: gather complete → skill XP line; operator chain item + XP; sync error visible. |
## Files to modify
| Path | Rationale |
|------|-----------|
| `client/scripts/quest_hud_controller.gd` | Transition detection, reward label render, item-def display names, sync-error idle state. |
| `client/scripts/quest_progress_client.gd` | Optional **`completion_reward_summary`** helper; class doc cites NEO-131 field. |
| `client/scripts/main.gd` | Wire **`QuestRewardDeliveryLabel`** + **`ItemDefinitionsClient`** into quest HUD **`setup`**. |
| `client/scenes/main.tscn` | Add **`QuestRewardDeliveryLabel`** node after accept feedback label. |
| `client/README.md` | Quest completion reward HUD subsection; cross-link NEO-129/131. |
| `docs/plans/E7M2-prototype-backlog.md` | E7M2-08 checkboxes + landed note when complete. |
| `docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md` | Client implementation anchor (NEO-131). |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E7.M2 row: NEO-131 client HUD. |
## Tests
| Test file | What it covers |
|-----------|----------------|
| `client/test/quest_progress_client_test.gd` | **Add:** parse completed row with **`completionRewardSummary`** (empty **`itemGrants`**, skill XP row); helper returns summary dict. |
| `client/test/quest_hud_controller_test.gd` | **Add:** transition **`active``completed`** paints reward label with skill XP line; boot with already-**`completed`** does not paint; sync failure leaves progress error + reward **`—`**. |
**Regression:** existing NEO-122 GdUnit suites unchanged; `dotnet test` N/A (client-only).
## Open questions / risks
| Question / risk | Agent recommendation | Status |
|-----------------|----------------------|--------|
| **Multiple quests complete in one GET** | Paint **last catalog-order** transition in snapshot iteration — rare in prototype; document in manual QA. | **adopted** |
| **Inventory deny on complete (NEO-128)** | Quest may stay **`active`** when router denies — no **`completed`** transition, no reward label; acceptable for prototype. | **adopted** |
| **Skill display without defs client** | Title-case **`skillId`**; defer skill-def client to pre-production. | **adopted** |
| **NEO-132 capstone overlap** | NEO-131 proves component HUD; NEO-132 owns end-to-end reward idempotency manual QA. | **deferred** |