From 89d57ef544e44f573b308e02ff9bd2801a650a40 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 26 Feb 2026 12:51:16 -0800 Subject: [PATCH 01/52] Delete unused penalty_setup_tactic_test.cpp --- src/software/ai/hl/stp/tactic/BUILD | 10 ---------- .../ai/hl/stp/tactic/penalty_setup_tactic_test.cpp | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 src/software/ai/hl/stp/tactic/penalty_setup_tactic_test.cpp diff --git a/src/software/ai/hl/stp/tactic/BUILD b/src/software/ai/hl/stp/tactic/BUILD index c69db8f0a3..253bdeaad6 100644 --- a/src/software/ai/hl/stp/tactic/BUILD +++ b/src/software/ai/hl/stp/tactic/BUILD @@ -1,15 +1,5 @@ package(default_visibility = ["//visibility:public"]) -cc_test( - name = "penalty_setup_tactic_test", - srcs = ["penalty_setup_tactic_test.cpp"], - deps = [ - "//shared/test_util:tbots_gtest_main", - "//software/ai/hl/stp/tactic/move:move_tactic", - "//software/test_util", - ], -) - cc_library( name = "all_tactics", hdrs = ["all_tactics.h"], diff --git a/src/software/ai/hl/stp/tactic/penalty_setup_tactic_test.cpp b/src/software/ai/hl/stp/tactic/penalty_setup_tactic_test.cpp deleted file mode 100644 index 9535174636..0000000000 --- a/src/software/ai/hl/stp/tactic/penalty_setup_tactic_test.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "software/ai/hl/stp/tactic/move/move_tactic.h" -#include "software/test_util/test_util.h" - -TEST(PenaltySetupTacticTest, constructor_test) -{ - PenaltySetupTactic tactic = - PenaltySetupTactic(std::make_shared()); -} From c7261bbe913ff2c861db5835696ac59b07b95998 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 26 Feb 2026 13:24:41 -0800 Subject: [PATCH 02/52] Port move_tactic_test, refactor a bit, write notes for porting --- PORTING_CPP_TO_PYTEST.txt | 125 +++++++ TODO.txt | 36 ++ .../message_translation/tbots_protobuf.py | 10 +- .../ai/hl/stp/play/passing_sim_test.py | 25 +- src/software/ai/hl/stp/tactic/move/BUILD | 17 +- .../ai/hl/stp/tactic/move/move_tactic_test.py | 314 ++++++++++++++++++ src/software/python_bindings.cpp | 5 + 7 files changed, 514 insertions(+), 18 deletions(-) create mode 100644 PORTING_CPP_TO_PYTEST.txt create mode 100644 TODO.txt create mode 100644 src/software/ai/hl/stp/tactic/move/move_tactic_test.py diff --git a/PORTING_CPP_TO_PYTEST.txt b/PORTING_CPP_TO_PYTEST.txt new file mode 100644 index 0000000000..77c5cf4e85 --- /dev/null +++ b/PORTING_CPP_TO_PYTEST.txt @@ -0,0 +1,125 @@ +Notes for porting simulated c++ tests to python + +The notes follow the format of: c++ -> python + +Note: there may be some validation functions in c++ that don't exist in python, + mark with TODO (#2558): validate for (... c++ validation) + +example: + +eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + # TODO (#2558): validate robot is at orientation half + # TODO (#2558): validate robot is has angular velocity zero + ] +] + +--- + +terminating_validation_functions -> eventually_validation_sequence_set + +std::vector terminating_validation_functions = { + [destination, tactic](std::shared_ptr world_ptr, + ValidationCoroutine::push_type& yield) + { + while (!tactic->done()) + { + yield("Tactic not done"); + } + robotAtPosition(1, world_ptr, destination, 0.05, yield); + // Check that conditions hold for 1000 ticks + unsigned num_ticks = 1000; + for (unsigned i = 0; i < num_ticks; i++) + { + robotAtPosition(1, world_ptr, destination, 0.05, yield); + } +}}; + +-> + +eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + ] +] + +--- + +non_terminating_validation_functions -> always_validation_sequence_set + +--- + +auto tactic = std::make_shared(std::make_shared()); +tactic->updateControlParams(destination, Angle::zero()); +setTactic(1, tactic); + +-> + +params = AssignedTacticPlayControlParams() +params.assigned_tactics[1].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) +) +simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params +) + +params = AssignedTacticPlayControlParams() +simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params +) + +--- + +Point initial_position = Point(-3, 1.5); +Point destination = Point(2.5, -1.1); +BallState ball_state(Point(4.5, -3), Vector(0, 0)); +auto friendly_robots = + TestUtil::createStationaryRobotStatesWithId({Point(-3, 2.5), initial_position}); +auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( + {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner()}); + +... + +runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); + +-> + +simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[initial_position], + blue_robot_orientations=[tbots_cpp.Angle.quarter()], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(1, 1), + ball_velocity=tbots_cpp.Vector(0, 0), + ), +) + +--- + +runTest(field_type, ball_state, friendly_robots, enemy_robots, + terminating_validation_functions, non_terminating_validation_functions, + Duration::fromSeconds(10)); + +-> + +simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, +) diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000000..e847f675ba --- /dev/null +++ b/TODO.txt @@ -0,0 +1,36 @@ +Found using: `rg SimulatedErForceSimPlayTestFixture` and manual sorting + +TACTIC TESTS TO PORT + +software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp +software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp +software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp +software/ai/hl/stp/tactic/move/move_tactic_test.cpp +software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp +software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp +software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp +software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp +software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp + + +(these are tests defined for specific tactics but separated for some reason... technically they test more specific interactions but could merge them) + +software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp +software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp +software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp +software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp + +PLAY TESTS TO PORT + +software/ai/hl/stp/play/kickoff_friendly_play_test.cpp +software/ai/hl/stp/play/kickoff_enemy_play_test.cpp +software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp +software/ai/hl/stp/play/stop_play_test.cpp +software/ai/hl/stp/play/stop_play_test.cpp +software/ai/hl/stp/play/example/example_play_test.cpp +software/ai/hl/stp/play/free_kick/free_kick_play_test.cpp +software/ai/hl/stp/play/shoot_or_chip_play_test.cpp +software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp +software/ai/hl/stp/play/crease_defense/crease_defense_play_test.cpp +software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp +software/ai/hl/stp/play/hardware_challenge_plays/pass_endurance_play_test.cpp diff --git a/src/proto/message_translation/tbots_protobuf.py b/src/proto/message_translation/tbots_protobuf.py index a573922f5c..f5e60d752b 100644 --- a/src/proto/message_translation/tbots_protobuf.py +++ b/src/proto/message_translation/tbots_protobuf.py @@ -11,7 +11,7 @@ def create_world_state( blue_robot_locations: list[tbots_cpp.Point], ball_location: tbots_cpp.Point, ball_velocity: tbots_cpp.Vector, - blue_robot_orientations: list[float] = [], + blue_robot_orientations: list[tbots_cpp.Angle] = [], ) -> WorldState: """Initializes the world from a list of robot locations and ball location/velocity. @@ -25,22 +25,24 @@ def create_world_state( """ world_state = WorldState() + # TODO (#2558): add param for robot angular velocity since its used for some tests + for robot_id, robot_location in enumerate(yellow_robot_locations): world_state.yellow_robots[robot_id].CopyFrom( RobotState( global_position=Point( x_meters=robot_location.x(), y_meters=robot_location.y() ), + # Initialize angle of yellow robots the same cause its specified in any tests (yet) global_orientation=Angle(radians=math.pi), ) ) for robot_id, robot_location in enumerate(blue_robot_locations): - orientation = 0 try: - orientation = blue_robot_orientations[robot_id] + orientation = blue_robot_orientations[robot_id].toRadians() except IndexError: - pass + orientation = 0 world_state.blue_robots[robot_id].CopyFrom( RobotState( global_position=Point( diff --git a/src/software/ai/hl/stp/play/passing_sim_test.py b/src/software/ai/hl/stp/play/passing_sim_test.py index b3821d363f..49f79a9500 100644 --- a/src/software/ai/hl/stp/play/passing_sim_test.py +++ b/src/software/ai/hl/stp/play/passing_sim_test.py @@ -1,5 +1,4 @@ import pytest -import math import software.python_bindings as tbots_cpp from proto.import_all_protos import * from software.simulated_tests.simulated_test_fixture import ( @@ -158,7 +157,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(-1.0, 0.0), [tbots_cpp.Point(1.0, 0.0)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [], ), # pass between 2 robots on opposite ends of the field @@ -167,7 +166,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(-3.5, 0.0), [tbots_cpp.Point(3.5, 0.0)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [], ), # TODO: Make Interception Better @@ -178,7 +177,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(0.0, -1.0), [tbots_cpp.Point(0.0, 3.0)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [], ), # pass between 2 robots on opposite ends of the field's diagonal @@ -187,7 +186,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(-3.5, 2.5), [tbots_cpp.Point(3.5, -2.5)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [], ), # straight pass with an enemy in between the 2 robots @@ -196,7 +195,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(-1.0, 0.0), [tbots_cpp.Point(1.5, 0.0)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [tbots_cpp.Point(0.5, 0.0)], ), # pass with a sparse wall of enemy robots in between the 2 robots @@ -205,7 +204,7 @@ def setup_pass_and_robots( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(-2.0, 0.0), [tbots_cpp.Point(1.0, 0.0)], - [0, math.pi], + [tbots_cpp.Angle.zero(), tbots_cpp.Angle.half()], [ tbots_cpp.Point(0.5, 2.0), tbots_cpp.Point(0.5, 1.0), @@ -277,7 +276,7 @@ def test_passing_receive_speed( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(2.0, -2.0), [tbots_cpp.Point(-2.5, 2.0)], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [], ), ( @@ -285,7 +284,7 @@ def test_passing_receive_speed( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(0.5, 0), [tbots_cpp.Point(-0.5, 0)], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [], ), ( @@ -293,7 +292,7 @@ def test_passing_receive_speed( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(0.6, 0), [tbots_cpp.Point(-0.6, 0)], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [], ), ( @@ -306,7 +305,7 @@ def test_passing_receive_speed( tbots_cpp.Point(1, -1), tbots_cpp.Point(2, 0), ], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [], ), ( @@ -314,7 +313,7 @@ def test_passing_receive_speed( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(1, 0), [tbots_cpp.Point(2.5, 2.5), tbots_cpp.Point(-1, 0)], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [], ), ( @@ -322,7 +321,7 @@ def test_passing_receive_speed( tbots_cpp.Vector(0.0, 0.0), tbots_cpp.Point(0.5, 0), [tbots_cpp.Point(-1, 0)], - [math.pi, 0], + [tbots_cpp.Angle.half(), tbots_cpp.Angle.zero()], [ tbots_cpp.Point(0.5, 0.5), tbots_cpp.Point(0.5, -0.5), diff --git a/src/software/ai/hl/stp/tactic/move/BUILD b/src/software/ai/hl/stp/tactic/move/BUILD index 82746f594b..15d3d3e4d2 100644 --- a/src/software/ai/hl/stp/tactic/move/BUILD +++ b/src/software/ai/hl/stp/tactic/move/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -29,7 +31,7 @@ cc_test( ) cc_test( - name = "move_tactic_test", + name = "move_tactic_cpp_test", srcs = ["move_tactic_test.cpp"], deps = [ ":move_tactic", @@ -42,3 +44,16 @@ cc_test( "//software/world", ], ) + +py_test( + name = "move_tactic_test", + srcs = ["move_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py new file mode 100644 index 0000000000..8ddd172b7b --- /dev/null +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -0,0 +1,314 @@ +import software.python_bindings as tbots_cpp +from software.simulated_tests.robot_enters_region import * +from software.simulated_tests.ball_enters_region import * +from software.simulated_tests.ball_moves_in_direction import * +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.message_translation.tbots_protobuf import create_world_state + +field = tbots_cpp.Field.createSSLDivisionBField() + + +def test_move_across_field(simulated_test_runner): + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(2.5, -1.1) + + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ], + ball_location=tbots_cpp.Point(4.5, -3), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + ] + ] + + # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after + + # TODO (#2558): check if there is better way to pass these arguments cleaner + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + ) + + +def test_autochip_move(simulated_test_runner): + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(0, 1.5) + + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ], + ball_location=destination, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick( + autochip_distance_meters=2.0, + ), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + # TODO (#2558): validate that ball gets kicked at an angle zero + ] + ] + + # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +def test_autokick_move(simulated_test_runner): + initial_position = tbots_cpp.Point(-1, -0.5) + destination = tbots_cpp.Point(-1, -1) + + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[initial_position], + blue_robot_orientations=[tbots_cpp.Angle.threeQuarter()], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ], + ball_location=destination, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.threeQuarter() + ), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick( + autokick_speed_m_per_s=3.0, + ), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + # TODO (#2558): validate that ball gets kicked at an angle threeQuarter + ] + ] + + # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + ) + + +def test_spinning_move_clockwise(simulated_test_runner): + initial_position = tbots_cpp.Point(-4, 2) + destination = tbots_cpp.Point(4, 2) + + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[initial_position], + # TODO (#2558): implement initializing robot angular velocities + # blue_robot_angular_velocities=[tbots_cpp.AngularVelocity.quarter()], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(1, 1), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + # TODO (#2558): validate robot is at orientation zero + # TODO (#2558): validate robot is at angular velocity something + # (bug with angular velocities so the original c++ test is actual wrong) + ] + ] + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +def test_spinning_move_counter_clockwise(simulated_test_runner): + initial_position = tbots_cpp.Point(4, 2) + destination = tbots_cpp.Point(-4, 2) + + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + blue_robot_locations=[initial_position], + blue_robot_orientations=[tbots_cpp.Angle.quarter()], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(1, 1), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.half()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + # TODO (#2558): validate robot is at orientation half + # TODO (#2558): validate robot is at angular velocity something + # (bug with angular velocities so the original c++ test is actual wrong) + ] + ] + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 1b792a5dc3..1bdf853cff 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -203,6 +203,11 @@ PYBIND11_MODULE(python_bindings, m) .def(py::self > Angle()) .def_static("fromRadians", &Angle::fromRadians) .def_static("fromDegrees", &Angle::fromDegrees) + .def_static("zero", &Angle::zero) + .def_static("quarter", &Angle::quarter) + .def_static("half", &Angle::half) + .def_static("threeQuarter", &Angle::threeQuarter) + .def_static("full", &Angle::full) .def("toRadians", &Angle::toRadians) // Overloaded .def("__repr__", From 47da103e6434b21b665fd96a34064b860b05d17e Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 26 Feb 2026 13:28:41 -0800 Subject: [PATCH 03/52] Update TODO.txt --- TODO.txt | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/TODO.txt b/TODO.txt index e847f675ba..15fb98d838 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,35 +2,35 @@ Found using: `rg SimulatedErForceSimPlayTestFixture` and manual sorting TACTIC TESTS TO PORT -software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp -software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp -software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp -software/ai/hl/stp/tactic/move/move_tactic_test.cpp -software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp -software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp -software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp -software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp -software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp +src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp +src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp +src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp +src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp +src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp +src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp +src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp +src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp +src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp (these are tests defined for specific tactics but separated for some reason... technically they test more specific interactions but could merge them) -software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp -software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp -software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp -software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp +src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp PLAY TESTS TO PORT -software/ai/hl/stp/play/kickoff_friendly_play_test.cpp -software/ai/hl/stp/play/kickoff_enemy_play_test.cpp -software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp -software/ai/hl/stp/play/stop_play_test.cpp -software/ai/hl/stp/play/stop_play_test.cpp -software/ai/hl/stp/play/example/example_play_test.cpp -software/ai/hl/stp/play/free_kick/free_kick_play_test.cpp -software/ai/hl/stp/play/shoot_or_chip_play_test.cpp -software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp -software/ai/hl/stp/play/crease_defense/crease_defense_play_test.cpp -software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp -software/ai/hl/stp/play/hardware_challenge_plays/pass_endurance_play_test.cpp +src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp +src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp +src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp +src/software/ai/hl/stp/play/stop_play_test.cpp +src/software/ai/hl/stp/play/stop_play_test.cpp +src/software/ai/hl/stp/play/example/example_play_test.cpp +src/software/ai/hl/stp/play/free_kick/free_kick_play_test.cpp +src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp +src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp +src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.cpp +src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp +src/software/ai/hl/stp/play/hardware_challenge_plays/pass_endurance_play_test.cpp From 1007fd6d35970cf49bb126392f72e674345a067a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 27 Feb 2026 10:05:15 -0800 Subject: [PATCH 04/52] pivot kick tk --- PORTING_CPP_TO_PYTEST.txt | 45 +++++++++ .../ai/hl/stp/tactic/pivot_kick/BUILD | 17 +++- .../stp/tactic/pivot_kick/pivot_kick_fsm.cpp | 9 +- .../pivot_kick/pivot_kick_tactic_test.py | 96 +++++++++++++++++++ 4 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py diff --git a/PORTING_CPP_TO_PYTEST.txt b/PORTING_CPP_TO_PYTEST.txt index 77c5cf4e85..718a55c228 100644 --- a/PORTING_CPP_TO_PYTEST.txt +++ b/PORTING_CPP_TO_PYTEST.txt @@ -123,3 +123,48 @@ simulated_test_runner.run_test( ag_always_validation_sequence_set=[[]], test_timeout_s=10, ) + +--- + +INSTANTIATE_TEST_CASE_P( + BallLocations, PivotKickTacticTest, + ::testing::Values( + // place the ball directly to the left of the robot + std::make_tuple(Vector(0, 0.5), Angle::zero()), + // place the ball directly to the right of the robot + std::make_tuple(Vector(0, -0.5), Angle::zero()), + // place the ball directly infront of the robot + std::make_tuple(Vector(0.5, 0), Angle::zero()), + // place the ball directly behind the robot + std::make_tuple(Vector(-0.5, 0), Angle::zero()), + // place the ball in the robots dribbler + std::make_tuple(Vector(ROBOT_MAX_RADIUS_METERS, 0), Angle::zero()), + +-> + +@pytest.mark.parametrize( + "blue_bots, yellow_bots, ball_initial_pos, ball_initial_velocity, should_chip", + [ + # Test auto chip over enemy + ( + friendly_defense_area_front_center + tbots_cpp.Vector(0.5, 0), + friendly_defense_area_front_center + tbots_cpp.Vector(1.1, 0), + friendly_defense_area_front_center + tbots_cpp.Vector(0.9, 0), + tbots_cpp.Vector(-2, 0), + True, + ), + # Test block, auto chip off, enemy far + ( + friendly_defense_area_front_center + tbots_cpp.Vector(0.5, 0), + friendly_defense_area_front_center + tbots_cpp.Vector(5, 0), + tbots_cpp.Point(0, 0), + tbots_cpp.Vector(-6, 0), + False, + ), + +--- + +in BUILD + +change name of existing c++ test from eg. pivot_kick_tactic_test -> pivot_kick_tactic_cpp_test, +then, create a py_test with original name eg. pivot_kick_tactic_test diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD index 34bc30121f..b47996b143 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD +++ b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -32,7 +34,7 @@ cc_test( ) cc_test( - name = "pivot_kick_tactic_test", + name = "pivot_kick_tactic_cpp_test", srcs = ["pivot_kick_tactic_test.cpp"], deps = [ ":pivot_kick_tactic", @@ -45,3 +47,16 @@ cc_test( "//software/world", ], ) + +py_test( + name = "pivot_kick_tactic_test", + srcs = ["pivot_kick_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp index ab03f6386c..9a0677fcb2 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp @@ -1,6 +1,7 @@ #include "software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.h" #include "software/ai/hl/stp/tactic/move_primitive.h" +#include "software/logger/logger.h" PivotKickFSM::PivotKickFSM(std::shared_ptr ai_config_ptr) : TacticFSM(ai_config_ptr) @@ -33,13 +34,17 @@ bool PivotKickFSM::ballKicked(const Update& event) if (event.control_params.auto_chip_or_kick.auto_chip_kick_mode == AutoChipOrKickMode::AUTOKICK) { - return event.common.world_ptr->ball().hasBallBeenKicked( + bool kicked = event.common.world_ptr->ball().hasBallBeenKicked( event.control_params.kick_direction); + LOG(WARNING) << "using hasBallBeenKicked check " << kicked; + return kicked; } else { // check for separation for chipping since kick angle is not reliable - return !event.common.robot.isNearDribbler( + bool kicked = !event.common.robot.isNearDribbler( event.common.world_ptr->ball().position(), ROBOT_MAX_RADIUS_METERS); + LOG(WARNING) << "using isNearDribbler check " << kicked; + return kicked; } } diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py new file mode 100644 index 0000000000..6c51dbd038 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -0,0 +1,96 @@ +import pytest +import math + +import software.python_bindings as tbots_cpp +from software.simulated_tests.robot_enters_region import * +from software.simulated_tests.ball_enters_region import * +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.message_translation.tbots_protobuf import create_world_state + + +@pytest.mark.parametrize( + "ball_offset_from_robot, angle_to_kick_at", + [ + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), 0), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), 0), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), 0), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), 0), + # place the ball in the robots dribbler + (tbots_cpp.Vector(0.1, 0), 0), + # place the ball directly to the right of the robot, kick backwards + (tbots_cpp.Vector(0, -0.5), math.pi), + # place the ball directly infront of the robot, kick backwards + (tbots_cpp.Vector(0.5, 0), math.pi), + # place the ball in the robots dribbler + (tbots_cpp.Vector(0.1, 0), 0), + ], +) +def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): + robot_position = tbots_cpp.Point(0, 0) + ball_position = robot_position + ball_offset_from_robot + + # Setup World + def setup(*args): + robot_position = tbots_cpp.Point(0, 0) + ball_position = robot_position + ball_offset_from_robot + simulated_test_runner.set_worldState( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_position, + ], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=ball_position, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + blue_play = Play() + blue_play.name = PlayName.AssignedTacticsPlay + simulated_test_runner.set_play(blue_play, True) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].pivot_kick.CopyFrom( + PivotKickTactic( + kick_origin=tbots_cpp.createPointProto(ball_position), + kick_direction=tbots_cpp.createAngleProto( + tbots_cpp.Angle.fromRadians(angle_to_kick_at) + ), + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), + ) + ) + simulated_test_runner.set_tactics(params, True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, False) + + kick_direction_vector = tbots_cpp.Vector(1, 0).rotate( + tbots_cpp.Angle.fromRadians(angle_to_kick_at) + ) + kick_target = ball_position + kick_direction_vector * 3 + + eventually_validation_sequence_set = [ + [ + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_position, 0.3)]), + BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 1.0)]), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=3, + ) + + +if __name__ == "__main__": + pytest_main(__file__) From 793298d6c0fab016c5fba48f76339305c2ff3255 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 27 Feb 2026 16:25:02 -0800 Subject: [PATCH 05/52] Fix robots not loading --- .../simulated_tests/simulated_test_fixture.py | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index ad2d319dbb..ba70e7f666 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -17,6 +17,7 @@ from software.thunderscope.binary_context_managers.simulator import Simulator from software.thunderscope.binary_context_managers.game_controller import Gamecontroller from software.thunderscope.thunderscope_config import configure_simulated_test_view +from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.logger.logger import create_logger from typing import override @@ -26,7 +27,6 @@ LAUNCH_DELAY_S = 0.1 WORLD_BUFFER_TIMEOUT = 0.5 PROCESS_BUFFER_DELAY_S = 0.01 -TEST_START_DELAY_S = 0.01 PAUSE_AFTER_FAIL_DELAY_S = 3 @@ -90,6 +90,30 @@ def __stopper(self, delay=PROCESS_BUFFER_DELAY_S): if self.thunderscope: self.thunderscope.close() + def sync_setup(self, setup): + """Run setup until simulator has received game state + + :param setup: Function that sets up the world state + """ + world_state_received_buffer = ThreadSafeBuffer(1, WorldStateReceivedTrigger) + self.simulator_proto_unix_io.register_observer( + WorldStateReceivedTrigger, world_state_received_buffer + ) + + while True: + setup() + + try: + world_state_received_buffer.get( + block=True, timeout=WORLD_BUFFER_TIMEOUT + ) + except queue.Empty: + # Did not receive a response within timeout period + continue + else: + # Received a response from the simulator + break + def runner( self, always_validation_sequence_set=[[]], @@ -259,14 +283,6 @@ def run_test( test_timeout_s[index] if type(test_timeout_s) == list else test_timeout_s ) - # Start the test with a delay to allow the simulator to receive - # the initial world state. Without this delay, the SimulatorTick - # message may be received before the initial world state, causing - # the world to be empty, failing some AlwaysValidations - # TODO (#2858): Replace delay with an actual feedback from the simulator - # for when it has received the initial world state - time.sleep(TEST_START_DELAY_S) - # If thunderscope is enabled, run the test in a thread and show # thunderscope on this thread. The excepthook is setup to catch # any test failures and propagate them to the main thread @@ -332,7 +348,7 @@ def run_test( """ threading.excepthook = self.excepthook - setup(params[0]) + super().sync_setup(lambda: setup(params[0])) super().run_test( inv_always_validation_sequence_set, @@ -377,7 +393,7 @@ def run_test( # Catches Assertion Error thrown by failing test and increments counter # Calculates overall results and prints them for x in range(len(params)): - setup(params[x]) + super().sync_setup(lambda: setup(params[x])) try: super().run_test( From 2b267794f581c3f9eeaa8ba6659a8ac272f7db64 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 11:56:44 -0800 Subject: [PATCH 06/52] Test run_till_end default false time save --- src/software/simulated_tests/simulated_test_fixture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index ba70e7f666..3644fba537 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -121,7 +121,7 @@ def runner( test_timeout_s=3, tick_duration_s=0.0166, # Default to 60hz ci_cmd_with_delay=[], - run_till_end=True, + run_till_end=False, ): """Run a test From d207cb22fbcd0ac9400c55b919aca9750b2e11e8 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 12:39:35 -0800 Subject: [PATCH 07/52] Add note to proto config widget --- src/software/thunderscope/common/proto_configuration_widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 98eb26693c..aa4fc531de 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -16,6 +16,8 @@ class ProtoConfigurationWidget(QWidget): """Creates a searchable parameter widget that can take any protobuf, and convert it into a pyqtgraph ParameterTree. This will allow users to modify the values. + + NOTE: If this widget is initialized, it will send a config proto with params """ DELAYED_CONFIGURATION_TIMEOUT_S = 5 From 57f95b5b390efe51e21c44eb23f9bfcff31ac137 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 12:51:59 -0800 Subject: [PATCH 08/52] Get rid of star import --- .../thunderscope/thunderscope_config.py | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index f3c328aee6..0fb5340d32 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -1,20 +1,37 @@ +import os +import signal +from dataclasses import dataclass +from typing import Sequence + +import pyqtgraph +import qdarktheme + from software.thunderscope.common.frametime_counter import FrameTimeCounter -from software.thunderscope.widget_setup_functions import * from software.thunderscope.constants import ProtoUnixIOTypes from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.robot_communication import RobotCommunication -from typing import Sequence -from dataclasses import dataclass from software.thunderscope.thunderscope_types import ( TScopeTab, TScopeWidget, WidgetPosition, WidgetStretchData, ) -import pyqtgraph -import qdarktheme -import signal -import os +from software.thunderscope.widget_setup_functions import ( + IndividualRobotMode, + RobotView, + setup_ball_speed_plot, + setup_diagnostics_widget, + setup_estop_view, + setup_fps_widget, + setup_gl_widget, + setup_log_widget, + setup_parameter_widget, + setup_performance_plot, + setup_play_info, + setup_referee_info, + setup_robot_error_log_view_widget, + setup_robot_view, +) @dataclass @@ -37,7 +54,7 @@ def initialize_application() -> None: signal.signal(signal.SIGINT, signal.SIG_DFL) # Setup MainApp and initialize DockArea - app = pyqtgraph.mkQApp("Thunderscope") + pyqtgraph.mkQApp("Thunderscope") # Setup stylesheet qdarktheme.setup_theme() @@ -71,9 +88,10 @@ def connect_robot_view_to_robot_communication( :param robot_communication: the RobotCommunication instance to connect to """ robot_view_widget.individual_robot_control_mode_signal.connect( - lambda robot_id, - robot_mode: robot_communication.toggle_individual_robot_control_mode( - robot_id, robot_mode + lambda robot_id, robot_mode: ( + robot_communication.toggle_individual_robot_control_mode( + robot_id, robot_mode + ) ) ) From a0c8b024d3426051ec3a17e6180e89478b79cbda Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 13:49:41 -0800 Subject: [PATCH 09/52] Create simplified simulated test view --- .../thunderscope/thunderscope_config.py | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 0fb5340d32..4816e0268b 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -272,6 +272,51 @@ def configure_base_diagnostics( ] + extra_widgets +def configure_base_simulated_test( + full_system_proto_unix_io: ProtoUnixIO, + sim_proto_unix_io: ProtoUnixIO, + friendly_colour_yellow: bool, +) -> list: + """Returns a list of widget data for a FullSystem tab + along with any extra widgets passed in + + :param full_system_proto_unix_io: the proto unix io to configure widgets with + :param sim_proto_unix_io: the proto unix io for the simulator + :param friendly_colour_yellow: if this is Yellow FullSystem (True) or Blue (False) + :param visualization_buffer_size: The size of the visualization buffer. + Increasing this will increase smoothness but will be less realtime. + :param frame_swap_counter: a FrameTimeCounter for the GLWidget to track + the time between frame swaps + :param refresh_counter: a FrameTimeCounter for the refresh function + :return: list of widget data for FullSystem when running simulated tests + """ + return [ + TScopeWidget( + name="Field", + widget=setup_gl_widget( + sim_proto_unix_io=sim_proto_unix_io, + full_system_proto_unix_io=full_system_proto_unix_io, + friendly_colour_yellow=friendly_colour_yellow, + visualization_buffer_size=5, + ), + ), + TScopeWidget( + name="Logs", + widget=setup_log_widget(proto_unix_io=full_system_proto_unix_io), + anchor="Field", + position=WidgetPosition.LEFT, + stretch=WidgetStretchData(x=3), + ), + TScopeWidget( + name="Play Info", + widget=setup_play_info(proto_unix_io=full_system_proto_unix_io), + anchor="Field", + position=WidgetPosition.BOTTOM, + stretch=WidgetStretchData(y=3), + ), + ] + + def configure_two_ai_gamecontroller_view( visualization_buffer_size: int = 5, ) -> TScopeConfig: @@ -339,7 +384,6 @@ def configure_simulated_test_view( simulator_proto_unix_io: ProtoUnixIO, blue_full_system_proto_unix_io: ProtoUnixIO, yellow_full_system_proto_unix_io: ProtoUnixIO, - visualization_buffer_size: int = 5, ) -> TScopeConfig: """Constructs the Thunderscope Config for simulated tests A view with 2 FullSystem tabs (Blue and Yellow) @@ -366,24 +410,20 @@ def configure_simulated_test_view( tabs=[ TScopeTab( name="Blue FullSystem", - widgets=configure_base_fullsystem( + widgets=configure_base_simulated_test( full_system_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=False, - visualization_buffer_size=visualization_buffer_size, - extra_widgets=[], ), ), TScopeTab( name="Yellow FullSystem", - widgets=configure_base_fullsystem( + widgets=configure_base_simulated_test( full_system_proto_unix_io=proto_unix_io_map[ ProtoUnixIOTypes.YELLOW ], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=True, - visualization_buffer_size=visualization_buffer_size, - extra_widgets=[], ), ), ], From 5c44650fcba52a84c707edacdd4dd03b9b7449c3 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 14:04:13 -0800 Subject: [PATCH 10/52] Change signature for sync_setup --- src/software/simulated_tests/simulated_test_fixture.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index 3644fba537..4649bc2c63 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -90,10 +90,11 @@ def __stopper(self, delay=PROCESS_BUFFER_DELAY_S): if self.thunderscope: self.thunderscope.close() - def sync_setup(self, setup): + def sync_setup(self, setup, param): """Run setup until simulator has received game state :param setup: Function that sets up the world state + :param param: Parameter passed into setup """ world_state_received_buffer = ThreadSafeBuffer(1, WorldStateReceivedTrigger) self.simulator_proto_unix_io.register_observer( @@ -101,7 +102,7 @@ def sync_setup(self, setup): ) while True: - setup() + setup(param) try: world_state_received_buffer.get( @@ -348,7 +349,7 @@ def run_test( """ threading.excepthook = self.excepthook - super().sync_setup(lambda: setup(params[0])) + super().sync_setup(setup, params[0]) super().run_test( inv_always_validation_sequence_set, @@ -393,7 +394,7 @@ def run_test( # Catches Assertion Error thrown by failing test and increments counter # Calculates overall results and prints them for x in range(len(params)): - super().sync_setup(lambda: setup(params[x])) + super().sync_setup(setup, params[x]) try: super().run_test( From ec4869f5162a788e176039e1b88eb60d08fcaa78 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 14:43:39 -0800 Subject: [PATCH 11/52] Use best practices for tactic tests --- .../crease_defender_tactic_test.py | 63 ++------ .../stp/tactic/goalie/goalie_tactic_test.py | 102 +++++-------- .../pass_defender_tactic_test.py | 141 ++++++++---------- 3 files changed, 109 insertions(+), 197 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index d470146178..a7ff50e9b4 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -46,21 +46,17 @@ def test_crease_positioning( ball_initial_velocity, simulated_test_runner, ): - # Setup Robot def setup(*args): - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_worldState( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], ball_location=ball_initial_pos, ball_velocity=ball_initial_velocity, - ), + ) ) - # Setup Tactic params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -69,18 +65,11 @@ def setup(*args): ball_steal_mode=BallStealMode.STEAL, ) ) + simulated_test_runner.set_tactics(params, is_friendly=True) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ RobotNeverEntersRegion( @@ -94,10 +83,8 @@ def setup(*args): ] ] - # Eventually Validation eventually_validation_sequence_set = [ [ - # Crease defender should be near the defense area RobotEventuallyEntersRegion( regions=[ tbots_cpp.Field.createSSLDivisionBField() @@ -172,21 +159,17 @@ def test_crease_autochip( should_chip, simulated_test_runner, ): - # Setup Robot def setup(*args): - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_worldState( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], ball_location=ball_initial_pos, ball_velocity=ball_initial_velocity, - ), + ) ) - # Setup Tactic params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -195,31 +178,21 @@ def setup(*args): ball_steal_mode=BallStealMode.STEAL, ) ) + simulated_test_runner.set_tactics(params, is_friendly=True) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ BallIsAlwaysOnGround(), NeverExcessivelyDribbles(), ] ] - # Eventually Validation eventually_validation_sequence_set = [[]] if should_chip: - # Always Validation for chipping always_validation_sequence_set = [[]] - # Eventually Validation for chipping eventually_validation_sequence_set = [[BallIsEventuallyOffGround()]] simulated_test_runner.run_test( @@ -261,21 +234,17 @@ def test_crease_get_ball( should_dribble, simulated_test_runner, ): - # Setup Robot def setup(*args): - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_worldState( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], ball_location=ball_initial_pos, ball_velocity=ball_initial_velocity, - ), + ) ) - # Setup Tactic params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( CreaseDefenderTactic( enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), @@ -284,25 +253,17 @@ def setup(*args): ball_steal_mode=BallStealMode.STEAL, ) ) + simulated_test_runner.set_tactics(params, is_friendly=True) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ BallIsAlwaysOnGround(), NeverExcessivelyDribbles(), ] ] - # Eventually Validation eventually_validation_sequence_set = [[]] if should_dribble: diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index ee0640db37..b2d5ce50d2 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -114,45 +114,25 @@ def test_goalie_blocks_shot( robot_initial_position, simulated_test_runner, ): - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[robot_initial_position], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) + def setup(*args): + simulated_test_runner.set_worldState( + create_world_state( + [], + blue_robot_locations=[robot_initial_position], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ) + ) - # These aren't necessary for this test, but this is just an example - # of how to send commands to the simulator. - # - # NOTE: The gamecontroller responses are automatically handled by - # the gamecontroller context manager class - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.FORCE_START, team=Team.BLUE - ) + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].goalie.CopyFrom( + GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].goalie.CopyFrom( - GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Always Validation always_validation_sequence_set = [ [ RobotNeverEntersRegion( @@ -165,7 +145,6 @@ def test_goalie_blocks_shot( ] ] - # Eventually Validation eventually_validation_sequence_set = [ [ # Goalie should be in the defense area @@ -178,6 +157,7 @@ def test_goalie_blocks_shot( ] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -212,35 +192,29 @@ def test_goalie_clears_from_dead_zone( should_clear, simulated_test_runner, ): - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[ - tbots_cpp.Field.createSSLDivisionBField().friendlyDefenseArea().centre() - ], - ball_location=ball_position, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) + def setup(*args): + simulated_test_runner.set_worldState( + create_world_state( + [], + blue_robot_locations=[ + tbots_cpp.Field.createSSLDivisionBField() + .friendlyDefenseArea() + .centre() + ], + ball_location=ball_position, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].goalie.CopyFrom( - GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].goalie.CopyFrom( + GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ BallNeverEntersRegion( @@ -253,7 +227,6 @@ def test_goalie_clears_from_dead_zone( if should_clear: always_validation_sequence_set = [[]] - # Eventually Validation eventually_validation_sequence_set = [[]] if should_clear: eventually_validation_sequence_set = [ @@ -268,6 +241,7 @@ def test_goalie_clears_from_dead_zone( ] simulated_test_runner.run_test( + setup=setup, test_timeout_s=8, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index 021426462a..3cb54aaef2 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -35,36 +35,28 @@ def test_ball_chipped_on_intercept( position_to_block_from, simulated_test_runner, ): - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[position_to_block_from], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) + def setup(*args): + simulated_test_runner.set_worldState( + create_world_state( + [], + blue_robot_locations=[position_to_block_from], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ) + ) - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), - ball_steal_mode=BallStealMode.STEAL, + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].pass_defender.CopyFrom( + PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + ball_steal_mode=BallStealMode.STEAL, + ) ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=True) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ RobotNeverEntersRegion( @@ -72,17 +64,16 @@ def test_ball_chipped_on_intercept( tbots_cpp.Field.createSSLDivisionBField().friendlyDefenseArea() ] ), - # Defender should intercept ball and prevent it from entering the goal BallNeverEntersRegion( regions=[tbots_cpp.Field.createSSLDivisionBField().friendlyGoal()] ), ] ] - # Eventually Validation eventually_validation_sequence_set = [[BallSpeedEventuallyBelowThreshold(0.4)]] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -117,36 +108,28 @@ def test_avoid_intercept_scenario( position_to_block_from, simulated_test_runner, ): - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[position_to_block_from], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) + def setup(*args): + simulated_test_runner.set_worldState( + create_world_state( + [], + blue_robot_locations=[position_to_block_from], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ) + ) - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), - ball_steal_mode=BallStealMode.STEAL, + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].pass_defender.CopyFrom( + PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + ball_steal_mode=BallStealMode.STEAL, + ) ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=True) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) - # Always Validation always_validation_sequence_set = [ [ # Defender should remain near position_to_block_from @@ -179,10 +162,10 @@ def test_avoid_intercept_scenario( ] ] - # Eventually Validation eventually_validation_sequence_set = [[BallSpeedEventuallyBelowThreshold(0.4)]] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -259,35 +242,28 @@ def test_steal_ball( should_steal, simulated_test_runner, ): - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[position_to_block_from], - yellow_robot_locations=[enemy_kicker_position], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) + def setup(*args): + simulated_test_runner.set_worldState( + create_world_state( + blue_robot_locations=[position_to_block_from], + yellow_robot_locations=[enemy_kicker_position], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ) + ) - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), - ball_steal_mode=BallStealMode.STEAL, + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].pass_defender.CopyFrom( + PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + ball_steal_mode=BallStealMode.STEAL, + ) ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - # Always Validation always_validation_sequence_set = [ [ RobotNeverEntersRegion( @@ -297,7 +273,7 @@ def test_steal_ball( ), NeverExcessivelyDribbles(), ] - ] # Eventually Validation + ] eventually_validation_sequence_set = [[BallSpeedEventuallyBelowThreshold(0.4)]] if should_steal: @@ -311,6 +287,7 @@ def test_steal_ball( ) simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, From ddd0184c37806e6bb9172bdcd626aad8bed73995 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 14:48:12 -0800 Subject: [PATCH 12/52] Rename set_worldState to set_world_state --- .../ai/hl/stp/play/passing_sim_test.py | 2 +- .../crease_defender_tactic_test.py | 6 +++--- .../hl/stp/tactic/goalie/goalie_tactic_test.py | 5 ++--- .../pass_defender/pass_defender_tactic_test.py | 18 ++++++++++++------ .../field_tests/movement_robot_field_test.py | 2 +- .../simulated_tests/simulated_test_fixture.py | 2 +- .../simulated_tests/tbots_test_runner.py | 4 ++-- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/software/ai/hl/stp/play/passing_sim_test.py b/src/software/ai/hl/stp/play/passing_sim_test.py index b3821d363f..90522c10d5 100644 --- a/src/software/ai/hl/stp/play/passing_sim_test.py +++ b/src/software/ai/hl/stp/play/passing_sim_test.py @@ -45,7 +45,7 @@ def setup_pass_and_robots( blue_robot_locations = [attacker_robot_position, *receiver_robot_positions] # Setup the world state - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=enemy_robot_positions, blue_robot_locations=blue_robot_locations, diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index a7ff50e9b4..3f17764a58 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -47,7 +47,7 @@ def test_crease_positioning( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], @@ -160,7 +160,7 @@ def test_crease_autochip( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], @@ -235,7 +235,7 @@ def test_crease_get_ball( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[blue_bots], yellow_robot_locations=[yellow_bots], diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index b2d5ce50d2..c915075666 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -12,7 +12,6 @@ pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team @pytest.mark.parametrize( @@ -115,7 +114,7 @@ def test_goalie_blocks_shot( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[robot_initial_position], @@ -193,7 +192,7 @@ def test_goalie_clears_from_dead_zone( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[ diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index 3cb54aaef2..d6eb0b4ee4 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -36,7 +36,7 @@ def test_ball_chipped_on_intercept( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[position_to_block_from], @@ -48,7 +48,9 @@ def setup(*args): params = AssignedTacticPlayControlParams() params.assigned_tactics[0].pass_defender.CopyFrom( PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), ball_steal_mode=BallStealMode.STEAL, ) ) @@ -109,7 +111,7 @@ def test_avoid_intercept_scenario( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( [], blue_robot_locations=[position_to_block_from], @@ -121,7 +123,9 @@ def setup(*args): params = AssignedTacticPlayControlParams() params.assigned_tactics[0].pass_defender.CopyFrom( PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), ball_steal_mode=BallStealMode.STEAL, ) ) @@ -243,7 +247,7 @@ def test_steal_ball( simulated_test_runner, ): def setup(*args): - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[position_to_block_from], yellow_robot_locations=[enemy_kicker_position], @@ -255,7 +259,9 @@ def setup(*args): params = AssignedTacticPlayControlParams() params.assigned_tactics[0].pass_defender.CopyFrom( PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto(position_to_block_from), + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), ball_steal_mode=BallStealMode.STEAL, ) ) diff --git a/src/software/field_tests/movement_robot_field_test.py b/src/software/field_tests/movement_robot_field_test.py index bd436869bf..aaabbba023 100644 --- a/src/software/field_tests/movement_robot_field_test.py +++ b/src/software/field_tests/movement_robot_field_test.py @@ -40,7 +40,7 @@ # ball_location=tbots_cpp.Point(1, 1), # ball_velocity=tbots_cpp.Point(0, 0), # ) -# simulated_test_runner.set_worldState(initial_worldstate) +# simulated_test_runner.set_world_state(initial_worldstate) # # # Setup Tactic # params = AssignedTacticPlayControlParams() diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index 4649bc2c63..37e1dccb8d 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -61,7 +61,7 @@ def __init__( self.simulator_proto_unix_io = simulator_proto_unix_io @override - def set_worldState(self, worldstate: WorldState): + def set_world_state(self, worldstate: WorldState): """Sets the simulation worldstate :param worldstate: proto containing the desired worldstate diff --git a/src/software/simulated_tests/tbots_test_runner.py b/src/software/simulated_tests/tbots_test_runner.py index 0f69c98730..cd9fbd17f5 100644 --- a/src/software/simulated_tests/tbots_test_runner.py +++ b/src/software/simulated_tests/tbots_test_runner.py @@ -129,12 +129,12 @@ def set_play(self, play: Play, is_friendly: bool): fs_proto_unix_io.send_proto(Play, play) @abstractmethod - def set_worldState(self, worldstate: WorldState): + def set_world_state(self, worldstate: WorldState): """Sets the worldstate for the given team :param worldstate: the worldstate proto to use """ - raise NotImplementedError("abstract class method called set_worldstate") + raise NotImplementedError("abstract class method called set_world_state") @abstractmethod def run_test( From 9e5749939955633ecea117fdf327dd517c40d236 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 15:35:39 -0800 Subject: [PATCH 13/52] Best practices example for ball placement play test --- .../ball_placement_play_test.py | 141 +++++++++--------- .../simulated_tests/tbots_test_runner.py | 10 +- 2 files changed, 71 insertions(+), 80 deletions(-) diff --git a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py index eba1625288..f34362a8e2 100644 --- a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py @@ -1,16 +1,72 @@ import pytest - import software.python_bindings as tbots_cpp from software.py_constants import ENEMY_BALL_PLACEMENT_DISTANCE_METERS -from proto.play_pb2 import Play, PlayName -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_stops_in_region import * -from software.simulated_tests.robot_enters_region import * + +from proto.import_all_protos import * +from proto.ssl_gc_common_pb2 import Team +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.ball_enters_region import ( + BallAlwaysStaysInRegion, + BallEventuallyEntersRegion, +) +from software.simulated_tests.robot_enters_region import RobotEventuallyExitsRegion from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team + + +@pytest.mark.parametrize( + "ball_start_point, ball_placement_point", + [ + # test normal ball placement (not edge case) + (tbots_cpp.Point(2, 2), tbots_cpp.Point(0, 1.5)), + # test when ball starting point is outside of the goal line + (tbots_cpp.Point(-4.7, 2.0), tbots_cpp.Point(0, 0.5)), + # test when ball starting point is outside of the side lines + (tbots_cpp.Point(-2.0, 3.2), tbots_cpp.Point(0, -0.5)), + # test when ball starting point is inside of enemy net + (tbots_cpp.Point(4.0, 0), tbots_cpp.Point(2.0, 2.0)), + # test when ball starting point is in friendly net + (tbots_cpp.Point(-4.0, 0), tbots_cpp.Point(-2.0, 2.0)), + ], +) +def test_two_ai_ball_placement( + simulated_test_runner, ball_start_point, ball_placement_point +): + run_ball_placement_scenario( + simulated_test_runner, ball_start_point, ball_placement_point + ) + + +@pytest.mark.parametrize( + "ball_start_point, ball_placement_point", + [ + # TODO: Remove these ball placement tests until #3561 is resolved + # # 2023 RoboCup ball placement scenarios + # # Scenario 1 + # (tbots_cpp.Point(-0.2, -2.8), tbots_cpp.Point(-0.2, 2.8)), + # # Scenario 2 + # (tbots_cpp.Point(-3.5, -2.25), tbots_cpp.Point(0, 0)), + # # Scenario 3 + # (tbots_cpp.Point(-1.5, -2.25), tbots_cpp.Point(-0.2, -2.8)), + # # Scenario 4 + # (tbots_cpp.Point(-4.4, -2.9), tbots_cpp.Point(-0.2, 2.8)), + # # Scenario 5 + # (tbots_cpp.Point(-0.5, -0), tbots_cpp.Point(-4.3, 2.8)), + # # Scenario 6 + # (tbots_cpp.Point(-1, -3.15), tbots_cpp.Point(-3.5, -2.8)), + # # Scenario 7 + # (tbots_cpp.Point(-1, 3.15), tbots_cpp.Point(-3.5, 2.8)), + # # Scenario 8 + # (tbots_cpp.Point(-4.45, -0.1), tbots_cpp.Point(-0.5, 2.8)), + ], +) +def test_robocup_technical_challenge_placement( + simulated_test_runner, ball_start_point, ball_placement_point +): + run_ball_placement_scenario( + simulated_test_runner, ball_start_point, ball_placement_point, blue_only=True + ) def ball_placement_play_setup( @@ -55,11 +111,11 @@ def ball_placement_play_setup( ] # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) # Pass in placement point here - not required for all play tests - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.BALL_PLACEMENT, team=Team.BLUE, final_ball_placement_point=ball_placement_point, @@ -72,15 +128,12 @@ def ball_placement_play_setup( yellow_play = Play() yellow_play.name = PlayName.HaltPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) + simulated_test_runner.set_play(blue_play, is_friendly=True) if not blue_only: - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(yellow_play, is_friendly=False) # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -90,68 +143,14 @@ def ball_placement_play_setup( ) -@pytest.mark.parametrize( - "ball_start_point, ball_placement_point", - [ - # test normal ball placement (not edge case) - (tbots_cpp.Point(2, 2), tbots_cpp.Point(0, 1.5)), - # test when ball starting point is outside of the goal line - (tbots_cpp.Point(-4.7, 2.0), tbots_cpp.Point(0, 0.5)), - # test when ball starting point is outside of the side lines - (tbots_cpp.Point(-2.0, 3.2), tbots_cpp.Point(0, -0.5)), - # test when ball starting point is inside of enemy net - (tbots_cpp.Point(4.0, 0), tbots_cpp.Point(2.0, 2.0)), - # test when ball starting point is in friendly net - (tbots_cpp.Point(-4.0, 0), tbots_cpp.Point(-2.0, 2.0)), - ], -) -def test_two_ai_ball_placement( - simulated_test_runner, ball_start_point, ball_placement_point -): - run_ball_placement_scenario( - simulated_test_runner, ball_start_point, ball_placement_point - ) - - -@pytest.mark.parametrize( - "ball_start_point, ball_placement_point", - [ - # TODO: Remove these ball placement tests until #3561 is resolved - # # 2023 RoboCup ball placement scenarios - # # Scenario 1 - # (tbots_cpp.Point(-0.2, -2.8), tbots_cpp.Point(-0.2, 2.8)), - # # Scenario 2 - # (tbots_cpp.Point(-3.5, -2.25), tbots_cpp.Point(0, 0)), - # # Scenario 3 - # (tbots_cpp.Point(-1.5, -2.25), tbots_cpp.Point(-0.2, -2.8)), - # # Scenario 4 - # (tbots_cpp.Point(-4.4, -2.9), tbots_cpp.Point(-0.2, 2.8)), - # # Scenario 5 - # (tbots_cpp.Point(-0.5, -0), tbots_cpp.Point(-4.3, 2.8)), - # # Scenario 6 - # (tbots_cpp.Point(-1, -3.15), tbots_cpp.Point(-3.5, -2.8)), - # # Scenario 7 - # (tbots_cpp.Point(-1, 3.15), tbots_cpp.Point(-3.5, 2.8)), - # # Scenario 8 - # (tbots_cpp.Point(-4.45, -0.1), tbots_cpp.Point(-0.5, 2.8)), - ], -) -def test_robocup_technical_challenge_placement( - simulated_test_runner, ball_start_point, ball_placement_point -): - run_ball_placement_scenario( - simulated_test_runner, ball_start_point, ball_placement_point, blue_only=True - ) - - def run_ball_placement_scenario( simulated_test_runner, ball_start_point, ball_placement_point, blue_only=False ): """Runs a ball placement test scenario with the specified parameters. :param simulated_test_runner: The test runner used to simulate robot and ball behavior. - :param ball_start_point: The initial position of the ball (provided by pytest parameterization). - :param ball_placement_point: The target position where the ball should be placed (provided by pytest parameterization). + :param ball_start_point: The initial position of the ball. + :param ball_placement_point: The target position where the ball should be placed. :param blue_only: If True, only the blue team is active; the yellow team is ignored. """ # Placement Eventually Validation diff --git a/src/software/simulated_tests/tbots_test_runner.py b/src/software/simulated_tests/tbots_test_runner.py index cd9fbd17f5..842e1fe97e 100644 --- a/src/software/simulated_tests/tbots_test_runner.py +++ b/src/software/simulated_tests/tbots_test_runner.py @@ -1,7 +1,6 @@ from proto.import_all_protos import * from software.logger.logger import create_logger from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer -from proto.ssl_gc_common_pb2 import Team from abc import abstractmethod logger = create_logger(__name__) @@ -72,7 +71,7 @@ def __init__( def send_gamecontroller_command( self, gc_command: proto.ssl_gc_state_pb2.Command, - is_friendly: bool, + team: proto.ssl_gc_common_pb2.Team, final_ball_placement_point=None, ): """Sends a gamecontroller command that is to be broadcasted to the given team @@ -81,13 +80,6 @@ def send_gamecontroller_command( :param is_friendly: whether the command should be sent to the friendly team :param final_ball_placement_point: where to place the ball in ball placement """ - friendly_team, enemy_team = ( - (Team.YELLOW, Team.BLUE) - if self.is_yellow_friendly - else (Team.BLUE, Team.YELLOW) - ) - team = friendly_team if is_friendly else enemy_team - self.gamecontroller.send_gc_command( gc_command=gc_command, team=team, From 8bd96ededb1cf8c99adece68793999f42f953881 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 17:10:35 -0800 Subject: [PATCH 14/52] Fix hrvo test args --- .../ai/navigator/trajectory/simulated_hrvo_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index cc5dc5d911..197ec87f75 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -8,6 +8,7 @@ from proto.message_translation.tbots_protobuf import create_world_state import math from proto.import_all_protos import * +from proto.ssl_gc_common_pb2 import Team from software.simulated_tests.simulated_test_fixture import SimulatedTestRunner from software.simulated_tests.validation import ( create_validation_types, @@ -186,13 +187,13 @@ def hrvo_setup( # Game Controller Setup simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.STOP, is_friendly=True + gc_command=Command.Type.STOP, team=Team.BLUE ) simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.STOP, is_friendly=False + gc_command=Command.Type.STOP, team=Team.YELLOW ) simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.FORCE_START, is_friendly=True + gc_command=Command.Type.FORCE_START, team=Team.BLUE ) blue_params = AssignedTacticPlayControlParams() From 746082040dc80b7bf33246b5cba81d075bc30fcf Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 17:11:10 -0800 Subject: [PATCH 15/52] Use best practices for play tests --- .../crease_defense_play_test.py | 88 +++++++-------- .../hl/stp/play/defense/defense_play_test.py | 38 ++----- .../enemy_ball_placement_play_test.py | 18 +-- .../enemy_free_kick_play_test.py | 17 +-- .../hl/stp/play/example/example_play_test.py | 19 +--- .../stp/play/free_kick/free_kick_play_test.py | 24 +--- .../hl/stp/play/halt_play/halt_play_test.py | 14 +-- .../ai/hl/stp/play/kickoff_play_test.py | 103 +++++++++--------- .../hl/stp/play/offense/offense_play_test.py | 21 +--- .../ai/hl/stp/play/shoot_or_chip_play_test.py | 11 +- 10 files changed, 133 insertions(+), 220 deletions(-) diff --git a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py index 54c258ba29..e2e7ce29a0 100644 --- a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py +++ b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py @@ -9,68 +9,58 @@ def test_crease_defense_play(simulated_test_runner): - # starting point must be Point - ball_initial_pos = tbots_cpp.Point(0.9, 2.85) - # placement point must be Vector2 to work with game controller - tbots_cpp.Point(-3, -2) + def setup(*args): + ball_initial_pos = tbots_cpp.Point(0.9, 2.85) - # Setup Bots - blue_bots = [ - tbots_cpp.Point(-4.5, 0), - tbots_cpp.Point(-3, 1.5), - tbots_cpp.Point(-3, 0.5), - tbots_cpp.Point(-3, -0.5), - tbots_cpp.Point(-3, -1.5), - tbots_cpp.Point(-3, -3.0), - ] + blue_bots = [ + tbots_cpp.Point(-4.5, 0), + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(-3, 0.5), + tbots_cpp.Point(-3, -0.5), + tbots_cpp.Point(-3, -1.5), + tbots_cpp.Point(-3, -3.0), + ] - yellow_bots = [ - tbots_cpp.Point(1, 3), - tbots_cpp.Point(1, -0.25), - tbots_cpp.Point(1, -1.25), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), - ] + yellow_bots = [ + tbots_cpp.Point(1, 3), + tbots_cpp.Point(1, -0.25), + tbots_cpp.Point(1, -1.25), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), + ] - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.FORCE_START, team=Team.BLUE - ) + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.FORCE_START, team=Team.BLUE + ) - # Force play override here - blue_play = Play() - blue_play.name = PlayName.CreaseDefensePlay + blue_play = Play() + blue_play.name = PlayName.CreaseDefensePlay - yellow_play = Play() - yellow_play.name = PlayName.HaltPlay + yellow_play = Play() + yellow_play.name = PlayName.HaltPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) - # Always Validation - # TODO- #2778 Validation always_validation_sequence_set = [[]] - # Eventually Validation - # TODO- #2778 Validation eventually_validation_sequence_set = [[]] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=25, diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index b17da5777c..337730476c 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -37,33 +37,24 @@ ) def test_defense_play_ball_steal(simulated_test_runner, blue_bots, yellow_bots): def setup(*args): - # Starting point must be Point ball_initial_pos = tbots_cpp.Point(0.93, 0) - # Placement point must be Vector2 to work with game controller - tbots_cpp.Point(-3, -2) - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - # Force play override here blue_play = Play() blue_play.name = PlayName.DefensePlay params = AssignedTacticPlayControlParams() - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_tactics(params, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, @@ -115,34 +106,25 @@ def setup(*args): ) def test_defense_play(simulated_test_runner, blue_bots, yellow_bots): def setup(*args): - # Starting point must be Point ball_initial_pos = tbots_cpp.Point(0.9, 2.85) - # Placement point must be Vector2 to work with game controller - tbots_cpp.Point(-3, -2) - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - # Force play override here blue_play = Play() blue_play.name = PlayName.DefensePlay yellow_play = Play() yellow_play.name = PlayName.ShootOrPassPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py index 815582f104..a6cd7473de 100644 --- a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py @@ -23,7 +23,6 @@ def test_two_ai_ball_placement( simulated_test_runner, ball_start_point, ball_placement_point ): def setup(*args): - # Setup Bots blue_bots = [ tbots_cpp.Point(-4.5, 0), tbots_cpp.Point(-4, 0.5), @@ -46,33 +45,26 @@ def setup(*args): .negXPosYCorner(), ] - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - # Pass in placement point here - not required for all play tests - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.BALL_PLACEMENT, team=Team.YELLOW, final_ball_placement_point=ball_placement_point, ) - # Force play override here blue_play = Play() blue_play.name = PlayName.EnemyBallPlacementPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) + simulated_test_runner.set_play(blue_play, is_friendly=True) yellow_play = Play() yellow_play.name = PlayName.BallPlacementPlay - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index b296df1b45..9b1ee7c9a4 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -102,31 +102,24 @@ def test_enemy_free_kick_play( simulated_test_runner, blue_bots, yellow_bots, ball_initial_pos ): - # Setup Bots def setup(*args): - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.YELLOW ) - # Force play override here blue_play = Play() blue_play.name = PlayName.EnemyFreeKickPlay yellow_play = Play() yellow_play.name = PlayName.FreeKickPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/example/example_play_test.py b/src/software/ai/hl/stp/play/example/example_play_test.py index e17a8da9b3..fee2ec411b 100644 --- a/src/software/ai/hl/stp/play/example/example_play_test.py +++ b/src/software/ai/hl/stp/play/example/example_play_test.py @@ -16,7 +16,6 @@ def test_example_play(simulated_test_runner): ball_initial_pos = tbots_cpp.Point(0, 0) def setup(*args): - # Setup Bots blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), @@ -39,32 +38,26 @@ def setup(*args): .negXPosYCorner(), ] - # Force play override here blue_play = Play() blue_play.name = PlayName.ExamplePlay yellow_play = Play() yellow_play.name = PlayName.HaltPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.BLUE ) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py index 4e24bc392c..1759d9732b 100644 --- a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py @@ -14,38 +14,26 @@ def free_kick_play_setup( blue_bots, yellow_bots, ball_initial_pos, play_name, simulated_test_runner ): - """Sets up the free kick play test - - :param blue_bots: positions of blue robots - :param yellow_bots: positions of yellow robots - :param ball_initial_pos: initial position of the ball - :param play_name: current play being used for blue robots - :param simulated_test_runner: the current test runner - """ - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.DIRECT, team=Team.BLUE ) - # Force play override here blue_play = Play() blue_play.name = play_name yellow_play = Play() yellow_play.name = PlayName.HaltPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py index 830f88f5f2..de9f0d0ef7 100644 --- a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py +++ b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py @@ -11,10 +11,8 @@ # @pytest.mark.parametrize("run_enemy_ai,test_duration", [(False, 20), (True, 20)]) def test_halt_play(simulated_test_runner): def setup(*args): - # starting point must be Point ball_initial_pos = tbots_cpp.Point(0, 0) - # Setup Bots blue_bots = [ tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(-3, 1.5), @@ -37,20 +35,14 @@ def setup(*args): .negXPosYCorner(), ] - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.UNKNOWN ) - # No plays to override. AI does whatever for 3 seconds before HALT CMD - # is issued - - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 521805bc2d..c47304138e 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -18,64 +18,60 @@ def test_kickoff_play(simulated_test_runner, is_friendly_test): ball_initial_pos = tbots_cpp.Point(0, 0) - # Setup Bots - blue_bots = [ - tbots_cpp.Point(-3, 2.5), - tbots_cpp.Point(-3, 1.5), - tbots_cpp.Point(-3, 0.5), - tbots_cpp.Point(-3, -0.5), - tbots_cpp.Point(-3, -1.5), - tbots_cpp.Point(-3, -2.5), - ] - - yellow_bots = [ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), - ] - - blue_play = Play() - yellow_play = Play() + def setup(*args): + blue_bots = [ + tbots_cpp.Point(-3, 2.5), + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(-3, 0.5), + tbots_cpp.Point(-3, -0.5), + tbots_cpp.Point(-3, -1.5), + tbots_cpp.Point(-3, -2.5), + ] + + yellow_bots = [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), + ] + + blue_play = Play() + yellow_play = Play() + + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) + if is_friendly_test: + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.KICKOFF, team=Team.BLUE + ) + blue_play.name = PlayName.KickoffFriendlyPlay + yellow_play.name = PlayName.KickoffEnemyPlay + else: + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.KICKOFF, team=Team.YELLOW + ) + blue_play.name = PlayName.KickoffEnemyPlay + yellow_play.name = PlayName.KickoffFriendlyPlay - if is_friendly_test: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.BLUE - ) - blue_play.name = PlayName.KickoffFriendlyPlay - yellow_play.name = PlayName.KickoffEnemyPlay - else: - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.KICKOFF, team=Team.YELLOW + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.NORMAL_START, team=Team.BLUE ) - blue_play.name = PlayName.KickoffEnemyPlay - yellow_play.name = PlayName.KickoffFriendlyPlay - simulated_test_runner.gamecontroller.send_gc_command( - gc_command=Command.Type.NORMAL_START, team=Team.BLUE - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Force play override here - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto(Play, yellow_play) - - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) # Always Validation always_validation_sequence_set = [[]] @@ -138,6 +134,7 @@ def test_kickoff_play(simulated_test_runner, is_friendly_test): ) simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, test_timeout_s=10, diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 3bc0668499..e13ec72683 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -12,13 +12,8 @@ def test_offense_play(simulated_test_runner): def setup(start_point): - # starting point must be Point ball_initial_pos = start_point - # placement point must be Vector2 to work with game controller - tbots_cpp.Point(-3, -2) - tbots_cpp.Field.createSSLDivisionBField() - # Setup Bots blue_bots = [ tbots_cpp.Point(-4.5, 3.0), tbots_cpp.Point(-2, 1.5), @@ -41,29 +36,23 @@ def setup(start_point): .negXPosYCorner(), ] - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - # Force play override here blue_play = Play() blue_play.name = PlayName.OffensePlay yellow_play = Play() yellow_play.name = PlayName.HaltPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - Play, yellow_play - ) + simulated_test_runner.set_play(blue_play, is_friendly=True) + simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, + simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, blue_robot_locations=blue_bots, diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py index 8b1d8ea44f..52b39444f0 100644 --- a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py +++ b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py @@ -52,22 +52,19 @@ def setup(*args): world_state.yellow_robots[5].CopyFrom(last_robot) - # Game Controller Setup - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) - simulated_test_runner.gamecontroller.send_gc_command( + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) blue_play = Play() blue_play.name = PlayName.ShootOrChipPlay - simulated_test_runner.blue_full_system_proto_unix_io.send_proto(Play, blue_play) + simulated_test_runner.set_play(blue_play, is_friendly=True) - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, world_state - ) + simulated_test_runner.set_world_state(world_state) simulated_test_runner.run_test( setup=setup, From fdcb89d8de3f00d6a471cbeab654342914e117ed Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 17:11:27 -0800 Subject: [PATCH 16/52] Formatting --- .../stp/play/crease_defense/crease_defense_play_test.py | 8 ++++++-- src/software/ai/hl/stp/play/kickoff_play_test.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py index e2e7ce29a0..7768f8950e 100644 --- a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py +++ b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py @@ -26,8 +26,12 @@ def setup(*args): tbots_cpp.Point(1, -0.25), tbots_cpp.Point(1, -1.25), tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), ] simulated_test_runner.send_gamecontroller_command( diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index c47304138e..9431e868b8 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -33,8 +33,12 @@ def setup(*args): tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField().enemyDefenseArea().negXPosYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), ] blue_play = Play() From 6f882e8f2a8a752fa3188cab63aed69e95df6ca4 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 18:29:17 -0800 Subject: [PATCH 17/52] Fix order of setup protos, world state sent first --- .../ball_placement_play_test.py | 20 +++++------ .../crease_defense_play_test.py | 18 +++++----- .../hl/stp/play/defense/defense_play_test.py | 36 +++++++++---------- .../enemy_ball_placement_play_test.py | 18 +++++----- .../enemy_free_kick_play_test.py | 18 +++++----- .../hl/stp/play/example/example_play_test.py | 18 +++++----- .../stp/play/free_kick/free_kick_play_test.py | 18 +++++----- .../hl/stp/play/halt_play/halt_play_test.py | 14 ++++---- .../ai/hl/stp/play/kickoff_play_test.py | 18 +++++----- .../hl/stp/play/offense/offense_play_test.py | 18 +++++----- .../ai/hl/stp/play/shoot_or_chip_play_test.py | 5 ++- 11 files changed, 100 insertions(+), 101 deletions(-) diff --git a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py index f34362a8e2..82bb5bb25e 100644 --- a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py @@ -110,6 +110,16 @@ def ball_placement_play_setup( .negXPosYCorner(), ] + # Create world state + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_start_point, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + # Game Controller Setup simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN @@ -132,16 +142,6 @@ def ball_placement_play_setup( if not blue_only: simulated_test_runner.set_play(yellow_play, is_friendly=False) - # Create world state - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_start_point, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - def run_ball_placement_scenario( simulated_test_runner, ball_start_point, ball_placement_point, blue_only=False diff --git a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py index 7768f8950e..a7324cb57a 100644 --- a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py +++ b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py @@ -34,6 +34,15 @@ def setup(*args): .negXPosYCorner(), ] + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -50,15 +59,6 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - always_validation_sequence_set = [[]] eventually_validation_sequence_set = [[]] diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index 337730476c..ba4ab7a8f1 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -39,6 +39,15 @@ def test_defense_play_ball_steal(simulated_test_runner, blue_bots, yellow_bots): def setup(*args): ball_initial_pos = tbots_cpp.Point(0.93, 0) + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -54,15 +63,6 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_tactics(params, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - simulated_test_runner.run_test( setup=setup, params=[0, 1, 2, 3, 4], # The aggregate test runs 5 times @@ -108,6 +108,15 @@ def test_defense_play(simulated_test_runner, blue_bots, yellow_bots): def setup(*args): ball_initial_pos = tbots_cpp.Point(0.9, 2.85) + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -124,15 +133,6 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - simulated_test_runner.run_test( setup=setup, params=[0, 1, 2, 3, 4], # The aggregate test runs 5 times diff --git a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py index a6cd7473de..f6515ae3a0 100644 --- a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py @@ -45,6 +45,15 @@ def setup(*args): .negXPosYCorner(), ] + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_start_point, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -64,15 +73,6 @@ def setup(*args): simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_start_point, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - always_validation_sequence_set = [ [RobotNeverEntersPlacementRegion(ball_placement_point)] ] diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index 9b1ee7c9a4..2a389e2bad 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -103,6 +103,15 @@ def test_enemy_free_kick_play( simulated_test_runner, blue_bots, yellow_bots, ball_initial_pos ): def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -119,15 +128,6 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - # Always Validation always_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/play/example/example_play_test.py b/src/software/ai/hl/stp/play/example/example_play_test.py index fee2ec411b..31f5ca4536 100644 --- a/src/software/ai/hl/stp/play/example/example_play_test.py +++ b/src/software/ai/hl/stp/play/example/example_play_test.py @@ -38,6 +38,15 @@ def setup(*args): .negXPosYCorner(), ] + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + blue_play = Play() blue_play.name = PlayName.ExamplePlay @@ -57,15 +66,6 @@ def setup(*args): gc_command=Command.Type.DIRECT, team=Team.BLUE ) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - # params just have to be a list of length 1 to ensure the test runs at least once simulated_test_runner.run_test( setup=setup, diff --git a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py index 1759d9732b..c3636d1418 100644 --- a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py @@ -14,6 +14,15 @@ def free_kick_play_setup( blue_bots, yellow_bots, ball_initial_pos, play_name, simulated_test_runner ): + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -33,15 +42,6 @@ def free_kick_play_setup( simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - # We want to test friendly half, enemy half, and at the border of the field @pytest.mark.parametrize( diff --git a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py index de9f0d0ef7..b55f213633 100644 --- a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py +++ b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py @@ -35,13 +35,6 @@ def setup(*args): .negXPosYCorner(), ] - simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.STOP, team=Team.UNKNOWN - ) - simulated_test_runner.send_gamecontroller_command( - gc_command=Command.Type.FORCE_START, team=Team.UNKNOWN - ) - simulated_test_runner.set_world_state( create_world_state( yellow_robot_locations=yellow_bots, @@ -51,6 +44,13 @@ def setup(*args): ), ) + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.FORCE_START, team=Team.UNKNOWN + ) + # params just have to be a list of length 1 to ensure the test runs at least once simulated_test_runner.run_test( setup=setup, diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 9431e868b8..54d4e53be6 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -41,6 +41,15 @@ def setup(*args): .negXPosYCorner(), ] + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + blue_play = Play() yellow_play = Play() @@ -68,15 +77,6 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - # Always Validation always_validation_sequence_set = [[]] diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index e13ec72683..bbb2f84cb5 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -36,6 +36,15 @@ def setup(start_point): .negXPosYCorner(), ] + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=yellow_bots, + blue_robot_locations=blue_bots, + ball_location=ball_initial_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -52,15 +61,6 @@ def setup(start_point): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) - simulated_test_runner.set_world_state( - create_world_state( - yellow_robot_locations=yellow_bots, - blue_robot_locations=blue_bots, - ball_location=ball_initial_pos, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - field = tbots_cpp.Field.createSSLDivisionBField() # Always Validation diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py index 52b39444f0..b6b59307dc 100644 --- a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py +++ b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py @@ -52,6 +52,8 @@ def setup(*args): world_state.yellow_robots[5].CopyFrom(last_robot) + simulated_test_runner.set_world_state(world_state) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.UNKNOWN ) @@ -61,11 +63,8 @@ def setup(*args): blue_play = Play() blue_play.name = PlayName.ShootOrChipPlay - simulated_test_runner.set_play(blue_play, is_friendly=True) - simulated_test_runner.set_world_state(world_state) - simulated_test_runner.run_test( setup=setup, # this array is just so that the test runs 5 times From ac81455f53d7ce8b49d68ca9acb475bfa56774b6 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 18:52:46 -0800 Subject: [PATCH 18/52] Add important comments that were accidentally deleted --- .../ai/hl/stp/play/crease_defense/crease_defense_play_test.py | 2 ++ .../ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py | 1 + 2 files changed, 3 insertions(+) diff --git a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py index a7324cb57a..d3ae9e561c 100644 --- a/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py +++ b/src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.py @@ -59,8 +59,10 @@ def setup(*args): simulated_test_runner.set_play(blue_play, is_friendly=True) simulated_test_runner.set_play(yellow_play, is_friendly=False) + # TODO (#2778): actually add validations always_validation_sequence_set = [[]] + # TODO (#2778): actually add validations eventually_validation_sequence_set = [[]] simulated_test_runner.run_test( diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index d6eb0b4ee4..5df7db7a5d 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -66,6 +66,7 @@ def setup(*args): tbots_cpp.Field.createSSLDivisionBField().friendlyDefenseArea() ] ), + # Defender should intercept ball and prevent it from entering the goal BallNeverEntersRegion( regions=[tbots_cpp.Field.createSSLDivisionBField().friendlyGoal()] ), From d0b34b938b1c21bc7164b6b065987df215439f18 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 22:52:20 -0800 Subject: [PATCH 19/52] Fix order of setup in HRVO test --- .../trajectory/simulated_hrvo_test.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index 197ec87f75..b88322d0ad 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -185,7 +185,15 @@ def hrvo_setup( ball_initial_vel = tbots.Point(0, 0) - # Game Controller Setup + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=enemy_robots_positions, + blue_robot_locations=friendly_robots_positions, + ball_location=ball_initial_pos, + ball_velocity=ball_initial_vel, + ) + ) + simulated_test_runner.send_gamecontroller_command( gc_command=Command.Type.STOP, team=Team.BLUE ) @@ -212,7 +220,6 @@ def hrvo_setup( simulated_test_runner.set_tactics(blue_params, True) - # Setup no tactics on the enemy side yellow_params = AssignedTacticPlayControlParams() for index, destination in enumerate(enemy_robots_destinations): @@ -222,17 +229,6 @@ def hrvo_setup( simulated_test_runner.set_tactics(yellow_params, False) - # Setup Robots - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - yellow_robot_locations=enemy_robots_positions, - blue_robot_locations=friendly_robots_positions, - ball_location=ball_initial_pos, - ball_velocity=ball_initial_vel, - ), - ) - @pytest.mark.parametrize( "friendly_robot_positions,friendly_robot_destinations,friendly_robots_final_orientations," From f1dbcdecdf5ae11595ec56c77f0fafa66b8a8376 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sat, 28 Feb 2026 23:55:00 -0800 Subject: [PATCH 20/52] Fix new pivot kick tactic test --- .../stp/tactic/pivot_kick/pivot_kick_fsm.cpp | 9 ++---- .../pivot_kick/pivot_kick_tactic_test.py | 29 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp index 9a0677fcb2..ab03f6386c 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.cpp @@ -1,7 +1,6 @@ #include "software/ai/hl/stp/tactic/pivot_kick/pivot_kick_fsm.h" #include "software/ai/hl/stp/tactic/move_primitive.h" -#include "software/logger/logger.h" PivotKickFSM::PivotKickFSM(std::shared_ptr ai_config_ptr) : TacticFSM(ai_config_ptr) @@ -34,17 +33,13 @@ bool PivotKickFSM::ballKicked(const Update& event) if (event.control_params.auto_chip_or_kick.auto_chip_kick_mode == AutoChipOrKickMode::AUTOKICK) { - bool kicked = event.common.world_ptr->ball().hasBallBeenKicked( + return event.common.world_ptr->ball().hasBallBeenKicked( event.control_params.kick_direction); - LOG(WARNING) << "using hasBallBeenKicked check " << kicked; - return kicked; } else { // check for separation for chipping since kick angle is not reliable - bool kicked = !event.common.robot.isNearDribbler( + return !event.common.robot.isNearDribbler( event.common.world_ptr->ball().position(), ROBOT_MAX_RADIUS_METERS); - LOG(WARNING) << "using isNearDribbler check " << kicked; - return kicked; } } diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index 6c51dbd038..b1b8d50726 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -13,6 +13,8 @@ @pytest.mark.parametrize( "ball_offset_from_robot, angle_to_kick_at", [ + # TODO (#2859): Flaky, the robot does not dribble far enough into the ball + # place the ball directly to the left of the robot (tbots_cpp.Vector(0, 0.5), 0), # place the ball directly to the right of the robot @@ -23,23 +25,18 @@ (tbots_cpp.Vector(-0.5, 0), 0), # place the ball in the robots dribbler (tbots_cpp.Vector(0.1, 0), 0), - # place the ball directly to the right of the robot, kick backwards - (tbots_cpp.Vector(0, -0.5), math.pi), - # place the ball directly infront of the robot, kick backwards - (tbots_cpp.Vector(0.5, 0), math.pi), - # place the ball in the robots dribbler - (tbots_cpp.Vector(0.1, 0), 0), + # place the ball directly to the left of the robot, kick left + (tbots_cpp.Vector(0, 0.5), math.pi), + # place the ball directly behind the robot, kick left + (tbots_cpp.Vector(-0.5, 0), math.pi), ], ) def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): robot_position = tbots_cpp.Point(0, 0) ball_position = robot_position + ball_offset_from_robot - # Setup World def setup(*args): - robot_position = tbots_cpp.Point(0, 0) - ball_position = robot_position + ball_offset_from_robot - simulated_test_runner.set_worldState( + simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), @@ -51,10 +48,6 @@ def setup(*args): ) ) - blue_play = Play() - blue_play.name = PlayName.AssignedTacticsPlay - simulated_test_runner.set_play(blue_play, True) - params = AssignedTacticPlayControlParams() params.assigned_tactics[1].pivot_kick.CopyFrom( PivotKickTactic( @@ -65,10 +58,10 @@ def setup(*args): auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), ) ) - simulated_test_runner.set_tactics(params, True) + simulated_test_runner.set_tactics(params, is_friendly=True) params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, False) + simulated_test_runner.set_tactics(params, is_friendly=False) kick_direction_vector = tbots_cpp.Vector(1, 0).rotate( tbots_cpp.Angle.fromRadians(angle_to_kick_at) @@ -78,7 +71,7 @@ def setup(*args): eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_position, 0.3)]), - BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 1.0)]), + BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), ] ] @@ -88,7 +81,7 @@ def setup(*args): inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_always_validation_sequence_set=[[]], - test_timeout_s=3, + test_timeout_s=10, ) From e9ea12658a39acc7bf68dfcb22d31d647a69ad42 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sun, 1 Mar 2026 13:30:43 -0800 Subject: [PATCH 21/52] Move pytest validations to its own directory --- src/software/ai/hl/stp/play/BUILD | 6 +- .../ai/hl/stp/play/ball_placement/BUILD | 2 +- .../ball_placement_play_test.py | 6 +- .../ai/hl/stp/play/crease_defense/BUILD | 2 +- src/software/ai/hl/stp/play/defense/BUILD | 2 +- .../hl/stp/play/defense/defense_play_test.py | 4 +- .../ai/hl/stp/play/enemy_ball_placement/BUILD | 2 +- .../enemy_ball_placement_play_test.py | 2 +- .../ai/hl/stp/play/enemy_free_kick/BUILD | 2 +- .../enemy_free_kick_play_test.py | 8 +- src/software/ai/hl/stp/play/example/BUILD | 3 +- .../hl/stp/play/example/example_play_test.py | 4 +- src/software/ai/hl/stp/play/free_kick/BUILD | 2 +- .../stp/play/free_kick/free_kick_play_test.py | 4 +- src/software/ai/hl/stp/play/halt_play/BUILD | 3 +- .../hl/stp/play/halt_play/halt_play_test.py | 2 +- .../ai/hl/stp/play/kickoff_play_test.py | 6 +- src/software/ai/hl/stp/play/offense/BUILD | 2 +- .../hl/stp/play/offense/offense_play_test.py | 6 +- .../ai/hl/stp/play/passing_sim_test.py | 10 +- .../ai/hl/stp/play/shoot_or_chip_play_test.py | 2 +- .../ai/hl/stp/tactic/crease_defender/BUILD | 3 +- .../crease_defender_tactic_test.py | 16 +- src/software/ai/hl/stp/tactic/goalie/BUILD | 3 +- .../stp/tactic/goalie/goalie_tactic_test.py | 14 +- src/software/ai/hl/stp/tactic/move/BUILD | 2 +- .../ai/hl/stp/tactic/move/move_tactic_test.py | 6 +- .../ai/hl/stp/tactic/pass_defender/BUILD | 3 +- .../pass_defender_tactic_test.py | 14 +- .../ai/hl/stp/tactic/pivot_kick/BUILD | 2 +- .../pivot_kick/pivot_kick_tactic_test.py | 5 +- src/software/ai/navigator/trajectory/BUILD | 2 +- .../trajectory/simulated_hrvo_test.py | 4 +- src/software/field_tests/BUILD | 8 +- .../field_tests/field_test_fixture.py | 2 +- .../field_tests/passing_field_test.py | 2 +- src/software/simulated_tests/BUILD | 139 +++++++----------- .../pytest_validations/__init__.py | 0 .../avoid_collisions.py | 2 +- .../ball_enters_region.py | 2 +- .../ball_is_off_ground.py | 2 +- .../ball_moves_forward.py | 2 +- .../ball_moves_from_rest.py | 2 +- .../ball_moves_in_direction.py | 2 +- .../ball_speed_threshold.py | 4 +- .../ball_stops_in_region.py | 2 +- .../excessive_dribbling.py | 2 +- .../friendly_has_ball_possession.py | 2 +- .../friendly_receives_ball_slow.py | 2 +- .../friendly_team_scored.py | 2 +- .../{ => pytest_validations}/or_validation.py | 2 +- .../robot_enters_placement_region.py | 2 +- .../robot_enters_region.py | 2 +- .../robot_enters_region_and_stops.py | 6 +- .../robot_speed_threshold.py | 4 +- .../speed_threshold_helpers.py | 0 .../{ => pytest_validations}/validation.py | 76 +++++----- .../simulated_test_ball_model.py | 16 +- .../simulated_tests/simulated_test_fixture.py | 2 +- 59 files changed, 203 insertions(+), 238 deletions(-) create mode 100644 src/software/simulated_tests/pytest_validations/__init__.py rename src/software/simulated_tests/{ => pytest_validations}/avoid_collisions.py (98%) rename src/software/simulated_tests/{ => pytest_validations}/ball_enters_region.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/ball_is_off_ground.py (97%) rename src/software/simulated_tests/{ => pytest_validations}/ball_moves_forward.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/ball_moves_from_rest.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/ball_moves_in_direction.py (98%) rename src/software/simulated_tests/{ => pytest_validations}/ball_speed_threshold.py (94%) rename src/software/simulated_tests/{ => pytest_validations}/ball_stops_in_region.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/excessive_dribbling.py (97%) rename src/software/simulated_tests/{ => pytest_validations}/friendly_has_ball_possession.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/friendly_receives_ball_slow.py (97%) rename src/software/simulated_tests/{ => pytest_validations}/friendly_team_scored.py (95%) rename src/software/simulated_tests/{ => pytest_validations}/or_validation.py (96%) rename src/software/simulated_tests/{ => pytest_validations}/robot_enters_placement_region.py (97%) rename src/software/simulated_tests/{ => pytest_validations}/robot_enters_region.py (97%) rename src/software/simulated_tests/{ => pytest_validations}/robot_enters_region_and_stops.py (95%) rename src/software/simulated_tests/{ => pytest_validations}/robot_speed_threshold.py (94%) rename src/software/simulated_tests/{ => pytest_validations}/speed_threshold_helpers.py (100%) rename src/software/simulated_tests/{ => pytest_validations}/validation.py (100%) diff --git a/src/software/ai/hl/stp/play/BUILD b/src/software/ai/hl/stp/play/BUILD index db672cbc01..a3deeb5167 100644 --- a/src/software/ai/hl/stp/play/BUILD +++ b/src/software/ai/hl/stp/play/BUILD @@ -169,7 +169,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) @@ -214,7 +214,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) @@ -270,7 +270,7 @@ py_test( "//proto:software_py_proto", "//proto:tbots_py_proto", "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/ball_placement/BUILD b/src/software/ai/hl/stp/play/ball_placement/BUILD index c59998362e..219650ea12 100644 --- a/src/software/ai/hl/stp/play/ball_placement/BUILD +++ b/src/software/ai/hl/stp/play/ball_placement/BUILD @@ -40,7 +40,7 @@ py_test( tags = ["exclusive"], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py index 82bb5bb25e..fbb09deebc 100644 --- a/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/ball_placement/ball_placement_play_test.py @@ -5,11 +5,13 @@ from proto.import_all_protos import * from proto.ssl_gc_common_pb2 import Team from proto.message_translation.tbots_protobuf import create_world_state -from software.simulated_tests.ball_enters_region import ( +from software.simulated_tests.pytest_validations.ball_enters_region import ( BallAlwaysStaysInRegion, BallEventuallyEntersRegion, ) -from software.simulated_tests.robot_enters_region import RobotEventuallyExitsRegion +from software.simulated_tests.pytest_validations.robot_enters_region import ( + RobotEventuallyExitsRegion, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/play/crease_defense/BUILD b/src/software/ai/hl/stp/play/crease_defense/BUILD index f166ec7122..bb6402127a 100644 --- a/src/software/ai/hl/stp/play/crease_defense/BUILD +++ b/src/software/ai/hl/stp/play/crease_defense/BUILD @@ -39,7 +39,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/defense/BUILD b/src/software/ai/hl/stp/play/defense/BUILD index ea875f597c..012f16b25c 100644 --- a/src/software/ai/hl/stp/play/defense/BUILD +++ b/src/software/ai/hl/stp/play/defense/BUILD @@ -42,7 +42,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index ba4ab7a8f1..4fdeab5419 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -2,8 +2,8 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.friendly_has_ball_possession import ( +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import ( FriendlyEventuallyHasBallPossession, ) from proto.message_translation.tbots_protobuf import create_world_state diff --git a/src/software/ai/hl/stp/play/enemy_ball_placement/BUILD b/src/software/ai/hl/stp/play/enemy_ball_placement/BUILD index e8a02bfdc6..c76845ca98 100644 --- a/src/software/ai/hl/stp/play/enemy_ball_placement/BUILD +++ b/src/software/ai/hl/stp/play/enemy_ball_placement/BUILD @@ -31,7 +31,7 @@ py_test( tags = ["exclusive"], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py index f6515ae3a0..01cb4f16cd 100644 --- a/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_ball_placement/enemy_ball_placement_play_test.py @@ -2,7 +2,7 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.robot_enters_placement_region import * +from software.simulated_tests.pytest_validations.robot_enters_placement_region import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/BUILD b/src/software/ai/hl/stp/play/enemy_free_kick/BUILD index 2e6193787a..c538123c6e 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/BUILD +++ b/src/software/ai/hl/stp/play/enemy_free_kick/BUILD @@ -45,7 +45,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index 2a389e2bad..1732e3a142 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -5,12 +5,12 @@ from software.simulated_tests.or_validation import OrValidation -from software.simulated_tests.ball_moves_from_rest import ( +from software.simulated_tests.pytest_validations.ball_moves_from_rest import ( BallEventuallyMovesFromRest, ) -from software.simulated_tests.friendly_team_scored import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.robot_enters_region import ( +from software.simulated_tests.pytest_validations.friendly_team_scored import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.robot_enters_region import ( RobotEventuallyEntersRegion, RobotNeverEntersRegion, ) diff --git a/src/software/ai/hl/stp/play/example/BUILD b/src/software/ai/hl/stp/play/example/BUILD index faa3db67d5..b3ff15e029 100644 --- a/src/software/ai/hl/stp/play/example/BUILD +++ b/src/software/ai/hl/stp/play/example/BUILD @@ -56,8 +56,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/example/example_play_test.py b/src/software/ai/hl/stp/play/example/example_play_test.py index 31f5ca4536..20ab8c1e07 100644 --- a/src/software/ai/hl/stp/play/example/example_play_test.py +++ b/src/software/ai/hl/stp/play/example/example_play_test.py @@ -1,9 +1,9 @@ import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import ( +from software.simulated_tests.pytest_validations.robot_enters_region import ( NumberOfRobotsEventuallyExitsRegion, NumberOfRobotsEventuallyEntersRegion, ) -from software.simulated_tests.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from proto.play_pb2 import Play, PlayName diff --git a/src/software/ai/hl/stp/play/free_kick/BUILD b/src/software/ai/hl/stp/play/free_kick/BUILD index df9375a693..88815024af 100644 --- a/src/software/ai/hl/stp/play/free_kick/BUILD +++ b/src/software/ai/hl/stp/play/free_kick/BUILD @@ -54,7 +54,7 @@ py_test( tags = ["exclusive"], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py index c3636d1418..5187df630b 100644 --- a/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/free_kick/free_kick_play_test.py @@ -2,8 +2,8 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.friendly_team_scored import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.friendly_team_scored import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from software.simulated_tests.simulated_test_fixture import ( diff --git a/src/software/ai/hl/stp/play/halt_play/BUILD b/src/software/ai/hl/stp/play/halt_play/BUILD index a491d9dcb6..d76695e405 100644 --- a/src/software/ai/hl/stp/play/halt_play/BUILD +++ b/src/software/ai/hl/stp/play/halt_play/BUILD @@ -43,8 +43,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py index b55f213633..a6ce7d2f5c 100644 --- a/src/software/ai/hl/stp/play/halt_play/halt_play_test.py +++ b/src/software/ai/hl/stp/play/halt_play/halt_play_test.py @@ -1,5 +1,5 @@ import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from software.simulated_tests.simulated_test_fixture import ( diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 54d4e53be6..35407cf981 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -2,9 +2,9 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_from_rest import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_from_rest import * from proto.import_all_protos import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team diff --git a/src/software/ai/hl/stp/play/offense/BUILD b/src/software/ai/hl/stp/play/offense/BUILD index 4e2658fa63..b01c38acad 100644 --- a/src/software/ai/hl/stp/play/offense/BUILD +++ b/src/software/ai/hl/stp/play/offense/BUILD @@ -39,7 +39,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index bbb2f84cb5..4588b64efe 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -1,8 +1,8 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.friendly_team_scored import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.friendly_has_ball_possession import * +from software.simulated_tests.pytest_validations.friendly_team_scored import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team from software.simulated_tests.simulated_test_fixture import ( diff --git a/src/software/ai/hl/stp/play/passing_sim_test.py b/src/software/ai/hl/stp/play/passing_sim_test.py index 08ea452b35..05b2fbb46e 100644 --- a/src/software/ai/hl/stp/play/passing_sim_test.py +++ b/src/software/ai/hl/stp/play/passing_sim_test.py @@ -5,14 +5,16 @@ pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from software.simulated_tests.friendly_receives_ball_slow import ( +from software.simulated_tests.pytest_validations.friendly_receives_ball_slow import ( FriendlyAlwaysReceivesBallSlow, ) -from software.simulated_tests.friendly_has_ball_possession import ( +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import ( FriendlyEventuallyHasBallPossession, ) -from software.simulated_tests.ball_moves_in_direction import BallMovesForwardInRegions -from software.simulated_tests.ball_enters_region import ( +from software.simulated_tests.pytest_validations.ball_moves_in_direction import ( + BallMovesForwardInRegions, +) +from software.simulated_tests.pytest_validations.ball_enters_region import ( BallEventuallyExitsRegion, BallEventuallyEntersRegion, ) diff --git a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py index b6b59307dc..5584002067 100644 --- a/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py +++ b/src/software/ai/hl/stp/play/shoot_or_chip_play_test.py @@ -3,7 +3,7 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/tactic/crease_defender/BUILD b/src/software/ai/hl/stp/tactic/crease_defender/BUILD index 8cabfce189..c75d4a6e30 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/BUILD +++ b/src/software/ai/hl/stp/tactic/crease_defender/BUILD @@ -59,8 +59,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index 3f17764a58..83989d46a4 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -1,17 +1,17 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * -from software.simulated_tests.friendly_has_ball_possession import * -from software.simulated_tests.ball_speed_threshold import * -from software.simulated_tests.robot_speed_threshold import * -from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import * +from software.simulated_tests.pytest_validations.ball_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.excessive_dribbling import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from software.simulated_tests.ball_is_off_ground import * +from software.simulated_tests.pytest_validations.ball_is_off_ground import * from proto.message_translation.tbots_protobuf import create_world_state diff --git a/src/software/ai/hl/stp/tactic/goalie/BUILD b/src/software/ai/hl/stp/tactic/goalie/BUILD index abdb4d7636..5fbd6a27c4 100644 --- a/src/software/ai/hl/stp/tactic/goalie/BUILD +++ b/src/software/ai/hl/stp/tactic/goalie/BUILD @@ -58,8 +58,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index c915075666..0129e96d35 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -1,13 +1,13 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * -from software.simulated_tests.friendly_has_ball_possession import * -from software.simulated_tests.ball_speed_threshold import * -from software.simulated_tests.robot_speed_threshold import * -from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import * +from software.simulated_tests.pytest_validations.ball_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.excessive_dribbling import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/tactic/move/BUILD b/src/software/ai/hl/stp/tactic/move/BUILD index 15d3d3e4d2..2f0155cf34 100644 --- a/src/software/ai/hl/stp/tactic/move/BUILD +++ b/src/software/ai/hl/stp/tactic/move/BUILD @@ -53,7 +53,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 8ddd172b7b..b9c4a81dd3 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_in_direction import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/tactic/pass_defender/BUILD b/src/software/ai/hl/stp/tactic/pass_defender/BUILD index e87d8f757c..31b9d28f46 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/BUILD +++ b/src/software/ai/hl/stp/tactic/pass_defender/BUILD @@ -45,8 +45,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index 5df7db7a5d..f0574b92f2 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -1,13 +1,13 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * -from software.simulated_tests.friendly_has_ball_possession import * -from software.simulated_tests.ball_speed_threshold import * -from software.simulated_tests.robot_speed_threshold import * -from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import * +from software.simulated_tests.pytest_validations.ball_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.excessive_dribbling import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD index b47996b143..cd501b698a 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/BUILD +++ b/src/software/ai/hl/stp/tactic/pivot_kick/BUILD @@ -56,7 +56,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index b1b8d50726..9ce6780b17 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -2,8 +2,8 @@ import math import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -14,7 +14,6 @@ "ball_offset_from_robot, angle_to_kick_at", [ # TODO (#2859): Flaky, the robot does not dribble far enough into the ball - # place the ball directly to the left of the robot (tbots_cpp.Vector(0, 0.5), 0), # place the ball directly to the right of the robot diff --git a/src/software/ai/navigator/trajectory/BUILD b/src/software/ai/navigator/trajectory/BUILD index e3a243f39c..667735d3dd 100644 --- a/src/software/ai/navigator/trajectory/BUILD +++ b/src/software/ai/navigator/trajectory/BUILD @@ -157,7 +157,7 @@ py_test( deps = [ "//proto/message_translation:py_tbots_protobuf", "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index b88322d0ad..a2ed3b441f 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -2,7 +2,7 @@ from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from software.simulated_tests.avoid_collisions import * +from software.simulated_tests.pytest_validations.avoid_collisions import * import software.python_bindings as tbots from software.py_constants import * from proto.message_translation.tbots_protobuf import create_world_state @@ -10,7 +10,7 @@ from proto.import_all_protos import * from proto.ssl_gc_common_pb2 import Team from software.simulated_tests.simulated_test_fixture import SimulatedTestRunner -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( create_validation_types, create_validation_geometry, ) diff --git a/src/software/field_tests/BUILD b/src/software/field_tests/BUILD index ad76156e99..b742fb70b5 100644 --- a/src/software/field_tests/BUILD +++ b/src/software/field_tests/BUILD @@ -16,7 +16,7 @@ py_library( "//software/networking/unix:threaded_unix_listener_py", "//software/networking/unix:threaded_unix_sender_py", "//software/simulated_tests:tbots_test_runner", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", "//software/thunderscope", "//software/thunderscope:constants", "//software/thunderscope:estop_helpers", @@ -39,7 +39,7 @@ py_test( deps = [ "//software:conftest", "//software/simulated_tests:tbots_test_runner", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) @@ -56,7 +56,7 @@ py_test( deps = [ "//software:conftest", "//software/simulated_tests:tbots_test_runner", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) @@ -68,7 +68,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/field_tests/field_test_fixture.py b/src/software/field_tests/field_test_fixture.py index 42c0c9f6ee..b8758292e7 100644 --- a/src/software/field_tests/field_test_fixture.py +++ b/src/software/field_tests/field_test_fixture.py @@ -7,7 +7,7 @@ import argparse from proto.import_all_protos import * -from software.simulated_tests import validation +from software.simulated_tests.pytest_validations import validation from software.thunderscope.constants import EstopMode, IndividualRobotMode from software.thunderscope.thunderscope import Thunderscope from software.thunderscope.proto_unix_io import ProtoUnixIO diff --git a/src/software/field_tests/passing_field_test.py b/src/software/field_tests/passing_field_test.py index 9899c6b63a..1af6b8ce29 100644 --- a/src/software/field_tests/passing_field_test.py +++ b/src/software/field_tests/passing_field_test.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * from software.field_tests.field_test_fixture import * -from software.simulated_tests.friendly_receives_ball_slow import ( +from software.simulated_tests.pytest_validations.friendly_receives_ball_slow import ( FriendlyAlwaysReceivesBallSlow, ) from software.simulated_tests.simulated_test_fixture import ( diff --git a/src/software/simulated_tests/BUILD b/src/software/simulated_tests/BUILD index 2b33a53da5..87691949ec 100644 --- a/src/software/simulated_tests/BUILD +++ b/src/software/simulated_tests/BUILD @@ -9,23 +9,54 @@ compile_pip_requirements( requirements_txt = "requirements_lock.txt", ) -py_test( - name = "simulated_test_ball_model", +# TODO: move somewhere else since its use in field test fixture so this makes no sense in simulated_tests +py_library( + name = "tbots_test_runner", srcs = [ - "simulated_test_ball_model.py", + "tbots_test_runner.py", ], - # TODO (#2619) Remove tag to run in parallel - tags = [ - "exclusive", + data = [ + "//software:py_constants.so", ], deps = [ - "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", - requirement("pytest"), + "//proto:import_all_protos", + "//software/logger:py_logger", + "//software/networking/unix:threaded_unix_listener_py", + "//software/networking/unix:threaded_unix_sender_py", + "//software/simulated_tests/pytest_validations:all_validations", + "//software/thunderscope", + "//software/thunderscope:config", + "//software/thunderscope:constants", + "//software/thunderscope:time_provider", + "//software/thunderscope/binary_context_managers:full_system", + "//software/thunderscope/binary_context_managers:game_controller", + "//software/thunderscope/binary_context_managers:simulator", + ], +) + +py_library( + name = "simulated_test_fixture", + srcs = [ + "simulated_test_fixture.py", + ], + data = [ + "//software:py_constants.so", + ], + deps = [ + ":tbots_test_runner", + "//proto:import_all_protos", + "//software/logger:py_logger", + "//software/networking/unix:threaded_unix_listener_py", + "//software/networking/unix:threaded_unix_sender_py", + "//software/simulated_tests/pytest_validations:all_validations", + "//software/thunderscope", + "//software/thunderscope/binary_context_managers:full_system", + "//software/thunderscope/binary_context_managers:game_controller", + "//software/thunderscope/binary_context_managers:simulator", ], ) +# TODO (#2581): remove cc_library( name = "simulated_er_force_sim_test_fixture", testonly = True, @@ -49,6 +80,7 @@ cc_library( ], ) +# TODO (#2581): remove cc_library( name = "simulated_er_force_sim_play_test_fixture", testonly = True, @@ -61,86 +93,19 @@ cc_library( ], ) -py_library( - name = "simulated_test_fixture", - srcs = [ - "simulated_test_fixture.py", - ], - data = [ - "//software:py_constants.so", - ], - deps = [ - ":tbots_test_runner", - "//proto:import_all_protos", - "//software/logger:py_logger", - "//software/networking/unix:threaded_unix_listener_py", - "//software/networking/unix:threaded_unix_sender_py", - "//software/simulated_tests:validation", - "//software/thunderscope", - "//software/thunderscope/binary_context_managers:full_system", - "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:simulator", - ], -) - -py_library( - name = "tbots_test_runner", - srcs = [ - "tbots_test_runner.py", - ], - data = [ - "//software:py_constants.so", - ], - deps = [ - "//proto:import_all_protos", - "//software/logger:py_logger", - "//software/networking/unix:threaded_unix_listener_py", - "//software/networking/unix:threaded_unix_sender_py", - "//software/simulated_tests:validation", - "//software/thunderscope", - "//software/thunderscope:config", - "//software/thunderscope:constants", - "//software/thunderscope:time_provider", - "//software/thunderscope/binary_context_managers:full_system", - "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:simulator", - ], -) - -py_library( - name = "validation", +# TODO (#2558): move this test somewhere else +py_test( + name = "simulated_test_ball_model", srcs = [ - "avoid_collisions.py", - "ball_enters_region.py", - "ball_is_off_ground.py", - "ball_moves_forward.py", - "ball_moves_from_rest.py", - "ball_moves_in_direction.py", - "ball_speed_threshold.py", - "ball_stops_in_region.py", - "excessive_dribbling.py", - "friendly_has_ball_possession.py", - "friendly_receives_ball_slow.py", - "friendly_team_scored.py", - "or_validation.py", - "robot_enters_placement_region.py", - "robot_enters_region.py", - "robot_enters_region_and_stops.py", - "robot_speed_threshold.py", - "validation.py", + "simulated_test_ball_model.py", ], - data = [ - "//software:py_constants.so", - "//software:python_bindings.so", + # TODO (#2619) Remove tag to run in parallel + tags = [ + "exclusive", ], deps = [ - "//proto:software_py_proto", - "//proto:tbots_py_proto", - "//software/logger:py_logger", + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), ], ) - -py_library( - name = "speed_threshold_helpers", - srcs = ["speed_threshold_helpers.py"], -) diff --git a/src/software/simulated_tests/pytest_validations/__init__.py b/src/software/simulated_tests/pytest_validations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/software/simulated_tests/avoid_collisions.py b/src/software/simulated_tests/pytest_validations/avoid_collisions.py similarity index 98% rename from src/software/simulated_tests/avoid_collisions.py rename to src/software/simulated_tests/pytest_validations/avoid_collisions.py index 28571474d4..9c057a23f0 100644 --- a/src/software/simulated_tests/avoid_collisions.py +++ b/src/software/simulated_tests/pytest_validations/avoid_collisions.py @@ -1,7 +1,7 @@ from software.py_constants import * import software.python_bindings as tbots from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_types, create_validation_geometry, diff --git a/src/software/simulated_tests/ball_enters_region.py b/src/software/simulated_tests/pytest_validations/ball_enters_region.py similarity index 96% rename from src/software/simulated_tests/ball_enters_region.py rename to src/software/simulated_tests/pytest_validations/ball_enters_region.py index 78c12c7895..3ba8310858 100644 --- a/src/software/simulated_tests/ball_enters_region.py +++ b/src/software/simulated_tests/pytest_validations/ball_enters_region.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_is_off_ground.py b/src/software/simulated_tests/pytest_validations/ball_is_off_ground.py similarity index 97% rename from src/software/simulated_tests/ball_is_off_ground.py rename to src/software/simulated_tests/pytest_validations/ball_is_off_ground.py index 35b5b5b514..9272dcfc82 100644 --- a/src/software/simulated_tests/ball_is_off_ground.py +++ b/src/software/simulated_tests/pytest_validations/ball_is_off_ground.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_moves_forward.py b/src/software/simulated_tests/pytest_validations/ball_moves_forward.py similarity index 96% rename from src/software/simulated_tests/ball_moves_forward.py rename to src/software/simulated_tests/pytest_validations/ball_moves_forward.py index 258b9db3bd..d3656ea26a 100644 --- a/src/software/simulated_tests/ball_moves_forward.py +++ b/src/software/simulated_tests/pytest_validations/ball_moves_forward.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_moves_from_rest.py b/src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py similarity index 96% rename from src/software/simulated_tests/ball_moves_from_rest.py rename to src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py index 11f182af15..6163b5a922 100644 --- a/src/software/simulated_tests/ball_moves_from_rest.py +++ b/src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_moves_in_direction.py b/src/software/simulated_tests/pytest_validations/ball_moves_in_direction.py similarity index 98% rename from src/software/simulated_tests/ball_moves_in_direction.py rename to src/software/simulated_tests/pytest_validations/ball_moves_in_direction.py index dff5046cd5..c85d278333 100644 --- a/src/software/simulated_tests/ball_moves_in_direction.py +++ b/src/software/simulated_tests/pytest_validations/ball_moves_in_direction.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_speed_threshold.py b/src/software/simulated_tests/pytest_validations/ball_speed_threshold.py similarity index 94% rename from src/software/simulated_tests/ball_speed_threshold.py rename to src/software/simulated_tests/pytest_validations/ball_speed_threshold.py index 3fc61aecdb..b57cbf28b3 100644 --- a/src/software/simulated_tests/ball_speed_threshold.py +++ b/src/software/simulated_tests/pytest_validations/ball_speed_threshold.py @@ -1,9 +1,9 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * from software.py_constants import * -from software.simulated_tests.speed_threshold_helpers import * +from software.simulated_tests.pytest_validations.speed_threshold_helpers import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/ball_stops_in_region.py b/src/software/simulated_tests/pytest_validations/ball_stops_in_region.py similarity index 96% rename from src/software/simulated_tests/ball_stops_in_region.py rename to src/software/simulated_tests/pytest_validations/ball_stops_in_region.py index 369a1a70de..331e7f2f78 100644 --- a/src/software/simulated_tests/ball_stops_in_region.py +++ b/src/software/simulated_tests/pytest_validations/ball_stops_in_region.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/pytest_validations/excessive_dribbling.py similarity index 97% rename from src/software/simulated_tests/excessive_dribbling.py rename to src/software/simulated_tests/pytest_validations/excessive_dribbling.py index d310ba1346..546b3317b0 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/pytest_validations/excessive_dribbling.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/friendly_has_ball_possession.py b/src/software/simulated_tests/pytest_validations/friendly_has_ball_possession.py similarity index 96% rename from src/software/simulated_tests/friendly_has_ball_possession.py rename to src/software/simulated_tests/pytest_validations/friendly_has_ball_possession.py index b63e4104fb..4af8c8da20 100644 --- a/src/software/simulated_tests/friendly_has_ball_possession.py +++ b/src/software/simulated_tests/pytest_validations/friendly_has_ball_possession.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/friendly_receives_ball_slow.py b/src/software/simulated_tests/pytest_validations/friendly_receives_ball_slow.py similarity index 97% rename from src/software/simulated_tests/friendly_receives_ball_slow.py rename to src/software/simulated_tests/pytest_validations/friendly_receives_ball_slow.py index 9b5b257ed1..e086a44296 100644 --- a/src/software/simulated_tests/friendly_receives_ball_slow.py +++ b/src/software/simulated_tests/pytest_validations/friendly_receives_ball_slow.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/friendly_team_scored.py b/src/software/simulated_tests/pytest_validations/friendly_team_scored.py similarity index 95% rename from src/software/simulated_tests/friendly_team_scored.py rename to src/software/simulated_tests/pytest_validations/friendly_team_scored.py index ecf68aa0d3..538485ad95 100644 --- a/src/software/simulated_tests/friendly_team_scored.py +++ b/src/software/simulated_tests/pytest_validations/friendly_team_scored.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/or_validation.py b/src/software/simulated_tests/pytest_validations/or_validation.py similarity index 96% rename from src/software/simulated_tests/or_validation.py rename to src/software/simulated_tests/pytest_validations/or_validation.py index efb96682ca..02c5259254 100644 --- a/src/software/simulated_tests/or_validation.py +++ b/src/software/simulated_tests/pytest_validations/or_validation.py @@ -1,5 +1,5 @@ from proto.validation_pb2 import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, ) from typing import override diff --git a/src/software/simulated_tests/robot_enters_placement_region.py b/src/software/simulated_tests/pytest_validations/robot_enters_placement_region.py similarity index 97% rename from src/software/simulated_tests/robot_enters_placement_region.py rename to src/software/simulated_tests/pytest_validations/robot_enters_placement_region.py index bb23adff33..978402f149 100644 --- a/src/software/simulated_tests/robot_enters_placement_region.py +++ b/src/software/simulated_tests/pytest_validations/robot_enters_placement_region.py @@ -2,7 +2,7 @@ from software.py_constants import ENEMY_BALL_PLACEMENT_DISTANCE_METERS from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/robot_enters_region.py b/src/software/simulated_tests/pytest_validations/robot_enters_region.py similarity index 97% rename from src/software/simulated_tests/robot_enters_region.py rename to src/software/simulated_tests/pytest_validations/robot_enters_region.py index 2f7ac6d1c2..1ddba832b9 100644 --- a/src/software/simulated_tests/robot_enters_region.py +++ b/src/software/simulated_tests/pytest_validations/robot_enters_region.py @@ -1,7 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/robot_enters_region_and_stops.py b/src/software/simulated_tests/pytest_validations/robot_enters_region_and_stops.py similarity index 95% rename from src/software/simulated_tests/robot_enters_region_and_stops.py rename to src/software/simulated_tests/pytest_validations/robot_enters_region_and_stops.py index 78210c6f43..2a9982f5be 100644 --- a/src/software/simulated_tests/robot_enters_region_and_stops.py +++ b/src/software/simulated_tests/pytest_validations/robot_enters_region_and_stops.py @@ -2,11 +2,13 @@ from software.py_constants import * from proto.import_all_protos import * -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( create_validation_types, create_validation_geometry, ) -from software.simulated_tests.robot_enters_region import RobotEntersRegion +from software.simulated_tests.pytest_validations.robot_enters_region import ( + RobotEntersRegion, +) from typing import override diff --git a/src/software/simulated_tests/robot_speed_threshold.py b/src/software/simulated_tests/pytest_validations/robot_speed_threshold.py similarity index 94% rename from src/software/simulated_tests/robot_speed_threshold.py rename to src/software/simulated_tests/pytest_validations/robot_speed_threshold.py index 52a9687ba7..5cf5474221 100644 --- a/src/software/simulated_tests/robot_speed_threshold.py +++ b/src/software/simulated_tests/pytest_validations/robot_speed_threshold.py @@ -1,11 +1,11 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * from software.py_constants import * -from software.simulated_tests.speed_threshold_helpers import * +from software.simulated_tests.pytest_validations.speed_threshold_helpers import * from typing import override -from software.simulated_tests.validation import ( +from software.simulated_tests.pytest_validations.validation import ( Validation, create_validation_geometry, create_validation_types, diff --git a/src/software/simulated_tests/speed_threshold_helpers.py b/src/software/simulated_tests/pytest_validations/speed_threshold_helpers.py similarity index 100% rename from src/software/simulated_tests/speed_threshold_helpers.py rename to src/software/simulated_tests/pytest_validations/speed_threshold_helpers.py diff --git a/src/software/simulated_tests/validation.py b/src/software/simulated_tests/pytest_validations/validation.py similarity index 100% rename from src/software/simulated_tests/validation.py rename to src/software/simulated_tests/pytest_validations/validation.py index 3b5e70d538..1191bd1d99 100644 --- a/src/software/simulated_tests/validation.py +++ b/src/software/simulated_tests/pytest_validations/validation.py @@ -137,6 +137,44 @@ def flip_validation(self, world): return eventually_true, eventually_false, always_true, always_false +def create_validation_geometry(geometry=[]) -> ValidationGeometry: + """Creates a ValidationGeometry which is a visual representation of the + validation to be rendered as either green (PASSING) or red (FAILING) + + Given a list of (vectors, polygons, circles), creates a ValidationGeometry + proto containing the protobuf representations. + + :param geometry: A list of geom + :return: ValidationGeometry + """ + validation_geometry = ValidationGeometry() + + CREATE_PROTO_DISPATCH = { + tbots_cpp.Vector.__name__: tbots_cpp.createVectorProto, + tbots_cpp.Polygon.__name__: tbots_cpp.createPolygonProto, + tbots_cpp.Rectangle.__name__: tbots_cpp.createPolygonProto, + tbots_cpp.Circle.__name__: tbots_cpp.createCircleProto, + tbots_cpp.Segment.__name__: tbots_cpp.createSegmentProto, + tbots_cpp.Stadium.__name__: tbots_cpp.createStadiumProto, + } + + ADD_TO_VALIDATION_GEOMETRY_DISPATCH = { + tbots_cpp.Vector.__name__: validation_geometry.vectors.append, + tbots_cpp.Polygon.__name__: validation_geometry.polygons.append, + tbots_cpp.Rectangle.__name__: validation_geometry.polygons.append, + tbots_cpp.Circle.__name__: validation_geometry.circles.append, + tbots_cpp.Segment.__name__: validation_geometry.segments.append, + tbots_cpp.Stadium.__name__: validation_geometry.stadiums.append, + } + + for geom in geometry: + ADD_TO_VALIDATION_GEOMETRY_DISPATCH[type(geom).__name__]( + CREATE_PROTO_DISPATCH[type(geom).__name__](geom) + ) + + return validation_geometry + + def run_validation_sequence_sets( world, eventually_validation_sequence_set, always_validation_sequence_set ): @@ -219,41 +257,3 @@ def check_validation(validation_proto_set): for validation_proto in validation_proto_set.validations: if validation_proto.status == ValidationStatus.FAILING: raise AssertionError(validation_proto.failure_msg) - - -def create_validation_geometry(geometry=[]) -> ValidationGeometry: - """Creates a ValidationGeometry which is a visual representation of the - validation to be rendered as either green (PASSING) or red (FAILING) - - Given a list of (vectors, polygons, circles), creates a ValidationGeometry - proto containing the protobuf representations. - - :param geometry: A list of geom - :return: ValidationGeometry - """ - validation_geometry = ValidationGeometry() - - CREATE_PROTO_DISPATCH = { - tbots_cpp.Vector.__name__: tbots_cpp.createVectorProto, - tbots_cpp.Polygon.__name__: tbots_cpp.createPolygonProto, - tbots_cpp.Rectangle.__name__: tbots_cpp.createPolygonProto, - tbots_cpp.Circle.__name__: tbots_cpp.createCircleProto, - tbots_cpp.Segment.__name__: tbots_cpp.createSegmentProto, - tbots_cpp.Stadium.__name__: tbots_cpp.createStadiumProto, - } - - ADD_TO_VALIDATION_GEOMETRY_DISPATCH = { - tbots_cpp.Vector.__name__: validation_geometry.vectors.append, - tbots_cpp.Polygon.__name__: validation_geometry.polygons.append, - tbots_cpp.Rectangle.__name__: validation_geometry.polygons.append, - tbots_cpp.Circle.__name__: validation_geometry.circles.append, - tbots_cpp.Segment.__name__: validation_geometry.segments.append, - tbots_cpp.Stadium.__name__: validation_geometry.stadiums.append, - } - - for geom in geometry: - ADD_TO_VALIDATION_GEOMETRY_DISPATCH[type(geom).__name__]( - CREATE_PROTO_DISPATCH[type(geom).__name__](geom) - ) - - return validation_geometry diff --git a/src/software/simulated_tests/simulated_test_ball_model.py b/src/software/simulated_tests/simulated_test_ball_model.py index d1a845b2b7..3f8276a144 100644 --- a/src/software/simulated_tests/simulated_test_ball_model.py +++ b/src/software/simulated_tests/simulated_test_ball_model.py @@ -1,14 +1,14 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * -from software.simulated_tests.friendly_has_ball_possession import * -from software.simulated_tests.ball_speed_threshold import * -from software.simulated_tests.robot_speed_threshold import * -from software.simulated_tests.ball_stops_in_region import * -from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.friendly_has_ball_possession import * +from software.simulated_tests.pytest_validations.ball_speed_threshold import * +from software.simulated_tests.pytest_validations.robot_speed_threshold import * +from software.simulated_tests.pytest_validations.ball_stops_in_region import * +from software.simulated_tests.pytest_validations.excessive_dribbling import * from proto.message_translation.tbots_protobuf import create_world_state from software.simulated_tests.simulated_test_fixture import ( pytest_main, diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index 37e1dccb8d..4e3bd1aeb9 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -8,7 +8,7 @@ import pytest from proto.import_all_protos import * -from software.simulated_tests import validation +from software.simulated_tests.pytest_validations import validation from software.simulated_tests.tbots_test_runner import TbotsTestRunner from software.thunderscope.thunderscope import Thunderscope from software.thunderscope.proto_unix_io import ProtoUnixIO From d65561805687931717380e493527e6e28a2026ff Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sun, 1 Mar 2026 15:35:05 -0800 Subject: [PATCH 22/52] Refactor move tactic test --- .../ai/hl/stp/tactic/move/move_tactic_test.py | 393 ++++++++---------- 1 file changed, 172 insertions(+), 221 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index b9c4a81dd3..3de5a94a7d 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -1,3 +1,5 @@ +import pytest + import software.python_bindings as tbots_cpp from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * @@ -7,64 +9,64 @@ ) from proto.message_translation.tbots_protobuf import create_world_state -field = tbots_cpp.Field.createSSLDivisionBField() - def test_move_across_field(simulated_test_runner): - initial_position = tbots_cpp.Point(-3, 1.5) - destination = tbots_cpp.Point(2.5, -1.1) - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[ - tbots_cpp.Point(-3, 2.5), - initial_position, - ], - yellow_robot_locations=[ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - field.enemyGoalCenter(), - field.enemyDefenseArea().negXNegYCorner(), - field.enemyDefenseArea().negXPosYCorner(), - ], - ball_location=tbots_cpp.Point(4.5, -3), - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) + def setup(*args): + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(2.5, -1.1) + + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), + ], + ball_location=tbots_cpp.Point(4.5, -3), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=True) - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + RobotEventuallyEntersRegion( + regions=[tbots_cpp.Circle(tbots_cpp.Point(2.5, -1.1), 0.05)] + ), ] ] # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after - # TODO (#2558): check if there is better way to pass these arguments cleaner simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -73,62 +75,62 @@ def test_move_across_field(simulated_test_runner): def test_autochip_move(simulated_test_runner): - initial_position = tbots_cpp.Point(-3, 1.5) - destination = tbots_cpp.Point(0, 1.5) - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[ - tbots_cpp.Point(-3, 2.5), - initial_position, - ], - yellow_robot_locations=[ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - field.enemyGoalCenter(), - field.enemyDefenseArea().negXNegYCorner(), - field.enemyDefenseArea().negXPosYCorner(), - ], - ball_location=destination, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick( - autochip_distance_meters=2.0, + def setup(*args): + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(0, 1.5) + + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), + ], + ball_location=destination, + ball_velocity=tbots_cpp.Vector(0, 0), ), - max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(autochip_distance_meters=2.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.set_tactics(params, True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, False) eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + RobotEventuallyEntersRegion( + regions=[tbots_cpp.Circle(tbots_cpp.Point(0, 1.5), 0.05)] + ), # TODO (#2558): validate that ball gets kicked at an angle zero ] ] # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after - simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -138,62 +140,62 @@ def test_autochip_move(simulated_test_runner): def test_autokick_move(simulated_test_runner): - initial_position = tbots_cpp.Point(-1, -0.5) - destination = tbots_cpp.Point(-1, -1) - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[initial_position], - blue_robot_orientations=[tbots_cpp.Angle.threeQuarter()], - yellow_robot_locations=[ - tbots_cpp.Point(1, 0), - tbots_cpp.Point(1, 2.5), - tbots_cpp.Point(1, -2.5), - field.enemyGoalCenter(), - field.enemyDefenseArea().negXNegYCorner(), - field.enemyDefenseArea().negXPosYCorner(), - ], - ball_location=destination, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.threeQuarter() - ), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick( - autokick_speed_m_per_s=3.0, + def setup(*args): + initial_position = tbots_cpp.Point(-1, -0.5) + destination = tbots_cpp.Point(-1, -1) + + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[initial_position], + blue_robot_orientations=[tbots_cpp.Angle.threeQuarter()], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXNegYCorner(), + tbots_cpp.Field.createSSLDivisionBField() + .enemyDefenseArea() + .negXPosYCorner(), + ], + ball_location=destination, + ball_velocity=tbots_cpp.Vector(0, 0), ), - max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.threeQuarter() + ), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=3.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), + RobotEventuallyEntersRegion( + regions=[tbots_cpp.Circle(tbots_cpp.Point(-1, -1), 0.05)] + ), # TODO (#2558): validate that ball gets kicked at an angle threeQuarter ] ] # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after - simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, @@ -201,107 +203,56 @@ def test_autokick_move(simulated_test_runner): ) -def test_spinning_move_clockwise(simulated_test_runner): - initial_position = tbots_cpp.Point(-4, 2) - destination = tbots_cpp.Point(4, 2) - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[initial_position], - # TODO (#2558): implement initializing robot angular velocities - # blue_robot_angular_velocities=[tbots_cpp.AngularVelocity.quarter()], - yellow_robot_locations=[tbots_cpp.Point(4, 0)], - ball_location=tbots_cpp.Point(1, 1), - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, +@pytest.mark.parametrize( + "orientation, initial_position, destination", + [ + (tbots_cpp.Angle.zero(), tbots_cpp.Point(-4, 2), tbots_cpp.Point(4, 2)), + (tbots_cpp.Angle.half(), tbots_cpp.Point(4, 2), tbots_cpp.Point(-4, 2)), + ], +) +def test_spinning_move( + orientation, initial_position, destination, simulated_test_runner +): + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[initial_position], + # TODO (#2558): implement initializing robot angular velocities + # blue_robot_angular_velocities=[tbots_cpp.AngularVelocity.quarter()], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(1, 1), + ball_velocity=tbots_cpp.Vector(0, 0), + ), ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - eventually_validation_sequence_set = [ - [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - # TODO (#2558): validate robot is at orientation zero - # TODO (#2558): validate robot is at angular velocity something - # (bug with angular velocities so the original c++ test is actual wrong) - ] - ] - - simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=10, - ) - - -def test_spinning_move_counter_clockwise(simulated_test_runner): - initial_position = tbots_cpp.Point(4, 2) - destination = tbots_cpp.Point(-4, 2) - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[initial_position], - blue_robot_orientations=[tbots_cpp.Angle.quarter()], - yellow_robot_locations=[tbots_cpp.Point(4, 0)], - ball_location=tbots_cpp.Point(1, 1), - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.half()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].move.CopyFrom( + MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(orientation), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics(params, is_friendly=True) - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - # TODO (#2558): validate robot is at orientation half - # TODO (#2558): validate robot is at angular velocity something - # (bug with angular velocities so the original c++ test is actual wrong) + # TODO (#2558): validate robot is at orientation + # TODO (#2558): validate robot is at angular velocity + # (bug with angular velocities so the original c++ test is actually wrong) ] ] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, From 0f70d382fc0dfc6046b656123c11697a824d385b Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sun, 1 Mar 2026 17:59:15 -0800 Subject: [PATCH 23/52] Add BUILD to pytest_validations package --- .../simulated_tests/pytest_validations/BUILD | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/software/simulated_tests/pytest_validations/BUILD diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD new file mode 100644 index 0000000000..2d54dc7e32 --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -0,0 +1,35 @@ +package(default_visibility = ["//visibility:public"]) + +py_library( + name = "all_validations", + srcs = [ + "avoid_collisions.py", + "ball_enters_region.py", + "ball_is_off_ground.py", + "ball_moves_forward.py", + "ball_moves_from_rest.py", + "ball_moves_in_direction.py", + "ball_speed_threshold.py", + "ball_stops_in_region.py", + "excessive_dribbling.py", + "friendly_has_ball_possession.py", + "friendly_receives_ball_slow.py", + "friendly_team_scored.py", + "or_validation.py", + "robot_enters_placement_region.py", + "robot_enters_region.py", + "robot_enters_region_and_stops.py", + "robot_speed_threshold.py", + "speed_threshold_helpers.py", + "validation.py", + ], + data = [ + "//software:py_constants.so", + "//software:python_bindings.so", + ], + deps = [ + "//proto:software_py_proto", + "//proto:tbots_py_proto", + "//software/logger:py_logger", + ], +) From 8ed90e9c1b1c0c894d8e7b38f338477f650b2e3e Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sun, 1 Mar 2026 20:27:46 -0800 Subject: [PATCH 24/52] Fix updated import path --- .../ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py | 2 +- src/software/ai/hl/stp/play/kickoff_play_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index 1732e3a142..1dd40ea1db 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -3,7 +3,7 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName -from software.simulated_tests.or_validation import OrValidation +from software.simulated_tests.pytest_validations.or_validation import OrValidation from software.simulated_tests.pytest_validations.ball_moves_from_rest import ( BallEventuallyMovesFromRest, diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index 35407cf981..c5a9f37d6f 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -11,7 +11,7 @@ from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from software.simulated_tests.or_validation import OrValidation +from software.simulated_tests.pytest_validations.or_validation import OrValidation @pytest.mark.parametrize("is_friendly_test", [True, False]) From dd7a1dfda763b52a11cab7371d2bea6edec9307a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Sun, 1 Mar 2026 22:43:23 -0800 Subject: [PATCH 25/52] Add chip, halt, and kick tactic tests --- TODO.txt | 19 ++-- src/software/ai/hl/stp/tactic/chip/BUILD | 17 ++- .../ai/hl/stp/tactic/chip/chip_tactic_test.py | 96 +++++++++++++++++ src/software/ai/hl/stp/tactic/halt/BUILD | 17 ++- .../ai/hl/stp/tactic/halt/halt_tactic_test.py | 100 ++++++++++++++++++ src/software/ai/hl/stp/tactic/kick/BUILD | 17 ++- .../ai/hl/stp/tactic/kick/kick_tactic_test.py | 92 ++++++++++++++++ 7 files changed, 344 insertions(+), 14 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py create mode 100644 src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py create mode 100644 src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py diff --git a/TODO.txt b/TODO.txt index 15fb98d838..e549677958 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,23 +2,20 @@ Found using: `rg SimulatedErForceSimPlayTestFixture` and manual sorting TACTIC TESTS TO PORT -src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp -src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp -src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp -src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp -src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp -src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp -src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp +src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) +src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp - - -(these are tests defined for specific tactics but separated for some reason... technically they test more specific interactions but could merge them) - src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp +src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp PLAY TESTS TO PORT diff --git a/src/software/ai/hl/stp/tactic/chip/BUILD b/src/software/ai/hl/stp/tactic/chip/BUILD index a4f05afb17..4ceb832f6a 100644 --- a/src/software/ai/hl/stp/tactic/chip/BUILD +++ b/src/software/ai/hl/stp/tactic/chip/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -30,7 +32,7 @@ cc_test( ) cc_test( - name = "chip_tactic_test", + name = "chip_tactic_cpp_test", srcs = ["chip_tactic_test.cpp"], deps = [ ":chip_tactic", @@ -41,3 +43,16 @@ cc_test( "//software/test_util", ], ) + +py_test( + name = "chip_tactic_test", + srcs = ["chip_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py new file mode 100644 index 0000000000..f13d24f0a1 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py @@ -0,0 +1,96 @@ +import pytest + +import software.python_bindings as tbots_cpp + +from software.py_constants import ROBOT_MAX_RADIUS_METERS +from software.simulated_tests.pytest_validations.ball_is_off_ground import ( + BallIsEventuallyOffGround, +) +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.message_translation.tbots_protobuf import create_world_state +from proto.import_all_protos import AssignedTacticPlayControlParams, ChipTactic + + +@pytest.mark.parametrize( + "ball_offset_from_robot, angle_to_chip_at", + [ + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.zero()), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.zero()), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.zero()), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.zero()), + # place the ball in the robots dribbler + ( + tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), + tbots_cpp.Angle.zero(), + ), + # Repeat the same tests but kick in the opposite direction + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.half()), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.half()), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.half()), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.half()), + # place the ball in the robots dribbler + ( + tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), + tbots_cpp.Angle.zero(), + ), + ], +) +def test_chip(ball_offset_from_robot, angle_to_chip_at, simulated_test_runner): + robot_position = tbots_cpp.Point(0, 0) + ball_position = robot_position + ball_offset_from_robot + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_position, + ], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=ball_position, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].chip.CopyFrom( + ChipTactic( + chip_origin=tbots_cpp.createPointProto(ball_position), + chip_direction=tbots_cpp.createAngleProto(angle_to_chip_at), + chip_distance_meters=2.0, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + BallIsEventuallyOffGround(), + # TODO (#2588): check ball is chipped/kicked in target direction + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=5, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/halt/BUILD b/src/software/ai/hl/stp/tactic/halt/BUILD index 266edf3f11..463fdd978b 100644 --- a/src/software/ai/hl/stp/tactic/halt/BUILD +++ b/src/software/ai/hl/stp/tactic/halt/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -28,7 +30,7 @@ cc_test( ) cc_test( - name = "halt_tactic_test", + name = "halt_tactic_cpp_test", srcs = ["halt_tactic_test.cpp"], deps = [ ":halt_tactic", @@ -39,3 +41,16 @@ cc_test( "//software/test_util", ], ) + +py_test( + name = "halt_tactic_test", + srcs = ["halt_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py new file mode 100644 index 0000000000..75fb5583d2 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py @@ -0,0 +1,100 @@ +import software.python_bindings as tbots_cpp + +from proto.import_all_protos import ( + Angle, + AngularVelocity, + AssignedTacticPlayControlParams, + HaltTactic, + Point, + RobotState, + Vector, +) +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.robot_speed_threshold import ( + RobotSpeedEventuallyBelowThreshold, +) +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) + + +def test_robot_already_stopped(simulated_test_runner): + def setup(*args): + world_state = create_world_state( + blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(0, 0.5), + ball_velocity=tbots_cpp.Vector(0, 0), + ) + + simulated_test_runner.set_world_state(world_state) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotSpeedEventuallyBelowThreshold(speed_threshold=0.05), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=5, + ) + + +def test_robot_start_moving(simulated_test_runner): + def setup(*args): + world_state = create_world_state( + blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(0, 0.5), + ball_velocity=tbots_cpp.Vector(0, 0), + ) + + # TODO (#2588): create better way of specifying this + world_state.blue_robots[1].CopyFrom( + RobotState( + global_position=Point(x_meters=0, y_meters=0), + global_velocity=Vector(x_component_meters=4, y_component_meters=4), + global_orientation=Angle(radians=0), + global_angular_velocity=AngularVelocity(radians_per_second=0), + ) + ) + + simulated_test_runner.set_world_state(world_state) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotSpeedEventuallyBelowThreshold(speed_threshold=0.05), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=6, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/kick/BUILD b/src/software/ai/hl/stp/tactic/kick/BUILD index e95d29e14e..c0cf23b7a2 100644 --- a/src/software/ai/hl/stp/tactic/kick/BUILD +++ b/src/software/ai/hl/stp/tactic/kick/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -30,7 +32,7 @@ cc_test( ) cc_test( - name = "kick_tactic_test", + name = "kick_tactic_cpp_test", srcs = ["kick_tactic_test.cpp"], deps = [ ":kick_tactic", @@ -41,3 +43,16 @@ cc_test( "//software/test_util", ], ) + +py_test( + name = "kick_tactic_test", + srcs = ["kick_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py new file mode 100644 index 0000000000..4e6663281e --- /dev/null +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -0,0 +1,92 @@ +import pytest +import software.python_bindings as tbots_cpp +from software.py_constants import ROBOT_MAX_RADIUS_METERS + +from proto.import_all_protos import AssignedTacticPlayControlParams, KickTactic +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.ball_enters_region import ( + BallEventuallyEntersRegion, +) +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) + + +@pytest.mark.parametrize( + "ball_offset_from_robot, angle_to_kick_at", + [ + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.zero()), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.zero()), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.zero()), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.zero()), + # place the ball in the robots dribbler + (tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), tbots_cpp.Angle.zero()), + # Repeat the same tests but kick in the opposite direction + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.half()), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.half()), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.half()), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.half()), + # place the ball in the robots dribbler + (tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), tbots_cpp.Angle.zero()), + ], +) +def test_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): + robot_position = tbots_cpp.Point(0, 0) + ball_position = robot_position + ball_offset_from_robot + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_position, + ], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=ball_position, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].kick.CopyFrom( + KickTactic( + kick_origin=tbots_cpp.createPointProto(ball_position), + kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), + kick_speed_meters_per_second=5.0, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + kick_direction_vector = tbots_cpp.Vector(1, 0).rotate(angle_to_kick_at) + kick_target = ball_position + kick_direction_vector * 3 + + eventually_validation_sequence_set = [ + [ + BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), + # TODO (#2588): add validation to check ball is kicked at target angle + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=5, + ) + + +if __name__ == "__main__": + pytest_main(__file__) From 5d4a3af316954d403ad9216c5de9f01ac6fc5383 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 10:17:16 -0800 Subject: [PATCH 26/52] Implement robot at orientation validation --- .../ai/hl/stp/tactic/move/move_tactic_test.py | 5 +- .../simulated_tests/pytest_validations/BUILD | 1 + .../robot_at_orientation.py | 74 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/software/simulated_tests/pytest_validations/robot_at_orientation.py diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 3de5a94a7d..79ea1a8f63 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -2,6 +2,7 @@ import software.python_bindings as tbots_cpp from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.robot_at_orientation import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.ball_moves_in_direction import * from software.simulated_tests.simulated_test_fixture import ( @@ -245,7 +246,9 @@ def setup(*args): eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - # TODO (#2558): validate robot is at orientation + RobotEventuallyAtOrientation( + robot_id=0, orientation=orientation + ), # TODO (#2558): validate robot is at angular velocity # (bug with angular velocities so the original c++ test is actually wrong) ] diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index 2d54dc7e32..3efab9363f 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -16,6 +16,7 @@ py_library( "friendly_receives_ball_slow.py", "friendly_team_scored.py", "or_validation.py", + "robot_at_orientation.py", "robot_enters_placement_region.py", "robot_enters_region.py", "robot_enters_region_and_stops.py", diff --git a/src/software/simulated_tests/pytest_validations/robot_at_orientation.py b/src/software/simulated_tests/pytest_validations/robot_at_orientation.py new file mode 100644 index 0000000000..f97c21af7d --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/robot_at_orientation.py @@ -0,0 +1,74 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * + +from software.simulated_tests.pytest_validations.validation import ( + Validation, + create_validation_geometry, + create_validation_types, +) +from typing import override + + +class RobotAtOrientation(Validation): + """Checks if a robot is at a specific orientation""" + + LINE_LENGTH = 0.3 + + def __init__(self, robot_id, orientation, threshold=0.1): + """Constructs the validation object + + :param robot_id: The ID of the robot to check + :param orientation: The expected orientation (tbots_cpp.Angle) + :param threshold: The tolerance in radians for the orientation check + """ + self.robot_id = robot_id + self.orientation = orientation + self.threshold = threshold + + @override + def get_validation_status(self, world) -> ValidationStatus: + """Checks if the robot is at the expected orientation + + :param world: The world msg to validate + :return: FAILING when the robot is not at the expected orientation + PASSING when the robot is at the expected orientation + """ + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + robot_angle = tbots_cpp.Angle.fromRadians( + robot.current_state.global_orientation.radians + ) + angle_diff = abs(robot_angle.toRadians() - self.orientation.toRadians()) + if angle_diff < self.threshold: + return ValidationStatus.PASSING + return ValidationStatus.FAILING + + @override + def get_validation_geometry(self, world) -> ValidationGeometry: + """Returns the orientation of the robot for visualization + + :param world: The world msg to create validation geometry from + :return: ValidationGeometry containing the robot's orientation + """ + # TODO (#2558): create better validation geometry + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + robot_pos = tbots_cpp.createPoint(robot.current_state.global_position) + direction = tbots_cpp.Vector(1, 0).rotate(self.orientation) + end_point = robot_pos + direction * self.LINE_LENGTH + return create_validation_geometry( + [tbots_cpp.Segment(robot_pos, end_point)] + ) + return create_validation_geometry() + + @override + def __repr__(self): + return f"Check that robot {self.robot_id} is at orientation {self.orientation} (threshold={self.threshold})" + + +( + RobotEventuallyAtOrientation, + RobotEventuallyNotAtOrientation, + RobotAlwaysAtOrientation, + RobotNeverAtOrientation, +) = create_validation_types(RobotAtOrientation) From 6b85f4d4a1b8566214360d378bda188f0dd42afd Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 10:37:03 -0800 Subject: [PATCH 27/52] Implement ball kicked (at angle) validation, remove some old validations --- .../enemy_free_kick_play_test.py | 8 +- .../ai/hl/stp/play/kickoff_play_test.py | 5 +- .../ai/hl/stp/tactic/kick/kick_tactic_test.py | 5 +- .../ai/hl/stp/tactic/move/move_tactic_test.py | 4 +- .../simulated_tests/pytest_validations/BUILD | 3 +- .../pytest_validations/ball_kicked.py | 82 +++++++++++++++++++ .../pytest_validations/ball_moves_forward.py | 64 --------------- .../ball_moves_from_rest.py | 61 -------------- 8 files changed, 94 insertions(+), 138 deletions(-) create mode 100644 src/software/simulated_tests/pytest_validations/ball_kicked.py delete mode 100644 src/software/simulated_tests/pytest_validations/ball_moves_forward.py delete mode 100644 src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py diff --git a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py index 1dd40ea1db..ccab4d5d37 100644 --- a/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py +++ b/src/software/ai/hl/stp/play/enemy_free_kick/enemy_free_kick_play_test.py @@ -5,9 +5,6 @@ from software.simulated_tests.pytest_validations.or_validation import OrValidation -from software.simulated_tests.pytest_validations.ball_moves_from_rest import ( - BallEventuallyMovesFromRest, -) from software.simulated_tests.pytest_validations.friendly_team_scored import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.robot_enters_region import ( @@ -136,11 +133,14 @@ def setup(*args): RobotNeverEntersRegion( regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)] ), - BallEventuallyMovesFromRest(position=ball_initial_pos), + BallEventuallyExitsRegion( + regions=[tbots_cpp.Circle(ball_initial_pos, 0.05)] + ), ] ) ] ] + # Eventually Validation eventually_validation_sequence_set = [ [RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_initial_pos, 1)])] diff --git a/src/software/ai/hl/stp/play/kickoff_play_test.py b/src/software/ai/hl/stp/play/kickoff_play_test.py index c5a9f37d6f..0d3438ef4f 100644 --- a/src/software/ai/hl/stp/play/kickoff_play_test.py +++ b/src/software/ai/hl/stp/play/kickoff_play_test.py @@ -4,7 +4,6 @@ from proto.play_pb2 import Play, PlayName from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * -from software.simulated_tests.pytest_validations.ball_moves_from_rest import * from proto.import_all_protos import * from proto.message_translation.tbots_protobuf import create_world_state from proto.ssl_gc_common_pb2 import Team @@ -80,8 +79,8 @@ def setup(*args): # Always Validation always_validation_sequence_set = [[]] - ball_moves_at_rest_validation = BallAlwaysMovesFromRest( - position=tbots_cpp.Point(0, 0), threshold=0.05 + ball_moves_at_rest_validation = BallNeverEntersRegion( + regions=[tbots_cpp.Circle(tbots_cpp.Point(0, 0), 0.05)] ) expected_center_circle_or_validation_set = [ diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index 4e6663281e..b528a2a490 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -7,6 +7,9 @@ from software.simulated_tests.pytest_validations.ball_enters_region import ( BallEventuallyEntersRegion, ) +from software.simulated_tests.pytest_validations.ball_kicked import ( + BallEventuallyKicked, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -74,7 +77,7 @@ def setup(*args): eventually_validation_sequence_set = [ [ BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), - # TODO (#2588): add validation to check ball is kicked at target angle + BallEventuallyKicked(kick_direction=angle_to_kick_at), ] ] diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 79ea1a8f63..f264d211a4 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -246,9 +246,7 @@ def setup(*args): eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - RobotEventuallyAtOrientation( - robot_id=0, orientation=orientation - ), + RobotEventuallyAtOrientation(robot_id=0, orientation=orientation), # TODO (#2558): validate robot is at angular velocity # (bug with angular velocities so the original c++ test is actually wrong) ] diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index 3efab9363f..e7f0c6425b 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -6,8 +6,7 @@ py_library( "avoid_collisions.py", "ball_enters_region.py", "ball_is_off_ground.py", - "ball_moves_forward.py", - "ball_moves_from_rest.py", + "ball_kicked.py", "ball_moves_in_direction.py", "ball_speed_threshold.py", "ball_stops_in_region.py", diff --git a/src/software/simulated_tests/pytest_validations/ball_kicked.py b/src/software/simulated_tests/pytest_validations/ball_kicked.py new file mode 100644 index 0000000000..14640de68a --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/ball_kicked.py @@ -0,0 +1,82 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * + +from software.simulated_tests.pytest_validations.validation import ( + Validation, + create_validation_geometry, + create_validation_types, +) +from typing import override + + +class BallKicked(Validation): + """Checks if the ball has been kicked in a specific direction""" + + LINE_LENGTH = 0.5 + + def __init__( + self, + kick_direction, + min_kick_speed=0.5, + max_angle_difference_degrees=20, + ): + """Constructs the validation object + + :param kick_direction: The expected direction the ball should be kicked (tbots_cpp.Angle) + :param min_kick_speed: Minimum speed for a valid kick in m/s + :param max_angle_difference_degrees: Maximum angle difference in degrees + """ + self.kick_direction = kick_direction + self.min_kick_speed = min_kick_speed + self.max_angle_difference = tbots_cpp.Angle.fromDegrees( + max_angle_difference_degrees + ) + + @override + def get_validation_status(self, world) -> ValidationStatus: + """Checks if the ball has been kicked in the expected direction + + :param world: The world msg to validate + :return: FAILING if the ball has not been kicked in the expected direction + PASSING if the ball has been kicked in the expected direction + """ + ball_pos = world.ball.current_state.global_position + ball_vel = world.ball.current_state.global_velocity + + point = tbots_cpp.Point(ball_pos.x_meters, ball_pos.y_meters) + vector = tbots_cpp.Vector( + ball_vel.x_component_meters, ball_vel.y_component_meters + ) + + ball = tbots_cpp.Ball(point, vector, tbots_cpp.Timestamp()) + + if ball.hasBallBeenKicked( + self.kick_direction, self.min_kick_speed, self.max_angle_difference + ): + return ValidationStatus.PASSING + return ValidationStatus.FAILING + + @override + def get_validation_geometry(self, world) -> ValidationGeometry: + """Returns arrow geometry showing the expected kick direction + + :param world: The world msg to create validation geometry from + :return: ValidationGeometry containing an arrow in the kick direction + """ + ball_pos = world.ball.current_state.global_position + start = tbots_cpp.Point(ball_pos.x_meters, ball_pos.y_meters) + direction = tbots_cpp.Vector(1, 0).rotate(self.kick_direction) + end = start + direction * self.LINE_LENGTH + return create_validation_geometry([tbots_cpp.Segment(start, end)]) + + @override + def __repr__(self): + return f"Check that the ball was kicked at {self.kick_direction} (min_speed={self.min_kick_speed}, max_angle_diff={self.max_angle_difference})" + + +( + BallEventuallyKicked, + BallEventuallyNotKicked, + BallAlwaysKicked, + BallNeverKicked, +) = create_validation_types(BallKicked) diff --git a/src/software/simulated_tests/pytest_validations/ball_moves_forward.py b/src/software/simulated_tests/pytest_validations/ball_moves_forward.py deleted file mode 100644 index d3656ea26a..0000000000 --- a/src/software/simulated_tests/pytest_validations/ball_moves_forward.py +++ /dev/null @@ -1,64 +0,0 @@ -import software.python_bindings as tbots_cpp -from proto.import_all_protos import * - -from software.simulated_tests.pytest_validations.validation import ( - Validation, - create_validation_geometry, - create_validation_types, -) -from typing import override - - -class BallMovesForward(Validation): - """Checks if ball is moving forward, i.e. in the +x direction""" - - def __init__(self, initial_ball_position): - self.last_ball_position = initial_ball_position - - @override - def get_validation_status(self, world) -> ValidationStatus: - """Checks if ball is moving forward, i.e. in the +x direction - - :param world: The world msg to validate - :return: FAILING if ball doesn't move forward - PASSING if ball moves forward - """ - validation_status = ValidationStatus.FAILING - current_ball_position = tbots_cpp.createPoint( - world.ball.current_state.global_position - ) - - if current_ball_position.x() > self.last_ball_position.x(): - validation_status = ValidationStatus.PASSING - self.last_ball_position = current_ball_position - return validation_status - - @override - def get_validation_geometry(self, world) -> ValidationGeometry: - """(override) Shows the last ball position line""" - return create_validation_geometry( - [ - tbots_cpp.Rectangle( - tbots_cpp.Point( - self.last_ball_position.x(), - tbots_cpp.Field(world.field).fieldBoundary().yMin(), - ), - tbots_cpp.Point( - self.last_ball_position.x() + 0.01, - tbots_cpp.Field(world.field).fieldBoundary().yMax(), - ), - ) - ] - ) - - @override - def __repr__(self): - return "Check that the ball moves forward" - - -( - BallEventuallyMovesForward, - BallStopsMovingForward, - BallAlwaysMovesForward, - BallNeverMovesForward, -) = create_validation_types(BallMovesForward) diff --git a/src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py b/src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py deleted file mode 100644 index 6163b5a922..0000000000 --- a/src/software/simulated_tests/pytest_validations/ball_moves_from_rest.py +++ /dev/null @@ -1,61 +0,0 @@ -import software.python_bindings as tbots_cpp -from proto.import_all_protos import * - -from software.simulated_tests.pytest_validations.validation import ( - Validation, - create_validation_geometry, - create_validation_types, -) -from typing import override - - -class BallMovesFromRest(Validation): - """Checks if ball has moved threshold meters from initial position""" - - def __init__(self, position, threshold=0.05): - """Constructor - - :param position: initial position of the ball - :param threshold: distance for which ball is considered to have moved - """ - self.initial_ball_position = position - self.threshold = threshold - - @override - def get_validation_status(self, world) -> ValidationStatus: - """Checks if ball has moved threshold meters from initial position. Default is 0.05m. - - :param world: The world msg to validate - :return: FAILING if ball doesn't move according to RoboCup rules - PASSING if ball moves according to RoboCup rules - """ - validation_status = ValidationStatus.FAILING - current_ball_position = tbots_cpp.createPoint( - world.ball.current_state.global_position - ) - - if ( - self.initial_ball_position - current_ball_position - ).length() > self.threshold: - validation_status = ValidationStatus.PASSING - - return validation_status - - @override - def get_validation_geometry(self, world) -> ValidationGeometry: - """(override) Shows the last ball position line""" - return create_validation_geometry( - [tbots_cpp.Circle(self.initial_ball_position, self.threshold)] - ) - - @override - def __repr__(self): - return "Check that the ball moves from rest" - - -( - BallEventuallyMovesFromRest, - BallStopsMovingFromRest, - BallAlwaysMovesFromRest, - BallNeverMovesFromRest, -) = create_validation_types(BallMovesFromRest) From a9888e6579ccd18db7e9d598ef10a3a43ea61a56 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 12:44:31 -0800 Subject: [PATCH 28/52] Implement robot at angular velocity validation --- .../ai/hl/stp/tactic/move/move_tactic_test.py | 14 ++-- .../simulated_tests/pytest_validations/BUILD | 1 + .../robot_at_angular_velocity.py | 66 +++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/software/simulated_tests/pytest_validations/robot_at_angular_velocity.py diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index f264d211a4..5372d65e74 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -3,6 +3,7 @@ import software.python_bindings as tbots_cpp from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.robot_at_orientation import * +from software.simulated_tests.pytest_validations.robot_at_angular_velocity import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.ball_moves_in_direction import * from software.simulated_tests.simulated_test_fixture import ( @@ -205,14 +206,14 @@ def setup(*args): @pytest.mark.parametrize( - "orientation, initial_position, destination", + "orientation, initial_position, destination, angular_velocity", [ - (tbots_cpp.Angle.zero(), tbots_cpp.Point(-4, 2), tbots_cpp.Point(4, 2)), - (tbots_cpp.Angle.half(), tbots_cpp.Point(4, 2), tbots_cpp.Point(-4, 2)), + (tbots_cpp.Angle.zero(), tbots_cpp.Point(-4, 2), tbots_cpp.Point(4, 2), tbots_cpp.Angle.fromDegrees(360)), + (tbots_cpp.Angle.half(), tbots_cpp.Point(4, 2), tbots_cpp.Point(-4, 2), tbots_cpp.Angle.fromDegrees(-1440)), ], ) def test_spinning_move( - orientation, initial_position, destination, simulated_test_runner + orientation, initial_position, destination, angular_velocity, simulated_test_runner ): def setup(*args): simulated_test_runner.set_world_state( @@ -246,9 +247,8 @@ def setup(*args): eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - RobotEventuallyAtOrientation(robot_id=0, orientation=orientation), - # TODO (#2558): validate robot is at angular velocity - # (bug with angular velocities so the original c++ test is actually wrong) + RobotEventuallyAtOrientation(robot_id=0, orientation=orientation, threshold=0.1), + # TODO (#2558): validate robot is at angular velocity (need to initialize robot angular velocity in Python first) ] ] diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index e7f0c6425b..803c0eaeb1 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -15,6 +15,7 @@ py_library( "friendly_receives_ball_slow.py", "friendly_team_scored.py", "or_validation.py", + "robot_at_angular_velocity.py", "robot_at_orientation.py", "robot_enters_placement_region.py", "robot_enters_region.py", diff --git a/src/software/simulated_tests/pytest_validations/robot_at_angular_velocity.py b/src/software/simulated_tests/pytest_validations/robot_at_angular_velocity.py new file mode 100644 index 0000000000..86154ae10d --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/robot_at_angular_velocity.py @@ -0,0 +1,66 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * + +from software.simulated_tests.pytest_validations.validation import ( + Validation, + create_validation_geometry, + create_validation_types, +) +from typing import override + + +class RobotAtAngularVelocity(Validation): + """Checks if a robot is at a specific angular velocity""" + + def __init__(self, robot_id, angular_velocity, threshold=0.1): + """Constructs the validation object + + :param robot_id: The ID of the robot to check + :param angular_velocity: The expected angular velocity (tbots_cpp.Angle) + :param threshold: The tolerance in radians/s for the angular velocity check + """ + self.robot_id = robot_id + self.angular_velocity = angular_velocity + self.threshold = threshold + + @override + def get_validation_status(self, world) -> ValidationStatus: + """Checks if the robot is at the expected angular velocity + + :param world: The world msg to validate + :return: FAILING when the robot is not at the expected angular velocity + PASSING when the robot is at the expected angular velocity + """ + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + robot_ang_vel = tbots_cpp.Angle.fromRadians( + robot.current_state.global_angular_velocity.radians_per_second + ) + angle_diff = abs( + robot_ang_vel.toRadians() - self.angular_velocity.toRadians() + ) + if angle_diff < self.threshold: + return ValidationStatus.PASSING + return ValidationStatus.FAILING + + @override + def get_validation_geometry(self, world) -> ValidationGeometry: + """Returns the angular velocity of the robot for visualization + + :param world: The world msg to create validation geometry from + :return: ValidationGeometry showing the robot's angular velocity + """ + # TODO (#2558): create validation geometry + return create_validation_geometry() + + @override + def __repr__(self): + return f"Check that robot {self.robot_id} is at angular velocity {self.angular_velocity} (threshold={self.threshold})" + + +( + RobotEventuallyAtAngularVelocity, + RobotEventuallyNotAtAngularVelocity, + RobotAlwaysAtAngularVelocity, + RobotNeverAtAngularVelocity, +) = create_validation_types(RobotAtAngularVelocity) From 3e87e5f456ba99ab2da57bbf2878ef2fdf8d6c3a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 13:01:24 -0800 Subject: [PATCH 29/52] Implement robot received ball (has ball) validation --- .../simulated_tests/pytest_validations/BUILD | 1 + .../pytest_validations/robot_received_ball.py | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/software/simulated_tests/pytest_validations/robot_received_ball.py diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index 803c0eaeb1..86aa2d43d2 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -20,6 +20,7 @@ py_library( "robot_enters_placement_region.py", "robot_enters_region.py", "robot_enters_region_and_stops.py", + "robot_received_ball.py", "robot_speed_threshold.py", "speed_threshold_helpers.py", "validation.py", diff --git a/src/software/simulated_tests/pytest_validations/robot_received_ball.py b/src/software/simulated_tests/pytest_validations/robot_received_ball.py new file mode 100644 index 0000000000..eb0b43f5c8 --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/robot_received_ball.py @@ -0,0 +1,60 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * + +from software.simulated_tests.pytest_validations.validation import ( + Validation, + create_validation_geometry, + create_validation_types, +) +from typing import override + + +class RobotReceivedBall(Validation): + """Checks if a specific robot has received the ball (ball is near dribbler)""" + + def __init__(self, robot_id, tolerance=0.02): + """Constructs the validation object + + :param robot_id: The ID of the robot to check + :param tolerance: The tolerance for when we check if the robot has the ball + """ + self.robot_id = robot_id + self.tolerance = tolerance + + @override + def get_validation_status(self, world) -> ValidationStatus: + """Checks if the specific robot has received the ball + + :param world: The world msg to validate + :return: FAILING when the robot does not have the ball + PASSING when the robot has the ball + """ + ball_position = tbots_cpp.createPoint(world.ball.current_state.global_position) + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + if tbots_cpp.Robot(robot).isNearDribbler(ball_position, self.tolerance): + return ValidationStatus.PASSING + return ValidationStatus.FAILING + + @override + def get_validation_geometry(self, world) -> ValidationGeometry: + """Highlights the dribbler area of the robot""" + # TODO (#2558): create better validation geometry + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + return create_validation_geometry( + [tbots_cpp.Robot(robot).dribblerArea()] + ) + return create_validation_geometry() + + @override + def __repr__(self): + return f"Check that robot {self.robot_id} has received the ball" + + +( + RobotEventuallyReceivedBall, + RobotEventuallyDidNotReceiveBall, + RobotAlwaysHasBall, + RobotNeverHasBall, +) = create_validation_types(RobotReceivedBall) From e85512a1643a82a92f8a789c5278eb7d33066b0f Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 13:43:47 -0800 Subject: [PATCH 30/52] Formatting --- .../ai/hl/stp/tactic/move/move_tactic_test.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 5372d65e74..2fb7c79337 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -208,8 +208,18 @@ def setup(*args): @pytest.mark.parametrize( "orientation, initial_position, destination, angular_velocity", [ - (tbots_cpp.Angle.zero(), tbots_cpp.Point(-4, 2), tbots_cpp.Point(4, 2), tbots_cpp.Angle.fromDegrees(360)), - (tbots_cpp.Angle.half(), tbots_cpp.Point(4, 2), tbots_cpp.Point(-4, 2), tbots_cpp.Angle.fromDegrees(-1440)), + ( + tbots_cpp.Angle.zero(), + tbots_cpp.Point(-4, 2), + tbots_cpp.Point(4, 2), + tbots_cpp.Angle.fromDegrees(360), + ), + ( + tbots_cpp.Angle.half(), + tbots_cpp.Point(4, 2), + tbots_cpp.Point(-4, 2), + tbots_cpp.Angle.fromDegrees(-1440), + ), ], ) def test_spinning_move( @@ -247,7 +257,9 @@ def setup(*args): eventually_validation_sequence_set = [ [ RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - RobotEventuallyAtOrientation(robot_id=0, orientation=orientation, threshold=0.1), + RobotEventuallyAtOrientation( + robot_id=0, orientation=orientation, threshold=0.1 + ), # TODO (#2558): validate robot is at angular velocity (need to initialize robot angular velocity in Python first) ] ] From 221913cbe5d89c7e60015bb41150aadeb8da3fc2 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 13:43:56 -0800 Subject: [PATCH 31/52] Port dribble tactic test --- TODO.txt | 2 +- src/software/ai/hl/stp/tactic/dribble/BUILD | 17 +- .../stp/tactic/dribble/dribble_tactic_test.py | 324 ++++++++++++++++++ 3 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py diff --git a/TODO.txt b/TODO.txt index e549677958..556757fa87 100644 --- a/TODO.txt +++ b/TODO.txt @@ -9,7 +9,7 @@ src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) -src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp +src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp diff --git a/src/software/ai/hl/stp/tactic/dribble/BUILD b/src/software/ai/hl/stp/tactic/dribble/BUILD index 3c5565efb3..fa08863773 100644 --- a/src/software/ai/hl/stp/tactic/dribble/BUILD +++ b/src/software/ai/hl/stp/tactic/dribble/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -30,7 +32,7 @@ cc_test( ) cc_test( - name = "dribble_tactic_test", + name = "dribble_tactic_cpp_test", srcs = ["dribble_tactic_test.cpp"], deps = [ ":dribble_tactic", @@ -45,6 +47,19 @@ cc_test( ], ) +py_test( + name = "dribble_tactic_test", + srcs = ["dribble_tactic_test.py"], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) + cc_test( name = "dribble_tactic_push_enemy_test", srcs = ["dribble_tactic_push_enemy_test.cpp"], diff --git a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py new file mode 100644 index 0000000000..d8ee5df819 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py @@ -0,0 +1,324 @@ +import pytest + +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * +from software.simulated_tests.pytest_validations.robot_enters_region import * +from software.simulated_tests.pytest_validations.ball_enters_region import * +from software.simulated_tests.pytest_validations.robot_received_ball import * +from software.simulated_tests.pytest_validations.robot_at_orientation import * +from software.simulated_tests.pytest_validations.excessive_dribbling import * +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.message_translation.tbots_protobuf import create_world_state + + +def get_enemy_robot_positions(): + field = tbots_cpp.Field.createSSLDivisionBField() + return [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ] + + +def test_intercept_ball_behind_enemy_robot(simulated_test_runner): + initial_position = tbots_cpp.Point(-3, 1.5) + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=get_enemy_robot_positions(), + ball_location=tbots_cpp.Point(3, -2), + ball_velocity=tbots_cpp.Vector(-0.5, 1), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), + # TODO (#2588): validate that robot keeps possession + # TODO (#2514): tune dribbling and re-enable + # Robot not excessively dribbling + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +def test_stopped_ball(simulated_test_runner): + initial_position = tbots_cpp.Point(-3, 1.5) + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(3, 3), + initial_position, + ], + yellow_robot_locations=get_enemy_robot_positions(), + ball_location=tbots_cpp.Point(-1, 1.5), + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), + # TODO (#2514): tune dribbling and re-enable + # Robot not excessively dribbling + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +def test_ball_bounce_off_of_enemy_robot(simulated_test_runner): + initial_position = tbots_cpp.Point(-3, 1.5) + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(3, 3), + initial_position, + ], + yellow_robot_locations=get_enemy_robot_positions(), + ball_location=tbots_cpp.Point(0, 0), + ball_velocity=tbots_cpp.Vector(2.5, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), + # TODO (#2514): tune dribbling and re-enable + # Robot not excessively dribbling + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=10, + ) + + +@pytest.mark.parametrize( + "dribble_destination, dribble_orientation, ball_pos, ball_vel", + [ + ( + tbots_cpp.Point(-3, 1), + None, + tbots_cpp.Point(3, -2), + tbots_cpp.Vector(-1, 2), + ), + ( + None, + tbots_cpp.Angle.quarter(), + tbots_cpp.Point(3, -2), + tbots_cpp.Vector(-1, 2), + ), + ( + tbots_cpp.Point(-1, 2), + tbots_cpp.Angle.zero(), + tbots_cpp.Point(1, 0), + tbots_cpp.Vector(1, 2), + ), + ], +) +def test_moving_ball_dribble( + dribble_destination, dribble_orientation, ball_pos, ball_vel, simulated_test_runner +): + initial_position = tbots_cpp.Point(-3, 1.5) + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=get_enemy_robot_positions(), + ball_location=ball_pos, + ball_velocity=ball_vel, + ) + ) + + dribble_params = DribbleTactic( + allow_excessive_dribbling=False, + ) + if dribble_destination: + dribble_params.dribble_destination.CopyFrom( + tbots_cpp.createPointProto(dribble_destination) + ) + if dribble_orientation: + dribble_params.final_dribble_orientation.CopyFrom( + tbots_cpp.createAngleProto(dribble_orientation) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].dribble.CopyFrom(dribble_params) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + validations = [RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05)] + + if dribble_destination: + validations.append( + BallEventuallyEntersRegion( + regions=[tbots_cpp.Circle(dribble_destination, 0.3)] + ) + # TODO (#2514): tune dribbling and re-enable + # Robot not excessively dribbling + ) + + if dribble_orientation: + validations.append( + RobotEventuallyAtOrientation( + robot_id=1, orientation=dribble_orientation, threshold=0.1 + ) + ) + + eventually_validation_sequence_set = [validations] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=15, + ) + + +def test_dribble_with_excessive_dribbling(simulated_test_runner): + dribble_destination = tbots_cpp.Point(3, 2) + initial_position = tbots_cpp.Point(4.5, -3.0) + dribble_orientation = tbots_cpp.Angle.half() + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + initial_position, + ], + yellow_robot_locations=get_enemy_robot_positions(), + ball_location=tbots_cpp.Point(4.2, -2.5), + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[1].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto( + dribble_orientation + ), + allow_excessive_dribbling=True, + ) + ) + simulated_test_runner.set_tactics(params, is_friendly=True) + + params = AssignedTacticPlayControlParams() + simulated_test_runner.set_tactics(params, is_friendly=False) + + eventually_validation_sequence_set = [ + [ + RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), + BallEventuallyEntersRegion( + regions=[tbots_cpp.Circle(dribble_destination, 0.3)] + ), + RobotEventuallyAtOrientation( + robot_id=1, orientation=dribble_orientation, threshold=0.1 + ), + # TODO (#2514): tune dribbling and re-enable + # Robot eventually excessively dribbling + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=[[]], + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=[[]], + test_timeout_s=12, + ) + + +if __name__ == "__main__": + pytest_main(__file__) From 74b08e44fbf03fc9985749dad17746a4ea6bc4c1 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Mon, 2 Mar 2026 14:15:47 -0800 Subject: [PATCH 32/52] Add to pivot kick tactic test, disable cause of flakiness --- .../pivot_kick/pivot_kick_tactic_test.py | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index 9ce6780b17..77344cd8bd 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -1,33 +1,38 @@ import pytest -import math - import software.python_bindings as tbots_cpp -from software.simulated_tests.pytest_validations.robot_enters_region import * -from software.simulated_tests.pytest_validations.ball_enters_region import * + +from proto.message_translation.tbots_protobuf import create_world_state +from proto.import_all_protos import * +from software.simulated_tests.pytest_validations.ball_enters_region import ( + BallEventuallyEntersRegion, +) +from software.simulated_tests.pytest_validations.ball_kicked import BallEventuallyKicked +from software.simulated_tests.pytest_validations.robot_enters_region import ( + RobotEventuallyEntersRegion, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from proto.message_translation.tbots_protobuf import create_world_state @pytest.mark.parametrize( "ball_offset_from_robot, angle_to_kick_at", [ # TODO (#2859): Flaky, the robot does not dribble far enough into the ball - # place the ball directly to the left of the robot - (tbots_cpp.Vector(0, 0.5), 0), - # place the ball directly to the right of the robot - (tbots_cpp.Vector(0, -0.5), 0), - # place the ball directly infront of the robot - (tbots_cpp.Vector(0.5, 0), 0), - # place the ball directly behind the robot - (tbots_cpp.Vector(-0.5, 0), 0), - # place the ball in the robots dribbler - (tbots_cpp.Vector(0.1, 0), 0), - # place the ball directly to the left of the robot, kick left - (tbots_cpp.Vector(0, 0.5), math.pi), - # place the ball directly behind the robot, kick left - (tbots_cpp.Vector(-0.5, 0), math.pi), + # # place the ball directly to the left of the robot + # (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.zero()), + # # place the ball directly to the right of the robot + # (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.zero()), + # # place the ball directly infront of the robot + # (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.zero()), + # # place the ball directly behind the robot + # (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.zero()), + # # place the ball in the robots dribbler + # (tbots_cpp.Vector(0.1, 0), tbots_cpp.Angle.zero()), + # # place the ball directly to the left of the robot, kick left + # (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.half()), + # # place the ball directly behind the robot, kick left + # (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.half()), ], ) def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): @@ -51,9 +56,7 @@ def setup(*args): params.assigned_tactics[1].pivot_kick.CopyFrom( PivotKickTactic( kick_origin=tbots_cpp.createPointProto(ball_position), - kick_direction=tbots_cpp.createAngleProto( - tbots_cpp.Angle.fromRadians(angle_to_kick_at) - ), + kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), ) ) @@ -62,15 +65,14 @@ def setup(*args): params = AssignedTacticPlayControlParams() simulated_test_runner.set_tactics(params, is_friendly=False) - kick_direction_vector = tbots_cpp.Vector(1, 0).rotate( - tbots_cpp.Angle.fromRadians(angle_to_kick_at) - ) + kick_direction_vector = tbots_cpp.Vector(1, 0).rotate(angle_to_kick_at) kick_target = ball_position + kick_direction_vector * 3 eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_position, 0.3)]), - BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), + RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_position, 0.5)]), + BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 1.0)]), + BallEventuallyKicked(kick_direction=angle_to_kick_at), ] ] From e734f87afa5eb9061330d79165f3ced01d0a762d Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Wed, 4 Mar 2026 14:11:12 -0800 Subject: [PATCH 33/52] Minor edits to pivot kick to be more accurate to cpp test --- TODO.txt | 2 +- .../ai/hl/stp/tactic/kick/kick_tactic_test.py | 4 +- .../pivot_kick/pivot_kick_tactic_test.py | 51 +++++++++---------- .../simulated_tests/pytest_validations/BUILD | 2 +- ..._kicked.py => ball_kicked_in_direction.py} | 44 ++++++---------- 5 files changed, 43 insertions(+), 60 deletions(-) rename src/software/simulated_tests/pytest_validations/{ball_kicked.py => ball_kicked_in_direction.py} (57%) diff --git a/TODO.txt b/TODO.txt index 556757fa87..9268d8e0d0 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,8 +2,8 @@ Found using: `rg SimulatedErForceSimPlayTestFixture` and manual sorting TACTIC TESTS TO PORT +src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE) -src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index b528a2a490..814e959eda 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -8,7 +8,7 @@ BallEventuallyEntersRegion, ) from software.simulated_tests.pytest_validations.ball_kicked import ( - BallEventuallyKicked, + BallEventuallyKickedInDirection, ) from software.simulated_tests.simulated_test_fixture import ( pytest_main, @@ -77,7 +77,7 @@ def setup(*args): eventually_validation_sequence_set = [ [ BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), - BallEventuallyKicked(kick_direction=angle_to_kick_at), + BallEventuallyKickedInDirection(kick_direction=angle_to_kick_at), ] ] diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index 77344cd8bd..b2879b3bd2 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -1,14 +1,11 @@ import pytest import software.python_bindings as tbots_cpp +from software.py_constants import ROBOT_MAX_RADIUS_METERS -from proto.message_translation.tbots_protobuf import create_world_state from proto.import_all_protos import * -from software.simulated_tests.pytest_validations.ball_enters_region import ( - BallEventuallyEntersRegion, -) -from software.simulated_tests.pytest_validations.ball_kicked import BallEventuallyKicked -from software.simulated_tests.pytest_validations.robot_enters_region import ( - RobotEventuallyEntersRegion, +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( + BallEventuallyKickedInDirection, ) from software.simulated_tests.simulated_test_fixture import ( pytest_main, @@ -19,20 +16,23 @@ "ball_offset_from_robot, angle_to_kick_at", [ # TODO (#2859): Flaky, the robot does not dribble far enough into the ball - # # place the ball directly to the left of the robot - # (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.zero()), - # # place the ball directly to the right of the robot - # (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.zero()), - # # place the ball directly infront of the robot - # (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.zero()), - # # place the ball directly behind the robot - # (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.zero()), - # # place the ball in the robots dribbler - # (tbots_cpp.Vector(0.1, 0), tbots_cpp.Angle.zero()), - # # place the ball directly to the left of the robot, kick left - # (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.half()), - # # place the ball directly behind the robot, kick left - # (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.half()), + # place the ball directly to the left of the robot + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.zero()), + # place the ball directly to the right of the robot + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.zero()), + # place the ball directly infront of the robot + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.zero()), + # place the ball directly behind the robot + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.zero()), + # place the ball in the robots dribbler + (tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), tbots_cpp.Angle.zero()), + # Repeat test cases for kicking in opposite direction + (tbots_cpp.Vector(0, 0.5), tbots_cpp.Angle.half()), + (tbots_cpp.Vector(0, -0.5), tbots_cpp.Angle.half()), + (tbots_cpp.Vector(0.5, 0), tbots_cpp.Angle.half()), + # TODO (#2909): Enable test once the robot can turn faster and hits the ball with the dribbler + (tbots_cpp.Vector(-0.5, 0), tbots_cpp.Angle.half()), + (tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0), tbots_cpp.Angle.half()), ], ) def test_pivot_kick(ball_offset_from_robot, angle_to_kick_at, simulated_test_runner): @@ -65,14 +65,9 @@ def setup(*args): params = AssignedTacticPlayControlParams() simulated_test_runner.set_tactics(params, is_friendly=False) - kick_direction_vector = tbots_cpp.Vector(1, 0).rotate(angle_to_kick_at) - kick_target = ball_position + kick_direction_vector * 3 - eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(ball_position, 0.5)]), - BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 1.0)]), - BallEventuallyKicked(kick_direction=angle_to_kick_at), + BallEventuallyKickedInDirection(angle_to_kick_at), ] ] @@ -82,7 +77,7 @@ def setup(*args): inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_always_validation_sequence_set=[[]], - test_timeout_s=10, + test_timeout_s=5, ) diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index 86aa2d43d2..a1d425fc7a 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -6,7 +6,7 @@ py_library( "avoid_collisions.py", "ball_enters_region.py", "ball_is_off_ground.py", - "ball_kicked.py", + "ball_kicked_in_direction.py", "ball_moves_in_direction.py", "ball_speed_threshold.py", "ball_stops_in_region.py", diff --git a/src/software/simulated_tests/pytest_validations/ball_kicked.py b/src/software/simulated_tests/pytest_validations/ball_kicked_in_direction.py similarity index 57% rename from src/software/simulated_tests/pytest_validations/ball_kicked.py rename to src/software/simulated_tests/pytest_validations/ball_kicked_in_direction.py index 14640de68a..1d01f72309 100644 --- a/src/software/simulated_tests/pytest_validations/ball_kicked.py +++ b/src/software/simulated_tests/pytest_validations/ball_kicked_in_direction.py @@ -9,7 +9,7 @@ from typing import override -class BallKicked(Validation): +class BallKickedInDirection(Validation): """Checks if the ball has been kicked in a specific direction""" LINE_LENGTH = 0.5 @@ -17,20 +17,12 @@ class BallKicked(Validation): def __init__( self, kick_direction, - min_kick_speed=0.5, - max_angle_difference_degrees=20, ): """Constructs the validation object :param kick_direction: The expected direction the ball should be kicked (tbots_cpp.Angle) - :param min_kick_speed: Minimum speed for a valid kick in m/s - :param max_angle_difference_degrees: Maximum angle difference in degrees """ self.kick_direction = kick_direction - self.min_kick_speed = min_kick_speed - self.max_angle_difference = tbots_cpp.Angle.fromDegrees( - max_angle_difference_degrees - ) @override def get_validation_status(self, world) -> ValidationStatus: @@ -40,21 +32,17 @@ def get_validation_status(self, world) -> ValidationStatus: :return: FAILING if the ball has not been kicked in the expected direction PASSING if the ball has been kicked in the expected direction """ - ball_pos = world.ball.current_state.global_position - ball_vel = world.ball.current_state.global_velocity + # Convert ball proto to tbots_cpp ball + ball_pos = tbots_cpp.createPoint(world.ball.current_state.global_position) + ball_vel = tbots_cpp.createVector(world.ball.current_state.global_velocity) - point = tbots_cpp.Point(ball_pos.x_meters, ball_pos.y_meters) - vector = tbots_cpp.Vector( - ball_vel.x_component_meters, ball_vel.y_component_meters - ) + ball = tbots_cpp.Ball(ball_pos, ball_vel, tbots_cpp.Timestamp()) - ball = tbots_cpp.Ball(point, vector, tbots_cpp.Timestamp()) - - if ball.hasBallBeenKicked( - self.kick_direction, self.min_kick_speed, self.max_angle_difference - ): - return ValidationStatus.PASSING - return ValidationStatus.FAILING + return ( + ValidationStatus.PASSING + if ball.hasBallBeenKicked(self.kick_direction) + else ValidationStatus.FAILING + ) @override def get_validation_geometry(self, world) -> ValidationGeometry: @@ -71,12 +59,12 @@ def get_validation_geometry(self, world) -> ValidationGeometry: @override def __repr__(self): - return f"Check that the ball was kicked at {self.kick_direction} (min_speed={self.min_kick_speed}, max_angle_diff={self.max_angle_difference})" + return f"Check that the ball is kicked in direction {self.kick_direction}" ( - BallEventuallyKicked, - BallEventuallyNotKicked, - BallAlwaysKicked, - BallNeverKicked, -) = create_validation_types(BallKicked) + BallEventuallyKickedInDirection, + _BallEventuallyNotKickedInDirection, # Doesn't make sense + _BallAlwaysKickedInDirection, # Doesn't make sense + BallNeverKickedInDirection, +) = create_validation_types(BallKickedInDirection) From 5427b438b2c9094726c6720c3c85851bba6ee162 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Wed, 4 Mar 2026 14:53:35 -0800 Subject: [PATCH 34/52] Port original crease defender tactic tests --- TODO.txt | 2 +- .../crease_defender_tactic_test.py | 154 ++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 9268d8e0d0..86d4af2360 100644 --- a/TODO.txt +++ b/TODO.txt @@ -3,7 +3,7 @@ Found using: `rg SimulatedErForceSimPlayTestFixture` and manual sorting TACTIC TESTS TO PORT src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% ACCURACY) -src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE 99% ACCURACY + ADDITIONAL) src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index 83989d46a4..7846053460 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -15,6 +15,160 @@ from proto.message_translation.tbots_protobuf import create_world_state +def test_not_bumping_ball_towards_net(simulated_test_runner): + enemy_threat_point = tbots_cpp.Point(3, 0) + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(0, 0)], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=enemy_threat_point, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].crease_defender.CopyFrom( + CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), + crease_defender_alignment=CreaseDefenderAlignment.CENTRE, + ) + ) + + simulated_test_runner.set_tactics(params, is_friendly=True) + simulated_test_runner.set_tactics( + AssignedTacticPlayControlParams(), is_friendly=False + ) + + simulated_test_runner.run_test( + setup=setup, + inv_always_validation_sequence_set=[[BallSpeedAlwaysBelowThreshold(0.001)]], + ag_always_validation_sequence_set=[[BallSpeedAlwaysBelowThreshold(0.001)]], + ) + + +@pytest.mark.parametrize( + "enemy_threat_point,crease_alignment,region_index", + [ + # Enemy threat in front of crease, LEFT + (tbots_cpp.Point(1, 2.5), CreaseDefenderAlignment.LEFT, 2), + # Enemy threat in front of crease, CENTRE + (tbots_cpp.Point(1, -2.5), CreaseDefenderAlignment.CENTRE, 3), + # Enemy threat in front of crease, RIGHT + (tbots_cpp.Point(1.5, 2), CreaseDefenderAlignment.RIGHT, 2), + # Enemy threat left side of crease, RIGHT + (tbots_cpp.Point(-3.5, 2.5), CreaseDefenderAlignment.RIGHT, 1), + # Enemy threat left side of crease, CENTRE + (tbots_cpp.Point(-4, 2.5), CreaseDefenderAlignment.CENTRE, 0), + # goal Enemy threat right side of crease, RIGHT + (tbots_cpp.Point(-4, -2), CreaseDefenderAlignment.RIGHT, 5), + # Enemy threat right side of crease, LEFT + (tbots_cpp.Point(-4.25, -2), CreaseDefenderAlignment.LEFT, 5), + ], +) +def test_crease_region_positioning( + enemy_threat_point, crease_alignment, region_index, simulated_test_runner +): + field = tbots_cpp.Field.createSSLDivisionBField() + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(-3, 1.5)], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + enemy_threat_point, + tbots_cpp.Point(1, -1.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ], + ball_location=tbots_cpp.Point(4.5, -3), + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].crease_defender.CopyFrom( + CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), + crease_defender_alignment=crease_alignment, + ) + ) + + simulated_test_runner.set_tactics(params, is_friendly=True) + simulated_test_runner.set_tactics( + AssignedTacticPlayControlParams(), is_friendly=False + ) + + # We check if the robot is in one of the following regions + # ┌───┬───┬────────────────┐ + # │ │ │ │ + # │ 0 │ 1 │ C + # │ │ │ E + # │ │ │ N + # ├───┴───┤ 2 T + # E │ R + # N Def. │ E + # D │ │ + # L ├────────────────┤ + # I Area │ │ + # N │ │ + # E │ L + # ├───┬───┤ I + # │ │ │ 3 N + # │ 5 │ 4 │ E + # │ │ │ │ + # │ │ │ │ + # └───┴───┴────────────────┘ + + defense_area = field.friendlyDefenseArea() + field_lines = field.fieldLines() + + defense_area_half_width = tbots_cpp.Vector(defense_area.xLength() / 2, 0) + + defender_regions = [ + tbots_cpp.Rectangle( + field.friendlyCornerPos(), + defense_area.negXPosYCorner() + defense_area_half_width, + ), + tbots_cpp.Rectangle( + field.friendlyCornerPos() + defense_area_half_width, + defense_area.posXPosYCorner(), + ), + tbots_cpp.Rectangle( + tbots_cpp.Point(defense_area.xMax(), field_lines.yMax()), + field.centerPoint(), + ), + tbots_cpp.Rectangle( + tbots_cpp.Point(defense_area.xMax(), field_lines.yMin()), + field.centerPoint(), + ), + tbots_cpp.Rectangle( + field.friendlyCornerNeg() + defense_area_half_width, + defense_area.posXNegYCorner(), + ), + tbots_cpp.Rectangle( + field.friendlyCornerNeg(), + defense_area.negXNegYCorner() + defense_area_half_width, + ), + ] + + eventually_validations = [ + [ + # TODO: check that it stays for 1000 ticks + RobotEventuallyEntersRegion(regions=[defender_regions[region_index]]) + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, + ) + + @pytest.mark.parametrize( "blue_bots, yellow_bots, ball_initial_pos, ball_initial_velocity", [ From afe65203f289c17f03de2415679cedaab4d47928 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Wed, 4 Mar 2026 22:34:29 -0800 Subject: [PATCH 35/52] Fixes with merge --- src/software/ai/hl/stp/tactic/dribble/BUILD | 3 +-- .../ai/hl/stp/tactic/dribble/excessive_dribble_test.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/BUILD b/src/software/ai/hl/stp/tactic/dribble/BUILD index 99a8c82ad8..77ddbae1ba 100644 --- a/src/software/ai/hl/stp/tactic/dribble/BUILD +++ b/src/software/ai/hl/stp/tactic/dribble/BUILD @@ -87,8 +87,7 @@ py_test( ], deps = [ "//software:conftest", - "//software/simulated_tests:speed_threshold_helpers", - "//software/simulated_tests:validation", + "//software/simulated_tests/pytest_validations:all_validations", requirement("pytest"), ], ) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 443cc06825..afb23aeebc 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -1,7 +1,7 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.excessive_dribbling import ( +from software.simulated_tests.pytest_validations.excessive_dribbling import ( NeverExcessivelyDribbles, EventuallyStartsExcessivelyDribbling, ) From d54e03caca5e104629b5121db2897dfdb7e3973e Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 08:24:46 -0800 Subject: [PATCH 36/52] Update import --- src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index 814e959eda..afcfadcec8 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -7,7 +7,7 @@ from software.simulated_tests.pytest_validations.ball_enters_region import ( BallEventuallyEntersRegion, ) -from software.simulated_tests.pytest_validations.ball_kicked import ( +from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) from software.simulated_tests.simulated_test_fixture import ( From ee693e261c0272b890d9b9c21f63b0864ebdd16f Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 08:51:12 -0800 Subject: [PATCH 37/52] Clean up halt tactic test --- TODO.txt | 2 +- .../message_translation/tbots_protobuf.py | 9 +++ .../ai/hl/stp/tactic/halt/halt_tactic_test.py | 71 +++++++++---------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/TODO.txt b/TODO.txt index 86d4af2360..8b91ce2d9b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,7 +4,7 @@ TACTIC TESTS TO PORT src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE 99% ACCURACY + ADDITIONAL) -src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) diff --git a/src/proto/message_translation/tbots_protobuf.py b/src/proto/message_translation/tbots_protobuf.py index f5e60d752b..a9f980786e 100644 --- a/src/proto/message_translation/tbots_protobuf.py +++ b/src/proto/message_translation/tbots_protobuf.py @@ -12,6 +12,7 @@ def create_world_state( ball_location: tbots_cpp.Point, ball_velocity: tbots_cpp.Vector, blue_robot_orientations: list[tbots_cpp.Angle] = [], + blue_robot_velocities: list[tbots_cpp.Vector] = [], ) -> WorldState: """Initializes the world from a list of robot locations and ball location/velocity. @@ -22,6 +23,7 @@ def create_world_state( :param ball_location: Location of the ball :param ball_velocity: Velocity of the ball :param blue_robot_orientations: A list of blue robots orientations + :param blue_robot_velocities: A list of blue robots velocities """ world_state = WorldState() @@ -43,12 +45,19 @@ def create_world_state( orientation = blue_robot_orientations[robot_id].toRadians() except IndexError: orientation = 0 + + try: + velocity = blue_robot_velocities[robot_id] + except IndexError: + velocity = tbots_cpp.Vector(0, 0) + world_state.blue_robots[robot_id].CopyFrom( RobotState( global_position=Point( x_meters=robot_location.x(), y_meters=robot_location.y() ), global_orientation=Angle(radians=orientation), + global_velocity=tbots_cpp.createVectorProto(velocity), ) ) diff --git a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py index 75fb5583d2..6b113f2936 100644 --- a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py @@ -20,79 +20,72 @@ def test_robot_already_stopped(simulated_test_runner): def setup(*args): - world_state = create_world_state( - blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], - yellow_robot_locations=[tbots_cpp.Point(4, 0)], - ball_location=tbots_cpp.Point(0, 0.5), - ball_velocity=tbots_cpp.Vector(0, 0), + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(0, 0.5), + ball_velocity=tbots_cpp.Vector(0, 0), + ) ) - simulated_test_runner.set_world_state(world_state) - params = AssignedTacticPlayControlParams() params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) - simulated_test_runner.set_tactics(params, is_friendly=True) - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) + simulated_test_runner.set_tactics(params, is_friendly=True) + simulated_test_runner.set_tactics( + AssignedTacticPlayControlParams(), is_friendly=False + ) - eventually_validation_sequence_set = [ + eventually_validations = [ [ - RobotSpeedEventuallyBelowThreshold(speed_threshold=0.05), + # TODO: check for 1000 ticks + RobotSpeedEventuallyBelowThreshold(speed_threshold=0.001), ] ] simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_always_validation_sequence_set=[[]], - test_timeout_s=5, ) def test_robot_start_moving(simulated_test_runner): def setup(*args): - world_state = create_world_state( - blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], - yellow_robot_locations=[tbots_cpp.Point(4, 0)], - ball_location=tbots_cpp.Point(0, 0.5), - ball_velocity=tbots_cpp.Vector(0, 0), - ) - - # TODO (#2588): create better way of specifying this - world_state.blue_robots[1].CopyFrom( - RobotState( - global_position=Point(x_meters=0, y_meters=0), - global_velocity=Vector(x_component_meters=4, y_component_meters=4), - global_orientation=Angle(radians=0), - global_angular_velocity=AngularVelocity(radians_per_second=0), + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[tbots_cpp.Point(-3, 2.5), tbots_cpp.Point(0, 0)], + blue_robot_velocities=[tbots_cpp.Vector(0, 0), tbots_cpp.Vector(4, 4)], + yellow_robot_locations=[tbots_cpp.Point(4, 0)], + ball_location=tbots_cpp.Point(0, 0.5), + ball_velocity=tbots_cpp.Vector(0, 0), ) ) - simulated_test_runner.set_world_state(world_state) - params = AssignedTacticPlayControlParams() params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) - simulated_test_runner.set_tactics(params, is_friendly=True) - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) + simulated_test_runner.set_tactics(params, is_friendly=True) + simulated_test_runner.set_tactics( + AssignedTacticPlayControlParams(), is_friendly=False + ) - eventually_validation_sequence_set = [ + eventually_validations = [ [ - RobotSpeedEventuallyBelowThreshold(speed_threshold=0.05), + # TODO: check for 1000 ticks + RobotSpeedEventuallyBelowThreshold(speed_threshold=0.001), ] ] simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_always_validation_sequence_set=[[]], - test_timeout_s=6, ) From 3e0ccfd9a8e954df17cc5c84bcb0a0cab9f1aa1a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 13:04:30 -0800 Subject: [PATCH 38/52] Change set_tactics to be cleaner when writing tests --- PORTING_CPP_TO_PYTEST.txt | 170 ------------------ .../hl/stp/play/defense/defense_play_test.py | 4 +- .../ai/hl/stp/play/passing_sim_test.py | 14 +- .../ai/hl/stp/tactic/chip/chip_tactic_test.py | 21 +-- .../crease_defender_tactic_test.py | 101 +++++------ .../stp/tactic/dribble/dribble_tactic_test.py | 107 +++++------ .../tactic/dribble/excessive_dribble_test.py | 30 ++-- .../stp/tactic/goalie/goalie_tactic_test.py | 29 ++- .../ai/hl/stp/tactic/halt/halt_tactic_test.py | 22 +-- .../ai/hl/stp/tactic/kick/kick_tactic_test.py | 21 +-- .../ai/hl/stp/tactic/move/move_tactic_test.py | 116 ++++++------ .../pass_defender_tactic_test.py | 66 +++---- .../pivot_kick/pivot_kick_tactic_test.py | 19 +- .../trajectory/simulated_hrvo_test.py | 48 ++--- .../field_tests/movement_robot_field_test.py | 18 +- .../field_tests/passing_field_test.py | 25 ++- .../field_tests/pivot_kick_field_test.py | 20 +-- .../simulated_test_ball_model.py | 87 +++------ .../simulated_tests/tbots_test_runner.py | 43 +++-- 19 files changed, 334 insertions(+), 627 deletions(-) delete mode 100644 PORTING_CPP_TO_PYTEST.txt diff --git a/PORTING_CPP_TO_PYTEST.txt b/PORTING_CPP_TO_PYTEST.txt deleted file mode 100644 index 718a55c228..0000000000 --- a/PORTING_CPP_TO_PYTEST.txt +++ /dev/null @@ -1,170 +0,0 @@ -Notes for porting simulated c++ tests to python - -The notes follow the format of: c++ -> python - -Note: there may be some validation functions in c++ that don't exist in python, - mark with TODO (#2558): validate for (... c++ validation) - -example: - -eventually_validation_sequence_set = [ - [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - # TODO (#2558): validate robot is at orientation half - # TODO (#2558): validate robot is has angular velocity zero - ] -] - ---- - -terminating_validation_functions -> eventually_validation_sequence_set - -std::vector terminating_validation_functions = { - [destination, tactic](std::shared_ptr world_ptr, - ValidationCoroutine::push_type& yield) - { - while (!tactic->done()) - { - yield("Tactic not done"); - } - robotAtPosition(1, world_ptr, destination, 0.05, yield); - // Check that conditions hold for 1000 ticks - unsigned num_ticks = 1000; - for (unsigned i = 0; i < num_ticks; i++) - { - robotAtPosition(1, world_ptr, destination, 0.05, yield); - } -}}; - --> - -eventually_validation_sequence_set = [ - [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - ] -] - ---- - -non_terminating_validation_functions -> always_validation_sequence_set - ---- - -auto tactic = std::make_shared(std::make_shared()); -tactic->updateControlParams(destination, Angle::zero()); -setTactic(1, tactic); - --> - -params = AssignedTacticPlayControlParams() -params.assigned_tactics[1].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) -) -simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params -) - -params = AssignedTacticPlayControlParams() -simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params -) - ---- - -Point initial_position = Point(-3, 1.5); -Point destination = Point(2.5, -1.1); -BallState ball_state(Point(4.5, -3), Vector(0, 0)); -auto friendly_robots = - TestUtil::createStationaryRobotStatesWithId({Point(-3, 2.5), initial_position}); -auto enemy_robots = TestUtil::createStationaryRobotStatesWithId( - {Point(1, 0), Point(1, 2.5), Point(1, -2.5), field.enemyGoalCenter(), - field.enemyDefenseArea().negXNegYCorner(), - field.enemyDefenseArea().negXPosYCorner()}); - -... - -runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); - --> - -simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - blue_robot_locations=[initial_position], - blue_robot_orientations=[tbots_cpp.Angle.quarter()], - yellow_robot_locations=[tbots_cpp.Point(4, 0)], - ball_location=tbots_cpp.Point(1, 1), - ball_velocity=tbots_cpp.Vector(0, 0), - ), -) - ---- - -runTest(field_type, ball_state, friendly_robots, enemy_robots, - terminating_validation_functions, non_terminating_validation_functions, - Duration::fromSeconds(10)); - --> - -simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=10, -) - ---- - -INSTANTIATE_TEST_CASE_P( - BallLocations, PivotKickTacticTest, - ::testing::Values( - // place the ball directly to the left of the robot - std::make_tuple(Vector(0, 0.5), Angle::zero()), - // place the ball directly to the right of the robot - std::make_tuple(Vector(0, -0.5), Angle::zero()), - // place the ball directly infront of the robot - std::make_tuple(Vector(0.5, 0), Angle::zero()), - // place the ball directly behind the robot - std::make_tuple(Vector(-0.5, 0), Angle::zero()), - // place the ball in the robots dribbler - std::make_tuple(Vector(ROBOT_MAX_RADIUS_METERS, 0), Angle::zero()), - --> - -@pytest.mark.parametrize( - "blue_bots, yellow_bots, ball_initial_pos, ball_initial_velocity, should_chip", - [ - # Test auto chip over enemy - ( - friendly_defense_area_front_center + tbots_cpp.Vector(0.5, 0), - friendly_defense_area_front_center + tbots_cpp.Vector(1.1, 0), - friendly_defense_area_front_center + tbots_cpp.Vector(0.9, 0), - tbots_cpp.Vector(-2, 0), - True, - ), - # Test block, auto chip off, enemy far - ( - friendly_defense_area_front_center + tbots_cpp.Vector(0.5, 0), - friendly_defense_area_front_center + tbots_cpp.Vector(5, 0), - tbots_cpp.Point(0, 0), - tbots_cpp.Vector(-6, 0), - False, - ), - ---- - -in BUILD - -change name of existing c++ test from eg. pivot_kick_tactic_test -> pivot_kick_tactic_cpp_test, -then, create a py_test with original name eg. pivot_kick_tactic_test diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index 4fdeab5419..fdf82bbcfe 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -58,10 +58,8 @@ def setup(*args): blue_play = Play() blue_play.name = PlayName.DefensePlay - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_play(blue_play, is_friendly=True) - simulated_test_runner.set_tactics(params, is_friendly=False) + simulated_test_runner.set_tactics(yellow_tactics={}) simulated_test_runner.run_test( setup=setup, diff --git a/src/software/ai/hl/stp/play/passing_sim_test.py b/src/software/ai/hl/stp/play/passing_sim_test.py index 05b2fbb46e..54baa96d17 100644 --- a/src/software/ai/hl/stp/play/passing_sim_test.py +++ b/src/software/ai/hl/stp/play/passing_sim_test.py @@ -109,9 +109,8 @@ def setup_pass_and_robots( # Setup the passer's tactic # We use KickTactic since AttackerTactic shoots towards the goal instead if open # KickTactic just does the kick we want - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].kick.CopyFrom( - KickTactic( + blue_tactics = { + 0: KickTactic( kick_origin=Point( x_meters=best_pass.passerPoint().x(), y_meters=best_pass.passerPoint().y(), @@ -119,7 +118,7 @@ def setup_pass_and_robots( kick_direction=Angle(radians=kick_vec.orientation().toRadians()), kick_speed_meters_per_second=best_pass.speed(), ) - ) + } # if we want a friendly robot to receive the pass if receive_pass: @@ -139,12 +138,9 @@ def setup_pass_and_robots( "disable_one_touch_shot": True, } - params.assigned_tactics[1].receiver.CopyFrom(ReceiverTactic(**receiver_args)) - simulated_test_runner.set_tactics(params, True) + blue_tactics[1] = ReceiverTactic(**receiver_args) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, False) + simulated_test_runner.set_tactics(blue_tactics=blue_tactics, yellow_tactics={}) return best_pass diff --git a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py index f13d24f0a1..e3ae36436a 100644 --- a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py @@ -10,7 +10,7 @@ pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.import_all_protos import AssignedTacticPlayControlParams, ChipTactic +from proto.import_all_protos import ChipTactic @pytest.mark.parametrize( @@ -62,18 +62,15 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].chip.CopyFrom( - ChipTactic( - chip_origin=tbots_cpp.createPointProto(ball_position), - chip_direction=tbots_cpp.createAngleProto(angle_to_chip_at), - chip_distance_meters=2.0, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: ChipTactic( + chip_origin=tbots_cpp.createPointProto(ball_position), + chip_direction=tbots_cpp.createAngleProto(angle_to_chip_at), + chip_distance_meters=2.0, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py index 7846053460..f04e82282c 100644 --- a/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.py @@ -1,6 +1,12 @@ import pytest import software.python_bindings as tbots_cpp +from proto.import_all_protos import ( + CreaseDefenderTactic, + CreaseDefenderAlignment, + MaxAllowedSpeedMode, + BallStealMode, +) from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.ball_moves_in_direction import * @@ -28,17 +34,13 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( - CreaseDefenderTactic( - enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), - crease_defender_alignment=CreaseDefenderAlignment.CENTRE, - ) - ) - - simulated_test_runner.set_tactics(params, is_friendly=True) simulated_test_runner.set_tactics( - AssignedTacticPlayControlParams(), is_friendly=False + blue_tactics={ + 0: CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), + crease_defender_alignment=CreaseDefenderAlignment.CENTRE, + ) + } ) simulated_test_runner.run_test( @@ -89,17 +91,13 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( - CreaseDefenderTactic( - enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), - crease_defender_alignment=crease_alignment, - ) - ) - - simulated_test_runner.set_tactics(params, is_friendly=True) simulated_test_runner.set_tactics( - AssignedTacticPlayControlParams(), is_friendly=False + blue_tactics={ + 0: CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(enemy_threat_point), + crease_defender_alignment=crease_alignment, + ) + } ) # We check if the robot is in one of the following regions @@ -210,19 +208,16 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( - CreaseDefenderTactic( - enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), - crease_defender_alignment=CreaseDefenderAlignment.CENTRE, - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), + crease_defender_alignment=CreaseDefenderAlignment.CENTRE, + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ @@ -323,19 +318,16 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( - CreaseDefenderTactic( - enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), - crease_defender_alignment=CreaseDefenderAlignment.CENTRE, - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), + crease_defender_alignment=CreaseDefenderAlignment.CENTRE, + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ @@ -398,19 +390,16 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].crease_defender.CopyFrom( - CreaseDefenderTactic( - enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), - crease_defender_alignment=CreaseDefenderAlignment.CENTRE, - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: CreaseDefenderTactic( + enemy_threat_origin=tbots_cpp.createPointProto(ball_initial_pos), + crease_defender_alignment=CreaseDefenderAlignment.CENTRE, + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py index d8ee5df819..83ff3c7a6d 100644 --- a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py @@ -1,7 +1,7 @@ import pytest import software.python_bindings as tbots_cpp -from proto.import_all_protos import * +from proto.import_all_protos import DribbleTactic from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.robot_received_ball import * @@ -41,20 +41,19 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), - final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() - ), - allow_excessive_dribbling=False, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto( + tbots_cpp.Point(0, 0) + ), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ @@ -91,20 +90,19 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), - final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() - ), - allow_excessive_dribbling=False, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto( + tbots_cpp.Point(0, 0) + ), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ @@ -140,20 +138,19 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(tbots_cpp.Point(0, 0)), - final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() - ), - allow_excessive_dribbling=False, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto( + tbots_cpp.Point(0, 0) + ), + final_dribble_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + allow_excessive_dribbling=False, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ @@ -226,12 +223,7 @@ def setup(*args): tbots_cpp.createAngleProto(dribble_orientation) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].dribble.CopyFrom(dribble_params) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) + simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) validations = [RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05)] @@ -281,20 +273,17 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto( - dribble_orientation - ), - allow_excessive_dribbling=True, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto( + dribble_orientation + ), + allow_excessive_dribbling=True, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index afb23aeebc..5c7ecd856b 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -7,7 +7,6 @@ ) from proto.message_translation.tbots_protobuf import ( WorldState, - AssignedTacticPlayControlParams, DribbleTactic, ) from software.simulated_tests.simulated_test_fixture import ( @@ -151,25 +150,16 @@ def test_excessive_dribbling( ) # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto( - final_dribble_orientation - ), - allow_excessive_dribbling=True, - ) - ) - - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params + simulated_test_runner.set_tactics( + blue_tactics={ + 0: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), + allow_excessive_dribbling=True, + ) + } ) simulated_test_runner.run_test( diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index 0129e96d35..b2a271ed51 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -1,6 +1,7 @@ import pytest import software.python_bindings as tbots_cpp +from proto.import_all_protos import GoalieTactic, MaxAllowedSpeedMode from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.ball_moves_in_direction import * @@ -116,21 +117,20 @@ def test_goalie_blocks_shot( def setup(*args): simulated_test_runner.set_world_state( create_world_state( - [], + yellow_robot_locations=[], blue_robot_locations=[robot_initial_position], ball_location=ball_initial_position, ball_velocity=ball_initial_velocity, ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].goalie.CopyFrom( - GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: GoalieTactic( + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ @@ -205,14 +205,13 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].goalie.CopyFrom( - GoalieTactic(max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: GoalieTactic( + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py index 6b113f2936..c039a89a62 100644 --- a/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/halt/halt_tactic_test.py @@ -1,13 +1,7 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import ( - Angle, - AngularVelocity, - AssignedTacticPlayControlParams, HaltTactic, - Point, - RobotState, - Vector, ) from proto.message_translation.tbots_protobuf import create_world_state from software.simulated_tests.pytest_validations.robot_speed_threshold import ( @@ -29,13 +23,7 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) - - simulated_test_runner.set_tactics(params, is_friendly=True) - simulated_test_runner.set_tactics( - AssignedTacticPlayControlParams(), is_friendly=False - ) + simulated_test_runner.set_tactics(blue_tactics={1: HaltTactic()}) eventually_validations = [ [ @@ -65,13 +53,7 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].halt.CopyFrom(HaltTactic()) - - simulated_test_runner.set_tactics(params, is_friendly=True) - simulated_test_runner.set_tactics( - AssignedTacticPlayControlParams(), is_friendly=False - ) + simulated_test_runner.set_tactics(blue_tactics={1: HaltTactic()}) eventually_validations = [ [ diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index afcfadcec8..ccfa6519ba 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -2,7 +2,7 @@ import software.python_bindings as tbots_cpp from software.py_constants import ROBOT_MAX_RADIUS_METERS -from proto.import_all_protos import AssignedTacticPlayControlParams, KickTactic +from proto.import_all_protos import KickTactic from proto.message_translation.tbots_protobuf import create_world_state from software.simulated_tests.pytest_validations.ball_enters_region import ( BallEventuallyEntersRegion, @@ -58,18 +58,15 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].kick.CopyFrom( - KickTactic( - kick_origin=tbots_cpp.createPointProto(ball_position), - kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), - kick_speed_meters_per_second=5.0, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: KickTactic( + kick_origin=tbots_cpp.createPointProto(ball_position), + kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), + kick_speed_meters_per_second=5.0, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) kick_direction_vector = tbots_cpp.Vector(1, 0).rotate(angle_to_kick_at) kick_target = ball_position + kick_direction_vector * 3 diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 2fb7c79337..255830a395 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -40,22 +40,21 @@ def setup(*args): ), ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.AVOID, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ @@ -104,22 +103,21 @@ def setup(*args): ), ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(tbots_cpp.Angle.zero()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(autochip_distance_meters=2.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.zero() + ), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(autochip_distance_meters=2.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + } ) - simulated_test_runner.set_tactics(params, True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, False) eventually_validation_sequence_set = [ [ @@ -167,24 +165,21 @@ def setup(*args): ), ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.threeQuarter() - ), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=3.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto( + tbots_cpp.Angle.threeQuarter() + ), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=3.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.COLLISIONS_ALLOWED, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ @@ -237,22 +232,19 @@ def setup(*args): ), ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].move.CopyFrom( - MoveTactic( - destination=tbots_cpp.createPointProto(destination), - final_orientation=tbots_cpp.createAngleProto(orientation), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: MoveTactic( + destination=tbots_cpp.createPointProto(destination), + final_orientation=tbots_cpp.createAngleProto(orientation), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py index f0574b92f2..2414d7b3dd 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_tactic_test.py @@ -1,6 +1,7 @@ import pytest import software.python_bindings as tbots_cpp +from proto.import_all_protos import PassDefenderTactic, BallStealMode from software.simulated_tests.pytest_validations.robot_enters_region import * from software.simulated_tests.pytest_validations.ball_enters_region import * from software.simulated_tests.pytest_validations.ball_moves_in_direction import * @@ -45,19 +46,16 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto( - position_to_block_from - ), - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ @@ -114,26 +112,23 @@ def test_avoid_intercept_scenario( def setup(*args): simulated_test_runner.set_world_state( create_world_state( - [], + yellow_robot_locations=[], blue_robot_locations=[position_to_block_from], ball_location=ball_initial_position, ball_velocity=ball_initial_velocity, ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto( - position_to_block_from - ), - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ @@ -257,19 +252,16 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].pass_defender.CopyFrom( - PassDefenderTactic( - position_to_block_from=tbots_cpp.createPointProto( - position_to_block_from - ), - ball_steal_mode=BallStealMode.STEAL, - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 0: PassDefenderTactic( + position_to_block_from=tbots_cpp.createPointProto( + position_to_block_from + ), + ball_steal_mode=BallStealMode.STEAL, + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) always_validation_sequence_set = [ [ diff --git a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py index b2879b3bd2..727317c302 100644 --- a/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.py @@ -52,18 +52,15 @@ def setup(*args): ) ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[1].pivot_kick.CopyFrom( - PivotKickTactic( - kick_origin=tbots_cpp.createPointProto(ball_position), - kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), - ) + simulated_test_runner.set_tactics( + blue_tactics={ + 1: PivotKickTactic( + kick_origin=tbots_cpp.createPointProto(ball_position), + kick_direction=tbots_cpp.createAngleProto(angle_to_kick_at), + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), + ) + } ) - simulated_test_runner.set_tactics(params, is_friendly=True) - - params = AssignedTacticPlayControlParams() - simulated_test_runner.set_tactics(params, is_friendly=False) eventually_validation_sequence_set = [ [ diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index a2ed3b441f..f9c6800be6 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -204,10 +204,10 @@ def hrvo_setup( gc_command=Command.Type.FORCE_START, team=Team.BLUE ) - blue_params = AssignedTacticPlayControlParams() + blue_tactics = {} for index, destination in enumerate(friendly_robots_destinations): - blue_params = get_move_update_control_params( + blue_tactics[index] = get_move_tactics( index, destination, ( @@ -215,19 +215,18 @@ def hrvo_setup( if friendly_robots_final_orientations else desired_orientation ), - params=blue_params, ) - simulated_test_runner.set_tactics(blue_params, True) + simulated_test_runner.set_tactics(blue_tactics=blue_tactics) - yellow_params = AssignedTacticPlayControlParams() + yellow_tactics = {} for index, destination in enumerate(enemy_robots_destinations): - yellow_params = get_move_update_control_params( - index, destination, tbots.Angle.fromRadians(0), params=yellow_params + yellow_tactics[index] = get_move_tactics( + index, destination, tbots.Angle.fromRadians(0) ) - simulated_test_runner.set_tactics(yellow_params, False) + simulated_test_runner.set_tactics(yellow_tactics=yellow_tactics) @pytest.mark.parametrize( @@ -533,39 +532,28 @@ def get_reached_destination_validation(robot_destinations: list[tbots.Point]): ] -def get_move_update_control_params( +def get_move_tactics( robot_id: int, destination: tbots.Point, desired_orientation: tbots.Angle, - params: AssignedTacticPlayControlParams = None, ): - """Constructs the control params for a Move Tactic for a single robot - with the given data - And adds it to an existing or new AssignedTacticPlayControlParams message + """Constructs a Move Tactic for a single robot with the given data :param robot_id: the id of the robot who will be assigned these params :param destination: the destination of the robot :param desired_orientation: the desired orientation of the robot - :param params: AssignedTacticPlayControlParams message - if not None, add this robot's params to this - else, create a new message and add - :return: an AssignedTacticPlayControlParams message with this robot's params added + :return: a MoveTactic """ - params = params if params else AssignedTacticPlayControlParams() - params.assigned_tactics[robot_id].move.CopyFrom( - MoveTactic( - destination=Point(x_meters=destination.x(), y_meters=destination.y()), - final_orientation=Angle(radians=desired_orientation.toRadians()), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.ALLOW, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.AGGRESSIVE, - ) + return MoveTactic( + destination=Point(x_meters=destination.x(), y_meters=destination.y()), + final_orientation=Angle(radians=desired_orientation.toRadians()), + dribbler_mode=DribblerMode.OFF, + ball_collision_type=BallCollisionType.ALLOW, + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), + max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, + obstacle_avoidance_mode=ObstacleAvoidanceMode.AGGRESSIVE, ) - return params - if __name__ == "__main__": pytest_main(__file__) diff --git a/src/software/field_tests/movement_robot_field_test.py b/src/software/field_tests/movement_robot_field_test.py index aaabbba023..82f9698201 100644 --- a/src/software/field_tests/movement_robot_field_test.py +++ b/src/software/field_tests/movement_robot_field_test.py @@ -103,11 +103,7 @@ def test_basic_rotation(field_test_runner): move_tactic.obstacle_avoidance_mode = ObstacleAvoidanceMode.SAFE # Setup Tactic - params = AssignedTacticPlayControlParams() - - params.assigned_tactics[id].move.CopyFrom(move_tactic) - - field_test_runner.set_tactics(params, True) + field_test_runner.set_tactics(blue_tactics={id: move_tactic}) field_test_runner.run_test( always_validation_sequence_set=[[]], eventually_validation_sequence_set=[[]], @@ -115,10 +111,8 @@ def test_basic_rotation(field_test_runner): ) # Send a halt tactic after the test finishes halt_tactic = HaltTactic() - params = AssignedTacticPlayControlParams() - params.assigned_tactics[id].stop.CopyFrom(halt_tactic) # send the halt tactic - field_test_runner.set_tactics(params, True) + field_test_runner.set_tactics(blue_tactics={id: halt_tactic}) # validate by eye logger.info(f"robot set to {angle} orientation") @@ -187,10 +181,7 @@ def test_one_robots_square(field_test_runner): for tactic in tactics: print(f"Going to {tactic.destination}") - params = AssignedTacticPlayControlParams() - params.assigned_tactics[id].move.CopyFrom(tactic) - - field_test_runner.set_tactics(params, True) + field_test_runner.set_tactics(blue_tactics={id: tactic}) field_test_runner.run_test( always_validation_sequence_set=[[]], eventually_validation_sequence_set=[[]], @@ -199,8 +190,7 @@ def test_one_robots_square(field_test_runner): # Send a halt tactic after the test finishes halt_tactic = HaltTactic() - params = AssignedTacticPlayControlParams() - params.assigned_tactics[id].stop.CopyFrom(halt_tactic) + field_test_runner.set_tactics(blue_tactics={id: halt_tactic}) if __name__ == "__main__": diff --git a/src/software/field_tests/passing_field_test.py b/src/software/field_tests/passing_field_test.py index 1af6b8ce29..7f9f7abbb0 100644 --- a/src/software/field_tests/passing_field_test.py +++ b/src/software/field_tests/passing_field_test.py @@ -41,9 +41,8 @@ def test_passing(field_test_runner): # Setup the passer's tactic # We use KickTactic since AttackerTactic shoots towards the goal instead if open # KickTactic just does the kick we want - params = AssignedTacticPlayControlParams() - params.assigned_tactics[passer_robot_id].kick.CopyFrom( - KickTactic( + tactics = { + passer_robot_id: KickTactic( kick_origin=Point( x_meters=pass_to_test.passerPoint().x(), y_meters=pass_to_test.passerPoint().y(), @@ -51,11 +50,11 @@ def test_passing(field_test_runner): kick_direction=Angle(radians=kick_vec.orientation().toRadians()), kick_speed_meters_per_second=pass_to_test.speed(), ) - ) + } # if we want a friendly robot to receive the pass if should_receive_pass: - # arguments for a ReceiverTactic + # arguments for a ReceiverTactic, required since pass is a python keyword receiver_args = { "pass": Pass( passer_point=Point( @@ -71,9 +70,7 @@ def test_passing(field_test_runner): "disable_one_touch_shot": True, } - params.assigned_tactics[receiver_robot_id].receiver.CopyFrom( - ReceiverTactic(**receiver_args) - ) + tactics[receiver_robot_id] = ReceiverTactic(**receiver_args) field = tbots_cpp.Field.createSSLDivisionBField() tbots_cpp.EighteenZonePitchDivision(field) @@ -89,7 +86,7 @@ def test_passing(field_test_runner): ] ] - field_test_runner.set_tactics(params, True) + field_test_runner.set_tactics(blue_tactics=tactics) field_test_runner.run_test( always_validation_sequence_set=always_validation_sequence_set, eventually_validation_sequence_set=[[]], @@ -98,10 +95,12 @@ def test_passing(field_test_runner): # Send a halt tactic after the test finishes halt_tactic = HaltTactic() - params = AssignedTacticPlayControlParams() - params.assigned_tactics[passer_robot_id].stop.CopyFrom(halt_tactic) - params.assigned_tactics[receiver_robot_id].stop.CopyFrom(halt_tactic) - field_test_runner.set_tactics(params, True) + field_test_runner.set_tactics( + blue_tactics={ + passer_robot_id: halt_tactic, + receiver_robot_id: halt_tactic, + } + ) if __name__ == "__main__": diff --git a/src/software/field_tests/pivot_kick_field_test.py b/src/software/field_tests/pivot_kick_field_test.py index 1b01d73c9c..884de63d1d 100644 --- a/src/software/field_tests/pivot_kick_field_test.py +++ b/src/software/field_tests/pivot_kick_field_test.py @@ -23,25 +23,25 @@ def test_pivot_kick(field_test_runner): ] ) - params = AssignedTacticPlayControlParams() - params.assigned_tactics[id].pivot_kick.CopyFrom( - PivotKickTactic( - kick_origin=Point(x_meters=-1.13, y_meters=0.75), - kick_direction=Angle(radians=-math.pi / 2), - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), - ) + field_test_runner.set_tactics( + blue_tactics={ + id: PivotKickTactic( + kick_origin=Point(x_meters=-1.13, y_meters=0.75), + kick_direction=Angle(radians=-math.pi / 2), + auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=5.0), + ) + } ) - field_test_runner.set_tactics(params, True) field_test_runner.run_test( always_validation_sequence_set=[[]], eventually_validation_sequence_set=[[]], test_timeout_s=15, ) + # Send a halt tactic after the test finishes halt_tactic = HaltTactic() - params = AssignedTacticPlayControlParams() - params.assigned_tactics[id].stop.CopyFrom(halt_tactic) + field_test_runner.set_tactics(blue_tactics={id: halt_tactic}) if __name__ == "__main__": diff --git a/src/software/simulated_tests/simulated_test_ball_model.py b/src/software/simulated_tests/simulated_test_ball_model.py index 3f8276a144..55607bd139 100644 --- a/src/software/simulated_tests/simulated_test_ball_model.py +++ b/src/software/simulated_tests/simulated_test_ball_model.py @@ -48,29 +48,17 @@ def test_simulator_move_ball( ball_initial_velocity, simulated_test_runner, ): - # Setup Ball - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Tactic - params = AssignedTacticPlayControlParams() - - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=[], + blue_robot_locations=[], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ) + ) + + simulated_test_runner.set_tactics() # expected ball position initial_v = ball_initial_velocity.length() @@ -105,9 +93,10 @@ def test_simulator_move_ball( ] simulated_test_runner.run_test( - test_timeout_s=8, + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, + test_timeout_s=8, ) @@ -116,40 +105,17 @@ def test_ball_robot_collision(simulated_test_runner): ball_initial_velocity = tbots_cpp.Vector(2.5, 0) robot_position = tbots_cpp.Point(2.5, 0) - # Setup Robot - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[robot_position], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Ball - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[], - ball_location=ball_initial_position, - ball_velocity=ball_initial_velocity, - ), - ) - - # Setup Tactic - params = AssignedTacticPlayControlParams() - - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=[], + blue_robot_locations=[robot_position], + ball_location=ball_initial_position, + ball_velocity=ball_initial_velocity, + ), + ) - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) + simulated_test_runner.set_tactics() # expected ball position initial_v = ball_initial_velocity.length() @@ -170,9 +136,6 @@ def test_ball_robot_collision(simulated_test_runner): distance_from_robot = (ball_expected_position - robot_position).length() - # Always Validation - always_validation_sequence_set = [] - # Eventually Validation eventually_validation_sequence_set = [ [ @@ -183,11 +146,11 @@ def test_ball_robot_collision(simulated_test_runner): ] simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, ) if __name__ == "__main__": - # Run the test, -s disables all capturing at -vv increases verbosity pytest_main(__file__) diff --git a/src/software/simulated_tests/tbots_test_runner.py b/src/software/simulated_tests/tbots_test_runner.py index 842e1fe97e..64e50366f3 100644 --- a/src/software/simulated_tests/tbots_test_runner.py +++ b/src/software/simulated_tests/tbots_test_runner.py @@ -88,22 +88,24 @@ def send_gamecontroller_command( def set_tactics( self, - tactics: AssignedTacticPlayControlParams, - is_friendly: bool, + blue_tactics={}, + yellow_tactics={}, ): - """Overrides current AI tactic for the given team + """Overrides current AI tactics for robots on each team - :param tactic: the tactic params proto to use - :param is_friendly: whether the play should be applied to the "friendly" team + :param blue_tactics: dict of robot_id -> tactic for blue team + :param yellow_tactics: dict of robot_id -> tactic for yellow team """ - fs_proto_unix_io = self.blue_full_system_proto_unix_io - # If (friendly & yellow_friendly) or (~friendly & ~yellow_friendly), set command team to yellow - if (is_friendly and self.is_yellow_friendly) or not ( - is_friendly or self.is_yellow_friendly - ): - fs_proto_unix_io = self.yellow_full_system_proto_unix_io + # TODO: add option to not set assigned tactics for play tests + blue_params = self._create_assigned_tactic_params(blue_tactics) + yellow_params = self._create_assigned_tactic_params(yellow_tactics) - fs_proto_unix_io.send_proto(AssignedTacticPlayControlParams, tactics) + self.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, blue_params + ) + self.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, yellow_params + ) def set_play(self, play: Play, is_friendly: bool): """Overrides current AI play for the given team @@ -120,6 +122,23 @@ def set_play(self, play: Play, is_friendly: bool): fs_proto_unix_io.send_proto(Play, play) + def _create_assigned_tactic_params(self, tactics): + """Converts dict to AssignedTacticPlayControlParams + + :param tactics: dict of robot_id -> tactic + """ + params = AssignedTacticPlayControlParams() + + # Checks which oneof field in Tactic to assign the specified tactic to + for robot_id, specific_tactic in tactics.items(): + tactic = params.assigned_tactics[robot_id] + for field in tactic.DESCRIPTOR.oneofs_by_name["tactic"].fields: + if field.message_type == specific_tactic.DESCRIPTOR: + getattr(tactic, field.name).CopyFrom(specific_tactic) + break + + return params + @abstractmethod def set_world_state(self, worldstate: WorldState): """Sets the worldstate for the given team From 6d0ec58e02d6a044e264b2eaaaba07ce13a03822 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 13:06:41 -0800 Subject: [PATCH 39/52] Clean up excessive dribble test --- .../tactic/dribble/excessive_dribble_test.py | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 5c7ecd856b..7fdb463254 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -5,10 +5,7 @@ NeverExcessivelyDribbles, EventuallyStartsExcessivelyDribbling, ) -from proto.message_translation.tbots_protobuf import ( - WorldState, - DribbleTactic, -) +from proto.message_translation.tbots_protobuf import DribbleTactic from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -128,6 +125,30 @@ def test_excessive_dribbling( simulated_test_runner, blue_robot_location, ): + def setup(*args): + blue_robot_locations = [blue_robot_location] + + simulated_test_runner.set_world_state( + create_world_state( + yellow_robot_locations=[], + blue_robot_locations=blue_robot_locations, + ball_location=initial_location, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + simulated_test_runner.set_tactics( + blue_tactics={ + 0: DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), + allow_excessive_dribbling=True, + ) + } + ) + if should_excessively_dribble: # Always and Eventually validation sets for excessive dribbling always_validation_sequence_set = [[]] @@ -137,32 +158,8 @@ def test_excessive_dribbling( always_validation_sequence_set = [[NeverExcessivelyDribbles()]] eventually_validation_sequence_set = [[]] - blue_robot_locations = [blue_robot_location] - - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=blue_robot_locations, - ball_location=initial_location, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) - - # Setup Tactic - simulated_test_runner.set_tactics( - blue_tactics={ - 0: DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto( - final_dribble_orientation - ), - allow_excessive_dribbling=True, - ) - } - ) - simulated_test_runner.run_test( + setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, From e1381cd7d8cd5c2b01b0883d281c36d64d25ea92 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 13:30:13 -0800 Subject: [PATCH 40/52] Add robot at position validation and clean up move tactic test --- TODO.txt | 2 +- .../ai/hl/stp/tactic/move/move_tactic_test.py | 116 ++++++++---------- .../simulated_tests/pytest_validations/BUILD | 1 + .../pytest_validations/robot_at_position.py | 63 ++++++++++ 4 files changed, 113 insertions(+), 69 deletions(-) create mode 100644 src/software/simulated_tests/pytest_validations/robot_at_position.py diff --git a/TODO.txt b/TODO.txt index 8b91ce2d9b..564c48b2bb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,7 +5,7 @@ TACTIC TESTS TO PORT src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE 99% ACCURACY + ADDITIONAL) src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE 99% ACCURACY) -src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 98% ACCURACY NEED TO IMPLEMENT THING) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 255830a395..2e92cd97c2 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -1,22 +1,31 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.pytest_validations.robot_enters_region import * -from software.simulated_tests.pytest_validations.robot_at_orientation import * -from software.simulated_tests.pytest_validations.robot_at_angular_velocity import * -from software.simulated_tests.pytest_validations.ball_enters_region import * -from software.simulated_tests.pytest_validations.ball_moves_in_direction import * +from software.simulated_tests.pytest_validations.robot_at_position import ( + RobotEventuallyAtPosition, +) +from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( + BallEventuallyKickedInDirection, +) +from software.simulated_tests.pytest_validations.robot_at_orientation import ( + RobotEventuallyAtOrientation, +) +from software.simulated_tests.pytest_validations.ball_is_off_ground import ( + BallIsEventuallyOffGround, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state +from proto.import_all_protos import * def test_move_across_field(simulated_test_runner): - def setup(*args): - initial_position = tbots_cpp.Point(-3, 1.5) - destination = tbots_cpp.Point(2.5, -1.1) + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(2.5, -1.1) + field = tbots_cpp.Field.createSSLDivisionBField() + def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[ @@ -27,13 +36,9 @@ def setup(*args): tbots_cpp.Point(1, 0), tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXPosYCorner(), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), ], ball_location=tbots_cpp.Point(4.5, -3), ball_velocity=tbots_cpp.Vector(0, 0), @@ -47,39 +52,30 @@ def setup(*args): final_orientation=tbots_cpp.createAngleProto( tbots_cpp.Angle.zero() ), - dribbler_mode=DribblerMode.OFF, - ball_collision_type=BallCollisionType.AVOID, - auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), - max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, - obstacle_avoidance_mode=ObstacleAvoidanceMode.SAFE, ) } ) eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion( - regions=[tbots_cpp.Circle(tbots_cpp.Point(2.5, -1.1), 0.05)] - ), + # TODO: should also validate that the robot stays at destination for 1000 ticks after + RobotEventuallyAtPosition(1, destination), ] ] - # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after - # TODO (#2558): check if there is better way to pass these arguments cleaner simulated_test_runner.run_test( setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], ) def test_autochip_move(simulated_test_runner): - def setup(*args): - initial_position = tbots_cpp.Point(-3, 1.5) - destination = tbots_cpp.Point(0, 1.5) + initial_position = tbots_cpp.Point(-3, 1.5) + destination = tbots_cpp.Point(0, 1.5) + field = tbots_cpp.Field.createSSLDivisionBField() + def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[ @@ -90,13 +86,9 @@ def setup(*args): tbots_cpp.Point(1, 0), tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXPosYCorner(), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), ], ball_location=destination, ball_velocity=tbots_cpp.Vector(0, 0), @@ -121,29 +113,27 @@ def setup(*args): eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion( - regions=[tbots_cpp.Circle(tbots_cpp.Point(0, 1.5), 0.05)] - ), - # TODO (#2558): validate that ball gets kicked at an angle zero + # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after + RobotEventuallyAtPosition(1, destination), + BallEventuallyKickedInDirection(tbots_cpp.Angle.zero()), + BallIsEventuallyOffGround(), ] ] - # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after simulated_test_runner.run_test( setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], test_timeout_s=10, ) def test_autokick_move(simulated_test_runner): - def setup(*args): - initial_position = tbots_cpp.Point(-1, -0.5) - destination = tbots_cpp.Point(-1, -1) + initial_position = tbots_cpp.Point(-1, -0.5) + destination = tbots_cpp.Point(-1, -1) + field = tbots_cpp.Field.createSSLDivisionBField() + def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[initial_position], @@ -152,13 +142,9 @@ def setup(*args): tbots_cpp.Point(1, 0), tbots_cpp.Point(1, 2.5), tbots_cpp.Point(1, -2.5), - tbots_cpp.Field.createSSLDivisionBField().enemyGoalCenter(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXNegYCorner(), - tbots_cpp.Field.createSSLDivisionBField() - .enemyDefenseArea() - .negXPosYCorner(), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), ], ball_location=destination, ball_velocity=tbots_cpp.Vector(0, 0), @@ -183,20 +169,17 @@ def setup(*args): eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion( - regions=[tbots_cpp.Circle(tbots_cpp.Point(-1, -1), 0.05)] - ), - # TODO (#2558): validate that ball gets kicked at an angle threeQuarter + # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after + RobotEventuallyAtPosition(0, destination), + BallEventuallyKickedInDirection(tbots_cpp.Angle.threeQuarter()), ] ] - # TODO (#2558): should also validate that the robot stays at destination for 1000 ticks after simulated_test_runner.run_test( setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], + test_timeout_s=10, ) @@ -248,20 +231,17 @@ def setup(*args): eventually_validation_sequence_set = [ [ - RobotEventuallyEntersRegion(regions=[tbots_cpp.Circle(destination, 0.05)]), - RobotEventuallyAtOrientation( - robot_id=0, orientation=orientation, threshold=0.1 - ), - # TODO (#2558): validate robot is at angular velocity (need to initialize robot angular velocity in Python first) + RobotEventuallyAtPosition(0, destination), + RobotEventuallyAtOrientation(0, orientation, 0.1), + # TODO: validate robot is at angular velocity (need to initialize robot angular velocity in Python first) + # TODO: validate position and orientation for 1000 ticks ] ] simulated_test_runner.run_test( setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], test_timeout_s=10, ) diff --git a/src/software/simulated_tests/pytest_validations/BUILD b/src/software/simulated_tests/pytest_validations/BUILD index a1d425fc7a..dc1553e453 100644 --- a/src/software/simulated_tests/pytest_validations/BUILD +++ b/src/software/simulated_tests/pytest_validations/BUILD @@ -17,6 +17,7 @@ py_library( "or_validation.py", "robot_at_angular_velocity.py", "robot_at_orientation.py", + "robot_at_position.py", "robot_enters_placement_region.py", "robot_enters_region.py", "robot_enters_region_and_stops.py", diff --git a/src/software/simulated_tests/pytest_validations/robot_at_position.py b/src/software/simulated_tests/pytest_validations/robot_at_position.py new file mode 100644 index 0000000000..1a391c34b5 --- /dev/null +++ b/src/software/simulated_tests/pytest_validations/robot_at_position.py @@ -0,0 +1,63 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * + +from software.simulated_tests.pytest_validations.validation import ( + Validation, + ValidationStatus, + create_validation_geometry, + create_validation_types, +) +from typing import override + + +class RobotAtPosition(Validation): + """Checks if a specific robot is at a given position within a threshold distance.""" + + def __init__( + self, robot_id: int, position: tbots_cpp.Point, threshold: float = 0.05 + ): + """Initializes the validation class with robot id, position, and threshold + + :param robot_id: the id of the robot to check + :param position: the target position the robot should be at + :param threshold: the distance threshold for the robot to be considered at position + """ + self.robot_id = robot_id + self.position = position + self.threshold = threshold + + @override + def get_validation_status(self, world) -> ValidationStatus: + """Checks if the robot is at the target position + + :param world: The world msg to validate + :returns: PASSING if the robot is at the target position within threshold + FAILING otherwise + """ + for robot in world.friendly_team.team_robots: + if robot.id == self.robot_id: + robot_pos = tbots_cpp.createPoint(robot.current_state.global_position) + distance = (robot_pos - self.position).length() + if distance <= self.threshold: + return ValidationStatus.PASSING + break + + return ValidationStatus.FAILING + + @override + def get_validation_geometry(self, world) -> ValidationGeometry: + """(override) shows the target position""" + circle = tbots_cpp.Circle(self.position, self.threshold) + return create_validation_geometry([circle]) + + @override + def __repr__(self): + return f"Robot {self.robot_id} at position {self.position} with threshold {self.threshold}" + + +( + RobotEventuallyAtPosition, + RobotEventuallyNotAtPosition, + RobotAlwaysAtPosition, + RobotAlwaysNotAtPosition, +) = create_validation_types(RobotAtPosition) From 48b842e5ec974bd9b7bcf29edff2a2f84a6ea9b5 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 19:14:49 -0800 Subject: [PATCH 41/52] Fix robot at orientation and clean up move tactic test --- TODO.txt | 2 +- .../ai/hl/stp/tactic/move/move_tactic_test.py | 57 ++++++++++++++----- src/software/python_bindings.cpp | 1 + .../robot_at_orientation.py | 2 +- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/TODO.txt b/TODO.txt index 564c48b2bb..14c1d787c4 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,7 +5,7 @@ TACTIC TESTS TO PORT src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE 99% ACCURACY + ADDITIONAL) src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE 99% ACCURACY) -src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 98% ACCURACY NEED TO IMPLEMENT THING) +src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) diff --git a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py index 2e92cd97c2..21ca8d4da5 100644 --- a/src/software/ai/hl/stp/tactic/move/move_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/move/move_tactic_test.py @@ -13,6 +13,9 @@ from software.simulated_tests.pytest_validations.ball_is_off_ground import ( BallIsEventuallyOffGround, ) +from software.simulated_tests.pytest_validations.robot_at_angular_velocity import ( + RobotEventuallyAtAngularVelocity, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -179,24 +182,54 @@ def setup(*args): setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - test_timeout_s=10, + test_timeout_s=5, ) @pytest.mark.parametrize( "orientation, initial_position, destination, angular_velocity", [ + # Robot facing right, should rotate counter clockwise to face up + ( + tbots_cpp.Angle.quarter(), + tbots_cpp.Point(2, 0), + tbots_cpp.Point(-2, 0), + tbots_cpp.Angle.fromDegrees(360), + ), + # Robot facing right, should rotate clockwise to face down + ( + tbots_cpp.Angle.threeQuarter(), + tbots_cpp.Point(2, 0), + tbots_cpp.Point(-2, 0), + tbots_cpp.Angle.fromDegrees(-360), + ), + # Robot facing right, should rotate counter clockwise to face up ( - tbots_cpp.Angle.zero(), - tbots_cpp.Point(-4, 2), - tbots_cpp.Point(4, 2), + tbots_cpp.Angle.quarter(), + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0, 0), tbots_cpp.Angle.fromDegrees(360), ), + # Robot facing right, should rotate clockwise to face down ( - tbots_cpp.Angle.half(), - tbots_cpp.Point(4, 2), - tbots_cpp.Point(-4, 2), - tbots_cpp.Angle.fromDegrees(-1440), + tbots_cpp.Angle.threeQuarter(), + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0, 0), + tbots_cpp.Angle.fromDegrees(-360), + ), + # Robot facing right, should rotate counter clockwise to face left, slightly up + ( + tbots_cpp.Angle.fromDegrees(175), + tbots_cpp.Point(3, -1), + tbots_cpp.Point(0, 1), + tbots_cpp.Angle.fromDegrees(360), + ), + # Robot facing right, should rotate clockwise to face down + ( + tbots_cpp.Angle.fromDegrees(185), + tbots_cpp.Point(3, -1), + tbots_cpp.Point(0, 1), + tbots_cpp.Angle.fromDegrees(-360), ), ], ) @@ -207,8 +240,6 @@ def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[initial_position], - # TODO (#2558): implement initializing robot angular velocities - # blue_robot_angular_velocities=[tbots_cpp.AngularVelocity.quarter()], yellow_robot_locations=[tbots_cpp.Point(4, 0)], ball_location=tbots_cpp.Point(1, 1), ball_velocity=tbots_cpp.Vector(0, 0), @@ -231,9 +262,10 @@ def setup(*args): eventually_validation_sequence_set = [ [ + # high threshold just to check direction of angular velocity + RobotEventuallyAtAngularVelocity(0, angular_velocity, 4), RobotEventuallyAtPosition(0, destination), - RobotEventuallyAtOrientation(0, orientation, 0.1), - # TODO: validate robot is at angular velocity (need to initialize robot angular velocity in Python first) + RobotEventuallyAtOrientation(0, orientation), # TODO: validate position and orientation for 1000 ticks ] ] @@ -242,7 +274,6 @@ def setup(*args): setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - test_timeout_s=10, ) diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 1bdf853cff..8100a8143c 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -209,6 +209,7 @@ PYBIND11_MODULE(python_bindings, m) .def_static("threeQuarter", &Angle::threeQuarter) .def_static("full", &Angle::full) .def("toRadians", &Angle::toRadians) + .def("minDiff", &Angle::minDiff) // Overloaded .def("__repr__", [](const Angle& a) diff --git a/src/software/simulated_tests/pytest_validations/robot_at_orientation.py b/src/software/simulated_tests/pytest_validations/robot_at_orientation.py index f97c21af7d..d8e7c1e632 100644 --- a/src/software/simulated_tests/pytest_validations/robot_at_orientation.py +++ b/src/software/simulated_tests/pytest_validations/robot_at_orientation.py @@ -38,7 +38,7 @@ def get_validation_status(self, world) -> ValidationStatus: robot_angle = tbots_cpp.Angle.fromRadians( robot.current_state.global_orientation.radians ) - angle_diff = abs(robot_angle.toRadians() - self.orientation.toRadians()) + angle_diff = robot_angle.minDiff(self.orientation).toRadians() if angle_diff < self.threshold: return ValidationStatus.PASSING return ValidationStatus.FAILING From 91aedabd29ce1f5733617a56d242e1cd8b1e6717 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 19:30:06 -0800 Subject: [PATCH 42/52] Finalize kick tactic test --- TODO.txt | 2 +- .../ai/hl/stp/tactic/kick/kick_tactic_test.py | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/TODO.txt b/TODO.txt index 14c1d787c4..15d5fecf47 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,7 +6,7 @@ src/software/ai/hl/stp/tactic/pivot_kick/pivot_kick_tactic_test.cpp (DONE 99% AC src/software/ai/hl/stp/tactic/crease_defender/crease_defender_tactic_test.cpp (DONE 99% ACCURACY + ADDITIONAL) src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 99%) -src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE) diff --git a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py index ccfa6519ba..c9cdc26dfe 100644 --- a/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/kick/kick_tactic_test.py @@ -4,9 +4,6 @@ from proto.import_all_protos import KickTactic from proto.message_translation.tbots_protobuf import create_world_state -from software.simulated_tests.pytest_validations.ball_enters_region import ( - BallEventuallyEntersRegion, -) from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( BallEventuallyKickedInDirection, ) @@ -68,23 +65,16 @@ def setup(*args): } ) - kick_direction_vector = tbots_cpp.Vector(1, 0).rotate(angle_to_kick_at) - kick_target = ball_position + kick_direction_vector * 3 - - eventually_validation_sequence_set = [ + eventually_validations = [ [ - BallEventuallyEntersRegion(regions=[tbots_cpp.Circle(kick_target, 0.5)]), - BallEventuallyKickedInDirection(kick_direction=angle_to_kick_at), + BallEventuallyKickedInDirection(angle_to_kick_at), ] ] simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=5, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, ) From 40da72e3506619b0423be9f783d202626e2ede3e Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 20:26:13 -0800 Subject: [PATCH 43/52] Finalize chip tactic test --- TODO.txt | 2 +- .../ai/hl/stp/tactic/chip/chip_tactic_test.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/TODO.txt b/TODO.txt index 15d5fecf47..afe8a1ca70 100644 --- a/TODO.txt +++ b/TODO.txt @@ -8,7 +8,7 @@ src/software/ai/hl/stp/tactic/halt/halt_tactic_test.cpp (DONE 99% ACCURACY) src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) -src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE) src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp diff --git a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py index e3ae36436a..53b82d3bcb 100644 --- a/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/chip/chip_tactic_test.py @@ -6,6 +6,9 @@ from software.simulated_tests.pytest_validations.ball_is_off_ground import ( BallIsEventuallyOffGround, ) +from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( + BallEventuallyKickedInDirection, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -72,20 +75,17 @@ def setup(*args): } ) - eventually_validation_sequence_set = [ + eventually_validations = [ [ BallIsEventuallyOffGround(), - # TODO (#2588): check ball is chipped/kicked in target direction + BallEventuallyKickedInDirection(angle_to_chip_at), ] ] simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=5, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, ) From 61e5f4e960ec623f4867ed3ecc384207af41c9b2 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Thu, 5 Mar 2026 20:39:13 -0800 Subject: [PATCH 44/52] Fix hrvo test --- src/software/ai/navigator/trajectory/simulated_hrvo_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py index f9c6800be6..aef3cad55e 100644 --- a/src/software/ai/navigator/trajectory/simulated_hrvo_test.py +++ b/src/software/ai/navigator/trajectory/simulated_hrvo_test.py @@ -217,8 +217,6 @@ def hrvo_setup( ), ) - simulated_test_runner.set_tactics(blue_tactics=blue_tactics) - yellow_tactics = {} for index, destination in enumerate(enemy_robots_destinations): @@ -226,7 +224,9 @@ def hrvo_setup( index, destination, tbots.Angle.fromRadians(0) ) - simulated_test_runner.set_tactics(yellow_tactics=yellow_tactics) + simulated_test_runner.set_tactics( + blue_tactics=blue_tactics, yellow_tactics=yellow_tactics + ) @pytest.mark.parametrize( From 2b4f1a4f2320e168149f4b3746a155c25ae3897a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 10:17:22 -0800 Subject: [PATCH 45/52] Almost finalize dribble tactic test --- TODO.txt | 2 +- .../stp/tactic/dribble/dribble_tactic_test.py | 370 +++++++++--------- 2 files changed, 182 insertions(+), 190 deletions(-) diff --git a/TODO.txt b/TODO.txt index afe8a1ca70..86a076cbe0 100644 --- a/TODO.txt +++ b/TODO.txt @@ -9,7 +9,7 @@ src/software/ai/hl/stp/tactic/move/move_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE 99%) -src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE) +src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE 98% need to implement thing) src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp diff --git a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py index 83ff3c7a6d..14ad635883 100644 --- a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py @@ -1,16 +1,25 @@ import pytest - import software.python_bindings as tbots_cpp +from software.py_constants import ROBOT_MAX_RADIUS_METERS + from proto.import_all_protos import DribbleTactic -from software.simulated_tests.pytest_validations.robot_enters_region import * -from software.simulated_tests.pytest_validations.ball_enters_region import * -from software.simulated_tests.pytest_validations.robot_received_ball import * -from software.simulated_tests.pytest_validations.robot_at_orientation import * -from software.simulated_tests.pytest_validations.excessive_dribbling import * +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.ball_enters_region import ( + BallAlwaysStaysInRegion, + BallEventuallyEntersRegion, +) +from software.simulated_tests.pytest_validations.excessive_dribbling import ( + EventuallyStartsExcessivelyDribbling, +) +from software.simulated_tests.pytest_validations.robot_at_orientation import ( + RobotEventuallyAtOrientation, +) +from software.simulated_tests.pytest_validations.robot_received_ball import ( + RobotEventuallyReceivedBall, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) -from proto.message_translation.tbots_protobuf import create_world_state def get_enemy_robot_positions(): @@ -25,178 +34,183 @@ def get_enemy_robot_positions(): ] -def test_intercept_ball_behind_enemy_robot(simulated_test_runner): - initial_position = tbots_cpp.Point(-3, 1.5) - +@pytest.mark.parametrize( + "initial_pos, dribble_dest, dribble_orientation, ball_pos, ball_vel", + [ + # test_intercept_ball_behind_enemy_robot + ( + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(0, 0), + None, + tbots_cpp.Point(3, -2), + tbots_cpp.Vector(-0.5, 1), + ), + # test_stopped_ball + ( + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(0, 0), + None, + tbots_cpp.Point(-1, 1.5), + tbots_cpp.Vector(0, 0), + ), + # test_ball_bounce_off_of_enemy_robot + ( + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(0, 0), + None, + tbots_cpp.Point(0, 0), + tbots_cpp.Vector(2.5, 0), + ), + # test_moving_ball_dribble_dest + ( + tbots_cpp.Point(-3, 1.5), + tbots_cpp.Point(-3, 1), + None, + tbots_cpp.Point(3, -2), + tbots_cpp.Vector(-1, 2), + ), + # test_moving_ball_dribble_orientation + ( + tbots_cpp.Point(-3, 1.5), + None, + tbots_cpp.Angle.quarter(), + tbots_cpp.Point(3, -2), + tbots_cpp.Vector(-1, 2), + ), + # test_moving_ball_dribble_dest_and_orientation + ( + tbots_cpp.Point(-2, 1.5), + tbots_cpp.Point(-1, 2), + tbots_cpp.Angle.zero(), + tbots_cpp.Point(1, 0), + tbots_cpp.Vector(1, 2), + ), + # test_dribble_dest_and_orientation_around_rectangle + ( + tbots_cpp.Point(3, -3), + tbots_cpp.Point(4, 2.5), + tbots_cpp.Angle.half(), + tbots_cpp.Point(4, -2.5), + tbots_cpp.Vector(0, 0), + ), + ], +) +def test_dribble( + initial_pos, + dribble_dest, + dribble_orientation, + ball_pos, + ball_vel, + simulated_test_runner, +): def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[ tbots_cpp.Point(-3, 2.5), - initial_position, + initial_pos, ], yellow_robot_locations=get_enemy_robot_positions(), - ball_location=tbots_cpp.Point(3, -2), - ball_velocity=tbots_cpp.Vector(-0.5, 1), + ball_location=ball_pos, + ball_velocity=ball_vel, ) ) - simulated_test_runner.set_tactics( - blue_tactics={ - 1: DribbleTactic( - dribble_destination=tbots_cpp.createPointProto( - tbots_cpp.Point(0, 0) - ), - final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() - ), - allow_excessive_dribbling=False, - ) - } + dribble_params = DribbleTactic( + allow_excessive_dribbling=False, ) + if dribble_dest: + dribble_params.dribble_destination.CopyFrom( + tbots_cpp.createPointProto(dribble_dest) + ) + if dribble_orientation: + dribble_params.final_dribble_orientation.CopyFrom( + tbots_cpp.createAngleProto(dribble_orientation) + ) - eventually_validation_sequence_set = [ - [ - RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), - # TODO (#2588): validate that robot keeps possession - # TODO (#2514): tune dribbling and re-enable - # Robot not excessively dribbling - ] - ] - - simulated_test_runner.run_test( - setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=10, - ) - + simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) -def test_stopped_ball(simulated_test_runner): - initial_position = tbots_cpp.Point(-3, 1.5) + eventually_validations = [[RobotEventuallyReceivedBall(1)]] - def setup(*args): - simulated_test_runner.set_world_state( - create_world_state( - blue_robot_locations=[ - tbots_cpp.Point(3, 3), - initial_position, - ], - yellow_robot_locations=get_enemy_robot_positions(), - ball_location=tbots_cpp.Point(-1, 1.5), - ball_velocity=tbots_cpp.Vector(0, 0), - ) + if dribble_dest: + eventually_validations[0].append( + BallEventuallyEntersRegion([tbots_cpp.Circle(dribble_dest, 0.3)]) ) - simulated_test_runner.set_tactics( - blue_tactics={ - 1: DribbleTactic( - dribble_destination=tbots_cpp.createPointProto( - tbots_cpp.Point(0, 0) - ), - final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() - ), - allow_excessive_dribbling=False, - ) - } + if dribble_orientation: + eventually_validations[0].append( + RobotEventuallyAtOrientation(1, dribble_orientation) ) - eventually_validation_sequence_set = [ - [ - RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), - # TODO (#2514): tune dribbling and re-enable - # Robot not excessively dribbling - ] - ] + eventually_validations[0].append(RobotEventuallyReceivedBall(1)) + + # TODO (#2514): tune dribbling and re-enable + # Robot always not excessively dribbling simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=10, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, + test_timeout_s=25, + run_till_end=False, ) -def test_ball_bounce_off_of_enemy_robot(simulated_test_runner): - initial_position = tbots_cpp.Point(-3, 1.5) +def test_dribble_with_excessive_dribbling(simulated_test_runner): + dribble_destination = tbots_cpp.Point(3, 2) + initial_position = tbots_cpp.Point(4.5, -3.0) + dribble_orientation = tbots_cpp.Angle.half() def setup(*args): simulated_test_runner.set_world_state( create_world_state( blue_robot_locations=[ - tbots_cpp.Point(3, 3), + tbots_cpp.Point(-3, 2.5), initial_position, ], yellow_robot_locations=get_enemy_robot_positions(), - ball_location=tbots_cpp.Point(0, 0), - ball_velocity=tbots_cpp.Vector(2.5, 0), + ball_location=tbots_cpp.Point(4.2, -2.5), + ball_velocity=tbots_cpp.Vector(0, 0), ) ) simulated_test_runner.set_tactics( blue_tactics={ 1: DribbleTactic( - dribble_destination=tbots_cpp.createPointProto( - tbots_cpp.Point(0, 0) - ), + dribble_destination=tbots_cpp.createPointProto(dribble_destination), final_dribble_orientation=tbots_cpp.createAngleProto( - tbots_cpp.Angle.zero() + dribble_orientation ), - allow_excessive_dribbling=False, + allow_excessive_dribbling=True, ) } ) eventually_validation_sequence_set = [ [ - RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), - # TODO (#2514): tune dribbling and re-enable - # Robot not excessively dribbling - ] + RobotEventuallyReceivedBall(1), + BallEventuallyEntersRegion([tbots_cpp.Circle(dribble_destination, 0.3)]), + RobotEventuallyAtOrientation(1, dribble_orientation), + ], + [EventuallyStartsExcessivelyDribbling()], ] simulated_test_runner.run_test( setup=setup, inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], test_timeout_s=10, ) -@pytest.mark.parametrize( - "dribble_destination, dribble_orientation, ball_pos, ball_vel", - [ - ( - tbots_cpp.Point(-3, 1), - None, - tbots_cpp.Point(3, -2), - tbots_cpp.Vector(-1, 2), - ), - ( - None, - tbots_cpp.Angle.quarter(), - tbots_cpp.Point(3, -2), - tbots_cpp.Vector(-1, 2), - ), - ( - tbots_cpp.Point(-1, 2), - tbots_cpp.Angle.zero(), - tbots_cpp.Point(1, 0), - tbots_cpp.Vector(1, 2), - ), - ], -) -def test_moving_ball_dribble( - dribble_destination, dribble_orientation, ball_pos, ball_vel, simulated_test_runner +def test_run_into_enemy_robot_knock_ball_away( + simulated_test_runner, ): - initial_position = tbots_cpp.Point(-3, 1.5) + initial_position = tbots_cpp.Point(-2, 1.5) + dribble_destination = tbots_cpp.Point(-1, 2) + dribble_orientation = tbots_cpp.Angle.half() + ball_pos = tbots_cpp.Point(2, -2) + ball_vel = tbots_cpp.Vector(2, 4) def setup(*args): simulated_test_runner.set_world_state( @@ -205,7 +219,8 @@ def setup(*args): tbots_cpp.Point(-3, 2.5), initial_position, ], - yellow_robot_locations=get_enemy_robot_positions(), + yellow_robot_locations=get_enemy_robot_positions() + + [tbots_cpp.Point(1, 1.1)], ball_location=ball_pos, ball_velocity=ball_vel, ) @@ -213,99 +228,76 @@ def setup(*args): dribble_params = DribbleTactic( allow_excessive_dribbling=False, + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto(dribble_orientation), ) - if dribble_destination: - dribble_params.dribble_destination.CopyFrom( - tbots_cpp.createPointProto(dribble_destination) - ) - if dribble_orientation: - dribble_params.final_dribble_orientation.CopyFrom( - tbots_cpp.createAngleProto(dribble_orientation) - ) simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) - validations = [RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05)] - - if dribble_destination: - validations.append( - BallEventuallyEntersRegion( - regions=[tbots_cpp.Circle(dribble_destination, 0.3)] - ) - # TODO (#2514): tune dribbling and re-enable - # Robot not excessively dribbling - ) - - if dribble_orientation: - validations.append( - RobotEventuallyAtOrientation( - robot_id=1, orientation=dribble_orientation, threshold=0.1 - ) - ) + eventually_validations = [ + [ + RobotEventuallyReceivedBall(1), + BallEventuallyEntersRegion([tbots_cpp.Circle(dribble_destination, 0.3)]), + RobotEventuallyAtOrientation(1, dribble_orientation), + RobotEventuallyReceivedBall(1), + ] + ] - eventually_validation_sequence_set = [validations] + # TODO (#2514): tune dribbling and re-enable + # Robot always not excessively dribbling simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=15, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, + test_timeout_s=10, ) -def test_dribble_with_excessive_dribbling(simulated_test_runner): - dribble_destination = tbots_cpp.Point(3, 2) - initial_position = tbots_cpp.Point(4.5, -3.0) - dribble_orientation = tbots_cpp.Angle.half() +def test_robot_not_bumping_ball_when_turning( + simulated_test_runner, +): + # The ball is placed right behind the friendly robot. Verify that the robot + # does not bump the ball away when turning around to dribble it. + robot_location = tbots_cpp.Point(-1, 0) + ball_location = robot_location + tbots_cpp.Vector(ROBOT_MAX_RADIUS_METERS, 0) def setup(*args): simulated_test_runner.set_world_state( create_world_state( - blue_robot_locations=[ - tbots_cpp.Point(-3, 2.5), - initial_position, + blue_robot_locations=[tbots_cpp.Point(-3, 2.5), robot_location], + blue_robot_orientations=[ + tbots_cpp.Angle.zero(), + tbots_cpp.Angle.half(), ], - yellow_robot_locations=get_enemy_robot_positions(), - ball_location=tbots_cpp.Point(4.2, -2.5), + yellow_robot_locations=get_enemy_robot_positions() + + [tbots_cpp.Point(1, 1.1)], + ball_location=ball_location, ball_velocity=tbots_cpp.Vector(0, 0), ) ) - simulated_test_runner.set_tactics( - blue_tactics={ - 1: DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto( - dribble_orientation - ), - allow_excessive_dribbling=True, - ) - } - ) + dribble_params = DribbleTactic() + simulated_test_runner.set_tactics(blue_tactics={1: dribble_params}) - eventually_validation_sequence_set = [ + eventually_validations = [ [ - RobotEventuallyReceivedBall(robot_id=1, tolerance=0.05), - BallEventuallyEntersRegion( - regions=[tbots_cpp.Circle(dribble_destination, 0.3)] - ), - RobotEventuallyAtOrientation( - robot_id=1, orientation=dribble_orientation, threshold=0.1 - ), - # TODO (#2514): tune dribbling and re-enable - # Robot eventually excessively dribbling + RobotEventuallyReceivedBall(1), ] ] + # TODO (#2514): tune dribbling and re-enable + # Robot always not excessively dribbling + always_validations = [ + [BallAlwaysStaysInRegion([tbots_cpp.Circle(ball_location, 0.05)])] + ] + simulated_test_runner.run_test( setup=setup, - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=[[]], - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=[[]], - test_timeout_s=12, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, + inv_always_validation_sequence_set=always_validations, + ag_always_validation_sequence_set=always_validations, ) From ea227c3cde8a0613add439ec653e7dd042f899bc Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 11:09:42 -0800 Subject: [PATCH 46/52] Add receiver tactic tests --- TODO.txt | 2 +- src/software/ai/hl/stp/tactic/receiver/BUILD | 15 +- .../tactic/receiver/receiver_tactic_test.py | 244 ++++++++++++++++++ 3 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py diff --git a/TODO.txt b/TODO.txt index 86a076cbe0..68e7d495d9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -10,7 +10,7 @@ src/software/ai/hl/stp/tactic/kick/kick_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLED) src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE 98% need to implement thing) -src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp +src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp diff --git a/src/software/ai/hl/stp/tactic/receiver/BUILD b/src/software/ai/hl/stp/tactic/receiver/BUILD index 8548bfe80e..f88aa6d4c5 100644 --- a/src/software/ai/hl/stp/tactic/receiver/BUILD +++ b/src/software/ai/hl/stp/tactic/receiver/BUILD @@ -1,5 +1,7 @@ package(default_visibility = ["//visibility:public"]) +load("@simulated_tests_deps//:requirements.bzl", "requirement") + cc_library( name = "receiver_tactic", srcs = [ @@ -34,7 +36,7 @@ cc_test( ) cc_test( - name = "receiver_tactic_test", + name = "receiver_tactic_cpp_test", srcs = ["receiver_tactic_test.cpp"], deps = [ ":receiver_tactic", @@ -47,3 +49,14 @@ cc_test( "//software/world", ], ) + +py_test( + name = "receiver_tactic_test", + srcs = ["receiver_tactic_test.py"], + tags = ["exclusive"], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py new file mode 100644 index 0000000000..260251eb78 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py @@ -0,0 +1,244 @@ +import pytest + +import software.python_bindings as tbots_cpp +from proto.import_all_protos import Pass, ReceiverTactic +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.friendly_team_scored import ( + FriendlyTeamEventuallyScored, +) +from software.simulated_tests.pytest_validations.robot_at_orientation import ( + RobotEventuallyAtOrientation, +) +from software.simulated_tests.pytest_validations.robot_received_ball import ( + RobotEventuallyReceivedBall, +) +from software.simulated_tests.simulated_test_fixture import pytest_main + + +def calculate_ball_velocity(passer_point, receiver_point, speed): + """Calculate ball velocity based on pass trajectory""" + direction = receiver_point - passer_point + if direction.length() > 0: + direction = direction.normalize() + return direction * speed + + +@pytest.mark.parametrize( + "passer_point, receiver_point, pass_speed, robot_pos, robot_orientation, one_touch", + [ + # Robot already at receive point + ( + tbots_cpp.Point(0.0, 0.5), + tbots_cpp.Point(2, 2), + 4, + tbots_cpp.Point(2, 2), + tbots_cpp.Angle.zero(), + False, + ), + # Robot slightly off from receive point: test 1 + ( + tbots_cpp.Point(0.0, 0.4), + tbots_cpp.Point(2, 2), + 4, + tbots_cpp.Point(2, 1.5), + tbots_cpp.Angle.zero(), + False, + ), + # Robot slightly off from receive point: test 2 + ( + tbots_cpp.Point(0.0, 0.4), + tbots_cpp.Point(2, 2), + 4, + tbots_cpp.Point(2.5, 2.0), + tbots_cpp.Angle.zero(), + False, + ), + # Robot facing away from pass + ( + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Point(-3, 0), + 4, + tbots_cpp.Point(-3, 0), + tbots_cpp.Angle.half(), + False, + ), + # Robot facing towards pass + ( + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Point(-3, 0), + 4, + tbots_cpp.Point(-3, 0), + tbots_cpp.Angle.zero(), + False, + ), + # Robot facing towards pass speedy + ( + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Point(-3, 0), + 5, + tbots_cpp.Point(-3, 0), + tbots_cpp.Angle.zero(), + False, + ), + # Sharp angles, these are only a finite set of what + # sort of sharp angles we can achieve. + # + # If we are noticing issues with one-touch on the field, we should + # add more tests here and explore more of the "one-touch" space + # + # TODO (#2570): re-enable when one-touch aren't flaky for these tests + # TODO(#2909): re-enable once the robot can turn faster and hits the ball + # + # one touch robot on receiver point + # ( + # tbots_cpp.Point(2.0, 0.0), + # tbots_cpp.Point(3.5, 2.5), + # 3.5, + # tbots_cpp.Point(3.5, 2.5), + # tbots_cpp.Angle.zero(), + # True, + # ), + # ( + # tbots_cpp.Point(2.0, 0.0), + # tbots_cpp.Point(3.5, -2.5), + # 3.5, + # tbots_cpp.Point(3.5, -2.5), + # tbots_cpp.Angle.zero(), + # True, + # ), + # # one touch robot away from receiver point + # ( + # tbots_cpp.Point(1.5, 0.0), + # tbots_cpp.Point(2.5, 2.5), + # 3.5, + # tbots_cpp.Point(2.0, 2.5), + # tbots_cpp.Angle.zero(), + # True, + # ), + # ( + # tbots_cpp.Point(1.5, 0.0), + # tbots_cpp.Point(2.5, -2.5), + # 3.5, + # tbots_cpp.Point(2.0, -2.5), + # tbots_cpp.Angle.zero(), + # True, + # ), + ( + tbots_cpp.Point(4.0, 1.5), + tbots_cpp.Point(4, -1), + 5, + tbots_cpp.Point(4, -1), + tbots_cpp.Angle.half(), + True, + ), + # ( + # tbots_cpp.Point(4.0, 1.5), + # tbots_cpp.Point(3.5, -1), + # 5, + # tbots_cpp.Point(3.5, -1), + # tbots_cpp.Angle.zero(), + # True, + # ), + ( + tbots_cpp.Point(4.0, 1.5), + tbots_cpp.Point(3.0, -1), + 4.5, + tbots_cpp.Point(3.0, -1), + tbots_cpp.Angle.half(), + True, + ), + # ( + # tbots_cpp.Point(4.0, -1.5), + # tbots_cpp.Point(4, 1), + # 5, + # tbots_cpp.Point(4, 1), + # tbots_cpp.Angle.half(), + # True, + # ), + # ( + # tbots_cpp.Point(4.0, -1.5), + # tbots_cpp.Point(3.5, 1), + # 5, + # tbots_cpp.Point(3.5, 1), + # tbots_cpp.Angle.zero(), + # True, + # ), + ( + tbots_cpp.Point(4.0, -1.5), + tbots_cpp.Point(3, 1), + 4.5, + tbots_cpp.Point(3, 1), + tbots_cpp.Angle.zero(), + True, + ), + # ( + # tbots_cpp.Point(3.0, 0.0), + # tbots_cpp.Point(2, 0), + # 4, + # tbots_cpp.Point(2, 0), + # tbots_cpp.Angle.zero(), + # True, + # ), + ], +) +def test_receiver( + passer_point, + receiver_point, + pass_speed, + robot_pos, + robot_orientation, + one_touch, + simulated_test_runner, +): + def setup(*args): + ball_velocity = calculate_ball_velocity( + passer_point, receiver_point, pass_speed + ) + + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_pos, + ], + yellow_robot_locations=[], + ball_location=passer_point, + ball_velocity=ball_velocity, + ) + ) + + # Necessary since pass is a python keyword + receiver_args = { + "pass": Pass( + passer_point=tbots_cpp.createPointProto(passer_point), + receiver_point=tbots_cpp.createPointProto(receiver_point), + pass_speed_m_per_s=pass_speed, + ), + } + + simulated_test_runner.set_tactics( + blue_tactics={1: ReceiverTactic(**receiver_args)} + ) + + eventually_validation_sequence_set = [ + [FriendlyTeamEventuallyScored()] + if one_touch + else [ + RobotEventuallyAtOrientation( + robot_id=1, + orientation=(passer_point - receiver_point).orientation(), + ), + RobotEventuallyReceivedBall(robot_id=1), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + run_till_end=False, + ) + + +if __name__ == "__main__": + pytest_main(__file__) From 113337e39859048272a531b6cd655291f00f433a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 11:17:01 -0800 Subject: [PATCH 47/52] Temp fix for defense play test --- src/software/ai/hl/stp/play/defense/defense_play_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/play/defense/defense_play_test.py b/src/software/ai/hl/stp/play/defense/defense_play_test.py index fdf82bbcfe..03ec7724a5 100644 --- a/src/software/ai/hl/stp/play/defense/defense_play_test.py +++ b/src/software/ai/hl/stp/play/defense/defense_play_test.py @@ -58,8 +58,9 @@ def setup(*args): blue_play = Play() blue_play.name = PlayName.DefensePlay + # TODO: fix having to set tactics first since it sets both teams + simulated_test_runner.set_tactics() simulated_test_runner.set_play(blue_play, is_friendly=True) - simulated_test_runner.set_tactics(yellow_tactics={}) simulated_test_runner.run_test( setup=setup, From 1aba4bd1596979284f38eb6f227591183d45acef Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 11:54:05 -0800 Subject: [PATCH 48/52] Add attacker tactic tests --- TODO.txt | 6 +- src/software/ai/hl/stp/tactic/attacker/BUILD | 13 + .../tactic/attacker/attacker_tactic_test.py | 415 ++++++++++++++++++ src/software/ai/hl/stp/tactic/receiver/BUILD | 4 +- 4 files changed, 433 insertions(+), 5 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py diff --git a/TODO.txt b/TODO.txt index 68e7d495d9..081f166e62 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,9 +11,9 @@ src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp (DISABLE src/software/ai/hl/stp/tactic/chip/chip_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.cpp (DONE 98% need to implement thing) src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp (DONE 99%) -src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp -src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp -src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp (DONE 98% need to implement thing) +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp (VERY COMPLEX VALIDATIONS) +src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp (DONE 98% need to implement thing) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp diff --git a/src/software/ai/hl/stp/tactic/attacker/BUILD b/src/software/ai/hl/stp/tactic/attacker/BUILD index 6b1300c8cd..6c72220060 100644 --- a/src/software/ai/hl/stp/tactic/attacker/BUILD +++ b/src/software/ai/hl/stp/tactic/attacker/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -76,3 +78,14 @@ cc_test( "//software/world", ], ) + +py_test( + name = "attacker_tactic_test", + srcs = ["attacker_tactic_test.py"], + tags = ["exclusive"], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py b/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py new file mode 100644 index 0000000000..5dfe7891c7 --- /dev/null +++ b/src/software/ai/hl/stp/tactic/attacker/attacker_tactic_test.py @@ -0,0 +1,415 @@ +import pytest +import software.python_bindings as tbots_cpp +from software.py_constants import ROBOT_MAX_RADIUS_METERS + +from proto.import_all_protos import AttackerTactic, Pass +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.ball_kicked_in_direction import ( + BallEventuallyKickedInDirection, +) +from software.simulated_tests.pytest_validations.excessive_dribbling import ( + NeverExcessivelyDribbles, +) +from software.simulated_tests.pytest_validations.friendly_team_scored import ( + FriendlyTeamEventuallyScored, +) +from software.simulated_tests.pytest_validations.robot_at_orientation import ( + RobotEventuallyAtOrientation, +) +from software.simulated_tests.pytest_validations.robot_at_position import ( + RobotEventuallyAtPosition, +) +from software.simulated_tests.simulated_test_fixture import pytest_main + + +def calculate_ball_velocity(passer_point, receiver_point, speed): + """Calculate ball velocity based on pass trajectory""" + direction = receiver_point - passer_point + if direction.length() > 0: + direction = direction.normalize() + return direction * speed + + +# Passing Tests +@pytest.mark.parametrize( + "passer_point, receiver_point, pass_speed, robot_pos, ball_pos, ball_velocity", + [ + # Stationary Ball Tests + # Attacker point != Balls location & Balls location != Robots Location + ( + tbots_cpp.Point(0, 0.5), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0.5, 0.5), + tbots_cpp.Vector(0, 0), + ), + # Attacker point == Balls location & Balls location != Robots Location + ( + tbots_cpp.Point(-0.5, -0.5), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(0, 0), + tbots_cpp.Point(-0.5, -0.5), + tbots_cpp.Vector(0, 0), + ), + # Attacker point != Balls location & Balls location == Robots Location + ( + tbots_cpp.Point(0.5, 0.5), + tbots_cpp.Point(0, 1), + 5, + tbots_cpp.Point(0.5, 0.5), + tbots_cpp.Point(-0.5, 0.5), + tbots_cpp.Vector(0, 0), + ), + # TODO(#2909): Enable test once the robot can turn faster and hits the ball with + # the ball at the exact robot location + # Attacker point == Balls location & Balls location == Robots Location + # ( + # tbots_cpp.Point(0.0, 0.0), + # tbots_cpp.Point(0, 0), + # 5, + # tbots_cpp.Point(0, 0), + # tbots_cpp.Point(0.0, 0.0), + # tbots_cpp.Vector(0, 0), + # ), + # Attacker point far away (not a normal use case, but just to sanity check) + ( + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(3.5, 2.5), + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Vector(0, 0), + ), + # Attacker point != Balls location & Balls location != Robots Location (duplicate) + ( + tbots_cpp.Point(0, 0.5), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0.5, 0.5), + tbots_cpp.Vector(0, 0), + ), + # Moving Ball Tests + # Attacker point == Balls location & Balls location != Robots Location + ( + tbots_cpp.Point(-0.5, -0.5), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(0, 0), + tbots_cpp.Point(-0.5, -0.5), + tbots_cpp.Vector(1, 0), + ), + # TODO (#2859): Robot does not kick ball when dribbler is off since it is + # too far away. + # Attacker point != Balls location & Balls location == Robots Location + # ( + # tbots_cpp.Point(0.4, 0.4), + # tbots_cpp.Point(0, 1), + # 5, + # tbots_cpp.Point(0.5, 0.5), + # tbots_cpp.Point(-0.4, 0.4), + # tbots_cpp.Vector(0, 1), + # ), + # Attacker point == Balls location & Balls location == Robots Location + ( + tbots_cpp.Point(0, 0), + tbots_cpp.Point(0, 0), + 5, + tbots_cpp.Point(0, 0), + tbots_cpp.Point(ROBOT_MAX_RADIUS_METERS, ROBOT_MAX_RADIUS_METERS), + tbots_cpp.Vector(1, 0), + ), + ], +) +def test_attacker_passing( + passer_point, + receiver_point, + pass_speed, + robot_pos, + ball_pos, + ball_velocity, + simulated_test_runner, +): + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_pos, + ], + # TODO (#2558) + # In C++ test: Uses ai_config to set min_open_angle_for_shot_deg to force passing + # Temp fix by blocking off the enemy goal + yellow_robot_locations=[ + tbots_cpp.Point(4, -0.4), + tbots_cpp.Point(4, -0.2), + tbots_cpp.Point(4, 0), + tbots_cpp.Point(4, 0.2), + tbots_cpp.Point(4, 0.4), + ], + ball_location=ball_pos, + ball_velocity=ball_velocity, + ) + ) + + simulated_test_runner.set_tactics( + blue_tactics={ + 1: AttackerTactic( + best_pass_so_far=Pass( + passer_point=tbots_cpp.createPointProto(passer_point), + receiver_point=tbots_cpp.createPointProto(receiver_point), + pass_speed_m_per_s=pass_speed, + ), + pass_committed=True, + ) + } + ) + + pass_orientation = (receiver_point - passer_point).orientation() + + eventually_validation_sequence_set = [ + [ + RobotEventuallyAtOrientation(robot_id=1, orientation=pass_orientation), + # Larger threshold since techically ball is at passer point, not the robot + RobotEventuallyAtPosition( + robot_id=1, position=passer_point, threshold=ROBOT_MAX_RADIUS_METERS + ), + BallEventuallyKickedInDirection(kick_direction=pass_orientation), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + run_till_end=False, + test_timeout_s=7, + ) + + +# Keep Away Tests +@pytest.mark.parametrize( + "passer_point, receiver_point, pass_speed, robot_pos, robot_orientation, ball_pos, ball_velocity, enemy_positions, ignore_score_checks", + [ + # Basic keep away with enemy robots around + ( + tbots_cpp.Point(-0.2, 0.0), + tbots_cpp.Point(-3, 2.5), + 5, + tbots_cpp.Point(0.25, 0), + tbots_cpp.Angle.half(), + tbots_cpp.Point(0.0, 0.0), + tbots_cpp.Vector(0, 0), + [ + tbots_cpp.Point(-0.6, 0.25), + tbots_cpp.Point(0.0, 0.6), + tbots_cpp.Point(-0.25, 0.5), + tbots_cpp.Point(0.6, -0.25), + ], + True, # This test is too unstable, so we ignore the checks + ), + # TODO(#2909): Enable test once the robot can turn faster and hits the ball with + # the ball at the exact robot location + # ( + # tbots_cpp.Point(0.0, 0.0), + # tbots_cpp.Point(-3, 2.5), + # 5, + # tbots_cpp.Point(0.25, 0), + # tbots_cpp.Angle.zero(), + # tbots_cpp.Point(0.0, 0.0), + # tbots_cpp.Vector(0, 0), + # [tbots_cpp.Point(-0.5, 0.5)], + # False, + # ), + ], +) +def test_attacker_keep_away( + passer_point, + receiver_point, + pass_speed, + robot_pos, + robot_orientation, + ball_pos, + ball_velocity, + enemy_positions, + ignore_score_checks, + simulated_test_runner, +): + # TODO(#2558): Port C++ validation functions that don't exist in Python yet + # In C++ test: + # - Uses ai_config to set min_open_angle_for_shot_deg to 90 and + # enemy_about_to_steal_ball_radius to 0.01 + # - Validates calculateProximityRisk improves over time + # - Validates ratePassEnemyRisk improves over time + # - Validates ball stays in field boundaries + # This test is skipped in C++ due to instability + + field = tbots_cpp.Field.createSSLDivisionBField() + field_top_left = field.fieldLines().negXPosYCorner() + + # Keep away near field corner (second test case from C++) + def setup(*args): + robot_pos = field_top_left + ball_pos = tbots_cpp.Point(field_top_left.x() + 0.05, field_top_left.y() - 0.2) + enemy_positions = [ + tbots_cpp.Point(-4, 2), + tbots_cpp.Point(-4, 2.25), + tbots_cpp.Point(-4, 2.5), + tbots_cpp.Point(-4, 2.75), + ] + + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + tbots_cpp.Point(-3, 2.5), + robot_pos, + ], + yellow_robot_locations=enemy_positions, + ball_location=ball_pos, + ball_velocity=tbots_cpp.Vector(0, 0), + ) + ) + + pass_point = tbots_cpp.Point( + field_top_left.x() + 0.05, field_top_left.y() - 0.05 + ) + receiver_point = tbots_cpp.Point(0, 0) + + simulated_test_runner.set_tactics( + blue_tactics={ + 1: AttackerTactic( + best_pass_so_far=Pass( + passer_point=tbots_cpp.createPointProto(pass_point), + receiver_point=tbots_cpp.createPointProto(receiver_point), + pass_speed_m_per_s=5, + ), + pass_committed=False, + ) + } + ) + + always_validation_sequence_set = [ + [ + NeverExcessivelyDribbles(), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_always_validation_sequence_set=always_validation_sequence_set, + ag_always_validation_sequence_set=always_validation_sequence_set, + ) + + +# Shoot Goal Tests +@pytest.mark.parametrize( + "ball_pos, ball_velocity, robot_pos, enemy_positions", + [ + # # TODO (#2693): Fix FSM looping endlessly + # # enemy goal blocked by enemy robots with enemy threat right + # ( + # tbots_cpp.Point(2, 1), + # tbots_cpp.Vector(0, 0), + # tbots_cpp.Point(1, 1), + # [ + # tbots_cpp.Point(2.4, 1), + # tbots_cpp.Point(3, 0.4), + # tbots_cpp.Point(3, 0.8), + # tbots_cpp.Point(3.1, 0.6), + # tbots_cpp.Point(3.1, 1), + # tbots_cpp.Point(4.2, 1.2), + # ], + # ), + # small opening in enemy formation + ( + tbots_cpp.Point(2, 1), + tbots_cpp.Vector(0, 0), + tbots_cpp.Point(1, 1), + [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(3, 0.2), + tbots_cpp.Point(3, 0.8), + tbots_cpp.Point(3.1, 0), + tbots_cpp.Point(3.1, 1), + tbots_cpp.Point(4.2, 1.2), + ], + ), + # extreme angle shot + ( + tbots_cpp.Point(3.8, -1.9), + tbots_cpp.Vector(0, 0), + tbots_cpp.Point(1, 1), + [ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(3, 1.2), + tbots_cpp.Point(3, 0.8), + tbots_cpp.Point(3.1, 0.6), + tbots_cpp.Point(3.1, 1), + tbots_cpp.Point(4.2, 0.5), + ], + ), + # enemy trying to steal + ( + tbots_cpp.Point(2.5, -1), + tbots_cpp.Vector(0, 0), + tbots_cpp.Point(1, 1), + [ + tbots_cpp.Point(2.5, -1.4), + tbots_cpp.Point(3, 0.4), + tbots_cpp.Point(3, 0.8), + tbots_cpp.Point(3.1, 0.6), + tbots_cpp.Point(3.1, 1), + tbots_cpp.Point(4.2, 1.2), + ], + ), + ], +) +def test_attacker_shoot_goal( + ball_pos, ball_velocity, robot_pos, enemy_positions, simulated_test_runner +): + # TODO (#2558) + # In C++ test: + # - Uses ai_config to set pass_delay_sec to 0.0 + # - Uses motion constraint FRIENDLY_DEFENSE_AREA + + field = tbots_cpp.Field.createSSLDivisionBField() + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[robot_pos], + yellow_robot_locations=enemy_positions, + ball_location=ball_pos, + ball_velocity=ball_velocity, + ) + ) + + simulated_test_runner.set_tactics( + blue_tactics={ + 0: AttackerTactic( + chip_target=tbots_cpp.createPointProto( + tbots_cpp.Point(0, field.fieldLines().yMin()) + ), + ) + } + ) + + eventually_validation_sequence_set = [ + [ + FriendlyTeamEventuallyScored(), + ] + ] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + run_till_end=False, + test_timeout_s=9, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/ai/hl/stp/tactic/receiver/BUILD b/src/software/ai/hl/stp/tactic/receiver/BUILD index f88aa6d4c5..672482d2e5 100644 --- a/src/software/ai/hl/stp/tactic/receiver/BUILD +++ b/src/software/ai/hl/stp/tactic/receiver/BUILD @@ -1,7 +1,7 @@ -package(default_visibility = ["//visibility:public"]) - load("@simulated_tests_deps//:requirements.bzl", "requirement") +package(default_visibility = ["//visibility:public"]) + cc_library( name = "receiver_tactic", srcs = [ From 38f8274658326b68dc00914768b69dac1bc78732 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 12:01:56 -0800 Subject: [PATCH 49/52] Port additional dribble tests --- TODO.txt | 3 +-- .../stp/tactic/dribble/dribble_tactic_test.py | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index 081f166e62..ae02adf794 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,8 +14,7 @@ src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.cpp (DONE 99%) src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp (DONE 98% need to implement thing) src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp (VERY COMPLEX VALIDATIONS) src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp (DONE 98% need to implement thing) -src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp -src/software/ai/hl/stp/tactic/penalty_kick/penalty_kick_tactic_test.cpp +src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp (DONE 99%) PLAY TESTS TO PORT diff --git a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py index 14ad635883..1cb5924195 100644 --- a/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/dribble_tactic_test.py @@ -1,6 +1,6 @@ import pytest import software.python_bindings as tbots_cpp -from software.py_constants import ROBOT_MAX_RADIUS_METERS +from software.py_constants import DIST_TO_FRONT_OF_ROBOT_METERS, ROBOT_MAX_RADIUS_METERS from proto.import_all_protos import DribbleTactic from proto.message_translation.tbots_protobuf import create_world_state @@ -93,6 +93,30 @@ def get_enemy_robot_positions(): tbots_cpp.Point(4, -2.5), tbots_cpp.Vector(0, 0), ), + # test_steal_ball_from_behind_enemy 1 + ( + tbots_cpp.Point(-2, 2.5), + tbots_cpp.Point(0, 2), + tbots_cpp.Angle.zero(), + tbots_cpp.Point(1 + DIST_TO_FRONT_OF_ROBOT_METERS, 2.5), + tbots_cpp.Vector(0, 0), + ), + # test_steal_ball_from_behind_enemy 2 + ( + tbots_cpp.Point(3.5, 2.5), + tbots_cpp.Point(0, 2), + tbots_cpp.Angle.zero(), + tbots_cpp.Point(1 + DIST_TO_FRONT_OF_ROBOT_METERS, 2.5), + tbots_cpp.Vector(0, 0), + ), + # test_steal_ball_from_behind_enemy 3 + ( + tbots_cpp.Point(1, 0), + tbots_cpp.Point(0, 2), + tbots_cpp.Angle.zero(), + tbots_cpp.Point(1 + DIST_TO_FRONT_OF_ROBOT_METERS, 2.5), + tbots_cpp.Vector(0, 0), + ), ], ) def test_dribble( From 915efcc4f2b679c9023334fc5224e22207be1e71 Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 12:19:39 -0800 Subject: [PATCH 50/52] Update TODO --- TODO.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index ae02adf794..4c66e31a57 100644 --- a/TODO.txt +++ b/TODO.txt @@ -15,18 +15,18 @@ src/software/ai/hl/stp/tactic/attacker/attacker_tactic_passing_test.cpp (DONE 98 src/software/ai/hl/stp/tactic/attacker/attacker_tactic_keep_away_test.cpp (VERY COMPLEX VALIDATIONS) src/software/ai/hl/stp/tactic/attacker/attacker_tactic_shoot_goal_test.cpp (DONE 98% need to implement thing) src/software/ai/hl/stp/tactic/dribble/dribble_tactic_push_enemy_test.cpp (DONE 99%) +src/software/ai/hl/stp/tactic/goalie/goalie_tactic_chip_test.cpp PLAY TESTS TO PORT src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp -src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp src/software/ai/hl/stp/play/stop_play_test.cpp -src/software/ai/hl/stp/play/stop_play_test.cpp -src/software/ai/hl/stp/play/example/example_play_test.cpp src/software/ai/hl/stp/play/free_kick/free_kick_play_test.cpp src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp -src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.cpp src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp -src/software/ai/hl/stp/play/hardware_challenge_plays/pass_endurance_play_test.cpp +src/software/ai/hl/stp/play/example/example_play_test.cpp + +src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp (GRAYSONS PR PERHAPS?) +src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp (GRAYSONS PR) From d7aefcd543ee2993462d4309d47930d8c1a14f8e Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 14:05:32 -0800 Subject: [PATCH 51/52] Fix receiver tactic test --- src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py index 260251eb78..d8350d40d5 100644 --- a/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/receiver/receiver_tactic_test.py @@ -214,6 +214,7 @@ def setup(*args): receiver_point=tbots_cpp.createPointProto(receiver_point), pass_speed_m_per_s=pass_speed, ), + "disable_one_touch_shot": not one_touch, } simulated_test_runner.set_tactics( From b5024692d81da355a531e6c3b264769b22d92d5a Mon Sep 17 00:00:00 2001 From: Eric Xiao Date: Fri, 6 Mar 2026 22:27:20 -0800 Subject: [PATCH 52/52] Port shoot or pass play test --- TODO.txt | 2 +- .../ai/hl/stp/play/shoot_or_pass/BUILD | 19 ++++- .../shoot_or_pass/shoot_or_pass_play_test.py | 71 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py diff --git a/TODO.txt b/TODO.txt index 4c66e31a57..b2d3d0bfc2 100644 --- a/TODO.txt +++ b/TODO.txt @@ -19,13 +19,13 @@ src/software/ai/hl/stp/tactic/goalie/goalie_tactic_chip_test.cpp PLAY TESTS TO PORT +src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp (DONE 99%) src/software/ai/hl/stp/play/kickoff_friendly_play_test.cpp src/software/ai/hl/stp/play/kickoff_enemy_play_test.cpp src/software/ai/hl/stp/play/stop_play_test.cpp src/software/ai/hl/stp/play/free_kick/free_kick_play_test.cpp src/software/ai/hl/stp/play/shoot_or_chip_play_test.cpp src/software/ai/hl/stp/play/crease_defense/crease_defense_play_test.cpp -src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.cpp src/software/ai/hl/stp/play/example/example_play_test.cpp src/software/ai/hl/stp/play/penalty_kick_enemy/penalty_kick_enemy_play_test.cpp (GRAYSONS PR PERHAPS?) diff --git a/src/software/ai/hl/stp/play/shoot_or_pass/BUILD b/src/software/ai/hl/stp/play/shoot_or_pass/BUILD index 14b18525f5..0ae65d7a8e 100644 --- a/src/software/ai/hl/stp/play/shoot_or_pass/BUILD +++ b/src/software/ai/hl/stp/play/shoot_or_pass/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) # We force linking for all plays so that the static variables required for the @@ -46,7 +48,7 @@ cc_test( ) cc_test( - name = "shoot_or_pass_play_test", + name = "shoot_or_pass_play_cpp_test", srcs = ["shoot_or_pass_play_test.cpp"], deps = [ ":shoot_or_pass_play", @@ -59,3 +61,18 @@ cc_test( "//software/world", ], ) + +py_test( + name = "shoot_or_pass_play_test", + srcs = [ + "shoot_or_pass_play_test.py", + ], + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests/pytest_validations:all_validations", + requirement("pytest"), + ], +) diff --git a/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py b/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py new file mode 100644 index 0000000000..62588021da --- /dev/null +++ b/src/software/ai/hl/stp/play/shoot_or_pass/shoot_or_pass_play_test.py @@ -0,0 +1,71 @@ +import software.python_bindings as tbots_cpp +from proto.import_all_protos import * +from proto.play_pb2 import Play, PlayName +from proto.ssl_gc_common_pb2 import Team +from proto.message_translation.tbots_protobuf import create_world_state +from software.simulated_tests.pytest_validations.friendly_team_scored import ( + FriendlyTeamEventuallyScored, +) +from software.simulated_tests.simulated_test_fixture import pytest_main + + +def test_shoot_or_pass_play(simulated_test_runner): + field = tbots_cpp.Field.createSSLDivisionBField() + + def setup(*args): + simulated_test_runner.set_world_state( + create_world_state( + blue_robot_locations=[ + field.friendlyGoalCenter(), + tbots_cpp.Point(-4.5, 3.0), + tbots_cpp.Point(-2, 1.5), + tbots_cpp.Point(-2, 0.5), + tbots_cpp.Point(-2, -1.7), + tbots_cpp.Point(-2, -1.5), + ], + yellow_robot_locations=[ + tbots_cpp.Point(1, 0), + tbots_cpp.Point(1, 2.5), + tbots_cpp.Point(1, -2.5), + field.enemyGoalCenter(), + field.enemyDefenseArea().negXNegYCorner(), + field.enemyDefenseArea().negXPosYCorner(), + ], + ball_location=tbots_cpp.Point(-4.4, 2.9), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + blue_play = Play() + blue_play.name = PlayName.ShootOrPassPlay + + simulated_test_runner.set_play(blue_play, is_friendly=True) + + yellow_play = Play() + yellow_play.name = PlayName.HaltPlay + + simulated_test_runner.set_play(yellow_play, is_friendly=False) + + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.STOP, team=Team.UNKNOWN + ) + simulated_test_runner.send_gamecontroller_command( + gc_command=Command.Type.FORCE_START, team=Team.BLUE + ) + + # TODO (#3233): The attacker robot sometimes doesn't kick the ball towards the receiver + + # Eventually Validation + eventually_validations = [[FriendlyTeamEventuallyScored()]] + + simulated_test_runner.run_test( + setup=setup, + inv_eventually_validation_sequence_set=eventually_validations, + ag_eventually_validation_sequence_set=eventually_validations, + test_timeout_s=15, + run_till_end=False, + ) + + +if __name__ == "__main__": + pytest_main(__file__)