neon-sprawl/client/test/npc_combat_hud_refresh_test.gd

126 lines
4.3 KiB
GDScript

extends GdUnitTestSuite
## NEO-97: combat-active gate + HUD label copy from snapshot fixtures.
const NpcRuntimeClient := preload("res://scripts/npc_runtime_client.gd")
const PrototypeTargetConstants := preload("res://scripts/prototype_target_constants.gd")
class NpcCombatHudHarness:
extends RefCounted
var last_target_state: Dictionary = {}
var npc_runtime_snapshot: Dictionary = {}
var dev_player_id: String = "dev-local-1"
func is_combat_active() -> bool:
var lock_id := locked_target_id()
if PrototypeTargetConstants.ANCHORS.has(lock_id):
return true
return not incoming_aggro_row().is_empty()
func locked_target_id() -> String:
var locked_variant: Variant = last_target_state.get("lockedTargetId", null)
if locked_variant is String:
return (locked_variant as String).strip_edges()
return ""
func incoming_aggro_row() -> Dictionary:
if npc_runtime_snapshot.is_empty():
return {}
return NpcRuntimeClient.row_for_aggro_holder(dev_player_id, npc_runtime_snapshot)
func resolve_hud_npc_rows() -> Dictionary:
var lock_id := locked_target_id()
var locked_row: Dictionary = {}
if PrototypeTargetConstants.ANCHORS.has(lock_id):
locked_row = NpcRuntimeClient.npc_row(lock_id, npc_runtime_snapshot)
var incoming_row: Dictionary = {}
var aggro_row: Dictionary = incoming_aggro_row()
if not aggro_row.is_empty():
var aggro_id: String = str(aggro_row.get("npcInstanceId", "")).strip_edges()
if aggro_id != lock_id:
incoming_row = aggro_row
return {"locked": locked_row, "incoming": incoming_row}
func build_npc_state_label() -> String:
var rows: Dictionary = resolve_hud_npc_rows()
var lines: PackedStringArray = PackedStringArray()
var locked_row: Dictionary = rows.get("locked", {}) as Dictionary
if not locked_row.is_empty():
var locked_id: String = str(locked_row.get("npcInstanceId", ""))
lines.append(
"NPC state: %s%s"
% [
PrototypeTargetConstants.display_short_name(locked_id),
str(locked_row.get("state", "unknown")),
]
)
var incoming_row: Dictionary = rows.get("incoming", {}) as Dictionary
if not incoming_row.is_empty():
var incoming_id: String = str(incoming_row.get("npcInstanceId", ""))
lines.append(
"Incoming: %s%s"
% [
PrototypeTargetConstants.display_short_name(incoming_id),
str(incoming_row.get("state", "unknown")),
]
)
if lines.is_empty():
return "NPC state: —"
return "\n".join(lines)
static func _elite_aggro_snapshot_json() -> String:
return (
'{"schemaVersion":1,"serverTimeUtc":"2026-05-29T12:00:00Z","npcInstances":['
+ '{"npcInstanceId":"prototype_npc_elite","behaviorDefId":"prototype_elite_boss",'
+ '"state":"aggro","aggroHolderPlayerId":"dev-local-1","activeTelegraph":null},'
+ '{"npcInstanceId":"prototype_npc_melee","behaviorDefId":"prototype_melee_rush",'
+ '"state":"idle","aggroHolderPlayerId":null,"activeTelegraph":null}'
+ "]}"
)
func test_npc_lock_makes_combat_active_without_aggro_snapshot() -> void:
# Arrange
var harness := NpcCombatHudHarness.new()
harness.last_target_state = {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
# Act
var active: bool = harness.is_combat_active()
# Assert
assert_that(active).is_true()
func test_aggro_holder_makes_combat_active_without_lock() -> void:
# Arrange
var harness := NpcCombatHudHarness.new()
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
harness.npc_runtime_snapshot = parsed as Dictionary
# Act
var active: bool = harness.is_combat_active()
# Assert
assert_that(active).is_true()
func test_idle_without_lock_is_not_combat_active() -> void:
# Arrange
var harness := NpcCombatHudHarness.new()
harness.npc_runtime_snapshot = {}
# Act
var active: bool = harness.is_combat_active()
# Assert
assert_that(active).is_false()
func test_locked_plus_incoming_state_label() -> void:
# Arrange
var harness := NpcCombatHudHarness.new()
harness.last_target_state = {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
harness.npc_runtime_snapshot = parsed as Dictionary
# Act
var label: String = harness.build_npc_state_label()
# Assert
assert_that(label).contains("NPC state: Melee → idle")
assert_that(label).contains("Incoming: Elite → aggro")