Starting a Dota Replay Action Decoder
I am starting a package for decoding detailed player actions from Dota
2 .dem replay files. Existing parsers like Clarity are
strong on broad match summaries, but my target is different: per-
player action sequences with rich context.
The end goal is a large-scale dataset for behavior analysis and learning hero pick/ban strategy, eventually pushing toward match result prediction from drafting plus early-game signals.
I have wanted this for a while because most public replay pipelines answer "what happened?" but not "why did this player decide to do that here?" I want the second question to be first-class.
What I want this project to do
- Process replay files at large scale (tens or hundreds of thousands of games).
- Capture per-player actions with scenario context (draft, lane, economy, vision, timeline state).
- Keep the system extensible so "action" definitions and features can evolve without rewriting decoder internals.
- Make outputs ML-friendly for Python tooling and columnar data flows.
Architecture I am building toward
The architecture is intentionally layered so low-level parsing and high-level feature extraction can evolve independently.
-
Replay ingestion: discover
.demfiles, queue jobs, apply retry and validation logic. - Decode/parsing core (Rust): parse packets and user messages, maintain entity state, timeline state, and draft state.
- Player action extraction: modular IR-to-action feature modules such as ability, movement, vision, and camera actions.
- Context enrichment: attach game phase, economy, spatial, and draft snapshot context to each action.
- Serialization/storage: write analytics-friendly Arrow/Parquet outputs (plus optional JSONL for debugging).
- Analysis/ML: build sequence datasets and train models in Python.
The IR contract (the important part)
The IR is the contract between decoding and higher-level logic. It needs to be stable, versioned, and expressive enough that future features rarely force decoder rewrites.
Replay file
-> Rust decoder/parsing core
-> IR stream (metadata, draft, ticks, entities, events, user messages)
-> action extraction modules
-> context enrichment
-> Parquet/Arrow tables
-> Python analysis + modeling
Core IR entities:
- Match metadata: replay build, result, duration
- Draft state: ordered pick/ban events by side and phase
- Timeline ticks: synchronized game-time stream
- Entity snapshots: hero/unit/building/ward deltas with stable IDs
- Game/combat events: normalized event payloads
- User messages: raw and parsed Dota-specific message payloads
Unified PlayerAction schema
Every derived event maps into a shared PlayerAction
record:
PlayerAction {
match_id
tick
time_seconds
player_slot
team_id
hero_id
action_type
action_args
context_version
}
Initial action modules include draft actions, ability casts, movement decisions, item usage, ward interactions, and camera/click patterns. These modules are independent and configurable.
How I want to store the data
The core dataset should be analytics-native and language-interoperable from day one:
matches(one row per match)players(one row per player per match)player_actions(primary table)- optional:
draft_events,combat_events,ward_events - optional heavy table:
ticks
How I will keep it extensible
-
Version everything:
decoder_version,ir_schema_version, andfeature_set_version. - Prefer additive schema changes with optional fields to reduce breakage.
- Register feature modules through config so new features can be plugged in without touching the core parser.
- Keep Rust core and Python analysis loosely coupled through Arrow/Parquet interoperability.
Tech stack choices
- Rust: replay decoding, IR generation, and feature extraction for performance and safety.
- Parquet/Arrow: storage and transport format for efficient analytics and interop.
- Python: orchestration, analysis, and modeling iteration speed.
Per-match processing flow
- Ingestion queues a replay path.
- Rust decoder parses
.deminto IR entities. - Feature modules emit
PlayerActionrecords. - Enrichers attach context to each action.
- Serializer writes table outputs (Parquet/Arrow).
- Python notebooks/scripts train and evaluate models.
Modeling direction: draft to result
I will start with draft-only baselines and then add early-game action-context features to quantify predictive lift. This should make it possible to separate what is explained by composition from what is explained by player decisions in specific scenarios.
Success is not just accuracy; interpretability matters too. I want to identify which pick/ban patterns and early actions move win probability the most.
Next step is implementation notes and first real outputs from replay batches. Once the pipeline is stable, I can compare models trained on draft-only inputs versus draft + player-action context and measure the gap directly.