Stat Tracking
Heir connects directly to your game servers and records matches as they happen — per round, per map, per match — then classifies, rates, and publishes them without anyone filing a report.
Live for Mordhau (Skirmish and Hardpoint) and Chivalry: Medieval Warfare (Last Team Standing and Team Objective). The tracking layer is a per-game module — each game declares its own identity system, stat schema, and rating skill set; see the Games section for what each title records.
What gets recorded
- Per player: kills, deaths, team kills, suicides, score, and objective time
- Per round (Skirmish): who won, who participated, and 1vX clutch situations
- Per map: mode, map, final score, winner, and which team held each side
- Per match: every game in the session, linked to the league match when scheduled
Team kills are tracked separately and never count toward kills or K/D. Suicides count as deaths.
Minimum match format
Not every game on a server is worth recording — two players dueling after a pug, a solo warmup, or a half-empty lobby shouldn't land in the stats. Each league sets a minimum match format, per side:
| Setting | Meaning |
|---|---|
1v1 | record everything, down to a duel |
2v2 | record 2v2 and larger |
3v3 | default — record 3v3 and larger |
… up to 8v8 |
A game only opens for recording once both sides can field that many — i.e.
format × 2 players are seated on teams. Anything smaller is ignored: no stat
match, no in-game "tracking active" announcement, nothing. This is set on the web
(Manage → Play → Stat tracking → Minimum match format) or with
/config min-team-size.
The minimum applies to every match, including a ref's .start — an organizer
can't force-track a game below the league's format. The default of 3v3 is also the
smallest a match can be to classify as anything but a pug (team classification needs
three linked players per side), so leagues that never touch the setting keep exactly
the behavior they always had.
Shared servers respect each league's own minimum. Two leagues can track the same
box — each runs its own collector against one shared connection. If one league is
3v3 and another is 2v2, a 2v2 played there is recorded only by the 2v2
league; the 3v3 league ignores it. Neither league sees the other's data.
Setting it up
Register a server
/server add — or Add server on the Tracked servers card at
Manage → Play → Servers on the web — stores the address and RCON credentials
(encrypted at rest) and the collector connects within a minute. /server status
is a live health check.
Have players link their accounts
/identity link connects a Discord account to an in-game ID. Stats recorded before
someone links are backfilled automatically once they do.
Set your minimum format
Manage → Play → Stat tracking (or /config min-team-size)
decides the smallest game the collector records. Default is 3v3.
Pick an approval mode
/config stats-approval decides whether tracked matches count immediately (auto)
or wait for an organizer's approval (manual, the default).
Point events at a channel
/config stats-channel receives live embeds — tracking started, round end, map end,
match finished — each with Void and Approve buttons.
Competitive, casual, overall
Every tracked match is classified automatically — and a match is a run, not a server session. When a game finishes, Heir works out which rostered teams (if any) held each side from the game's complete final roster. Consecutive games with the same pairing belong to the same match; the moment the pairing changes — a scrim starts after warmup pugs, or different teams take over — the current match closes and a new one begins. Warmup and the real thing never share a page.
- Competitive — the run's pairing matches a scheduled fixture on the active schedule
- Scrim — a run of two rostered teams playing each other, unscheduled
- Pug — everything else
Because the pairing comes from each game's full roster rather than a moment-in-time player list, subs joining, relogs between maps, and players drifting out at the end of the night can't flip a match's label.
Those roll up into three stat buckets — Competitive, Casual (scrims and
pugs), and Overall — which are filters applied at query time, so
reclassifying a match with /stats reclassify moves it instantly without
touching the recorded data.
Automatic results
A session becomes a league match one of three ways:
- A ref binds it with
.start <code>— every unplayed match on the published schedule shows a short code (likeK7QX) for exactly this. - Both scheduled teams are detected playing near their match time, decided once when their run's first game finishes. A fixture only arms detection from 45 minutes before its scheduled start until 3 hours after — the same two teams pugging together earlier in the evening stay a scrim.
- A ref forces it with
.start force, for a league match that isn't on the schedule.
All three still respect the league's minimum match format — a game below it never opens, so there's nothing to arm.
The moment a session arms as a league match, the server announces it in game
with both team names — LEAGUE MATCH armed: X vs Y — the result records automatically when the series is decided. Not a league match? .void — so a
false positive can be killed before it decides anything.
An arbiter then watches the series. At the configured length
(/config series-format, default best of 5) the result is recorded through the
same pipeline as /report-match: elo applied, schedule updated, and the stat
match linked to the league match.
In manual approval mode the result waits until an organizer approves the stat
match, so a bad tracking session can never move elo on its own.
In-game controls
Referees — anyone holding the configured ref role, plus organizers and admins — can control tracking from in-game chat:
.start <code>— bind the session to a scheduled match by the code shown on the published schedule. Knowing the two teams up front means one linked player is enough to detect which side is which..start— binds automatically when exactly one scheduled match is within a couple of hours; with several it lists their codes. With nothing scheduled it does not start anything — recording an unscheduled league match takes a deliberate.start force..red/.blue— answer the "What color team is X?" prompt when a map has no linked players to detect sides from. Sides re-detect every map (teams swap), so this can be re-issued any time to correct the current map..stop— finalize the session immediately.void— close and void the session (false start, remake, false positive).status— re-announce the tracking state, including the bound match and the current map's Red/Blue mapping
The ref role must be a specific role, never @everyone — Heir rejects setting
@everyone as the ref role, since it would make every linked player a ref.
Ref identity is matched through their linked in-game ID, so refs must have run
/identity link. Every ref action is echoed to the stats channel with who did it.
Related
/server— server registry and health/stats— player stats, leaderboards, imports/config— minimum format, approval, ref role, series length/identity— linking in-game accounts- Ratings & Elo — how tracked stats become a rating