Skater Attributes & Stats#
How a skater's four attributes are stored, modified, combined into OVR, and fed into gameplay.
1. The attributes#
Enum value (Data.EForwardStat) |
Data.BasicStats |
Stored field (ForwardData) |
In-game meaning |
|---|---|---|---|
Overall = 0 |
– | (computed) | Average of the four below |
Speed = 1 |
Speed = 3 |
speed |
Top speed and acceleration (see skating-and-movement.md) |
Accuracy = 2 |
ShotAccuracy = 1 |
shotAccuracy |
Shot aim spread |
ShotPower = 3 |
ShotPower = 0 |
shotPower |
Shot speed |
Checking = 4 |
Checking = 2 |
checking |
Bodycheck strength and resistance. Also skater "weight" |
There are two enums with different orderings. EForwardStat is used by StatBonus/UI and BasicStats is used by ForwardData/ForwardStats. Goalies use EGoalieStat, covered in goalies.md.
ForwardData also has a set of hidden stats (_hiddenSpeed, _hiddenShotPower, …). The value shown in the UI is Display<Stat> = stat − hiddenStat, and GetDisplayOverallStats() averages those display values. The real stat, hidden part included, is what gameplay uses.
Source: Data.EForwardStat, Data.BasicStats, Data.ForwardData (get_DisplaySpeed, GetDisplayOverallStats).
2. Ranges and caps#
Stored (permanent) values#
ForwardData.SetSpeed/SetShotPower/SetChecking and IncrementStats clamp the stored base value:
| Attribute | Stored range |
|---|---|
| Speed | 0 – 250 |
| Shot Power | 0 – 200 |
| Checking | 0 – 500 |
| Accuracy | 0 – 100 |
In-match (final) values#
ForwardStats clamps again after all modifiers, injury and fatigue:
| Attribute | Final clamp | Notes |
|---|---|---|
| Accuracy | 0 – 100 | GetAccuracy |
| Checking | 0 – 500 | GetChecking |
| Shot Power | 0 – 300 | GetShotPower (total shot-power stat) |
| Speed | none in GetSpeed |
Movement uses the cached speed clamped to 0–300, then 0–200 inside the speed curve. Speed above 200 does nothing for movement. |
Source: ForwardData::SetSpeed (cmp 0xFA), SetShotPower (0xC8), SetChecking (0x1F4), IncrementStats (Accuracy 0x64); ForwardStats::GetAccuracy/GetChecking/GetShotPower/GetMaxSpeed.
3. Overall (OVR)#
OVR = RoundToInt( (max(0,Speed) + max(0,Accuracy) + max(0,ShotPower) + max(0,Checking)) × 0.25 )
This is a plain average with negative values counted as 0. T2TUtils.GetOverall, ForwardData.GetOverallStats and GetRealAverageStats all use the same code. In-match, ForwardStats.GetOverall uses the same 0.25 average but on modified stats (see §5). The rounding helper is presumably Mathf.RoundToInt (inferred).
When the UI turns a single-attribute bonus into an OVR change, it uses bonus / 4 rounded (DetailedSkaterDataUtils.GetStatBonusImpactOnOverallStat, SkaterStatsPreview.GetOverallBonusFromSingleForwardStatBonus). So +8 Speed shows as +2 OVR.
Source: Tape2Tape.T2TUtils::GetOverall, Data.ForwardData::GetOverallStats, Skaters.ForwardStats::GetOverall.
4. How "+X OVR" works#
An OVR bonus is not a separate number. It is added in full to every one of the four attributes:
- Permanent:
ForwardData.IncrementOverallStats(delta)callsIncrementStatswith the samedeltafor Checking, Speed, Accuracy and ShotPower, so the average rises by exactlydelta. - Temporary (in match): OVR relics and talents register the same
x => x + bonusfunction into all four modifier lists. For example,ParticipationTrophyController.AddStatspushes intooverallCheckingEvent,overallShotAccuracyEvent,overallShotPowerEventandoverallSpeedEvent.
So "+10 OVR" means +10 Speed, +10 Accuracy, +10 Shot Power and +10 Checking. Each attribute's own cap still applies. For example, Accuracy is capped at 100, so a +10 OVR bonus on a 95-accuracy skater adds only +5 accuracy.
Balanced Talent#
If the skater has the Balanced Talent ("All attributes are balanced evenly"), two things change:
- Permanent gains are spread.
IncrementStats(stat, delta)becomesIncrementOverallStats(RoundToInt(delta × 0.25)). A +8 Speed reward becomes +2 to every attribute. - In-match values are averaged. Every attribute getter returns
ForwardStats.GetOverall(stat)instead of its own stat. That function takes base OVR, runs it through each attribute's modifier list, and averages the four results. All four attributes therefore show the same value.
Source: ForwardData::IncrementOverallStats, ForwardData::IncrementStats (balance branch ×0.25), ForwardStats::.ctor (_isBalanced), ForwardStats::GetOverall, Rogue.ParticipationTrophyController::AddStats.
5. In-match stat pipeline#
Each attribute getter (ForwardStats.GetSpeed, GetAccuracy, GetChecking, GetTotalShotPowerStats) follows the same order:
- Stat lock. If the skater has a
StatLockTalentfor this attribute (e.g. Bedrock sets Checking, Railgun sets Shot Power), the locked value is returned at once. Steps 2–7 are skipped. - Base. Start from the stored
ForwardDatavalue. - Modifiers.
GetStats(base, list)folds the attribute's list ofFunc<int,int>modifiers (overallSpeedEvent,overallShotAccuracyEvent,overallShotPowerEvent,overallCheckingEvent) in order. In practice each modifier isx => x + bonus, so flat bonuses simply add up. Relic and talent controllers add and remove these functions during a match. With Balanced Talent,GetOverallis used here instead (§4). - Speed only: if the team owns a certain relic (generic type not resolved) and the skater is not Balanced, the modifier step is skipped and the raw base speed is used. - "Original" relics (+50% bonuses).
stat = stat + (stat − base) / 2 (integer division, truncates toward 0)This applies to Speed with Original Skate, Accuracy with Original Stick, Checking with Original Glove, and Shot Power with Original Puck. It is skipped for referees (SkaterType.Referee = 5). Because it scalesstat − base, it also enlarges penalties. - Injury (only when
includeInjury). See §8. - Fatigue (Double Decker squad only). See §9.
- Clamp (§2).
At match start and on refresh, UpdateCachedStats() caches max speed, supernova max speed, speed with and without injury, and accuracy.
Source: Skaters.ForwardStats::GetSpeed/GetAccuracy/GetChecking/GetTotalShotPowerStats/GetStats/AddOriginalRelicModifier/UpdateCachedStats; Rogue.Powerups.Chaos.StatLockTalent.
6. StatBonus and AddStatBonuses / AddConditionalStatBonuses#
struct StatBonus {
ESkateType SkaterType; // forward or goalie
EForwardStat StatForward;
EGoalieStat StatGoalie;
int Bonus;
bool IsConditional; // only active under a condition (e.g. "while tied")
bool IsBlockableStatGain;
}
Every PowerUp/Relic has a virtual AddStatBonuses(IList<StatBonus>, SkaterData, TeamData). A second pass, AddConditionalStatBonuses(..., IReadOnlyList<StatBonus> collectedBonuses), lets an item react to bonuses already collected. These lists feed the stat previews and tooltips (SkaterStatsPreview, SkaterStatBox.CumulateCustomStatBonuses, ForwardStatsPreview). In a StatBonus, StatForward = Overall stands for "+X OVR", which the preview converts with the ÷4 rule above. (inferred) Actual in-match values come from the modifier lists in §5. The two pipelines are kept consistent by each item's controller rather than shared code.
Source: Data.StatBonus, Rogue.Relic/PowerUp::AddStatBonuses/AddConditionalStatBonuses, Tape2Tape.Customization.UI.SkaterStatsPreview.
7. Permanent stat sources#
Training (Conditioning NPC)#
| Event | RewardType |
Bonus | Who |
|---|---|---|---|
RegularPlayerTraining |
0 | +15 to one attribute | one skater |
StatsTraining |
1 | +15 to one attribute | several skaters |
TeamTraining |
2 | +10 (statsBonusForward) |
whole team |
Gains go through ForwardData.IncrementStats. That means stat locks block them, the Balanced spread applies, and caps apply. TrainingEvent.GetModifiedStatBonus multiplies the bonus by (1 + trainingMultiplier [+ trainingMultiplierUpgrade]):
- Can of Worms: 0.5, so training gives +50% (+100% when upgraded).
- Training Whistle: +10 Speed after training (+5 more when upgraded).
Bench Bonus (hidden relic)#
At game end (BenchBonusController.OnGameEnd), skaters who sat on the bench all game get one permanent reward. The reward is an OVR bonus, a single-attribute bonus, or a talent (AddRandomTalent/UpgradeRandomTalent).
| Variant | overallBonus |
single-stat bonus |
|---|---|---|
| Bench Bonus / Bum Bench Bonus | +4 OVR | +8 |
| Gauntlet Bench Bonus | +3 OVR | +6 |
The amount is RoundToInt(bonus × GetBenchBonusMultiplier()). Contributors seen in the multiplier code are ×2 (Double bench bonus relic), ×1.5 (Butt Bench), a ×3 helmet-stack factor (HelmetStackOvrTalent.GetHelmetPowerupMultiplier), and a +X%/100 term (Double-Decker talent). (The mapping of each factor to a specific item is inferred.)
Hot / Cold benchwarmers#
After a run, a benchwarmer can be Hot (HotTalent, +15 OVR) or Cold (ColdTalent, −15 OVR). Both are hidden talents. The Hot & Cold relic grants a random +OVR and −OVR pair at game start.
Source: Rogue.NPC.ConditioningNPC.*Training, TrainingEvent::GetModifiedStatBonus, Rogue.VanillaTraining::get_TrainingMultiplier, Rogue.Relics.TrainingWhistle, Rogue.BenchBonus + BenchBonusController, Rogue.HotTalent/ColdTalent data.
8. Injuries (stat effect only)#
Each attribute is tied to one injury type, and an injured skater loses most of that attribute:
Injury (InjuryType) |
Attribute | Effect |
|---|---|---|
| Eye (0) | Accuracy | stat − min(injuredStatPenalty, stat) with injuredStatPenalty = 100, so Accuracy drops to 0 |
| Shoulder (1) | Checking | stat × injuredStatMultiplier, so ×0.2 |
| Leg (2) | Speed | ×0.2 |
| Arm (3) | Shot Power | ×0.2 |
The multiplier result is rounded. A specific talent (generic type not resolved) skips the injury check entirely. Other InjuryConfig values, for reference: knockout time added 8 s, base new-injury chance on a check 9%, same-team 0.5%, player max 3 natural injuries per act.
Source: ForwardStats::GetStatAfterInjuryCheck (types passed: Speed = 2, Checking = 1, Accuracy = 0, ShotPower = 3), Skaters.Injuries.InjuryConfig data.
9. Fatigue (Double Decker squad only)#
Stamina exists only when playing the "lines" squad (Double Decker Squad). At stamina s (0–1), every attribute is multiplied by:
statMult(s) = 1 − (1 − floor) × (1 − s)² floor = 0.5
The result is rounded. Movement speed has its own, stronger multiplier, described in skating-and-movement.md.
| Stamina | 100% | 75% | 50% | 25% | 0% |
|---|---|---|---|---|---|
| Attribute multiplier | 1.00 | 0.969 | 0.875 | 0.719 | 0.50 |
Source: ForwardStats::GetStatAfterFatigueCheck, Skaters.StaminaConfig, TeamStaminaSystem::Initialize (IsPlayingSquad("lines")).
10. What the attributes drive#
Speed is covered in skating-and-movement.md. The other mappings live in StatsUtil/ForwardStats (p = total shot-power stat, 0–300; a = accuracy):
| Value | Formula |
|---|---|
| Wrist-shot power | max(10, 15 + 35·(p/100)^0.55) |
| Slapshot power | max(13, 20 + 50·(p/100)^0.55) |
| Backhand power | max(8, 12 + 27·(p/100)^0.55) |
| Batting power | 12 + 27·(p/100)^0.55 |
| Max wrist-shot angle | 10 + 0.75·a (wristShotAngleMin/Increment) |
| Max slapshot angle | 5 + 0.45·a |
| Slapshot / one-timer accuracy term | 85·(a/100)^1.5 (exponent read as 1.5; use inferred) |
Weight (GetWeight) |
Checking + 100 |
| Shot-power stat | 0 | 50 | 100 | 150 | 200 | 300 |
|---|---|---|---|---|---|---|
| Wrist | 15.0 | 38.9 | 50.0 | 58.7 | 66.2 | 79.0 |
| Slap | 20.0 | 54.2 | 70.0 | 82.5 | 93.2 | 111.5 |
See critical-shots.md for crits.
Source: Skaters.StatsUtil (data statUtil), ForwardStats::GetWristShotPower/GetSlapShotPower/GetMaxSlapshotAngle/GetSlapShotAccuracy/GetWeight.
11. Skater Size#
Data.SkaterSize: ExtraSmall 0, Small 1, Medium 2, Big 3, ExtraBig 4, ExtraExtraBig 5. The UI labels them XS/S/M/L/XL. The glossary says bigger skaters hit harder, absorb more, and take more space, while smaller skaters accelerate, stop and turn quicker.
Size can change during a run. ForwardData.SetSizeEffect/RemoveSizeEffect stack size steps, and SkaterSizeUtil.GetModifiedSize/Resize apply them (e.g. Shrink Serum).
Verified gameplay effects#
| Effect | XS | S | M | L (Big) | XL | XXL | Source |
|---|---|---|---|---|---|---|---|
Body scale (GetBodySize, × sizeOffsetPercentage 0.9–1.1) |
0.100 | 0.115 | 0.1375 | 0.16 | 0.19 | 0.23 | ForwardData::GetBodySize |
| Relative to M | 0.73 | 0.84 | 1.00 | 1.16 | 1.38 | 1.67 | |
| Supernova (overspeed) top-speed multiplier | ×1.05 | ×1.05 | ×1.00 | ×0.95 | ×0.95 | ×0.90 | ForwardStats::GetMaxSpeedAtSupernova |
| Extra "min turning" (larger = wider turns, inferred) | 0 | 0 | 0 | 0 | +0.02 | +0.04 | ForwardStats::getMinTurning |
Bodycheck size modifiers#
SkaterCollision.GetSizeBonus(checker, target) adds to the knockout score:
| Situation | Bonus |
|---|---|
| Checker is XXL | +60 (always) |
| Checker bigger than target | +30 |
| Same size | 0 |
| Checker smaller than target | −20 |
GetStaggerForceSizeMultiplier scales the stagger force by ×1.25 if the checker is bigger, ×0.75 if smaller, and ×1.0 if equal.
UI-only scale#
SkaterSizeToScaleSettings changes only portrait and preview scale: XS 0.925, S 0.97, L 1.05, XL 1.10, XXL 1.12. SkaterSizeData maps sizes to icons.
Not found: no size term in ForwardStats.GetAcceleration or GetMaxSpeed, and the capsule collider radius is not scaled by size in Forward.SetSize. The glossary's "smaller skaters accelerate/stop quicker" may come from physics mass or animation and was not verified.
Source: Data.ForwardData::GetBodySize, Skaters.SkaterCollision::GetSizeBonus/GetStaggerForceSizeMultiplier (consts SIZE_BONUS_*, STAGGER_FORCE_*), Customization.UI.SkaterSizeToScaleSettings data.
Comments