extends SceneTree ## Verifica il rig del fuoco: rumore continuo, assenza di strobing, coerenza fra le ## luci e modulazione della fiamma. Il campione di rumore e' puro, quindi il test ## simula 60 s a 60 Hz senza dipendere dal frame rate reale. const FireLight = preload("res://scripts/fire_light.gd") func _initialize() -> void: call_deferred("run_test") func run_test() -> void: var world = load("res://scenes/main.tscn").instantiate() root.add_child(world) var rig = world.get_node("OfficeAtmosphere").fire_rig var main_light: OmniLight3D = rig.main_light var bounce: OmniLight3D = rig.bounce_light var core: OmniLight3D = rig.core_light # --- limiti, variazione e assenza di strobing ----------------------------- var previous := -1.0 var maximum_step := 0.0 var minimum := INF var maximum := -INF var total := 0.0 var squares := 0.0 var samples := 0 rig._pulse = 0.0 for i in 3600: # 60 s a 60 Hz rig._process(1.0 / 60.0) var energy := main_light.light_energy minimum = minf(minimum, energy) maximum = maxf(maximum, energy) total += energy squares += energy * energy samples += 1 if previous >= 0.0: maximum_step = maxf(maximum_step, absf(energy - previous)) previous = energy var mean := total / samples var deviation := sqrt(squares / samples - mean * mean) assert(minimum >= rig.MIN_ENERGY and maximum <= rig.MAX_ENERGY) assert(deviation > 0.05) # non e' una luce fissa assert(maximum_step < 0.05) # nessun salto: niente strobing assert(maximum - minimum > 0.4) # la gamma viene usata davvero print("PASS: flicker entro [%.2f, %.2f], deviazione %.3f, salto massimo per frame %.3f" % [minimum, maximum, deviation, maximum_step]) # --- coerenza fra le tre luci --------------------------------------------- assert(bounce.light_energy > 0.0 and core.light_energy > 0.0) assert(bounce.light_energy < main_light.light_energy) assert(core.omni_range < main_light.omni_range) assert(is_equal_approx(main_light.omni_range, rig.BASE_RANGE)) # raggio mai animato rig._pulse = 1.0 rig._apply() var bright := [main_light.light_energy, bounce.light_energy, core.light_energy] rig._pulse = -1.0 rig._apply() var dim := [main_light.light_energy, bounce.light_energy, core.light_energy] for i in 3: assert(bright[i] > dim[i]) print("PASS: principale, rimbalzo e nucleo pulsano insieme e il raggio resta fisso") # --- ricetta ufficiale del rimbalzo e unicita' delle ombre ---------------- assert(not bounce.shadow_enabled and not core.shadow_enabled) assert(bounce.light_specular == 0.0 and core.light_specular == 0.0) assert(is_equal_approx(bounce.omni_range, rig.BASE_RANGE * rig.BOUNCE_RANGE_FACTOR)) assert(bounce.get_parent() == main_light and core.get_parent() == main_light) assert(main_light.omni_shadow_mode == OmniLight3D.SHADOW_CUBE) # il dual paraboloid non esiste in Compatibility assert(main_light.shadow_enabled and not bounce.shadow_enabled) print("PASS: rimbalzo figlio senza ombre ne' specular, raggio +40%, ombre solo sulla principale") # --- micro-scarto e fiamma ------------------------------------------------ var maximum_offset := 0.0 var flame_heights: Array[float] = [] rig._time = 0.0 for i in 600: rig._process(1.0 / 60.0) maximum_offset = maxf(maximum_offset, Vector2(main_light.position.x, main_light.position.z).length()) flame_heights.append(rig._flame_mesh.size.y) assert(maximum_offset <= rig.JITTER * 1.45) # scarto piccolo e limitato assert(maximum_offset > 0.005) # le ombre si muovono davvero assert(flame_heights.max() > flame_heights.min()) assert(flame_heights.max() < rig._flame_height * 1.2) print("PASS: scarto massimo %.3f m, altezza fiamma da %.2f a %.2f" % [maximum_offset, flame_heights.min(), flame_heights.max()]) # --- braci e determinismo ------------------------------------------------- assert(rig.embers is CPUParticles3D and rig.embers.amount == 8 and rig.embers.emitting) assert(rig.embers.mesh.material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED) var same := Node3D.new() same.set_script(FireLight) world.add_child(same) var different := false for i in 200: var t := 0.01 * i if not is_equal_approx(rig.pulse_at(t), same.pulse_at(t)): different = true assert(not different) # stesso seed -> stessa sequenza, test riproducibili print("PASS: braci presenti e flicker deterministico con seed fisso") world.queue_free() await process_frame quit()