📚 API Reference¶
Demo¶
from boon import Demo
demo = Demo("match.dem")
A Deadlock demo file. Construction parses the file header, file info, and first tick to extract metadata.
Raises:
FileNotFoundError– If the file does not exist.InvalidDemoError– If the file is not a valid demo.DemoHeaderError– If required fields (build number, map name) are missing from the file header.DemoInfoError– If required fields (playback ticks, playback time) are missing from the file info.DemoMessageError– If the match ID could not be resolved from game entities.
Parameters:
path (
str) – Path to the.demfile.
Methods¶
verify()¶
demo.verify() # -> bool
Verify that the file is a valid demo file. Returns True if valid.
This is already called during construction, so it will always return True
for an existing Demo instance.
available_datasets()¶
Demo.available_datasets() # -> list[str]
Static method. Returns the list of dataset names that can be passed to load() or accessed as properties.
load()¶
demo.load("kills", "player_ticks", "world_ticks")
Load one or more datasets from the demo file in a single pass. See available_datasets() for valid names.
Already-loaded datasets are skipped. Multiple datasets requested together share a single parse pass over the file for efficiency.
Parameters:
*datasets (
str) – One or more dataset names to load.
Raises:
ValueError– If an unknown dataset name is provided.NotStreetBrawlError– If a street brawl dataset is requested on a non-street-brawl demo.
tick_to_seconds()¶
demo.tick_to_seconds(11400) # -> 190.0
Convert a tick number to seconds elapsed, excluding paused time.
Automatically loads world_ticks on first call to determine pauses.
Parameters:
tick (
int) – The game tick to convert.
Returns: float – The elapsed time in seconds, excluding pauses.
tick_to_clock_time()¶
demo.tick_to_clock_time(11400) # -> "3:10"
Convert a tick number to a clock time string (e.g., "3:14" or "12:34"),
excluding paused time. Automatically loads world_ticks on first call to
determine pauses.
Parameters:
tick (
int) – The game tick to convert.
Returns: str – A formatted clock time string.
summary()¶
summary = demo.summary()
summary.keys() # dict_keys(['snapshots', 'last_hits', 'objectives', 'damage'])
summary["snapshots"] # pl.DataFrame -- one row per (snapshot, player)
summary["last_hits"] # pl.DataFrame -- hero_id, last_hits
summary["objectives"] # pl.DataFrame -- post-match objective records
summary["damage"] # pl.DataFrame -- damage matrix (long form)
Parse the post-match summary from the demo’s PostMatchDetails event. Returns a
dict with four top-level keys:
snapshots– a Polars DataFrame with one row per (snapshot, player). Snapshots are taken at intervals through the match (not every minute);snapshot_time_smarks each one. Columns hold that player’s running totals at that time:hero_id,kills,deaths,assists,net_worth,denies,level,lane,creep_kills,neutral_kills,player_damage, and the per-source gold/orbs breakdown (player_*,lane_creep_*,neutral_creep*,boss_*,treasure_*,denies_*,team_bonus_*,breakable_*,assassinate_*,trophy_collector_*,cultist_sacrifice_*,assists_*, andunknown_*).last_hits– a Polars DataFrame ofhero_idandlast_hits: the final scoreboard last-hit (souls secured) total. This is only recorded per match, not per snapshot, which is why it is a separate frame rather than a snapshot column.objectives– a Polars DataFrame of post-match objective records:team_objective_id,team,destroyed_time_s,first_damage_time_s,creep_damage,player_damage,player_spirit_damage.destroyed_time_sandfirst_damage_time_sare null when the objective was never destroyed/damaged.damage– a Polars DataFrame of the damage matrix in long form: one row per (dealer_player_slot,target_player_slot,source_name,sample_time_s). Dealer/target are also resolved todealer_hero_idandtarget_hero_id(null for non-player slots like0), so the frame joins tosnapshots/last_hitsonhero_id.damageis the per-interval, additive amount for thatstat_typedealt during the interval ending at that sample –sumit for totals,cumsumoversample_time_sfor the running total.stat_typeis a string (damage,healing,heal_prevented,mitigated,lethal,regen).is_category(bool) flags Valve’s coarse damage-type buckets (Bullet/Ability/Melee/Misc/UnknownAbility), which duplicate the specific-source rows fordamage; filter tois_category == Falsefor the complete, non-overlapping per-source breakdown across all stat types.import polars as pl dmg = demo.summary()["damage"] # player-vs-player damage matrix (totals) -- the obvious query just works: (dmg.filter((pl.col("stat_type") == "damage") & ~pl.col("is_category")) .group_by("dealer_player_slot", "target_player_slot") .agg(pl.col("damage").sum()))
Returns: dict – The post-match summary (Polars DataFrames keyed by name).
Raises: DemoMessageError – If the demo contains no post-match details
(for example, an incomplete recording).
kill_participation()¶
demo.kill_participation() # whole match
demo.kill_participation(start_tick=0, end_tick=18000) # windowed
Each player’s kill participation: (kills + assists) / team_kills. A player is
credited on a team kill as either the killer or an assister (never both), so the
value is a fraction in [0, 1] — the share of their team’s kills they were
involved in. Convenience method that delegates to
boon.stats.kill_participation(); see that section for details.
Optional start_tick / end_tick restrict the count to kills within that tick
window (the denominator is the team’s kills in the same window).
Returns: polars.DataFrame — one row per player, sorted by team_num then
hero_id:
Column |
Type |
Description |
|---|---|---|
|
|
The player’s hero ID |
|
|
The player’s team number |
|
|
Kills credited to the player (in the window) |
|
|
Assists credited to the player (in the window) |
|
|
Total kills by the player’s team (in the window) |
|
|
|
time_dead()¶
demo.time_dead()
Time each player spent dead during regulation. A player is dead on any tick
where they are not alive (is_alive == False); only non-paused ticks up to the
game-over event are counted, so the totals align with regulation_ticks /
regulation_seconds. Convenience method that delegates to
boon.stats.time_dead().
Returns: polars.DataFrame — one row per player, sorted by team_num then
hero_id:
Column |
Type |
Description |
|---|---|---|
|
|
The player’s hero ID |
|
|
The player’s team number |
|
|
Non-paused regulation ticks spent dead |
|
|
|
|
|
|
Raises: ValueError — If the demo has no game-over event (regulation time,
and therefore this metric, is undefined).
in_combat()¶
demo.in_combat()
Whether each player is in combat, per tick. Deadlock tracks combat on the pawn
as a window (in_combat_end_time on player_ticks, pushed to
last_damage_time + delay on every hit — ~0.5s for trooper/denizen damage, ~3.0s
for hero damage); this derives the live boolean by comparing the current game
time against that window. Convenience method that delegates to
boon.stats.in_combat().
Returns: polars.DataFrame — one row per (tick, hero_id), so it joins
directly onto player_ticks, sorted by tick then hero_id:
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
The player’s hero ID |
|
|
Whether the player is in combat on that tick |
Metadata Properties¶
path¶
demo.path # pathlib.Path
The path to the demo file.
total_ticks¶
demo.total_ticks # int
The total number of ticks in the demo.
total_seconds¶
demo.total_seconds # float
The total duration of the demo in seconds, covering the entire recording
(including pre-game and post-match time). For the duration of actual gameplay,
see regulation_seconds.
total_clock_time¶
demo.total_clock_time # str
The total duration of the demo as a formatted string (e.g., "12:34"), covering
the entire recording. For gameplay duration, see regulation_clock_time.
build¶
demo.build # int
The build number of the game that recorded the demo.
map_name¶
demo.map_name # str
The name of the map the demo was recorded on.
match_id¶
demo.match_id # int
The match ID for this demo.
game_mode¶
demo.game_mode # int
The game mode ID for this demo (use game_mode_names() to resolve).
tick_rate¶
demo.tick_rate # int
The tick rate of the demo (ticks per second).
winning_team_num¶
demo.winning_team_num # int | None
The team number of the winning team, or None if no game-over event was found.
Scans for the k_EUserMsg_GameOver event on first access.
game_over_tick¶
demo.game_over_tick # int | None
The tick when the game ended, or None if no game-over event was found.
Scans for the k_EUserMsg_GameOver event on first access.
regulation_ticks¶
demo.regulation_ticks # int | None
The number of active (non-paused) ticks of regulation play, counted from the
start of the recording up to the game-over event. Reflects how much of the game
was actually played, unlike total_ticks (the full recording). None if no
game-over event was found. Scans for k_EUserMsg_GameOver and loads
world_ticks on first access.
regulation_seconds¶
demo.regulation_seconds # float | None
The active gameplay duration in seconds, up to the game-over event. Equal to
regulation_ticks / tick_rate. The regulation counterpart to total_seconds.
None if no game-over event was found.
regulation_clock_time¶
demo.regulation_clock_time # str | None
The regulation play duration as a formatted string (e.g., "32:45"). The
counterpart to total_clock_time. None if no game-over event was found.
DataFrame Properties¶
players¶
demo.players # polars.DataFrame
Player information. Computed from the final tick.
Column |
Type |
Description |
|---|---|---|
|
|
The player’s display name |
|
|
The player’s Steam ID |
|
|
The player’s hero ID (use |
|
|
Raw team number (use |
|
|
Original lane color (1=yellow, 3=green, 4=blue, 6=purple, 0=none; from the |
player_ticks¶
demo.player_ticks # polars.DataFrame
Per-tick, per-player state. Returns one row per player per tick.
Rows where the pawn is not found or hero_id == 0 are skipped.
Auto-loads on first access if not already loaded via load().
Pawn fields (from CCitadelPlayerPawn):
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
Hero ID |
|
|
Player X position in world (Hammer) units |
|
|
Player Y position in world (Hammer) units |
|
|
Player Z position in world (Hammer) units |
|
|
Camera pitch angle |
|
|
Camera yaw angle |
|
|
Camera roll angle |
|
|
In a regeneration zone |
|
|
In an item shop zone |
|
|
Time of death |
|
|
Time of last spawn |
|
|
Time until respawn |
|
|
Current health |
|
|
Maximum health |
|
|
Life state value (use |
|
|
Current souls (currency) |
|
|
Total spent souls |
|
|
In-combat timer end |
|
|
In-combat last damage time |
|
|
In-combat timer start |
|
|
Damage dealt timer end |
|
|
Damage dealt last damage time |
|
|
Damage dealt timer start |
|
|
Damage taken timer end |
|
|
Damage taken last damage time |
|
|
Damage taken timer start |
|
|
Time revealed on minimap by NPC |
|
|
Hero build ID |
Controller fields (from CCitadelPlayerController):
Column |
Type |
Description |
|---|---|---|
|
|
Whether the player is alive |
|
|
Has rebirth |
|
|
Has rejuvenator |
|
|
Ultimate is trained |
|
|
Health regeneration rate |
|
|
Ultimate cooldown start time |
|
|
Ultimate cooldown end time |
|
|
Ability power net worth |
|
|
Gold net worth |
|
|
Total denies |
|
|
Total hero damage dealt |
|
|
Total hero healing |
|
|
Total objective damage |
|
|
Total self healing |
|
|
Current kill streak |
|
|
Total last hits |
|
|
Player level |
|
|
Total kills |
|
|
Total deaths |
|
|
Total assists |
world_ticks¶
demo.world_ticks # polars.DataFrame
World state at every tick. Returns one row per tick.
Auto-loads on first access if not already loaded via load().
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
Whether the game is paused |
|
|
Time until next midboss spawn |
kills¶
demo.kills # polars.DataFrame
Hero kill events. Auto-loads on first access if not already loaded via load().
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the kill occurred |
|
|
The hero ID of the killed player |
|
|
The hero ID of the attacker |
|
|
List of hero IDs of players who assisted |
damage¶
demo.damage # polars.DataFrame
Damage events. Auto-loads on first access if not already loaded via load().
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the damage occurred |
|
|
The damage dealt |
|
|
The damage before mitigation |
|
|
The hero ID of the victim (0 if not a hero) |
|
|
The hero ID of the attacker (0 if not a hero) |
|
|
The victim’s health after damage |
|
|
The hitgroup that was hit (use |
|
|
Critical damage amount |
|
|
The attacker’s entity class ID |
|
|
The victim’s entity class ID |
flex_slots¶
demo.flex_slots # polars.DataFrame
Flex slot unlock events. Auto-loads on first access if not already loaded via load().
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the flex slot was unlocked |
|
|
The team number that unlocked the flex slot |
abilities¶
demo.abilities # polars.DataFrame
Important ability usage events. Auto-loads on first access if not already loaded via load().
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the ability was used |
|
|
The hero ID of the player |
|
|
The ability name |
ability_upgrades¶
demo.ability_upgrades # polars.DataFrame
Hero ability point spending events (skill tier upgrades). Emits a row each time a player upgrades one of their abilities. Auto-loads on first access.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the upgrade occurred |
|
|
The hero ID of the player |
|
|
The raw MurmurHash2 ability ID (use |
|
|
Upgrade tier (1, 2, or 3) |
item_purchases¶
demo.item_purchases # polars.DataFrame
Item shop transactions. Includes purchases, upgrades, sells, swaps, and failures. Auto-loads on first access.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the transaction occurred |
|
|
The hero ID of the player |
|
|
The raw MurmurHash2 item/ability ID (use |
|
|
Transaction type: |
chat¶
demo.chat # polars.DataFrame
In-game chat messages. Auto-loads on first access.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the message was sent |
|
|
The hero ID of the sender |
|
|
The message text |
|
|
|
objectives¶
demo.objectives # polars.DataFrame
Objective health state changes. Tracks walkers, barracks, shrines, patrons, and mid boss. Emits a row when an objective’s health or max_health changes. Auto-loads on first access.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
|
|
|
The team that owns the objective |
|
|
Lane color (1=yellow, 3=green, 4=blue, 6=purple; 0 for patron/shrine/mid_boss) |
|
|
Current health |
|
|
Maximum health |
|
|
Patron phase — resolve with |
|
|
X position in world (Hammer) units |
|
|
Y position in world (Hammer) units |
|
|
Z position in world (Hammer) units |
|
|
Entity index (stable per structure across ticks) |
mid_boss¶
demo.mid_boss # polars.DataFrame
Mid boss lifecycle events including spawn, kill, and rejuvenator buff tracking. Auto-loads on first access.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
The team involved |
|
|
|
troopers¶
demo.troopers # polars.DataFrame
Per-tick alive lane trooper state. Tracks CNPC_Trooper and CNPC_TrooperBoss entities.
Emits a row for every alive trooper at every tick.
Warning: This is a large dataset (~5M+ rows). Not loaded by default.
Access this property or call load("troopers") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
|
|
|
The trooper’s team |
|
|
Lane assignment (1, 4, or 6) |
|
|
Current health |
|
|
Maximum health |
|
|
X position in world (Hammer) units |
|
|
Y position in world (Hammer) units |
|
|
Z position in world (Hammer) units |
|
|
Entity index (stable per trooper across ticks) |
neutrals¶
demo.neutrals # polars.DataFrame
Neutral creep state changes. Tracks CNPC_TrooperNeutral and CNPC_TrooperNeutralNodeMover.
Only emits a row when an alive neutral’s state changes (health, position), significantly
reducing data volume compared to per-tick tracking.
Not loaded by default. Access this property or call load("neutrals") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the state changed |
|
|
The neutral’s team |
|
|
Current health |
|
|
Maximum health |
|
|
X position in world (Hammer) units |
|
|
Y position in world (Hammer) units |
|
|
Z position in world (Hammer) units |
|
|
Entity index (stable per neutral across ticks) |
stat_modifier_events¶
demo.stat_modifier_events # polars.DataFrame
Permanent stat bonus change events from urn and breakable pickups. Emits a row whenever a stat total changes.
Not loaded by default. Access this property or call load("stat_modifier_events") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the stat changed |
|
|
The player’s hero ID |
|
|
|
|
|
The increase from this event |
active_modifiers¶
demo.active_modifiers # polars.DataFrame
Active buff/debuff modifiers on players. Tracks applied and removed events for each modifier.
Not loaded by default. Access this property or call load("active_modifiers") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the modifier event occurred |
|
|
The affected player’s hero ID |
|
|
|
|
|
Raw modifier subclass hash ID (use |
|
|
Raw ability subclass hash ID (use |
|
|
Modifier duration |
|
|
Hero ID of the caster |
|
|
Number of stacks |
ability_ticks¶
demo.ability_ticks # polars.DataFrame
Ability cooldown and charge state over time. Change-only: a row is emitted
for an ability only on the tick its cooldown or charge state changes (not every
tick), keeping the frame compact. One ability entity exists per ability a player
owns, including innate movement abilities (jump, dash, slide, …), which can be
filtered out via slot.
Not loaded by default. Access this property or call load("ability_ticks") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick of the state change |
|
|
The owning player’s hero ID |
|
|
Ability subclass hash (use |
|
|
Ability slot ( |
|
|
Game time the cooldown started |
|
|
Game time the ability is available again |
|
|
Charges currently available |
|
|
Game time the regenerating charge started |
|
|
Game time the regenerating charge completes |
urn¶
demo.urn # polars.DataFrame
Urn lifecycle events. Tracks when the urn is picked up, dropped, or returned
by filtering urn-related modifiers from the ActiveModifiers string table.
Also tracks delivery point activation/deactivation via CCitadelIdolReturnTrigger entities.
Not loaded by default. Access this property or call load("urn") explicitly.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick when the event occurred |
|
|
|
|
|
The hero involved (0 for delivery events) |
|
|
Team of the delivery point (0 for modifier events) |
|
|
Delivery point or pawn X position in world (Hammer) units |
|
|
Delivery point or pawn Y position in world (Hammer) units |
|
|
Delivery point or pawn Z position in world (Hammer) units |
street_brawl_ticks¶
demo.street_brawl_ticks # polars.DataFrame
Per-tick street brawl state. Only available for street brawl demos (game_mode=4).
Auto-loads on first access if not already loaded via load().
Raises: NotStreetBrawlError if the demo is not a street brawl game.
Column |
Type |
Description |
|---|---|---|
|
|
The game tick |
|
|
Current round number |
|
|
Street brawl state enum value |
|
|
The Hidden King (old name: Amber Hand) score |
|
|
The Archmother (old name: Sapphire Flame) score |
|
|
Last buy phase countdown value |
|
|
Time of next state transition |
|
|
Time the current state started |
|
|
Total non-combat time elapsed |
street_brawl_rounds¶
demo.street_brawl_rounds # polars.DataFrame
Street brawl round scoring events. Only available for street brawl demos (game_mode=4).
Auto-loads on first access if not already loaded via load().
Raises: NotStreetBrawlError if the demo is not a street brawl game.
Column |
Type |
Description |
|---|---|---|
|
|
Sequential round number (1-indexed) |
|
|
The game tick when the round ended |
|
|
The team that scored |
|
|
The Hidden King (old name: Amber Hand) cumulative score |
|
|
The Archmother (old name: Sapphire Flame) cumulative score |
Name Lookup Functions¶
Module-level functions for resolving IDs to human-readable names. These do not require a parsed demo.
hero_names()¶
from boon import hero_names
hero_names() # -> dict[int, str]
Return a mapping of hero ID to hero name.
Returns: dict[int, str] – Hero ID to hero name mapping (e.g., {1: "Infernus", 2: "Seven", ...}).
team_names()¶
from boon import team_names
team_names() # -> dict[int, str]
Return a mapping of team number to team name.
Returns: dict[int, str] – {1: "Spectator", 2: "Hidden King", 3: "Archmother"}.
ability_names()¶
from boon import ability_names
ability_names() # -> dict[int, str]
Return a mapping of MurmurHash2 ability ID to ability name.
Returns: dict[int, str] – Ability hash to name mapping.
game_mode_names()¶
from boon import game_mode_names
game_mode_names() # -> dict[int, str]
Return a mapping of game mode ID to game mode name.
Returns: dict[int, str] – Game mode ID to name mapping (e.g., {1: "6v6", 4: "street_brawl"}).
modifier_names()¶
from boon import modifier_names
modifier_names() # -> dict[int, str]
Return a mapping of MurmurHash2 modifier ID to modifier name.
Returns: dict[int, str] – Modifier hash to name mapping.
patron_phase_names()¶
from boon import patron_phase_names
patron_phase_names() # -> dict[int, str]
Return a mapping of patron phase ID to phase name. Phases are the values of
CNPC_Boss_Tier3.m_ePhase: 0=normal (shielded), 1=final (killable),
2=transforming (vulnerable). Non-patron objectives report 0 by default.
Returns: dict[int, str] – Patron phase ID to name mapping (e.g., {0: "normal", 1: "final", 2: "transforming"}).
hitgroup_names()¶
from boon import hitgroup_names
hitgroup_names() # -> dict[int, str]
Return a mapping of hit group ID to hit group name, for resolving the
hitgroup_id column on the damage frame. Values are Source 2’s HitGroup_t
enum: 0=generic, 1=head, 2=chest, 3=stomach, the limbs (4=left_arm,
5=right_arm, 6=left_leg, 7=right_leg), 8=neck, 10=gear, 11=special,
the tier-2 / drone boss weakpoints (12–18), 19=head_no_resist, and
-1=invalid. The HITGROUP_COUNT sentinel is omitted.
Returns: dict[int, str] – Hit group ID to name mapping.
lifestate_names()¶
from boon import lifestate_names
lifestate_names() # -> dict[int, str]
Return a mapping of life state ID to life state name, for resolving the
lifestate column on player_ticks. Values are Source 2’s LifeState_t enum.
Returns: dict[int, str] – Life state ID to name mapping ({0: "alive", 1: "dying", 2: "dead", 3: "respawnable", 4: "respawning"}).
Stats (boon.stats)¶
An analysis layer of derived metrics computed from parsed demo data. Each
function takes a Demo and returns a Polars DataFrame, keyed on
hero_id so results join cleanly to the parser’s other frames (players,
kills, player_ticks, the summary() outputs, …). Every metric is also
surfaced as a convenience method on Demo (e.g. demo.kill_participation()
delegates to boon.stats.kill_participation(demo) — same computation).
kill_participation()¶
from boon import stats
stats.kill_participation(demo) # whole match
stats.kill_participation(demo, start_tick=0, end_tick=18000) # windowed
demo.kill_participation() # equivalent method form
Each player’s (kills + assists) / team_kills. A player is credited on a team
kill as either the killer or an assister (never both on the same kill), so the
value is a fraction in [0, 1] — the share of their team’s kills they were
involved in. Pass start_tick / end_tick to count only kills within that tick
window (the denominator is the team’s kills in the same window).
Returns: polars.DataFrame with columns hero_id, team_num, kills,
assists, team_kills, kill_participation (see the
Demo.kill_participation() table), one row per player,
sorted by team_num then hero_id.
time_dead()¶
from boon import stats
stats.time_dead(demo) # equivalently: demo.time_dead()
Time each player spent dead during regulation. A player is dead on any tick
where is_alive == False; only non-paused ticks up to the game-over event are
counted, so the totals align with demo.regulation_ticks /
demo.regulation_seconds.
Returns: polars.DataFrame with columns hero_id, team_num,
ticks_dead, seconds_dead, pct_regulation_dead (see the
Demo.time_dead() table), one row per player, sorted by
team_num then hero_id.
Raises: ValueError — if the demo has no game-over event.
in_combat()¶
from boon import stats
stats.in_combat(demo) # equivalently: demo.in_combat()
Whether each player is in combat, per tick, derived from the pawn’s
in_combat_end_time window on player_ticks. The current game time per tick is
reconstructed from non-paused elapsed ticks plus a constant offset calibrated
against the data (at a damage tick in_combat_last_damage_time equals the
current game time).
Returns: polars.DataFrame with columns tick, hero_id, in_combat (see
the Demo.in_combat() table), one row per (tick, hero_id),
sorted by tick then hero_id.
Exceptions¶
InvalidDemoError¶
from boon import InvalidDemoError
Raised when a demo file is invalid or cannot be parsed (bad magic bytes, corrupted data).
DemoHeaderError¶
from boon import DemoHeaderError
Raised when required fields are missing from the demo file header (build number, map name).
DemoInfoError¶
from boon import DemoInfoError
Raised when required fields are missing from the demo file info (playback ticks, playback time).
DemoMessageError¶
from boon import DemoMessageError
Raised when required data could not be resolved from demo messages (e.g., match ID from CCitadelGameRulesProxy).
NotStreetBrawlError¶
from boon import NotStreetBrawlError
Raised when accessing street brawl datasets (street_brawl_ticks, street_brawl_rounds) on a demo that is not a street brawl game (game_mode != 4).