Fix 354 gdlint warnings across 65 files: 194 class-definitions-order (reorder declarations), 138 max-line-length (split long lines), 22 code issues (unused args, no-else-return, naming). Update .gdlintrc to exclude addons/ and raise max-public-methods for test files. No logic changes — declaration order, whitespace, and naming only. Ticket: #783 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
401 lines
14 KiB
GDScript
401 lines
14 KiB
GDScript
## #522: OQ-07 resolution — no-insert interaction behavior.
|
|
## Tests that when insert_active == false, interaction labels disappear
|
|
## (diegetic test from D-056 and D-057), and that cursor and interaction
|
|
## list agree on insert state.
|
|
##
|
|
## D-056: "Interaction labels render on z-layer 6 (insert overlay).
|
|
## If the insert is off, labels disappear."
|
|
## D-057: "Labels render on z-layer 6. If insert is off, labels disappear."
|
|
##
|
|
## OQ-07 resolution (expected option a): cursor shape still changes
|
|
## (character physically orients), but verb labels are suppressed.
|
|
##
|
|
## Spec refs: D-056, D-057, D-048, D-049
|
|
## Ticket #522
|
|
class_name TestInsertOffBehavior
|
|
extends GdUnitTestSuite
|
|
|
|
|
|
func after_each() -> void:
|
|
# Reset GameState fields mutated by tests to prevent cross-test leakage.
|
|
GameState.nearby_interactions = []
|
|
GameState.player_stance = "Walk"
|
|
GameState.insert_active = true
|
|
|
|
|
|
# -- Helpers -------------------------------------------------------------------
|
|
|
|
func _make_cursor() -> Node:
|
|
for path in ["res://ui/cursor_state_machine.tscn", "res://scenes/cursor.tscn", "res://ui/cursor.tscn"]:
|
|
if ResourceLoader.exists(path):
|
|
var scene = load(path)
|
|
var cursor = scene.instantiate()
|
|
add_child(cursor)
|
|
return cursor
|
|
return null
|
|
|
|
|
|
func _make_cursor_or_skip() -> Node:
|
|
var cursor = _make_cursor()
|
|
if cursor == null:
|
|
push_warning("TestInsertOffBehavior: cursor scene not found — test skipped (awaiting #522)")
|
|
return cursor
|
|
|
|
|
|
func _make_interaction_list() -> Node:
|
|
for path in ["res://ui/interaction_list.tscn", "res://ui/entity_interaction_list.tscn",
|
|
"res://scenes/interaction_list.tscn"]:
|
|
if ResourceLoader.exists(path):
|
|
var scene = load(path)
|
|
var node = scene.instantiate()
|
|
add_child(node)
|
|
return node
|
|
return null
|
|
|
|
|
|
func _make_list_or_skip() -> Node:
|
|
var list = _make_interaction_list()
|
|
if list == null:
|
|
push_warning("TestInsertOffBehavior: interaction list scene not found — test skipped (awaiting #522)")
|
|
return list
|
|
|
|
|
|
func _set_test_interactions() -> void:
|
|
GameState.nearby_interactions = [{
|
|
"entity_id": 2,
|
|
"entity_type": "Npc",
|
|
"distance": 1,
|
|
"verbs": [
|
|
{"kind": "Talk", "label": "Talk", "priority": 1, "available": true},
|
|
],
|
|
}]
|
|
|
|
|
|
func _clear_test_interactions() -> void:
|
|
GameState.nearby_interactions = []
|
|
|
|
|
|
# -- Interaction list: diegetic test (D-056, D-057) ---------------------------
|
|
|
|
func test_insert_off_hides_interaction_list() -> void:
|
|
# D-056/D-057 diegetic test: "If the insert is off, labels disappear."
|
|
var list = _make_list_or_skip()
|
|
if list == null:
|
|
return
|
|
_set_test_interactions()
|
|
if list.has_method("set_insert_active"):
|
|
list.set_insert_active(false)
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
if list.has_method("is_showing"):
|
|
assert_that(list.is_showing()).override_failure_message(
|
|
"D-056/D-057 diegetic test: interaction list must be hidden when insert is off"
|
|
).is_false()
|
|
elif list.has_method("get_visible_verb_count"):
|
|
assert_that(list.get_visible_verb_count()).override_failure_message(
|
|
"Interaction list must show 0 verbs when insert is off"
|
|
).is_equal(0)
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
|
|
|
|
func test_insert_on_shows_interaction_list() -> void:
|
|
# When insert is on (normal state), verbs should be visible.
|
|
var list = _make_list_or_skip()
|
|
if list == null:
|
|
return
|
|
_set_test_interactions()
|
|
if list.has_method("set_insert_active"):
|
|
list.set_insert_active(true)
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
if list.has_method("is_showing"):
|
|
assert_that(list.is_showing()).override_failure_message(
|
|
"Interaction list must be visible when insert is on and verbs present"
|
|
).is_true()
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
|
|
|
|
func test_insert_reenable_restores_list() -> void:
|
|
# Toggling insert off then on restores verb list visibility.
|
|
var list = _make_list_or_skip()
|
|
if list == null:
|
|
return
|
|
_set_test_interactions()
|
|
if not list.has_method("set_insert_active"):
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
return
|
|
list.set_insert_active(false)
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
list.set_insert_active(true)
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
if list.has_method("is_showing"):
|
|
assert_that(list.is_showing()).override_failure_message(
|
|
"Re-enabling insert must restore list visibility"
|
|
).is_true()
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
|
|
|
|
# -- Cursor: insert-off behavior (OQ-07 resolution option a) ------------------
|
|
|
|
func test_insert_off_suppresses_should_show_interactions() -> void:
|
|
# OQ-07 option (a): labels are suppressed when insert is off.
|
|
# Regardless of cursor visual state, should_show_interactions() must return false.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active"):
|
|
push_warning("TestInsertOffBehavior: cursor.set_insert_active not found — awaiting #522")
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"should_show_interactions() must return false when insert is off"
|
|
).is_false()
|
|
cursor.queue_free()
|
|
|
|
|
|
func test_insert_on_restores_should_show_interactions() -> void:
|
|
# After re-enabling insert, interactions should show again (not in weapon mode).
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active"):
|
|
push_warning("TestInsertOffBehavior: cursor.set_insert_active not found — awaiting #522")
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
cursor.set_insert_active(true)
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"should_show_interactions() must return true after re-enabling insert"
|
|
).is_true()
|
|
cursor.queue_free()
|
|
|
|
|
|
func test_insert_off_at_startup_no_corruption() -> void:
|
|
# Insert can be off from the start — no state machine corruption.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active") or not cursor.has_method("get_state"):
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
# Cursor must be in a valid state (not null/error)
|
|
var state_str := str(cursor.get_state())
|
|
assert_that(["Default", "EntityHover", "ObjectHover", "WeaponAim"].has(state_str)).override_failure_message(
|
|
"Cursor must be in a valid state after insert-off at startup, got: %s" % state_str
|
|
).is_true()
|
|
cursor.queue_free()
|
|
|
|
|
|
# -- OQ-07 option (a): cursor shape still changes, labels suppressed ----------
|
|
|
|
func test_insert_off_option_a_cursor_still_transitions() -> void:
|
|
# Option (a): cursor shape changes even when insert off
|
|
# (character physically orients to target, just no labels).
|
|
# If implementation chose option (b) instead, this test would fail — that's informative.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active") or not cursor.has_method("set_hover_target"):
|
|
push_warning("TestInsertOffBehavior: set_insert_active or set_hover_target not found — awaiting #522")
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
cursor.set_hover_target({"entity_id": 2, "kind": "Npc", "relationship": "Unknown", "in_los": true})
|
|
# Option (a): cursor SHOULD be in EntityHover despite insert being off
|
|
# Option (b): cursor would stay in Default
|
|
# Test documents expected behavior — fail message explains which option is active
|
|
if cursor.has_method("get_state"):
|
|
var state := str(cursor.get_state())
|
|
# If this assertion fails, option (b) was implemented (cursor full-suppressed).
|
|
# Neither (a) nor (b) is wrong — this test documents which was chosen.
|
|
# Consult decisions/perception.md for the OQ-07 amendment.
|
|
assert_that(state).override_failure_message(
|
|
"OQ-07 option (a): cursor should still transition to EntityHover when insert is off. "
|
|
+ "If this fails, option (b) was implemented (full suppression) — update this test to assert 'Default' instead."
|
|
).is_equal("EntityHover")
|
|
cursor.queue_free()
|
|
|
|
|
|
# -- Edge cases ---------------------------------------------------------------
|
|
|
|
func test_insert_off_plus_weapon_mode_stays_suppressed() -> void:
|
|
# Insert-off AND weapon mode: should_show_interactions() must still return false.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active") or not cursor.has_method("set_weapon_mode"):
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
cursor.set_weapon_mode(true)
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"Insert-off + weapon mode must keep should_show_interactions() false"
|
|
).is_false()
|
|
cursor.queue_free()
|
|
|
|
|
|
func test_insert_off_shift_override_still_suppressed() -> void:
|
|
# OQ-07: Insert-off trumps Shift override.
|
|
# Shift restores interactions in weapon mode, but insert-off is a harder constraint.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active") or not cursor.has_method("set_shift_held"):
|
|
cursor.queue_free()
|
|
return
|
|
cursor.set_insert_active(false)
|
|
if cursor.has_method("set_shift_held"):
|
|
cursor.set_shift_held(true)
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"Insert-off must suppress interactions even with Shift held (insert-off > shift override)"
|
|
).is_false()
|
|
cursor.queue_free()
|
|
|
|
|
|
func test_insert_off_rapid_toggle_no_corruption() -> void:
|
|
# Rapid on/off toggle must not leave either system in inconsistent state.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if not cursor.has_method("set_insert_active"):
|
|
cursor.queue_free()
|
|
return
|
|
for _i in range(10):
|
|
cursor.set_insert_active(false)
|
|
cursor.set_insert_active(true)
|
|
# After 10 toggles, insert should be on
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"After rapid toggle ending on 'on', interactions must be visible"
|
|
).is_true()
|
|
cursor.queue_free()
|
|
|
|
|
|
# -- Cross-system consistency (cursor + list agree) ---------------------------
|
|
|
|
func test_cursor_and_list_agree_on_insert_off() -> void:
|
|
# Both cursor and interaction list must be suppressed when insert is off.
|
|
# This tests the integration contract — both must read from the same source of truth.
|
|
var cursor = _make_cursor_or_skip()
|
|
var list = _make_list_or_skip()
|
|
|
|
if cursor == null or list == null:
|
|
if cursor != null: cursor.queue_free()
|
|
if list != null: list.queue_free()
|
|
return
|
|
|
|
_set_test_interactions()
|
|
|
|
if cursor.has_method("set_insert_active") and list.has_method("set_insert_active"):
|
|
cursor.set_insert_active(false)
|
|
list.set_insert_active(false)
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
|
|
var cursor_suppressed := true
|
|
if cursor.has_method("should_show_interactions"):
|
|
cursor_suppressed = not cursor.should_show_interactions()
|
|
|
|
var list_suppressed := true
|
|
if list.has_method("is_showing"):
|
|
list_suppressed = not list.is_showing()
|
|
elif list.has_method("get_visible_verb_count"):
|
|
list_suppressed = list.get_visible_verb_count() == 0
|
|
|
|
assert_that(cursor_suppressed).override_failure_message(
|
|
"Cursor: should_show_interactions() must return false when insert is off"
|
|
).is_true()
|
|
assert_that(list_suppressed).override_failure_message(
|
|
"Interaction list: must be hidden when insert is off"
|
|
).is_true()
|
|
|
|
cursor.queue_free()
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
|
|
|
|
func test_game_state_insert_active_field_exists() -> void:
|
|
# GameState must have an insert_active field after #522 implementation.
|
|
assert_that(GameState.get("insert_active") != null or "insert_active" in GameState).override_failure_message(
|
|
"GameState must have insert_active field after #522 — awaiting implementation"
|
|
).is_true()
|
|
|
|
|
|
func test_game_state_insert_active_default_true() -> void:
|
|
# Default state: insert is on (player starts with a functioning neural insert).
|
|
if not ("insert_active" in GameState):
|
|
push_warning("TestInsertOffBehavior: GameState.insert_active not found — awaiting #522")
|
|
return
|
|
assert_that(GameState.insert_active).override_failure_message(
|
|
"GameState.insert_active should default to true (insert is normally on)"
|
|
).is_true()
|
|
|
|
|
|
# -- D-049: z-layer verification (labels on layer 6) -------------------------
|
|
|
|
func test_interaction_list_on_insert_layer() -> void:
|
|
# D-056/D-057: "Labels render on z-layer 6 (insert overlay)"
|
|
# This is already tested in test_interaction_list.gd but we verify here
|
|
# that the z-layer is CANVAS_INSERT (the insert overlay layer).
|
|
var list = _make_list_or_skip()
|
|
if list == null:
|
|
return
|
|
if list.has_method("get_z_layer"):
|
|
assert_that(list.get_z_layer()).override_failure_message(
|
|
"Interaction list labels must render on CANVAS_INSERT (z-layer 6 per D-049)"
|
|
).is_equal(Constants.CANVAS_INSERT)
|
|
list.queue_free()
|
|
|
|
|
|
# -- Regression: existing behavior unchanged ----------------------------------
|
|
|
|
func test_sprint_suppression_still_works_with_insert_on() -> void:
|
|
# D-055 regression: Sprint suppresses interaction list regardless of insert state.
|
|
var list = _make_list_or_skip()
|
|
if list == null:
|
|
return
|
|
_set_test_interactions()
|
|
# Ensure insert is on (should not affect sprint suppression)
|
|
if list.has_method("set_insert_active"):
|
|
list.set_insert_active(true)
|
|
GameState.player_stance = "Sprint"
|
|
if list.has_method("update_from_state"):
|
|
list.update_from_state()
|
|
if list.has_method("is_showing"):
|
|
assert_that(list.is_showing()).override_failure_message(
|
|
"Sprint must suppress interaction list even when insert is on (D-055 regression)"
|
|
).is_false()
|
|
elif list.has_method("get_visible_verb_count"):
|
|
assert_that(list.get_visible_verb_count()).is_equal(0)
|
|
list.queue_free()
|
|
_clear_test_interactions()
|
|
GameState.player_stance = "Walk"
|
|
|
|
|
|
func test_weapon_mode_suppression_still_works_with_insert_on() -> void:
|
|
# D-056 regression: weapon mode suppresses interactions when insert is on.
|
|
var cursor = _make_cursor_or_skip()
|
|
if cursor == null:
|
|
return
|
|
if cursor.has_method("set_insert_active"):
|
|
cursor.set_insert_active(true) # Insert is on
|
|
if cursor.has_method("set_weapon_mode"):
|
|
cursor.set_weapon_mode(true)
|
|
if cursor.has_method("should_show_interactions"):
|
|
assert_that(cursor.should_show_interactions()).override_failure_message(
|
|
"Weapon mode must suppress interactions when insert is on (D-056 regression)"
|
|
).is_false()
|
|
cursor.queue_free()
|