subtr-actor
▶ Try the live stats player — watch a real replay play back and stats accumulate frame-by-frame, right in your browser.
subtr-actor turns raw boxcars replay data into
higher-level game state, derived replay events, structured frame payloads, and
dense numeric features for analytics and ML workflows.
- Higher-level game state modeled from the raw actor graph
- Frame-by-frame structured data ready for JSON export and playback UIs
- Dense numeric feature matrices for ML, built from a string-addressable feature registry
- Derived events and cumulative stats — touches, boost pickups, dodge refreshes, goals, demolishes, and more
- One pipeline, three languages — the same Rust core drives the Python and JavaScript/WASM bindings
Processing model
ReplayProcessorwalks the replay's network frames, models actor state, and tracks derived replay events such as touches, boost pad pickups, dodge refreshes, goals, player stat events, and demolishes.Collectoris the core extension point. Collectors observe the replay frame by frame and can either process every frame or control sampling viaTimeAdvance.ReplayProcessor::process_alllets multiple collectors share a single replay pass when you want to build several outputs at once.FrameRateDecoratorandCallbackCollectorprovide lightweight utilities for downsampling a collector or attaching side-effectful hooks such as progress reporting and debugging.
Primary output layers
ReplayDataCollectorbuilds a serde-friendly replay payload with frame data, replay metadata, and derived event streams suitable for JSON export and playback UIs.NDArrayCollectoremits a densendarray::Array2with replay metadata and headers. It supports both explicit feature adders and the string-based registry exposed throughNDArrayCollector::from_stringsandNDArrayCollector::from_strings_typed.StatsCollectoraccumulates graph-backed replay statistics as a module-keyed dynamic payload suitable for builtin module selection and JSON export.StatsTimelineEventCollectoraccumulates graph-backed replay statistics as event streams plus lightweight frame scaffolding. This is the preferred timeline export when callers do not need to serialize full per-frame partial sums.StatsTimelineCollectorpreserves the legacy full snapshot timeline form (ReplayStatsTimeline) for parity checks and compatibility.
Stats and exports
The stats module houses analysis calculators, graph nodes, stat
event calculators, and the labeled stat-aggregation types
(LabeledCounts, LabeledFloatSums) consumed by the stats collectors.
Architecture / module map
Read top-down — each module's own documentation expands on the summary here and links to the collections of implementations it contains.
- [
processor] — the replay-walking core. [ReplayProcessor] models actor state fromboxcarsnetwork frames and tracks derived events, applying a sequence of per-frame state updaters. - [
collector] — the output layer. The [Collector] trait is the extension point; built-in collectors are [ReplayDataCollector] (structured frames), [NDArrayCollector] ([numeric features][collector::ndarray]), and the stats-timeline collectors ([collector::stats]). - [
stats] — the analysis layer. A dependency graph of [analysis nodes][stats::analysis_graph] wraps [gameplay-event calculators][StatsEvent] that detect mechanics; results land in accumulators and the [exported stat-field model][stats::export]. - [
replay_model] / [replay_meta] — the serde-friendly higher-level game state and replay metadata produced for export and playback UIs. - [
interop] — bindings-facing helpers shared by the Python and JavaScript/WASM wrappers (e.g. the replay-player manifest). - [
util] — geometry, search, and small data-structure helpers used throughout the crate.
Where to find collections of implementations
Several parts of the crate are large families of similar types. Each has a catalog in its module documentation, and the shared trait's Implementors list is a second way to browse them:
| Collection | Module | Shared trait / registry |
|---|---|---|
| Gameplay-event calculators | [stats::analysis_graph] |
[StatsEvent] |
| Analysis-graph nodes | [stats::analysis_graph] |
AnalysisNode |
| Stat accumulators | [stats::accumulators] |
(plain accumulation structs) |
| Exported stat fields | [stats::export] |
[StatFieldProvider] |
| NDArray feature adders | [collector::ndarray] |
[FeatureAdder] family + string registry |
| Processor state updaters | [processor] |
(impl ReplayProcessor methods) |
In-depth guides
Longer prose guides are rendered into the API docs under [guides]:
- [
guides::calculators_and_analysis_nodes] — the stats runtime DAG layout. - [
guides::stat_confidence] — how to read exported-stat confidence levels. - [
guides::replay_format_evolution] — replay-format changes that matter to parsing.
Examples
Collect structured replay data
use ParserBuilder;
use ReplayDataCollector;
let bytes = read.unwrap;
let replay = new
.must_parse_network_data
.on_error_check_crc
.parse
.unwrap;
let replay_data = new.get_replay_data.unwrap;
println!;
println!;
Build a sampled feature matrix
use ParserBuilder;
use ;
let bytes = read.unwrap;
let replay = new
.must_parse_network_data
.on_error_check_crc
.parse
.unwrap;
let mut collector = from_strings
.unwrap;
new_from_fps
.process_replay
.unwrap;
let = collector.get_meta_and_ndarray.unwrap;
println!;
println!;
Export compact event-backed stats timeline
use ParserBuilder;
use StatsTimelineEventCollector;
let bytes = read.unwrap;
let replay = new
.must_parse_network_data
.on_error_check_crc
.parse
.unwrap;
let timeline = new
.get_replay_stats_timeline_scaffold
.unwrap;
println!;
let rush_events = timeline
.events
.events
.iter
.filter
.count;
println!;
Packages
- Rust:
subtr-actor - Python:
subtr-actor-py - JavaScript / WASM bindings:
@rlrml/subtr-actor - JavaScript replay player:
@rlrml/player - JavaScript stats player:
@rlrml/stats-player(see the live demo above)
Installation
Rust
Python
# or, with uv:
# or, with Poetry:
JavaScript
JavaScript player
Using the bindings
The Rust examples above carry over to the bindings: you choose feature adders by
name and get back replay metadata plus a numeric array. PlayerBoost is exposed
in raw replay units (0-255), not a percentage.
Python
, =
JavaScript
import init from "@rlrml/subtr-actor";
await ;
const replayData = ;
const validation = ;
if
const result = ;
console.log;
console.log;
Common feature names
These string identifiers select feature adders through the Python and JavaScript bindings:
- Global:
BallRigidBody,CurrentTime,SecondsRemaining - Player:
PlayerRigidBody,PlayerBoost,PlayerAnyJump,PlayerJump,PlayerEvent:touch
Analysis-backed player event indicators use PlayerEvent:<event_name> and emit
1 for a sampled frame when that player has a new event, otherwise 0.
Documentation
- Rust API docs: https://docs.rs/subtr-actor
- Changelog: CHANGELOG.md
- Python package README: python/PYTHON-README.md
- JavaScript package README: js/README.md
- JavaScript player README: js/player/README.md
- Stat definitions: docs/event-definitions.md
- Statistic confidence: docs/stat-confidence.md
- Release notes and process: docs/RELEASING.md
Development
These just recipes enter the flake dev shell, so they use the Rust toolchain
from nix develop instead of any older cargo/rustc on your ambient PATH.
Bindings:
just build-js builds the repo-local bundler target into js/pkg. To build the web-target package that matches npm publish, run npm --prefix js install once and then npm --prefix js run build.
The crate-level docs in src/lib.rs are the source of truth for the overview
section above. Run just readme after editing them to regenerate this file;
just check fails if the two drift apart.
License
MIT