///- --package .

geometry (test)

test geometry: prelude
///- --test static --symbol

:< testing
:| direction =
    | North
    | NorthEast
    | East
    | SouthEast
    | South
    | SouthWest
    | West
    | NorthWest

:== position = { x : i32; y : i32; z : i32 }

:= position x y z : position = { x y z }

:= offset (heading : direction) : i32 * i32 =
    ?= heading ?|
    | North => 0, -1
    | NorthEast => 1, -1
    | East => 1, 0
    | SouthEast => 1, 1
    | South => 0, 1
    | SouthWest => -1, 1
    | West => -1, 0
    | NorthWest => -1, -1

:= is_diagonal (heading : direction) : bool =
    ?= heading ?|
    | NorthEast | SouthEast | SouthWest | NorthWest => !+
    | _ => !-

:= step (heading : direction) (from : position) : position =
    := dx, dy = offset heading
    { from ?| x = from::x + dx; y = from::y + dy }

:= magnitude (n : i32) : i32 =
    ? n < 0 ?> -n |: n

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

:= same_floor (a : position) (b : position) : bool =
    a::z = b::z

:= chebyshev_distance (a : position) (b : position) : i32 =
    larger (magnitude (a::x - b::x)) (magnitude (a::y - b::y))

:= within_range (reach : i32) (a : position) (b : position) : bool =
    same_floor a b && chebyshev_distance a b <= reach

:= unit (n : i32) : i32 = ? n > 0 ?> 1 |? n < 0 ?> -1 |: 0

:= straight_offset (dx : i32) (dy : i32) : i32 * i32 =
    ? magnitude dx >= magnitude dy && dx <> 0 ?> unit dx, 0
    |? dy <> 0 ?> 0, unit dy
    |: 1, 0

:= straight_step (sign : i32) (target : position) (from : position) : position =
    := ox, oy = straight_offset (target::x - from::x) (target::y - from::y)
    { from ?| x = from::x + sign * ox; y = from::y + sign * oy }

:= step_toward (target : position) (from : position) : position = straight_step 1 target from

:= monster_view_range () : i32 = 11

:= sees (watcher : position) (seen : position) : bool =
    watcher::z = seen::z
    && magnitude (watcher::x - seen::x) <= monster_view_range ()
    && magnitude (watcher::y - seen::y) <= monster_view_range ()

:= same_tile (a : position) (b : position) : bool = a::x = b::x && a::y = b::y && a::z = b::z

:= diagonal_step_factor () : i32 = 3

:= moved_diagonally (from : position) (to : position) : bool = from::x <> to::x && from::y <> to::y

:= step_cost (straight_ms : i32) (from : position) (to : position) : i32 =
    ? moved_diagonally from to
    ?> straight_ms * diagonal_step_factor ()
    |: straight_ms
:= step_away (threat : position) (from : position) : position = straight_step -1 threat from

:| up_ramp =
    | RampNorth
    | RampSouth
    | RampEast
    | RampWest
    | RampSouthAlt
    | RampEastAlt

:= up_ramp_offset (ramp : up_ramp) : i32 * i32 =
    ?= ramp ?|
    | RampNorth => 0, -1
    | RampSouth => 0, 1
    | RampEast => 1, 0
    | RampWest => -1, 0
    | RampSouthAlt => 0, 2
    | RampEastAlt => 2, 0

:= climbed (ramp : up_ramp) (onto : position) : position =
    := dx, dy = up_ramp_offset ramp
    { x = onto::x + dx; y = onto::y + dy; z = onto::z - 1 }

:= sixteen_bits (n : u32) : u32 = n &&& 65535u32

:== blocker = position -> bool

:= sight_near (a : position) (b : position) : bool = magnitude (a::x - b::x) <= 1 && magnitude (a::y - b::y) <= 1

:= swapped_axes (swap : bool) (p : position) : position = ? swap ?> { x = p::y; y = p::x; z = p::z } |: p

:= stepped (p : position) (d : position) : position = { p ?| x = p::x + d::x; y = p::y + d::y }

:= unit_toward (from : position) (to : position) : position =
    { x = unit (to::x - from::x); y = unit (to::y - from::y); z = 0 }

:= cells_between (from : position) (to : position) : i32 = chebyshev_distance from to - 1

:= straight_clear (blocks : blocker) (from : position) (to : position) : bool =
    := d = unit_toward from to
    :~ ^ run (p : position) (n : i32) : bool = ? n <= 0 ?> !+ |? blocks p ?> !- |: run (stepped p d) (n - 1)
    run (stepped from d) (cells_between from to)

:== slope = { swap : bool; finish : position; adjust : u32; drift : i32 }

:= slope_of (swap : bool) (start : position) (finish : position) : slope =
    := adjust : u32 = (conv (magnitude (start::x - finish::x)) <<< 16) / conv (finish::y - start::y)
    { swap = swap; finish = finish; adjust = adjust; drift = ? start::x > finish::x ?> -1 |: 1 }

:== ray = { at : position; accumulated : u32; left : i32 }

:= ray_from (s : slope) (start : position) : ray =
    := accumulated = ? s::drift < 0 ?> sixteen_bits (65536u32 - s::adjust) |: 0u32
    { at = start; accumulated = accumulated; left = s::finish::y - start::y - 1 }

:= ray_step (s : slope) (r : ray) : ray =
    := next = sixteen_bits (r::accumulated + s::adjust)
    := drift = ? next <= r::accumulated ?> s::drift |: 0
    { at = { x = r::at::x + drift; y = r::at::y + 1; z = r::at::z }; accumulated = next; left = r::left - 1 }

:= ordered_by_y (a : position) (b : position) : position * position = ? a::y > b::y ?> b, a |: a, b

:= sloped_start (swap : bool) (from : position) (to : position) : slope * ray =
    := start, finish = ordered_by_y (swapped_axes swap from) (swapped_axes swap to)
    := s = slope_of swap start finish
    s, ray_from s start

:= blocked_at (blocks : blocker) (s : slope) (p : position) : bool = blocks (swapped_axes s::swap p)

:= sloped_clear (blocks : blocker) (start : slope * ray) : bool =
    := s, first = start
    :~ ^ run (r : ray) : bool =
        := ahead = ray_step s r
        ? r::left <= 0 ?> !+ |? blocked_at blocks s ahead::at ?> sight_near r::at s::finish |: run ahead
    run first

:= x_major (from : position) (to : position) : bool = magnitude (from::x - to::x) >= magnitude (from::y - to::y)

:= sight_line_clear (blocks : blocker) (from : position) (to : position) : bool =
    ? from::x = to::x && from::y = to::y ?> !+
    |? from::x = to::x || from::y = to::y ?> straight_clear blocks from to
    |: sloped_clear blocks (sloped_start (x_major from to) from to)

step_north_decrements_y (test)

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

:= moved = position 100 100 7 |> step North
moved::y |> _assert_eq 99i32
__assert_eq { expected = 99 }

step_south_east_moves_both_axes (test)

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

:= moved = position 100 100 7 |> step SouthEast
moved::x |> _assert_eq 101i32
moved::y |> _assert_eq 101i32
__assert_eq { expected = 101 }
__assert_eq { expected = 101 }

step_keeps_floor (test)

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

:= moved = position 100 100 7 |> step West
moved::z |> _assert_eq 7i32
__assert_eq { expected = 7 }

diagonal_headings (test)

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

is_diagonal NorthWest |> _assert_eq !+
is_diagonal East |> _assert_eq !-
__assert_eq { expected = true }
__assert_eq { expected = false }

chebyshev_counts_diagonals_as_one (test)

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

chebyshev_distance (position 0 0 7) (position 3 2 7) |> _assert_eq 3i32
__assert_eq { expected = 3 }

opposite_steps_cancel (test)

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

:= start = position 50 60 7
:= back = start |> step NorthEast |> step SouthWest
back::x |> _assert_eq start::x
back::y |> _assert_eq start::y
__assert_eq { expected = 50 }
__assert_eq { expected = 60 }
:= xy (p : position) : i32 * i32 = p::x, p::y

steps_match_canary_walk_probe_2026_10_12 (test)

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

:= ~start = position 1942 1346 7
:= north = step North start
(xy north, xy (step East north)) |> _assert_eq ((1942i32, 1345i32), (1943i32, 1345i32))
__assert_eq { expected = ((1942, 1345), 1943, 1345) }

steps_close_the_canary_walk_probe_loop_2026_10_12 (test)

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

:= ~start = position 1942 1346 7
:= south = start |> step North |> step East |> step South
(xy south, same_tile start (step West south)) |> _assert_eq ((1943i32, 1346i32), !+)
__assert_eq { expected = ((1943, 1346), true) }

diagonal_steps_match_canary_walk_probe_2026_10_12 (test)

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

:= ~start = position 1932 1352 7
:= north_east = step NorthEast start
(xy north_east, xy (step SouthEast north_east)) |> _assert_eq ((1933i32, 1351i32), (1934i32, 1352i32))
__assert_eq { expected = ((1933, 1351), 1934, 1352) }

diagonal_steps_close_the_canary_walk_loop_2026_10_12 (test)

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

:= ~start = position 1932 1352 7
:= third = start |> step NorthEast |> step SouthEast |> step SouthWest
(xy third, same_tile start (step NorthWest third)) |> _assert_eq ((1933i32, 1353i32), !+)
__assert_eq { expected = ((1933, 1353), true) }

stepping_onto_the_south_ramp_matches_the_live_floor_change_2026_10_15 (test)

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

:= ~ramp = position 1940 1372 7
:= up = climbed RampSouth ramp
(xy up, up::z, chebyshev_distance ramp up, same_floor ramp up) |> _assert_eq ((1940i32, 1373i32), 6i32, 1, !-)
__assert_eq { expected = ((1940, 1373), 6, 1, false) }
:= ramp_shift (ramp : up_ramp) (onto : position) : i32 * i32 * i32 =
    := p = climbed ramp onto
    p::x - onto::x, p::y - onto::y, p::z

cardinal_up_ramps_shift_like_canary_query_destination (test)

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

:= ~onto = position 100 100 7
(ramp_shift RampNorth onto, ramp_shift RampEast onto, ramp_shift RampWest onto)
|> _assert_eq ((0i32, -1i32, 6i32), (1i32, 0i32, 6i32), (-1i32, 0i32, 6i32))
__assert_eq { expected = ((0, -1, 6), (1, 0, 6), -1, 0, 6) }

alt_up_ramps_shift_two_tiles_like_canary_query_destination (test)

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

:= ~onto = position 100 100 7
(ramp_shift RampSouthAlt onto, ramp_shift RampEastAlt onto) |> _assert_eq ((0i32, 2i32, 6i32), (2i32, 0i32, 6i32))
__assert_eq { expected = ((0, 2, 6), 2, 0, 6) }
:= walled_grid (p : position) : bool = (p::x = 5 && p::y >= 2 && p::y <= 8) || (p::x = 2 && p::y = 6)

:= grid_cell (i : i32) : position = position (i % 11) (i / 11) 7

:= grid_hash (hash : i32) (p : position) : i32 = (hash * 31 + p::y * 11 + p::x) % 1000003

:= grid_sees (s : position) (p : position) : bool = walled_grid p = !- && sight_line_clear walled_grid s p

:~ ^ grid_scan (s : position) (i : i32) (clear : i32) (hash : i32) : i32 * i32 =
    := p = grid_cell i
    ? i >= 121 ?> clear, hash
    |? grid_sees s p ?> grid_scan s (i + 1) (clear + 1) (grid_hash hash p)
    |: grid_scan s (i + 1) clear hash

sight_lines_match_canary_check_sight_line_on_a_walled_grid (test)

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

:= ~source_x = 0i32
:= from x y = grid_scan (position (source_x + x) y 7) 0 0 0
(from 0 5, from 9 0, from 7 10) |> _assert_eq ((55i32, 733935i32), (75i32, 978411i32), (73i32, 128078i32))
__assert_eq { expected = ((55, 733935), (75, 978411), 73, 128078) }
:= grid_clear (a : i32) (b : i32) (c : i32) (d : i32) : bool =
    sight_line_clear walled_grid (position a b 7) (position c d 7)

sight_lines_are_blocked_or_clear_like_canary_for_named_pairs (test)

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

:= ~x = 0i32
(grid_clear x 5 10 5, grid_clear x 5 10 1, grid_clear x 0 10 1, grid_clear 4 4 6 9, grid_clear 6 9 4 4)
|> _assert_eq (!-, !-, !+, !-, !-)
__assert_eq { expected = (false, false, true, false, false) }

sight_lines_on_gpu (test)

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

:= ~x = 0i32
? grid_clear x 0 10 1 && grid_clear x 5 10 5 = !- ?> 0i32 |: 1i32

range_needs_same_floor (test)

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

within_range 7 (position 0 0 7) (position 1 1 6) |> _assert_eq !-
within_range 7 (position 0 0 7) (position 7 7 7) |> _assert_eq !+
within_range 7 (position 0 0 7) (position 8 0 7) |> _assert_eq !-
__assert_eq { expected = false }
__assert_eq { expected = true }
__assert_eq { expected = false }

steps_on_gpu (test)

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

:= ~x = 1942i32
:= moved = position x 1346 7 |> step NorthEast
? moved::x = 1943i32 && moved::y = 1345i32 ?> 0i32 |: 1i32

distance_on_gpu (test)

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

:= ~x = 1942i32
:= far = chebyshev_distance (position 0 0 7) (position x 2 7)
? far = 1942i32 && within_range 1 (position x 0 7) (position (x + 1) 1 7) ?> 0i32 |: 1i32

straight_steps_take_the_longer_axis_first (test)

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

:= ~x = 100i32
:= p = position x 100 7
:= ahead = position (x + 3) 101 7
((step_toward ahead p)::x, (step_toward (position x 95 7) p)::y, (step_away ahead p)::x)
|> _assert_eq (101i32, 99i32, 99i32)
__assert_eq { expected = (101, 99, 99) }