gem
v0.13.0

Roshan Conversion

gem.build_rosh_conversions(match) returns one record per Roshan kill: who killed it, what happened to the Aegis, and what happened in the window that followed. It compares the two teams across fights, structures, resources, forward wards, the Tormentor and buybacks, and keeps the signed values visible.

It reports what happened, not whether the Roshan "converted". gem 0.12's tags (conversion_tags, RoshTagThresholds), verdicts (conversion_score, conversion_label, aegis_outcome, drivers), territory windows and enemy_half_farm_share_* were removed in 0.13. For where a team was, use gem.region_of with the players' position_log; for an example of answering a question from these facts, see the Roshan recipe.

Why this is experimental

The replay does not contain a native rosh_conversion field. This analysis joins already-parsed facts:

  • Roshan kills and Aegis pickup, steal, or deny events
  • fight windows
  • towers and barracks destroyed
  • team gold and XP advantage curves
  • sampled hero positions and observer-ward placements
  • Tormentor kills and buybacks

The facts are observed replay data, but their association with one Roshan (the window boundaries) is an analytical choice. Treat the output as an explainable comparison, not proof that Roshan caused every later event.

Attribution and windows

An Aegis event is associated only when it occurs between the Roshan kill and the earliest of:

  • 30 seconds after the kill
  • one tick before the next Roshan
  • game end

For a pickup or steal, the conversion team is the holder's team. The ownership horizon runs from pickup until the earliest of five minutes, the next Roshan, or game end. A holder death inside that horizon is treated as an inferred Aegis consume. aegis_fate_source distinguishes that inference from a denial event, nominal expiry, game-end boundary, next-Roshan boundary, or missing event.

Item-entity deletion was investigated but is not a stronger lifecycle signal: the entity can disappear on pickup or later removal and does not reliably separate consumption, natural expiry, transfer/drop, and game-end cleanup. gem therefore retains the bounded holder-death inference and states its provenance instead of presenting it as observed fact.

The analysis window may continue for up to 120 seconds after Aegis ends so that the immediate aftermath remains visible. If the inferred consume occurs inside a detected fight, that whole fight is included. The result is always capped at the next Roshan and game end, so one event cannot be credited to two Roshan windows.

When Aegis is denied or no reliable holder is available, gem uses the immediate three-minute post-Roshan window and marks the profile partial or unavailable. If the Roshan killer's team can be resolved, that team remains the comparison side; otherwise team-dependent values are unavailable rather than reported as zero.

Team attribution prefers the Source 2 protocol attacker_team, then player ID, then a unique damage-source hero, then a unique attacker hero, then an explicit npc_dota_goodguys_* / npc_dota_badguys_* attacker allegiance for lane creeps. The selected source is exposed as roshan_team_source or conversion_team_source. Unknown structure and Tormentor killers remain unattributed; they are counted in the profile and never silently credited to the conversion team. A structure killed by its owning team is treated as a deny and credited to neither side.

Differential profile

Every differential is signed from the conversion team's perspective:

conversion team value - opponent value

A positive value means the conversion team had more; a negative value means the opponent had more.

Fights

fights won by conversion team - fights won by opponent

Drawn or unknown-winner fights are reported separately and do not change the differential.

Fight association uses the engagement-start evidence from fight positioning, rather than the padded detector window alone. fight_evidence records the fight index, whether the engagement was already underway at the Roshan boundary, engagement-start source, first-death/end ticks, winner, and active participant IDs split by side.

Structures

Each destroyed structure contributes a transparent, tier-aware value:

StructureValue
Tier 1 tower1
Tier 2 tower2
Tier 3 tower3
Tier 4 tower4
Barracks4

The profile reports both teams' tower and barracks counts, both weighted values, and their signed difference. Actual structures are always shown beside the weighted comparison.

Net worth and XP

Gold and XP use the match-level radiant_gold_adv and radiant_xp_adv curves, which come from total-earned team fields. Values are signed for the conversion team and sampled immediately after the acquisition boundary so the direct Roshan bounty does not masquerade as downstream conversion.

For each resource the profile reports:

  • advantage at the start and end of the analysis window
  • total swing (end - start)
  • swing per minute

Missing or incomplete curves produce None / Unavailable, never a fabricated zero.

Forward wards

conversion observer wards in the enemy half
- opponent observer wards in the conversion team's half

Only observer wards with known coordinates inside the analysis window count.

Tormentor

conversion-team Tormentor kills - opponent Tormentor kills

Tormentor remains a separate secondary-objective dimension. It is not assigned the same weight as towers or barracks. Events without a resolvable killer team are excluded rather than guessed.

Buybacks

Buybacks are listed on the timeline. They are not a differential.

The HTML report's Roshan tab shows only the kill and Aegis lifecycle facts from these records (see Match Reports). The differential profile is available in Python and the DataFrame exports.

Status and missing data

Each profile carries a status and concrete reasons:

  • complete: the team and main evidence needed for the profile are available
  • partial: attribution exists, but one or more evidence streams are incomplete
  • unavailable: the conversion team cannot be established or the comparison cannot be made responsibly

A genuine zero means the replay contained the relevant evidence and the event did not occur. Unavailable means the evidence was absent or insufficient.

Exports

build_dataframes(match, include=["analysis"]) exports:

  • roshan_conversions: one flat row per Roshan, including provenance, raw differentials and evidence status
  • roshan_conversion_fights: one flat row per associated fight with engagement provenance, relation, and participant IDs

Direct gem.to_dict(build_rosh_conversions(match)) serialization preserves the nested public records and enum values as strings.

Current limits

  • Aegis consumption remains inferred from the holder's first hero death inside the ownership horizon; transfer/drop is not independently observable.
  • Engagement start is evidence-aware but still falls back to first death when earlier damage evidence is unavailable.
  • Forward wards are counted by map half (gem.region_of), not by lane topology or fog state.
  1. Reports
  2. Roshan Conversion Calibration
  3. Farming Patterns
  4. Point-Vision Evidence
  5. Vision Modifiers