Skip to content

Referee integration#111

Open
isaac0804 wants to merge 48 commits into
mainfrom
referee_integration
Open

Referee integration#111
isaac0804 wants to merge 48 commits into
mainfrom
referee_integration

Conversation

@isaac0804
Copy link
Copy Markdown
Contributor

Referee data pipeline

  • Receive and parse complete SSL referee protobuf messages into RefereeData
  • RefereeRefiner records referee state changes
  • RefereeData converted from NamedTuple to @dataclass to support a custom eq that ignores timestamps and game events (preventing spurious re-records)

Custom referee & state machine

  • GameStateMachine tracks referee commands, stages, and transitions
  • 5 configurable auto-advance transitions (HALT→STOP, STOP→kickoff, kickoff→NORMAL, direct free→NORMAL, ball placement→next command)
  • 2-second sustained-readiness delay on game-starting advances for safety
  • All auto-advances disabled by default in physical environment profiles

Compliant action nodes (actions.py)

  • BallPlacementTheirsStep: active radial clearance — robots push out of the 0.55 m keep-out zone instead of freezing
  • PrepareKickoffTheirsStep: robots clamped to own half before kickoff
  • BallPlacementOursStep: two-phase placer motion (drive to ball → carry to target) with non-placer radial clearance and completion detection
  • PreparePenaltyOursStep / PreparePenaltyTheirsStep: non-kicker/non-keeper robots placed at touch-line boundary (SSL rule approximation)

Operator GUI (gui.py)

  • Browser-based panel served over HTTP + SSE at ~30 Hz
  • Displays live referee state, game score, stage, and command history
  • Config panel exposes all 5 auto-advance flags as toggleable ON/OFF pills
  • Integrates with the custom referee via attach_gui(referee, profile, port)

Profiles

  • arcade.yaml and strict_ai.yaml with explicit auto_advance blocks
  • AutoAdvanceConfig dataclass parsed from YAML game.auto_advance section

isaac0804 and others added 7 commits November 26, 2025 15:50
…st bugs

- Convert RefereeData from NamedTuple to @DataClass(eq=False) so the
  custom __eq__ is respected (NamedTuple.__eq__ cannot be overridden —
  tuple equality always wins)
- Use TYPE_CHECKING guard for TeamInfo import to avoid circular import
  (game/__init__ → Game → GameFrame → RefereeData → TeamInfo → game/__init__)
- __eq__ compares TeamInfo by .score and .goalkeeper (the mutable game-state
  fields) since TeamInfo has no structural __eq__ of its own
- Add __hash__ consistent with the subset of fields used in __eq__
- RefereeRefiner.add_new_referee_data: replace tuple slicing [1:] with ==
  (now correctly uses the custom __eq__)
- test_referee_unit.py: fix GameHistory() → GameHistory(10) (max_history
  is a required positional argument)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adopted main's SideRuntime refactor (my/opp sides) in strategy_runner.py
while preserving referee integration. Fixed imports to new data_processing
module paths. Resolved standard_ssl.py conflicts keeping RefereeData usage
from our branch with main's improved assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

utama_core/rsoccer_simulator/src/ssl/envs/standard_ssl.py:231

  • _frame_to_observations() docstring says it returns a 4-tuple including referee_data, and StrategyRunner now branches on len(obs) == 4, but the function still returns only a 3-tuple. This means embedded referee data is never provided to StrategyRunner and RSIM mode will always see referee_data=None unless externally injected. Update the return value (and type annotation) to actually include the current RefereeData from the embedded referee state machine, or revert the docstring/StrategyRunner logic if referee data is not available here.
    def _frame_to_observations(
        self,
    ) -> Tuple[RawVisionData, RobotResponse, RobotResponse]:
        """Return observation data that aligns with grSim. There may be Gaussian noise and vanishing added.

        Returns (vision_observation, yellow_robot_feedback, blue_robot_feedback, referee_data)
        vision_observation: closely aligned to SSLVision that returns a FramData object
        yellow_robots_info: feedback from individual yellow robots that returns a List[RobotInfo]
        blue_robots_info: feedback from individual blue robots that returns a List[RobotInfo]
        referee_data: current referee state from embedded referee state machine
        """

        if self.latest_observation[0] == self.steps:
            return self.latest_observation[1]

        # Ball observation shared by all robots
        if self._vanishing():
            ball_obs = []
        else:
            SSLStandardEnv._add_gaussian_noise_ball(self.frame.ball, self.gaussian_noise)
            ball_obs = [RawBallData(self.frame.ball.x, -self.frame.ball.y, self.frame.ball.z, 1.0)]

        # Robots observation (Blue + Yellow)
        blue_obs = []
        blue_robots_info = []
        for i in range(len(self.frame.robots_blue)):
            if self._vanishing():
                continue

            robot = self.frame.robots_blue[i]
            robot_pos, robot_info = self._get_robot_observation(robot)
            blue_obs.append(robot_pos)
            blue_robots_info.append(robot_info)

        yellow_obs = []
        yellow_robots_info = []
        for i in range(len(self.frame.robots_yellow)):
            if self._vanishing():
                continue

            robot = self.frame.robots_yellow[i]
            robot_pos, robot_info = self._get_robot_observation(robot)
            yellow_obs.append(robot_pos)
            yellow_robots_info.append(robot_info)

        # Return the complete shared observation
        # note that ball_obs stored in list to standardise with SSLVision
        # As there is sometimes multiple possible positions for the ball

        # Get referee data
        # current_time = self.time_step * self.steps

        # Camera id as 0, only one camera for RSim
        result = (
            RawVisionData(self.time_step * self.steps, yellow_obs, blue_obs, ball_obs, 0),
            yellow_robots_info,
            blue_robots_info,
        )
        self.latest_observation = (self.steps, result)
        return result

Comment thread utama_core/entities/data/referee.py Outdated
Comment thread utama_core/strategy/referee/actions.py Outdated
Comment thread utama_core/rsoccer_simulator/src/ssl/referee_state_machine.py Outdated
@isaac0804
Copy link
Copy Markdown
Contributor Author

Referee integration

  • Added full referee data plumbing into the game pipeline: complete SSL referee protobuf messages are parsed into RefereeData, recorded by RefereeRefiner, and exposed to strategies through the game state
  • RefereeData was converted to a @dataclass with custom equality so timestamps and non-behavioral fields do not cause noisy re-recording

Custom referee

  • Added an in-process CustomReferee with a GameStateMachine that works across rsim, grsim, and real
  • Added explicit referee source selection in StrategyRunner: none, official, or custom
  • Ported built-in profiles and renamed them to:
    • simulation for auto-progressing simulator / RL workflows
    • human for operator-controlled physical / testing workflows
  • Added configurable auto-advance behavior for queued restarts, including kickoff, direct free, penalty, and ball placement

Referee override behavior

  • Added the referee override tree and action nodes so strategies comply with referee commands like HALT, STOP, kickoff, penalty, direct free, and ball placement
  • Fixed key legality behaviors:
    • active clearance during STOP and opponent restarts
    • two-phase ball placement for our team
    • teammate clearance during our ball placement
    • clearance from both ball and designated target during opponent ball placement
    • kickoff and penalty positioning now scale correctly with field size and side assignment

Operator UX

  • Added a browser-based custom-referee GUI over HTTP/SSE for command control and state inspection
  • Live terminal status now shows richer referee context, including command, next command, referee source, and custom-referee profile
  • Custom-referee status messages are now preserved and shown to the operator so stoppages include a visible reason

Geometry / configurability

  • Removed hardcoded referee-action geometry where practical and moved shared distances into constants or live field-derived helpers
  • Kickoff and penalty formations now work on variable field sizes instead of assuming the standard field

Tests

  • Added focused unit/integration coverage for:
    • referee source selection in StrategyRunner
    • custom-referee profile loading and restart progression
    • referee action-node legality and field-scaling behavior
    • status-message propagation through the custom-referee path

isaac0804 and others added 6 commits April 3, 2026 11:43
- Add rsim integration tests for ball placement, direct free kick (ours
  and theirs), and kickoff positioning in test_referee_rsim.py
- Add 15 unit tests covering PrepareKickoffTheirsStep, DirectFreeOursStep,
  and DirectFreeTheirsStep action nodes in test_referee_unit.py
- Switch demo script control_scheme from dwa to pid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Field._UNDERSCORE_CONSTANTS with their public ClassProperty
equivalents in math_utils.py and geometry.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ument future work

- conftest.py: default --headless to True so tests don't open rsim window
- strategy_runner.py: skip ball teleport on STOP when next_command is
  BALL_PLACEMENT so the robot must physically carry the ball
- test_referee_rsim.py: replace broken full-sequence test with a comment
  documenting why it is deferred (ball placement carry mechanics not yet
  reliable in rsim)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Penalty and ball placement buttons are not in use; removing them keeps
the operator panel focused on the commands we actually use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Ball placement phase before free kick per SSL rulebook
- BallPlacementOursStep carry mechanics investigation (two-robot kissing)
- GUI suggested next action to reduce operator cognitive load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 50 changed files in this pull request and generated 3 comments.

Comment thread utama_core/rsoccer_simulator/src/ssl/referee_state_machine.py Outdated
Comment thread utama_core/rsoccer_simulator/src/ssl/envs/standard_ssl.py Outdated
Comment thread utama_core/entities/data/referee.py
- Delete dead RefereeStateMachine (never wired up, duplicated GameStateMachine
  with a broken _replace() call on a mutable class)
- Remove dead len(obs)==4 branch in _run_step; RSim always reads from ref_buffer
- Snapshot TeamInfo via copy.copy() in _generate_referee_data() to prevent
  score mutations retroactively corrupting stored RefereeRefiner records
- Update docs and stale docstrings accordingly
- Document TeamInfo frozen dataclass refactor as deferred follow-up work

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 49 changed files in this pull request and generated 2 comments.

Comment thread conftest.py
Comment thread utama_core/data_processing/refiners/referee.py Outdated
…a record

Properties like last_command, blue_team, status_message, etc. were all
reading from _referee_records[-1], which is not updated when the new
RefereeData compares equal (e.g. only status_message or time_sent changes).
This could leave live properties stale between meaningful state changes.

Add _latest_referee_data, updated unconditionally on every refine() call
(alongside the existing _latest_stage_time_left), and point all live
properties at it. _referee_records still grows only on meaningful state
changes, preserving its role as a deduplicated history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 49 changed files in this pull request and generated 6 comments.

Comment thread docs/custom_referee.md Outdated
Comment thread docs/custom_referee_gui.md Outdated
Comment thread docs/custom_referee_gui.md Outdated
Comment thread docs/referee_integration.md Outdated
Comment thread docs/custom_referee.md Outdated
Comment thread utama_core/custom_referee/profiles/profile_loader.py
isaac0804 and others added 3 commits April 16, 2026 18:15
The field was renamed in code (RefereeGeometry, YAML profiles) but three
doc files still used the old name in YAML examples and the field table.
Updated docs/custom_referee.md, docs/referee_integration.md, and
docs/custom_referee_gui.md to match the actual API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only simulation.yaml and human.yaml exist under profiles/.
exhibition.yaml was listed in the directory tree but was never created.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 10, 2026 14:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 49 changed files in this pull request and generated 3 comments.

Comment thread utama_core/run/strategy_runner.py
Comment thread utama_core/custom_referee/state_machine.py Outdated
Comment thread demo_custom_referee.py
energy-in-joles and others added 2 commits May 10, 2026 16:50
GameStateMachine.stage_start_time is now seeded lazily from the first
current_time passed to step(), so the timebase matches the caller's
clock. StrategyRunner now passes game_frame.ts instead of time.time(),
making referee cooldowns and stage durations operate in sim-time for
RSIM (which starts near 0) rather than wall-clock time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 15, 2026 09:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 50 changed files in this pull request and generated 2 comments.

Comment thread main.py Outdated
Comment thread utama_core/strategy/referee/tree.py
isaac0804 and others added 3 commits May 15, 2026 10:37
GameStateMachine now sets bot_substitution_allowed=False by default
and flips it to True only when the stage transitions to a halftime/break
stage (where SSL rules permit substitution). PositionRefiner reads this
flag from game_frame.referee and skips adding None vanishing entries for
robots that disappear during an open substitution window, preventing the
Kalman filter from ghost-predicting their positions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…chers

- Move CustomReferee construction inside main() to eliminate import-time side effects
- Store _is_opp_strat on AbstractBehaviour so setup_() can propagate it to inner nodes
- Fix dispatcher setup_() calls in tree.py to forward is_opp_strat instead of hardcoding False

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 51 changed files in this pull request and generated 3 comments.

Comment thread utama_core/custom_referee/rules/defense_area_rule.py Outdated
Comment thread docs/custom_referee.md Outdated
Comment thread docs/custom_referee_design_decisions.md Outdated
Previously the rule only checked the "friendly" team's defense area, missing
violations in the opponent's box. Now derives yellow/blue sides from
my_team_is_right XOR my_team_is_yellow and checks both areas in one pass.

Also fix two stale doc issues:
- Remove STOP from KeepOutRule active-commands table (it was always excluded)
- Update design-decisions section 4 to reflect that reset() is already called
  on command transitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 51 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

conftest.py:23

  • With --headless using action='store_true' and default=True, the --headless flag itself becomes a no-op (it can only ever set True). Since --no-headless exists to flip it back, consider either removing --headless entirely or switching to a single --headless/--headful style option to make the CLI intent clearer.
    parser.addoption(
        "--headless",
        action="store_true",
        default=True,
        help="Don't display any graphics (runs faster)",
    )
    parser.addoption(
        "--no-headless",
        action="store_false",
        dest="headless",
        help="Run with graphics",
    )

Comment thread utama_core/run/strategy_runner.py
…een packets

When using referee_system='official', the GC sends packets at ~10 Hz while
the main loop runs at 60 Hz. popleft() returned None on most ticks, causing
GameFrame.referee to drop out intermittently and disabling the referee layer.

Now the last successfully received RefereeData is cached and reused on ticks
where the buffer is empty. The RSIM/custom path is unaffected (data is always
produced on the same tick it is consumed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 51 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

conftest.py:22

  • --headless is defined with action='store_true' but also has default=True, so the flag itself can never change behavior (only --no-headless can). If the intent is to default to headless while still allowing an explicit opt-in, consider dropping --headless entirely (keeping only --no-headless), or switch to a single boolean option pair so both flags are meaningful.
    parser.addoption(
        "--headless",
        action="store_true",
        default=True,
        help="Don't display any graphics (runs faster)",
    )
    parser.addoption(
        "--no-headless",
        action="store_false",
        dest="headless",
        help="Run with graphics",

Comment on lines +193 to +203
def setup_(self):
self._ours.setup(is_opp_strat=self._is_opp_strat)
self._theirs.setup(is_opp_strat=self._is_opp_strat)

def update(self) -> py_trees.common.Status:
if self._is_yellow_command == self.blackboard.game.my_team_is_yellow:
self._ours.blackboard = self.blackboard
return self._ours.update()
else:
self._theirs.blackboard = self.blackboard
return self._theirs.update()
Comment on lines +415 to +424
behind_idx = 0
behind_y_step = PENALTY_LINE_Y_STEP_RATIO * _field_half_width(game)
for robot_id in robot_ids:
if robot_id == kicker_id:
self.blackboard.cmd_map[robot_id] = move(game, motion_controller, robot_id, penalty_mark, goal_oren)
else:
# Place behind the line, spread in y
offset = (behind_idx - (len(robot_ids) - 1) / 2.0) * behind_y_step
pos = Vector2D(behind_line_x, offset)
self.blackboard.cmd_map[robot_id] = move(game, motion_controller, robot_id, pos, 0.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants