Release v1.0.0 #major
release / goreleaser (push) Failing after 51s

This commit is contained in:
2026-06-02 23:12:36 -05:00
parent 276981a7df
commit d5e65fbb03
17 changed files with 3447 additions and 0 deletions
+185
View File
@@ -1,2 +1,187 @@
# 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.
## Quickstart
```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.