extends SceneTree const EffectPlayer = preload("res://fx/effect_player.gd") var failures := 0 func check(ok: bool, label: String) -> void: if not ok: failures += 1 printerr("FAIL: " + label) func _init() -> void: var fx := EffectPlayer.new() # D3D8: SRCALPHA=5, INVSRCALPHA=6; both mesh and particle use this helper. check(fx._blend(5, 6) == BaseMaterial3D.BLEND_MODE_MIX, "source-alpha transparency") check(fx._blend(5, 2) == BaseMaterial3D.BLEND_MODE_ADD, "additive glow preserved") fx.build({"particles": [{"start_time": 0.5, "emitter": {"CycleLength": 1.0, "CycleLoopEnable": 1, "LoopCount": 2}, "particle": {"SrcBlendType": 5, "DestBlendType": 6}}]}) var state: Dictionary = fx._particle_states[0] var emitter: GPUParticles3D = state.node check(emitter.material_override.blend_mode == BaseMaterial3D.BLEND_MODE_MIX, "built particle uses alpha material") fx._advance_particle(state, 0.25) check(is_equal_approx(state.clock, 0.25) and not emitter.emitting, "stationary emitter advances clock and respects start delay") # Simulate the play timer starting emission; isolate cycle gating from timers. emitter.emitting = true fx._advance_particle(state, 2.0) check(emitter.emitting and not state.emission_stopped, "finite loop before end") fx._advance_particle(state, 0.25) check(not emitter.emitting and state.emission_stopped, "stationary finite loop ends") state.clock = 0.0 state.start_time = 0.0 state.cycle_loop = false state.emission_stopped = false emitter.emitting = true fx._advance_particle(state, 1.0) check(not emitter.emitting and state.emission_stopped, "stationary non-loop ends") state.clock = 0.0 state.cycle_loop = true state.loop_count = 0 state.emission_stopped = false emitter.emitting = true fx._advance_particle(state, 10.0) check(emitter.emitting and not state.emission_stopped, "unlimited loop preserved") check(emitter.position == Vector3.ZERO, "missing position stays at origin") fx.free() var path := AssetRoot.path().path_join("PC/ymir work/pc/shaman/effect/7budongbakbu_throw.mse") var parsed: Dictionary = preload("res://fx/mse.gd").new().parse_file(path) check(not parsed.get("particles", []).is_empty(), "real shaman effect loaded") var real_fx := EffectPlayer.new() real_fx.build(parsed, AssetRoot.path()) var alpha_count := 0 for i in parsed.get("particles", []).size(): var prop: Dictionary = parsed.particles[i].particle if int(prop.get("SrcBlendType", 0)) == 5 and int(prop.get("DestBlendType", 0)) == 6: alpha_count += 1 check(real_fx._emitters[i].material_override.blend_mode == BaseMaterial3D.BLEND_MODE_MIX, "real shaman alpha particle %d" % i) check(alpha_count > 0, "real fixture exercises alpha blending") real_fx.free() print("effect_render_regression_test: failures=%d" % failures) quit(1 if failures else 0)