///- --package .

cooldowns (test)

test cooldowns: prelude
///- --test static --symbol

:< testing
:| spell_group =
    | AttackGroup
    | HealingGroup
    | SupportGroup

:| spell_id =
    | StrikeSpell
    | LightHealingSpell
    | WoundCleansingSpell
    | HasteSpell

:== spell = { id : spell_id; group : spell_group; own_ms : i32; group_ms : i32 }

:== timers =
    {
        attack_ready_at : i32
        healing_ready_at : i32
        support_ready_at : i32
        strike_ready_at : i32
        light_healing_ready_at : i32
        wound_cleansing_ready_at : i32
        haste_ready_at : i32
    }

:= idle_timers () : timers =
    {
        attack_ready_at = 0
        healing_ready_at = 0
        support_ready_at = 0
        strike_ready_at = 0
        light_healing_ready_at = 0
        wound_cleansing_ready_at = 0
        haste_ready_at = 0
    }

:= group_ready_at (group : spell_group) (held : timers) : i32 =
    ?= group ?|
    | AttackGroup => held::attack_ready_at
    | HealingGroup => held::healing_ready_at
    | SupportGroup => held::support_ready_at

:= own_ready_at (id : spell_id) (held : timers) : i32 =
    ?= id ?|
    | StrikeSpell => held::strike_ready_at
    | LightHealingSpell => held::light_healing_ready_at
    | WoundCleansingSpell => held::wound_cleansing_ready_at
    | HasteSpell => held::haste_ready_at

:= with_strike (at : i32) (t : timers) : timers = { t ?| strike_ready_at = at }

:= with_light_healing (at : i32) (t : timers) : timers = { t ?| light_healing_ready_at = at }

:= with_wound_cleansing (at : i32) (t : timers) : timers = { t ?| wound_cleansing_ready_at = at }

:= with_haste (at : i32) (t : timers) : timers = { t ?| haste_ready_at = at }

:= with_own_ready_at (id : spell_id) (at : i32) (t : timers) : timers =
    ?= id ?|
    | StrikeSpell => with_strike at t
    | LightHealingSpell => with_light_healing at t
    | WoundCleansingSpell => with_wound_cleansing at t
    | HasteSpell => with_haste at t

:= with_group_ready_at (group : spell_group) (at : i32) (t : timers) : timers =
    ?= group ?|
    | AttackGroup => { t ?| attack_ready_at = at }
    | HealingGroup => { t ?| healing_ready_at = at }
    | SupportGroup => { t ?| support_ready_at = at }

:= later (a : i32) (b : i32) : i32 =
    ? a < b ?> b |: a

:= ready_at (cast : spell) (held : timers) : i32 =
    later (group_ready_at cast::group held) (own_ready_at cast::id held)

:= can_cast (now : i32) (cast : spell) (held : timers) : bool =
    ready_at cast held <= now

:= after_cast (now : i32) (cast : spell) (held : timers) : timers =
    := own_until = later (own_ready_at cast::id held) (now + cast::own_ms)
    with_own_ready_at cast::id own_until held |> with_group_ready_at cast::group (now + cast::group_ms)

:= strike_spell () : spell = { id = StrikeSpell; group = AttackGroup; own_ms = 2000; group_ms = 2000 }

:= light_healing () : spell = { id = LightHealingSpell; group = HealingGroup; own_ms = 1000; group_ms = 1000 }

:= haste () : spell = { id = HasteSpell; group = SupportGroup; own_ms = 2000; group_ms = 2000 }

:= after_strike_at (now : i32) : timers = idle_timers () |> after_cast now (strike_spell ())

fresh_timers_allow_any_spell (test)

test fresh_timers_allow_any_spell: passed
///- --test --symbol
///> c

:= ~now = 0i32
can_cast now (strike_spell ()) (idle_timers ()) |> _assert_eq !+
__assert_eq { expected = true }

attack_blocks_its_group_until_it_expires (test)

test attack_blocks_its_group_until_it_expires: passed
///- --test --symbol
///> c

:= ~now = 1000i32
:= h = idle_timers () |> after_cast now (strike_spell ())
can_cast (now + 1999) (strike_spell ()) h |> _assert_eq !-
can_cast (now + 2000) (strike_spell ()) h |> _assert_eq !+
__assert_eq { expected = false }
__assert_eq { expected = true }

attack_does_not_block_healing_group (test)

test attack_does_not_block_healing_group: passed
///- --test --symbol
///> c

:= ~now = 1000i32
:= held = idle_timers () |> after_cast now (strike_spell ())
group_ready_at HealingGroup held |> _assert_eq 0i32
__assert_eq { expected = 0 }

own_cooldown_gates_only_the_same_spell_like_canary (test)

test own_cooldown_gates_only_the_same_spell_like_canary: passed
///- --test --symbol
///> c

:= ~now = 1000i32
:= held = after_strike_at now
can_cast (now + 500) (light_healing ()) held |> _assert_eq !+
can_cast (now + 500) (strike_spell ()) held |> _assert_eq !-
__assert_eq { expected = true }
__assert_eq { expected = false }

the_own_cooldown_ends_with_the_group_cooldown (test)

test the_own_cooldown_ends_with_the_group_cooldown: passed
///- --test --symbol
///> c

:= ~now = 1000i32
can_cast (now + 2000) (strike_spell ()) (after_strike_at now) |> _assert_eq !+
__assert_eq { expected = true }

support_sets_only_support_group (test)

test support_sets_only_support_group: passed
///- --test --symbol
///> c

:= ~now = 0i32
:= held = idle_timers () |> after_cast now (haste ())
held::support_ready_at |> _assert_eq 2000i32
held::attack_ready_at |> _assert_eq 0i32
__assert_eq { expected = 2000 }
__assert_eq { expected = 0 }

attack_cooldown_on_gpu (test)

test attack_cooldown_on_gpu: passed
///- --test --symbol
///> wgsl

:= ~now = 1000i32
:= held, strike = after_strike_at now, strike_spell ()
? can_cast (now + 1999) strike held = !- && can_cast (now + 2000) strike held && held::healing_ready_at = 0i32
?> 0i32
|: 1i32

healing_right_after_a_strike_is_allowed_like_canary (test)

test healing_right_after_a_strike_is_allowed_like_canary: passed
///- --test --symbol
///> c

:= ~now = 1000i32
:= held = after_cast now (strike_spell ()) (idle_timers ())
can_cast now (light_healing ()) held |> _assert_eq !+
can_cast now (strike_spell ()) held |> _assert_eq !-
__assert_eq { expected = true }
__assert_eq { expected = false }