Real mapping - #123
Real mapping#123
Conversation
…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>
- 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>
- 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>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Fix CustomReferee.step() to reset rules only on actual command transition, not when the state machine ignores the violation due to the cooldown - Remove dead STOP branch and unused my_team_is_yellow param from KeepOutRule - Add last_status_message and last_next_command properties to RefereeRefiner; remove direct _referee_records[-1] access from StrategyRunner - Fix designated_position type annotation: Tuple[float] → Tuple[float, float] - Import BALL_KEEP_OUT_DISTANCE from referee_constants in test file instead of duplicating the literal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RefereeData.__eq__ excludes stage_time_left to avoid spurious re-records, so the deduplication in add_new_referee_data() was discarding every frame's updated countdown. The stage_time_left property was reading from the stale cached record, causing the terminal display to freeze (e.g. always showing 4:59). Fix: track _latest_stage_time_left independently in refine(), updated on every call before the dedup check, and return it from the stage_time_left property. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation loop Action nodes were parking robots at 0.55 m from the ball while the KeepOutRule fires at < 0.5 m. A small ball nudge during restart would put defending robots inside the rule threshold, triggering another STOP and looping indefinitely. Increasing the action node clearance to 0.8 m gives enough buffer to absorb positioning inaccuracy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts in main.py, field.py, game_frame.py, math_utils.py, strategy_runner.py, and test_runner_misconfig.py. Updated referee branch code to use the new FieldDimensions-based Field API (removed ClassProperty constants, added field_dims param to Field.__init__), fixed Vector3D→Vector2D issues in actions.py for fpp controller compatibility, and updated test assertions to match the increased BALL_KEEP_OUT_DISTANCE (0.55→0.8). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
utama_core/team_controller/src/controllers/real/real_robot_controller.py:248
_generate_command_buffer()now silently masksrobot_idwith& 0xFF. If a caller ever passes an out-of-range (e.g. negative or >255) ID, it will wrap and send a command to the wrong robot instead of failing fast. Add an explicit range check and use the validatedrobot_idbyte directly.
def _generate_command_buffer(self, robot_id: int, c_command: RobotCommand) -> bytes:
"""Generates the command buffer to be sent to the robot."""
# endianness: little endian
packet = bytearray(
[
robot_id & 0xFF, # Robot ID
c_command.local_forward_vel & 0xFF, # Forward velocity low byte
utama_core/team_controller/src/controllers/real/real_robot_controller.py:248
_generate_command_buffer()now silently masksrobot_idwith& 0xFF. If a caller ever passes an out-of-range (e.g. negative or >255) ID, it will wrap and send a command to the wrong robot instead of failing fast. Add an explicit range check and use the validatedrobot_idbyte directly.
def _generate_command_buffer(self, robot_id: int, c_command: RobotCommand) -> bytes:
"""Generates the command buffer to be sent to the robot."""
# endianness: little endian
packet = bytearray(
[
robot_id & 0xFF, # Robot ID
c_command.local_forward_vel & 0xFF, # Forward velocity low byte
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Add yellow_trusted_ir_robots / blue_trusted_ir_robots params to StrategyRunner (colour-keyed, matching vision_to_cmd_mapping convention). Robots not in the trusted set have has_ball inferred from vision proximity (~0.13 m) instead of the IR sensor — handles broken sensors without disabling IR globally. Default None trusts all sensors (backwards-compatible). Remove the args once sensors are stable hardware-wide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents the dribbler motor from overheating by capping continuous use at 30 s. The bucket fills 1 step/step while dribbling and drains at the same rate while off — so 20 s on + 10 s off leaves 10 s on the clock, matching real thermal recovery behaviour. Dribbler is forced off and a warning is emitted when the bucket is full; it resumes automatically once drained. Sim controllers are unaffected. Limit defined by DRIBBLER_MAX_ON_STEPS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…conds Uses time.monotonic() so the 30s budget is real elapsed time regardless of control-loop frequency. Replaces DRIBBLER_MAX_ON_STEPS with DRIBBLER_MAX_ON_SECONDS = 30.0 and _dribbler_steps with _dribbler_seconds + _dribbler_last_tick per robot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Warn only once per limit event (not every tick) using _dribbler_limit_warned - Add hysteresis: once throttled, dribbler stays off until bucket drains to DRIBBLER_RESUME_SECONDS (15s = 50%) — prevents 1s-on/1s-off oscillation at the limit boundary - Bucket continues draining even while strategy requests dribble (throttled) - telop_gui: add heat: XX% label per robot, colour-coded muted→amber→red at 0-50% / 50-80% / 80-100% bucket fill Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fred-huang122
left a comment
There was a problem hiding this comment.
Review notes from the branch diff against main, focused on real-mode ID mapping correctness and typing.
…tests
Brings in the single-robot hardware test suite:
- demo_dribbler_test.py — fetch ball, carry forward with dribbler
- demo_dribbler_test2.py — dribbler sequence (forward/left/right/back)
- demo_kicker_test.py — fetch, align, kick, repeat
- demo_one_robot_placement.py — 3x3 grid waypoint traversal
- DribblerTestStrategy — single-robot dribble carry
- DribblerSequenceStrategy — multi-leg dribble sequence
- KickerTestStrategy — fetch/align/kick loop
- RobotPlacementStep — updated to 3x3 snake-pattern grid (was simple oscillation)
demo_kicker_test and demo_one_robot_placement updated from the old
robot_id_map={} API to yellow_vision_to_cmd_mapping={} (real_mapping API).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. robot_info.py: untrusted robots now always get vision-proximity inference each frame, even when no RobotResponse arrives (dropped packet). Previously has_ball would stay frozen at the prior value. 2. strategy_runner.py: vision_to_cmd_mapping validation now checks that mapping keys cover all expected vision IDs (0..exp_n-1), not just the count. A mapping with the right length but wrong keys would previously pass and silently command the wrong physical robot. 3. real_robot_controller.py: annotate _dribbler_limit_warned and _dribbler_throttled as set[int] instead of bare set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fred-huang122
left a comment
There was a problem hiding this comment.
Follow-up review on latest PR head f2915f5, focused on the new fixes and migrated demo strategy commits.
- strategy_runner: replace contiguous-ID assumption (set(range(n))) with
a count check at init; add _validate_mapping_covers_game_frame() called
after _load_game() to verify keys match real observed vision IDs —
fixes rejection of valid non-contiguous deployments like {5:0, 6:1, 7:2}
- one_robot_placement_strategy: remove stale `target = Vector2D(0, 0)`
override that overwrote the waypoint index selection every frame
- tests: update misconfig tests to match new error messages and add
coverage for _validate_mapping_covers_game_frame()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erance - one_robot_placement_strategy: raise _ARRIVE_TOL from 0.1 to 0.15 m so the rsim motion controller reliably triggers waypoint advance (0.1 m caused the robot to hover just outside and freeze) - test_placement_coords: rewrite tests to match 3x3 grid behaviour — verify the robot visits any two distinct grid waypoints instead of checking the old two-point oscillation targets; use same _ARRIVE_TOL as the strategy so test and strategy agree on when a waypoint is reached Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fred-huang122
left a comment
There was a problem hiding this comment.
Left one comment about making the mapping invariant explicit.
| robot_id (int): The ID of the robot. | ||
| command (RobotCommand): A named tuple containing the robot command with keys: 'local_forward_vel', 'local_left_vel', 'angular_vel', 'kick', 'chip', 'dribble'. | ||
| """ | ||
| if robot_id in self._vision_to_cmd_mapping: |
There was a problem hiding this comment.
When vision_to_cmd_mapping is non-empty, missing entries should be treated as a configuration error. The controller cannot know that an unmapped vision ID is safe to send as a command ID; falling back to the raw ID partially restores identity mapping and can command the wrong physical robot. Please validate mapping coverage before real-mode startup, or raise/drop the command when a mapped controller receives an unmapped robot ID.
There was a problem hiding this comment.
@energy-in-joles should this be implemented as I assume when we use mapping that we are mapping all robots no matter if the vision id and firmware id are the same? This is mostly an issue when mapping robots when a single strategy is running
There was a problem hiding this comment.
don't know. don't care.
* feat: referee support in rsim * hotfix: self.name bug * feat: receive complete referee data and use it in strategy runner * feat: referee command nodes and test * fix: make RefereeData.__eq__ actually override tuple equality; fix test 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> * improved some defence interactions + added new tests * Add custom referee integration and profile rename * Fix referee runner startup and keep-out behavior * Scale referee actions with field geometry * Fix referee restart progression and clearance * Fix custom referee demo wiring and status messages * Show referee source details in live status * Add referee behaviour integration tests and unit test coverage - 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> * Use public Field class properties instead of private constants 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> * Default headless=True, skip ball teleport when placement pending, document 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> * Document end-to-end ball placement test as future work Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove advanced controls (penalty & ball placement) from referee GUI 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> * Add three future work items to referee integration docs - 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> * Fix three bugs identified in Copilot review - 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> * Update utama_core/custom_referee/state_machine.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address second round of Copilot review comments - Fix CustomReferee.step() to reset rules only on actual command transition, not when the state machine ignores the violation due to the cooldown - Remove dead STOP branch and unused my_team_is_yellow param from KeepOutRule - Add last_status_message and last_next_command properties to RefereeRefiner; remove direct _referee_records[-1] access from StrategyRunner - Fix designated_position type annotation: Tuple[float] → Tuple[float, float] - Import BALL_KEEP_OUT_DISTANCE from referee_constants in test file instead of duplicating the literal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: cleanup * fix: stage_time_left now updates every frame in RefereeRefiner RefereeData.__eq__ excludes stage_time_left to avoid spurious re-records, so the deduplication in add_new_referee_data() was discarding every frame's updated countdown. The stage_time_left property was reading from the stale cached record, causing the terminal display to freeze (e.g. always showing 4:59). Fix: track _latest_stage_time_left independently in refine(), updated on every call before the dedup check, and return it from the stage_time_left property. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: increase BALL_KEEP_OUT_DISTANCE to 0.8 m to prevent restart violation loop Action nodes were parking robots at 0.55 m from the ball while the KeepOutRule fires at < 0.5 m. A small ball nudge during restart would put defending robots inside the rule threshold, triggering another STOP and looping indefinitely. Increasing the action node clearance to 0.8 m gives enough buffer to absorb positioning inaccuracy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: adapt referee code to FieldDimensions API and arbitrary field sizes - 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> * fix: use own-half fallback direction when robot is coincident with ball _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> * feat: make StrategyRunner the single source of truth for referee geometry 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> * refactor: remove RefereeGeometry.from_standard_div_b() 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> * refactor: rename half_defense_length → half_defense_depth in RefereeGeometry 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> * fix: remove dead CLEARANCE_FALLBACK_DIRECTION and fix _clear_to_legal_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> * fix: exclude PREPARE_KICKOFF/PENALTY from keep-out rule to prevent sequence 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> * Revert "fix: exclude PREPARE_KICKOFF/PENALTY from keep-out rule to prevent sequence resets" This reverts commit d6f4f11. * fix: clear encroaching robots from current position, not formation target 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> * fix: use BALL_KEEP_OUT_DISTANCE in _ensure_outside_center_circle 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> * fix: address Copilot review issues in referee code and docs - 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> * fix: address Copilot review issues in referee actions and tests - 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> * fix: keep RefereeRefiner properties current when deduplication skips 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> * docs: rename half_defense_length → half_defense_depth in all docs 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> * docs: remove non-existent exhibition.yaml from file structure 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> * bug fix for pytest and add referee example in main * fix(defense): make keeper and defender geometry radius-aware Share shadow-geometry helpers between goalkeeping and defender positioning. Use keeper-line and post limits adjusted for robot radius, account for ball and robot radius in defender shadows, and dynamically choose the single defender's coverage side. Update goalkeep and defend_parameter tests for standard and custom field geometry. * Fix graceful SIGINT shutdown under pixi Use setsid for pixi main tasks so Ctrl+C is not delivered twice to the Python child. Update StrategyRunner SIGINT handling so the first interrupt requests graceful shutdown, stops the Rich FPS display, and lets cleanup send stop commands; a later interrupt still force-quits. Add regression coverage for SIGINT behavior and REAL-mode zero-velocity stop commands during shutdown. * Update Pixi environment configuration * Fix FPP no-render path and point cycle API * Fix FPP nullable RSim env typing * fix: use game.ts as referee timebase instead of wall clock 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> * fix(#107): suppress Kalman ghost tracking during robot substitution 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> * Revert "fix(#107): suppress Kalman ghost tracking during robot substitution" This reverts commit 69b96e0. * fix: address Copilot review issues in main.py and referee tree dispatchers - 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> * fix: make DefenseAreaRule color-aware for symmetric enforcement 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> * fix: cache last RefereeData so GameFrame.referee stays populated between 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> * fix: address Copilot review issues in referee tree dispatchers and penalty docstrings Remove redundant child.blackboard assignments in all dispatcher update() methods since each child already attaches to the same global blackboard namespace during setup(). Clarify penalty positioning docstrings to reflect current behaviour and flag them as the place for strategy team tuning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent keep-out violations and defense-area infinite loop - PrepareKickoffOursStep: route support robots through _clear_to_legal_positions with intended_targets so their paths never cross the 0.5m keep-out circle, even when starting on the enemy half after a STOP clearance. Kicker approaches from own-half side offset (ROBOT_RADIUS + 0.03m) instead of exactly (0,0). - StopStep: add own-defense-area eviction so excess defenders are pushed outside the front edge during STOP. Prevents the infinite loop where two robots sit in their own area, STOP ends, the DefenseAreaRule fires immediately, STOP restarts, and no robot ever moves out. New helpers in actions.py: _is_in_own_defense_area(game, x, y) _own_defense_area_exit_x(game) New params on _clear_to_legal_positions: clear_own_defense_area: bool = False max_own_defenders: int = 1 - formations.py: add START_2V2 formation for 2v2 exhibition matches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: seed _prepare_entered_time in set_command() for direct PREPARE_* commands When set_command() is called with a PREPARE_KICKOFF_* or PREPARE_PENALTY_* command while already in STOP/HALT (bypassing the insert-STOP branch), _prepare_entered_time was never updated from -inf, causing the auto-advance prepare-duration check to fire immediately. Seed it (and _stop_entered_time for STOP/HALT) in the fallthrough path so the delay is always respected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: replace referee_system+custom_referee args with typed RefereeSource union Replaces the two coupled StrategyRunner arguments (`referee_system: str` and `custom_referee: CustomReferee`) with a single `referee: RefereeSource` parameter that accepts `None`, `OfficialReferee()`, or a `CustomReferee` instance — a proper discriminated union with no string magic. Also fixes referee clock initialisation: StrategyRunner now calls `CustomReferee.seed_clock()` after `_load_game()` so all internal timers are anchored to the first real `game_frame.ts` rather than a hardcoded 0.0. This makes the referee work correctly when reusing a long-running grsim instance (wall-clock ts) or rsim (sim-time ts). The initial command is automatically chosen by mode — FORCE_START for rsim/grsim, HALT for real — so developers no longer need to call `set_command()` at startup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add center_circle_radius to FieldDimensions Moves the hardcoded 0.5 m center circle radius out of RefereeGeometry and into FieldDimensions, the single source of truth for all field measurements. RefereeGeometry.from_field_dims() now reads it from the field dims instead of hardcoding it. GREAT_EXHIBITION_FIELD_DIMS uses 0.3 m to reflect its smaller scale. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: replace hardcoded geometry fallbacks with STANDARD_FIELD_DIMS references profile_loader.py: default geometry values in _parse_profile() now read from STANDARD_FIELD_DIMS instead of bare magic numbers, so they stay consistent if standard field constants ever change. state_machine.py: the 4.5 m half_length fallback in _penalty_kicker_ready() replaced with STANDARD_FIELD_DIMS.full_field_half_length for the same reason. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: remove dead initial_command and initial_time params from GameStateMachine Both were made obsolete by seed_clock(), which is now the single place where the initial command and clock are applied after the first real game frame is available. The constructor always starts in HALT with stage_start_time=None. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: remove GameTracer from referee_integration branch GameTracer belongs to agentic-harness; its import was causing CI collection errors on this branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Copilot review issues — timebase mismatch and center_circle_radius validation - test_referee_rsim: replace time.time() with game.ts in set_command() calls so timer comparisons use sim-time consistently - field_params: validate center_circle_radius > 0 and <= half_width in __post_init__ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: snapshot TeamInfo per packet to prevent retroactive history mutation RefereeMessageReceiver was passing the same mutable blue_info/yellow_info instances into every RefereeData record, so a score or goalkeeper update would silently mutate all previously recorded referee states. Added TeamInfo.snapshot() and call it in the receiver so each RefereeData owns independent TeamInfo values — consistent with how GameStateMachine already used copy.copy() on its own team instances. Also fixes the Optional[tuple[float]] → Optional[tuple[float, float]] type annotation on RefereeRefiner.designated_position. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * update center circle render for rsim * fix crash when rsim not included for rendering fpp * refactor: make StrategyRunner.full_field_dims the single source of field geometry - Remove geometry block from RefereeProfile, profile_loader.py, and built-in YAML profiles (human, simulation); CustomReferee now initialises _geometry from STANDARD_FIELD_DIMS as a placeholder - StrategyRunner calls override_geometry() at startup so the referee always uses full_field_dims (or its field_bounds projection) - For gRSim/Real, VisionReceiver fires a one-shot on_geometry callback that validates the vision geometry packet against full_field_dims and raises RuntimeError on mismatch - GUI /config endpoint reads referee.geometry live so it reflects the overridden geometry, not stale profile values - Update docs to document the geometry authority chain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add Exhibition Road festival demo (2v2, compact field, human-operator referee) * refactor: use WanderingStrategy instead of external Strategy_2v2 import * feat: scale WanderingStrategy waypoints to field_dims; pass exhibition dims in demo * fix: propagate geometry mismatch error from vision thread to main thread * fix: initialise _stop_event before _setup_vision_and_referee in __init__ * fix: guard draw_line calls when env is None (grsim/real mode) * fix: re-raise parked thread exception after stop_event exits the run loop * feat: redesign GUI to 2x2 quadrant layout with field filling top-left * feat: muted colour palette and grouped ghost-style buttons in GUI * Revert "feat: muted colour palette and grouped ghost-style buttons in GUI" This reverts commit c5caa3459ea2fa9e9da262e47d6729dbbb0eb46b. * Revert "feat: redesign GUI to 2x2 quadrant layout with field filling top-left" This reverts commit d922a78d20ee7a969c37105e33497936986cd5df. * feat: restore 2x2 quadrant layout with original colour scheme * fix: pass waypoints directly to WanderingStep instead of via blackboard * fix: set _waypoints before super().__init__() in WanderingStrategy * config: disable defense area and keep-out rules for exhibition road * feat: add goal_depth to FieldDimensions and propagate to RefereeGeometry and RSim renderer * docs: fix stale referee_gui.py references and outdated StrategyRunner API in docs * fix: add _vision_receiver=None to mock_runner fixture to match StrategyRunner.__init__ * referee should be using FieldDimensions only * tighten docs * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * update docs * implement mapping * remove automatic id increment * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * check for duplication of cmd ids * prevent double saving duplicaterobot responses * fix mapping logic * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix robot response pulling * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix unbound reference * move self.opp * migrate mapping logic to strat runner for get_robot_responses * change erro to warning * fix: address 6 open Copilot inline review comments in real mapping - Fix None mapping TypeError in _validate_vision_to_cmd_mapping for REAL+PVP friendly team - Remap RobotResponse IDs from cmd_id to vision_id in _split_robot_responses_by_team - Fix duplicate cmd ID error message to report specific conflicting ID - Fix stale comment: clarify cmd->vision remap happens in _split_robot_responses_by_team - Fix misleading warning message in _add_robot_command on shared transmitter - Fix serial port log to distinguish opened vs shared connections * fix: follow-up review fixes in real mapping - Fix _step_game real_responses type annotation and fallback from dict to List - Skip friendly mapping length check when mapping was implicitly defaulted to {} - Only pass real_responses kwarg to _step_game in REAL mode * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * add test cases for new checks * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat: per-robot IR sensor trust list with vision-proximity fallback Add yellow_trusted_ir_robots / blue_trusted_ir_robots params to StrategyRunner (colour-keyed, matching vision_to_cmd_mapping convention). Robots not in the trusted set have has_ball inferred from vision proximity (~0.13 m) instead of the IR sensor — handles broken sensors without disabling IR globally. Default None trusts all sensors (backwards-compatible). Remove the args once sensors are stable hardware-wide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: leaky-bucket dribbler thermal limiter in RealRobotController Prevents the dribbler motor from overheating by capping continuous use at 30 s. The bucket fills 1 step/step while dribbling and drains at the same rate while off — so 20 s on + 10 s off leaves 10 s on the clock, matching real thermal recovery behaviour. Dribbler is forced off and a warning is emitted when the bucket is full; it resumes automatically once drained. Sim controllers are unaffected. Limit defined by DRIBBLER_MAX_ON_STEPS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: switch dribbler thermal limiter from steps to wall-clock seconds Uses time.monotonic() so the 30s budget is real elapsed time regardless of control-loop frequency. Replaces DRIBBLER_MAX_ON_STEPS with DRIBBLER_MAX_ON_SECONDS = 30.0 and _dribbler_steps with _dribbler_seconds + _dribbler_last_tick per robot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: warning spam, hysteresis, and heat telemetry for dribbler limiter - Warn only once per limit event (not every tick) using _dribbler_limit_warned - Add hysteresis: once throttled, dribbler stays off until bucket drains to DRIBBLER_RESUME_SECONDS (15s = 50%) — prevents 1s-on/1s-off oscillation at the limit boundary - Bucket continues draining even while strategy requests dribble (throttled) - telop_gui: add heat: XX% label per robot, colour-coded muted→amber→red at 0-50% / 50-80% / 80-100% bucket fill Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: migrate hardware test demos and strategies from robot-hardware-tests Brings in the single-robot hardware test suite: - demo_dribbler_test.py — fetch ball, carry forward with dribbler - demo_dribbler_test2.py — dribbler sequence (forward/left/right/back) - demo_kicker_test.py — fetch, align, kick, repeat - demo_one_robot_placement.py — 3x3 grid waypoint traversal - DribblerTestStrategy — single-robot dribble carry - DribblerSequenceStrategy — multi-leg dribble sequence - KickerTestStrategy — fetch/align/kick loop - RobotPlacementStep — updated to 3x3 snake-pattern grid (was simple oscillation) demo_kicker_test and demo_one_robot_placement updated from the old robot_id_map={} API to yellow_vision_to_cmd_mapping={} (real_mapping API). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Fred's review comments on PR #123 1. robot_info.py: untrusted robots now always get vision-proximity inference each frame, even when no RobotResponse arrives (dropped packet). Previously has_ball would stay frozen at the prior value. 2. strategy_runner.py: vision_to_cmd_mapping validation now checks that mapping keys cover all expected vision IDs (0..exp_n-1), not just the count. A mapping with the right length but wrong keys would previously pass and silently command the wrong physical robot. 3. real_robot_controller.py: annotate _dribbler_limit_warned and _dribbler_throttled as set[int] instead of bare set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address Fred's second-round review comments - strategy_runner: replace contiguous-ID assumption (set(range(n))) with a count check at init; add _validate_mapping_covers_game_frame() called after _load_game() to verify keys match real observed vision IDs — fixes rejection of valid non-contiguous deployments like {5:0, 6:1, 7:2} - one_robot_placement_strategy: remove stale `target = Vector2D(0, 0)` override that overwrote the waypoint index selection every frame - tests: update misconfig tests to match new error messages and add coverage for _validate_mapping_covers_game_frame() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: update placement strategy tests for 3x3 grid and fix arrival tolerance - one_robot_placement_strategy: raise _ARRIVE_TOL from 0.1 to 0.15 m so the rsim motion controller reliably triggers waypoint advance (0.1 m caused the robot to hover just outside and freeze) - test_placement_coords: rewrite tests to match 3x3 grid behaviour — verify the robot visits any two distinct grid waypoints instead of checking the old two-point oscillation targets; use same _ARRIVE_TOL as the strategy so test and strategy agree on when a waypoint is reached Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add public rSim scenario hooks * Enforce blackboard contracts in behaviour setup * fix: track ball y with lone goalkeeper fallback * Reset strategy on referee stoppages * Make out-of-bounds restarts playable * Use startup strategy in core demo * feat: muted colour palette and grouped ghost-style buttons in GUI * Revert "feat: muted colour palette and grouped ghost-style buttons in GUI" This reverts commit c5caa3459ea2fa9e9da262e47d6729dbbb0eb46b. * Revert "feat: redesign GUI to 2x2 quadrant layout with field filling top-left" This reverts commit d922a78d20ee7a969c37105e33497936986cd5df. * feat: restore 2x2 quadrant layout with original colour scheme * feat: add goal_depth to FieldDimensions and propagate to RefereeGeometry and RSim renderer * feat: add ball placement entry point, strategy skeleton, and tests - demo_ball_placement.py: colleague entry point wired with the Exhibition Road field (GREAT_EXHIBITION_FIELD_DIMS, 4 m × 3 m), CustomReferee pre-configured for out-of-bounds → ball placement cycles, and GUI on port 8080. Zero referee config work needed by the strategy developer. - utama_core/strategy/examples/ball_placement_strategy.py: BallPlacementStep skeleton with detailed docstring guide (approach → capture → carry flow, relevant API pointers, dribbler usage). BallPlacementStrategy wires it into the referee override tree for 2v2 exhibition play. - utama_core/tests/strategy_runner/test_ball_placement_rsim.py: three rsim integration tests covering the full placement contract — placer approaches ball, placer progresses toward designated_position, non-placer robots clear and hold outside BALL_KEEP_OUT_DISTANCE. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: finish the ball_placement * init demp * take the free kick * success sow the demo * finish the ball placement * test: tighten ball placement rsim tests and add placer selection coverage - Test 1: robot 0 now starts 1.5 m from the ball (was already within tolerance), checks distance decrease ≥ 0.8 m instead of threshold crossing - Test 3: hardcode placer_id=0 from reset_field instead of re-deriving it with the same min(distance_to_ball) logic as the implementation (tautological); move ball to centre so robot 1 has room to reach 0.8 m clearance - Test 4 (new): robot 1 starts closer to the ball than robot 0; asserts robot 1 approaches the ball and robot 0 does not — exercises placer selection with robot 0 as non-placer, which original tests never covered - Module docstring: add Known Gap section explaining why has_ball carry phase transition is not tested end-to-end Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add god mode to referee GUI for manual ball placement testing - Right-click field canvas (with God Mode active) to issue BALL_PLACEMENT_YELLOW/BLUE directly to any target point - Uses force_command() on the state machine to bypass the STOP-first guard, so the command takes effect immediately without operator needing to advance through STOP manually - Add demo_ball_placement_real.py: single-robot rsim/real entry point with all auto-rules off, operator-driven via GUI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: update direct free kick tests broken by ball-placement-first routing DirectFreeOursStep unit tests were asserting the robot targets the exact ball position, but the new implementation targets an approach point behind the ball (_APPROACH_OFFSET). Update assertions to check the target is closer to the ball than the robot's start position. The two rsim integration tests were relying on OutOfBoundsRule triggering DIRECT_FREE via OOB detection, but the state machine now routes through BALL_PLACEMENT first before DIRECT_FREE. Switch reset_field to inject the command directly via force_command() so each test stays focused on its subject (kicker approach / keep-out clearance) rather than OOB detection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: pivot around ball using turn_on_spot during ball placement carry When the placer has the ball and needs to reorient toward the target, use turn_on_spot (dribbling=True) instead of move — this offsets lateral velocity so the robot orbits the ball contact point rather than spinning around its own centre, reducing the chance the dribbler loses the ball. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 4 P2 review comments from fred - state_machine: manual STOP→BALL_PLACEMENT_* via set_command now uses _post_ball_placement_command (like auto-advance 1) instead of unconditionally setting next_command=NORMAL_START, so foul-restart is preserved through placement. - state_machine: force_command sets next_command=NORMAL_START when forcing a BALL_PLACEMENT_* command so auto-advance 4 can fire. - KickAfterDirectFreeStep: replace seen-first-restart guard with prev_command tracking; only kick on NORMAL_START that followed a DIRECT_FREE_* command, so the post-placement restart is never skipped. - demo_ball_placement_real.py: mode="real" (was "rsim"). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: test_placer_moves_toward_designated_position robot start position Robot 0 was teleported onto the ball expecting has_ball to fire, but rsim infrared doesn't trigger reliably. Start robot 0 at (-1.3, 0) instead — behind the ball relative to the target — so the approach motion reduces distance to target, making progress measurable without requiring has_ball. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 2 new P2 review comments from fred - ball_placement_and_kick_strategy: KickAfterDirectFreeStep was never ticked during DIRECT_FREE_* (the RefereeOverride subtree handles it), so _prev_command never reached a DIRECT_FREE value and the post- placement kick was always skipped. Introduced _CommandTracker, a mutable container updated by BallPlacementAndKickStrategy.step() before every tree tick, so the transition is observable even when the strategy subtree is overridden. - robot_info: when trusted_ir_robots is set, untrusted robots that did not appear in robot_responses (non-blocking real-mode polling can omit robots) were keeping their stale has_ball value. Refiner now applies vision-proximity inference for all untrusted robots up-front, then overlays IR readings only for trusted robots that responded. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add vision-to-firmware ID mapping to demo_ball_placement_real * fix: approach ball with back facing it for back-mounted kicker go_to_ball was setting target_oren = robot.angle_to(ball), meaning the robot approached with its front toward the ball. Since the kicker and IR dribbler sensor are on the back, the robot now approaches with back toward ball so the IR sensor triggers correctly. * Filter vision detections to roster defined by vision→cmd mapping PositionRefiner now accepts allowed_yellow_ids and allowed_blue_ids. Any robot ID reported by SSL-Vision that is not in the allowlist is dropped before it enters the game state, preventing stray detections from permanently accumulating in friendly_robots/enemy_robots. StrategyRunner derives the allowlists from yellow/blue vision_to_cmd_mapping keys so the roster is implicitly defined by what is configured in gefr.py (or any other runner) — no extra config needed. * fix: remove circular coverage check and guard blue allowlist in single-team mode _validate_mapping_covers_game_frame was called after wait_until_game_valid but observed_ids were already allowlist-filtered, making the check circular and causing empty observed IDs in output. Removed the dead post-loop calls. Blue allowlist is now only derived when opp_strategy is present; in single-team mode blue robots must remain visible as enemies regardless of whether a blue mapping was provided. --------- Co-authored-by: isaac0804 <isaacyong0804@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Isaac Yong <72080621+isaac0804@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joel <nzmjoel@gmail.com> Co-authored-by: Joel <42647510+energy-in-joles@users.noreply.github.com> Co-authored-by: SSL_Linux_Laptop <firstorderrobotics@gmail.com> Co-authored-by: Ningchaun <1311856651@qq.com>
add numerical mapping for real control when robot ids dont match with vision or when using one transmitter