///- --package . --timeout 1800000

pack_sim (test)

test pack_sim: prelude
///- --test static --symbol

:< testing
:== tally3 = i32 * i32 * i32

:== camp = hunting_pack::camp

:== tactic = hunting_pack::tactic

:== pack = hunting_pack::pack

:== hit_tally = { landed : i32; total : i32; high : i32 }

:= tallied_hit (t : hit_tally) (h : i32) : hit_tally =
    { landed = (? h > 0 ?> t::landed + 1 |: t::landed); total = t::total + h; high = (? h > t::high ?> h |: t::high) }

:= cyclops_melee_max () : i32 = (creatures::stats_of (:< creatures ;; Cyclops))::melee_max

:= cyclops_hit (seed : u32) (i : i32) : i32 =
    := roll = hunting_pack::key seed i 0 hunting_pack::PackCreatureHit
    combat::creature_hit (cyclops_melee_max ()) (combat::knight_sample_gear ()) !+ roll

:~ ^ cyclops_hits_from (seed : u32) (i : i32) (t : hit_tally) : hit_tally =
    ? i >= 2000 ?> t |: cyclops_hits_from seed (i + 1) (tallied_hit t (cyclops_hit seed i))

:= hit_facts (seed : u32) : i32 * i32 * i32 * i32 =
    := gear = combat::knight_sample_gear ()
    := t = cyclops_hits_from seed 0 { landed = 0; total = 0; high = 0 }
    gear::armor, gear::defense, t::total * 100 / t::landed, t::high

cyclops_hits_on_the_knight_sample_match_the_live_measurement (test)

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

:= ~seed = 3u32
hit_facts seed |> _assert_eq (17i32, 15i32, 3497i32, 86i32)
__assert_eq { expected = (17, 15, 3497, 86) }
:= troll_camp () : camp = hunting_pack::uniform_camp (:< creatures ;; Troll)

:= cyclops_camp () : camp = hunting_pack::uniform_camp (:< creatures ;; Cyclops)

:= reach_cavebot () : tactic = hunting_pack::cavebot (:< policy ;; ReachFirst)

golden_seed_7_troll_pack (test)

test golden_seed_7_troll_pack: passed
///- --test --symbol
///> rust
///> c

:= ~seed = 7u32
:= score = (hunting_pack::simulate (troll_camp ()) (reach_cavebot ()) seed (hunting_pack::scenario_ticks ()))::score
(score::kills, score::escapes, score::deaths) |> _assert_eq (6i32, 18i32, 0i32)
.rs (rust)
__assert_eq { expected = (6, 18, 0) }
.c (c)
__assert_eq { expected = (6, 18, 0) }
0
:= current_cavebot () : tactic = hunting_pack::cavebot (:< policy ;; CurrentFirst)

:= targeting_totals (first : u32) : tally3 * tally3 =
    := totals_of t = hunting_pack::totals (troll_camp ()) t first 20
    totals_of (reach_cavebot ()), totals_of (current_cavebot ())

:= finishes_fleeing (reach : tally3) (current : tally3) : bool =
    := reach_kills, reach_escapes, _ = reach
    := current_kills, current_escapes, _ = current
    current_kills > reach_kills && current_escapes < reach_escapes

current_first_targeting_finishes_fleeing_trolls_over_twenty_seeds (test)

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

:= ~first = 1u32
:= reach, current = targeting_totals first
((reach, current), finishes_fleeing reach current) |> _assert_eq (((71, 393, 0), (303, 20, 0)), !+)
__assert_eq { expected = (((71, 393, 0), 303, 20, 0), true) }
:= catalog_camp () : camp =
    :< catalog
    := entry kind = pack_entry (spawn_of kind 1 2)
    hunting_pack::camp_of_entries (entry TrollKind, entry GoblinKind, entry TrollKind)

mixed_camp_from_the_catalog_over_twenty_seeds (test)

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

:= ~first = 1u32
hunting_pack::totals (catalog_camp ()) (current_cavebot ()) first 20 |> _assert_eq (340, 1, 0)
__assert_eq { expected = (340, 1, 0) }

a_strike_needs_a_readiness_proof (test)

test a_strike_needs_a_readiness_proof: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
:< creatures
player_strikes (uniform_camp Troll) 7 0 (quantity::ms 0) (start (uniform_camp Troll))
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:81:46 (main.spi:51:46): ETY-TermError: Unification failure. Got:      amount milliseconds Expected: ready attack_group In the definition: spiral_cells_test.

a_pack_with_millisecond_health_is_rejected (test)

test a_pack_with_millisecond_health_is_rejected: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
:< creatures
:= p = start (uniform_camp Troll)
respawn_player (uniform_camp Troll) { p ?| player_health = quantity::ms 150 }
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:88:41 (main.spi:52:41): ETY-TermError: Unification failure. Got:      {hunter : {at : {x : i32; y : i32; z : i32}; health : amount hitpoints; next_step_at : i32; target : i32; clock : {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}; block_charges : i32; next_charge_at : i32; next_action_at : amount milliseconds; next_decision_at : i32}; tally : {potions : i32; hits_taken : i32; damage_taken : amount hitpoints; retreats : i32; cornered : i32}; waypoint : i32; call_sign : i32}; ally : {at : {x : i32; y : i32; z : i32}; health : amount hitpoints; next_step_at : i32; target : i32; clock : {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}; block_charges : i32; next_charge_at : i32; next_action_at : amount milliseconds; next_decision_at : i32}; tally : {potions : i32; hits_taken : i32; damage_taken : amount hitpoints; retreats : i32; cornered : i32}; waypoint : i32; call_sign : i32}; ally_in_world : bool; foes : band {at : {x : i32; y : i32; z : i32}; spawn : {x : i32; y : i32; z : i32}; life : vitality alive; strikes_at : i32; next_step_at : i32; chase : {met_target : bool; nearby : i32; acquired_at : i32}; respawn : {returns_at : i32; check_until : i32}; aim : {aims_at : i32; entrant : i32; in_view : i32; damage_first : i32; damage_second : i32; retarget_at : i32; last_x : i32; last_y : i32}}; score : {kills : i32; experience : i32; escapes : i32; deaths : i32; gold : amount gold_coins}; player_health : amount milliseconds} Expected: {hunter : {at : {x : i32; y : i32; z : i32}; health : amount hitpoints; next_step_at : i32; target : i32; clock : {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}; block_charges : i32; next_charge_at : i32; next_action_at : amount milliseconds; next_decision_at : i32}; tally : {potions : i32; hits_taken : i32; damage_taken : amount hitpoints; retreats : i32; cornered : i32}; waypoint : i32; call_sign : i32}; ally : {at : {x : i32; y : i32; z : i32}; health : amount hitpoints; next_step_at : i32; target : i32; clock : {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}; block_charges : i32; next_charge_at : i32; next_action_at : amount milliseconds; next_decision_at : i32}; tally : {potions : i32; hits_taken : i32; damage_taken : amount hitpoints; retreats : i32; cornered : i32}; waypoint : i32; call_sign : i32}; ally_in_world : bool; foes : band {at : {x : i32; y : i32; z : i32}; spawn : {x : i32; y : i32; z : i32}; life : vitality alive; strikes_at : i32; next_step_at : i32; chase : {met_target : bool; nearby : i32; acquired_at : i32}; respawn : {returns_at : i32; check_until : i32}; aim : {aims_at : i32; entrant : i32; in_view : i32; damage_first : i32; damage_second : i32; retarget_at : i32; last_x : i32; last_y : i32}}; score : {kills : i32; experience : i32; escapes : i32; deaths : i32; gold : amount gold_coins}} In the definition: spiral_cells_test.
:= heal_deaths (first : u32) (heal : i32) : i32 =
    := _, _, deaths = hunting_pack::totals (cyclops_camp ()) (hunting_pack::cavebot_with_heal heal) first 100
    deaths

:= heal_score (first : u32) (heal : i32) : i32 =
    hunting_pack::score (cyclops_camp ()) (hunting_pack::cavebot_with_heal heal) first 100

cyclops_pack_healing_below_130_dies_while_170_and_above_tie_on_kills (test)

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

:= ~first = 1u32
:= deaths, at = heal_deaths first, heal_score first
(deaths 90, deaths 170, at 90, at 130, at 170, at 210) |> _assert_eq (127, 0, -5399, -247, 950, 950)
__assert_eq { expected = (127, 0, -5399, -247, 950, 950) }
:== gold_objective = hunting_pack::objective quantity::gold_coins

:== kp = i32 * i32

:== table5 = kp * kp * kp * kp * kp

:== six = i32 * i32 * i32 * i32 * i32 * i32

:= held_out_pair (camp : camp) (t : tactic) : kp =
    := l = hunting_pack::ledger_on camp t (hunting_pack::held_out_block ())
    l::hunt_kills, units::value_of l::hunt_profit

:= profit_floor (camp : camp) (heal : i32) : i32 =
    (hunting_pack::ledger_on camp (hunting_pack::cavebot_with_heal heal) (hunting_pack::training_block ()))::hunt_kills

:= trained (o : gold_objective) (camp : camp) (t : tactic) : i32 =
    hunting_pack::worth_on_training o camp t (hunting_pack::training_block ())

:= kiting_on (camp : camp) (heal : i32) (n : i32) : tactic =
    hunting_pack::network_tactic (hunting_pack::cavebot_network camp heal |> network::with_kiting n)

:= best_kiting (o : gold_objective) (camp : camp) (heal : i32) : i32 =
    := k n = trained o camp (kiting_on camp heal n)
    := k1, k2, k3 = k 1, k 2, k 3
    ? k1 >= k2 && k1 >= k3 ?> 1 |? k2 >= k3 ?> 2 |: 3

:= network_evolved (o : gold_objective) (camp : camp) (seed : u32) (spread : i32) (start : network::weights) =
    := s = { seed = seed; generations = 6; offspring = 6; spread = spread }
    network::evolve (\ w => trained o camp (hunting_pack::network_tactic w)) s start

:= tuned_heal (camp : camp) : i32 =
    hunting_pack::cavebot_heal_tuned_on (:< hunting_pack ;; Profit) camp (hunting_pack::training_block ())

:== warm_base = { camp : camp; heal : i32; floor : i32; o : gold_objective; chosen : i32 }

:= warm_base_of () : warm_base =
    := c = cyclops_camp ()
    := heal = tuned_heal c
    := floor = profit_floor c heal
    := o = (:< hunting_pack ;; ProfitKeepingKills) floor
    { camp = c; heal = heal; floor = floor; o = o; chosen = best_kiting o c heal }

:= six_by_six (seed : u32) : hunting_pack::search_plan = { seed = seed; generations = 6; offspring = 6 }

:= rules_genome (b : warm_base) (search : u32) : search::genome =
    := start = { first = b::heal; second = 0; third = 4 }
    hunting_pack::tactic_evolved_on b::o start b::camp (hunting_pack::training_block ()) (six_by_six search)

:= cold_net (b : warm_base) (search : u32) : network::weights =
    network_evolved b::o b::camp (search + 2u32) 256 (network::zero_weights ())

:= warm_net (b : warm_base) (search : u32) : network::weights =
    := start = hunting_pack::cavebot_network b::camp b::heal |> network::with_kiting b::chosen
    network_evolved b::o b::camp (search + 4u32) 64 start

:== warm_nets = { g : search::genome; cold : network::weights; warm : network::weights }

:= warm_nets_of (b : warm_base) (search : u32) : warm_nets =
    { g = rules_genome b search; cold = cold_net b search; warm = warm_net b search }

:= baseline_rows (b : warm_base) (n : warm_nets) : kp * kp * kp =
    := held = held_out_pair b::camp
    := cave = held (hunting_pack::cavebot_with_heal b::heal)
    cave, held (hunting_pack::tactic_of n::g), held (kiting_on b::camp b::heal b::chosen)

:= warm_rows (b : warm_base) (n : warm_nets) : table5 =
    := cave, rules, kited = baseline_rows b n
    := held t = held_out_pair b::camp (hunting_pack::network_tactic t)
    cave, rules, kited, held n::cold, held n::warm

:= warm_experiment (search : u32) : six * table5 =
    := b = warm_base_of ()
    := n = warm_nets_of b search
    (b::heal, b::floor, b::chosen, n::g::first, n::g::second, n::g::third), warm_rows b n

:= dominates (a : kp) (b : kp) : bool =
    := k, p = a
    := bk, bp = b
    k >= bk && p >= bp && (k > bk || p > bp)

:= dominance_of (table : table5) : bool * bool * bool * bool =
    := cavebot, rules, kited, cold, warm = table
    dominates kited cavebot, dominates rules cavebot, dominates cold cavebot, dominates warm cavebot

:= warm_network_expected () : six * table5 =
    (160, 950, 1, 160, 0, 4), ((955, -454093), (955, -454093), (300, -31134), (955, -1480893), (955, -452543))

cyclops_pack_only_the_warm_started_network_dominates_the_tuned_cavebot_by_a_hair (test)

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

:= ~search = 11u32
:= tuned, table = warm_experiment search
((tuned, table), dominance_of table) |> _assert_eq (warm_network_expected (), (!-, !-, !-, !+))
__assert_eq { expected = (((160, 950, 1, 160, 0, 4), (955, -454093), (955, -454093), (300, -31134), (955, -1480893), 955, -452543), false, false, false, true) }
:== tally5 = i32 * i32 * i32 * i32 * i32

:= clear_tallied (acc : tally5) (dt : i32) (p : pack) : tally5 =
    := ms, potions, deaths, hits, taken = acc
    := t, s = p::hunter::tally, p::score
    ms + dt, potions + t::potions, deaths + s::deaths, hits + t::hits_taken, taken + units::value_of t::damage_taken

:== seeds = hunting_pack::seed_range

:~ ^ clear_sums (camp : camp) (t : tactic) (r : seeds) (i : i32) (acc : tally5) : tally5 =
    ? i >= r::count ?> acc
    |:
        := elapsed, p = hunting_pack::clear_run camp t (r::first + conv i) 6000
        clear_sums camp t r (i + 1) (clear_tallied acc elapsed p)

:= sums_over (camp : camp) (t : tactic) (r : seeds) : tally5 = clear_sums camp t r 0 (0, 0, 0, 0, 0)

:= twenty_from (first : u32) : seeds = { first = first; count = 20 }

:= clear_averages (camp : camp) (t : tactic) (first : u32) : tally3 =
    := ms, potions, deaths, _, _ = sums_over camp t (twenty_from first)
    ms / 20, potions * 10 / 20, deaths

:= clear_pair (camp : camp) (a : tactic) (b : tactic) (first : u32) : tally3 * tally3 =
    clear_averages camp a first, clear_averages camp b first

:= open_field_expected () : tally3 * tally3 = (84740, 140, 10), (137580, 141, 7)

open_field_clear_predictions_with_kiting_losing_the_monsters (test)

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

:= ~first = 1u32
:= c = cyclops_camp ()
clear_pair c (hunting_pack::cavebot_with_heal 70) (kiting_on c 70 3) first |> _assert_eq (open_field_expected ())
__assert_eq { expected = ((84740, 140, 10), 137580, 141, 7) }

a_knight_slain_by_a_hit_cannot_drink_in_the_same_tick (test)

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

:< hunting_pack
:< creatures
:= ~s = 1u32
:= p = simulate (uniform_camp Cyclops) (cavebot_with_heal 0) s 600
(p::hunter::tally::potions, p::score::deaths) |> _assert_eq (0i32, 4i32)
__assert_eq { expected = (0, 4) }
:= corridor_expected () : tally3 * tally3 = (85915, 153, 3), (85000, 65, 0)

:= fewer_potions_and_deaths (a : tally3) (b : tally3) : bool * bool =
    := _, a_potions, a_deaths = a
    := _, b_potions, b_deaths = b
    b_potions < a_potions, b_deaths < a_deaths

:= corridor_clears (first : u32) : tally3 * tally3 =
    := t = hunting_pack::cavebot_with_heal 100
    := corridor = cyclops_camp () |> hunting_pack::on_layout (:< terrain ;; EastCorridor)
    clear_averages (cyclops_camp ()) t first, clear_averages corridor t first

a_corridor_cuts_potions_and_deaths_in_a_cyclops_clear (test)

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

:= ~first = 1u32
:= opened, walled = corridor_clears first
((opened, walled), fewer_potions_and_deaths opened walled) |> _assert_eq (corridor_expected (), (!+, !+))
__assert_eq { expected = (((85915, 153, 3), 85000, 65, 0), true, true) }
:= room_camp () : camp = cyclops_camp () |> hunting_pack::on_layout (:< terrain ;; RoomAndCorridor)

:== tuned_base = { camp : camp; heal : i32; floor : i32; o : gold_objective }

:= tuned_base_on (camp : camp) : tuned_base =
    := heal = tuned_heal camp
    := floor = profit_floor camp heal
    { camp = camp; heal = heal; floor = floor; o = (:< hunting_pack ;; ProfitKeepingKills) floor }

:= order_genome (b : tuned_base) (search : u32) (order : policy::targeting) : search::genome =
    := start = { first = b::heal; second = 0; third = 4 }
    hunting_pack::rules_evolved_on b::o order start b::camp (hunting_pack::training_block ()) (six_by_six search)

:= order_worth (b : tuned_base) (order : policy::targeting) (g : search::genome) : i32 =
    trained b::o b::camp (hunting_pack::tactic_with_order order g)

:= reach_or_current (reach_wins : bool) : policy::targeting =
    ? reach_wins ?> (:< policy ;; ReachFirst) |: (:< policy ;; CurrentFirst)

:== choice = bool * search::genome

:= reach_order () : policy::targeting = :< policy ;; ReachFirst

:= current_order () : policy::targeting = :< policy ;; CurrentFirst

:= order_genomes_with \* g. (genome_of : policy::targeting -> g) : g * g =
    genome_of (current_order ()), genome_of (reach_order ())

:= reach_beats_with \* g. (worth_of : policy::targeting -> g -> i32) (current : g) (reach : g) : bool =
    worth_of (reach_order ()) reach > worth_of (current_order ()) current

:= pick_order \* g. (genome_of : policy::targeting -> g) (worth : policy::targeting -> g -> i32) : bool * g =
    := current, reach = order_genomes_with genome_of
    := reach_wins = reach_beats_with worth current reach
    reach_wins, (? reach_wins ?> reach |: current)

:= order_choice (b : tuned_base) (search : u32) : choice =
    pick_order (order_genome b search) (order_worth b)

:== tuned_facts = i32 * i32 * i32 * i32 * i32 * bool

:= order_facts (b : tuned_base) (reach_wins : bool) (genes : search::genome) : tuned_facts =
    b::heal, b::floor, genes::first, genes::second, genes::third, reach_wins

:= cavebot_row (b : tuned_base) : kp = held_out_pair b::camp (hunting_pack::cavebot_with_heal b::heal)

:= chosen_row (b : tuned_base) (reach_wins : bool) (genes : search::genome) : kp =
    held_out_pair b::camp (hunting_pack::tactic_with_order (reach_or_current reach_wins) genes)

:= order_experiment (b : tuned_base) (search : u32) : tuned_facts * kp * kp =
    := reach_wins, genes = order_choice b search
    order_facts b reach_wins genes, cavebot_row b, chosen_row b reach_wins genes

:= holds_kills_gains_profit (base : kp) (evolved : kp) : bool * bool =
    := base_kills, base_profit = base
    := kills, profit = evolved
    kills >= base_kills, profit > base_profit

:= checked (r : tuned_facts * kp * kp) : (tuned_facts * kp * kp) * (bool * bool) =
    := _, cavebot, evolved = r
    r, holds_kills_gains_profit cavebot evolved

:= room_expected () : tuned_facts * kp * kp = (230, 956, 207, 34, 3, !+), (942, -477181), (946, -288217)

room_and_corridor_evolved_retreat_dominates_the_fully_tuned_cavebot (test)

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

:= ~search = 29u32
checked (order_experiment (tuned_base_on (room_camp ())) search) |> _assert_eq (room_expected (), (!+, !+))
__assert_eq { expected = (((230, 956, 207, 34, 3, true), (942, -477181), 946, -288217), true, true) }
:= spot_camp () : camp = cyclops_camp () |> hunting_pack::on_layout (:< terrain ;; ArenaSpot)

:= clear_with_hits (camp : camp) (t : tactic) (first : u32) : i32 * i32 * i32 * i32 =
    := ms, potions, deaths, hits, taken = sums_over camp t (twenty_from first)
    ms / 20, potions * 10 / 20, deaths, taken * 100 / hits

the_arena_spot_clear_takes_the_live_clear_time (test)

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

:= ~first = 1u32
clear_with_hits (spot_camp ()) (hunting_pack::cavebot_with_heal 160) first |> _assert_eq (87600, 168, 0, 3591)
__assert_eq { expected = (87600, 168, 0, 3591) }
:== row5 = tally3 * tally3 * tally3 * tally3 * tally3

:= reach_rules (n : i32) : tactic =
    hunting_pack::tactic_with_order (:< policy ;; ReachFirst) { first = 160; second = 0; third = n }

:= current_rules (n : i32) : tactic =
    hunting_pack::tactic_with_order (:< policy ;; CurrentFirst) { first = 160; second = 0; third = n }

:= reach_kite (n : i32) : tactic =
    := t = kiting_on (spot_camp ()) 160 n
    { t ?| order = (:< policy ;; ReachFirst) }

:= spot_clear (first : u32) (t : tactic) : tally3 = clear_averages (spot_camp ()) t first

:= retreat_rows (first : u32) : row5 =
    := at = spot_clear first
    := cave = at (hunting_pack::cavebot_with_heal 160)
    cave, at (reach_rules 2), at (reach_rules 3), at (current_rules 2), at (reach_kite 2)

:= saves_potions (cavebot : tally3) (retreat : tally3) : bool * bool =
    := _, cavebot_potions, cavebot_deaths = cavebot
    := _, retreat_potions, retreat_deaths = retreat
    retreat_potions < cavebot_potions, retreat_deaths <= cavebot_deaths

:= retreat_expected () : row5 = (87600, 168, 0), (88545, 159, 0), (85610, 139, 0), (98605, 153, 0), (87500, 154, 0)

on_the_arena_spot_retreating_at_two_attackers_saves_potions (test)

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

:= ~first = 1u32
:= rows = retreat_rows first
:= cavebot, retreating, _, _, _ = rows
(rows, saves_potions cavebot retreating) |> _assert_eq (retreat_expected (), (!+, !+))
__assert_eq { expected = (((87600, 168, 0), (88545, 159, 0), (85610, 139, 0), (98605, 153, 0), 87500, 154, 0), true, true) }
:== clear_base = { camp : camp; heal : i32; cavebot_ms : i32; o : gold_objective }

:= training_range () : u32 * i32 = hunting_pack::training_first (), hunting_pack::training_count ()

:= clear_tuned_heal (camp : camp) : i32 =
    := limit = (:< hunting_pack ;; CheapClearWithin) 1000000
    := tf, tc = training_range ()
    hunting_pack::tuned_cavebot_heal_for limit camp tf tc

:= cavebot_clear_ms (camp : camp) (heal : i32) : i32 =
    := tf, tc = training_range ()
    (hunting_pack::clear_ledger_of camp (hunting_pack::cavebot_with_heal heal) tf tc)::clear_ms / tc

:= clear_base_on (camp : camp) : clear_base =
    := heal = clear_tuned_heal camp
    := ms = cavebot_clear_ms camp heal
    { camp = camp; heal = heal; cavebot_ms = ms; o = (:< hunting_pack ;; CheapClearWithin) (ms * 115 / 100) }

:= clear_genome (b : clear_base) (search : u32) (order : policy::targeting) : search::genome =
    := start = { first = b::heal; second = 0; third = 4 }
    hunting_pack::rules_evolved_on b::o order start b::camp (hunting_pack::training_block ()) (six_by_six search)

:= clear_worth (b : clear_base) (order : policy::targeting) (g : search::genome) : i32 =
    := tf, tc = training_range ()
    hunting_pack::worth b::o b::camp (hunting_pack::tactic_with_order order g) tf tc

:= clear_row (camp : camp) (t : tactic) : tally3 =
    := l = hunting_pack::clears_judged_on camp t (hunting_pack::held_out_block ())
    l::clear_potions, l::clear_deaths, l::clear_ms / hunting_pack::held_out_count ()

:= clear_chosen_row (b : clear_base) (reach_wins : bool) (genes : search::genome) : tally3 =
    clear_row b::camp (hunting_pack::tactic_with_order (reach_or_current reach_wins) genes)

:= clear_facts (b : clear_base) (reach_wins : bool) (genes : search::genome) : tuned_facts =
    b::heal, b::cavebot_ms, genes::first, genes::second, genes::third, reach_wins

:= clear_cavebot_row (b : clear_base) : tally3 = clear_row b::camp (hunting_pack::cavebot_with_heal b::heal)

:= clear_experiment (b : clear_base) (search : u32) : tuned_facts * tally3 * tally3 =
    := reach_wins, genes = pick_order (clear_genome b search) (clear_worth b)
    clear_facts b reach_wins genes, clear_cavebot_row b, clear_chosen_row b reach_wins genes

:= beats_baseline (baseline : tally3) (learned : tally3) : bool * bool * bool =
    := base_potions, base_deaths, base_ms = baseline
    := potions, deaths, ms = learned
    potions < base_potions, deaths <= base_deaths, ms <= base_ms

:= within_limit (cavebot_ms : i32) (learned : tally3) : bool =
    := _, _, ms = learned
    ms <= cavebot_ms * 115 / 100

:= clear_checks (r : tuned_facts * tally3 * tally3) : bool * bool * bool * bool =
    := facts, baseline, learned = r
    := _, cavebot_ms, _, _, _, _ = facts
    := a, b, c = beats_baseline baseline learned
    a, b, c, within_limit cavebot_ms learned

:= real_map_expected () : tuned_facts * tally3 * tally3 = (90, 88400, 88, 26, 1, !+), (1702, 0, 90140), (1070, 1, 95629)

on_the_real_arena_map_evolved_rules_save_potions_at_a_slightly_slower_clear (test)

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

:= ~search = 31u32
:= r = clear_experiment (clear_base_on (spot_camp ())) search
(r, clear_checks r) |> _assert_eq (real_map_expected (), (!+, !-, !-, !+))
__assert_eq { expected = (((90, 88400, 88, 26, 1, true), (1702, 0, 90140), 1070, 1, 95629), true, false, false, true) }
:== mixed_result = (tuned_facts * tuned_facts) * row5

:== four_checks = bool * bool * bool * bool

:= clear_trial (b : clear_base) : hunting_pack::trial quantity::gold_coins =
    hunting_pack::trial_on b::o b::camp (hunting_pack::training_block ())

:= aware_plan (search : u32) : hunting_pack::search_plan = { seed = search + 2u32; generations = 6; offspring = 3 }

:= aware_genome (b : clear_base) (search : u32) (order : policy::targeting) : search::vector =
    := start = hunting_pack::temperate_genes b::heal 0 0 4 4
    hunting_pack::evolve_temperate_for (clear_trial b) order start (aware_plan search)

:= aware_worth (b : clear_base) (order : policy::targeting) (g : search::vector) : i32 =
    := tf, tc = training_range ()
    hunting_pack::worth b::o b::camp (hunting_pack::temperate_tactic order g) tf tc

:== mixed_choice = choice * (bool * search::vector)

:= mixed_choices (b : clear_base) (search : u32) : mixed_choice =
    pick_order (clear_genome b search) (clear_worth b), pick_order (aware_genome b search) (aware_worth b)

:= aware_facts (wins : bool) (g : search::vector) : tuned_facts =
    := gene i = search::gene g i
    gene 0, gene 1, gene 2, gene 3, gene 4, wins

:= cyclops_rules_row (camp : camp) : tally3 =
    clear_row camp (hunting_pack::tactic_with_order (current_order ()) (hunting_pack::evolved_on_the_cyclops_spot ()))

:= standing_only_row (camp : camp) : tally3 =
    := genes = hunting_pack::against_standing_only (hunting_pack::evolved_on_the_cyclops_spot ())
    clear_row camp (hunting_pack::temperate_tactic (current_order ()) genes)

:= aware_row (b : clear_base) (wins : bool) (aware : search::vector) : tally3 =
    clear_row b::camp (hunting_pack::temperate_tactic (reach_or_current wins) aware)

:= five_rows (b : clear_base) (blind_wins : bool) (blind : search::genome) (aware : tally3) : row5 =
    clear_cavebot_row b,
    clear_chosen_row b blind_wins blind,
    cyclops_rules_row b::camp,
    standing_only_row b::camp,
    aware

:= mixed_rows (b : clear_base) (choices : mixed_choice) : row5 =
    ?= choices ?|
    | (blind_wins, blind), (aware_wins, aware) => five_rows b blind_wins blind (aware_row b aware_wins aware)

:= mixed_facts (b : clear_base) (choices : mixed_choice) : tuned_facts * tuned_facts =
    ?= choices ?|
    | (blind_wins, blind), (aware_wins, aware) => clear_facts b blind_wins blind, aware_facts aware_wins aware

:= fewer (a : tally3) (b : tally3) : bool =
    := p, d, ms = a
    := bp, bd, bms = b
    p <= bp && d <= bd && ms <= bms && (p < bp || d < bd || ms < bms)

:= mixed_dominance (rows : row5) : four_checks =
    := cavebot, blind, cyclops, standing, aware = rows
    fewer aware cavebot, fewer aware blind, fewer blind cavebot, fewer standing cyclops

:= mixed_camp () : camp = hunting_pack::cyclops_and_two_trolls_on_the_arena_spot ()

:= checked_mixed (search : u32) : mixed_result * four_checks =
    := b = clear_base_on (mixed_camp ())
    := choices = mixed_choices b search
    := rows = mixed_rows b choices
    (mixed_facts b choices, rows), mixed_dominance rows

:= mixed_expected () : mixed_result =
    (
        ((80, 38366, 59, 5, 3, !-), (0, 62, 0, 4, 2, !-)),
        ((85, 0, 37668), (73, 0, 37559), (190, 0, 38608), (135, 0, 37806), (67, 1, 37866))
    )

mixed_arena_camp_evolved_rules_dominate_the_tuned_cavebot_and_kind_aware_cyclops_rules_drink_less (test)

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

:= ~search_seed = 37u32
checked_mixed search_seed |> _assert_eq (mixed_expected (), (!-, !-, !+, !+))
__assert_eq { expected = ((((80, 38366, 59, 5, 3, false), 0, 62, 0, 4, 2, false), (85, 0, 37668), (73, 0, 37559), (190, 0, 38608), (135, 0, 37806), 67, 1, 37866), false, false, true, true) }

kills_and_gold_do_not_add (test)

test kills_and_gold_do_not_add: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
:< creatures
:= camp = uniform_camp Cyclops
:= t = cavebot_with_heal 160
units::plus (measured Kills camp t 1 1) (measured Profit camp t 1 1)
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:519:46 (main.spi:407:46): ETY-TermError: Unification failure. Got:      amount gold_coins Expected: amount kill_score In the definition: spiral_cells_test.
:= cavebot_160 () : tactic = hunting_pack::cavebot_with_heal 160

:= sliced_clear (spot : camp) (t : tactic) : hunting_pack::clear_ledger =
    hunting_pack::clears_judged_on spot t (hunting_pack::held_out_slice 3)

:= clears_match (spot : camp) (t : tactic) (first : u32) : bool * bool =
    := typed, raw = sliced_clear spot t, hunting_pack::clear_ledger_of spot t first 3
    typed::clear_potions = raw::clear_potions, typed::clear_ms = raw::clear_ms

:= kills_match (spot : camp) (t : tactic) (first : u32) : bool =
    := typed = hunting_pack::judged_on (:< hunting_pack ;; Kills) spot t (hunting_pack::held_out_slice 1)
    units::value_of typed = hunting_pack::score spot t first 1

:= typed_matches (first : u32) : bool * bool * bool =
    := potions, ms = clears_match (spot_camp ()) (cavebot_160 ()) first
    potions, ms, kills_match (spot_camp ()) (cavebot_160 ()) first

typed_seed_blocks_give_the_same_numbers_as_raw_ranges (test)

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

:= ~first = 1001u32
typed_matches first |> _assert_eq (!+, !+, !+)
__assert_eq { expected = (true, true, true) }

tuning_on_held_out_seeds_does_not_compile (test)

test tuning_on_held_out_seeds_does_not_compile: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
:< creatures
cavebot_heal_tuned_on Profit (uniform_camp Cyclops) (held_out_block ())
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:547:58 (main.spi:421:58): ETY-TermError: Unification failure. Got:      seed_block held_out Expected: seed_block training In the definition: spiral_cells_test.

a_held_out_block_cannot_be_forged_from_training_seeds (test)

test a_held_out_block_cannot_be_forged_from_training_seeds: passed
///- --test --symbol --fails "The module does not have the key: HeldOutBlock"
///> c

:< hunting_pack
judged_on Profit (cyclops_camp ()) (cavebot_with_heal 160) (hunting_pack::HeldOutBlock { first = 1u32; count = 3 })
failed to compile as expected ("The module does not have the key: HeldOutBlock"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:552:65 (main.spi:420:65): ETY-ModuleIndexFailed: The module does not have the key: HeldOutBlock In the definition: spiral_cells_test.

a_strike_proof_cannot_be_forged_outside_readiness (test)

test a_strike_proof_cannot_be_forged_outside_readiness: passed
///- --test --symbol --fails "The module does not have the key: AttackReady"
///> c

readiness::Strike (readiness::AttackReady (quantity::ms 0))
failed to compile as expected ("The module does not have the key: AttackReady"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:556:24 (main.spi:420:24): ETY-ModuleIndexFailed: The module does not have the key: AttackReady In the definition: spiral_cells_test.

judging_on_training_seeds_does_not_compile (test)

test judging_on_training_seeds_does_not_compile: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
:< creatures
judged_on Profit (uniform_camp Cyclops) (cavebot_with_heal 160) (training_block ())
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:562:70 (main.spi:421:70): ETY-TermError: Unification failure. Got:      seed_block training Expected: seed_block held_out In the definition: spiral_cells_test.
:= standing_heal (camp : camp) : i32 =
    hunting_pack::cavebot_heal_tuned_on (:< hunting_pack ;; Experience) camp (hunting_pack::training_block ())

:= ground_xp (camp : camp) (t : tactic) : i32 =
    units::value_of (
        hunting_pack::judged_on (:< hunting_pack ;; Experience) camp t (hunting_pack::held_out_block ())
    )

:= walked (camp : camp) (cavebot : tactic) (route : hunting_pack::patrol_route) : i32 =
    ground_xp camp (cavebot |> hunting_pack::patrolling route)

:= route_rows (camp : camp) (cavebot : tactic) : tally3 =
    := on = walked camp cavebot
    := plaza, loop = on (hunting_pack::plaza_and_fields_route ()), on (hunting_pack::spawn_side_loop_route ())
    plaza, loop, on (hunting_pack::central_shuttle_route ())

:= walking_rows (camp : camp) (tuned : i32) : i32 * i32 * tally3 =
    := cavebot = hunting_pack::cavebot_with_heal tuned
    ground_xp camp cavebot, ground_xp camp (hunting_pack::sweeping_spawns cavebot), route_rows camp cavebot

:= walking_expected () : i32 * (i32 * i32 * tally3) = 120, (109030, 118460, (107540, 113040, 124830))

on_the_hunting_ground_walking_pays_and_the_evolved_shuttle_edges_the_hand_route (test)

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

:= camp = hunting_pack::arena_hunt ()
:= ~tuned = standing_heal camp
(tuned, walking_rows camp tuned) |> _assert_eq (walking_expected ())
__assert_eq { expected = (120, 109030, 118460, 107540, 113040, 124830) }
:= held_out_totals (camp : camp) (t : tactic) : tally3 =
    := ms, potions, _, hits, _ = sums_over camp t (hunting_pack::range_of (hunting_pack::held_out_block ()))
    potions, ms, hits

:= potions_hits (camp : camp) (t : tactic) : kp =
    := potions, _, hits = held_out_totals camp t
    potions, hits

:= live_retreat () : tactic = hunting_pack::live_retreat_tactic 160 2

:= arena_spot_of (kind : creatures::arena_creature) : camp =
    hunting_pack::uniform_camp kind |> hunting_pack::on_layout (:< terrain ;; ArenaSpot)

:= bundle_rows (kind : creatures::arena_creature) : kp * kp =
    := arena = arena_spot_of kind |> hunting_pack::mirroring_the_current_arena
    potions_hits arena (cavebot_160 ()), potions_hits arena (live_retreat ())

:= bundle_expected () : kp * kp = (1394, 6649), (1332, 6428)

the_current_arena_bundle_pins_rules_and_retreat_potions_and_hits_over_the_held_out_block (test)

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

:= ~kind = :< creatures ;; Cyclops
bundle_rows kind |> _assert_eq (bundle_expected ())
__assert_eq { expected = ((1394, 6649), 1332, 6428) }
:= formula_spot () : camp = spot_camp () |> hunting_pack::stepping_by_canary_formula

:= cyclops_step_ms (camp : camp) (at : geometry::position) : i32 =
    := cyclops = creatures::stats_of (:< creatures ;; Cyclops)
    hunting_pack::step_ms_at camp at cyclops::speed cyclops::step_ms

:= formula_steps (at : geometry::position) : kp * kp =
    := camp = formula_spot ()
    := road = geometry::position 115 89 7
    := hunter = hunting_pack::hunter_step_ms_at camp
    (cyclops_step_ms camp at, cyclops_step_ms camp road), (hunter at, hunter road)

by_canary_formula_a_cyclops_steps_700_ms_on_arena_ground_and_450_on_the_road_and_the_level_30_knight_500_and_300 (test)

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

:= ~ground = geometry::position 99 99 7
formula_steps ground |> _assert_eq ((700, 450), (500, 300))
__assert_eq { expected = ((700, 450), 500, 300) }
:= respawns (spawntime : i32) : tally3 =
    := at = hunting_pack::canary_respawn_ms
    at spawntime, at (spawntime / 2), at (spawntime + 30000)

a_canary_spawn_returns_after_its_cleanup_check_its_spawntime_and_the_teleport_effect (test)

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

:= ~spawntime = 60000i32
respawns spawntime |> _assert_eq (94200, 64200, 124200)
__assert_eq { expected = (94200, 64200, 124200) }
:= random_hunt (camp : camp) : tally3 =
    := l = hunting_pack::ledger_on camp (hunting_pack::random_control ()) (hunting_pack::held_out_block ())
    l::hunt_kills, l::hunt_deaths, units::value_of l::hunt_profit

:= random_clear () : tally3 =
    := held = hunting_pack::held_out_block ()
    := l = hunting_pack::clears_judged_on (spot_camp ()) (hunting_pack::random_control ()) held
    l::clear_potions, l::clear_deaths, l::clear_ms

:= random_rows (marker : i32) : i32 * tally3 * tally3 =
    := camp = hunting_pack::arena_hunt ()
    ground_xp camp (hunting_pack::random_control ()) + marker, random_hunt camp, random_clear ()

:= random_expected () : i32 * tally3 * tally3 = 57950, (993, 0, -1145455), (7206, 0, 9353800)

the_random_control_stays_far_below_every_rule (test)

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

:= ~marker = 0i32
random_rows marker |> _assert_eq (random_expected ())
__assert_eq { expected = (57950, (993, 0, -1145455), 7206, 0, 9353800) }
:= sweeping_heal (camp : camp) : i32 =
    := b = hunting_pack::training_block ()
    hunting_pack::shaped_cavebot_heal_tuned_on (:< hunting_pack ;; Experience) hunting_pack::sweeping_spawns camp b

:= ground_judged (camp : camp) (t : tactic) : tally3 =
    := l = hunting_pack::ledger_on camp t (hunting_pack::held_out_block ())
    ground_xp camp t, l::hunt_deaths, units::value_of l::hunt_profit

:= retreat_tactic () : tactic = hunting_pack::tactic_with_order (reach_order ()) { first = 154; second = 26; third = 2 }

:= sweeping_cavebot (heal : i32) : tactic = hunting_pack::cavebot_with_heal heal |> hunting_pack::sweeping_spawns

:= shuttling_cavebot (heal : i32) : tactic =
    hunting_pack::cavebot_with_heal heal |> hunting_pack::patrolling (hunting_pack::central_shuttle_route ())

:= cavebot_rows (camp : camp) (standing : i32) (sweeping : i32) : tally3 * tally3 * tally3 =
    := at = ground_judged camp
    at (hunting_pack::cavebot_with_heal standing), at (sweeping_cavebot sweeping), at (shuttling_cavebot standing)

:= ground_rows (camp : camp) (standing : i32) (sweeping : i32) : row5 =
    := cave, sweep, shuttle = cavebot_rows camp standing sweeping
    := r = retreat_tactic ()
    cave, sweep, shuttle, ground_judged camp r, ground_judged camp (hunting_pack::sweeping_spawns r)

:= ground_experiment (camp : camp) : (i32 * i32) * row5 =
    := ~standing = standing_heal camp
    := sweeping = sweeping_heal camp
    (standing, sweeping), ground_rows camp standing sweeping

:= ground_expected () : (i32 * i32) * row5 =
    (
        (120, 130),
        ((102550, 4, -248017), (127200, 3, -296549), (132200, 2, -281049), (100650, 0, -118297), (120750, 0, -167225))
    )

on_the_cyclops_hunting_ground_sweeping_leads_and_stepping_back_buys_cost (test)

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

ground_experiment (hunting_pack::cyclops_hunting_ground ()) |> _assert_eq (ground_expected ())
__assert_eq { expected = ((120, 130), (102550, 4, -248017), (127200, 3, -296549), (132200, 2, -281049), (100650, 0, -118297), 120750, 0, -167225) }
:= wake_spot (wake : i32) : tally3 * tally3 =
    := spot = spot_camp () |> hunting_pack::waking_at wake
    held_out_totals spot (cavebot_160 ()), held_out_totals spot (retreat_tactic ())

:= wake_rows (wake : i32) : tally3 * tally3 * tally3 =
    := cave, retreat = wake_spot wake
    cave, retreat, held_out_totals (mixed_camp () |> hunting_pack::waking_at wake) (cavebot_160 ())

:= wake_expected () : tally3 * tally3 * tally3 = (1334, 8838000, 6417), (1201, 9040500, 5835), (36, 3710300, 946)

the_live_wake_delay_brings_the_arena_clears_toward_the_live_potion_counts (test)

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

:= ~wake = hunting_pack::live_wake_ms ()
wake_rows wake |> _assert_eq (wake_expected ())
__assert_eq { expected = ((1334, 8838000, 6417), (1201, 9040500, 5835), 36, 3710300, 946) }
:= canary_swings (spot : camp) : tally3 * tally3 =
    := canary = spot |> hunting_pack::swinging_like_canary
    held_out_totals canary (reach_rules 2), held_out_totals canary (cavebot_160 ())

:= swing_rows () : tally3 * tally3 * tally3 =
    := spot = spot_camp () |> hunting_pack::waking_at (hunting_pack::live_wake_ms ())
    := retreat, cavebot = canary_swings spot
    held_out_totals spot (reach_rules 2), retreat, cavebot

:= swing_expected () : tally3 * tally3 * tally3 = (1179, 8968500, 5750), (1232, 9033300, 6020), (1334, 8838000, 6417)

after_a_step_the_swing_waits_for_a_think_or_a_move_costing_retreat_half_a_potion_and_rules_nothing (test)

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

swing_rows () |> _assert_eq (swing_expected ())
__assert_eq { expected = ((1179, 8968500, 5750), (1232, 9033300, 6020), 1334, 8838000, 6417) }
:= mirrored_spot () : camp = spot_camp () |> hunting_pack::mirroring_the_live_arena

:= cavebot_and_retreat (camp : camp) : tally3 * tally3 =
    held_out_totals camp (cavebot_160 ()), held_out_totals camp (live_retreat ())

:= mirror_rows () : (tally3 * tally3) * (tally3 * tally3) =
    := three = mirrored_spot ()
    cavebot_and_retreat three, cavebot_and_retreat (three |> hunting_pack::sized 2)

:= mirror_expected () : (tally3 * tally3) * (tally3 * tally3) =
    ((1230, 8937100, 5986), (1176, 9424500, 5640)), ((472, 6142500, 2793), (491, 6415400, 2924))

mirroring_the_live_arena_predicts_rules_and_retreat_within_the_live_noise_at_two_and_three_cyclops (test)

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

mirror_rows () |> _assert_eq (mirror_expected ())
__assert_eq { expected = (((1230, 8937100, 5986), 1176, 9424500, 5640), (472, 6142500, 2793), 491, 6415400, 2924) }
:== kp3 = kp * kp * kp

:= never_retreating () : tactic =
    hunting_pack::tactic_with_order (reach_order ()) { first = 160; second = 0; third = policy::never_retreat () }

:= first_swing_three (three : camp) : kp3 =
    := at = potions_hits three
    at (cavebot_160 ()), at (never_retreating ()), at (live_retreat ())

:= first_swing_rest (three : camp) : kp3 =
    := two = potions_hits (three |> hunting_pack::sized 2)
    two (cavebot_160 ()), two (live_retreat ()), potions_hits (three |> hunting_pack::waking_at 0) (cavebot_160 ())

:= first_swing_rows () : kp3 * kp3 =
    := three = mirrored_spot ()
    first_swing_three three, first_swing_rest three

:= first_swing_expected () : kp3 * kp3 =
    ((1230, 5986), (1247, 6021), (1176, 5640)), ((472, 2793), (491, 2924), (1529, 7076))

a_cyclops_first_swing_waits_one_period_after_it_can_target_which_fits_six_live_means_best (test)

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

first_swing_rows () |> _assert_eq (first_swing_expected ())
__assert_eq { expected = (((1230, 5986), (1247, 6021), 1176, 5640), (472, 2793), (491, 2924), 1529, 7076) }
:= cavebot_run (c : camp) (i : i32) : pack = hunting_pack::simulate c (cavebot_160 ()) (1001u32 + conv i) 1200

:~ ^ hunter_sums (c : camp) (i : i32) (kills : i32) (hits : i32) : kp =
    ? i >= 5 ?> kills, hits
    |:
        := p = cavebot_run c i
        hunter_sums c (i + 1) (kills + p::score::kills) (hits + p::hunter::tally::hits_taken)

:= hunter_camp (layout : terrain::layout) : camp =
    hunting_pack::clearing (hunting_pack::uniform_camp (:< creatures ;; Hunter) |> hunting_pack::on_layout layout)

:= kills_within (layout : terrain::layout) : kp = hunter_sums (hunter_camp layout) 0 0 0

:= hunter_rows () : kp * kp = kills_within (:< terrain ;; OpenField), kills_within (:< terrain ;; ArenaSpot)

:= cornered_only (rows : kp * kp) : bool * bool =
    := (open_kills, _), (arena_kills, _) = rows
    open_kills = 0, arena_kills > 0

a_hunter_keeping_four_tiles_away_only_falls_where_walls_corner_it_and_walking_beside_the_knight_halves_its_pace (test)

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

:= rows = hunter_rows ()
(rows, cornered_only rows) |> _assert_eq (((0, 173), (11, 82)), (!+, !+))
__assert_eq { expected = (((0, 173), 11, 82), true, true) }
:= placed_at (camp : camp) (dx : i32) (dy : i32) : pack =
    := begun = hunting_pack::start camp
    := k = begun::hunter::at
    hunting_pack::with_slot 0 { (hunting_pack::slot 0 begun) ?| at = { k ?| x = k::x + dx; y = k::y + dy } } begun

:~ ^ foe_turns (camp : camp) (tick : i32) (p : pack) : pack =
    ? tick >= 10 ?> p
    |: foe_turns camp (tick + 1) (hunting_pack::foe_turn camp 1001u32 tick (tick * hunting_pack::tick_ms ()) 0 p)

:= closed_tiles (gap : i32) : i32 =
    := camp = hunter_camp (:< terrain ;; OpenField)
    := placed = placed_at camp gap 0
    geometry::chebyshev_distance (hunting_pack::slot 0 (foe_turns camp 0 placed))::at (hunting_pack::slot 0 placed)::at

a_hunter_beyond_four_tiles_closes_one_tile_per_think_like_canarys_follow_replanning (test)

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

:= ~gap = 8i32
(closed_tiles gap, (creatures::stats_of (:< creatures ;; Hunter))::step_ms < 1000) |> _assert_eq (1i32, !+)
__assert_eq { expected = (1, true) }
:= open_cyclops () : camp = cyclops_camp () |> hunting_pack::on_layout (:< terrain ;; OpenField)

:= dancing_cyclops () : camp = hunting_pack::clearing (open_cyclops () |> hunting_pack::dancing_like_canary)

:= dance_landing (camp : camp) (placed : pack) (i : i32) : geometry::position =
    (hunting_pack::slot 0 (hunting_pack::dance_step (hunting_pack::turn_at camp 1001u32 i 0 0) placed))::at

:== sides = bool * bool * bool

:= side_of (k : geometry::position) (moved : geometry::position) : bool * bool =
    moved::x = k::x + 1 && moved::y = k::y, moved::x = k::x && moved::y = k::y + 1

:= tallied_side (acc : sides) (side : bool * bool) : sides =
    := allowed, north, west = acc
    := went_north, went_west = side
    allowed && (went_north || went_west), north || went_north, west || went_west

:~ ^ dance_tries (camp : camp) (placed : pack) (i : i32) (acc : sides) : sides =
    ? i >= 20 ?> acc
    |: dance_tries camp placed (i + 1) (tallied_side acc (side_of placed::hunter::at (dance_landing camp placed i)))

an_adjacent_cyclops_dances_only_along_the_two_sides_that_keep_it_next_to_the_knight_like_canary (test)

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

:= camp = dancing_cyclops ()
dance_tries camp (placed_at camp 1 1) 0 (!+, !-, !-) |> _assert_eq (!+, !+, !+)
__assert_eq { expected = (true, true, true) }
:== dancer = { camp : camp; placed : pack; seed : u32 }

:= dance_moved (d : dancer) (now : i32) : bool =
    := after = hunting_pack::dances d::camp d::seed (now / hunting_pack::tick_ms ()) now 0 d::placed
    geometry::same_tile (hunting_pack::slot 0 after)::at (hunting_pack::slot 0 d::placed)::at = !-

:~ ^ dance_thinks (d : dancer) (now : i32) (k : i32) (danced : i32) : i32 =
    ? k >= 200 ?> danced
    |:
        := moved = dance_moved d now
        dance_thinks d (now + hunting_pack::player_think_ms ()) (k + 1) (? moved ?> danced + 1 |: danced)

:= danced_of (seed : u32) : i32 =
    := camp = dancing_cyclops ()
    := d = { camp = camp; placed = placed_at camp 1 1; seed = seed }
    dance_thinks d (hunting_pack::creature_think_phase_ms seed 0) 0 0

an_adjacent_cyclops_dances_on_about_one_think_in_ten_like_canarys_static_attack_chance_of_90 (test)

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

:= ~seed = 1001u32
:= danced = danced_of seed
(danced >= 10, danced <= 30) |> _assert_eq (!+, !+)
__assert_eq { expected = (true, true) }

a_compass_direction_is_not_a_router_step (test)

test a_compass_direction_is_not_a_router_step: passed
///- --test --symbol --fails "Unification failure"
///> c

terrain::route_offset (:< geometry ;; NorthEast)
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:866:43 (main.spi:637:43): ETY-TermError: Unification failure. Got:      (route_choice : *) Expected: (direction : *) In the definition: spiral_cells_test.

raw_weights_are_not_a_tactic_brain (test)

test raw_weights_are_not_a_tactic_brain: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
{ brain = network::zero_weights (); order = :< policy ;; CurrentFirst; footing = StepAway; patrol = Stays }
|> \ (t : tactic) => t
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:872:15 (main.spi:638:15): ETY-TermError: Unification failure. Got:      {brain : (weights : *); order : (targeting : *); footing : (footing_rule : *); patrol : (patrol : *)} Expected: {brain : (brain : *); order : (targeting : *); footing : (footing_rule : *); patrol : (patrol : *)} In the definition: spiral_cells_test.
:= one_point_route () : hunting_pack::patrol_route =
    { points = fixed_views::band_of (geometry::position 1936 1349 7); count = 1 }

a_patrol_route_is_not_a_patrol_without_its_case (test)

test a_patrol_route_is_not_a_patrol_without_its_case: passed
///- --test --symbol --fails "Unification failure"
///> c

:< hunting_pack
{ brain = RandomControl; order = :< policy ;; CurrentFirst; footing = StepAway; patrol = one_point_route () }
|> \ (t : tactic) => t
failed to compile as expected ("Unification failure"): FatalError: File main has a type error somewhere in its path. /home/runner/work/tibia/tibia/src/pack_sim.spi:881:15 (main.spi:640:15): ETY-TermError: Unification failure. Got:      {brain : (brain : *); order : (targeting : *); footing : (footing_rule : *); patrol : {points : band {x : i32; y : i32; z : i32}; count : i32}} Expected: {brain : (brain : *); order : (targeting : *); footing : (footing_rule : *); patrol : (patrol : *)} In the definition: spiral_cells_test.