Production fixes surfaced by honest test triage: - hud_groups.gd: _set_group_z crashed on freed HUD nodes — the typed loop variable errors before the is_instance_valid guard runs; prune first - fog_state.gd: _resize cleared _prev_visible (world-space keys survive resizes), so pre-resize tiles never decayed VISIBLE→EXPLORED (D-059) Test debt (T-928/929/934/935/936/937/938/939, T-864, T-973): lambda local-capture bugs rewritten with array captures (now assert exact emission counts), e2e suites updated to the current handshake + StartupMessage protocol and stream-aware reads against the live binary, fog perf test measures steady state, chime test pins the shipped 800ms catalog asset (D-067 amended separately), monologue gdUnit4 API typo, battery-warning tests follow the MetaScreen on_open lifecycle. 3 sprint2 proof tests revived (corner_reveal had passed from the wrong tile — NPC3 blocks (18,14); route corrected). Soft-skips converted to real do_skip reporting. T-1068: 7 orphan .gd.uid deleted, _format_pop/_format_radius deduped into atlas_format.gd (preload, no class_name — headless cache). Suite: 1264 cases/20 failures → 1268/0, independently re-verified (2536/2536, exit 0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
292 lines
11 KiB
GDScript
292 lines
11 KiB
GDScript
## Sprint 17 — UX: E-Talk overlay improvement (#537) — Phase 1
|
|
## Tests for relationship color indicator in interaction_list.gd.
|
|
##
|
|
## Spec refs:
|
|
## D-033 (entity color = relationship to player)
|
|
## D-051 (diegetic insert display — insert-only overlay)
|
|
## D-057 (interaction list, z-layer 6)
|
|
##
|
|
## Phase 1 scope (2026-02-24):
|
|
## Color bar only — NPC name and dialogue tier deferred to Phase 2 pending
|
|
## server protocol change (no known_attributes in wire protocol v13).
|
|
##
|
|
## Implementation: client/ui/interaction_list.gd
|
|
## `var _relationship_color: Color = Constants.IMPLANT_TEXT_DIM`
|
|
## `func _cache_entity_relationship() -> void`
|
|
## 3px left-edge bar drawn in _draw() at 85% alpha, called from update_from_state()
|
|
class_name TestETalkOverlaySprint17
|
|
extends GdUnitTestSuite
|
|
|
|
|
|
func before_test() -> void:
|
|
SimBridge.reset_test_state()
|
|
GameState.nearby_interactions = []
|
|
GameState.visible_entities = []
|
|
GameState.player_stance = ""
|
|
|
|
|
|
func after_test() -> void:
|
|
GameState.nearby_interactions = []
|
|
GameState.visible_entities = []
|
|
GameState.player_stance = ""
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# D-033: Constants.color_for_relationship() — palette baseline (pure logic)
|
|
# -------------------------------------------------------------------------
|
|
|
|
func test_relationship_unknown_maps_to_teal() -> void:
|
|
assert_that(Constants.color_for_relationship("Unknown")).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
|
|
func test_relationship_friendly_maps_to_green() -> void:
|
|
assert_that(Constants.color_for_relationship("Friendly")).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
|
|
|
func test_relationship_poi_maps_to_amber() -> void:
|
|
assert_that(Constants.color_for_relationship("PersonOfInterest")).is_equal(Constants.ENTITY_COLOR_POI)
|
|
|
|
func test_relationship_hostile_maps_to_red() -> void:
|
|
assert_that(Constants.color_for_relationship("Hostile")).is_equal(Constants.ENTITY_COLOR_HOSTILE)
|
|
|
|
func test_relationship_known_falls_back_to_unknown_color() -> void:
|
|
# "Known" not matched in color_for_relationship() — falls through _ → ENTITY_COLOR_UNKNOWN
|
|
assert_that(Constants.color_for_relationship("Known")).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
|
|
func test_relationship_unknown_string_falls_back_to_unknown() -> void:
|
|
assert_that(Constants.color_for_relationship("SomeNewState")).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# UIStrings: relationship state labels exist (Phase 2 pre-fixture)
|
|
# -------------------------------------------------------------------------
|
|
|
|
func test_ui_strings_has_relationship_unknown_label() -> void:
|
|
assert_that(UIStrings.has_key("relationship_states.unknown.label")).is_true()
|
|
|
|
func test_ui_strings_has_relationship_known_label() -> void:
|
|
assert_that(UIStrings.has_key("relationship_states.known.label")).is_true()
|
|
|
|
func test_ui_strings_has_relationship_friendly_label() -> void:
|
|
assert_that(UIStrings.has_key("relationship_states.friendly.label")).is_true()
|
|
|
|
func test_ui_strings_has_relationship_poi_label() -> void:
|
|
assert_that(UIStrings.has_key("relationship_states.person_of_interest.label")).is_true()
|
|
|
|
func test_ui_strings_has_relationship_hostile_label() -> void:
|
|
assert_that(UIStrings.has_key("relationship_states.hostile.label")).is_true()
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# _relationship_color initial state
|
|
# -------------------------------------------------------------------------
|
|
|
|
func test_relationship_color_default_is_implant_dim() -> void:
|
|
# Before any update, _relationship_color starts at IMPLANT_TEXT_DIM
|
|
var list = _make_list()
|
|
assert_that(list._relationship_color).is_equal(Constants.IMPLANT_TEXT_DIM)
|
|
list.queue_free()
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# _cache_entity_relationship(): D-033 palette via update_from_state() (#537 core)
|
|
# -------------------------------------------------------------------------
|
|
|
|
func test_cache_relationship_unknown_sets_unknown_color() -> void:
|
|
_setup_npc_interaction(2, "Unknown")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_friendly_sets_green() -> void:
|
|
_setup_npc_interaction(2, "Friendly")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_poi_sets_amber() -> void:
|
|
_setup_npc_interaction(2, "PersonOfInterest")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_POI)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_hostile_sets_red() -> void:
|
|
_setup_npc_interaction(2, "Hostile")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_HOSTILE)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_absent_field_uses_unknown_color() -> void:
|
|
# Entity present but "relationship" key missing → get("relationship", "Unknown") → Unknown color
|
|
GameState.visible_entities = [{
|
|
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
|
"kind": {"variant": "Npc", "data": null},
|
|
"visibility": "Forward",
|
|
}]
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
}]
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_entity_not_in_visible_uses_dim_fallback() -> void:
|
|
# Entity in nearby_interactions but NOT in visible_entities → IMPLANT_TEXT_DIM fallback
|
|
GameState.visible_entities = []
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
}]
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.IMPLANT_TEXT_DIM)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_updates_when_relationship_changes() -> void:
|
|
# First call: Unknown
|
|
_setup_npc_interaction(2, "Unknown")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
|
|
|
# Relationship shifts → re-cache picks up new value
|
|
_setup_npc_interaction(2, "Hostile")
|
|
list.update_from_state()
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_HOSTILE)
|
|
list.queue_free()
|
|
|
|
func test_cache_relationship_targets_correct_entity_by_id() -> void:
|
|
# Two entities visible; nearby_interactions[0] is the target (entity 2, Friendly)
|
|
# Entity 3 (Hostile) must not pollute the color
|
|
GameState.visible_entities = [
|
|
{
|
|
"entity_id": 2, "x": 11.0, "y": 9.0, "z": 0,
|
|
"kind": {"variant": "Npc", "data": null},
|
|
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
|
},
|
|
{
|
|
"entity_id": 3, "x": 12.0, "y": 9.0, "z": 0,
|
|
"kind": {"variant": "Npc", "data": null},
|
|
"visibility": "Forward", "relationship": "Hostile", "observation": "Visible",
|
|
},
|
|
]
|
|
GameState.nearby_interactions = [
|
|
{
|
|
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
},
|
|
{
|
|
"entity_id": 3, "entity_type": "Npc", "distance": 2,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
},
|
|
]
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
# interaction[0] = entity 2 (Friendly) → green bar
|
|
assert_that(list._relationship_color).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
|
list.queue_free()
|
|
|
|
func test_z_layer_is_insert_canvas() -> void:
|
|
# D-049 / D-057: interaction list lives on InsertOverlay (CanvasLayer 10)
|
|
var list = _make_list()
|
|
assert_that(list.get_z_layer()).is_equal(Constants.CANVAS_INSERT)
|
|
list.queue_free()
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Regression: interaction_list public API unaffected by #537 changes
|
|
# -------------------------------------------------------------------------
|
|
|
|
func test_list_hides_when_no_interactions() -> void:
|
|
GameState.nearby_interactions = []
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list.is_showing()).is_false()
|
|
list.queue_free()
|
|
|
|
func test_list_shows_on_npc_interaction() -> void:
|
|
_setup_npc_interaction(2, "Unknown")
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list.is_showing()).is_true()
|
|
list.queue_free()
|
|
|
|
func test_list_get_selected_verb_returns_first_verb() -> void:
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [
|
|
{"kind": "Talk", "label": "Talk", "priority": 1, "available": true},
|
|
{"kind": "ExamineNpc", "label": "Look", "priority": 2, "available": true},
|
|
],
|
|
}]
|
|
var list = _make_list()
|
|
assert_that(list.get_selected_verb()).is_equal("Talk")
|
|
list.queue_free()
|
|
|
|
func test_list_get_interaction_target_returns_entity_id() -> void:
|
|
GameState.visible_entities = [{
|
|
"entity_id": 5, "x": 12.0, "y": 9.0, "z": 0,
|
|
"kind": {"variant": "Npc", "data": null},
|
|
"visibility": "Forward", "relationship": "Unknown", "observation": "Visible",
|
|
}]
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 5, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
}]
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list.get_interaction_target()).is_equal(5)
|
|
list.queue_free()
|
|
|
|
func test_list_suppressed_when_insert_inactive() -> void:
|
|
_setup_npc_interaction(2, "Unknown")
|
|
var list = _make_list()
|
|
list.set_insert_active(false)
|
|
list.update_from_state()
|
|
assert_that(list.is_showing()).is_false()
|
|
list.queue_free()
|
|
|
|
func test_list_hides_on_empty_verb_list() -> void:
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [],
|
|
}]
|
|
var list = _make_list()
|
|
list.update_from_state()
|
|
assert_that(list.is_showing()).is_false()
|
|
list.queue_free()
|
|
|
|
|
|
# Empty skip_test_ placeholders for NPC-name display and dialogue-tier context
|
|
# hints removed (T-1068). They contained no test body. The blocking protocol gap
|
|
# (no known_attributes in v13) is long lifted (player_knowledge shipped in v14,
|
|
# #264/D-041), but the interaction-list features they anticipated do not exist —
|
|
# that is later-phase NPC detail work which will arrive with its own tests.
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Helpers
|
|
# -------------------------------------------------------------------------
|
|
|
|
func _make_list() -> Control:
|
|
var scene = load("res://ui/interaction_list.tscn")
|
|
var list = scene.instantiate()
|
|
add_child(list) # _ready() fires here — @onready var _vbox resolves
|
|
return list
|
|
|
|
|
|
## Set up GameState with a single NPC entity + matching interaction for tests.
|
|
func _setup_npc_interaction(entity_id: int, relationship: String) -> void:
|
|
GameState.visible_entities = [{
|
|
"entity_id": entity_id, "x": 12.0, "y": 9.0, "z": 0,
|
|
"kind": {"variant": "Npc", "data": null},
|
|
"visibility": "Forward", "relationship": relationship, "observation": "Visible",
|
|
}]
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": entity_id, "entity_type": "Npc", "distance": 1,
|
|
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
|
}]
|