Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 57 additions & 31 deletions crates/nodes/behavior_node/src/walk.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use coordinate_systems::{Field, Ground};
use filtering::hysteresis::less_than_with_relative_hysteresis;
use hsl_network_messages::PlayerNumber;
use hsl_network_messages::{PlayerNumber, Team};
use linear_algebra::{Isometry2, Orientation2, Point, Point2, Pose2, point};
use path_planner::path_planner::PathPlanner;
use types::{
behavior_tree::Status,
field_dimensions::FieldDimensions,
motion_command::{BodyMotion, MotionCommand, OrientationMode},
motion_type::MotionType,
parameters::VoronoiParameters,
parameters::{KickOffPose, KickoffParameters, VoronoiParameters},
path::{Path, direct_path},
world_state::WorldState,
};
use voronoi::{Ownership, VoronoiGrid};

Expand All @@ -18,7 +19,6 @@ use crate::{
actions::stand,
behavior_tree::Node,
condition,
conditions::hulks_is_kicking_team,
kick::{kick, select_kick_target, use_last_kick_power},
node::Blackboard,
selection, sequence, subtree,
Expand Down Expand Up @@ -213,39 +213,65 @@ pub fn walk_to_block_position(blackboard: &mut Blackboard) -> Status {
}

pub fn walk_to_kickoff_pose(blackboard: &mut Blackboard) -> Status {
if let (Some(ground_to_field), player_number) = (
blackboard.world_state.robot.ground_to_field,
blackboard.world_state.robot.player_number,
) {
let field_to_ground = ground_to_field.inverse();
let kickoff = &blackboard.parameters.kickoff;
let standard_pose = kickoff.standard_positions[player_number];
let striker_position = kickoff.striker_position;
let walk_and_stand = blackboard.parameters.walking.walk_and_stand;
let walk_to_kickoff_speed = blackboard.parameters.walking.speed.walk_to_kickoff;

let mut target_position = standard_pose.position;
let Some(ground_to_field) = blackboard.world_state.robot.ground_to_field else {
return Status::Failure;
};
let Some(kickoff_pose) = select_kickoff_pose(
&blackboard.world_state,
blackboard.parameters.goalkeeper.player_number,
&blackboard.parameters.kickoff,
) else {
return Status::Failure;
};
Comment on lines +219 to +225

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let Some(kickoff_pose) = select_kickoff_pose(
&blackboard.world_state,
blackboard.parameters.goalkeeper.player_number,
&blackboard.parameters.kickoff,
) else {
return Status::Failure;
};
let kickoff_pose = select_kickoff_pose(
&blackboard.world_state,
blackboard.parameters.goalkeeper.player_number,
&blackboard.parameters.kickoff,
)?;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the function returns a Status and not a option of a status that is because i wrote it like that


if hulks_is_kicking_team(blackboard) && player_number == PlayerNumber::Three {
target_position = striker_position;
}
let kickoff_pose_in_field = Pose2::from_parts(
kickoff_pose.position,
Orientation2::new(kickoff_pose.rotation),
);
Comment thread
knoellle marked this conversation as resolved.
let walk_and_stand = blackboard.parameters.walking.walk_and_stand;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We are already in a "walk and stand" context. Calling this variable "walk and stand" is not useful.
Please call it something with "parameters".


walk_to(
blackboard,
ground_to_field.inverse() * kickoff_pose_in_field,
blackboard.parameters.walking.speed.walk_to_kickoff,
OrientationMode::AlignWithPath,
walk_and_stand.normal_distance_to_be_aligned,
walk_and_stand.hysteresis,
)
}

let kickoff_pose_in_field =
Pose2::from_parts(target_position, Orientation2::new(standard_pose.rotation));
fn select_kickoff_pose(
world_state: &WorldState,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is only used to short-circuit if we are penalized. Do that in the outer function pleasel.
Then you can drop the Option in the return type.

goalkeeper_player_number: PlayerNumber,
parameters: &KickoffParameters,
) -> Option<KickOffPose> {
let player_number = world_state.robot.player_number;
let game_controller_state = world_state.filtered_game_controller_state.as_ref()?;
if game_controller_state.penalties[player_number].is_some() {
return None;
}

let kickoff_pose_in_ground = field_to_ground * kickoff_pose_in_field;
if player_number == goalkeeper_player_number {
return Some(parameters.goalkeeper_pose);
}

walk_to(
blackboard,
kickoff_pose_in_ground,
walk_to_kickoff_speed,
OrientationMode::AlignWithPath,
walk_and_stand.normal_distance_to_be_aligned,
walk_and_stand.hysteresis,
);
Status::Success
let field_player_rank = game_controller_state
.penalties
.iter()
.rev()
.filter(|(number, penalty)| *number != goalkeeper_player_number && penalty.is_none())
.position(|(number, _)| number == player_number)?;

if game_controller_state.kicking_team == Some(Team::Hulks) {
match field_player_rank {
0 => Some(parameters.striker_pose),
rank => parameters.aggressive_positions.get(rank - 1).copied(),
}
} else {
Status::Failure
parameters
.defensive_positions
.get(field_player_rank)
.copied()
}
}

Expand Down
10 changes: 7 additions & 3 deletions crates/types/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use coordinate_systems::{Camera, Field, Ground, NormalizedPixel, Pixel, Robot};
use linear_algebra::{Framed, Point2, Vector2, Vector3};

use crate::{field_color::FieldColorParameters, motion_command::MotionCommand, players::Players};
use crate::{field_color::FieldColorParameters, motion_command::MotionCommand};

#[derive(Clone, Debug, Default, Deserialize, Serialize, ros_z::Message)]
pub struct WhistleDetectionParameters {
Expand Down Expand Up @@ -101,8 +101,12 @@ pub struct KickOffPose {

#[derive(Clone, Debug, Default, Deserialize, Serialize, ros_z::Message)]
pub struct KickoffParameters {
pub striker_position: Point2<Field>,
pub standard_positions: Players<KickOffPose>,
pub goalkeeper_pose: KickOffPose,
pub striker_pose: KickOffPose,
/// Own-kickoff support slots, filled in order by descending active player number.
pub aggressive_positions: [KickOffPose; 3],
/// Opponent-kickoff slots, filled in order by descending active player number.
pub defensive_positions: [KickOffPose; 4],
}

#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, ros_z::Message)]
Expand Down
54 changes: 46 additions & 8 deletions docs/robotics/behavior/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,36 @@ branches first check the robot's `PrimaryState`:

If no branch succeeds, behavior falls back to a safe standing command.

## Ready Formations

Ready uses static poses from `behavior_node.kickoff`, expressed in field
coordinates (meters, with the own goal at negative x) and rotations in radians.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Specifying these units and coordinate frames here is redundant. We use these everywhere.

The base poses are starting values for a 9 × 6 m field and can be overridden for
other field dimensions.

- The player selected by `goalkeeper.player_number` uses `goalkeeper_pose`.
- On our kickoff, the highest-numbered active field player uses `striker_pose`.
The remaining field players fill `aggressive_positions` in array order, from
highest to lowest player number.
- On opponent or unknown kickoff, all field players fill `defensive_positions`
in the same order.

The GameController's unpenalized lineup determines the active players.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"the GameController's lineup"?
what?

Substitutes, sent-off players, and players with another penalty do not occupy
formation slots. Ready requires a GameController state; a missing state or a
penalty for the current robot produces the standing fallback. For example, with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see in the code where it defaults to standing.

only players 3, 4, and 5 eligible, our kickoff places 5 at `striker_pose`, 4 at
`aggressive_positions[0]`, and 3 at `aggressive_positions[1]`.

The GameController operator must keep the selected lineup up to date. In the
behavior simulator, robots absent from the scenario are marked as substitutes;
penalties explicitly assigned to simulated robots are preserved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"preserved"?


Assignments depend on the available roster and kickoff ownership; the target

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"roster"? "lineup"?
where are these terms coming from?

coordinates stay fixed while teammates move. Path planning continues to account
for obstacles. These assignments apply during Ready; Playing selects its tasks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
for obstacles. These assignments apply during Ready; Playing selects its tasks
for obstacles. These assignments only apply during Ready; Playing selects its tasks

using the playing tree below.

## Playing

The playing subtree assigns one of four high-level tasks:
Expand Down Expand Up @@ -106,16 +136,24 @@ teammates and obstacles. If no support position can be produced, it stands.

## Team Communication

While playing, behavior periodically creates a State message. It contains the
player number, the robot pose, and the observed ball position and age when a
ball is available. A message is sent only when the robot has a field pose, the
send interval has elapsed, and the remaining game message budget is high
enough.
During Playing, behavior periodically creates a State message. It
contains the player number, the robot pose, and the observed ball position and
age when a ball is available. A message is sent only when the robot has a field
pose, the send interval has elapsed, and the remaining game message budget is
high enough.
Comment on lines -109 to +143

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why this change?


The interval is configured by `network.hsl_playing_state_message_send_interval`
(300 ms by default). Received teammate states expire after
`player_states_receiver.playing_maximum_age` (one second).
Comment on lines +145 to +147

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why does this PR add this paragraph?
First, it will be outdated again once the dynamic message interval is merged and second, it has nothing to do with the changes in this PR.


Ready formation assignment uses the GameController lineup and sends no team
State messages. GameController return messages are sent independently during
Ready and the other states.
Comment on lines +149 to +151

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is there a comment about game controller return messages in the team communication section?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this mean?


Received teammate states provide the poses used for closest-to-ball selection
and supporter positioning. Team communication can be absent or delayed, so the
tree retains branches for simple operation, the last active robot, and missing
ball information.
and supporter positioning during Playing. Team communication can be absent or
delayed, so the tree retains branches for simple operation, the last active
robot, and missing ball information.
Comment on lines -109 to +156

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One sentence per line please.


## Motion Output

Expand Down
23 changes: 15 additions & 8 deletions etc/parameters/base/behavior_node.json5
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,21 @@
penalty_kick_target_y_scale: 0.7,
},
kickoff: {
striker_position: [-1.5, 0.0],
standard_positions: {
one: { position: [-4.2, 0.0], rotation: 0 },
two: { position: [-3, -0.5], rotation: 0 },
three: { position: [-2, 0], rotation: 0 },
four: { position: [-2.5, -1.5], rotation: 0.4 },
five: { position: [-2.5, 1.5], rotation: -0.4 },
},
goalkeeper_pose: { position: [-4.2, 0.0], rotation: 0.0 },
striker_pose: { position: [-0.6, 0.0], rotation: 0.0 },
// Remaining players fill these support slots in descending player-number order.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are these comments different from the ones in the code?

aggressive_positions: [
{ position: [-1.7, 1.5], rotation: -0.7 },
{ position: [-1.7, -1.5], rotation: 0.7 },
{ position: [-3.0, 0.0], rotation: 0.0 },
],
// On opponent/unknown kickoff, all field players fill these slots in order.
defensive_positions: [
{ position: [-1.8, 0.0], rotation: 0.0 },
{ position: [-2.8, 1.6], rotation: -0.5 },
{ position: [-2.8, -1.6], rotation: 0.5 },
{ position: [-3.2, 0.0], rotation: 0.0 },
],
},
voronoi: {
grid_resolution: 0.2,
Expand Down
Loading