# subtr-actor
[](https://github.com/rlrml/subtr-actor/actions?query=workflow%3A%22main%22) [](https://docs.rs/subtr-actor) [](https://crates.io/crates/subtr-actor) [](https://pypi.org/project/subtr-actor-py/) [](https://www.npmjs.com/package/rl-replay-subtr-actor) 
`subtr-actor` turns Rocket League replay files into data that is easier to work with than the raw actor graph exposed by [`boxcars`](https://docs.rs/boxcars/).
It supports two main workflows:
- structured replay data for inspection, export, and analysis
- dense numeric arrays for ML and other downstream pipelines
The core crate is written in Rust, with bindings for Python and JavaScript.
## Packages
- Rust: [`subtr-actor`](https://crates.io/crates/subtr-actor)
- Python: [`subtr-actor-py`](https://pypi.org/project/subtr-actor-py/)
- JavaScript / WASM: [`rl-replay-subtr-actor`](https://www.npmjs.com/package/rl-replay-subtr-actor)
## What It Gives You
- A higher-level replay model built from `boxcars`
- Frame-by-frame structured game state via `ReplayDataCollector`
- Configurable numeric feature extraction via `NDArrayCollector`
- Frame-rate resampling with `FrameRateDecorator`
- The same replay-processing model across Rust, Python, and JS
## Installation
### Rust
```toml
[dependencies]
subtr-actor = "0.1.15"
```
### Python
```bash
pip install subtr-actor-py
```
### JavaScript
```bash
npm install rl-replay-subtr-actor
```
## Quick Start
### Rust: get structured replay data
```rust
use boxcars::ParserBuilder;
use subtr_actor::ReplayDataCollector;
fn main() -> anyhow::Result<()> {
let data = std::fs::read("example.replay")?;
let replay = ParserBuilder::new(&data)
.must_parse_network_data()
.on_error_check_crc()
.parse()?;
let replay_data = ReplayDataCollector::new()
.get_replay_data(&replay)
.map_err(|e| e.variant)?;
println!("{}", replay_data.as_json()?);
Ok(())
}
```
### Rust: build an ndarray for ML
```rust
use subtr_actor::*;
fn main() -> anyhow::Result<()> {
let data = std::fs::read("example.replay")?;
let replay = boxcars::ParserBuilder::new(&data)
.must_parse_network_data()
.on_error_check_crc()
.parse()?;
let mut collector = NDArrayCollector::new(
vec![
InterpolatedBallRigidBodyNoVelocities::arc_new(0.003),
CurrentTime::arc_new(),
],
vec![
InterpolatedPlayerRigidBodyNoVelocities::arc_new(0.003),
PlayerBoost::arc_new(),
PlayerAnyJump::arc_new(),
],
);
FrameRateDecorator::new_from_fps(30.0, &mut collector)
.process_replay(&replay)
.map_err(|e| e.variant)?;
let (meta, array) = collector.get_meta_and_ndarray().map_err(|e| e.variant)?;
println!("rows={} cols={}", array.nrows(), array.ncols());
println!("players={}", meta.replay_meta.player_stats.len());
Ok(())
}
```
### Python
```python
import subtr_actor
meta, ndarray = subtr_actor.get_ndarray_with_info_from_replay_filepath(
"example.replay",
global_feature_adders=["BallRigidBody"],
player_feature_adders=["PlayerRigidBody", "PlayerBoost", "PlayerAnyJump"],
fps=10.0,
dtype="float32",
)
print(ndarray.shape)
print(meta["column_headers"]["player_headers"][:5])
```
### JavaScript
```javascript
import init, { get_ndarray_with_info } from 'rl-replay-subtr-actor';
await init();
const replayData = new Uint8Array(await fetch('example.replay').then((r) => r.arrayBuffer()));
const result = get_ndarray_with_info(
replayData,
['BallRigidBody'],
['PlayerRigidBody', 'PlayerBoost', 'PlayerAnyJump'],
10.0
);
console.log(result.shape);
console.log(result.metadata.column_headers.player_headers.slice(0, 5));
```
## Core Concepts
### `ReplayDataCollector`
Use this when you want a serializable, frame-by-frame representation of the replay without dealing directly with the low-level actor graph.
### `NDArrayCollector`
Use this when you want numeric features in a 2D matrix. You choose which global and player features to include, either by constructing feature adders directly in Rust or by referring to them by string names in bindings.
### `FrameRateDecorator`
Use this to resample replay processing to a fixed FPS before collecting data.
## Common Feature Names
These are useful when working through the Python or JavaScript bindings:
- Global: `BallRigidBody`, `CurrentTime`, `SecondsRemaining`
- Player: `PlayerRigidBody`, `PlayerBoost`, `PlayerAnyJump`, `PlayerDoubleJump`
`PlayerBoost` is exposed in raw replay units (`0-255`), not percentage.
## Documentation
- Rust API docs: <https://docs.rs/subtr-actor>
- Python package README: [python/README.md](./python/README.md)
- JavaScript package README: [js/README.md](./js/README.md)
- Release notes and process: [RELEASING.md](./RELEASING.md)
## Development
```bash
just build
just test
just fmt
just clippy
```
Bindings:
```bash
just build-python
just build-js
```
## License
MIT