Repository navigation
Sim realism, goal-based strategy evaluation, regression moments, and on-pitch pictures - #146
Merged
Merged
Conversation
Match pixi.toml and the CI ruff-action pin, and move ignore/fixable under [lint] so ruff 0.15 stops warning about top-level keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
motion_stats imported it from tools/evaluation/round_robin.py, but tools/ is not part of the installed package. The table now lives in utama_core/analysis/strategy_table.py; round_robin.py imports it from there, and the tournament summary test imports it from the new location. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delete align_defenders, to_defense_parametric, find_likely_enemy_shooter, velocity_to_orientation, and RSimVSS, is_our_command, to_polygons: each was referenced only by its own definition (git grep -w). Also drop the helpers only they used (predict_goal_y_location, clamp_to_parametric, ccw, ang_between, step_curve, EPS) and the imports they orphaned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only bank_v8 is loaded (tools/scenario_bench.py, bench_vs_standings.py, docs). v5/v6 stay in git history; docs mentioning them as calibration history are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-status modules Pure move, no logic change: SideRuntime and the _turned/_mirrored frame transforms go to run/side_runtime.py; the rsim teleport-settle, placement and frozen-field recovery to run/sim_recovery.py (SimRecoveryMixin); the browser vision stream and its commentary/roster to run/vision_stream_status.py (VisionStreamStatusMixin); and the rich live status panel to run/live_status.py (LiveStatusMixin). StrategyRunner inherits the mixins, and the names tests import stay importable from strategy_runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h tools motion_planning_benchmark and scenario_bench each had an identical _git_revision (short HEAD, None outside a checkout). Move it to utama_core/global_utils/git_utils.py. The scenario_bench copy in hand_authored_scenarios.py returns "unknown" instead of None, so it stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…essing path The Doc column named a TODO and a bare data_processing/ path. Point it at utama_core/team_controller/README.md (brief) and fix the path to utama_core/data_processing/. CLAUDE.md is a symlink to AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pixi.toml defined main = "python -m main" both at the root and again under [target.linux-64.tasks] with the identical command, so the target entry changed nothing and is removed. docs/tools.md's task table gains the build-planner task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The block printed values test_vector.py already asserts. The one check it added, Vector2D.dot as a method, is now an assertion in test_vector2d_operations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing references assets/images/pipeline.drawio.png; the README and docs/pipeline_method.md use pipeline_new.drawio.png. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kalman.py had type-signature comments above three call sites, and game.py had a stray '# def predict_/' stub. None of it is code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
utama_core/tests/referee/demo_referee_gui_rsim.py is an older copy of examples/demo_referee_gui_rsim.py (WanderingTactic instead of tiki_taka_plus). It is not a test (pytest would not collect it), nothing imports it, and only its own docstring names it. wandering_strategy.py stays: other files import it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ties Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…om replays Phase 1 of docs/sim_realism.md, for converted official logs and our own matches alike: deceleration by ball speed and time since a kick, rolling segment fits, per-robot speed, acceleration, braking and turn-rate percentiles, and dropped detections, with between and within spreads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…_core/analysis/realism Ports the 2026-10-14 xG analysis (~/dev/ssl/xg-analysis) into the repo. Shots, goal linking (real goals via operator-confirmed GOAL events), shot outcomes and the xG fit are unchanged; per-match output is identical to the old scripts on a real and a sim match. tools/realism_scorecard.py builds the real reference from converted logs and scores a round-robin against it, row by row with 95% bootstrap intervals over matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
SimNoise (rsoccer_simulator/src/Utils/sim_noise.py) holds ranges and a seed, all off by default. Per match it draws each robot's command latency (whole ticks) and kick-speed offset; kicks add their own spread, drawn when a kick command starts and held while it lasts; vision noise and dropped detections come from a stream per tick. Draws depend on the seed, colour and robot id only, so every pairing in a run gets the same physics. Same seed, byte-identical match. Wired through SSLStandardEnv, StrategyRunner(sim_noise=...), match.run_match and round_robin.py --sim-noise JSON. Recorded in each columnar replay (load_sim_noise) and summary.json; enters match_key only when set. Fix: vision noise was added to env.frame in place. Teleports write that frame back into the physics, so with noise on every ball placement moved every robot by its noise. Noise now goes on the observation only (regression test in test_sim_noise.py). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Keeps HEAD's wording; the other side still described the status labels removed on 2026-10-10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECXiwf3VYxBTMmrLaexRQK
…(with repo hygiene base) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
A shortened run cached its matches under the full-length key, so a later full-length --reuse run would have served them as full matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
54 matches, 1059 attempts, 217 scored; held-out AUC 0.819. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…detections as sidecars The converter dropped data the sim-realism work needs. Each converted log now also writes <stem>.kicks.json (the tracker's KickedBall reports, once per kick) and <stem>.tracker.zip (per-tick visibility and angular velocity); --raw-vision adds <stem>.vision.zip with every camera detection. Kicks and visibility come from whichever tracker source sends them (in 2026 ER-Force, the busiest, sent neither). --backfill-extras adds the sidecars to replays converted earlier without rewriting them. The archives are named .zip so replay globs (*.npz) skip them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…w vision Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy # Conflicts: # docs/tools.md
…t, more rows - Shot outcomes split wide / post / crossed with no goal (crossing point interpolated to the goal line; a bounce near a post counts, including the frame the next kick is detected on). Wide shots record how far outside the post. - reaction.py: keeper and nearest-defender time from the kick to a velocity change, and to moving toward the shot line, with the window the shot left to react. - passes.py: a softer kick detector for passes, the inferred receiver's offset from the kick's line, and completion. - New rows: open-play and set-piece attempts, attempt distance, keeper saves in every xG band, aim outside the posts, wide miss distance, reaction shares and medians, pass offset and completion. The broken-tracker match is excluded. --sim-cache option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
tools/match_timeline.py REPLAY.npz T0 T1 [--side friendly|enemy] draws the referee command band, one lane per robot coloured by the tactic it was in (ticks at switches, a strip where it had the ball), each robot's distance to the ball, and goals, fouls and shots along the top. It prints tactic switches per minute and the longest tactic span per robot. Events come from replay_trace.events; shots from turnover_breakdown.analyse_match. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
tools/restart_picture.py draws, for one restart in a replay, the target each friendly robot is commanded to by RefereeOverride.tick: the ball and its keep-out, the placement or free-kick spot, roles (kicker, placer, keeper), and red flags on targets inside the keep-out or a defence area they must avoid. --all-restarts draws one small panel per restart command in the referee sidecar. Targets are captured by swapping move/turn_on_spot in custom_referee.actions for recorders during the call only; no engine or planner code changes. The pitch, palette and game-from-scenario helpers move into plan_snapshot unchanged in API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
tools/plan_trace.py runs FastPathPlanner._path_to on every sampled tick of a replay window and draws the robot's path (coloured by time) with each tick's carrot, the ball path, and time panels for robot-to-ball distance, robot-to-carrot length, speed and the referee command. --json prints the per-tick rows. The trace function takes a game-per-time callback, so it is testable without a replay. plan_snapshot.py: pitch styling, pitch, obstacle, ball and robot drawing moved into shared helpers that plan_trace reuses; its public API and picture are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
plan_trace replays recorded states, it does not re-simulate: say so, and read the kick-off example right. Recorded before the push-out fix, the robot stays 0.80 m from the ball, while the current planner's carrot from those states is the push-out point 0.85 m out. The short-carrot annotation now tells a push-out from a parked robot (short_carrot_label, with a test at the 258 s state). The open-play example is dropped: its target was a guess the robot never followed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Positions with and without the ball, shots (struck from, and conceded) and
fouls by rule, drawn on a pitch for every strategy of a round-robin run. Each
panel attacks right; a team that attacks left is rotated 180 degrees.
Shot records from ChanceTracker now carry the origin ("xy") they are drawn at.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Four pitch figures from the tournament_20261017_151052 run, with the observations they support, in docs/signal_report.md "On the pitch". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…hampion tools/evaluation/explain.py writes positions (with and without the ball), shots for and against, fouls by rule and a summary.json for a candidate and the champion, from the replays in a run dir (a round-robin's or an evaluate.py run's). A match is picked by its file stem (<config_a>_vs_<config_b>[_s<seed>]); the scores come from the replay's final referee state and the fouls from the match's .stats.json. The pictures explain a verdict and never make one. pitch_maps.py: the per-match jobs now go through views_for_matches(), so explain builds its views the same way; run_views and the RUN_DIR behaviour are unchanged. Two-strategy figures get one row of two panels. evaluate.py --explain (off by default) keeps each match's files until explain has written replays/eval/explain/<candidate>_<run id>/; a failure is printed and never loses a verdict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
STRATEGY_DEVELOPMENT.md (Evaluating a strategy) and tools.md describe --explain and explain.py: the pictures explain a verdict, goals stay the verdict. The example is split_shape against clear_press_plus from tournament_20261017_151052, made by tools/evaluation/explain.py without playing any match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…s write findings Each tool's flags go to JSON lines (--findings PATH, - for stdout), so agents filter on the JSON and open a picture only to understand it. match_timeline: tactic_thrash at THRASH_PER_MIN (4) switches per minute of live play, plus timeline_findings() without drawing. plan_trace: parked and pushed_out runs of at least PARKED_MIN_S (1 s); the short-carrot label now uses the same classifier. pitch_maps: six zone_share facts per strategy, no threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…is 1 cm --findings PATH writes one finding per flag on a flagged target for every restart in the match (each occurrence, not drawn): target_in_keep_out, keep_out_short and target_in_defense_area, with the measured value and its limit. restart_findings(replay, side) is the callable form. The deadband flag now covers 0.79-0.80 m (1 cm, as the comment in actions.py says) instead of 0.78-0.80 m. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Most strategies switch tactic 10-25 times per live minute by design, so a switch-rate threshold flagged nearly all of them. A flip-back (a tactic held under 1 s, then reverted) separates them: median 0-0.5 per minute for the steady strategies, 4.6-5.6 for most possession-split ones, 13.4 for press_and_pass. The finding fires at 2 per live minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
One page that walks through every pitch picture from real replays: how each strategy stands, shoots and fouls; a candidate against the champion; one match over time; a restart; the planner on fixed scenarios and on one recorded tick and window. For each: how to read it, what it has shown so far (flip-backs, the 1 cm keep-out shortfall, the pre-fix parked robot, dwa's collisions) and the command that draws it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
For each strategy, its N worst matches by goal difference (friendly side, config_a) are analysed: tactic_thrash over the whole match, stall windows from stuck_detector (with plan_trace's parked / pushed_out findings for each oscillating robot), and with --restarts the restart flags. Every finding goes to findings.jsonl; the top K with a picture source are drawn with the picture tool for their kind; index.md is the report. The match-file helpers move from tools/evaluation/explain.py to utama_core/analysis/matches.py so triage and explain share them. explain also runs triage on the candidate's two worst matches into <out>/triage/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
docs/tools.md: the triage row at the top of "Debugging a match". STRATEGY_DEVELOPMENT.md, "Debugging a foul or failure": the index.md, findings, picture and numbers steps, and the config_a-only limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…race plan_trace needs the robot's target and the replay does not record it; with the robot's final position as the target the carrots meant nothing and `parked` could never fire. A stall now gets match_timeline from 10 s before to 10 s after it (tactics, possession and distances, all recorded), and plan_trace stays a hand tool for when the target is known. The docs row also says triage covers only matches a strategy played as config_a. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…es are The README shows the position and shot maps of every strategy, named in the AGENTS.md glossary. The visual guide and signal report say which tactic draws the vertical lines: in our half mostly BlockShape's fixed-depth screen (robots still about 60% of the time, the ball 2-3 m ahead); the GiveAndGo lines are not explained yet. Roadmap: the match timeline as a dashboard panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
The one-tick Mermaid diagram moves to docs/architecture.md (which pointed back to the README for it) and the README describes the tick in five steps. The outdated 2025 drawio pipeline picture is removed. A row for seeing what the robots did (visual guide, triage), the badge under the title, tighter map captions, and the one-entry Milestones section dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
…ethod In the style of the old drawio diagram (sources, receivers, buffers, the refiner chain on the main loop, then strategy, motion and output), with today's class and module names, drawn by docs/img/pipeline/make_one_tick.py so it can be redrawn when a name changes. The README's System design shows it; the Mermaid version with every module path stays in architecture.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
docs/img/diagram_style.py holds the boxes, receivers, arrows, main-path line, key and palette of the one-tick picture, so further architecture pictures share them. Each picture script lists the classes, functions, constants and paths it shows in NAMES; test_diagram_names.py checks they still exist. It caught one already: the referee receiver is RefereeMessageReceiver, not RefereeReceiver. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
The match data flow, the tactic-kernel model, the referee's architecture and state machine, one rsim tick, the planner control schemes and the real robot path, in the one-tick picture's style (docs/img/diagram_style.py), each from its own make_*.py whose NAMES the name test checks. Drawn from today's code, which corrected the old diagrams: command noise runs inside SSLStandardEnv.step and _get_commands and physics parameters are set once; the defense-area rules each have a stopping and a non-stopping branch; the real path has one-slot buffers, and GameFrame lives in game_frame.py. Step-by-step flowcharts, the generated package graph and the detailed one-tick Mermaid stay as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
CI's runners failed test_corner_route_arrives[28]; here it arrives at tick 64 after 14 carrot reversals, on the edge of the known detour-side flip (roadmap item 5). The code is identical (main has not moved) and generic numba codegen does not reproduce it, so the difference is the host's float results. A non-strict xfail lets either outcome pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L4M4hMLtZTUGNu6jThP1gy
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.
Sim realism measured against real Division B matches, a strategy evaluation system that decides on goals, regression moments, planner and referee fixes, and a set of on-pitch pictures with docs built around them.
Release notes
Strategy evaluation
tools/evaluation/evaluate.py: is a candidate stronger than the champion of a fixed reference pool (pools/pool_v1.json)? It plays rounds under seeded sim noise, decides on paired goal differences and stops once the answer is clear. Every match goes into a ledger (replays/eval/ledger.jsonl), with ratings over all of it. Goals only: shots, xG and the other signals were tested as rankers and rejected.--explaindraws candidate-vs-champion pictures before the replays are removed: positions with and without the ball, shots and fouls, plussummary.jsonand a triage of the candidate's two worst matches.Seeing the game
docs/visual_guide.md: the game drawn on the pitch from real replays, with what each picture has shown us and the command that draws it. It names and shows the position maps and shot maps of every strategy.tools/pitch_maps.py: position, shot and foul maps per strategy.tools/match_timeline.py: one match window as swimlanes (referee, each robot's tactic, possession, distance to the ball).tools/restart_picture.py: where the referee override sends each robot at a restart.tools/plan_snapshot.pyandtools/plan_trace.py: the fast-path planner's decision on one tick, and over a window.tools/motion_planning_benchmark.py --figures: trajectories and a summary per scenario.tools/package_graph.pyanddocs/architecture.md: the package dependency graph, with the replay/analysis import rule checked.tools/triage.py RUN_DIR: start here when a strategy plays badly. It takes each strategy's worst matches, collects every flagged problem and pictures the worst. Output:index.md,findings.jsonland pictures.--findingson the picture tools: one shared JSON format (utama_core/analysis/findings.py), so agents can filter problems without reading images.docs/img/diagram_style.py) instead of Mermaid: one tick ofStrategyRunner(in the README), the match data flow, the tactic-kernel model, the referee and its state machine, one rsim tick, the planner control schemes and the real robot path. Each comes from amake_*.pyscript, and a test checks every class and path a picture names still exists.docs/signal_report.mdwas redrawn as figures that explain results instead of rank colours.Sim realism
utama_core/analysis/realism), covering ball deceleration, robot motion, dropouts, shots and xG.convert_ssl_log --keep-raw, with resume and memory caps.SimNoise: seeded per-robot latency, kick speed and direction noise, vision noise, and physics knobs (off by default).rc-robosimfrom our fork invendor/rSim.docs/sim_realism.md.Regression moments and debugging
tools/moments.py,utama_core/tests/moments/), run inpixi run test.replay_trace --events/--json,repro_from_replay --summary,moments fouls --by/--json, andtools/setup_worktree.shfor parallel agents.docs/STRATEGY_DEVELOPMENT.md, "Debugging a foul or failure": the workflow, and the bugs it has found.Fixes
carry_exhaustedcounts reaction time before braking.Repo hygiene
strategy_runnersplit into modules.docs/img/pipeline/one_tick.png, frommake_one_tick.py) replaces the outdated 2025 drawio one in the README andpipeline_method.md; the Mermaid version moved todocs/architecture.md.Known, not fixed here
press_and_pass13. Thetactic_thrashfinding flags it.keep_out_short).GiveAndGoTacticlines in the position maps.--restarts.Testing
pixi run test: 12857 passed, 85 xfailed.pixi run lintclean.