Files
fdestefano ec2ba08b5e
release / goreleaser (push) Successful in 27s
Release v1.0.0 #major
2026-06-03 00:43:39 -05:00

217 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RocketLeagueBot-Renderer
A web-based 3D Rocket League **live viewer and replay system** for telemetry
streamed from a RocketSimVis-compatible UDP source (e.g., a training bot).
Built with Go (Fiber + SQLite + zstd) and Three.js.
```
bot ──UDP──▶ ┌──────────────────────┐
│ udpListener │
│ │ │
│ ├──▶ Hub ───WS──▶│──▶ live viewers (read-only)
│ │ │
│ └──▶ Recorder ──▶│──▶ SQLite (zstd frames)
└──────────┬───────────┘
Player ──WS──▶ playback viewer (seek/pause/speed)
```
## Features
- **Live UDP ingest** at any rate; non-blocking hub keeps a slow client from
stalling the whole pipeline.
- **3D Three.js viewer**: orbit, zoom, pan, mobile-friendly, low-quality mode
for weak GPUs.
- **Rich HUD**: ball position/height/speed/distance to each goal, per-car boost,
speed, distance to ball, flags (DEMO / AIR / SONIC / TOUCH), packets-per-sec
and last-packet age.
- **Automatic recording** to SQLite with optional **zstd compression** (default
on). Typical compression ratio of 510× on JSON telemetry.
- **Frame-accurate playback** with **seek bar, pause, variable speed**
(0.25× 8×), keyboard shortcuts, and autoplay-to-newer.
- **Server-pushed events**: new/stopped/deleted recordings reflect in the UI
without polling.
- **Live `/api/stats`** endpoint and an in-panel stats box (uptime, packets,
drops, db size, compression ratio).
- **HTTPS via Let's Encrypt autocert** with HTTP→HTTPS redirect.
- **Basic Auth** on every endpoint (constant-time comparison).
- **Graceful shutdown** flushes the recorder before exiting.
## Example
![Live viewer with ball trail and predicted path](imgs/Screenshot_2026-05-16_00-32-01.png)
The viewer shows a detailed arena, car models (blue/orange), ball with ring indicator, boost pads with pulsing animation when active, and optional ball-trail/prediction lines. The HUD displays real-time metrics, and the side panel lists recordings and server stats.
## Download
Prebuilt, statically-linked binaries for each tagged release are published to the
Gitea generic package registry under
[`texasmade/RocketLeagueBot-Renderer`](https://git.destefano.cloud/texasmade/-/packages/generic/RocketLeagueBot-Renderer).
No build toolchain required.
```sh
# Pick your platform: linux/darwin/windows and amd64/arm64.
# Windows binaries have a .exe suffix; macOS is "darwin".
VERSION=v1.0.0
FILE=RocketLeagueBot-Renderer_linux_amd64
curl -fLO "https://git.destefano.cloud/api/packages/texasmade/generic/RocketLeagueBot-Renderer/${VERSION}/${FILE}"
chmod +x "$FILE"
./"$FILE" -password=secret -http=:8080
```
Available filenames: `RocketLeagueBot-Renderer_{linux,darwin}_{amd64,arm64}` and
`RocketLeagueBot-Renderer_windows_amd64.exe`. A `checksums.txt` is published
alongside each version — verify with:
```sh
curl -fLO "https://git.destefano.cloud/api/packages/texasmade/generic/RocketLeagueBot-Renderer/${VERSION}/checksums.txt"
sha256sum -c checksums.txt --ignore-missing
```
## Quickstart
To build from source instead:
```sh
git clone <repo>
cd RocketLeagueBot-Renderer
go build -o RocketLeagueBot-Renderer .
# Local HTTP only
./RocketLeagueBot-Renderer -password=secret -http=:8080
# Production with HTTPS (ports 80+443 must be reachable, DNS pointed at host)
sudo ./RocketLeagueBot-Renderer -password=secret -domain=example.com
```
Open the viewer at `http://localhost:8080` (or `https://example.com`). Log in
with any username and the password you set.
## Flags
| Flag | Default | Purpose |
|----------------|--------------------|--------------------------------------------------------|
| `-password` | *(required)* | Password for HTTP Basic Auth. |
| `-domain` | *(empty)* | Domain for Let's Encrypt autocert. Empty → HTTP only. |
| `-http` | `:80` | HTTP listen address (must be `:80` for ACME). |
| `-tls` | `:443` | HTTPS listen address. |
| `-udp` | `:9273` | UDP ingest address. |
| `-certdir` | `./certs` | Directory for autocert cache. |
| `-db` | `./recordings.db` | SQLite database file. |
| `-retention` | `168h` (7 days) | How long to keep recordings. |
| `-compress` | `true` | zstd-compress recorded frames. |
| `-verbose` | `false` | Log every HTTP request (including WS upgrades). |
| `-log-level` | `info` | `debug` / `info` / `warn` / `error`. |
## UDP payload
Bytes received on the UDP port are treated as **opaque** — they are recorded
verbatim (optionally compressed) and forwarded verbatim to live viewers.
The viewer expects each packet to be a JSON object compatible with
[RocketSimVis](https://github.com/ZealanL/RocketSimVis), specifically:
```jsonc
{
"ball_phys": { "pos": [x,y,z], "vel": [x,y,z], "ang_vel": [...] },
"cars": [
{
"car_id": 0, "team_num": 0,
"phys": { "pos": [x,y,z], "vel": [...], "forward": [...], "up": [...], "right": [...] },
"boost_amount": 0.0..1.0, "is_demoed": false,
"on_ground": true, "ball_touched": false
}
],
"boost_pad_states": [true, false, ...] // length 34, same order as PAD_DEFS in viewer.html
}
```
Coordinates are Unreal Units (1 uu = 1 cm). Field dimensions used by the
viewer are the official RL values (4096 × 5120 × 2044 uu).
Quick UDP smoke test (requires `nc`):
```sh
echo '{"ball_phys":{"pos":[0,0,200],"vel":[0,0,0]},"cars":[]}' | nc -u -w0 127.0.0.1 9273
```
## Playback controls
| Action | Mouse/UI | Keyboard |
|-----------------|-------------------------|-----------------------|
| Play / Pause | ⏸ / ▶ button | `Space` |
| Scrub | Drag the seek bar | `←` / `→` (±5 s) |
| Step ±1 frame | ⏮ / ⏭ buttons | `Shift+←` / `Shift+→` |
| Speed down/up | Speed dropdown | `[` / `]` |
| Stop playback | ✕ button or LIVE button | — |
| Toggle autoplay | "Auto: ON/OFF" button | — |
Speed and autoplay preferences are persisted in `localStorage`.
## API
All endpoints require HTTP Basic Auth.
- `GET /` — Viewer HTML.
- `GET /ws` — Live WebSocket stream (text JSON).
Also delivers out-of-band server events:
`{"type":"event","kind":"recording_started|stopped|deleted","recording":{...}}`.
- `GET /api/recordings?since=&limit=` — Newest-first JSON list of recordings.
`since` is RFC3339; `limit` is optional row cap.
- `DELETE /api/recordings/:id` — Delete a recording (cascades frames).
- `GET /ws/playback?id=<n>` — Playback WebSocket. Client sends:
- `{"cmd":"play"}` / `{"cmd":"pause"}` / `{"cmd":"stop"}`
- `{"cmd":"seek","ms":12345}`
- `{"cmd":"speed","value":2.0}`
Server emits:
- `{"type":"playback_start","name":...,"frames":N,"duration_ms":D}`
- `{"type":"playback_frame","offset_ms":T,"frame":i,"data":<state>}`
- `{"type":"playback_state","playing":bool,"speed":s,"position_ms":T}` (every ~250 ms)
- `{"type":"playback_end"}` / `{"error":"..."}`
- `GET /api/stats` — Process & DB observability JSON.
## Storage & tuning
The recorder buffers frames in memory and flushes to SQLite either every
**64 frames** or every **100 ms**, whichever comes first, in a single
transaction. This collapses ~120 fsyncs/sec down to ~10/sec at typical
telemetry rates.
With `-compress=true` (default), each frame is zstd-encoded individually.
Typical RocketSimVis JSON shrinks 510×. Disable with `-compress=false` if you
want raw JSON in the DB.
Idle detection automatically ends the current recording after **5 seconds**
without packets, finalizing the row (frame count + duration).
Old recordings are pruned every hour according to `-retention`.
## Security
- Always run behind HTTPS in production; use `-domain` for free Let's Encrypt
certs (ports 80 + 443 must be reachable).
- Behind a reverse proxy: ensure it forwards `Authorization` headers and
supports WebSocket upgrades on `/ws` and `/ws/playback`. Disable any
buffering on those paths.
## Dependencies
- Go 1.22+ (uses `log/slog`, `signal.NotifyContext`)
- [Fiber](https://gofiber.io/), [websocket](https://github.com/gofiber/websocket)
- [klauspost/compress](https://github.com/klauspost/compress) (zstd)
- [ncruces/go-sqlite3](https://github.com/ncruces/go-sqlite3) (pure-Go SQLite)
- [Three.js](https://threejs.org/) (loaded from CDN by the viewer)
## License
MIT.
---
**Note:** Not affiliated with Psyonix or Rocket League. For educational and
personal use only.