Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
8733d80
dribbling
fred-huang122 Nov 14, 2025
306e651
improved logic
fred-huang122 Nov 17, 2025
f7aeeb1
bug fix
fred-huang122 Nov 17, 2025
c535696
hot fix
fred-huang122 Nov 17, 2025
960a66c
updates
fred-huang122 Nov 17, 2025
1443eb8
improvements
fred-huang122 Nov 20, 2025
6992fff
typing
fred-huang122 Nov 20, 2025
c41dcb5
optimizations to rsim
fred-huang122 Nov 20, 2025
a3af538
Update utama_core/rsoccer_simulator/src/ssl/envs/standard_ssl.py
fred-huang122 Nov 20, 2025
702f2ed
Merge branch 'main' into rsim-dribbling
fred-huang122 Nov 20, 2025
6284cde
Merge branch 'main' into rsim-dribbling
fred-huang122 Nov 23, 2025
838fff7
bug fixes + updates
fred-huang122 Nov 23, 2025
582d27b
linting
fred-huang122 Nov 23, 2025
82850be
test bug fix
fred-huang122 Nov 23, 2025
b011603
test_fix
fred-huang122 Nov 23, 2025
0c36171
undo
fred-huang122 Nov 23, 2025
e1f16ac
undo
fred-huang122 Nov 23, 2025
d0b8210
reverted params
fred-huang122 Nov 23, 2025
9b33ef8
bug fixes
fred-huang122 Nov 23, 2025
dddafec
improved dribbler logic
fred-huang122 Nov 23, 2025
bbb4023
Update utama_core/team_controller/src/controllers/real/real_robot_con…
fred-huang122 Nov 24, 2025
75bcf79
Merge branch 'main' into rsim-dribbling
isaac0804 Nov 25, 2025
4ed3e4a
updates
fred-huang122 Nov 26, 2025
174dc74
update fix
fred-huang122 Nov 26, 2025
d4678ab
updates
fred-huang122 Nov 26, 2025
30faaa8
updates
fred-huang122 Nov 26, 2025
a28a158
fixes
fred-huang122 Nov 26, 2025
d5339ca
removed prints
fred-huang122 Nov 26, 2025
b547a90
feat: change goalkeep
Nov 26, 2025
368d9d7
feat: fix bug
Nov 26, 2025
0370dfc
add change
Jan 14, 2026
7cf62de
Fix: fix the bug run toward enemy
NingchuanIC Jan 17, 2026
60d37cd
Fix: add a barrier to the forbiden square
NingchuanIC Jan 17, 2026
232cce1
Fix: goal keep
NingchuanIC Jan 17, 2026
b7d6afa
simplification of similar codes
Jan 18, 2026
8a5938a
simplification
Jan 21, 2026
c055a87
minor: improve in algo
Jan 21, 2026
527a3ef
minor: improve in algo
Jan 21, 2026
d4970ec
fix: algorithm problem
Jan 21, 2026
c024939
Fix: write the algorithm that let goalkeeper go between predict line …
NingchuanIC Jan 24, 2026
3a307b7
Feat: add the third defender
NingchuanIC Jan 24, 2026
fbf65d6
Fix: better tribble defender
NingchuanIC Jan 24, 2026
481c57c
Merge branch 'main' into fix/goalkeeping
fred-huang122 Jan 24, 2026
9356700
chore: linting
fred-huang122 Jan 24, 2026
0dae44e
Update utama_core/skills/src/goalkeep.py
NingchuanIC Jan 24, 2026
eb0a3d0
Update utama_core/skills/src/defend_parameter.py
NingchuanIC Jan 24, 2026
44e91ad
Update utama_core/skills/src/defend_parameter.py
NingchuanIC Jan 24, 2026
ac6d7ca
Remove unused imports from defend_parameter.py (#100)
Copilot Jan 24, 2026
6ad963b
Fix goalkeep to handle teams with more than 3 robots (#99)
Copilot Jan 24, 2026
82d7be6
Remove dead code file goal_keep.py (#98)
Copilot Jan 24, 2026
4930ce2
Remove unused math import from standard_ssl.py (#97)
Copilot Jan 24, 2026
892ef5e
Fix hardcoded goal coordinates in defend_parameter.py for bidirection…
Copilot Jan 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 69 additions & 42 deletions utama_core/skills/src/defend_parameter.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,85 @@
import math
from typing import Optional

from utama_core.entities.data.command import RobotCommand
import numpy as np

from utama_core.entities.data.vector import Vector2D
from utama_core.entities.game import Game
from utama_core.motion_planning.src.common.motion_controller import MotionController
from utama_core.rsoccer_simulator.src.ssl.envs.standard_ssl import SSLStandardEnv
from utama_core.skills.src.go_to_point import go_to_point
from utama_core.skills.src.utils.defense_utils import (
align_defenders,
find_likely_enemy_shooter,
to_defense_parametric,
velocity_to_orientation,
)


def defend_parameter(
game: Game,
motion_controller: MotionController,
defender_id: int,
robot_id: int,
env: Optional[SSLStandardEnv] = None,
) -> RobotCommand:
_, enemy, ball = game.friendly_robots, game.enemy_robots, game.ball
shooters_data = find_likely_enemy_shooter(enemy, ball)
orientation = None
tracking_ball = False

if not shooters_data:
target_tracking_coord = ball.p.to_2d()
if ball.v is not None and None not in ball.v:
orientation = velocity_to_orientation(ball.v.to_2d())
tracking_ball = True
):
defenseing_friendly = game.friendly_robots[robot_id]
vel = game.ball.v.to_2d()
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function assumes game.ball.v is not None and has a valid to_2d() method. If the ball velocity is None or invalid, this will cause an AttributeError. Consider adding a None check before accessing ball velocity.

Suggested change
vel = game.ball.v.to_2d()
ball_v = game.ball.v
if ball_v is None:
vel = np.array([0.0, 0.0])
else:
vel = ball_v.to_2d()

Copilot uses AI. Check for mistakes.
if len(game.friendly_robots) > 2:
if game.ball.p.y >= -0.5 and robot_id == 1:
target_pos = [3.0 if game.my_team_is_right else -3.0, -1.2]
return go_to_point(game, motion_controller, robot_id, Vector2D(target_pos[0], target_pos[1]))
elif game.ball.p.y < -0.5 and robot_id == 2:
target_pos = [3.0 if game.my_team_is_right else -3.0, 1.2]
return go_to_point(game, motion_controller, robot_id, Vector2D(target_pos[0], target_pos[1]))
Comment on lines +22 to +27
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic at lines 24 and 27 has asymmetric conditions. Line 24 uses '>= -0.5' while line 27 uses '< -0.5', meaning when game.ball.p.y equals -0.5, both conditions could execute if robot_id is 1 and len(game.friendly_robots) > 2. Additionally, when ball.p.y is between -0.5 and positive values and robot_id is 2, no early return occurs. The logic should ensure all cases are properly handled.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +27
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditions at lines 24-29 only handle specific robot IDs (1 and 2) and don't account for other defender robot IDs that might exist. If robot_id is something other than 1 or 2 but len(game.friendly_robots) > 2, the function continues without the early return, which may not be the intended behavior. Consider using a more robust approach to identify which defenders should take which positions.

Copilot uses AI. Check for mistakes.
if robot_id == 1:
goal_frame = 0.5
else:
goal_frame = -0.5

def positions_to_defend_parameter(x2, y2):
x1, y1 = game.ball.p.x, game.ball.p.y
x3, y3 = defenseing_friendly.p.x, defenseing_friendly.p.y
t = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / ((x2 - x1) ** 2 + (y2 - y1) ** 2)
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential division by zero when (x2 - x1)^2 + (y2 - y1)^2 equals zero (i.e., when the ball position equals the goal target position). This should be checked before the division.

Suggested change
t = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / ((x2 - x1) ** 2 + (y2 - y1) ** 2)
denom = (x2 - x1) ** 2 + (y2 - y1) ** 2
if denom < 1e-12:
t = 0.0
else:
t = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / denom

Copilot uses AI. Check for mistakes.
x4 = x1 + t * (x2 - x1)
y4 = y1 + t * (y2 - y1)

def cal_xy5(xa, ya, xb, yb, w, x4, y4):
x5 = xa - w
dx = xb - xa
if abs(dx) < 1e-12:
return x4, y4
t = (x5 - xa) / dx
y5 = ya + t * (yb - ya)
return x5, y5

def distance(x1, y1, x2, y2):
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

if abs(x2 - x4) < 1 and -1 < y4 < 1:
hori_x, hori_y = cal_xy5(x2, y2, x1, y1, 1.0, x4, y4)
ver_x, ver_y = cal_xy5(y2, x2, y1, x1, 2.0, x4, y4)
if distance(x4, y4, hori_x, hori_y) < distance(x4, y4, ver_x, ver_y):
x4, y4 = hori_x, hori_y
else:
Comment on lines +52 to +57
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function cal_xy5 has parameter names that suggest swapping x and y coordinates (xa, ya, xb, yb at line 56 vs ya=y2, xa=x2, yb=y1, xb=x1), which is confusing. However, the return value at line 60 swaps them back (ver_y, ver_x). This double-swapping makes the code difficult to understand and error-prone. Consider using clearer variable names or adding comments to explain the coordinate transformation.

Suggested change
if abs(x2 - x4) < 1 and -1 < y4 < 1:
hori_x, hori_y = cal_xy5(x2, y2, x1, y1, 1.0, x4, y4)
ver_x, ver_y = cal_xy5(y2, x2, y1, x1, 2.0, x4, y4)
if distance(x4, y4, hori_x, hori_y) < distance(x4, y4, ver_x, ver_y):
x4, y4 = hori_x, hori_y
else:
if abs(x2 - x4) < 1 and -1 < y4 < 1:
# Horizontal candidate: shift the projection point by `w` along the x-axis
hori_x, hori_y = cal_xy5(x2, y2, x1, y1, 1.0, x4, y4)
# Vertical candidate: reuse the same helper by transposing coordinates
# (treat y as x and x as y). The result is therefore (y, x) in field
# coordinates and must be swapped back below.
ver_x, ver_y = cal_xy5(y2, x2, y1, x1, 2.0, x4, y4)
if distance(x4, y4, hori_x, hori_y) < distance(x4, y4, ver_x, ver_y):
x4, y4 = hori_x, hori_y
else:
# Swap back to (x, y) field coordinates obtained via the transposed call

Copilot uses AI. Check for mistakes.
x4, y4 = ver_y, ver_x

return x3, y3, x4, y4

if vel[0] ** 2 + vel[1] ** 2 > 0.05:
x2, y2 = 4.5 if game.my_team_is_right else -4.5, goal_frame + 0.2 if robot_id == 1 else goal_frame - 0.2
x3, y3, x4, y4 = positions_to_defend_parameter(x2, y2)
target_pos = np.array([x4, y4])

else:
# TODO (deploy more defenders, or find closest shooter?)
sd = shooters_data[0]
target_tracking_coord = Vector2D(sd.p.x, sd.p.y)
orientation = sd.orientation

real_def_pos = game.friendly_robots[defender_id].p
current_def_parametric = to_defense_parametric(game, real_def_pos)
target = align_defenders(game, current_def_parametric, target_tracking_coord, orientation, env)
cmd = go_to_point(
game,
motion_controller,
defender_id,
target,
dribbling=True,
)

gp = (game.field.my_goal_line[0][0], 0)
if env:
env.draw_line(
[gp, (target_tracking_coord[0], target_tracking_coord[1])],
width=5,
color="RED" if tracking_ball else "PINK",
robot_rad = 0.09
x2, y2 = 4.5 if game.my_team_is_right else -4.5, -goal_frame
x3, y3, x4, y4 = positions_to_defend_parameter(x2, y2)
vec_to_target = np.array(
[
x4 - x3,
y4 - y3,
]
)
dist_to_target = np.linalg.norm(vec_to_target)

if dist_to_target > 0:
vec_dir = vec_to_target / dist_to_target
else:
vec_dir = np.array([0.0, 0.0])
target_pos = np.array([x4, y4]) - vec_dir * robot_rad

return cmd
return go_to_point(game, motion_controller, robot_id, Vector2D(target_pos[0], target_pos[1]), dribbling=True)
51 changes: 38 additions & 13 deletions utama_core/skills/src/goalkeep.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,50 @@ def goalkeep(
robot_id: int,
env: Optional[SSLStandardEnv] = None,
):
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed code previously handled the case when the goalkeeper has possession of the ball by moving to a clear position and kicking. This logic is now completely absent from the goalkeep function. Without this, the goalkeeper may not properly clear the ball when it gains possession, which could lead to defensive failures.

Suggested change
):
):
# Determine if the goalkeeper effectively has possession of the ball.
# In the absence of an explicit possession flag, approximate it by proximity.
keeper = game.friendly_robots[robot_id]
ball = game.ball
distance_to_ball = np.hypot(keeper.p.x - ball.p.x, keeper.p.y - ball.p.y)
# If very close to the ball, clear it toward a safer area away from our own goal.
if distance_to_ball < 0.1:
if game.my_team_is_right:
# Our goal is on the right; clear toward the left half.
clear_target = Vector2D(-3.0, 0.0)
else:
# Our goal is on the left; clear toward the right half.
clear_target = Vector2D(3.0, 0.0)
return go_to_point(
game,
motion_controller,
robot_id,
clear_target,
dribbling=False,
)

Copilot uses AI. Check for mistakes.
goalie_obj = game.friendly_robots[robot_id]
if goalie_obj.has_ball:
target_oren = np.pi if game.my_team_is_right else 0
return move(
game,
motion_controller,
robot_id,
Vector2D((4 if game.my_team_is_right else -4), 0),
target_oren,
True,
)

if game.my_team_is_right:
target = predict_ball_pos_at_x(game, 4.5)
else:
target = predict_ball_pos_at_x(game, -4.5)

stop_y = 0.0

def intersection_with_vertical_line(a, b, x_line=4.5):
xa, ya = a
xb, yb = b
if xb < xa:
return a

k = (yb - ya) / (xb - xa)
y_intersect = ya + k * (x_line - xa)
if y_intersect < -0.5:
return (x_line, -0.5)
elif y_intersect > 0.5:
return (x_line, 0.5)
return (x_line, y_intersect)
Comment on lines +27 to +39
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intersection_with_vertical_line function doesn't consider the my_team_is_right parameter. The x_line default value of 4.5 is hardcoded and will be incorrect when defending the left goal.

Copilot uses AI. Check for mistakes.

if len(game.friendly_robots) == 2:
try:
_, yy = intersection_with_vertical_line(
(game.ball.p.x, game.ball.p.y), (game.friendly_robots[1].p.x, game.friendly_robots[1].p.y + 0.1)
)
stop_y = (yy + 0.5) / 2
except (IndexError, KeyError):
# If robot with ID 1 is not available, keep default stop_y
pass
elif len(game.friendly_robots) >= 3:
try:
_, yy1 = intersection_with_vertical_line(
(game.ball.p.x, game.ball.p.y), (game.friendly_robots[1].p.x, game.friendly_robots[1].p.y + 0.1)
)
_, yy2 = intersection_with_vertical_line(
(game.ball.p.x, game.ball.p.y), (game.friendly_robots[2].p.x, game.friendly_robots[2].p.y - 0.1)
)
stop_y = (yy1 + yy2) / 2
except (IndexError, KeyError):
# If robots with IDs 1 or 2 are not available, keep existing stop_y
pass
if not target or abs(target[1]) > 0.5:
target = Vector2D(4.5 if game.my_team_is_right else -4.5, 0)
target = Vector2D(4.5 if game.my_team_is_right else -4.5, stop_y)

# shooters_data = find_likely_enemy_shooter(game.enemy_robots, game.ball)

Expand Down
Loading