A real-time 3D desktop simulator for FRC teams to visualize shoot-on-the-move behavior: robot motion, turret aiming, flywheel/hood setpoints, latency-aware prediction, actuator limits, and ball trajectories.
License: The Unlicense (do whatever you want)
This repo is a tuning + teaching lab:
- see how latency + actuator slew limits affect “would we actually shoot?”
- validate solver stability (no crazy setpoint jitter)
- understand lead/compensation when the robot is translating + rotating
- iterate on tolerances and constraints before you burn practice time on a real bot
It’s not a full WPILib sim and it’s not a perfect physics engine—this is a visual + practical approximation that’s fast enough to run interactively.
- Python 3.12+ recommended
- Main deps:
numpyPyQt5pyvistapyvistaqt
Windows (PowerShell):
python -m venv .venv
. .\.venv\Scripts\Activate.ps1
pip install -U pip
pip install numpy pyqt5 pyvista pyvistaqtLinux / macOS:
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install numpy pyqt5 pyvista pyvistaqtpython main.pyThe simulator has two “shooting behaviors” depending on where you are:
-
When the robot is inside the scoring zone, the sim ignores Space and runs normal “shoot into goal” logic.
-
Fire decision is based on:
- in scoring zone
- actuators READY
- shot sim indicates a hit (within hit radius)
-
When the robot is outside the scoring zone, holding Space enables a “zone dump” behavior:
- turret is commanded toward a world-fixed direction (World X-)
- if that yaw is impossible (turret limits), it picks the closest reachable yaw
- it stays active while Space is held even if you keep driving / turning
- it will still shoot even if the dump shot wouldn’t hit the goal (that’s the point: dumping)
Safety behavior in dump mode:
- if the robot spins and the commanded turret angle would cause a big discontinuous jump (the “360 realignment” vibe), firing is temporarily blocked for a short moment so it doesn’t spam shots during the weird transition.
- Arrow keys: drive robot
- Shift: slow mode (scale down translation + rotation)
- M: toggle teleop on/off
- Space: dump mode only outside scoring zone (inside zone it does nothing)
- Params: change robot constraints, shooter constraints, physics, solver tuning
- State: watch live computed values (ready, miss distance, predicted fire time, etc.)
Each tick (fixed-step ~60 Hz):
-
Robot motion
- either autopilot path (swerve-ish) or teleop (arrow keys)
- translation and rotation are accel-limited
-
Solver (throttled)
- runs ~20–30 Hz (not every render frame)
- predicts robot pose at
fire_latency + time_to_ready - finds turret yaw / rpm (and optionally hood) to minimize miss
-
Actuators step
- turret, hood, flywheel move toward the desired setpoints with rate limits
-
READY gate
- checks yaw/hood/rpm errors vs tolerances
-
Fire gate
- if firing conditions are true, emits pulses at a configured ball rate (visual rate limiter)
-
Visualization
- current shot path, predicted (planned) shot path, vectors, ghost pose, trail
There are 3 layers:
Ready means the modeled actuators are close enough:
- turret yaw error ≤
turret_ready_tol_deg - hood error ≤
hood_ready_tol_deg - rpm error ≤
rpm_ready_tol
- GOAL mode:
READY && in_zone && sim_hit_now - DUMP mode:
READY && space_held && outside_zone && (not in dump realign block window)
Even if would_fire stays true, balls spawn at a max rate using FireGate:
- ball rate is controlled by the UI (
Ball rate (balls/s))
-
Field axes:
- x = field length direction
- y = field width direction
-
The field dimensions in
make_field()currently assume:- field: 57 ft (x) × 24 ft (y)
- scoring zone: 182.11 in in the x direction (full width in y)
-
Goal is a 3D point (or “top entry” ring model if you switch
goal_type).
Below is a “what it does” map matching the Params UI and ShooterParams.
- Goal X/Y/Z (m): the target point in field coordinates.
- Path speed multiplier: scales the autopilot velocity profile frequency/magnitude.
- Path radius multiplier: changes how wide the autopilot loop is.
- Max speed (m/s) (
vmax): speed clamp for robot translation. - Max accel (m/s²) (
amax): accel clamp for robot translation.
- Enable teleop: arrow keys drive instead of autopilot.
- Teleop max speed (m/s): teleop translation speed cap.
- Teleop max accel (m/s²): teleop translation accel cap.
- Teleop omega max (deg/s): yaw rate limit.
- Teleop alpha max (deg/s²): yaw accel limit.
- Shift slow factor: multiplies teleop commands while Shift is held.
- Turret min/max (deg) (
turret_min_deg,turret_max_deg): hard yaw travel limits relative to robot. - Turret slew rate (deg/s) (
turret_slew_rate_dps): max turret speed. - Turret ready tolerance (deg) (
turret_ready_tol_deg): max yaw error allowed for READY.
- Solve hood angle (
solve_for_hood): if on, solver selects hood; if off, uses fixed hood. - Hood angle (deg) (
hood_fixed_deg): fixed elevation when not solving hood. - Hood min/max (deg): hood travel limits.
- Hood rate (deg/s) (
hood_rate_dps): hood speed limit. - Hood ready tolerance (deg) (
hood_ready_tol_deg): max hood error allowed for READY.
- Solve flywheel RPM (auto) (
solve_for_rpm): if on, solver picks RPM; if off, you can effectively “lock” it. - RPM init: initial flywheel rpm at reset and when params re-apply.
- RPM min/max: solver/search clamp range.
- Flywheel accel limit (rpm/s) (
flywheel_accel_rpm_s): spool rate limit. - RPM ready tolerance (rpm) (
rpm_ready_tol): max rpm error allowed for READY. - Wheel radius (m) (
wheel_radius_m): used for rpm→exit-speed mapping. - Slip factor (
slip_factor): accounts for ball slip/contact losses. - Exit speed factor (
exit_speed_factor): additional scaling to match reality.
Exit speed approx:
exit_speed = wheel_surface_speed * slip_factor * exit_speed_factor
- Enable drag (
enable_drag): aerodynamic drag on the ball. - Cd (
Cd): drag coefficient. - Enable Magnus (
enable_magnus): simplified spin lift model. - Magnus k (
magnus_k): lift scaling constant (very approximate). - Spin (rev/s) (
spin_rps): spin rate used for Magnus. - Enable fired balls (visual): spawns moving sphere actors (purely visual).
- Ball rate (balls/s): max firing pulse rate when would_fire is true.
- Fire latency (s) (
fire_latency_s): delay between “decide to shoot” and ball release. - Hit radius (m) (
hit_radius_m): how close to the goal counts as a hit (also used in “ok”). - Sim dt (s) (
rt_dt): time step for internal trajectory sim. - Sim tmax (s) (
rt_tmax): max simulated time-of-flight. - Yaw/RPM/Hood samples (
rt_*_samples): legacy UI knobs kept for compatibility; the current “instant solver” uses its owninstant_*parameters internally. - Output smoothing τ (s) (
output_smooth_tau_s): low-pass filtering on setpoints (reduces twitch). - Continuity weight (
continuity_weight): penalizes big jumps from previous solution (reduces snapping).
- Robot + turret (solid): current pose
- Ghost robot + turret (transparent): predicted pose at fire time
- Velocity vector (green) and accel vector (orange)
- Aim line (yellow): where the turret is pointing from predicted pose
- Shot path now:
- green if it would fire (and is “good” under current logic)
- red otherwise
- Planned path (blue/gray): trajectory from predicted pose + desired setpoints
project/
main.py # entry point
src/
sim_window.py # Qt window + render + tick loop + gating logic
ui_panels.py # Params + State UI
field_world.py # field geometry + zones + robot motion models
shooter_logic.py # physics, solver, actuator stepping, dump/landing helper
ball_logic.py # pooled ball actors + fire rate limiter
geom2d.py # small vector helpers
assets/
FieldImage2026.svg
This is simplified on purpose:
- no wheel slip changes, ball compression, hood friction, shot-to-shot variance
- drag/magnus are approximations
- field obstacles aren’t modeled
But it is great for: constraints + latency intuition + solver stability and tuning tradeoffs.