Skip to content

Referee integration#111

Open
isaac0804 wants to merge 40 commits intomainfrom
referee_integration
Open

Referee integration#111
isaac0804 wants to merge 40 commits intomainfrom
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>
Copilot AI review requested due to automatic review settings April 14, 2026 22:57
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 46 out of 48 changed files in this pull request and generated no new comments.

isaac0804 and others added 10 commits April 15, 2026 00:09
- Replace from_field_bounds with from_field_dims in RefereeGeometry so
  goal/defense dimensions scale with the actual field, not hardcoded to
  STANDARD_FIELD_DIMS
- Fix Vector3D→Vector2D conversions in actions.py for fpp controller
  compatibility (DirectFreeOursStep, BallPlacementOursStep)
- Rename get_min_bounding_zone→get_min_bounding_req in point_cycle_strategy
  and wandering_strategy to match abstract base class
- Update test fixtures to use new Field(field_dims=...) constructor and
  correct BALL_KEEP_OUT_DISTANCE (0.8 m)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_project_outside_circle was using a hardcoded (1, 0) fallback when a
robot sits exactly on the obstruction center (dist == 0), pushing it to
positive-x regardless of which half the team defends. Now _clear_to_legal_
positions passes an own-half-aware fallback so coincident robots are always
cleared toward their correct side.

Also fix test_all_robots_placed_on_own_half_* to track final target per
robot (dict keyed by robot_id) rather than a flat list, so the count
assertion is robust against the clearing pass re-issuing a move command.

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

Add CustomReferee.override_geometry() to replace geometry on both the
referee and its internal state machine. StrategyRunner now calls this
immediately after resolving field_bounds, so the custom referee always
uses the actual full_field_dims + field_bounds rather than the standard-
field values baked into the YAML profile.

The YAML geometry block is preserved as a fallback for standalone
CustomReferee use outside of StrategyRunner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
from_field_dims(STANDARD_FIELD_DIMS) is the direct equivalent and makes
the standard-field case no more special than any other. Update the test
fixture accordingly.

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

Aligns with FieldDimensions.half_defense_area_depth. Updated geometry.py,
profile_loader.py, gui.py, and both YAML profiles.

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

Three issues addressed:

1. Remove CLEARANCE_FALLBACK_DIRECTION from referee_constants — it was the
   default parameter of _project_outside_circle but no caller used the
   default; the fallback is now always passed explicitly.

2. _clear_to_legal_positions now accepts an optional intended_targets dict.
   When provided, clearance starts from the intended formation target rather
   than robot.p, so the clearing pass refines rather than discards the
   formation intent. PrepareKickoffTheirsStep now passes intended_targets
   instead of pre-issuing move commands and relying on overwrite.

3. Use own-half direction as the coincidence fallback for both ball and
   designated-position clearance — consistent with the invariant that
   friendly robots belong on their own half during all restart states.

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

During PREPARE_KICKOFF_* and PREPARE_PENALTY_*, robots are actively moving
to their formation positions. The keep-out rule was firing after 30 frames
of encroachment and resetting the sequence to STOP → DIRECT_FREE, causing
a violation loop.

These states are now excluded from _STOPPAGE_COMMANDS — the state machine
already gates progression via _kicker_in_centre_circle and
_penalty_kicker_ready, so keep-out violations here are spurious. The rule
remains active for DIRECT_FREE_* where defending robots must genuinely stay
clear while the free kick is set up.

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

If a robot is currently inside a keep-out zone, sending it toward a
distant formation target makes it traverse the exclusion zone for multiple
frames, triggering the keep-out violation counter. Instead, when a robot
is encroaching, project it out from its current position immediately.
Only use the intended formation target when the robot is already clear of
all exclusion zones.

This prevents keep-out rule violations during PREPARE_KICKOFF without
modifying the referee state machine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The function previously used a hardcoded 0.5 m (centre-circle radius),
meaning formation positions could be placed at exactly 0.5 m from the
ball. _clear_to_legal_positions would then push them out to 0.8 m, but
the robot still had to travel through the keep-out zone to reach its
target, accumulating violation frames.

Using BALL_KEEP_OUT_DISTANCE (0.8 m) ensures formation positions are
always placed at or beyond our enforced clearance distance, so robots
never need to traverse the keep-out zone to reach their assigned position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 15, 2026 00:11
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 4 comments.

Comment thread docs/custom_referee.md Outdated
Comment thread utama_core/entities/data/referee.py Outdated
Comment thread utama_core/entities/game/game_frame.py Outdated
Comment thread demo_custom_referee.py
- Update docs/custom_referee.md: replace stale from_standard_div_b() /
  from_field_bounds() references with the actual from_field_dims() API;
  fix half_defense_length → half_defense_depth in the dataclass snippet
- Fix misleading __eq__ comment in RefereeData: clarify that time_sent
  and time_received are excluded, not all timestamps
- Fix GameFrame.is_ball_in_goal: accept a Field parameter (self.field
  does not exist on GameFrame); add ball None guard; simplify with abs()
- Fix demo_custom_referee.py: replace non-existent from_standard_div_b()
  with from_field_dims(STANDARD_FIELD_DIMS); fix half_defense_length →
  half_defense_depth crash

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 3 comments.

Comment thread utama_core/strategy/referee/actions.py
Comment thread demo_referee_gui_rsim.py Outdated
Comment thread utama_core/tests/strategy_runner/test_runner_misconfig.py Outdated
- Fix PreparePenaltyTheirsStep: behind_line_x used +sign which placed
  non-keeper robots toward our goal instead of toward the centre line;
  change to -sign so robots are correctly positioned behind the penalty
  mark facing midfield (mirrors the existing PreparePenaltyOursStep logic)
- Rename misleading test: test_resolve_referee_system_defaults_to_none_even_when_custom_referee_is_provided
  → test_resolve_referee_system_rejects_custom_referee_without_explicit_system
- Fix demo_referee_gui_rsim.py docstring: run command pointed to
  utama_core/tests/referee/ path instead of the actual root-level file

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 2 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>
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.

2 participants