Files
gamedev-the-steward/tests/npc_visual_lifecycle_test.gd
T

113 lines
3.5 KiB
GDScript

extends SceneTree
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var main_scene: Node = load("res://main.tscn").instantiate()
root.add_child(main_scene)
await process_frame
await physics_frame
await physics_frame
var manager: Node = main_scene.get_node("SimulationManager")
var view: Node = main_scene.get_node("WorldViewManager")
manager.set_process(false)
var npc: SimNPC = manager.npcs[0]
var visual: Node3D = view.active_npc_visuals[npc.id]
visual.set_physics_process(false)
var carried_food: MeshInstance3D = visual.get_node("CarriedFood")
_check(not carried_food.visible, "NPC should begin without a carried-food prop")
npc.add_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
manager.emit_signal(
"npc_inventory_changed",
npc,
SimulationIds.RESOURCE_FOOD,
1.0
)
_check(carried_food.visible, "Carried food should be visible on the NPC")
npc.remove_inventory(SimulationIds.RESOURCE_FOOD, 1.0)
manager.emit_signal(
"npc_inventory_changed",
npc,
SimulationIds.RESOURCE_FOOD,
0.0
)
_check(not carried_food.visible, "Deposited or eaten food should be hidden")
var resource_state: ResourceStateRecord = manager.get_resource_state(
&"berry_bush_01"
)
npc.set_task(SimulationIds.ACTION_GATHER_FOOD)
npc.target_id = resource_state.get_node_id()
npc.travel_target_position = Vector3(-6.0, 0.0, -8.0)
npc.has_travel_target = true
resource_state.reserve(npc.id)
manager.request_current_travel(npc.id)
var saved_action := npc.current_task
var saved_target_id := npc.target_id
var saved_target_position := npc.travel_target_position
var authoritative_position := Vector3(-2.5, 0.5, 1.75)
visual.global_position = authoritative_position
_check(view.despawn_npc_visual(npc.id), "Visual should unload")
await process_frame
_check(
npc.position.is_equal_approx(authoritative_position),
"Unload should synchronize the last active position"
)
_check(
npc.current_task == saved_action
and npc.target_id == saved_target_id
and npc.travel_target_position.is_equal_approx(saved_target_position),
"Unload should preserve action and target state"
)
_check(
resource_state.get_reserved_by() == npc.id,
"Unload should preserve the resource reservation"
)
var unloaded_checksum: String = manager.get_state_checksum()
_check(view.reload_npc_visual(npc.id), "Visual should reload")
var reloaded: Node3D = view.active_npc_visuals[npc.id]
reloaded.set_physics_process(false)
_check(
reloaded.global_position.is_equal_approx(authoritative_position),
"Reloaded visual should use authoritative position"
)
var resumed_target: Vector3 = reloaded.get("current_target")
_check(
resumed_target.is_equal_approx(saved_target_position),
"Reloaded visual should resume the persisted travel target"
)
_check(
manager.get_state_checksum() == unloaded_checksum,
"Loading a visual must not mutate simulation state"
)
_check(
resource_state.get_reserved_by() == npc.id,
"Reload should preserve the reservation"
)
var moved_position := authoritative_position + Vector3(0.5, 0.0, 0.25)
reloaded.emit_signal("position_changed", npc.id, moved_position)
_check(
npc.position.is_equal_approx(moved_position),
"Active position callback should synchronize simulation state"
)
if failures.is_empty():
print("[TEST] NPC visual lifecycle passed")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)