Skip to content

Filter vision detections to roster defined by vision→cmd mapping - #127

Merged
energy-in-joles merged 8 commits into
mainfrom
feat/roster-filtering
Jun 3, 2026
Merged

energy-in-joles merged 8 commits into
mainfrom
feat/roster-filtering

Conversation

@isaac0804

@isaac0804 isaac0804 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • `PositionRefiner` now accepts `allowed_yellow_ids` and `allowed_blue_ids` (`frozenset[int]`). Any robot ID reported by SSL-Vision that is not in the allowlist is dropped before entering the game state, preventing stray detections from permanently accumulating in `friendly_robots` / `enemy_robots`.
  • `StrategyRunner` derives the allowlists from the validated `vision_to_cmd_mapping` keys — no extra config needed. Empty mapping (rsim) → `None` → no filtering.

Stricter real-mode validation:

  • `yellow_vision_to_cmd_mapping` is now required for the friendly team in real mode (single-team and PVP). Previously only PVP enforced this.
  • Mapping entry count must equal `exp_friendly` / `exp_enemy` in both single-team and PVP real mode. Previously only PVP checked count.
  • Post-load coverage check (mapping keys == observed vision IDs) now runs for single-team real mode too, not just PVP.
  • Mappings are validated before `PositionRefiner` is constructed, so allowlists are always derived from validated data.

Single-team real mode behaviour:

  • Friendly mapping: required (raises if absent)
  • Opponent-color mapping: optional — silently ignored if no `opp_strategy` is provided

Test plan

  • Run rsim scenario — confirm no change in behaviour (no mapping = no filtering)
  • Run real mode single-team (`YELLOW_VISION_TO_CMD = {1: 0}`) — confirm only vision ID 1 appears in `game.friendly_robots`, even if other robot IDs are briefly seen by SSL-Vision
  • Omit `yellow_vision_to_cmd_mapping` in real single-team mode — confirm `ValueError` raised at startup
  • Provide mapping with wrong count — confirm `ValueError` raised at startup
  • Provide mapping whose keys don't match observed vision IDs — confirm `ValueError` raised after first game frame

New unit tests (42 → 77 passing)

  • `test_validate_vision_to_cmd_mapping_real_friendly_mapping_required`
  • `test_validate_vision_to_cmd_mapping_real_opponent_not_controlled`
  • `test_validate_vision_to_cmd_mapping_single_team_count_check`
  • `test_validate_vision_to_cmd_mapping_single_team_correct_count_passes`

🤖 Generated with Claude Code

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.
Copilot AI review requested due to automatic review settings June 3, 2026 20:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces optional per-team allowlists to prevent stray SSL-Vision robot detections (IDs not in the roster) from entering and accumulating in the GameFrame robot dictionaries, by deriving those allowlists from the configured vision→command ID mappings and filtering detections in PositionRefiner.

Changes:

  • Plumbs allowed_yellow_ids / allowed_blue_ids from StrategyRunner into the refiner initialization pipeline.
  • Extends PositionRefiner to filter vision robot detections (and prior-frame carryover) to the allowlisted IDs when provided.
  • Derives allowlists from the vision→cmd mapping keys (intended for real mode) to avoid additional configuration.

Reviewed changes

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

File Description
utama_core/run/strategy_runner.py Derives and passes per-color allowlists into side/refiner setup so roster filtering can be enabled automatically from mappings.
utama_core/data_processing/refiners/position.py Applies allowlist filtering to vision detections (and last-frame robot sets) to prevent non-roster IDs from entering/persisting in game state.

Comment thread utama_core/run/strategy_runner.py Outdated
Comment on lines +212 to +219
# Derive per-color roster allowlists from the vision→cmd mappings (real mode only).
# Any robot ID seen by vision that is not in the allowlist is silently dropped so that
# stray detections from robots not in play never pollute the game state.
_allowed_yellow = (
frozenset(yellow_vision_to_cmd_mapping.keys()) if yellow_vision_to_cmd_mapping is not None else None
)
_allowed_blue = frozenset(blue_vision_to_cmd_mapping.keys()) if blue_vision_to_cmd_mapping is not None else None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 9f215d1: allowlists are now derived from self.yellow_vision_to_cmd_mapping / self.blue_vision_to_cmd_mapping — i.e. after _validate_vision_to_cmd_mapping has already run and normalised them. Ignored opponent mappings return {}, so frozenset({}) or NoneNone → no allowlist applied, consistent with the ignore semantics.

Comment on lines +346 to +350
if self.allowed_yellow_ids is not None:
yellow_vision_robots = [r for r in yellow_vision_robots if r.id in self.allowed_yellow_ids]
old_yellow_robots = {k: v for k, v in old_yellow_robots.items() if k in self.allowed_yellow_ids}
if self.allowed_blue_ids is not None:
blue_vision_robots = [r for r in blue_vision_robots if r.id in self.allowed_blue_ids]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit f85d874: added test_allowlist_filters_stray_yellow_robot and test_no_allowlist_passes_all_robots to position_unit_test.py covering exactly this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit f85d874: added test_allowlist_filters_stray_yellow_robot and test_no_allowlist_passes_all_robots to position_unit_test.py covering exactly this case.

isaac0804 added 2 commits June 3, 2026 21:17
- Friendly vision_to_cmd_mapping is now required in real mode (single-team
  and PVP); previously only PVP enforced this.
- Count check (mapping entries == exp_friendly/exp_enemy) now applies in
  single-team real mode, not just PVP.
- Post-game-frame coverage check (_validate_mapping_covers_game_frame) now
  runs for single-team real mode, not just PVP.
- Update tests: fix test that incorrectly assumed None mapping was valid for
  the friendly team; add 4 new tests for single-team validation paths.
Previously the allowlists passed to PositionRefiner were derived from
raw (unvalidated) mapping params before _validate_vision_to_cmd_mapping
ran, meaning a bad mapping could silently construct an incorrect refiner
before raising. Also, rsim mode could temporarily get a non-None
allowlist if a mapping was mistakenly provided.

Fix: validate both mappings first, then derive frozenset allowlists from
the validated self.*_vision_to_cmd_mapping dicts. Empty dict (rsim) →
falsy frozenset → None (no filtering). Use opp_strategy as a sentinel
for self.opp truthiness check during validation (overwritten immediately
after by _setup_sides_data).
Copilot AI review requested due to automatic review settings June 3, 2026 20:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread utama_core/run/strategy_runner.py
Comment thread utama_core/config/settings.py
isaac0804 added 2 commits June 3, 2026 21:56
_validate_mapping_covers_game_frame now lives in GameGater as a static
method and is called from wait_until_game_valid, after the first valid
game frame is available. StrategyRunner passes the real-mode mappings
in; non-real modes pass None so no check runs.

This keeps all "is the game state valid?" logic in the one place that
owns waiting for a valid initial frame, rather than split between
GameGater (robot/ball count) and StrategyRunner._load_game (ID coverage).
…st tests

When GameGater is waiting for a valid frame and an allowlist is active,
the periodic status print now shows both the observed vision IDs and the
expected IDs from the mapping so a misconfigured mapping is immediately
visible in the terminal output rather than causing a silent hang.

Also adds two PositionRefiner unit tests covering the allowlist path:
one that verifies stray IDs are dropped when an allowlist is set, and one
that verifies all IDs pass through when no allowlist is configured.
Copilot AI review requested due to automatic review settings June 3, 2026 21:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

utama_core/run/strategy_runner.py:352

  • This comment still references _validate_mapping_covers_game_frame() on StrategyRunner, but that helper was moved to GameGater. Updating the reference will prevent confusion when debugging mapping validation failures.
                # At init time we only know how many robots to expect, not their
                # actual vision IDs (those are non-contiguous in some deployments).
                # Check count here; key-coverage against observed IDs happens in
                # _validate_mapping_covers_game_frame() after _load_game().

Comment thread utama_core/run/strategy_runner.py
Comment thread utama_core/config/settings.py
Copilot AI review requested due to automatic review settings June 3, 2026 22:23
@energy-in-joles
energy-in-joles merged commit 158d45a into main Jun 3, 2026
3 checks passed
@energy-in-joles
energy-in-joles deleted the feat/roster-filtering branch June 3, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 6 out of 6 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

utama_core/run/strategy_runner.py:352

  • This comment still refers to _validate_mapping_covers_game_frame() running “after _load_game()”, but that method was removed from StrategyRunner and the coverage check now happens inside GameGater.wait_until_game_valid() (via GameGater._validate_mapping_covers_game_frame). The stale reference is misleading for future maintenance/debugging.
                # At init time we only know how many robots to expect, not their
                # actual vision IDs (those are non-contiguous in some deployments).
                # Check count here; key-coverage against observed IDs happens in
                # _validate_mapping_covers_game_frame() after _load_game().

Comment on lines +332 to 337
# if we are not running an opp strat, but the opponent-color mapping provided, warn it will be ignored
if self.opp is None and self.my_team_is_yellow ^ is_yellow:
warnings.warn(
"vision_to_cmd_mapping is provided but will be ignored since the opponent team is not being controlled."
)

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