Turn Dota 2 replays into analysis-ready Python data.
Parse Source 2 .dem files offline into typed models, pandas DataFrames,
JSON, Parquet, and interactive HTML reports.
Documentation · Quickstart · API reference · Changelog
Install
Gem requires Python 3.10 or newer.
pip install gem-dota
Using another package manager? Run uv add gem-dota or poetry add gem-dota.
Parquet export additionally requires pyarrow or fastparquet; see the
DataFrame and export reference
for current engine notes.
Quickstart
import gem
match = gem.parse("match.dem")
print(f"Score: {match.radiant_score}–{match.dire_score}")
for player in match.players:
hero = gem.constants.hero_display(player.hero_name)
print(f"{hero:20} {player.kills}/{player.deaths}/{player.assists} {player.net_worth:,} NW")
# Convert the same structured result to JSON.
json_payload = gem.to_json(match, indent=2)
Need tables instead?
frames = gem.parse_to_dataframe("match.dem")
players = frames["player_summary"] # one row per player
series = frames["player_timeseries"] # sampled gold/XP/net worth
positions = frames["positions"]
combat = frames["combat_log"]
Or use the CLI:
python -m gem match.dem
python -m gem match.dem --format json --output match.json
python -m gem batch replays/ --format parquet --output out/ --workers 4
Why Gem?
| 🐍 Python-native Typed match objects plug directly into notebooks, pandas, ML pipelines, and ordinary Python code. |
🔎 Replay-first Analyze local replays without depending on third-party match-history availability. |
| ⚔️ Full-match context Draft, combat, economy, vision, movement, objectives, items, chat, and fights in one model. |
📦 Flexible outputs Work with dataclasses, DataFrames, JSON, Parquet, batch exports, or a self-contained HTML report. |
Gem is named after the Gem of True Sight: it reveals the structured match state hidden inside dense replay bytes. The parser is an independent Python implementation, cross-checked against Manta, Clarity, the OpenDota parser, and replay-derived validation fixtures.
Facts, not verdicts. Gem reports what the replay records: who was where, what they did, what it cost and what happened next. It doesn't score plays or decide whether a Roshan was "converted"; that interpretation is yours to build on top. Gem 0.12 deprecates the older helpers that did this, and 0.13 removes them.
Match reports
Gem can turn a parsed replay into a self-contained interactive report with overview, combat, laning, farming, fight, Roshan, vision, economy, draft, and movement views.
Interactive HTML report generated from a real .dem replay.
|
Vision — scrub through observer and sentry ward activity. |
Fights — step through positions, then inspect damage, abilities, and reveals. |
from gem.reports import fetch_match_icons, write_html_report
assets = fetch_match_icons(match) # downloads the hero/item icons this match needs
write_html_report(match, "match-report.html", assets=assets)
Hero and item icons are Valve's artwork, so Gem doesn't ship them. fetch_match_icons
downloads the ones a match needs into a local cache; write_html_report itself never
downloads, and shows a hero's name where its icon is missing.
examples/match_report.py fetches the icons for you (--offline skips this). See the
asset-cache guide
and the report API for details.
What you get
| Domain | Examples |
|---|---|
| Match and players | Scores, winner, duration, teams, K/D/A, level, GPM/XPM, final net worth |
| Draft and objectives | Picks/bans, towers, barracks, Roshan, Aegis, Tormentor, building status |
| Combat | Normalized combat log, damage/healing, kills, ability and item usage, fights |
| Economy | Gold, XP, net-worth and minute-aligned advantage curves, purchases, per-player gold ledger, exact buyback costs |
| Map state | Player positions, map regions (river, halves, lotus pools), neutral camps, lane heatmaps, wards, smoke groups, courier snapshots |
| Items | Final inventories, neutral-item finds, consumed upgrades, Roshan drops and banner plants |
| Lookups | Position, net worth and ability level at a tick, nearby heroes, the fight at a tick, hero visibility, smoke lifecycles and first fights, the Aegis lifecycle |
| Exports | DataFrames, JSON, Parquet, multi-replay processing, interactive HTML reports |
Useful entry points include:
gem.parse()→ParsedMatchgem.parse_to_dataframe()/gem.to_json()/gem.to_parquet()gem.parse_many*()for parallel replay batchesgem.find_player(),gem.position_at_tick(), andgem.fight_at_tick()gem.region_of()to label a position as river, either half, or a lotus poolgem.fetch_replay()for OpenDota/Valve replay download and decompression
For current complete replays, Gem's minute curves are validated against OpenDota's
effective sampling boundaries. Embedded MatchDetails data enables exact postgame
duration, damage, healing, GPM, and XPM values. Older or incomplete replays use
replay-derived fallbacks. Fields whose absence is meaningful—such as consumed-upgrade
flags—remain None; other outputs use the documented defaults for their field type.
Documentation
The hosted documentation covers both the public API and the replay format itself:
- Getting started
- Architecture
- Parser internals
- CLI reference
- Parser performance
- Map regions and camps: how Gem's map annotation was checked against the replays
- Experimental analysis
New to replay internals? Start with the Bits & Bytes Primer, then continue into the parser and entity-system deep dives.
Performance and scope
Gem is a pure-Python parser optimized for research, batch analysis, and direct use in the Python data ecosystem. Multi-replay APIs distribute work across processes, while bounded Parquet export avoids retaining every completed match at once. The v0.8 performance study records the benchmark method, compatibility checks, and current optimization boundary without claiming an apples-to-oranges win over Go or Java parsers.
Some outputs are necessarily reconstructed:
- Farming routes, smoke analysis, fight positioning and Roshan records are experimental: they join replay facts with documented, inspectable rules. gem 0.13 removed the tags, scores and territory estimates that used to sit on top of them.
- Incomplete replays can return partial output, and some exact postgame fields require embedded match details.
- Healing Lotus pickups are not available from the replay event stream.
- Hero/item icons and the report map image are not shipped in the wheel; call
gem.reports.fetch_match_icons(match)to download the icons a report needs. (The report is deprecated and removed in 0.14.)
See Replay Edge Cases and the experimental-feature guides for the detailed boundaries.
Development
git clone https://github.com/whanyu1212/gem-dota.git
cd gem-dota
uv sync --group dev
uv run pytest
uv run pytest -m "integration and not network"
uv run ruff check src/ tests/
uv run mypy src/gem/
Contributions are welcome. Read CONTRIBUTING.md for the workflow and PR checklist. Parser changes should be checked against the pinned upstream reference revisions listed in CLAUDE.md and accompanied by focused regression tests. Additional tool guidance is available in AGENTS.md.
Acknowledgements
Gem builds on years of open work by the Dota replay community: Manta, Clarity, OpenDota parser, and dotaconstants.
No source code was copied from these projects; they are reference implementations used to understand protocol behavior and validate Gem's independent Python implementation. See THIRD_PARTY_LICENSES for license texts.
Metadata
Release files for gem-dota 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gem_dota-0.13.0.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gem_dota-0.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.6 MB
Release files / gem_dota-0.13.0.tar.gz
| Download URL | gem_dota-0.13.0.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e22431a3a6d0bcf8975621732a6b999970a0ec8356c913fb3357efb2960fec9d
|
|
BLAKE2b-256 checksum How to use checksums |
60b403c322e8ab8d7666b463b4dd62e3dc5552ed756ff59d2b571760560bbd77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / gem_dota-0.13.0-py3-none-any.whl
| Download URL | gem_dota-0.13.0-py3-none-any.whl |
|---|---|
| Size | 1.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
61982608f022171cc89698f3524ddfbe505cdaf04c22a5b28ea6819fb596b646
|
|
BLAKE2b-256 checksum How to use checksums |
dbf9f75e71e727804a1fd26a8477910d71565fd19e7ba2769c8212dec6e1517e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|