ajhahn.de
← Theria
GDScript 73 lines
extends GutTest
## The cast-FX log the simulation leaves for the renderer. A resolved cast appends one
## entry to `SimState.fx_events` carrying enough geometry to draw it — origin, landing
## point, area radius, and the cast's kind/effect/status — and `step` clears the log at the
## top of every tick, so a flash draws exactly once. The log is presentation-only: it
## never feeds back into the simulation and never crosses the wire. Headless, deterministic.

const SPIRIT_BOLT_ID := 1  # wildkin human SKILLSHOT, range 600 / radius 60
const MEND_ID := 2  # wildkin human SELF heal
const WEB_NEST_ID := 54  # spider animal GROUND stun, range 340 / radius 220


func _hero(sim: SimCore, kit_id: String, pos: Vector2) -> int:
	var id := sim.add_hero(0, pos, 300.0)
	sim.equip_kit(id, kit_id)
	return id


func test_a_skillshot_cast_records_a_beam_to_its_landing() -> void:
	var sim := SimCore.new()
	sim.spawn_creeps = false
	var id := _hero(sim, "wildkin", Vector2.ZERO)
	var spec := AbilityData.spec(SPIRIT_BOLT_ID)
	var cmd := InputCommand.new()
	cmd.target_point = Vector2(100.0, 0.0)  # aim +x — the bolt flies the full range along it
	AbilityExecutor.execute(sim.state, sim.state.get_entity(id), spec, cmd)
	assert_eq(sim.state.fx_events.size(), 1, "the cast is recorded once")
	var fx: Dictionary = sim.state.fx_events[0]
	assert_eq(fx["kind"], AbilitySpec.TARGET_SKILLSHOT, "drawn as a skillshot beam")
	assert_eq(fx["effect"], AbilitySpec.EFFECT_DAMAGE)
	assert_eq(fx["origin"], Vector2.ZERO, "from the caster")
	assert_eq(fx["point"], Vector2(600.0, 0.0), "to the skillshot's full-range landing")


func test_a_ground_stun_cast_records_its_zone_at_true_radius() -> void:
	var sim := SimCore.new()
	sim.spawn_creeps = false
	var id := _hero(sim, "spider", Vector2.ZERO)
	var spec := AbilityData.spec(WEB_NEST_ID)
	var cmd := InputCommand.new()
	cmd.target_point = Vector2(300.0, 0.0)  # inside the 340 range, so it lands on the point
	AbilityExecutor.execute(sim.state, sim.state.get_entity(id), spec, cmd)
	var fx: Dictionary = sim.state.fx_events[0]
	assert_eq(fx["kind"], AbilitySpec.TARGET_GROUND, "drawn as a ground area")
	assert_eq(fx["status"], AbilitySpec.STATUS_STUN, "carries its stun so it reads as control")
	assert_eq(fx["radius"], spec.radius, "the zone is drawn at the ability's true radius")
	assert_eq(fx["point"], Vector2(300.0, 0.0), "centred where the area landed")


func test_a_self_cast_records_a_pulse_on_the_caster() -> void:
	var sim := SimCore.new()
	sim.spawn_creeps = false
	var id := _hero(sim, "wildkin", Vector2(50.0, 0.0))
	var spec := AbilityData.spec(MEND_ID)
	AbilityExecutor.execute(sim.state, sim.state.get_entity(id), spec, InputCommand.new())
	var fx: Dictionary = sim.state.fx_events[0]
	assert_eq(fx["kind"], AbilitySpec.TARGET_SELF, "drawn as a pulse, not a beam")
	assert_eq(fx["effect"], AbilitySpec.EFFECT_HEAL)
	assert_eq(fx["point"], Vector2(50.0, 0.0), "on the caster itself")


func test_step_clears_the_previous_tick_fx_so_a_flash_draws_once() -> void:
	var sim := SimCore.new()
	sim.spawn_creeps = false
	var id := _hero(sim, "wildkin", Vector2.ZERO)
	var cmd := InputCommand.new()
	cmd.ability_slot = 0  # Spirit Bolt
	cmd.target_point = Vector2(100.0, 0.0)
	sim.step({id: cmd})
	assert_eq(sim.state.fx_events.size(), 1, "the cast tick records its flash")
	sim.step({})  # a tick with no cast
	assert_eq(sim.state.fx_events.size(), 0, "the next tick clears it, so the flash draws once")