Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a784cce
WIP: Valorant replay viewer on a native vrfkit decoder
SunkenInTime Oct 2, 2026
a2ff1a8
WIP: harden replay reading, finish ability catalog, glossary
SunkenInTime Oct 2, 2026
2c67be7
Add clean-room Selkie decoder for replay archives
SunkenInTime Oct 2, 2026
87ba706
Harden the replay viewer after review
SunkenInTime Oct 2, 2026
ff8c1f6
Finish the replay decoder: vitals, utility shapes, release wiring
SunkenInTime Oct 2, 2026
f4fed86
Stand replay cones on the replay's level; keep playback smooth
SunkenInTime Oct 2, 2026
0c1e24d
Merge remote-tracking branch 'origin/main' into t3code/valorant-repla…
SunkenInTime Oct 2, 2026
ee0751f
Drop an unneeded import in the capture test
SunkenInTime Oct 2, 2026
0eb3383
Address review: bounded decoding, atomic control handle, release notices
SunkenInTime Oct 2, 2026
a3452aa
Replace kept replays in one step; bump the decoder version
SunkenInTime Oct 2, 2026
b80af76
Replay: smokes block sight, utility arrives and leaves, playback at 1…
SunkenInTime Oct 2, 2026
d533636
Review fixes: smoke shadows stay put, cones stand on their agent
SunkenInTime Oct 2, 2026
74ef9fb
Merge the cone runtime fixes; cut replay floors on the worker
SunkenInTime Oct 3, 2026
e44254c
Replay: cut cones all the way round, face them when painting
SunkenInTime Oct 3, 2026
e09a4f3
Merge the cone review fixes
SunkenInTime Oct 3, 2026
46be9bd
Merge the second round of cone review fixes
SunkenInTime Oct 3, 2026
1c9378b
Merge merged-outline loader and faster floor shadows
SunkenInTime Oct 3, 2026
750571b
Merge branch 't3code/cone-runtime' into t3code/valorant-replay-overhaul
SunkenInTime Oct 3, 2026
e22b0c8
Merge branch 't3code/cone-runtime' into t3code/valorant-replay-overhaul
SunkenInTime Oct 3, 2026
ddd08d6
Merge branch 't3code/cone-runtime' into t3code/valorant-replay-overhaul
SunkenInTime Oct 3, 2026
60a014a
Merge branch 't3code/cone-runtime' into t3code/valorant-replay-overhaul
SunkenInTime Oct 4, 2026
7d148aa
Block replay sight with each smoke as wide as it is drawn
SunkenInTime Oct 5, 2026
9d84253
Fade a replay ability's range, not its icon
SunkenInTime Oct 5, 2026
50a3c36
Show a back arrow in an editor opened from a replay capture
SunkenInTime Oct 5, 2026
9e67b93
Merge main into t3code/valorant-replay-overhaul
SunkenInTime Oct 5, 2026
886bf5a
Merge remote-tracking branch 'origin/main' into t3code/valorant-repla…
SunkenInTime Oct 5, 2026
1f8a810
Pin Split's crane spot to the floor default #238 gives it
SunkenInTime Oct 5, 2026
58f9f00
Start a replay capture's strategy in the local library
SunkenInTime Oct 5, 2026
977472c
Set the replay window title in the body type role
SunkenInTime Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,55 @@ env:
PUB_CACHE: ${{ github.workspace }}/.pub-cache

jobs:
native-replay:
runs-on: ubuntu-latest
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Cargo build scripts run here: no write token, none left in .git.
permissions:
contents: read
defaults:
run:
working-directory: native/replay
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

# The version in native/replay/rust-toolchain.toml.
- name: Install Rust
uses: dtolnay/rust-toolchain@1.96.1
with:
components: clippy, rustfmt

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
native/replay/target
key: ${{ runner.os }}-cargo-replay-${{ hashFiles('native/replay/Cargo.lock', 'native/replay/rust-toolchain.toml') }}
restore-keys: |
${{ runner.os }}-cargo-replay-

# oozextract is GPL-derived: it must never enter the build
# (docs/adr/0006-replay-decoder.md).
- name: Keep oozextract out
run: |
if grep -q oozextract Cargo.lock; then
echo "::error::oozextract is in native/replay/Cargo.lock"
exit 1
fi

- name: Clippy
run: |
cargo clippy --locked --all-targets -- -D warnings
cargo clippy --locked --all-targets --features dev-vectors -- -D warnings

- name: Test
run: |
cargo test --locked
cargo test --locked --features dev-vectors

# Decides which lanes a run needs. Every lane runs for manual runs, PRs into
# main, PRs that change CI itself, and PRs whose files can't be listed.
# Pushes run every lane but the desktop builds: the release runs the upgrade
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ jobs:
shell: pwsh
run: fvm install

# windows/CMakeLists.txt builds the replay decoder (native/replay) with
# cargo, on the version in native/replay/rust-toolchain.toml.
- name: Install Rust
uses: dtolnay/rust-toolchain@1.96.1
with:
components: clippy, rustfmt

- name: Build Windows Release Binaries
shell: pwsh
env:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-store.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ jobs:
shell: pwsh
run: powershell -ExecutionPolicy Bypass -File scripts/bump_version.ps1 -Bump ${{ inputs.version_bump }}

# windows/CMakeLists.txt builds the replay decoder (native/replay) with
# cargo, on the version in native/replay/rust-toolchain.toml.
- name: Install Rust
uses: dtolnay/rust-toolchain@1.96.1
with:
components: clippy, rustfmt

- name: Build Store Package
shell: pwsh
env:
Expand Down
17 changes: 17 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ into lineup groups.
Icarus's zip-based strategy interchange format for import/export of whole
strategies. Unrelated to video export.
_Avoid_: archive (ambiguous with library backups)

**Replay**:
A Valorant match recording (a `.vrf` file) that Valorant downloads from a
player's career page. Icarus plays it back on the map. Replays are files, not
part of the library; decoding one only fills a rebuildable cache.
_Avoid_: demo, VOD, recording

**Perspective**:
The team a replay is watched from. Its players are allies, the other team's
are enemies, and its side each round decides which way up the map is drawn.
_Avoid_: point of view, team view

**Capture**:
Saving the replay's current moment as a page of a strategy: players, their
facing, and the utility on the map at that instant.
_Avoid_: snapshot, export

**Op**:
One queued change to cloud data. An op lands when the server accepts it.
Its op ID names that exact change. Changing the intended work creates a new op
Expand Down
48 changes: 48 additions & 0 deletions docs/adr/0006-replay-decoder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 6. Valorant replays are decoded by a native library built on vrfkit

Status: accepted, 2026-10-01

## Context

Valorant writes match replays as `.vrf` files: an Unreal local replay, its
replication payloads Oodle-compressed and scrambled by a transform Riot
rotates every patch or two. We spent June and July reverse engineering the
format in Node (`codex/valorant-replay-file-reverse-engineering`). That work
proved the layers and decoded movement for build 13.00, but it ran 30 to 300
seconds per match, knew three builds, and shelled out to Node, which no user
has installed.

Since then two open-source parsers have caught up and track every patch:
vrfkit (Rust, Apache-2.0, builds 11.06 to 13.06) and Michel Giehl's
ValorantReplayParser (C#, MIT, which vrfkit derives from). vrfkit decodes one
of our 13.00 replays in about a second with no decode errors.

Every open-source Oodle decoder (powzix/ooz and its ports: ooz-wasm,
oozextract, OozSharp, oozle) is GPL-3.0 or derived from it, including the ones
labelled MIT. Valorant only uses a narrow Oodle subset: Mermaid/Selkie blocks
with raw, un-entropy-coded streams.

## Decision

- `native/replay` is a Rust library, loaded over FFI like `native/height`.
It depends on vrfkit's library crates pinned to a release tag, with the
`oodle` feature off, and vendors the CLI's packet pass with attribution.
- Its Oodle decoder is our own, written clean-room for the Selkie subset from
a facts-only format spec. ooz-derived code never enters the repo or the
build. Any block outside the subset fails loudly.
- The library emits Valorant facts only (subjects, agent UUIDs, class paths,
game-world centimetres) in the format described in
[`docs/replay-format.md`](../replay-format.md). Mapping those facts onto
Icarus agents, abilities and map coordinates is Dart's job, so the decoded
document stays readable by code that has never seen our widgets.
- A replay from an unknown build is refused, never guessed. Keeping up with
patches means bumping the vrfkit tag.

## Consequences

- Building the desktop app needs a Rust toolchain on Windows and macOS.
Unit tests that do not touch decoding stay pure Dart.
- New Valorant patches reach users when vrfkit registers the build and we
ship a release with the bumped tag. Until then that patch's replays show a
clear "not supported yet" state.
- The same crate can compile to WASM if the web app ever decodes replays.
Loading
Loading