extends SceneTree ## NPC performance budget smoke: put the documented live-NPC count in the scene, ## let the animations run, then report the average frame time. The budget is ## 50 ms (20 FPS) so slow CI hardware does not fail on noise; the report is the ## useful part when tuning the rig. ## ## Usage: godot --headless --path . --script res://src/test/smoke_npc_budget.gd const NpcAvatar := preload("res://src/game/npc_avatar.gd") const NpcProfile := preload("res://src/core/npc_profile.gd") const Rules := preload("res://src/core/rules.gd") const COUNT := 16 const WARMUP_FRAMES := 60 const SAMPLE_FRAMES := 180 const BUDGET_MS := 50.0 var frame := 0 var avatars: Array = [] var sample_ms := 0.0 func _initialize() -> void: root.size = Vector2i(1280, 720) var stage := Node3D.new() stage.name = "BudgetStage" root.add_child(stage) for index in COUNT: var profile := NpcProfile.new() profile.id = "budget_%d" % index profile.npc_type = Rules.NpcType.TOURIST profile.display_name = "Budget %d" % index var avatar := NpcAvatar.new() avatar.setup(profile, "roam", index + 1, Vector3(-6.0 + (index % 8) * 1.7, 0.5, 4.0 + (index / 8) * 2.0), Vector3(0, 0.5, 8.0), Vector3(0, 0.5, 1.2), profile.id) stage.add_child(avatar) avatars.append(avatar) func _process(delta: float) -> bool: frame += 1 if frame > WARMUP_FRAMES: sample_ms += delta * 1000.0 if frame >= WARMUP_FRAMES + SAMPLE_FRAMES: var average := sample_ms / SAMPLE_FRAMES var meshes := 0 for avatar in avatars: meshes += avatar.find_children("*", "MeshInstance3D", true, false).size() print("NPC budget: %d avatars, %d meshes, avg frame %.2f ms (budget %.0f ms)" % [ COUNT, meshes, average, BUDGET_MS]) if average > BUDGET_MS: printerr("SMOKE FAIL: frame time %.1f ms exceeds the %.0f ms budget" % [ average, BUDGET_MS]) quit(1) return true print("SMOKE OK: %d avatars stay inside the frame budget" % COUNT) quit(0) return true return false