149 lines
5.4 KiB
GDScript
149 lines
5.4 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 NpcRuntimeHudState := preload("res://scripts/npc_runtime_hud_state.gd")
|
|
const NpcCombatHudHelpers := preload("res://scripts/npc_combat_hud_helpers.gd")
|
|
|
|
|
|
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}'
|
|
+ "]}"
|
|
)
|
|
|
|
|
|
static func _elite_telegraph_snapshot_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"serverTimeUtc":"2026-05-29T12:00:00Z","npcInstances":['
|
|
+ '{"npcInstanceId":"prototype_npc_elite","behaviorDefId":"prototype_elite_boss",'
|
|
+ '"state":"telegraph_windup","aggroHolderPlayerId":"dev-local-1",'
|
|
+ '"activeTelegraph":{"telegraphId":"prototype_elite_slam","npcInstanceId":'
|
|
+ '"prototype_npc_elite","windupRemainingSeconds":2.5,"archetypeKind":"elite"}},'
|
|
+ '{"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 target_state := {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
|
|
# Act
|
|
var active: bool = NpcCombatHudHelpers.is_combat_active(target_state, {}, "dev-local-1")
|
|
# Assert
|
|
assert_that(active).is_true()
|
|
|
|
|
|
func test_aggro_holder_makes_combat_active_without_lock() -> void:
|
|
# Arrange
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
|
|
# Act
|
|
var active: bool = NpcCombatHudHelpers.is_combat_active({}, parsed as Dictionary, "dev-local-1")
|
|
# Assert
|
|
assert_that(active).is_true()
|
|
|
|
|
|
func test_idle_without_lock_is_not_combat_active() -> void:
|
|
# Arrange
|
|
# Act
|
|
var active: bool = NpcCombatHudHelpers.is_combat_active({}, {}, "dev-local-1")
|
|
# Assert
|
|
assert_that(active).is_false()
|
|
|
|
|
|
func test_combat_poll_gate_stops_when_idle() -> void:
|
|
# Arrange
|
|
var timer := Timer.new()
|
|
timer.one_shot = false
|
|
add_child(timer)
|
|
timer.start()
|
|
# Act
|
|
var should_poll: bool = NpcCombatHudHelpers.is_combat_active({}, {}, "dev-local-1")
|
|
if not should_poll:
|
|
timer.stop()
|
|
# Assert
|
|
assert_that(should_poll).is_false()
|
|
assert_that(timer.is_stopped()).is_true()
|
|
|
|
|
|
func test_combat_poll_gate_starts_when_lock_held() -> void:
|
|
# Arrange
|
|
var timer := Timer.new()
|
|
timer.one_shot = false
|
|
add_child(timer)
|
|
var target_state := {"lockedTargetId": "prototype_npc_elite", "validity": "ok"}
|
|
# Act
|
|
var should_poll: bool = NpcCombatHudHelpers.is_combat_active(target_state, {}, "dev-local-1")
|
|
if should_poll and timer.is_stopped():
|
|
timer.start()
|
|
# Assert
|
|
assert_that(should_poll).is_true()
|
|
assert_that(timer.is_stopped()).is_false()
|
|
|
|
|
|
func test_locked_plus_incoming_state_label() -> void:
|
|
# Arrange
|
|
var target_state := {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
|
|
var rows: Dictionary = NpcCombatHudHelpers.resolve_hud_npc_rows(
|
|
target_state, parsed as Dictionary, "dev-local-1"
|
|
)
|
|
# Act
|
|
var label: String = NpcCombatHudHelpers.build_npc_state_label(rows)
|
|
# Assert
|
|
assert_that(label).contains("NPC state: Melee → idle")
|
|
assert_that(label).contains("Incoming: Elite → aggro")
|
|
|
|
|
|
func test_telegraph_label_falls_back_to_incoming_row() -> void:
|
|
# Arrange
|
|
var target_state := {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_telegraph_snapshot_json())
|
|
var rows: Dictionary = NpcCombatHudHelpers.resolve_hud_npc_rows(
|
|
target_state, parsed as Dictionary, "dev-local-1"
|
|
)
|
|
# Act
|
|
var label: String = NpcCombatHudHelpers.build_telegraph_label(rows)
|
|
# Assert
|
|
assert_that(label).contains("Telegraph: Elite prototype_elite_slam")
|
|
assert_that(label).contains("(elite)")
|
|
|
|
|
|
func test_telegraph_label_empty_when_local_windup_expired() -> void:
|
|
# Arrange
|
|
var short_windup_json := (
|
|
'{"schemaVersion":1,"serverTimeUtc":"2026-05-29T12:00:00Z","npcInstances":['
|
|
+ '{"npcInstanceId":"prototype_npc_elite","behaviorDefId":"prototype_elite_boss",'
|
|
+ '"state":"telegraph_windup","aggroHolderPlayerId":"dev-local-1",'
|
|
+ '"activeTelegraph":{"telegraphId":"prototype_elite_slam","npcInstanceId":'
|
|
+ '"prototype_npc_elite","windupRemainingSeconds":0.1,"archetypeKind":"elite"}}'
|
|
+ "]}"
|
|
)
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(short_windup_json)
|
|
var rows: Dictionary = NpcCombatHudHelpers.resolve_hud_npc_rows(
|
|
{"lockedTargetId": "prototype_npc_elite", "validity": "ok"},
|
|
parsed as Dictionary,
|
|
"dev-local-1"
|
|
)
|
|
var hud_state: RefCounted = NpcRuntimeHudState.new()
|
|
hud_state.call("apply_snapshot", parsed as Dictionary)
|
|
await (Engine.get_main_loop() as SceneTree).create_timer(0.15).timeout
|
|
# Act
|
|
var label: String = NpcCombatHudHelpers.build_telegraph_label(rows, hud_state)
|
|
# Assert
|
|
assert_that(label).is_equal("Telegraph: —")
|
|
|
|
|
|
func test_runtime_sync_error_surfaces_on_empty_npc_state_label() -> void:
|
|
# Arrange
|
|
# Act
|
|
var label: String = NpcCombatHudHelpers.build_npc_state_label({}, "HTTP 500")
|
|
# Assert
|
|
assert_that(label).is_equal("NPC state: — (HTTP 500)")
|