Repository navigation
Conversation
…ints at The roadmap still described the pre-evaluate.py evaluation plan, kept a mirror_swap xfail that no longer exists, and listed several items that had already shipped. Rewritten along its own convention (a resolved item becomes a one-line pointer under "Done"), with the remaining items numbered in sequence: - the strategy-evaluation items -> Done: evaluate.py and pool_v1 replaced the situation bench and ladder.py. The two genuinely open parts moved instead of disappearing: the CI stall gate into the stalls entry, the tactic contracts into the ball-holding entry. - directional tube and Replay overlays -> Done; fpp mirror_swap -> Done: the 0.52 m was the defense-area keep-out clamp, not a local planner minimum, and the xfail went in 5f97199. - stalls -> the 2026-10-17 full round-robin figures; sim realism -> Phases 0-2 built, with what remains named. - bang-bang, trajsample, testing gaps and convergence revised for what has landed since; the unrecorded Sumatra findings (#4-7) and the shared-Sticky item removed (Sticky exists and is used). Also fixes the stale notes that pointed into the roadmap: obstacles.py's class docstring still described a growing circle, FORK_NOTES referenced two deleted roadmap sections, sim_realism's status line read as if nothing had been built, and testing_gaps #6 now records the one sim case for ball_placement_interference.
Corrections to the previous commit, checked against the code: - stalls: 1731e90 fixed only the kick-off that waited the 15 s cap; the three COMMITTED_FROZEN stalls of the 2026-10-17 round-robin are not yet diagnosed (docs/strategies.md says the same). - sim realism: P1's value model is fitted (passes 1-3) but not yet a better ranker than goals; only P2 is not started. - Shared hysteresis helper: Sticky's users are PassAndShoot, ClearBall and PressAndContain; pickers.friendly_closer_to_ball keeps its own hysteresis. - grsim entry: drop the note that FORK_NOTES points at a missing section (the previous commit fixed it); "like 10c" names its entry; pool_v1 holds the v3 ranges, v3_seed2 is the round-robin's seeded run of them. The roadmap now asks to cite entries by title, since open items renumber. Every comment, docstring and doc that cited a roadmap number used numbers long gone ("roadmap item 15/16", "item 5", "item 1"); each now names its entry. fastpathplanner_corner_test's docstring said seeds were pinned as stuck; the list has been empty since #147. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…field A team with no robot of goalkeeper_id (default 0) crashed on its first tick with KeyError in the goalkeeper tactic. Real tests often run without robot 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
Against a centred keeper the two corners are near-equal, and the aim flipped between them every tick; a lone carrier turned back and forth ~11 s before shooting. Uses find_best_shot's switch margin, as _pass_and_score does. Changes strategy code: cached matches rerun. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
Field, camera half, vision/firmware IDs, IR trust and tactics in one config block; operator-only referee driven from the dashboard; starts in HALT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
A team without the goalkeeper's robot showed a phantom goalkeeper (e.g. ID 15) in the dashboard's tactics list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
FieldCheck judges raw vision, the geometry packet and radio feedback: each driven robot seen, under its ID, inside field_bounds; camera sending; field size; radio still answering; unconfigured robots in play. The Live tab shows the rows, dims everything outside field_bounds and draws the field as ssl-vision sees it. On real robots any failing row forces HALT. The dashboard opens on Live when a match is attached; Space only halts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…t a dribbler
RobotProfile(kicker, dribbler, ir) per robot, passed to StrategyRunner as
{yellow,blue}_robot_profiles and applied in every mode, so a test with a
broken robot can be rehearsed in a simulator. ProfiledRobotController wraps
the team's robot controller: a kick becomes a 0.3 s forward push at 1 m/s
(a stop command ends it); a dribble request without a dribbler never backs
up and is scaled to 0.5 m/s and 2 rad/s together. A robot without a ball
sensor is left out of the trusted-IR set. A profile for a robot that is not
driven, or ball sensors given both ways, stops the run at startup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
… Live page
The attacker/defender example takes one TestRobot per robot (vision ID,
firmware ID, RobotProfile) and RULES = "off" | "on" ("on": the human
referee profile, nothing auto-advances). The Live page lists only failing
and warning field-check rows, folds the passing ones into an "N ok" line,
hides the restart buttons when no rule sets up a restart, and drops the
robot list the field check already covers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…ts from them StrategyRunner(game_log_path=...) records a camera run in the league's game-log format: every vision packet as received, the referee's state, the radio's bytes both ways and a run-info record (ID mappings, profiles), all on this machine's clock. tools/convert_ssl_log.py reads it unchanged. The attacker/defender example records every camera run to replays/field/, and --calibrate plays fixed moves (ScriptedMovesTactic: rest, steps at 0.3/0.6/1 m/s, sideways, turns; a pause restarts the interrupted move). tools/robot_calibration.py measures each robot from such a log: loop delay, speed gain and acceleration per axis (the sim's move model fitted to the camera's positions), vision noise at rest, missed detections and kick speed. On a synthetic robot it recovers delay within 10 ms, acceleration within 15 % and gain within 1 %. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…ler replay payload timeline.js draws tools/match_timeline.py's swimlanes in the browser: the referee command band, one lane per friendly robot coloured by its tactic with a strip where it had the ball, each robot's distance to the ball, and the event markers. In the Replay tab it follows the cursor and seeks on click or drag (30 s / 60 s / 3 min / whole match); its tactic spans match match_timeline.tactic_changes exactly on a tournament match. In the Live tab each streamed state extends the lanes, stamped with the frame's game time (now sent with the state), keeping the last 5 minutes. A 13-minute replay's frames were 108 MB of JSON (full 17-digit floats) against a 16 MB .npz. Positions now go to the millimetre, velocities to the centimetre, and responses over 64 KB are gzipped: 7.4 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…ks, shots The Replay side panel's raw event and intention logs were hard to read. A Play list replaces them: one row per situation (a restart going live or a side winning the ball, from goal_ledger), with its third, length, shots and how it ended (the stoppage's reason, or who won the ball and how). Click a row to seek; the raw intention log stays, folded. The timeline gains a possession band, tactic switch ticks, shots and hover tooltips. The slow analysis (ball losses, shots) is kept beside the replay in <stem>.timeline.json, and npz frames are built straight from the columns, so a replay serves in ~3 s instead of ~16 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
Unseen, the ball filter carried the ball on at its last velocity, and the velocity refiner kept that velocity alive from the guessed positions: a ball hidden by a robot drifted away, and one that left vision's area flew off for good (an rsim attacker/defender run had it 600 m out after 15 minutes). Now it rolls on from where it was lost, slowing at BALL_ROLL_DECEL (0.29 m/s^2, official Div B logs) until it stops, and never leaves the area vision accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…riction, kicks, occlusions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
utama_core is not installed in the pixi environment, so every example failed at its first import when run by path, as their docstrings say to (the field-test example included). Each now puts the repo root on sys.path first, as the scripts in tools/ do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
… Live page - Right-click (long-press on a phone) on the Live field: ball placement to that spot by Yellow or Blue, as the old referee GUI's God Mode had. In a simulator the ball is moved there. - Below 760 px wide the panels stack, the page scrolls and buttons are bigger. - The field shows the 0.3 m run-off; a ball beyond the view is drawn hollow at its edge. - States arrive at 60 Hz: the field and timeline draw once per screen frame, the side panel's text 4 times a second (it was rebuilt on every state, so a click could miss). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
One JSON object per tick, every key repeated, made a match 65 MB (7.4 MB gzipped) and took 4 s to build. Columns of mm, cm/s and mrad with `seen` masks are 21 MB (4.3 MB gzipped) and build in 1.5 s; the page rebuilds the same per-tick frames in about 0.2 s, so nothing else changes. Pickled replays keep the old shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…trol loop `notify` writes each state to every stream client on the control loop (`CustomReferee.step`), on blocking sockets with no timeout. A client that stopped reading (a phone locked with the page open, a laptop asleep, WiFi gone) filled its buffers and blocked the loop for good: no radio commands, no HALT, no auto-HALT. Stream sockets are now non-blocking: once a client's buffers are full its write fails at once, it is dropped and its browser reconnects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
The field check ran after the referee stepped, so the tick that forced HALT still carried the command it replaced: pressing Force Start (or a placement) while a check failed sent one tick of motion. Vision is now taken and judged before the referee steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…s drawn first The runner kept the last auto-HALT's reason for good, so the red banner stayed up with the robots playing, and the Live page, which skipped its 4 Hz panel throttle while a reason was set, rebuilt the side panel on every state from then on. The reason now lasts as long as the HALT it forced. The command and the banner are drawn first on every frame, before the field and timeline, so neither waits on the throttle or fails with a drawing error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
The end was read from `frames`, which a columnar replay leaves empty since its ticks are sent as columns, so the last row ended at 0 s and read "-29 s" with no shots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
With our goal on the right in a view that mirrors x (rsim, replays), the heading dash was drawn from the raw orientation, so a robot facing +x pointed the wrong way. It now goes through the same field-to-screen transform as positions. The test runs field_canvas.js in node with a 2D context that records lines, skipped where node is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…onnects d9e6f2e dropped a client that stopped reading from the list but left its connection open: its keep-alives kept it alive, so a phone woken up got keep-alives and no states, its page frozen with the dot green, and a failed write could leave half a frame in the stream. A client whose write fails is now shut down and its browser reconnects (after 0.5 s: the stream sets `retry`). States and keep-alives, written from two threads, share a lock per client so neither cuts into the other's frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…a in play Any point on the Live page was accepted, the run-off and the half the camera does not see included, so the placing robots could drive out of vision. The command handler now refuses a target outside the field lines or, with a camera, outside the field check's area in play: a 400 with the reason, and nothing reaches the referee. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…lt pinned on a phone - A command the server refused (a placement outside the area in play) or never got shows under the buttons for 6 s; it was only logged to the browser console, so a Halt that did not go through looked the same as one that did. - No state for half a second turns the dot grey and shows "no update for N s" beside the command: the page was showing the past with the dot green. - On a phone, Halt floats in the bottom-right corner, in reach wherever the page is scrolled; it sat below the field, the timeline and the field check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
iPhone Safari fires no contextmenu on a long press, so the menu never opened there. A touch held still for 0.6 s opens it too; a click within 0.4 s of opening (the lifted finger) no longer closes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
…e ball was last seen Smoothing off and imputation on, the ball filter runs only while the ball is unseen, so it never heard the ball had been seen again: a second loss carried on the first one's roll from where that one started. A seen ball now resets the filter when it is not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJGHdb8Dht67hn4Lohndf5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything for the first real-robot field test (2026-10-22): an attacker-vs-defender example, field checks with auto-HALT, field-test recording and calibration, and a dashboard that works from a phone beside the field. Also a refreshed roadmap and a few engine and tactic fixes found along the way.
Field test
examples/real_attacker_defender.py: one attacker against one defender, on real robots, grSim or rsim. Per-robot hardware switches (no kicker, no dribbler) and a RULES switch. Its docstring is the test-day checklist:run/field_check.py): on camera runs it checks each robot is seen, inside the area in play and has a live radio link, plus the ball, the camera and the geometry. On real robots any failure forces HALT, judged before the referee steps so no tick of motion gets through.replay/game_log_writer.py).analysis/robot_calibration.pymeasures robots from them, andphysics_rangesmeasures floor friction.pixi run python examples/<name>.py). They used to fail at their first import, becauseutama_coreis not installed.Ball filter
BALL_ROLL_DECEL, 0.29 m/s²) and stays inside vision's area. It used to keep its last velocity forever: the rsim "ball escape", hundreds of metres away after a long match.Dashboard
Engine and tactics
debug_status.Testing
pixi run lint: clean.pixi run test: 12947 passed, 83 xfailed.