📝 Changelog¶
0.5.0¶
boon-python¶
New
boon.statsmetricin_combat(demo)(Demo.in_combat()) — a per-tickin_combatboolean keyed on(tick, hero_id), derived from the pawn’sin_combat_end_timewindow so it joins directly ontoplayer_ticks.New
player_tickscolumn:in_item_shop(zone bool alongsidein_regen_zone).New
ability_ticksdataset — change-only ability cooldown/charge state (cooldown_start/cooldown_end,remaining_charges,charge_recharge_start/charge_recharge_end) per(hero_id, ability_id, slot), emitting a row only when an ability’s state changes. Not loaded by default.Faster: the change-only entity datasets (
ability_ticks,objectives,neutrals,urn) now process only the entities each tick actually changed instead of rescanning every active entity. An ability’s cooldown/charge state can only change on a tick it was updated, sodemo.ability_ticksis ~2.4× faster; output is byte-for-byte identical. The gain compounds when several of these datasets are loaded together.
boon-cli¶
New
ability-tickscommand — inspect ability cooldown/charge state changes (the CLI counterpart of theability_ticksdataset), with--filter,--summary, tick-window flags, and--json.Faster: the
ability-ticks,objectives, andneutralscommands use the same change-only scan —ability-ticksdrops from ~1010 ms to ~426 ms on a 64 MB demo, with identical output.
boon¶
New
EntityContainer::updated_indices()/clear_updated(): the parser now records which tracked entities were created or updated on each tick and exposes them on the per-tickContext, letting change-only consumers process just what changed instead of rescanning every active entity. The set is cleared after eachrun_to_end*callback.Faster: entity decode is ~5% quicker on a full-demo parse (back-to-back measurement), from two changes with identical output: (1)
SerializerContainernow uses anFxHashMaprather than the default SipHashHashMap, since the per-update serializer lookup hashes the class-name string on the hot path; and (2) a new entity’s field map is pre-sized to its class’s field count, avoiding repeated rehashing as the baseline and create delta populate it.
0.4.0¶
boon-python¶
New
boon.statsmodule — derived metrics computed from parsed demo data, keyed onhero_id. Each is also a thinDemomethod. Initial metrics:kill_participation(demo)(Demo.kill_participation()) —(kills + assists) / team_killsper player (a[0, 1]fraction), with an optionalstart_tick/end_tickwindow.time_dead(demo)(Demo.time_dead()) — per-playerticks_dead,seconds_dead, andpct_regulation_dead, counting only non-paused ticks up to game-over so totals align withregulation_ticks/regulation_seconds.
hitgroup_names()returningdict[int, str]for thehitgroup_idcolumn ondamage. Values are Source 2’sHitGroup_tenum (-1=invalid,0=generic,1=head, …19=head_no_resist; theHITGROUP_COUNTsentinel is omitted).lifestate_names()returningdict[int, str]for thelifestatecolumn onplayer_ticks. Values are Source 2’sLifeState_tenum (0=alive,1=dying,2=dead,3=respawnable,4=respawning).Changed:
patron_phase_names()renames patron phase2fromshields_downtotransforming. The rawphaseinteger is unchanged (still2); update any code comparing the resolved string against"shields_down".
boon-cli¶
No functional changes; the version is bumped in step with the workspace.
boon¶
New
hitgroupsandlifestateslookup tables:hitgroup_name(id)/all_hitgroups()andlifestate_name(id)/all_lifestates(), mapping Source 2’sHitGroup_tandLifeState_tenums to names and re-exported at the crate root. These back the Pythonhitgroup_names()/lifestate_names().Changed:
patron_phase_name(2)now returnstransforming(wasshields_down).
0.3.0¶
boon-python¶
Faster: all parsing is a few percent quicker — packet messages the parser doesn’t consume (sounds, temp entities, etc.) are now skipped in place instead of copied out of the bitstream first.
Fixed & faster:
active_modifiers(and the idol events inurn) emitted duplicateapplied/removedrows on nearly every tick. Source 2 keeps a modifier’s originalActiveModifiersentry and adds a separateentry_type=2removal entry (the table never shrinks), so the old per-tick full-table rescan re-applied and re-removed it indefinitely — ~99% of rows were per-tick duplicates (e.g. 3.16M rows where ~41k were real). The scan now processes only the entries each string-table delta touches, reporting each modifier once applied and once removed. Much faster too: the ActiveModifiers decode dropped from ~7.3s to ~0.06s (full dataset load ~18s → ~5s).urnoutput is unchanged apart from dropping the duplicate idol events.Fixed:
modifier_names()resolved only ~87 modifiers — the generic ones defined as top-level keys inmodifiers.vdata; most gameplay modifiers are nestedsubclass:blocks the generator never scanned. It now unions every top-level key inmodifiers.vdata, every nested_my_subclass_namethere, and the_my_subclass_nameof each modifier subclass inabilities.vdata(those whose_classstarts withmodifier_) — ~917 entries. This is the right field because a demo identifies a modifier by themodifier_subclasstoken onCModifierTableEntry, theCUtlStringToken(MurmurHash2) of its_my_subclass_name. Many modifiers live only in engine/C++ code and appear in no vdata file, so a share ofmodifier_idvalues stayMODIFIER_NOT_FOUND(name-list-bound, not a hashing limitation).ability_names(),modifier_names(), andhero_names()reflect the latest Deadlock build (6557). The hero table gains Raven (hero_operative, id 62) and a test hero (id 83), and renames id 82 (hero_opera) from “Raven” to “Opera” — Valve moved “Raven” onto the new slot. The ability table also tracked a few upstream removals.Fixed: the
max_healthcolumn onplayer_ticksreported the pawn’sm_iMaxHealth, a stale base value that current health exceeds on over half of all ticks (e.g.817vs a reported max of780). It now reads the controller’sm_PlayerDataGlobal.m_iHealthMax— the live effective max (level growth, items, buffs) — falling back to the pawn value only before the controller is populated. Thehealthcolumn is unchanged.
boon-cli¶
Fixed & faster: the
active-modifierscommand had the same per-tick flicker — re-emitting anapplied/removedpair for stale entries on nearly every tick (e.g. 435,795 events where ~5,763 were real). It now processes only the entries each string-table delta touches, reporting each modifier once applied and once removed, and runs much faster.
boon-proto¶
Synced protobuf definitions to the latest Deadlock build (
6536→6557);boon-protois now0.2.10717574+6557. The notable change is a newCMsgServerSignoutData_DetailedStats.UrnCapturemessage (per-urn post-match stats) added as a repeatedurn_capturesfield. An earlier build also dropped a redundant[default = 0]from twousermessages.protouint32fields — a no-op, since0is already the implicit default.
0.2.0¶
boon-python¶
Demo.summary()method returning the post-match summary as a dict withsnapshots,last_hits,objectives, anddamagekeys.snapshotsis a Polars DataFrame with one row per (snapshot, player) — asnapshot_time_scolumn plus per-player running totals (kills/deaths/assists, net worth, denies, level, lane, creep/neutral kills, player damage, and the per-source gold/orbs breakdown).last_hitsis a Polars DataFrame ofhero_idandlast_hits(the final scoreboard last-hit total, only recorded per match).objectivesis a Polars DataFrame of post-match objective records (destruction time and damage taken).damageis a Polars DataFrame of the damage matrix in long form — one row per (dealer, target, source, sample) with dealer/target as both*_player_slotand resolved*_hero_id(null for non-player slots, joinable to the other frames onhero_id), the per-interval (additive)damageperstat_type(a readable string), anis_categoryflag distinguishing coarse damage-type buckets from specific sources, andsample_time_s. Filter tois_category == Falseandsumfor totals, orcumsumoversample_time_sfor the running total.Demo.regulation_ticks,Demo.regulation_seconds,Demo.regulation_clock_timeproperties for the duration of actual gameplay (active, paused-time-excluded ticks up to the game-over event), distinct from the full-recordingtotal_ticks/total_seconds/total_clock_time. ReturnNonewhen no game-over event is present.patron_phase_names()module-level function returningdict[int, str]of patron phase ID to name (0=normal,1=final,2=shields_down) for thephasecolumn on patron objective rows.Fixed: the
start_lane/lanecolumn docs previously claimed1=left, 4=center, 6=right, which is wrong — the values areCMsgLaneColorcolor IDs and3=greenwas also missing. Docs now correctly read1=yellow, 3=green, 4=blue, 6=purple, 0=none.Fixed:
player_ticksdropped most players, often leaving only one hero. Player controllers link to their pawn through aCHandlewhose entity index is the low 14 bits; the index was masked with0x7FFF(15 bits) instead of0x3FFF, so any handle with an odd serial resolved to the wrong entity and that player was silently skipped. The mask is now0x3FFF, andplayer_ticksagain covers every player on the roster.Fixed: the
x/y/zcolumns onplayer_ticks,objectives,troopers,neutrals, andurnpreviously emitted only the in-cell offset half of Source 2’s split position storage — values bounded to[0, 512)that reset to0every time the entity crossed a cell boundary, producing a sawtooth instead of a trajectory. They now emit full world (Hammer-unit) positions, combining the networkedm_cellX/Y/Zcell index with them_vecOrigin.m_vec{X,Y,Z}offset via the newboon::position::cell_to_worldhelper. No display-side scaling is applied; downstream plotters supply their own map projection.
boon-cli¶
summarycommand for post-match details: a match overview, a timing section (total ticks/time and tick rate from the recording, the game-over tick, and the regulation/gameplay duration), each player’s final snapshot (with the scoreboard last-hit total), and an objectives table mirroring the Pythonsummary()objectivesframe.--jsondumps the full decoded metadata.Name resolution reflects the refreshed ability and modifier name tables from the latest Deadlock build.
Fixed: the
neutralsandtrooperscommands’x/y/zcolumns now report full world coordinates (Hammer units) instead of just the in-cell offset half of Source 2’s split position storage — see the matching Python fix above.
boon-proto¶
Synced protobuf definitions to the latest Deadlock build and regenerated the ability and modifier name lookup tables (surfaced via
ability_names()andmodifier_names()).Versioned independently from the rest of the workspace to track the game build:
MAJOR.MINOR.<SourceRevision>+<GameBuild>(e.g.0.2.10691905+6536). The monotonicSourceRevisionis the patch, so each proto sync yields a higher, publishable version while staying compatible within the0.2line.
0.1.0¶
boon-python (breaking changes from pre-release)¶
Breaking: Removed
heroandteamstring columns fromplayersDataFrame. Usehero_names()andteam_names()to resolve IDs to names.Breaking: Removed
teamsDataFrame property. Useteam_names()module-level function instead.Breaking: Removed
winning_teamproperty. Usewinning_team_numwithteam_names().Breaking: Removed
banned_heroesproperty. Thek_EUserMsg_BannedHeroesevent is no longer reliably present in GOTV demo recordings (see Known Limitations).Breaking: Moved
Demo.hero_names()andDemo.team_names()from static methods to module-level functionshero_names()andteam_names(). Import directly fromboon.Breaking:
purchasesandshop_eventsdatasets merged intoitem_purchases. Columns:tick,hero_id,ability_id,change.Breaking:
abilitycolumn removed fromability_upgrades. Useability_names()to resolveability_id.Breaking:
modifierandabilitycolumns removed fromactive_modifiers. Usemodifier_names()andability_names()to resolve IDs.
boon-python¶
Democlass with metadata properties:path,total_ticks,total_seconds,total_clock_time,build,map_name,match_id,tick_rate,game_mode.Demo.playersproperty returning a Polars DataFrame of player info.Demo.player_ticksproperty returning per-tick, per-player state (48 columns).Demo.world_ticksproperty returning per-tick world state.Demo.killsproperty for hero kill events with attacker, victim, and assisters.Demo.damageproperty for damage events with pre/post mitigation, hitgroups, and crit damage.Demo.flex_slotsproperty for flex slot unlock events.Demo.abilitiesproperty for important ability usage events.Demo.ability_upgradesproperty for hero ability point spending events.Demo.item_purchasesproperty for item shop transactions.Demo.chatproperty for in-game chat messages.Demo.objectivesproperty for objective health state changes.Demo.mid_bossproperty for mid boss lifecycle events.Demo.troopersproperty for per-tick alive lane trooper state (opt-in, large dataset).Demo.neutralsproperty for neutral creep state changes with change detection (opt-in).Demo.stat_modifier_eventsproperty for permanent stat bonus change events (opt-in).Demo.active_modifiersproperty for active buff/debuff modifier events (opt-in).Demo.urnproperty for urn (idol) lifecycle events (picked up, dropped, returned) and delivery point tracking (active, inactive with position and team).Demo.street_brawl_ticksproperty for per-tick street brawl state (round, scores, state transitions).Demo.street_brawl_roundsproperty for street brawl round scoring events.NotStreetBrawlErrorexception raised when accessing street brawl datasets on non-street-brawl demos.Demo.winning_team_num,Demo.game_over_tickproperties for game-over state (lazy-scanned on first access).Demo.available_datasets()static method returning the list of valid dataset names.Demo.load()method to batch-load multiple datasets in a single parse pass.All DataFrame properties auto-load on first access and can be batch-loaded via
load().hero_names()module-level function returningdict[int, str]of hero ID to name.team_names()module-level function returningdict[int, str]of team number to name.ability_names()module-level function returningdict[int, str]of ability hash ID to name.modifier_names()module-level function returningdict[int, str]of modifier hash ID to name.game_mode_names()module-level function returningdict[int, str]of game mode ID to name.Custom exceptions:
InvalidDemoError,DemoHeaderError,DemoInfoError,DemoMessageError.
boon-cli¶
CLI with commands:
verify,info,messages,classes,send-tables,string-tables,entities,events.ability-upgradescommand for tracking hero ability point spending (skill tier upgrades).shop-eventscommand for item shop transactions (purchased, upgraded, sold, swapped, failure).chatcommand for in-game chat messages (all chat and team chat).objectivescommand for per-tick objective entity health (walkers, titans, barracks, mid boss).mid-bosscommand for mid boss lifecycle events (spawn, kill, rejuv pickup/use/expire).trooperscommand for per-tick alive lane trooper state (position, health, lane).neutralscommand for neutral creep state changes with change detection.stat-modifierscommand for per-player cumulative permanent stat bonuses.active-modifierscommand for active buff/debuff modifier events.All commands support
--filter,--summary,--limit, and--jsonflags.