GDScript 241 lines
extends GutTest
## Geometry contracts for the 3v3 arena. These pin the map's mirror-fairness across the y = x
## axis and the lane orientation the creep layer depends on. Pure data checks — no engine or
## render coupling, the same discipline as the sim-core tests.
func test_mirror_swaps_components_and_is_its_own_inverse() -> void:
var p := Vector2(120.0, -340.0)
assert_eq(MapData.mirror(p), Vector2(-340.0, 120.0), "the mirror reflects across y = x")
assert_eq(MapData.mirror(MapData.mirror(p)), p, "mirroring twice returns the original point")
func test_nexuses_are_axially_symmetric_and_in_bounds() -> void:
var n0 := MapData.nexus_for_team(0)
var n1 := MapData.nexus_for_team(1)
assert_eq(n1, MapData.mirror(n0), "team 1's nexus must be team 0's nexus mirrored across y = x")
assert_eq(MapData.clamp_to_bounds(n0), n0, "nexus 0 must sit inside the bounds")
assert_eq(MapData.clamp_to_bounds(n1), n1, "nexus 1 must sit inside the bounds")
func test_team_fountains_sit_at_base_and_mirror_across_the_axis() -> void:
var f0 := MapData.spawn_for_team(0)
var f1 := MapData.spawn_for_team(1)
assert_eq(f1, MapData.mirror(f0), "team 1's fountain must be team 0's fountain mirrored")
assert_eq(MapData.clamp_to_bounds(f0), f0, "the fountain must sit inside the bounds")
# It is at base: closer to its own nexus than to the map centre.
var nexus := MapData.nexus_for_team(0)
assert_true(
f0.distance_to(nexus) < f0.length(),
"a fountain spawns at its base, not out near the centre",
)
func test_lane_path_orients_from_each_team_nexus_to_the_enemy_nexus() -> void:
for lane in MapData.lane_count():
var team0 := MapData.lane_path(lane, 0)
var team1 := MapData.lane_path(lane, 1)
assert_eq(team0[0], MapData.nexus_for_team(0), "team 0 starts at its own nexus")
assert_eq(team0[team0.size() - 1], MapData.nexus_for_team(1), "team 0 ends at the enemy nexus")
assert_eq(team1[0], MapData.nexus_for_team(1), "team 1 starts at its own nexus")
assert_eq(team1[team1.size() - 1], MapData.nexus_for_team(0), "team 1 ends at the enemy nexus")
func test_lane_path_for_each_team_is_the_reverse_of_the_other() -> void:
for lane in MapData.lane_count():
var forward := MapData.lane_path(lane, 0)
var reversed := MapData.lane_path(lane, 1)
reversed.reverse()
assert_eq(forward, reversed, "a corridor is one path walked in opposite directions")
func test_lane_path_returns_a_fresh_copy() -> void:
var first := MapData.lane_path(0, 0)
first.reverse()
var second := MapData.lane_path(0, 0)
assert_ne(first, second, "mutating a returned path must not alter the stored geometry")
func test_each_lane_is_its_own_axial_reflection() -> void:
# Mirror-and-reverse must map each corridor onto itself, so the two teams walk a lane the
# same way and neither has a shorter or safer route.
for lane in MapData.lane_count():
var path := MapData.lane_path(lane, 0)
var mirrored := PackedVector2Array()
for i in range(path.size() - 1, -1, -1):
mirrored.append(MapData.mirror(path[i]))
assert_eq(mirrored, path, "each lane must be its own reflection across the y = x axis")
func test_jungle_camps_are_closed_under_the_axis_mirror_and_in_bounds() -> void:
var camps := MapData.JUNGLE_CAMPS
for camp in camps:
assert_eq(MapData.clamp_to_bounds(camp), camp, "every camp must sit inside the bounds")
assert_true(camps.has(MapData.mirror(camp)), "every camp must have a mirror partner across y = x")
func test_two_jungle_camps_are_neutral_on_the_axis() -> void:
# The camps sitting on the y = x axis are their own mirror — the shared, team-neutral camps.
# The map has exactly two; the rest are off-axis mirror pairs, one per side.
var neutral := 0
for camp in MapData.JUNGLE_CAMPS:
if is_equal_approx(camp.x, camp.y):
neutral += 1
assert_eq(neutral, 2, "exactly two jungle camps are neutral (on the y = x axis)")
func test_tower_positions_are_axially_symmetric_between_teams() -> void:
var team0 := MapData.tower_positions(0)
var team1 := MapData.tower_positions(1)
assert_eq(team0.size(), team1.size(), "both teams field the same number of towers")
for i in team0.size():
assert_eq(team1[i], MapData.mirror(team0[i]), "team 1's towers must be team 0's mirrored")
func test_tower_positions_are_in_bounds() -> void:
for team in MapData.NEXUS_POSITIONS.size():
for tower in MapData.tower_positions(team):
assert_eq(MapData.clamp_to_bounds(tower), tower, "every tower must sit inside the bounds")
func test_tower_positions_returns_a_fresh_copy() -> void:
var first := MapData.tower_positions(0)
first[0] = Vector2.ZERO
var second := MapData.tower_positions(0)
assert_ne(first[0], second[0], "mutating a returned tower list must not alter the stored slots")
func test_squad_spawn_of_one_falls_back_to_the_fountain() -> void:
for team in MapData.NEXUS_POSITIONS.size():
assert_eq(
MapData.squad_spawn(team, 0, 1),
MapData.spawn_for_team(team),
"a squad of one spawns on the bare fountain",
)
func test_squad_spawn_fans_a_team_into_distinct_in_bounds_seats() -> void:
var count := 3
for team in MapData.NEXUS_POSITIONS.size():
var seen: Array[Vector2] = []
for i in count:
var seat := MapData.squad_spawn(team, i, count)
assert_eq(MapData.clamp_to_bounds(seat), seat, "every squad seat sits inside the bounds")
assert_false(seen.has(seat), "squadmates spawn on distinct points, not stacked")
seen.append(seat)
func test_squad_spawn_is_axially_symmetric_between_teams() -> void:
var count := 3
for i in count:
assert_eq(
MapData.squad_spawn(1, i, count),
MapData.mirror(MapData.squad_spawn(0, i, count)),
"team 1's squad seat must be team 0's mirrored, so neither side has an edge",
)
func test_squad_spawn_fan_is_centred_on_the_fountain() -> void:
# The fan is symmetric about the fountain, so the seats average back to it.
var count := 3
var sum := Vector2.ZERO
for i in count:
sum += MapData.squad_spawn(0, i, count)
var centre := sum / float(count)
assert_almost_eq(
centre, MapData.spawn_for_team(0), Vector2(0.01, 0.01), "the squad fan centres on the fountain"
)
func test_river_is_axially_symmetric_and_in_bounds() -> void:
# Mirror-and-reverse must map the river onto itself, so it divides the map evenly and
# neither team has more water in its half.
var river := MapData.river_polyline()
var mirrored := PackedVector2Array()
for i in range(river.size() - 1, -1, -1):
mirrored.append(MapData.mirror(river[i]))
assert_eq(mirrored, river, "the river must be its own reflection across the y = x axis")
for point in river:
assert_eq(MapData.clamp_to_bounds(point), point, "every river point sits inside the bounds")
func test_river_polyline_returns_a_fresh_copy() -> void:
var first := MapData.river_polyline()
first.reverse()
var second := MapData.river_polyline()
assert_ne(first, second, "mutating the returned river must not alter the stored course")
func test_each_team_holds_four_towers() -> void:
# Two towers ring the nexus and two stand forward down the lanes — four a side, mirrored.
assert_eq(MapData.tower_positions(0).size(), 4, "each team fields four towers")
assert_eq(MapData.tower_positions(1).size(), 4, "both teams field the same count")
# --- collision obstacles ---------------------------------------------------------------------
func test_obstacles_are_mirror_symmetric() -> void:
var obs := MapData.obstacles()
assert_gt(obs.size(), 0, "the map has collision obstacles")
for o in obs:
var mirrored: Vector2 = MapData.mirror(o["center"])
var found := false
for q in obs:
if q["center"].distance_to(mirrored) < 0.01 and absf(q["radius"] - o["radius"]) < 0.01:
found = true
break
assert_true(found, "every obstacle has its y = x mirror in the set — collision is team-fair")
func test_vision_blockers_are_the_walls_only_and_mirror_symmetric() -> void:
# Sight is blocked by terrain, not buildings: the vision blockers are the jungle walls — every
# one a WALL_RADIUS circle on a wall point — and never a tower or the nexus.
var blockers := MapData.vision_blockers()
assert_eq(blockers.size(), MapData.jungle_wall_points().size(), "one sight blocker per wall point")
for b in blockers:
assert_eq(b["radius"], MapData.WALL_RADIUS, "a sight blocker is a wall-footprint circle")
for team in MapData.NEXUS_POSITIONS.size():
var structures := PackedVector2Array(MapData.tower_positions(team))
structures.append(MapData.nexus_for_team(team))
for s in structures:
for b in blockers:
assert_gt(b["center"].distance_to(s), 1.0, "a structure is not a sight blocker")
for b in blockers:
var mirrored: Vector2 = MapData.mirror(b["center"])
var found := false
for q in blockers:
if q["center"].distance_to(mirrored) < 0.01:
found = true
break
assert_true(found, "every sight blocker has its y = x mirror — vision is team-fair")
func test_spawns_and_camp_centres_are_walkable() -> void:
for team in MapData.NEXUS_POSITIONS.size():
assert_false(
MapData.point_blocked(MapData.spawn_for_team(team), SimCore.UNIT_RADIUS),
"a team spawns on free ground, never walled in",
)
for camp in MapData.JUNGLE_CAMPS:
assert_false(
MapData.point_blocked(camp, SimCore.UNIT_RADIUS),
"a camp's interior stays clear of its own pocket rocks",
)
func test_slide_pushes_a_unit_out_of_an_obstacle_and_leaves_free_ground_alone() -> void:
var obs := MapData.obstacles()
var center: Vector2 = obs[0]["center"]
var radius: float = obs[0]["radius"]
# Driven straight at the obstacle's centre, the unit is stopped outside it.
var from := center + Vector2(radius + 500.0, 0.0)
var resolved := MapData.slide(from, center, SimCore.UNIT_RADIUS)
assert_gt(resolved.distance_to(center), radius, "slide keeps a unit out of the obstacle it enters")
# A move that ends on open ground (the map centre is clear) is returned untouched.
assert_eq(
MapData.slide(Vector2.ZERO, Vector2(5.0, 0.0), SimCore.UNIT_RADIUS),
Vector2(5.0, 0.0),
"slide leaves a clear destination unchanged",
)