Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New passing and receiving architecture and tuned offense strategy #3200

Open
wants to merge 203 commits into
base: master
Choose a base branch
from

Conversation

nimazareian
Copy link
Contributor

@nimazareian nimazareian commented May 10, 2024

Description

updated_passing2_cut.mp4

This PR includes many changes across our offensive gameplay. To name a few:

  • Updated PassGenerator to sample pass receiver positions more intelligently around where the robots are most likely going to be able to receive passes from as opposed to sampling the entire field. As a result, the pass generator does not use zones or pass evaluations anymore, however, it still does use the GradientDescentOptimizer to optimize the sampled passes.
  • New ReceiverPositionGenerator used for finding the best receiving positions. This allows the offensive robots that do not have the ball to constantly look for better receiving positions, independent of the pass generator. Previously the receiving robots were tied to the best passes found by the pass generator which limited the receiving robots ability to go around the field. This was fundamentally as a result of ratePassFriendlyCapability only giving high scores to receiving positions around the robot's current position.
  • Many cost functions were rewritten, tuned, or newly added. As part of this I tried to expand the cost functions that we could visualize in Thunderscope's pass cost layer. Furthermore, the cost functions should be much more tunable on the go since many of the core constants were added to the dynamic parameters.
  • New visualization features were added for the PassGenerator and ReceiverPositionGenerator that could be enabled through the dynamic parameters. For simplicity, these visualizations are shown in the debug shapes layer.
  • Shoot or pass play FSM was updated to abort poor passes better while also aborting good passes less. Some notable changes include aborting passes that were previously highly rated but are now poorly rated (e.g. an enemy has moved in the way). Receiver FSM was also updated to make one touch kicks more likely to happen. Keep away + Offense play were also updated to not have the robot with the ball constantly look away from enemies even when there was no thread nearby.

Testing Done

Added new unit tests for pass generator, cost functions, and receiver position generator. Ran AI vs AI and tuned many of the constants manually.

Resolved Issues

Resolves #3191
Resolves #3192
Resolves #2577
Resolves #2538
Resolves #2136
Resolves #1988
Could affect #2930

Length Justification and Key Files to Review

This PR includes the changes from #3198, so please ignore those files, or comment on that PR for specific Thunderscope changes that are unrelated to this PR. Some of the changes are also taken from #2953 (e.g. removing corner kick play. This was done to make the integration of the pass generator simpler.)

Core files to review:

  • receiver_position_generator.hpp
  • pass_generator.h/cpp
  • cost_functions.h/cpp
  • shoot_or_pass_play_fsm.cpp

It is the reviewers responsibility to also make sure every item here has been covered

  • Function & Class comments: All function definitions (usually in the .h file) should have a javadoc style comment at the start of them. For examples, see the functions defined in thunderbots/software/geom. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.
  • Remove all commented out code
  • Remove extra print statements: for example, those just used for testing
  • Resolve all TODO's: All TODO (or similar) statements should either be completed or associated with a github issue

Comment on lines 107 to 109
// Linearly scale score to [min_pass_shoot_score, 1.0] to stop this cost function
// from returning a very low score, causing the other cost functions to be ignored.
return open_angle_to_goal_score;
Copy link
Contributor

@itsarune itsarune Jun 20, 2024

Choose a reason for hiding this comment

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

shouldn't this return a minimum of min_pass_shoot_score (according to the comment)?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh you've done it in a different function. you should delete that comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call! Updated.

Copy link
Contributor

@itsarune itsarune left a comment

Choose a reason for hiding this comment

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

could you also update passing_sim_test.py? It seems like it ends very early and probably requires fiddling around with some of the validations. (mayhaps use an OrValidation to do an if-then type of thing)

@nimazareian
Copy link
Contributor Author

could you also update passing_sim_test.py? It seems like it ends very early and probably requires fiddling around with some of the validations. (mayhaps use an OrValidation to do an if-then type of thing)

@itsarune I digged further into this, and interestingly enough I found a bug which results in us ignoring the second validation, when we have a sequence of eventual validations. This bug is caused by us removing the first validation from the list of validations (after it is PASSING) while we're iterating over it. Here's the original code:

for validation in validation_sequence:
# Add to validation_proto_set and get status
status = create_validation_proto_helper(
eventually_validation_proto_set, validation
)
# If the current validation is failing, we don't care about
# the next one. Keep evaluating until this one passes.
if status == ValidationStatus.FAILING:
break
# If the validation has passed, remove it from the set.
if status == ValidationStatus.PASSING:
validation_sequence.remove(validation)
continue

Most of the tests run longer now since we actually wait for the receiver to receive the pass, however, some still end really fast. In those cases, your best off watching the replay since the test was likely very short (e.g. the robot taking a very short pass).

williamckha
williamckha previously approved these changes Jun 28, 2024
Copy link
Contributor

@williamckha williamckha left a comment

Choose a reason for hiding this comment

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


// TODO (#2570): try to make it as big as possible when tuning
// The maximum deflection angle that we will attempt a one-touch kick towards the
// enemy goal with
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// enemy goal with
// enemy goal

// of a soft limit.
required double ideal_max_rotation_to_shoot_degrees = 5 [
default = 60.0,
// A value multiplied by the duration that our enemy robot model predicts it will
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// A value multiplied by the duration that our enemy robot model predicts it will
// A value multiplied by the duration that our enemy robot model predicts will

Copy link
Contributor

Choose a reason for hiding this comment

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

out of curiosity, why'd you need to change some of these parametrized tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A limitation of the simulated pytests is that the plays/tactics can only be set at the start of the test. But for passing/receiving to work properly, we need to run the pass generator at each tick if the test. So what was happening with some of these tests was that the passer couldn't find a pass given the starting conditions of the test.

I also tried dividing the test into two parts (i.e. 2 run_test calls), first moving the receivers to the best receiving positions, then finding the best pass. But I ran into an issue which was that there's no easy way of getting the state of the World after the test starts. This was by part caused by the issue I was talking about last week about Python queues not overwriting old values. since I was trying to have a buffer of size one registered to receive the latest World, but that wasn't working.

Copy link
Contributor

@itsarune itsarune left a comment

Choose a reason for hiding this comment

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

no major concerns from me, pending minor nits

@itsarune
Copy link
Contributor

nice catch with the bug!

@@ -45,8 +44,7 @@ void AttackerTactic::updatePrimitive(const TacticUpdate& tactic_update, bool res
if (reset_fsm)
{
fsm_map[tactic_update.robot.id()] = std::make_unique<FSM<AttackerFSM>>(
DribbleFSM(ai_config.dribble_tactic_config()),
AttackerFSM(ai_config.attacker_tactic_config()));
DribbleFSM(ai_config.dribble_tactic_config()), AttackerFSM(ai_config));
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the original code writers section below, but why don't you also need to update the DribbleFSM control params as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code here is actually not the ControlParams, rather, it's just creating the FSM objects. In this case we're creating an AttackerFSM and from my understanding of how we've setup boost SML (the FSM library we use), we need to pass in the sub FSMs that the FSM uses + the FSM itself as arguments to the constructor of the FSM<XXXXFSM>.

So in this case, AttackerFSM has DribbleFSM as a sub-FSM, so to generate FSM<AttackerFSM>, we need to pass in both the DribbleFSM and AttackerFSM objects.

Regarding the change I made, in this PR I've update AttackerFSM to take in TbotsProto::AiConfig (part of our dynamic parameters), rather than just taking the TbotsProto::AttackerTacticConfig. You can have a look at attacker FSM and see that I needed some of the other constants from AI config, so I had to make this change.

Copy link
Contributor

@Andrewyx Andrewyx left a comment

Choose a reason for hiding this comment

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

Took me two days to even go through all the code. Looks like most comments are addressed. The only part that stands out to me is the free_kick_play changes, but you have already noted that it will be overhauled in #2953 . Additionally, I have made a comment regarding the attacker_tactic update function. I personally had some bugs with FSM update functions, but if this is not the case for you, then LGTM!

@nimazareian
Copy link
Contributor Author

I personally had some bugs with FSM update functions, but if this is not the case for you, then LGTM!

@Andrewyx Did you have bugs running code from this branch, or are you referring to running into FSM problems elsewhere?

@Andrewyx
Copy link
Contributor

Andrewyx commented Jul 5, 2024

I personally had some bugs with FSM update functions, but if this is not the case for you, then LGTM!

@Andrewyx Did you have bugs running code from this branch, or are you referring to running into FSM problems elsewhere?

FSM problems elsewhere, I have not tested it with your branch directly since I am not fully familiar with the intended behaviour of the new strategy. But for my ticket where I was adding DribbleFSM to the Creases, I realized that I needed to update the DribbleFSM control params within the updatePrimitive function for it to work effectively, otherwise, I had a bug where the Crease would endlessly chase the ball.

@nimazareian
Copy link
Contributor Author

But for my ticket where I was adding DribbleFSM to the Creases, I realized that I needed to update the DribbleFSM control params within the updatePrimitive function for it to work effectively, otherwise, I had a bug where the Crease would endlessly chase the ball.

That's interesting. So are you saying that you added DribbleFSM as a sub-FSM to the CreaseDefenderFSM, and as part of CreaseDefenderTactic::updatePrimitive you had to call process_event with the DribbleFSM::update as well for it to work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants