aoe2-openings
Read an Age of Empires II: Definitive Edition replay and get, for every player, the opening they played, their age-up times, the army, buildings and technologies of each age, and their economy: market trades, tributes, APM.
Every opening comes with the rule that named it and the counts behind it, so a call is never a black box:
$ aoe2-openings game.aoe2record
game.aoe2record
Arabia · 22:33 · rated
1. Player 1 (Chinese) won
Scout Rush · Feudal 6:14 Castle 15:24 Imperial - · 43 villagers · 99.3 APM
why: 3+ Scouts in the Feudal Age, with a Stable
2. Player 2 (Britons) lost
Archer Rush · Feudal 6:38 Castle 17:50 Imperial - · 48 villagers · 74.6 APM
why: 4+ Archers in the Feudal Age
Age times are when the player clicked to advance, in game time.
These are the rules AoE2Meta used to classify more than half a million ranked 1v1 games and show which play styles win, by civilization, map and ELO. OPENINGS.md lists all 25 of them in plain English.
Install
pip install aoe2-openings
Python 3.10 or newer. It reads replays with mgz-fast, AoE2Insights' fork of mgz that keeps up with the latest game patches, and names units, maps and civilizations with aocref.
Use it
From the command line, for one replay or many:
aoe2-openings game.aoe2record # a short summary
aoe2-openings game.aoe2record --json # everything, as JSON
aoe2-openings replays/*.aoe2record --json --timeline > games.json
From Python:
from aoe2_openings import analyze
game = analyze("game.aoe2record") # a path, bytes, or an open binary file
print(game.map, game.duration_ms)
for p in game.players:
print(p.name, p.civ, p.opening.name)
print(" why:", p.opening.reason) # 3+ Militia in the Dark Age, ...
print(" feudal at", p.feudal_ms, "ms") # None if they never got there
print(" feudal army:", dict(p.units["feudal"])) # {unit id: count}
game_dict(game) gives the same thing as plain data, with names next to every
ID and times as m:ss, the way --json prints it.
To classify from counts you already have, without a replay, build the signals yourself:
from aoe2_openings import classify, signals
opening = classify(signals(
civ="Franks", feudal_ms=600_000, castle_ms=1_080_000,
dark_units={74: 3}, # 3 Militia
feudal_units={4: 5}, # 5 Archers
feudal_buildings={10: 1}, # an Archery Range
))
opening.name # "Drush Archers"
opening.rule # "drush_archers"
What comes out of a replay
| Per player | |
|---|---|
| Match | Map, duration, rated or not, game and save version |
| Player | Name, profile ID, civilization, team, colour, result |
| Ages | When they clicked Feudal, Castle and Imperial Age |
| Villagers | Queued in each age, and in total |
| Army | Every unit queued, by age |
| Buildings | Every building and wall segment placed, by age |
| Technologies | Every technology researched, by age |
| Economy | Market buys and sells, tributes sent |
| Activity | Commands issued, APM, deletions |
| Timeline | Running totals of army and villagers every 30 seconds (--timeline) |
| Opening | Its name, the rule that matched, the reason and the signals |
Limits worth knowing
- Counts are commands. A replay is a log of what players told the game to do, not what happened. A unit queued and then cancelled still counts, and nothing about resources at a given moment can be read without replaying the match in the game.
- The result is inferred. A replay doesn't say who won. A side whose
players all resigned lost; if exactly one side is left, it won. Otherwise
wonisNone. - APM is raw. Every command counts, so it runs higher than an effective (eAPM) figure.
- Names follow aocref. After a patch adds a civilization or map, update aocref to get its name.
- Tuned on 1v1. The opening rules were built on ranked 1v1 Random Map; OPENINGS.md has the known gaps.
Tests
pip install -e ".[test]"
pytest
Every rule has a test built from hand-made counts. To also run the tests on
real games, put .aoe2record files in tests/replays/ or point
AOE2_REPLAYS at a folder of them.
How it was made
I built this, like AoE2Meta, by vibe coding with Claude: I knew the game and what I wanted out of it, and Claude wrote the code. The full story is in the write-up.
Thanks to happyleavesaoc for mgz, which does the hard part of reading replays, to AoEInsights for keeping its fast parser current, and to the Siege Engineers for the reference data.
License
MIT. Age of Empires II: Definitive Edition is a trademark of Microsoft Corporation. This is an independent fan project, not affiliated with or endorsed by Microsoft, made under Microsoft's Game Content Usage Rules.
Metadata
Release files for aoe2-openings 0.1.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 | |
|---|---|---|---|
| aoe2_openings-0.1.0.tar.gz | 16.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aoe2_openings-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.3 kB
Release files / aoe2_openings-0.1.0.tar.gz
| Download URL | aoe2_openings-0.1.0.tar.gz |
|---|---|
| Size | 16.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1de2b375f9d90c43991dc3855ac0245d53cd4cc418d39ce91423754e02d901da
|
|
BLAKE2b-256 checksum How to use checksums |
76baaeba111afaeb05595fa59b941c96ce2de53e3469f871943f11dd9aac83a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / aoe2_openings-0.1.0-py3-none-any.whl
| Download URL | aoe2_openings-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
960ad539e23e1277b3c4aaf02a6573de968ce4c1a2f1fd35446941980ca4f6b1
|
|
BLAKE2b-256 checksum How to use checksums |
1d3616428479369ef888df5c352c289034a53805286980e8f933ae120165358d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log