This page contains no advice. Phase 1 builds the data foundation only: it turns each Shiv item purchase into a record of what was knowable at the moment of the purchase and what happened in the two and five minutes afterwards. Nothing has been compared, tested for significance, or ranked. There is deliberately no "buy this instead of that" here, and there will not be until the statistical work in a later phase says something survives.
Current size: 54,824 decision records from 3,393 hydrated matches. Collection is incremental and deliberately slow; this page reflects whatever exists right now.
| Hero | Shiv (id 19, resolved from /v1/assets/heroes/by-name/Shiv) |
|---|---|
| Index endpoint | /v1/matches/metadata?hero_ids=19&game_mode=normal |
| Hydrate endpoint | /v1/matches/metadata?match_ids=… with info, player info, items, stats, objectives and death details |
| Matches indexed | 7,162 |
| Matches hydrated | 3,393 |
| Hydration failures | 6 |
| Excluded at discovery | 0 |
| Match window covered | 2026-07-06 00:33:09 to 2026-08-18 03:52:27 |
| Modes kept | Ranked (2,552), Unranked (841) |
| Date buckets | 2026-07 (658), 2026-08 (2,735) |
| Skill rank available | 2,986 of 3,393 (88.0%) |
Two passes are mandatory rather than tidy: combining hero_ids with any player include returns HTTP 500 from the API, verified against every include combination. Hydration therefore addresses matches by explicit id, which is exactly what the index pass produces. Street Brawl and any non-standard mode are dropped at discovery, the same rule the coaching history uses.
| Decision records | 54,824 |
|---|---|
| Matches represented | 3,392 |
| Distinct Shiv players | 3,047 |
| Distinct items bought | 147 |
| Decisions per match | 16.2 |
| Median purchase time | 16:42 |
| Slot | Decisions | Share |
|---|---|---|
| spirit | 31,924 | 58.2% |
| vitality | 21,073 | 38.4% |
| weapon | 1,827 | 3.3% |
| Tier | Decisions | Share |
|---|---|---|
| 1 | 12,025 | 21.9% |
| 2 | 19,001 | 34.7% |
| 3 | 15,016 | 27.4% |
| 4 | 8,782 | 16.0% |
| Item | Slot | Tier | Cost | Times bought |
|---|---|---|---|---|
| Radiant Regeneration | spirit | 3 | 3,200 | 3,271 |
| Healbane | vitality | 2 | 1,600 | 3,094 |
| Mystic Regeneration | spirit | 1 | 800 | 3,071 |
| Extra Charge | spirit | 1 | 800 | 3,023 |
| Compress Cooldown | spirit | 2 | 1,600 | 2,515 |
| Extra Regen | vitality | 1 | 800 | 2,473 |
| Mystic Vulnerability | spirit | 2 | 1,600 | 2,409 |
| Torment Pulse | spirit | 3 | 3,200 | 2,380 |
| Superior Cooldown | spirit | 3 | 3,200 | 2,129 |
| Escalating Exposure | spirit | 4 | 6,400 | 2,114 |
| Healing Booster | vitality | 2 | 1,600 | 2,070 |
| Dispel Magic | vitality | 3 | 3,200 | 1,962 |
Frequency only. A common purchase is not a good one, and this page makes no claim either way.
One real record, unedited: Slowing Hex bought at 6:15 in match 100108683.
| State field (knowable at purchase) | Value |
|---|---|
| net_worth | 4050 |
| net_worth_source | stats_at_or_before |
| stats_staleness_s | 15 |
| level | 8 |
| kills | 0 |
| deaths | 1 |
| assists | 1 |
| deaths_last_2m | 1 |
| last_hits | 16 |
| player_damage | 1626 |
| team_soul_advantage | -1160 |
| lobby_economy_rank | 3 |
| objectives_lost | 0 |
| objectives_taken | 0 |
| existing_item_count | 3 |
| existing_items | [3862866912, 1439347412, 3776945997] |
| ability_points_spent | 6 |
| enemy_heroes | [1, 16, 31, 35, 63, 66] |
| enemy_items_observed | [7409189, 84321454, 98582110, 668299740, 968099481, 1009965641, 1437614329, 1548066885, 1998374645, 2010028405, 2356412290, 2462046703, 2829638276, 3399065363, 3403085434, 4139877411] |
| Outcome field (after the purchase) | Value |
|---|---|
| horizon_ok_2m | 1 |
| horizon_ok_5m | 1 |
| stats_samples_2m | 0 |
| stats_samples_5m | 1 |
| deaths_next_2m | 0 |
| deaths_next_5m | 0 |
| souls_gained_next_2m | |
| souls_gained_next_5m | 2609 |
| damage_next_5m | 1414 |
| damage_taken_next_5m | 1350 |
| team_soul_change_next_5m | 555 |
| objective_events_next_5m | 3 |
| won_match | 0 |
| Net worth measured from a stats sample | 50,448 of 54,824 (92.0%) |
|---|---|
| Net worth unobserved (purchase before the first sample) | 4,376 (8.0%) — stored as NULL, never as 0 |
API net_worth_at_buy agrees with our value | 50,189 of 50,448 measured (99.5%) |
| Median state staleness | 113 s |
| Worst state staleness | 299 s |
| Lobby economy rank resolvable | 50,448 (92.0%) |
| Full 2-minute future exists | 53,135 (96.9%) |
| Full 5-minute future exists | 49,457 (90.2%) |
| Soul gain unmeasurable at 2 min (no stats sample in window) | 25,574 (46.6%) |
| Soul gain unmeasurable at 5 min | 5 (0.0%) |
| Death count unmeasurable at 2 min | 299 (0.5%) |
| Records with both a measured state and a full 5-minute future | 45,081 (82.2%) |
The API's items.net_worth_at_buy is an as-of join onto the stats series, and for a purchase made before the first sample it back-fills the player's final net worth. Measured on live data during design: 44 of 44 purchases after the first sample matched an independent as-of lookup exactly, and 12 of 12 purchases before it carried the end-of-match value. The field is therefore recomputed here and the API value kept only as the cross-check reported above.
Stats are sampled every ~180 seconds. Souls, damage, level and lobby economy rank in a state are up to three minutes stale; the exact lag is stored per record as stats_staleness_s. Purchases, deaths and objectives carry exact timestamps and do not have this problem, which is why they are the load-bearing measurements.
A two-minute outcome window often contains no stats sample. Soul and damage deltas are then recorded as NULL rather than 0. A fabricated zero would read as "this purchase earned nothing", which is a different claim from "we cannot measure it".
Skill rank is mostly unavailable. average_badge is frequently null on this endpoint, and extra_player_columns=player_rank_initial_display_rank returns HTTP 500. Per-player rank exists only in the ClickHouse SQL endpoint, which allows 2 requests a minute. Until that is joined in, the dataset cannot be split by skill bracket, so any pattern found in it may simply be a pattern of the average lobby.
The sample is only as spread out as the collector has managed so far. Shiv is popular enough that a single 200-row index page can cover twenty minutes of real time, so a naive backwards walk never leaves the current hour - the first run collected 105 matches spanning 26 minutes. The collector now steps its cursor by a fixed stride and hydrates an evenly spaced slice of what it has indexed, but the window actually covered is reported above and should be read before trusting anything about "the current meta".
There is no patch identifier in the API. Records are bucketed by calendar month as a proxy. The raw start time is stored, so a real patch calendar can be applied later without refetching anything.
Purchases made before the first stats sample have no observable net worth. Those states are stored with NULL rather than a guess, and the rate is reported above. They are early-game buys, so excluding them biases the set toward later decisions - a trade-off to make deliberately, not silently.
Ability points are not purchases and are excluded. The items array mixes shop purchases, ability points and weapon rows under one name. They are separated using the asset catalogue's item type. Ability choices are a real strategic decision, but a different one, and are out of scope.
This is observational data. Nobody was assigned an item. Players who buy a given item differ from those who do not in ways this dataset does not record - and skill, the most important of them, is exactly the variable that is missing. Any comparison made later has to carry that caveat, and no comparison has been made yet.
18 tests, all passing.
| Test | What it holds down |
|---|---|
test_state_is_a_pure_function_of_the_past | Rebuilds every state from a match physically truncated at the decision time and requires an identical result. Any leak - a final stat, a later purchase, an objective that has not fallen yet - makes the two disagree. |
test_net_worth_is_recomputed_not_taken_from_the_leaky_api_field | Pins the API back-fill bug: the fixture reproduces it, extraction must not use it. |
test_no_final_match_values_appear_in_any_state | The synthetic match ends on absurd values (99,999 souls, 50 kills) so a leak is unmistakable rather than plausible. |
test_state_reconstructed_before_the_purchase | Owned items exclude the item being bought, and exclude anything already sold. |
test_enemy_build_reconstruction | Enemy items are those bought by the decision time, team-relative, abilities excluded. |
test_death_counting | Exact death timestamps, correct half-open windows. |
test_horizon_flags | A purchase too near the end is marked, not silently truncated. |
test_windows_without_a_sample_are_none_not_zero | An unmeasurable window must not be reported as a measured zero. |
test_soul_and_damage_deltas | Deltas read from the correct pair of samples. |
test_team_advantage | Team soul advantage and lobby economy rank at the cutoff. |
test_objective_events | Objectives before and after the cutoff; a never-destroyed objective is not counted as destroyed at t=0. |
test_purchase_ordering_and_timestamps | Time order, stable tie-breaking, and sequence numbers that survive reclassification. |
test_abilities_and_repeat_acquisitions_are_not_decisions | Ability points, and re-acquiring something already owned, are not new choices. |
test_upgrade_id_is_not_used_to_classify_purchases | Pins a bug that was live in this code: the API's upgrade_id means different things in different patch eras (0 on old shop rows, 1 on current ones), and using it as the purchase filter silently discarded every modern purchase. Classification comes from the item catalogue and nothing else. |
test_hero_identification_and_player_selection | Only Shiv players, both of them when both teams have one, and no teammate rows. |
test_parallel_array_payload_shape_is_accepted | Both payload encodings produce identical decisions. |
test_never_destroyed_objectives_are_not_counted_at_zero | Guards the sentinel value. |
test_won_match_and_decision_ids | Result attribution and id uniqueness. |
| Deliberate bug introduced | Result |
|---|---|
Classify purchases by upgrade_id again (the bug that actually shipped) | caught by 15 tests |
| Trust the leaky API net worth | caught by 1 test |
| Read the next stats sample instead of the previous one | caught by 5 tests |
| Observe every enemy item regardless of time | caught by 2 tests |
| Count sold items as still owned | caught by 1 test |
| Report 0 for an unmeasurable window | caught by 1 test |
A test suite that passes proves nothing on its own. Each of these bugs was introduced into the extractor on purpose and the suite was re-run; all six were caught, and the code was restored. The first one is not hypothetical: it was the real behaviour of this extractor against live data, and it produced zero decisions from 105 perfectly good matches.
Phase 2 is the statistical engine: grouping decisions by comparable game states, measuring outcome differences with confidence intervals and correction for multiple comparisons, and reporting how much of any difference survives once the obvious confounders are accounted for. Until that exists and reports something that holds up, this dataset describes decisions - it does not evaluate them.