116 lines
5.8 KiB
Markdown
116 lines
5.8 KiB
Markdown
# The Level Calculator
|
|
|
|
## Introduction
|
|
|
|
The level calculator turns a player's raw performance on a handful of physical
|
|
tests — how much they can squat, how fast they run a mile, how far they can
|
|
broad jump — into a single, easy-to-understand number: their **level** for
|
|
each attribute, and an overall player level.
|
|
|
|
The intent is to make performance legible and comparable across people of
|
|
different ages, weights, and genders. A 45-year-old squatting 225 lb and a
|
|
22-year-old squatting 225 lb are not doing the same thing physiologically, so
|
|
raw numbers alone aren't a fair yardstick. Instead, each activity is measured
|
|
against **standards**: tables of "what performance corresponds to what level"
|
|
for a given age/weight/gender, built from published strength and athletic
|
|
performance research. A player's level for an activity is found by comparing
|
|
their performance against the standard for players like them; their level for
|
|
an attribute (e.g. Strength) is the rounded average of their levels across
|
|
that attribute's activities; their overall player level is the rounded average
|
|
across all four attributes.
|
|
|
|
The guiding principle is: **use real external standards as ground truth
|
|
wherever they exist, and only ever generate/extrapolate around that ground
|
|
truth** — never invent numbers from nothing. Standards data is also fully
|
|
config-driven (via `apps/portal`'s calculator config/dataset pages), so it can
|
|
be tuned or replaced without a code change.
|
|
|
|
## Attributes and activities
|
|
|
|
| Attribute | Activity | Unit |
|
|
| --- | --- | --- |
|
|
| Strength | Back Squat, Deadlift, Bench Press | kg |
|
|
| Power | Broad Jump | cm |
|
|
| Endurance | 1 Mile Run | ms |
|
|
| Agility | 3 Cone Drill | ms |
|
|
|
|
## Sources
|
|
|
|
The raw standards tables (before any generation/extrapolation) come from:
|
|
|
|
| Activity | Source |
|
|
| --- | --- |
|
|
| Back Squat, Deadlift, Bench Press | [Lon Kilgore Strength Standard Tables (2023)](http://lonkilgore.com/resources/Lon_Kilgore_Strength_Standard_Tables-Copyright-2023.pdf) |
|
|
| 1 Mile Run | [runninglevel.com — 1 mile times](https://runninglevel.com/running-times/1-mile-times) |
|
|
| Broad Jump | [nrpt.co.uk — broad jump power test](https://nrpt.co.uk/training/tests/power/broad.htm) |
|
|
| 3 Cone Drill | [nflsavant.com combine data](https://nflsavant.com/combine.php) |
|
|
|
|
These are also recorded per-activity as a `source` field on each activity's metadata
|
|
in the seeded standards dataset
|
|
(`apps/api/src/database/seed-data/standards-config.json`), which is what's
|
|
actually loaded at runtime — the config is editable from the portal, so that
|
|
seed file (and this document) may drift from whatever standards are live.
|
|
|
|
## How a level is calculated
|
|
|
|
`LevelCalculator.calculate` (`packages/calculator/src/index.ts`):
|
|
|
|
1. For each activity performance the player submitted, look up the standard
|
|
for that activity at the player's exact age/weight/gender (interpolated —
|
|
see below), and find the level whose value is numerically closest to the
|
|
player's performance (`findLevel`).
|
|
2. Average the levels of all activities belonging to the same attribute,
|
|
rounded to the nearest whole level. That's the attribute level.
|
|
3. Average all four attribute levels, rounded, for the overall player level.
|
|
|
|
If any required input is missing (a metric, or a performance of `0` or less),
|
|
the calculator returns level `0` rather than guessing.
|
|
|
|
## How the standards tables are built
|
|
|
|
The raw source data only covers a handful of discrete levels, ages, and
|
|
weights. `Standards` (`packages/calculator/src/index.ts`) expands that into a
|
|
continuous table through a fixed pipeline, run once per config:
|
|
|
|
1. **Stretch** — the raw data defines 5 base levels. To support fewer/more
|
|
levels below/above those 5, an exponential-decay curve
|
|
(`A·e^(-B·i) + C`) is fit (via Levenberg-Marquardt) to the ratio between
|
|
consecutive levels, then used to extrapolate additional levels in either
|
|
direction, per `stretch.lower` / `stretch.upper` config.
|
|
2. **Expand / compress** — every standard's level count is resampled to a
|
|
single configurable `maxLevel`: expansion linearly inserts intermediate
|
|
levels, compression proportionally resamples down.
|
|
3. **Skew** — each activity has a `difficultyModifier` multiplier applied
|
|
uniformly across its levels, to make an activity easier or harder relative
|
|
to its source data.
|
|
4. **Age generation** — for ages missing from the source data, a standard is
|
|
derived from the nearest reference age using a parabolic falloff centered
|
|
on a configurable `peakAge` (steepness controlled by `ageModifier`,
|
|
clamped to a `[0.2, 10.0]` multiplier), scaled against average bodyweight
|
|
for that age (`avg-weights.json`). Real data always takes precedence over
|
|
generated data.
|
|
5. **Weight generation** — for weights missing from the source data, a
|
|
standard is derived from the reference weight using allometric scaling:
|
|
`newLevel = refLevel * (weight / refWeight) ^ weightModifier`. Again, real
|
|
data always takes precedence.
|
|
|
|
## Finding a player's standard
|
|
|
|
Given a player's exact age/weight/gender, `interpolateByAgeAndWeight` performs
|
|
bilinear interpolation across the nearest surrounding age and weight entries
|
|
in the (by now fully generated) standards table, producing a standard specific
|
|
to that player. `findLevel` then maps their submitted performance onto the
|
|
nearest level in that standard.
|
|
|
|
## Where this lives in code
|
|
|
|
- `packages/calculator/src/index.ts` — `LevelCalculator`, `Standards`
|
|
- `packages/calculator/src/models.ts` — schemas/types (`StandardsData`,
|
|
`StandardsParams`, etc.)
|
|
- `packages/calculator/src/avg-weights.ts` + `data/avg-weights.json` —
|
|
average bodyweight by age/gender, used in age generation
|
|
- `apps/api/src/services/calculator.ts` — loads the active `StandardsConfig`
|
|
from the database and constructs `LevelCalculator`
|
|
- `apps/api/src/database/seed-data/standards-config.json` — seeded standards
|
|
data/params, including per-activity `source` citations
|