NEO-132: Ship Slice 2 quest reward capstone docs and economy refresh.
Add NEO-132 manual QA extending NEO-123 with reward HUD and economy verification, README capstone section, E7.M2 Ready alignment, client completion-transition signal for inventory/skill refresh, and GdUnit coverage for the new signal.pull/171/head
parent
a1947d8f05
commit
63156c353f
|
|
@ -246,11 +246,39 @@ Full checklist: [`docs/manual-qa/NEO-122.md`](../docs/manual-qa/NEO-122.md).
|
||||||
- **HUD:**
|
- **HUD:**
|
||||||
- **`UICanvas/HudRootScroll/HudRoot/QuestRewardDeliveryLabel`** — **`Quest rewards: —`** until a quest newly becomes **`completed`** in-session; then quest display name + grant lines.
|
- **`UICanvas/HudRootScroll/HudRoot/QuestRewardDeliveryLabel`** — **`Quest rewards: —`** until a quest newly becomes **`completed`** in-session; then quest display name + grant lines.
|
||||||
- Item lines use **`ItemDefinitionsClient.display_name_for`** (NEO-110 loot precedent); skill XP lines use title-case **`skillId`** + amount (e.g. **`Salvage +25 XP`**).
|
- Item lines use **`ItemDefinitionsClient.display_name_for`** (NEO-110 loot precedent); skill XP lines use title-case **`skillId`** + amount (e.g. **`Salvage +25 XP`**).
|
||||||
- **Refresh:** reuses NEO-122 triggers (boot hydrate + gather/craft/defeat/accept). **Transition-only** — boot with already-completed quests does not replay grant copy (NEO-123 idempotency).
|
- **Refresh:** reuses NEO-122 triggers (boot hydrate + gather/craft/defeat/accept). **Transition-only** — boot with already-completed quests does not replay grant copy (NEO-123 idempotency). **Economy HUD** (inventory + skills) refreshes on completion transition via NEO-132 signal from `quest_hud_controller.gd`.
|
||||||
- **Errors:** failed quest-progress GET shows **`sync error — …`** on **`QuestProgressLabel`**; reward label **`—`** (NEO-122 pattern).
|
- **Errors:** failed quest-progress GET shows **`sync error — …`** on **`QuestProgressLabel`**; reward label **`—`** (NEO-122 pattern).
|
||||||
|
|
||||||
Full checklist: [`docs/manual-qa/NEO-131.md`](../docs/manual-qa/NEO-131.md).
|
Full checklist: [`docs/manual-qa/NEO-131.md`](../docs/manual-qa/NEO-131.md).
|
||||||
|
|
||||||
|
## End-to-end quest reward loop (NEO-132)
|
||||||
|
|
||||||
|
Epic 7 Slice 2 capstone — complete all four prototype quests **in Godot** and verify quest completion bundles (skill XP + item grants) apply **once** with idempotent replay.
|
||||||
|
|
||||||
|
**Flow:** fresh server restart → NEO-123 four-quest path → per-completion **`QuestRewardDeliveryLabel`** + economy HUD verification → Godot restart idempotency.
|
||||||
|
|
||||||
|
| Step | Input / trigger | HUD / server outcome |
|
||||||
|
|------|-----------------|----------------------|
|
||||||
|
| Boot | Godot **F5** + fresh server | Four quests **`not started`**; reward label **`—`**; skill xp **0** |
|
||||||
|
| Gather intro | **Q** + gather ×3 scrap | Salvage Run **`completed`**; **`Salvage +25 XP`** reward line; salvage xp **+25** |
|
||||||
|
| Combat intro | **Shift+Q** + defeat ×3 NPCs | Clear the Pocket **`completed`**; encounter loot on **`EncounterCompleteLabel`**; **`Salvage +25 XP`** reward line; salvage xp **+25** |
|
||||||
|
| Refine intro | **Shift+Q** + craft **`refine_scrap_standard`** | Refine Stock **`completed`**; **`Refine +25 XP`** reward line; refine xp **+25** |
|
||||||
|
| Operator chain | **Shift+Q** + chain objectives | Operator Chain **`completed`**; **`Survey Drone Kit ×1`** + **`Salvage +50 XP`**; bag contains kit |
|
||||||
|
| Idempotency | Stop Godot + **F5** (server still running) | Quests still **`completed`**; reward label **`—`**; economy counts unchanged |
|
||||||
|
| Duplicate accept | **Q** / **Shift+Q** on finished quests | Deny; no duplicate skill XP or items |
|
||||||
|
|
||||||
|
**Reward vs encounter loot:** quest completion bundles (`completionRewardBundle`) surface on **`QuestRewardDeliveryLabel`**; encounter pocket loot remains on **`EncounterCompleteLabel`** (E7M2 freeze — encounter loot unchanged).
|
||||||
|
|
||||||
|
**Economy refresh:** on in-session quest completion transition, **`InventoryLabel`** + **`SkillProgressionLabel`** auto-refresh (NEO-132); reward label paint remains transition-only (NEO-131).
|
||||||
|
|
||||||
|
**Cross-links:** [NEO-124](../docs/plans/NEO-124-implementation-plan.md) catalog freeze · [NEO-126](../docs/plans/NEO-126-implementation-plan.md) delivery store · [NEO-127](../docs/plans/NEO-127-implementation-plan.md) router · [NEO-129](../docs/plans/NEO-129-implementation-plan.md) HTTP summary · [NEO-131](../docs/manual-qa/NEO-131.md) reward HUD · [NEO-123](../docs/manual-qa/NEO-123.md) onboarding flow.
|
||||||
|
|
||||||
|
**Scripts:** `quest_hud_controller.gd` (completion transition signal → economy refresh), `quest_progress_client.gd`, `inventory_client.gd`, `skill_progression_client.gd` — wired from `main.gd`.
|
||||||
|
|
||||||
|
**Preconditions:** **Server restart** before capstone run resets quest progress, reward delivery store, inventory, and skill progression.
|
||||||
|
|
||||||
|
Full capstone checklist: [`docs/manual-qa/NEO-132.md`](../docs/manual-qa/NEO-132.md).
|
||||||
|
|
||||||
## End-to-end onboarding quest loop (NEO-123)
|
## End-to-end onboarding quest loop (NEO-123)
|
||||||
|
|
||||||
Epic 7 Slice 1 capstone — complete all four prototype quests (three onboarding intros + operator chain) **in Godot** without Bruno.
|
Epic 7 Slice 1 capstone — complete all four prototype quests (three onboarding intros + operator chain) **in Godot** without Bruno.
|
||||||
|
|
|
||||||
|
|
@ -867,6 +867,20 @@ func _setup_quest_progress_sync() -> void:
|
||||||
_quest_reward_delivery_label,
|
_quest_reward_delivery_label,
|
||||||
_item_defs_client
|
_item_defs_client
|
||||||
)
|
)
|
||||||
|
if _quest_hud.has_signal("quest_completion_reward_transition"):
|
||||||
|
_quest_hud.connect(
|
||||||
|
"quest_completion_reward_transition", Callable(self, "_on_quest_completion_reward_transition")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quest_completion_reward_transition(_quest_id: String, _summary: Dictionary) -> void:
|
||||||
|
# NEO-132: refresh economy HUD after server applies quest completion bundle.
|
||||||
|
_request_inventory_refresh()
|
||||||
|
if (
|
||||||
|
is_instance_valid(_skill_progression_client)
|
||||||
|
and _skill_progression_client.has_method("request_sync_from_server")
|
||||||
|
):
|
||||||
|
_skill_progression_client.call("request_sync_from_server")
|
||||||
|
|
||||||
|
|
||||||
func _request_quest_progress_refresh() -> void:
|
func _request_quest_progress_refresh() -> void:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ extends Node
|
||||||
|
|
||||||
## NEO-122: quest progress + accept HUD wiring (extracted from main.gd).
|
## NEO-122: quest progress + accept HUD wiring (extracted from main.gd).
|
||||||
## NEO-131: quest completion reward label on in-session completed transition.
|
## NEO-131: quest completion reward label on in-session completed transition.
|
||||||
|
## NEO-132: emits quest_completion_reward_transition for economy HUD refresh.
|
||||||
|
|
||||||
|
signal quest_completion_reward_transition(quest_id: String, summary: Dictionary)
|
||||||
|
|
||||||
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
||||||
const GATHER_QUEST_ID := "prototype_quest_gather_intro"
|
const GATHER_QUEST_ID := "prototype_quest_gather_intro"
|
||||||
|
|
@ -205,6 +208,8 @@ func _detect_and_apply_completion_transitions(snapshot: Dictionary) -> void:
|
||||||
_last_reward_summary = (summary_variant as Dictionary).duplicate(true)
|
_last_reward_summary = (summary_variant as Dictionary).duplicate(true)
|
||||||
else:
|
else:
|
||||||
_last_reward_summary = {}
|
_last_reward_summary = {}
|
||||||
|
if not _last_reward_quest_id.is_empty() and not _last_reward_summary.is_empty():
|
||||||
|
quest_completion_reward_transition.emit(_last_reward_quest_id, _last_reward_summary)
|
||||||
|
|
||||||
|
|
||||||
func _quest_ids_in_render_order(snapshot: Dictionary) -> Array:
|
func _quest_ids_in_render_order(snapshot: Dictionary) -> Array:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
extends GdUnitTestSuite
|
extends GdUnitTestSuite
|
||||||
|
|
||||||
## NEO-131: quest completion reward label HUD tests.
|
## NEO-131: quest completion reward label HUD tests.
|
||||||
|
## NEO-132: quest_completion_reward_transition signal for economy refresh.
|
||||||
|
|
||||||
const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
||||||
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
||||||
|
|
@ -272,3 +273,28 @@ func test_sync_failure_shows_progress_error_and_idle_reward_label() -> void:
|
||||||
# Assert
|
# Assert
|
||||||
assert_str(progress_label.text).contains("error — HTTP 503")
|
assert_str(progress_label.text).contains("error — HTTP 503")
|
||||||
assert_str(reward_label.text).is_equal("Quest rewards:\n—")
|
assert_str(reward_label.text).is_equal("Quest rewards:\n—")
|
||||||
|
|
||||||
|
|
||||||
|
func test_completion_transition_emits_economy_refresh_signal() -> void:
|
||||||
|
# Arrange
|
||||||
|
var controller: Node = QuestHudController.new()
|
||||||
|
auto_free(controller)
|
||||||
|
add_child(controller)
|
||||||
|
monitor_signals(controller)
|
||||||
|
# Act
|
||||||
|
controller.call("_on_progress_received", _active_gather_snapshot())
|
||||||
|
controller.call("_on_progress_received", _completed_gather_with_summary_snapshot())
|
||||||
|
# Assert
|
||||||
|
await assert_signal(controller).is_emitted("quest_completion_reward_transition", any())
|
||||||
|
|
||||||
|
|
||||||
|
func test_boot_completed_does_not_emit_economy_refresh_signal() -> void:
|
||||||
|
# Arrange
|
||||||
|
var controller: Node = QuestHudController.new()
|
||||||
|
auto_free(controller)
|
||||||
|
add_child(controller)
|
||||||
|
monitor_signals(controller)
|
||||||
|
# Act
|
||||||
|
controller.call("_on_progress_received", _completed_gather_with_summary_snapshot())
|
||||||
|
# Assert
|
||||||
|
await assert_signal(controller).wait_until(200).is_not_emitted("quest_completion_reward_transition")
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
| **Module ID** | E7.M2 |
|
| **Module ID** | E7.M2 |
|
||||||
| **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) |
|
| **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) |
|
||||||
| **Stage target** | Prototype |
|
| **Stage target** | Prototype |
|
||||||
| **Status** | Planned — Slice 2 backlog [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md): **E7M2-01** [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132). **NEO-43 prep landed:** `MissionRewardSkillXpGrant` for bundle skill XP (`mission_reward`). Upstream: E7.M1 **Ready**, E2.M2 grant stack, E3.M3 inventory. |
|
| **Status** | Ready — Slice 2 backlog [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md): **E7M2-01** [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) **landed**. **NEO-43 prep landed:** `MissionRewardSkillXpGrant` for bundle skill XP (`mission_reward`). Upstream: E7.M1 **Ready**, E2.M2 grant stack, E3.M3 inventory. |
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
|
|
@ -79,3 +79,5 @@ Epic 7 **Slice 2** — `reward_delivery`, `unlock_granted`; no double-claim on r
|
||||||
**NEO-130 (E7M2-07 telemetry hooks):** comment-only **`reward_delivery`** + **`unlock_granted`** stub hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`** — first-time **`TryRecord`** success and future **`UnlockGrant`** apply region respectively; no production ingest. See [NEO-130 implementation plan](../../plans/NEO-130-implementation-plan.md); [server README — Reward telemetry hooks (NEO-130)](../../../server/README.md#reward-telemetry-hooks-neo-130).
|
**NEO-130 (E7M2-07 telemetry hooks):** comment-only **`reward_delivery`** + **`unlock_granted`** stub hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`** — first-time **`TryRecord`** success and future **`UnlockGrant`** apply region respectively; no production ingest. See [NEO-130 implementation plan](../../plans/NEO-130-implementation-plan.md); [server README — Reward telemetry hooks (NEO-130)](../../../server/README.md#reward-telemetry-hooks-neo-130).
|
||||||
|
|
||||||
**NEO-131 (E7M2-08 client HUD):** Godot **`QuestRewardDeliveryLabel`** + **`quest_hud_controller.gd`** transition detection paints **`completionRewardSummary`** grant lines when a quest newly becomes **`completed`** in-session (parse via **`quest_progress_client.gd`**). See [NEO-131 implementation plan](../../plans/NEO-131-implementation-plan.md); [client README — Quest completion reward HUD (NEO-131)](../../../client/README.md#quest-completion-reward-hud-neo-131).
|
**NEO-131 (E7M2-08 client HUD):** Godot **`QuestRewardDeliveryLabel`** + **`quest_hud_controller.gd`** transition detection paints **`completionRewardSummary`** grant lines when a quest newly becomes **`completed`** in-session (parse via **`quest_progress_client.gd`**). See [NEO-131 implementation plan](../../plans/NEO-131-implementation-plan.md); [client README — Quest completion reward HUD (NEO-131)](../../../client/README.md#quest-completion-reward-hud-neo-131).
|
||||||
|
|
||||||
|
**NEO-132 (E7M2-09 client capstone):** playable four-quest reward delivery loop — [`NEO-132` manual QA](../../manual-qa/NEO-132.md); [client README — End-to-end quest reward loop (NEO-132)](../../../client/README.md#end-to-end-quest-reward-loop-neo-132); economy HUD refresh on completion transition via **`quest_completion_reward_transition`** signal. Epic 7 Slice 2 client capstone complete.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -110,9 +110,9 @@ Gameplay **content and curves** default to **boot load** with optional **dev-onl
|
||||||
| E7.M1 | QuestStateMachine | E3.M2, E5.M1 | QuestDef, QuestStepState, QuestStateTransition | Prototype | Ready |
|
| E7.M1 | QuestStateMachine | E3.M2, E5.M1 | QuestDef, QuestStepState, QuestStateTransition | Prototype | Ready |
|
||||||
|
|
||||||
**E7.M1 note:** Epic 7 **Slice 1** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) → [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) **landed**; label **`E7.M1`**. See [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md), [E7_M1_QuestStateMachine.md](E7_M1_QuestStateMachine.md). Upstream: E3.M1 gather **Ready**, E3.M2 craft **Ready**, E5.M3 encounter **Ready**. **E7M1-12 / NEO-123** client capstone landed — [`NEO-123` manual QA](../../manual-qa/NEO-123.md), [client README — End-to-end onboarding quest loop (NEO-123)](../../../client/README.md#end-to-end-onboarding-quest-loop-neo-123), plan [NEO-123](../../plans/NEO-123-implementation-plan.md). Epic 7 Slice 1 client capstone complete.
|
**E7.M1 note:** Epic 7 **Slice 1** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) → [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) **landed**; label **`E7.M1`**. See [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md), [E7_M1_QuestStateMachine.md](E7_M1_QuestStateMachine.md). Upstream: E3.M1 gather **Ready**, E3.M2 craft **Ready**, E5.M3 encounter **Ready**. **E7M1-12 / NEO-123** client capstone landed — [`NEO-123` manual QA](../../manual-qa/NEO-123.md), [client README — End-to-end onboarding quest loop (NEO-123)](../../../client/README.md#end-to-end-onboarding-quest-loop-neo-123), plan [NEO-123](../../plans/NEO-123-implementation-plan.md). Epic 7 Slice 1 client capstone complete.
|
||||||
| E7.M2 | RewardAndUnlockRouter | E2.M2, E3.M3, E7.M1 | QuestRewardBundle, UnlockGrant, RewardDeliveryEvent | Prototype | Planned |
|
| E7.M2 | RewardAndUnlockRouter | E2.M2, E3.M3, E7.M1 | QuestRewardBundle, UnlockGrant, RewardDeliveryEvent | Prototype | Ready |
|
||||||
|
|
||||||
**E7.M2 note:** Epic 7 **Slice 2** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132); label **`E7.M2`**. See [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md), [E7_M2_RewardAndUnlockRouter.md](E7_M2_RewardAndUnlockRouter.md). Upstream: E7.M1 **Ready**, E2.M2 grant stack ([NEO-43](https://linear.app/neon-sprawl/issue/NEO-43)), E3.M3 inventory **Ready**. Client capstone **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132). Register row stays **Planned** until capstone lands.
|
**E7.M2 note:** Epic 7 **Slice 2** backlog in Linear ([Epic 7 — Questing, Narrative, and Faction Reputation](https://linear.app/neon-sprawl/project/epic-7-questing-narrative-and-faction-reputation-a9416783ceee)): [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) **landed**; label **`E7.M2`**. See [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md), [E7_M2_RewardAndUnlockRouter.md](E7_M2_RewardAndUnlockRouter.md). Upstream: E7.M1 **Ready**, E2.M2 grant stack ([NEO-43](https://linear.app/neon-sprawl/issue/NEO-43)), E3.M3 inventory **Ready**. **E7M2-09 / NEO-132** client capstone landed — [`NEO-132` manual QA](../../manual-qa/NEO-132.md), [client README — End-to-end quest reward loop (NEO-132)](../../../client/README.md#end-to-end-quest-reward-loop-neo-132), plan [NEO-132](../../plans/NEO-132-implementation-plan.md). Epic 7 Slice 2 client capstone complete.
|
||||||
| E7.M3 | FactionReputationLedger | E7.M1 | FactionStanding, ReputationDelta, FactionGateRule | Pre-production | Planned |
|
| E7.M3 | FactionReputationLedger | E7.M1 | FactionStanding, ReputationDelta, FactionGateRule | Pre-production | Planned |
|
||||||
| E7.M4 | ContractMissionGenerator | E4.M1, E5.M3, E7.M3 | ContractTemplate, ContractSeed, ContractOutcome | Pre-production | Planned |
|
| E7.M4 | ContractMissionGenerator | E4.M1, E5.M3, E7.M3 | ContractTemplate, ContractSeed, ContractOutcome | Pre-production | Planned |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ Adjust gathers/crafts if bag counts differ; counters and craft denies are author
|
||||||
- Chain terminal step is **`inventory_has_item`** — no token consume in Slice 1.
|
- Chain terminal step is **`inventory_has_item`** — no token consume in Slice 1.
|
||||||
- **`POST /game/__dev/combat-targets-fixture`** is **out of scope** — clears encounter progress unsuitable for idempotency ([server README — dev combat-target fixture](../../server/README.md#dev-combat-target-fixture-brunomanual-qa)).
|
- **`POST /game/__dev/combat-targets-fixture`** is **out of scope** — clears encounter progress unsuitable for idempotency ([server README — dev combat-target fixture](../../server/README.md#dev-combat-target-fixture-brunomanual-qa)).
|
||||||
- Epic 7 Slice 1 AC: 3 onboarding + 1 chain complete without duped completion ([epic_07 Definition of Done](../decomposition/epics/epic_07_quest_faction.md#epic-7-definition-of-done)).
|
- Epic 7 Slice 1 AC: 3 onboarding + 1 chain complete without duped completion ([epic_07 Definition of Done](../decomposition/epics/epic_07_quest_faction.md#epic-7-definition-of-done)).
|
||||||
|
- **Slice 2 reward verification:** quest completion bundles + economy idempotency — [NEO-132 manual QA](NEO-132.md) (supersedes for reward delivery AC).
|
||||||
|
|
||||||
## Acceptance
|
## Acceptance
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,5 @@
|
||||||
|
|
||||||
- [NEO-122](NEO-122.md) — quest progress + accept HUD unchanged except new label below accept feedback.
|
- [NEO-122](NEO-122.md) — quest progress + accept HUD unchanged except new label below accept feedback.
|
||||||
- [NEO-110](NEO-110.md) — encounter loot label still independent from quest completion bundles.
|
- [NEO-110](NEO-110.md) — encounter loot label still independent from quest completion bundles.
|
||||||
|
|
||||||
|
**Full-flow capstone:** [NEO-132](NEO-132.md) extends this checklist with economy HUD verification across all four quest completion bundles.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
# NEO-132 — Manual QA checklist
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Key | NEO-132 |
|
||||||
|
| Title | E7M2-09: Playable quest reward delivery capstone (Godot) |
|
||||||
|
| Linear | https://linear.app/neon-sprawl/issue/NEO-132/e7m2-09-playable-quest-reward-delivery-capstone-godot |
|
||||||
|
| Plan | `docs/plans/NEO-132-implementation-plan.md` |
|
||||||
|
| Branch | `NEO-132-playable-quest-reward-delivery-capstone` |
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
|
||||||
|
- **Fresh dev player:** stop any running server, then start a new instance so in-memory quest progress, **`IRewardDeliveryStore`**, inventory, skill progression, encounter progress, NPC HP, and resource-node depletion reset.
|
||||||
|
- **No Bruno/curl** for this checklist — accept uses **Q** / **Shift+Q**; gameplay uses **R**, **Tab** + **1**, and Economy HUD **Craft** buttons only.
|
||||||
|
- NEO-124–NEO-131 reward spine and NEO-123 onboarding capstone landed on `main`.
|
||||||
|
- **`Economy HUD`** toggle **on** for inventory + skill verification.
|
||||||
|
|
||||||
|
## Quest completion reward freeze (E7M2-01)
|
||||||
|
|
||||||
|
| Quest id | Display name | Bundle grants | Expected **`QuestRewardDeliveryLabel`** |
|
||||||
|
|----------|--------------|---------------|----------------------------------------|
|
||||||
|
| `prototype_quest_gather_intro` | Intro: Salvage Run | `salvage` **+25** XP | `Salvage +25 XP` |
|
||||||
|
| `prototype_quest_combat_intro` | Intro: Clear the Pocket | `salvage` **+25** XP | `Salvage +25 XP` |
|
||||||
|
| `prototype_quest_refine_intro` | Intro: Refine Stock | `refine` **+25** XP | `Refine +25 XP` |
|
||||||
|
| `prototype_quest_operator_chain` | Operator Chain | `survey_drone_kit` ×1 + `salvage` **+50** XP | `Survey Drone Kit ×1` + `Salvage +50 XP` |
|
||||||
|
|
||||||
|
**Encounter loot (unchanged — E5.M3):** combat pocket still grants **`scrap_metal_bulk` ×10** + **`contract_handoff_token` ×1** on **`EncounterCompleteLabel`**, not quest bundles.
|
||||||
|
|
||||||
|
## Skill XP verification method
|
||||||
|
|
||||||
|
Record **`SkillProgressionLabel`** salvage/refine **`xp`** values **immediately before** each quest completion transition and **after** the HUD refreshes. Assert the **delta** matches the freeze table (+25 or +50 for salvage; +25 for refine). Gather/craft activity XP may also change totals — deltas per quest completion are authoritative.
|
||||||
|
|
||||||
|
## Expected HUD progression (extends NEO-123)
|
||||||
|
|
||||||
|
| Phase | `QuestProgressLabel` | `QuestRewardDeliveryLabel` | Economy HUD |
|
||||||
|
|-------|----------------------|----------------------------|-------------|
|
||||||
|
| Boot | All four **`not started`** | `Quest rewards:` / `—` | salvage/refine xp **0** (fresh server) |
|
||||||
|
| Gather intro **`completed`** | Salvage Run **`completed`** | `Intro: Salvage Run` + `Salvage +25 XP` | salvage xp **+25** delta |
|
||||||
|
| Combat intro **`completed`** | Clear the Pocket **`completed`** | `Intro: Clear the Pocket` + `Salvage +25 XP` | salvage xp **+25** delta |
|
||||||
|
| Refine intro **`completed`** | Refine Stock **`completed`** | `Intro: Refine Stock` + `Refine +25 XP` | refine xp **+25** delta |
|
||||||
|
| Operator chain **`completed`** | Operator Chain **`completed`** | `Survey Drone Kit ×1` + `Salvage +50 XP` | bag has **`survey_drone_kit`**; salvage xp **+50** delta |
|
||||||
|
| Godot restart | All four still **`completed`** | `—` (no transition replay) | counts **unchanged** |
|
||||||
|
| Duplicate accepts | Unchanged | Unchanged | counts **unchanged** |
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
Follow [NEO-123](NEO-123.md) for accept order, anchors, material math, and quest steps **1–15**. **Extend** each completion with reward + economy checks below.
|
||||||
|
|
||||||
|
### Boot
|
||||||
|
|
||||||
|
1. Start server: `cd server/NeonSprawl.Server && dotnet run`.
|
||||||
|
2. Run Godot main scene (**F5**). Confirm four quests **`not started`**, **`QuestRewardDeliveryLabel`** shows **`Quest rewards:`** / **`—`**, and **`SkillProgressionLabel`** shows salvage/refine xp **0** (fresh server).
|
||||||
|
|
||||||
|
### Intro: Salvage Run (gather + skill XP reward)
|
||||||
|
|
||||||
|
3. Press **Q**; gather at **gamma** **(12, 0)** once (+3 scrap) until **`Intro: Salvage Run: completed`**.
|
||||||
|
4. Verify **`QuestRewardDeliveryLabel`**: `Intro: Salvage Run` + **`Salvage +25 XP`**.
|
||||||
|
5. Verify **`SkillProgressionLabel`**: salvage xp increased by **25** vs value recorded before completion (activity gather XP may also apply — quest bundle delta is **+25**).
|
||||||
|
|
||||||
|
### Intro: Clear the Pocket (combat + skill XP reward)
|
||||||
|
|
||||||
|
6. Press **Shift+Q**; defeat all three prototype NPCs (**Tab** + **1**, any order).
|
||||||
|
7. Verify **`EncounterCompleteLabel`** still lists encounter loot (**`scrap_metal_bulk ×10`**, **`contract_handoff_token ×1`**) — separate from quest bundle.
|
||||||
|
8. Verify **`Intro: Clear the Pocket: completed`** on **`QuestProgressLabel`**.
|
||||||
|
9. Verify **`QuestRewardDeliveryLabel`**: `Intro: Clear the Pocket` + **`Salvage +25 XP`**.
|
||||||
|
10. Verify **`SkillProgressionLabel`**: salvage xp **+25** delta since step 5.
|
||||||
|
|
||||||
|
### Intro: Refine Stock (craft + skill XP reward)
|
||||||
|
|
||||||
|
11. Press **Shift+Q**; craft **`refine_scrap_standard`** when **≥5** scrap in bag.
|
||||||
|
12. Verify **`Intro: Refine Stock: completed`**.
|
||||||
|
13. Verify **`QuestRewardDeliveryLabel`**: `Intro: Refine Stock` + **`Refine +25 XP`**.
|
||||||
|
14. Verify **`SkillProgressionLabel`**: refine xp **+25** delta since step 10.
|
||||||
|
|
||||||
|
### Operator Chain (item + skill XP reward)
|
||||||
|
|
||||||
|
15. Press **Shift+Q**; complete chain steps 1–4 per [NEO-123](NEO-123.md) (gather ×5 → refine → stim → token held).
|
||||||
|
16. Verify **`Operator Chain: completed`**.
|
||||||
|
17. Verify **`QuestRewardDeliveryLabel`**: **`Survey Drone Kit ×1`** (or raw id fallback) + **`Salvage +50 XP`**.
|
||||||
|
18. Verify **`InventoryLabel`** lists **`survey_drone_kit`** (display name when item defs loaded).
|
||||||
|
19. Verify **`SkillProgressionLabel`**: salvage xp **+50** delta since step 14.
|
||||||
|
|
||||||
|
### Capstone snapshot (record for idempotency)
|
||||||
|
|
||||||
|
20. **Record** final state:
|
||||||
|
- **`QuestProgressLabel`**: all four **`completed`**
|
||||||
|
- **`SkillProgressionLabel`**: salvage + refine xp totals
|
||||||
|
- **`InventoryLabel`**: includes **`survey_drone_kit`**; note scrap/plate/token/stim counts
|
||||||
|
|
||||||
|
### Idempotency (no duplicate reward delivery)
|
||||||
|
|
||||||
|
21. **Godot restart:** stop Godot (**Shift+F5**); **F5** again **without** stopping the server.
|
||||||
|
22. Verify all four quests still **`completed`**; **`QuestRewardDeliveryLabel`** is **`—`** (transition-only).
|
||||||
|
23. Verify **`SkillProgressionLabel`** and **`InventoryLabel`** counts **match step 20** exactly.
|
||||||
|
24. Press **Q** on gather intro. Verify **`Quest accept: denied — already_completed`** (or **`already_active`**).
|
||||||
|
25. Press **Shift+Q**. Verify **`Quest accept: no eligible quest`** (or equivalent deny).
|
||||||
|
26. Verify economy HUD counts **still match step 20** — no duplicate skill XP or items.
|
||||||
|
|
||||||
|
### Regression
|
||||||
|
|
||||||
|
27. Component reward HUD: [NEO-131 manual QA](NEO-131.md).
|
||||||
|
28. Slice 1 quest chain: [NEO-123 manual QA](NEO-123.md).
|
||||||
|
29. Encounter loot independence: [NEO-110 manual QA](NEO-110.md).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Shift+Q catalog order** matches NEO-123 — combat intro before refine intro after gather completes.
|
||||||
|
- Quest completion bundles are **idempotent** server-side (`{playerId}:quest_complete:{questId}`); Godot restart must not replay grant copy on **`QuestRewardDeliveryLabel`** (NEO-131 transition-only).
|
||||||
|
- Economy HUD auto-refreshes on quest completion transition (NEO-132); no manual **I** required for reward verification.
|
||||||
|
- Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim ([epic_07 Slice 2](../decomposition/epics/epic_07_quest_faction.md#slice-2---reward-and-unlock-routing)).
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
- [ ] Steps 1–26 completable in one session without Bruno/curl.
|
||||||
|
- [ ] All four quest completion bundles visible on **`QuestRewardDeliveryLabel`** during in-session transitions.
|
||||||
|
- [ ] Skill XP deltas and **`survey_drone_kit`** in bag match freeze table.
|
||||||
|
- [ ] Godot restart + duplicate accepts leave economy counts unchanged (no double-claim).
|
||||||
|
- [ ] Epic 7 Slice 2 AC satisfied in Godot.
|
||||||
|
|
@ -296,6 +296,8 @@ Combat intro bundle is **skill XP only** — encounter loot already granted **`c
|
||||||
- [ ] Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
|
- [ ] Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
|
||||||
- [ ] Re-read [epic_07 Slice 2 AC](../decomposition/epics/epic_07_quest_faction.md#slice-2---reward-and-unlock-routing).
|
- [ ] Re-read [epic_07 Slice 2 AC](../decomposition/epics/epic_07_quest_faction.md#slice-2---reward-and-unlock-routing).
|
||||||
|
|
||||||
|
**Landed ([NEO-132](https://linear.app/neon-sprawl/issue/NEO-132)):** capstone manual QA [`NEO-132`](../../manual-qa/NEO-132.md); [client README — End-to-end quest reward loop (NEO-132)](../../client/README.md#end-to-end-quest-reward-loop-neo-132); economy HUD refresh on quest completion transition; plan [NEO-132](../../plans/NEO-132-implementation-plan.md).
|
||||||
|
|
||||||
**Client counterpart:** this story **is** the Slice 2 client capstone.
|
**Client counterpart:** this story **is** the Slice 2 client capstone.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,15 @@
|
||||||
- [ ] Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
|
- [ ] Epic 7 Slice 2 AC: idempotent reward delivery; replays cannot double-claim.
|
||||||
- [ ] Re-read [epic_07 Slice 2 AC](../decomposition/epics/epic_07_quest_faction.md#slice-2---reward-and-unlock-routing).
|
- [ ] Re-read [epic_07 Slice 2 AC](../decomposition/epics/epic_07_quest_faction.md#slice-2---reward-and-unlock-routing).
|
||||||
|
|
||||||
|
## Implementation reconciliation (shipped)
|
||||||
|
|
||||||
|
- **`docs/manual-qa/NEO-132.md`** — four-quest capstone extending NEO-123 with reward HUD + economy verification + idempotency steps.
|
||||||
|
- **`client/README.md`** — **End-to-end quest reward loop (NEO-132)** section with flow table and cross-links.
|
||||||
|
- **`client/scripts/quest_hud_controller.gd`** — **`quest_completion_reward_transition`** signal on in-session completion with summary.
|
||||||
|
- **`client/scripts/main.gd`** — connects signal to inventory + skill progression GET refresh (NEO-131 deferred economy HUD gap).
|
||||||
|
- **`docs/manual-qa/NEO-123.md`**, **`docs/manual-qa/NEO-131.md`** — cross-links to NEO-132 capstone.
|
||||||
|
- **`docs/plans/E7M2-prototype-backlog.md`**, **`E7_M2_RewardAndUnlockRouter.md`**, **`documentation_and_implementation_alignment.md`**, **`module_dependency_register.md`** — E7M2-09 landed; E7.M2 **Ready**; Epic 7 Slice 2 client capstone complete.
|
||||||
|
|
||||||
## Technical approach
|
## Technical approach
|
||||||
|
|
||||||
### 1. Reward freeze reference (E7M2-01)
|
### 1. Reward freeze reference (E7M2-01)
|
||||||
|
|
@ -142,16 +151,15 @@ Document any fix in **Implementation reconciliation** and add regression note to
|
||||||
|
|
||||||
| Test file | What it covers |
|
| Test file | What it covers |
|
||||||
|-----------|----------------|
|
|-----------|----------------|
|
||||||
| *(none planned)* | **Docs-primary scope** — server reward spine covered by **`RewardRouterOperationsTests`**, **`QuestStateOperationsTests`**, **`QuestProgressApiTests`**, and content CI; client reward HUD covered by NEO-131 GdUnit suites. Capstone proof is **`docs/manual-qa/NEO-132.md`** human QA. |
|
| `client/test/quest_reward_hud_test.gd` | **Add:** `quest_completion_reward_transition` emitted on in-session completion; not emitted on boot-completed snapshot. |
|
||||||
|
| *(server unchanged)* | Reward spine covered by **`RewardRouterOperationsTests`**, **`QuestStateOperationsTests`**, **`QuestProgressApiTests`**. Capstone proof is **`docs/manual-qa/NEO-132.md`** human QA. |
|
||||||
If integration fixes land, add or extend GdUnit only when the fix introduces new client refresh behavior worth locking (follow [testing-expectations.md](../../.cursor/rules/testing-expectations.md) AAA layout).
|
|
||||||
|
|
||||||
## Open questions / risks
|
## Open questions / risks
|
||||||
|
|
||||||
| Question / risk | Agent recommendation | Status |
|
| Question / risk | Agent recommendation | Status |
|
||||||
|-----------------|----------------------|--------|
|
|-----------------|----------------------|--------|
|
||||||
| **Economy HUD auto-refresh gaps** | Run capstone QA first; if combat/operator-chain completions leave stale labels, add minimal **`main.gd`** refresh hooks in same story — NEO-131 explicitly deferred this to NEO-132. | **deferred** (resolve during implementation QA) |
|
| **Economy HUD auto-refresh gaps** | Add **`quest_completion_reward_transition`** signal + **`main.gd`** inventory/skill refresh — combat and operator-chain paths lacked auto-refresh. | **adopted** |
|
||||||
| **NEO-123 vs NEO-132 relationship** | NEO-132 **supersedes** as Slice 2 capstone for reward idempotency; keep NEO-123 as Slice 1 regression. | **adopted** |
|
| **NEO-123 vs NEO-132 relationship** | NEO-132 **supersedes** as Slice 2 capstone for reward idempotency; keep NEO-123 as Slice 1 regression. | **adopted** |
|
||||||
| **Manual I key in QA script** | Prefer asserting auto-refresh after each completion; use **I** only as documented workaround if docs-primary QA passes with manual refresh and auto-fix is out of scope. | **deferred** |
|
| **Manual I key in QA script** | Prefer asserting auto-refresh after each completion; use **I** only as documented workaround if docs-primary QA passes with manual refresh and auto-fix is out of scope. | **deferred** |
|
||||||
| **Cumulative skill XP math** | Script records **delta** per completion from **`SkillProgressionLabel`**, not absolute totals — avoids coupling to unrelated XP sources. | **adopted** |
|
| **Cumulative skill XP math** | Script records **delta** per completion from **`SkillProgressionLabel`**, not absolute totals — avoids coupling to unrelated XP sources. | **adopted** |
|
||||||
| **Docs-only fails capstone** | Escalate to minimal client refresh fix in same story; record in reconciliation. | **deferred** |
|
| **Docs-only fails capstone** | Economy refresh hook landed proactively; capstone QA confirms end-to-end. | **adopted** |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue