NEO-131: Address review — item grant and defs repaint tests.
Add operator-chain item display name and definitions_ready reward label GdUnit cases; add NEO-131 plan link to alignment register.pull/170/head
parent
ca9a3f1b38
commit
0e13a10b79
|
|
@ -6,6 +6,8 @@ const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
||||||
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
||||||
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
||||||
|
|
||||||
|
const OPERATOR_CHAIN_QUEST_ID := "prototype_quest_operator_chain"
|
||||||
|
|
||||||
|
|
||||||
class MockHttpTransport:
|
class MockHttpTransport:
|
||||||
extends Node
|
extends Node
|
||||||
|
|
@ -75,6 +77,15 @@ class FailStartAcceptHttpTransport:
|
||||||
return ERR_UNAVAILABLE
|
return ERR_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
class StubItemDefsClient:
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
func display_name_for(item_id: String) -> String:
|
||||||
|
if item_id == "survey_drone_kit":
|
||||||
|
return "Survey Drone Kit"
|
||||||
|
return item_id
|
||||||
|
|
||||||
|
|
||||||
func _make_progress_client(sync_transport: Node, accept_transport: Node = null) -> Node:
|
func _make_progress_client(sync_transport: Node, accept_transport: Node = null) -> Node:
|
||||||
var c: Node = QuestProgressClient.new()
|
var c: Node = QuestProgressClient.new()
|
||||||
c.set("injected_sync_http", sync_transport)
|
c.set("injected_sync_http", sync_transport)
|
||||||
|
|
@ -548,6 +559,88 @@ func test_boot_completed_snapshot_does_not_paint_reward_label() -> void:
|
||||||
assert_str(reward_label.text).is_equal("Quest rewards:\n—")
|
assert_str(reward_label.text).is_equal("Quest rewards:\n—")
|
||||||
|
|
||||||
|
|
||||||
|
func _active_operator_chain_snapshot() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"playerId": "dev-local-1",
|
||||||
|
"quests":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"questId": OPERATOR_CHAIN_QUEST_ID,
|
||||||
|
"status": "active",
|
||||||
|
"currentStepIndex": 3,
|
||||||
|
"objectiveCounters": {},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _completed_operator_chain_with_summary_snapshot() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"playerId": "dev-local-1",
|
||||||
|
"quests":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"questId": OPERATOR_CHAIN_QUEST_ID,
|
||||||
|
"status": "completed",
|
||||||
|
"currentStepIndex": 3,
|
||||||
|
"objectiveCounters": {},
|
||||||
|
"completedAt": "2026-06-07T14:00:00Z",
|
||||||
|
"completionRewardSummary":
|
||||||
|
{
|
||||||
|
"itemGrants": [{"itemId": "survey_drone_kit", "quantity": 1}],
|
||||||
|
"skillXpGrants": [{"skillId": "salvage", "amount": 50}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func test_completion_transition_paints_item_grant_with_display_name() -> void:
|
||||||
|
# Arrange
|
||||||
|
var controller: Node = QuestHudController.new()
|
||||||
|
var reward_label := Label.new()
|
||||||
|
var item_defs := StubItemDefsClient.new()
|
||||||
|
auto_free(controller)
|
||||||
|
auto_free(reward_label)
|
||||||
|
auto_free(item_defs)
|
||||||
|
add_child(controller)
|
||||||
|
add_child(item_defs)
|
||||||
|
controller.set("_reward_label", reward_label)
|
||||||
|
controller.set("_item_defs_client", item_defs)
|
||||||
|
# Act
|
||||||
|
controller.call("_on_progress_received", _active_operator_chain_snapshot())
|
||||||
|
controller.call("_on_progress_received", _completed_operator_chain_with_summary_snapshot())
|
||||||
|
# Assert
|
||||||
|
assert_str(reward_label.text).contains("Survey Drone Kit ×1")
|
||||||
|
assert_str(reward_label.text).contains("Salvage +50 XP")
|
||||||
|
|
||||||
|
|
||||||
|
func test_definitions_ready_repaints_reward_label_with_display_name() -> void:
|
||||||
|
# Arrange
|
||||||
|
var defs_transport := MockHttpTransport.new()
|
||||||
|
defs_transport.body_json = _gather_defs_json()
|
||||||
|
var defs_client: Node = _make_defs_client(defs_transport)
|
||||||
|
var controller: Node = QuestHudController.new()
|
||||||
|
var reward_label := Label.new()
|
||||||
|
auto_free(controller)
|
||||||
|
auto_free(reward_label)
|
||||||
|
add_child(controller)
|
||||||
|
controller.set("_defs_client", defs_client)
|
||||||
|
controller.set("_reward_label", reward_label)
|
||||||
|
controller.call("_on_progress_received", _active_gather_snapshot())
|
||||||
|
controller.call("_on_progress_received", _completed_gather_with_summary_snapshot())
|
||||||
|
assert_str(reward_label.text).contains(QuestHudController.GATHER_QUEST_ID)
|
||||||
|
# Act
|
||||||
|
defs_client.call("request_sync_from_server")
|
||||||
|
controller.call("_on_definitions_ready", [])
|
||||||
|
# Assert
|
||||||
|
assert_str(reward_label.text).contains("Intro: Salvage Run")
|
||||||
|
assert_str(reward_label.text).contains("Salvage +25 XP")
|
||||||
|
assert_str(reward_label.text).not_contains("prototype_quest_gather_intro")
|
||||||
|
|
||||||
|
|
||||||
func test_sync_failure_shows_progress_error_and_idle_reward_label() -> void:
|
func test_sync_failure_shows_progress_error_and_idle_reward_label() -> void:
|
||||||
# Arrange
|
# Arrange
|
||||||
var controller: Node = QuestHudController.new()
|
var controller: Node = QuestHudController.new()
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -61,7 +61,7 @@
|
||||||
- **`quest_progress_client.gd`:** **`completion_reward_summary`** static helper; parse preserves NEO-129 nested summary on completed rows.
|
- **`quest_progress_client.gd`:** **`completion_reward_summary`** static helper; parse preserves NEO-129 nested summary on completed rows.
|
||||||
- **`quest_hud_controller.gd`:** transition-only detection (**`_previous_status_by_quest`**); **`QuestRewardDeliveryLabel`** render with item-def display names + title-case skill XP lines; sync error idle state.
|
- **`quest_hud_controller.gd`:** transition-only detection (**`_previous_status_by_quest`**); **`QuestRewardDeliveryLabel`** render with item-def display names + title-case skill XP lines; sync error idle state.
|
||||||
- **`main.gd` / `main.tscn`:** **`QuestRewardDeliveryLabel`** after accept feedback; **`ItemDefinitionsClient`** threaded into controller **`setup`**.
|
- **`main.gd` / `main.tscn`:** **`QuestRewardDeliveryLabel`** after accept feedback; **`ItemDefinitionsClient`** threaded into controller **`setup`**.
|
||||||
- **Tests:** `quest_progress_client_test.gd` (summary parse); `quest_hud_controller_test.gd` (transition, boot idempotency, sync error).
|
- **Tests:** `quest_progress_client_test.gd` (summary parse); `quest_hud_controller_test.gd` (transition, boot idempotency, sync error, item grant display name, `definitions_ready` reward repaint).
|
||||||
- **Docs:** `client/README.md`; `docs/manual-qa/NEO-131.md`; E7.M2 module + backlog alignment.
|
- **Docs:** `client/README.md`; `docs/manual-qa/NEO-131.md`; E7.M2 module + backlog alignment.
|
||||||
|
|
||||||
## Technical approach
|
## Technical approach
|
||||||
|
|
@ -165,7 +165,7 @@ Combat intro completion is **skill XP only** in quest bundle — encounter token
|
||||||
| Test file | What it covers |
|
| 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_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 **`—`**. |
|
| `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 **`—`**; operator-chain item grant + display name; **`definitions_ready`** reward label repaint. |
|
||||||
|
|
||||||
**Regression:** existing NEO-122 GdUnit suites unchanged; `dotnet test` N/A (client-only).
|
**Regression:** existing NEO-122 GdUnit suites unchanged; `dotnet test` N/A (client-only).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ NEO-131 adds Godot client surfacing for server-owned quest completion grants: **
|
||||||
| `docs/plans/NEO-131-implementation-plan.md` | **Matches** — kickoff decisions adopted; acceptance checklist checked; reconciliation accurate. |
|
| `docs/plans/NEO-131-implementation-plan.md` | **Matches** — kickoff decisions adopted; acceptance checklist checked; reconciliation accurate. |
|
||||||
| `docs/plans/E7M2-prototype-backlog.md` (E7M2-08) | **Matches** — acceptance checkboxes checked; landed note + README/plan/manual QA links. |
|
| `docs/plans/E7M2-prototype-backlog.md` (E7M2-08) | **Matches** — acceptance checkboxes checked; landed note + README/plan/manual QA links. |
|
||||||
| `docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md` | **Matches** — NEO-131 client HUD snapshot under implementation anchor. |
|
| `docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md` | **Matches** — NEO-131 client HUD snapshot under implementation anchor. |
|
||||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Partially matches** — E7M2-08 / NEO-131 landed note in row body; references column omits explicit [NEO-131 plan](../../plans/NEO-131-implementation-plan.md) link (nit). |
|
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7M2-08 / NEO-131 landed note in row body; references column includes [NEO-131 plan](../../plans/NEO-131-implementation-plan.md). |
|
||||||
| `docs/decomposition/modules/module_dependency_register.md` | **Matches** — no register row change required. |
|
| `docs/decomposition/modules/module_dependency_register.md` | **Matches** — no register row change required. |
|
||||||
| `docs/decomposition/modules/client_server_authority.md` | **Matches** — client displays GET snapshot only; no local grant math. |
|
| `docs/decomposition/modules/client_server_authority.md` | **Matches** — client displays GET snapshot only; no local grant math. |
|
||||||
| `client/README.md` | **Matches** — quest completion reward HUD subsection with routes, triggers, error pattern. |
|
| `client/README.md` | **Matches** — quest completion reward HUD subsection with routes, triggers, error pattern. |
|
||||||
|
|
@ -34,15 +34,15 @@ NEO-131 adds Godot client surfacing for server-owned quest completion grants: **
|
||||||
|
|
||||||
## Suggestions
|
## Suggestions
|
||||||
|
|
||||||
1. **GdUnit: item grant + display name** — New HUD tests cover skill XP transition, boot idempotency, and sync-error idle state. A small case with a mock **`ItemDefinitionsClient`** (or stub **`display_name_for`**) asserting **`Survey Drone Kit ×1`** would lock the operator-chain path referenced in manual QA and the freeze table without requiring Godot gameplay.
|
1. ~~**GdUnit: item grant + display name** — New HUD tests cover skill XP transition, boot idempotency, and sync-error idle state. A small case with a mock **`ItemDefinitionsClient`** (or stub **`display_name_for`**) asserting **`Survey Drone Kit ×1`** would lock the operator-chain path referenced in manual QA and the freeze table without requiring Godot gameplay.~~ **Done.** `test_completion_transition_paints_item_grant_with_display_name` + **`StubItemDefsClient`** in `client/test/quest_hud_controller_test.gd`.
|
||||||
|
|
||||||
2. **GdUnit: `definitions_ready` reward repaint** — **`_on_definitions_ready`** repaints the reward label when **`_last_reward_quest_id`** is set (quest display name upgrade). Mirror the existing NEO-122 progress-label test pattern for the reward label so regressions in **`_display_name`** wiring are caught.
|
2. ~~**GdUnit: `definitions_ready` reward repaint** — **`_on_definitions_ready`** repaints the reward label when **`_last_reward_quest_id`** is set (quest display name upgrade). Mirror the existing NEO-122 progress-label test pattern for the reward label so regressions in **`_display_name`** wiring are caught.~~ **Done.** `test_definitions_ready_repaints_reward_label_with_display_name` in `client/test/quest_hud_controller_test.gd`.
|
||||||
|
|
||||||
## Nits
|
## Nits
|
||||||
|
|
||||||
- Nit: **`_format_reward_summary_lines`** appends **`(no grants)`** when summary dict is present but all grant rows are filtered out (empty arrays or zero qty). Server delivery snapshots should always include at least one grant when **`completionRewardSummary`** is emitted; harmless defensive copy.
|
- Nit: **`_format_reward_summary_lines`** appends **`(no grants)`** when summary dict is present but all grant rows are filtered out (empty arrays or zero qty). Server delivery snapshots should always include at least one grant when **`completionRewardSummary`** is emitted; harmless defensive copy.
|
||||||
|
|
||||||
- Nit: **`documentation_and_implementation_alignment.md`** E7.M2 references pipe lists NEO-124–NEO-130 plan links but not **`NEO-131-implementation-plan.md`** — row body already cites NEO-131; add link for parity with other landed slices.
|
- ~~Nit: **`documentation_and_implementation_alignment.md`** E7.M2 references pipe lists NEO-124–NEO-130 plan links but not **`NEO-131-implementation-plan.md`** — row body already cites NEO-131; add link for parity with other landed slices.~~ **Done.** References column now includes [NEO-131 plan](../../plans/NEO-131-implementation-plan.md).
|
||||||
|
|
||||||
- Nit: Encounter loot (**NEO-110**) repaints on every **`completed`** snapshot; quest rewards intentionally use transition-only detection — correct per plan; keep the distinction in mind when NEO-132 capstone asserts end-to-end economy HUD refresh.
|
- Nit: Encounter loot (**NEO-110**) repaints on every **`completed`** snapshot; quest rewards intentionally use transition-only detection — correct per plan; keep the distinction in mind when NEO-132 capstone asserts end-to-end economy HUD refresh.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue