From 5578de9de393dcabbf9cf332e407694640e99b01 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 7 Jun 2026 22:57:23 -0400 Subject: [PATCH] NEO-131: Add kickoff implementation plan for quest reward HUD. --- docs/plans/NEO-131-implementation-plan.md | 171 ++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/plans/NEO-131-implementation-plan.md diff --git a/docs/plans/NEO-131-implementation-plan.md b/docs/plans/NEO-131-implementation-plan.md new file mode 100644 index 0000000..56a18f8 --- /dev/null +++ b/docs/plans/NEO-131-implementation-plan.md @@ -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** |