NEO-122: Fix CI flake and address Bugbot quest HUD feedback.

Harden npc windup timing test, refresh quest progress on encounter
complete, surface accept busy/loading feedback, and treat missing quest
rows as not_started for Shift+Q eligibility.
pull/161/head
VinPropane 2026-06-07 14:38:06 -04:00
parent 2514772b43
commit 3606d0ba57
4 changed files with 65 additions and 14 deletions

View File

@ -745,6 +745,7 @@ func _on_encounter_progress_received(snapshot: Dictionary) -> void:
var row: Dictionary = _encounter_row(PROTOTYPE_ENCOUNTER_ID) var row: Dictionary = _encounter_row(PROTOTYPE_ENCOUNTER_ID)
if str(row.get("state", "")) == "completed": if str(row.get("state", "")) == "completed":
_request_inventory_refresh() _request_inventory_refresh()
_request_quest_progress_refresh()
func _on_encounter_progress_sync_failed(reason: String) -> void: func _on_encounter_progress_sync_failed(reason: String) -> void:

View File

@ -225,8 +225,7 @@ func _find_first_eligible_quest_id() -> String:
var qid: String = str(def.get("id", "")) var qid: String = str(def.get("id", ""))
if qid.is_empty(): if qid.is_empty():
continue continue
var row: Dictionary = _quest_row(qid) if _quest_status(qid) != "not_started":
if str(row.get("status", "")) != "not_started":
continue continue
var prereqs: Variant = def.get("prerequisiteQuestIds", []) var prereqs: Variant = def.get("prerequisiteQuestIds", [])
if not prereqs is Array: if not prereqs is Array:
@ -234,7 +233,7 @@ func _find_first_eligible_quest_id() -> String:
var prereqs_met := true var prereqs_met := true
for p_variant in prereqs as Array: for p_variant in prereqs as Array:
var pid: String = str(p_variant) var pid: String = str(p_variant)
if str(_quest_row(pid).get("status", "")) != "completed": if _quest_status(pid) != "completed":
prereqs_met = false prereqs_met = false
break break
if prereqs_met: if prereqs_met:
@ -242,19 +241,39 @@ func _find_first_eligible_quest_id() -> String:
return "" return ""
func _request_accept(quest_id: String) -> void: func _quest_status(quest_id: String) -> String:
var row: Dictionary = _quest_row(quest_id)
if row.is_empty():
return "not_started"
return str(row.get("status", "not_started"))
func _request_accept(quest_id: String) -> bool:
if not is_instance_valid(_progress_client): if not is_instance_valid(_progress_client):
return _render_accept_feedback_label("Quest accept: failed — client unavailable")
var accept_busy := false return false
if _progress_client.has_method("is_accept_busy"): if (
accept_busy = bool(_progress_client.call("is_accept_busy")) _progress_client.has_method("is_accept_busy")
if accept_busy: and bool(_progress_client.call("is_accept_busy"))
return ):
if _progress_client.has_method("request_accept"): _render_accept_feedback_label("Quest accept: busy — try again")
_progress_client.call("request_accept", quest_id) return false
if not _progress_client.has_method("request_accept"):
_render_accept_feedback_label("Quest accept: failed — client unavailable")
return false
var started: bool = bool(_progress_client.call("request_accept", quest_id))
if not started:
_render_accept_feedback_label("Quest accept: failed — could not start")
return started
func _request_eligible_accept() -> void: func _request_eligible_accept() -> void:
if _progress_error.is_empty() and _last_snapshot.is_empty():
_render_accept_feedback_label("Quest accept: progress loading — try again")
return
if _defs_error.is_empty() and _defs_snapshot().is_empty():
_render_accept_feedback_label("Quest accept: definitions loading — try again")
return
var eligible_id: String = _find_first_eligible_quest_id() var eligible_id: String = _find_first_eligible_quest_id()
if eligible_id.is_empty(): if eligible_id.is_empty():
_render_accept_feedback_label("Quest accept: no eligible quest") _render_accept_feedback_label("Quest accept: no eligible quest")

View File

@ -29,11 +29,13 @@ func test_display_windup_remaining_decreases_over_time() -> void:
state.call("apply_snapshot", _telegraph_snapshot(2.0)) state.call("apply_snapshot", _telegraph_snapshot(2.0))
var first: float = float(state.call("display_windup_remaining", "prototype_npc_elite")) var first: float = float(state.call("display_windup_remaining", "prototype_npc_elite"))
# Act # Act
await (Engine.get_main_loop() as SceneTree).create_timer(0.05).timeout for _i in 3:
await get_tree().process_frame
await (Engine.get_main_loop() as SceneTree).create_timer(0.1).timeout
var second: float = float(state.call("display_windup_remaining", "prototype_npc_elite")) var second: float = float(state.call("display_windup_remaining", "prototype_npc_elite"))
# Assert # Assert
assert_that(second).is_less(first) assert_that(second).is_less(first)
assert_that(second).is_greater(1.9) assert_that(second).is_greater(1.85)
func test_new_snapshot_reconciles_windup_anchor() -> void: func test_new_snapshot_reconciles_windup_anchor() -> void:

View File

@ -58,3 +58,32 @@ func test_progress_label_shows_loading_before_snapshot() -> void:
# Assert # Assert
assert_str(text).contains("Quests:") assert_str(text).contains("Quests:")
assert_str(text).contains("Loading") assert_str(text).contains("Loading")
func test_shift_q_shows_progress_loading_before_snapshot() -> void:
# Arrange
var controller: Node = QuestHudController.new()
var accept_label := Label.new()
add_child(controller)
controller.set("_progress_client", QuestProgressClient.new())
controller.set("_defs_client", QuestDefinitionsClient.new())
controller.set("_accept_label", accept_label)
controller.set("_last_snapshot", {})
controller.set("_progress_error", "")
# Act
controller.call("_request_eligible_accept")
# Assert
assert_str(accept_label.text).contains("progress loading")
func test_request_accept_shows_busy_feedback() -> void:
# Arrange
var controller := await _build_controller()
var progress: Node = controller.get("_progress_client")
progress.call("request_accept", QuestHudController.GATHER_QUEST_ID)
var accept_label: Label = controller.get("_accept_label")
# Act
var started: bool = bool(controller.call("_request_accept", QuestHudController.GATHER_QUEST_ID))
# Assert
assert_bool(started).is_false()
assert_str(accept_label.text).contains("busy")