Selected work / 03Independent
Rivals Vision
Marvel Rivals replays, decoded and rebuilt into a match you can scrub, measure and review.
- Role
- Creator
- Since
- Sep 2026
- Stack
- Rust, Python, TypeScript, React, three.js, WebGL
- Status
- Working, private
3DThe match rebuilt in 3D: every hero, ability and health bar where the replay puts it, in a lit model of the map.
First personAny player’s point of view, at any moment of the match.
Sight linesWho could see whom. Green is clear, amber partly blocked, red blocked by the map’s collision.
2DTop-down, with trails, damage, ultimates and kills over a timeline of every team fight.
01The problem
A replay file holds an entire match, but only in a form the game can read: Unreal network-demo data, the match’s own network traffic, with no spec for the game’s types.
Rivals Vision turns it into data that can be scrubbed, measured and reviewed: where every player was, what they could see, how each fight went and why each death happened.
02Why it was hard
01 · Bits, not bytes
Packets are bit-packed
Frames hold packets, packets hold bunches for actor channels, and property updates are read bit by bit against layouts the game defines.
02 · No ground truth
Only the scoreboard is official
Every rule for deaths, final hits or healing had to be checked against the end-of-match scoreboard, on players it was never tuned on.
03 · Speed
Minutes per match
The Python reference took 4 to 11 minutes to turn one match into a viewer bundle.
03What I built
- DecoderRust: header, zstd containers, frames, packets, actor channels and property updates, then every state lane in a single pass: players, aim, combat, the kill feed, objectives, phases and abilities.
- MatchingA frozen Python reference, and a Rust port that has to reproduce it exactly: every table cell, bit for bit, and every published file byte for byte, on four full matches. Corrupted files are tested too.
- Line of sightNative ray casting against the map’s collision for every aim sample of every player, so a review can say who could see whom.
- Replay StudioThe viewer. A WebGL top-down map with instant seeking, and a three.js 3D view with free, orbit, follow and first-person cameras, sight lines and heights. The game’s levels are exported to glTF and lit offline.
- BundlesEach match becomes a manifest plus one checksummed, gzipped buffer of typed arrays, so the viewer reads it with zero copies and seeks instantly.
04Decisions
| Decision | Instead of | Because |
|---|---|---|
| Freeze a Python reference, then port to Rust | Optimizing the Python | The port can be proven identical, cell for cell, before it replaces anything. |
| Judge every rule on held-out players | Tuning on every match | The accuracy numbers hold up on matches the rules never saw. Weak spots stay visible instead of fitted away. |
| Typed-array bundles with a checksum | JSON per match | Zero-copy loading and instant seeking, and the viewer refuses bytes from a different build. |
05Results
| Match | Length | Python | Rust |
|---|---|---|---|
| Klyntar, Convergence | 11.4 min | 241 s | 0.66 s |
| Hell’s Heaven, Domination | 22.8 min | 656 s | 1.84 s |
| Central Park, Convergence | 18.9 min | ≥ 325 s | 1.29 s |
| Spider-Islands, Convoy | 19.7 min | ≥ 399 s | 1.52 s |
Method:
bench --stage full, one core, both on the same 4-vCPU VM. The Rust output matches the Python reference cell for cell on all four.| Stat | Exact | Note |
|---|---|---|
| Deaths | 647 / 648 | 99.85% |
| Final hits | 644 / 648 | 99.38% |
| Healing | 536 / 648 | 82.7% exact; 442 of 468 nonzero within 1% |
| Damage taken | 95 / 648 | The weak one, and still open |
Method: decoded sums compared with each player’s scoreboard on 648 players the rules were never tuned on. Candidate rules, not proofs.
- 121.6M
- sight-line rays over 68 matches, a median of 3.6 s a match
- 43.7 s
- what the JavaScript version needed per match, for fight windows alone
- 86–141
- frames a second in the 3D viewer at 2560×1440
Method: line of sight on a 4-core VM, medians over 68 matches. Viewer frame rates in headless Chrome on an RX 9070 XT, over 200 frames with GPU timer queries; headless Chrome caps near 141.
06Evidence
The same round, drawn straight from the decoded data in your browser. Press play or drag the timeline.
- Attackers
- Defenders
- Capture point
- A kill
- Header
- Containers
- Frames
- Packets
- Actor channels
- Properties
- State over time
- Bundle
- Viewer
Next
Frontier, 1899