WARNING: This is an early prototype. The API and features may change significantly in future releases.
Julia bindings for the peppi Slippi replay parser, built using Apache Arrow and jlrs
@TODO Publish Yggdrasil Peppi.jl Pkg
@TODO Rename _end and GameEnd to stop and GameStop
@TODO Use Serde.jl instead JSON.jl (Benchmark Comparison)
@TODO DuckDB rewrite over Arrow (document tradeoffs and technical debt)
using Pkg
Pkg.add(url="https://github.com/your-repo/peppi-jl")To build from source, first install Rust. Then:
cd julia_app
cargo build --releasepeppi-jl exposes one function:
read_slippi(path; skip_frames=false)
This parses a Slippi replay file (.slp) into a Game object.
Frame data is stored as a struct-of-arrays for performance, using Arrow. So to get the value of an attribute foo.bar for the nth frame of the game, you'd write game.frames.foo.bar[n] instead of game.frames[n].foo.bar. See the code example below.
You can do many other things with Arrow arrays, such as converting them to regular Julia arrays. See the Arrow.jl docs for more.
Also see the Slippi replay spec for detailed information about the available fields and their meanings.
using Peppi
game = read_slippi("path/to/game.slp")
# Metadata
game.metadata
# Dict{String, Any} with 4 entries:
# "startAt" => "2018-06-22T07:52:59Z"
# "lastFrame" => 5085
# "players" => Dict{String, Any}("1"=>Dict{String, Any}("characters"=>Dict{String, Any}("1"=>5209)), "0"=>…)
# "playedOn" => "dolphin"
# Game start info
game.start
# GameStart(slippi=Slippi(version=(1, 0, 0)), ...)
game.start.slippi.version
# (1, 0, 0)
game.start.stage
# 8 (Yoshi's Story)
game.start.players[1].character
# 9 (Marth)
# Game end info
game._end
# GameEnd(method=RESOLVED, lras_initiator=nothing, players=nothing)
game._end.method
# RESOLVED::EndMethod = 3
# Frame data (struct-of-arrays via Arrow)
game.frames.ports[1].leader.pre.position.x[1]
# -42.0f0
game.frames.ports[1].leader.post.percent[1000]
# 45.0f0
# Access P2's position at frame 1001
game.frames.ports[2].leader.pre.position.x[1001]
# 42.195167541503906Read a Slippi replay file and return a Game object.
Arguments:
path::String: Path to the.slpfileskip_frames::Bool=false: Iftrue, skip parsing frame data (faster for metadata-only reads)
Returns:
Game: Parsed game data includingstart,_end,metadata, andframes
struct Game
start::GameStart # Game start information
_end::GameEnd # Game end information
metadata::Dict # Replay metadata
frames::Frame # Frame data (Arrow arrays)
endContains game configuration including:
slippi::Slippi- Slippi version infostage::Int- Stage IDplayers::Tuple{Vararg{Player}}- Player informationtimer::Int- Game timer settingrandom_seed::Int- Random seed- And more...
struct GameEnd
method::EndMethod # How the game ended
lras_initiator::Union{Port, Nothing} # Who quit (if applicable)
players::Union{Tuple{Vararg{PlayerEnd}}, Nothing}
endstruct Player
port::Port # P1, P2, P3, or P4
character::Int # Character ID
type::PlayerType # HUMAN, CPU, or DEMO
stocks::Int # Starting stocks
costume::Int # Costume/color index
# ... and more
endPort:P1,P2,P3,P4PlayerType:HUMAN,CPU,DEMOEndMethod:UNRESOLVED,TIME,GAME,RESOLVED,NO_CONTESTLanguage:JAPANESE,ENGLISHDashBack:DASHBACK_UCF,DASHBACK_ARDUINOShieldDrop:SHIELDDROP_UCF,SHIELDDROP_ARDUINO
Frame data uses Arrow arrays for efficient columnar access:
frames
├── id # Frame index
├── start # Frame start data
│ └── random_seed
├── end # Frame end data
│ └── latest_finalized_frame
├── ports[] # Per-port data (up to 4 players)
│ └── leader # Main character data
│ ├── pre # Pre-frame update
│ │ ├── position.x, position.y
│ │ ├── joystick.x, joystick.y
│ │ ├── state, direction
│ │ └── ...
│ └── post # Post-frame update
│ ├── position.x, position.y
│ ├── percent, stocks
│ ├── state, character
│ └── ...
└── items[] # Item data
MIT