NEO-73: preserve gather finalize across coalesced terminal POST.
Use awaiting-inventory flag so R-then-E still paints GatherFeedbackLabel after inventory sync; only clear session on true orphans (Bugbot #107).pull/107/head
parent
d0622701eb
commit
07ca427f2f
|
|
@ -82,9 +82,9 @@ var _inventory_error: String = ""
|
|||
var _interactables_catalog: Array = []
|
||||
var _last_progression_snapshot: Dictionary = {}
|
||||
var _progression_error: String = ""
|
||||
var _pending_gather_feedback: bool = false
|
||||
var _pending_gather_interactable_id: String = ""
|
||||
var _gather_pre_scrap_qty: int = 0
|
||||
var _gather_pending_interactable_id: String = ""
|
||||
var _gather_awaiting_inventory_finalize: bool = false
|
||||
|
||||
@onready var _camera: Camera3D = $World/IsometricFollowCamera/Camera3D
|
||||
@onready var _world: Node3D = $World
|
||||
|
|
@ -375,8 +375,8 @@ func _on_inventory_sync_failed(reason: String) -> void:
|
|||
_inventory_error = reason
|
||||
_last_inventory_snapshot = {}
|
||||
_render_inventory_label()
|
||||
if _pending_gather_feedback:
|
||||
_clear_pending_gather_feedback()
|
||||
if _gather_awaiting_inventory_finalize:
|
||||
_clear_gather_session()
|
||||
_render_gather_feedback_label("Gather: inventory refresh failed")
|
||||
|
||||
|
||||
|
|
@ -528,27 +528,33 @@ func _render_gather_feedback_label(text: String = "Gather: —") -> void:
|
|||
_gather_feedback_label.text = text
|
||||
|
||||
|
||||
func _clear_pending_gather_feedback() -> void:
|
||||
_pending_gather_feedback = false
|
||||
_pending_gather_interactable_id = ""
|
||||
func _clear_gather_session() -> void:
|
||||
_gather_pending_interactable_id = ""
|
||||
_gather_awaiting_inventory_finalize = false
|
||||
|
||||
|
||||
func _on_interaction_result_for_gather(
|
||||
interactable_id: String, allowed: bool, reason_code: String
|
||||
) -> void:
|
||||
if _pending_gather_feedback and interactable_id != _pending_gather_interactable_id:
|
||||
_clear_pending_gather_feedback()
|
||||
if _gather_pending_interactable_id.is_empty():
|
||||
return
|
||||
if interactable_id != _gather_pending_interactable_id:
|
||||
if _gather_awaiting_inventory_finalize:
|
||||
return
|
||||
_clear_gather_session()
|
||||
return
|
||||
if not PrototypeInteractablePicker.is_resource_node_id(_interactables_catalog, interactable_id):
|
||||
_clear_gather_session()
|
||||
return
|
||||
if not allowed:
|
||||
_clear_pending_gather_feedback()
|
||||
_clear_gather_session()
|
||||
var rc := reason_code.strip_edges()
|
||||
if rc.is_empty():
|
||||
_render_gather_feedback_label("Gather: denied (no reasonCode)")
|
||||
else:
|
||||
_render_gather_feedback_label("Gather: denied — %s" % rc)
|
||||
return
|
||||
_gather_awaiting_inventory_finalize = true
|
||||
_request_inventory_refresh()
|
||||
if (
|
||||
is_instance_valid(_skill_progression_client)
|
||||
|
|
@ -558,12 +564,14 @@ func _on_interaction_result_for_gather(
|
|||
|
||||
|
||||
func _on_interaction_request_failed_for_gather(interactable_id: String, _reason: String) -> void:
|
||||
if not _pending_gather_feedback:
|
||||
if _gather_pending_interactable_id.is_empty():
|
||||
return
|
||||
if interactable_id != _pending_gather_interactable_id:
|
||||
_clear_pending_gather_feedback()
|
||||
if interactable_id != _gather_pending_interactable_id:
|
||||
if not _gather_awaiting_inventory_finalize:
|
||||
_clear_gather_session()
|
||||
return
|
||||
_clear_pending_gather_feedback()
|
||||
if not _gather_awaiting_inventory_finalize:
|
||||
_clear_gather_session()
|
||||
|
||||
|
||||
func _total_bag_qty(item_id: String, snapshot: Dictionary = {}) -> int:
|
||||
|
|
@ -585,10 +593,10 @@ func _total_bag_qty(item_id: String, snapshot: Dictionary = {}) -> int:
|
|||
|
||||
|
||||
func _finalize_pending_gather_feedback() -> void:
|
||||
if not _pending_gather_feedback:
|
||||
if not _gather_awaiting_inventory_finalize:
|
||||
return
|
||||
var pre_qty: int = _gather_pre_scrap_qty
|
||||
_clear_pending_gather_feedback()
|
||||
_clear_gather_session()
|
||||
var new_qty: int = _total_bag_qty(SCRAP_METAL_BULK_ID)
|
||||
var delta: int = new_qty - pre_qty
|
||||
if delta <= 0:
|
||||
|
|
@ -837,8 +845,8 @@ func _forward_interact_resource_nearest() -> void:
|
|||
_interactables_catalog, _player.global_position
|
||||
)
|
||||
_gather_pre_scrap_qty = _total_bag_qty(SCRAP_METAL_BULK_ID)
|
||||
_pending_gather_interactable_id = node_id
|
||||
_pending_gather_feedback = true
|
||||
_gather_pending_interactable_id = node_id
|
||||
_gather_awaiting_inventory_finalize = false
|
||||
if _interaction_client.has_method("post_interact_id"):
|
||||
_interaction_client.call("post_interact_id", node_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ class GatherRefreshHarness:
|
|||
extends Node
|
||||
var inventory_sync_calls: int = 0
|
||||
var skill_sync_calls: int = 0
|
||||
var pending_gather: bool = false
|
||||
var pending_gather_interactable_id: String = ""
|
||||
var awaiting_inventory_finalize: bool = false
|
||||
var _inventory: SpySyncClient
|
||||
var _skill: SpySyncClient
|
||||
var _ix: Node
|
||||
|
|
@ -61,23 +61,29 @@ class GatherRefreshHarness:
|
|||
|
||||
func queue_resource_gather(interactable_id: String) -> void:
|
||||
pending_gather_interactable_id = interactable_id
|
||||
pending_gather = true
|
||||
awaiting_inventory_finalize = false
|
||||
|
||||
func _clear_pending_gather() -> void:
|
||||
pending_gather = false
|
||||
func _clear_gather_session() -> void:
|
||||
pending_gather_interactable_id = ""
|
||||
awaiting_inventory_finalize = false
|
||||
|
||||
func _on_interaction_result(
|
||||
interactable_id: String, allowed: bool, _reason_code: String
|
||||
) -> void:
|
||||
if pending_gather and interactable_id != pending_gather_interactable_id:
|
||||
_clear_pending_gather()
|
||||
if pending_gather_interactable_id.is_empty():
|
||||
return
|
||||
if interactable_id != pending_gather_interactable_id:
|
||||
if awaiting_inventory_finalize:
|
||||
return
|
||||
_clear_gather_session()
|
||||
return
|
||||
if not Picker.is_resource_node_id([], interactable_id):
|
||||
_clear_gather_session()
|
||||
return
|
||||
if not allowed:
|
||||
_clear_pending_gather()
|
||||
_clear_gather_session()
|
||||
return
|
||||
awaiting_inventory_finalize = true
|
||||
_inventory.request_sync_from_server()
|
||||
_skill.request_sync_from_server()
|
||||
|
||||
|
|
@ -136,6 +142,25 @@ func test_terminal_result_clears_orphaned_gather_pending() -> void:
|
|||
# Act
|
||||
harness.emit_terminal_allowed()
|
||||
# Assert
|
||||
assert_that(harness.pending_gather).is_false()
|
||||
assert_that(harness.pending_gather_interactable_id).is_empty()
|
||||
assert_that(harness.awaiting_inventory_finalize).is_false()
|
||||
assert_that(harness._inventory.sync_calls).is_equal(0)
|
||||
assert_that(harness._skill.sync_calls).is_equal(0)
|
||||
|
||||
|
||||
func test_terminal_after_gather_success_keeps_awaiting_inventory() -> void:
|
||||
# Arrange
|
||||
var transport := MockInteractTransport.new()
|
||||
var harness := GatherRefreshHarness.new()
|
||||
auto_free(transport)
|
||||
auto_free(harness)
|
||||
add_child(harness)
|
||||
harness.setup(self, transport)
|
||||
# Act
|
||||
harness.post_resource_alpha()
|
||||
harness.emit_terminal_allowed()
|
||||
# Assert
|
||||
assert_that(harness.awaiting_inventory_finalize).is_true()
|
||||
assert_that(harness.pending_gather_interactable_id).is_equal("prototype_resource_node_alpha")
|
||||
assert_that(harness._inventory.sync_calls).is_equal(1)
|
||||
assert_that(harness._skill.sync_calls).is_equal(1)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ The branch adds **`skill_progression_client.gd`**, **`prototype_interactable_pic
|
|||
|
||||
- ~~Nit: **`Gather: ok — inventory refresh failed`** (`main.gd` **`_on_inventory_sync_failed`**) reads as success; consider **`Gather: inventory refresh failed`** for clarity.~~ **Done.**
|
||||
- Nit: Rapid **R** while a gather POST is in flight overwrites **`_gather_pre_scrap_qty`**; delta can be wrong if two responses complete out of order. Prototype-acceptable; last-wins POST coalescing already exists on the interaction client.
|
||||
- ~~**Bugbot:** Gather feedback flag orphaned when interaction coalesced (**E** overwrites pending **R** POST) — stale **`_pending_gather_feedback`** could paint spurious delta on manual **I**.~~ **Done.** Track **`_pending_gather_interactable_id`**; clear pending when a different interactable completes or fails.
|
||||
- ~~**Bugbot:** Gather feedback flag orphaned when interaction coalesced (**E** overwrites pending **R** POST) — stale **`_pending_gather_feedback`** could paint spurious delta on manual **I**.~~ **Done.** Track **`_gather_pending_interactable_id`**; clear session only when gather never completed.
|
||||
- ~~**Bugbot:** Coalesced POST clears gather feedback before inventory arrives — terminal result after successful **R** cleared pending before inventory GET returned, dropping **`GatherFeedbackLabel`** delta.~~ **Done.** **`_gather_awaiting_inventory_finalize`** preserves session until inventory sync paints delta.
|
||||
- Nit: **`KNOWN_RESOURCE_NODE_IDS`** in **`prototype_interactable_picker.gd`** duplicates the frozen four-node table — intentional catalog-empty fallback; keep in sync with **`E3_M1`** if ids ever change (they are frozen).
|
||||
|
||||
## Verification
|
||||
|
|
|
|||
Loading…
Reference in New Issue