In Roman mythology Dies (dai-ez) was the personification of day and the mother of Mercury.
Dies is the framework powering the Delft Mercurians' RoboCup SSL AI. The core is written in Rust: vision and referee networking, world tracking, robot control and skills, a physics simulator with an auto-referee, binary logging, and a web UI. Strategies, the team-level logic that decides what each robot does, are separate Rust binaries that run in their own process and talk to the core over IPC, so they can be rebuilt and hot-swapped while Dies keeps running.
See docs.delftmercurians.nl for the full documentation.
You'll need:
- A stable Rust toolchain (use rustup).
- On Linux:
sudo apt install libudev-dev libssl-dev pkg-config clang. - For the web UI build:
pnpmandtypeshare(cargo install typeshare-cli) on yourPATH. Thedies-webuibuild script regenerates the TypeScript bindings and the frontend bundle itself.
cargo run -- --strategy concerto # build the strategy once, run Dies in sim
cargo run -- --strategy concerto --watch # rebuild + hot-swap the strategy on change
mprocs # vite dev server + dies --watch (web UI work)
cargo run -- self-play --blue-strategy concerto --yellow-strategy concerto # headless matchThe web UI is served at http://localhost:5555. If you work on the frontend (./webui), run pnpm install there once and use mprocs for hot module reloading.
For hangs, tokio-console (cargo install --locked tokio-console) attaches to a running Dies and shows task state.
The quickest way to find where a change belongs is to follow one frame of data through the system: from a camera packet to a robot command. The map below is organised along that path, not along the crate dependency graph.
flowchart TD
VIS[SSL Vision / Game Controller<br/>dies-ssl-client]
SIM[Simulator + auto-ref<br/>dies-simulator]
BS[Robot feedback<br/>dies-basestation-client]
TR[World tracking<br/>dies-world]
EX[Executor frame tick<br/>dies-executor]
STRAT[Strategy process<br/>StrategyHost - IPC - Strategy::update]
subgraph ctrl[TeamController, per robot]
SK[Skills] --> COMP[Rule compliance] --> PLAN[Path planning + ORCA] --> PC[PlayerController]
end
OUT[Robot commands<br/>basestation / simulator]
UI[Web UI<br/>dies-webui + webui/]
LOG[Binary log<br/>dies-logger]
VIS --> TR
SIM --> TR
BS --> TR
TR -- WorldData --> EX
EX -- WorldSnapshot --> STRAT
STRAT -- SkillCommands --> SK
EX -- WorldUpdate --> UI
UI -. ControlMsg .-> EX
EX --> LOG
PC -- PlayerCmd --> OUT
Everything is driven by a vision frame. In sim the loop steps the simulator and feeds its output through exactly the same entry points as real packets.
| # | Stage | Where | What happens |
|---|---|---|---|
| 1 | Receive | dies-ssl-client, dies-basestation-client, dies-simulator |
Vision/referee packets arrive over UDP (or TCP); robot feedback (breakbeam, voltages, β¦) over the serial basestation. The simulator produces the same messages (detection, gc_message, feedback). |
| 2 | Dispatch | Executor (run_rt_live, run_rt_sim, step_simulation, run_headless) |
The main loop in dies-executor selects on all inputs and calls update_from_vision_msg / update_from_gc_msg / update_from_bs_msg. |
| 3 | Track | WorldTracker |
Kalman-filtered players and ball (filters), game state from referee commands, and possession. WorldTracker::get() yields a WorldData in absolute coordinates. |
| 4 | Frame tick | Executor::update_team_controller |
The heart of the system. Runs the steps below in order, once per vision frame. |
| 4a | Publish | same | Broadcasts a WorldUpdate to the web UI (absolute coordinates). |
| 4b | To team frame | WorldData::get_team_data, sides.rs |
Converts to a team-relative frame (+x always toward the opponent goal). Everything downstream of this point is team-relative. |
| 4c | Strategy | StrategyHost β transform.rs β IPC β runner β Strategy::update |
Builds a WorldSnapshot, sends it to each team's strategy process, and collects SkillCommands, roles and debug output. Strategies use TeamContext / PlayerHandle to issue commands. |
| 4d | Skills | TeamController β SkillExecutor, JointSkillExecutor |
Each SkillCommand becomes a running executable skill (GoToPos, HandleBall, Receive, β¦) that emits a PlayerControlInput per tick. Two-robot passes go through the pass coordinator. |
| 4e | Rules | comply in team_controller.rs |
Enforces game-state rules on every robot's input (stop distances, defense-area keep-outs, ball placement). Yellow-carded robots are benched earlier, by plan_sidelining, before the strategy runs. |
| 4f | Motion | avoidance planner, ORCA, PlayerController, path follower, yaw control |
Plans a path around obstacles, follows it, deflects through ORCA, applies acceleration/velocity limits and dribbler/kicker logic. Produces a PlayerCmd and transforms it back to absolute coordinates. |
| 4g | Log | dies-logger worker |
Records the frame (world, debug values, raw feedback) to the columnar log. |
| 5 | Send | Executor::player_commands β BasestationHandle::send_no_wait / Simulation::push_cmd |
Commands go out over radio, or into the simulator for its next step. |
- Web UI control. The React app reads world/debug state over a WebSocket and sends commands via REST (
api.tsx). Backend:server.rs/routes.rsβUiCommandβexecutor_task.rsβExecutorHandle/ControlMsgβExecutor::handle_control_msg. Rust types reach TypeScript throughtypeshare(generated intowebui/src/bindings.tsbybuild.rs). - Settings and tunables.
ExecutorSettings(persisted indies-settings.json) configures tracker, controller and avoidance. Skill knobs use thetunables!macro (tunable.rs); strategy-declared parameters useparams.rs. All are editable live from the UI. - Debug output. Executor code draws/records with the global functions in
debug_info.rs; strategies usedies-strategy-api/src/debug.rs, which travels over IPC. Both end up in the UI and the log. - Logging and replay. Schema in
schema.rs; logs land inlogs/.replay.rs+replay_controller.rsplay a log back through the live UI channels. Offline analysis:tools/dieslog.pyandtools/match_analytics.py. - Strategy lifecycle. The CLI builds/watches strategy crates (
dies-cli/src/strategy.rs);StrategyHostspawns one process per team and hot-swaps it when the binary's mtime changes. - Headless self-play.
commands/self_play.rsβExecutor::run_headless(headless.rs): a deterministic, faster-than-realtime sim match with no UI. - Entry point.
dies-cliparses flags (cli.rs) and starts the UI server, a self-play match, or a hardware test.
| I want to⦠| Start here |
|---|---|
| Change team behaviour: who goes where, when to shoot or pass | strategies/concerto: planner.rs (where the ball should go), driver.rs (turns a plan into skills), formation.rs (everyone else), keeper.rs, penalty.rs, constants in config.rs |
| Change how a robot does one thing (pick up the ball, aim, kick, receive) | dies-executor/src/skills/executable/, e.g. handle_ball/ |
| Add a new skill | Variant in SkillCommand β builder in PlayerHandle / skill_builders.rs β implementation in skills/executable/ β wire it up in create_skill_from_command (skill_executor.rs) |
| Change passing between two robots | pass_coordinator.rs, joint_skill_executor.rs, passing.rs |
| Expose new world information to strategies | WorldData β create_world_snapshot β WorldSnapshot β World accessor |
| Fix noisy or laggy robot/ball estimates | dies-world/src/player.rs, ball.rs, filter/, tuning in TrackerSettings |
| Change who is considered to have the ball | dies-world/src/possession.rs (types in dies-core/src/possession.rs) |
| Change how referee commands map to game states | dies-world/src/game_state.rs, GameState |
| Change rule enforcement (stop distance, defense area, yellow cards) | comply and plan_sidelining in team_controller.rs |
| Change obstacle avoidance or path planning | control/avoidance/ (planner.rs, orca.rs, obstacle.rs), config in avoidance_config.rs |
| Change trajectory tracking, acceleration limits, dribbler ramp | player_controller.rs, path_follower.rs, yaw_control.rs, motion_bounds.rs |
| Change what is sent to the robots / the radio protocol | PlayerCmd, basestation_client.rs |
| Change simulator physics or auto-referee | dies-simulator/src/lib.rs |
| Test a skill in isolation | strategies/scenarios (one binary per scenario) |
| Add a web UI panel or view | webui/src/components/panels/, webui/src/views/, data hooks in api.tsx |
| Add a UI β executor command | UiCommand β executor_task.rs β ControlMsg β Executor::handle_control_msg (lib.rs) |
| Add a REST endpoint | server.rs + routes.rs |
| Add a runtime-tunable knob | tunables! in the skill; generic settings in executor_settings.rs |
| Change what gets logged | schema.rs, builders.rs, worker.rs; reader in tools/dieslog.py |
| Add a CLI flag or subcommand | cli.rs, commands/ |
| Change in-match announcements | announcer.rs |
- Coordinates. Strategies and controllers work in team-relative coordinates (+x toward the opponent goal). The tracker, simulator, UI and log use absolute vision coordinates. Conversion happens only at the boundaries listed above. Never expose absolute coordinates to a strategy.
- Time. Anything in the per-frame path (skills, controllers, FSMs) uses world time (
ctx.world.dt,t_received), neverInstant. The headless sim runs faster than realtime, so a wall-clock timer fires at the wrong moment and breaks determinism. - Dependencies.
dies-coreholds shared types and nothing else. Feature crates depend on it, anddies-cliglues them together. Strategy crates only seedies-strategy-api.
See the developer guide for conventions (branches, commits, error handling, logging).