The space
Embedding
A list of numbers standing in for a player-season, arranged so that
players who played alike end up near each other.
A 64-dimensional unit-length vector produced by the MTNN from 130 input
features. Because every vector has length 1, the dot product between two of them
is their cosine.
assets/mtnn_arch.json
Cosine similarity
How close two players are, on a scale where 1.00 means identical and 0
means unrelated. It is the number behind every comparison on this site.
The cosine of the angle between two embedding vectors. On unit-length
vectors this is the dot product. It ignores magnitude and measures direction only — which
is the point: two players can be similar in shape without being equally productive.
computed in the browser
Era-z / per-100 possessions
Stats adjusted so a 1997 season and a 2025 season can be compared without
the pace of the league doing the talking.
Every rate is computed per 100 possessions, then z-scored
within its own season — so a value says how far from that season's average a
player was, not how the eras differ in raw volume. Clipped at ±4 standard deviations.
assets/season_norms.json
Caveat the file states itself:
three features are not invertible back to a raw rate, because they were
empirical-Bayes shrunk toward the league mean by attempts before z-scoring. For those,
a percentile is honest and a reconstructed percentage is not.
Archetype
One of eight play-style groups the model sorts every player-season into.
They are named after what separates them, not after positions.
Clusters over the embedding, frozen as a 14-dimension game contract so
the game scores against a stable target. The eight, read live from the model file:
assets/mtnn_arch.json → gameArchetypes
Tower
A slice of the network that only looks at one family of stats — shooting,
or defense, or durability — before anything gets combined.
The MTNN reads its 130 features in family groups, each through its own
small sub-network, and fuses the results into the single 64-d embedding. A tower reads
cat([x*m, m]) — the values and a mask saying which were measured.
assets/mtnn_arch.json → towerFamilies
Change over time
Rotation (Procrustes)
How much the whole map of the league twisted between one season and the
next. A big angle means the game re-sorted who counts as similar to whom.
Orthogonal Procrustes alignment between consecutive seasons, computed on
the players who appear in both — so roster turnover cannot manufacture a
rotation. Reported as the mean principal angle of the rotation matrix against identity.
assets/drift.json
Axis drift
How much one particular stat stopped meaning what it used to mean.
1 − |Qii|, the extent to which that feature's own
axis had to move during the alignment. High drift on three-point accuracy in 2019-20 is
the league re-sorting itself around shooting, not shooters getting better overnight.
assets/drift.json → axisDrifts
Residual
What the rotation could not explain — the part of the change that
is not a twist of the map but a genuine reshuffle of players.
Normalized Frobenius norm of the difference remaining after alignment.
No scaling is applied, because the spaces are already z-normalized.
assets/drift.json
Era twin
The player from a different decade who played most like this one.
Nearest career in another decade by cosine between signature seasons,
restricted to careers with at least four charted seasons.
assets/eratwins.json
Caveat: that file was built
in the older 48-dimensional embedding, as its own method line says. The model the
game ships now is 64-dimensional. The twins are a snapshot of an earlier space.
Is it any good?
Adjacent-season retrieval (top-1, top-5)
The test: hand the model one season of a player's career and ask it to
find that same player's next season out of ~13,000 candidates. If the model
understands play style, the same person a year later should be the nearest thing to them.
Hit if season N+1 ranks in the top k, self excluded, ties counted
against the target. Measured over …
eligible pairs.
Loading measured scores…
assets/eval_scoreboard.json
Held-out split
The only scores that really count — seasons the model never saw while
learning.
Target season ≤2021 is train, 2022-23 is validation, ≥2024 is test.
Train-split pairs were training positives, so their score is inflated by construction and
should never be quoted as accuracy.
assets/eval_scoreboard.json → protocol.splits
Transparent 14-d baseline
The dumb version, kept around on purpose. If the neural net cannot beat
plain era-adjusted stats, it has not earned its place.
Cosine over the frozen 14-dimension era-z game profile, evaluated on the
identical pairs and protocol.
Loading…
assets/eval_scoreboard.json
Explaining a prediction
Gradient × input attribution
Which input stats moved a given prediction, and in which direction.
aj = xj · ∂(target)/∂xj over
the raw input features. A local linearization — it says how the output responds
to a nudge, not what would happen if a feature were removed. It is not SHAP.
This entry used to add "and is not labelled as such anywhere on this site", which was
wrong — and wrong in the direction that matters, because it told you not to look for
something that is there. SHAP is on this site, for a different model:
/methods reports mean|SHAP| for the
draft model zoo — global 1245.3, log_overall 398.7, round 187.2, cap_growth 6.3 —
and those figures are in assets/data/model_zoo_eval.json under
glass_box.shap_global_mean_abs, computed as coefficient×(x−mean)
for the linear models and an ablation approximation for the DeepMLP. Two different models,
two different methods. The bars on the model page are the MTNN's local linearization; the
SHAP figures on /methods belong to the draft zoo. Neither is the other.
assets/mtnn_attr_pop.json → method · assets/data/model_zoo_eval.json → glass_box
Coverage, and why zero does not mean zero
Some stats were not tracked in older seasons. Where a stat is missing, the
model is told it is missing — so it contributes nothing, which is not the same as
mattering nothing.
A tower reads cat([x*m, m]), so a masked feature has exactly
zero gradient. A zero attribution means NEVER MEASURED, not "no effect." Read it
against the coverage figure, which is the share of seasons where the feature exists.
assets/mtnn_attr_pop.json → maskedNote, coverage
The game
Chimera
A blend of real player-seasons that the puzzle asks you to identify.
A composite point in the embedding built from real player-season vectors,
scored by cosine against your guesses.
assets/vectors.json
Signature season
The one season used to stand for a whole career when comparing players.
The season selected as representative when matching careers across
decades.
assets/eratwins.json
Daily seed / LCG
Why everyone gets the same puzzle today, and why refreshing will not give
you an easier one.
A linear congruential generator seeded from the UTC date:
(seed × 1103515245 + 12345) & 0x7fffffff. Same date in, same puzzle out,
for every visitor — and a shared ?pack= link reproduces an identical set.
play.html
Terms this site uses that have no file behind them
Kept honest on purpose. These appear in copy somewhere on the site but are not
produced by any committed asset, so they cannot be checked. Treat them as claims.
Purity@k
Usually: of a player's k nearest neighbours, the share sharing their
archetype label.
A specific value, purity@10 0.7057, sat on the map on
play.html as “pulp 0.7057” until 2026-08-10, when it was removed for the
reason this entry gives.
It is not in assets/eval_scoreboard.json, which is the committed
evidence file and reports adjacent-season retrieval instead. It may come from an older
48-d evaluation. Until a scoreboard ships it, it is a claim.
no committed source
Lift
How many times better than a baseline something is.
A specific value, lift 6.32, has appeared in site copy with no
stated denominator. The ratio that is computable from the scoreboard —
model top-5 over the transparent 14-d baseline on the held-out test split — does not come
to 6.32. Stated as a claim until its definition ships with it.
no committed source