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
The 3D replay viewer orbiting above a corridor: twelve heroes with team outlines, health bars and ability effects, moving through a lit model of the map, seconds before the first kill.

3DThe match rebuilt in 3D: every hero, ability and health bar where the replay puts it, in a lit model of the map.

Replay Studio, on round 1 of a professional match. One player name is blurred.

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

  • Decoder
    Rust: 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.
  • Matching
    A 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 sight
    Native ray casting against the map’s collision for every aim sample of every player, so a review can say who could see whom.
  • Replay Studio
    The 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.
  • Bundles
    Each match becomes a manifest plus one checksummed, gzipped buffer of typed arrays, so the viewer reads it with zero copies and seeks instantly.

04Decisions

DecisionInstead ofBecause
Freeze a Python reference, then port to RustOptimizing the PythonThe port can be proven identical, cell for cell, before it replaces anything.
Judge every rule on held-out playersTuning on every matchThe accuracy numbers hold up on matches the rules never saw. Weak spots stay visible instead of fitted away.
Typed-array bundles with a checksumJSON per matchZero-copy loading and instant seeking, and the viewer refuses bytes from a different build.

05Results

Time to turn one match into a viewer bundle
MatchLengthPythonRust
Klyntar, Convergence11.4 min241 s0.66 s
Hell’s Heaven, Domination22.8 min656 s1.84 s
Central Park, Convergence18.9 min≥ 325 s1.29 s
Spider-Islands, Convoy19.7 min≥ 399 s1.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.
Decoded totals against the official scoreboard, 648 held-out players
StatExactNote
Deaths647 / 64899.85%
Final hits644 / 64899.38%
Healing536 / 64882.7% exact; 442 of 468 nonzero within 1%
Damage taken95 / 648The 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.
Every player’s path through the first round, drawn as thin lines. The attackers’ red paths enter from the right and the defenders’ light paths from the lower left, and they tangle around the capture point.
  • Attackers
  • Defenders
  • Capture point
  • A kill
Twelve players over five minutes from the first spawn, two samples a second. Red attacks, light defends, the ring is the capture point.
The viewer's playback controls and timeline for the whole match: numbered team fights, kill ticks, ultimate markers and the two rounds as bands.
The timeline: team fights numbered, kills and ultimates marked, both rounds as bands. Seeking anywhere is instant.
  1. Header
  2. Containers
  3. Frames
  4. Packets
  5. Actor channels
  6. Properties
  7. State over time
  8. Bundle
  9. Viewer