Methods • Every number recomputable • No vibes

Method & data sources

Doctrine: every number a player sees must be recomputable from source; every mechanic states its rules; nothing is vibes. Games use 14 era-z per-100 features for speed + honesty. The research model MTNN v4 expands to 120 feats / 17 families → 48-d L2 for neighbor search, archetype scoring, and daily puzzles. Both live in the same era-honest space. Glass-box: Inside the Network → 17×160→32 towers

Solo personal project, no connection to employer, built with public/free-tier only • Okabe-Ito AAA palette • 18px/1.65 body • mobile-first 56px tabs safe-area • best-app-ever polish • ONNX WASM 2MB distilled, localStorage only.

The vector space — two views, one honest frame

Games (fast, transparent): 12,392 player-seasons 1996-97 → 2025-26 from stats.nba.com per-100-poss splits, MIN ≥800. Each season 14 features, z-scored within its own season (era-honest). This is what you play with.

FeatureWhat it measuresEra note
PTSscoring volume per 100pace-adjusted at door
ASTplaymakingraw per 100, era-z
OREB / DREBoff / def glassboards per 100
STL / BLKdef eventssteals, rim protection
TOVturnoversnegative signal
FG3A / FGA / FTAshot mix + rim pressurevolume, era-z
FG3_PCT / FG_PCT / FT_PCTaccuracyempirical-Bayes shrunk by attempts → shown as percentile, not fake %
PLUS_MINUSon-court impactper 100, not RAPM

Cleaning: dedupe (player,season), missing→season mean + mask m∈{0,1} so we track cat([x·m,m]), attempt-weighted EB on %s, clip ±4σ.

MTNN v4 expanded — 120 feats / 17 families

Same 12,392 seasons but richer: volume, playmaking, rebounding, defense (STL/BLK/DEF_RTG), efficiency (EFG%/TS%/OFF_RTG/NET_RTG), shotmix, bio (AGE/HEIGHT/WEIGHT/DRAFT), tracking (2013+ masked), form (FORM_DD), market, roster, career (YEAR_IN_LEAGUE/LAG1_COSINE/DELTA_NORM), competition (SOS_NET_RTG/B2B_RATE), team, pedigree, playoffs, honors. Each family = own residual tower.

120 feats17 families30 seasons8 archetypes48-d L2~224K params

Reconciled: 14 = game distillation for speed + transparency. 120 = research MTNN that powers daily puzzles. Cosine in 48-d decides neighbors in both, root-frame Procrustes aligned.

Three words this page keeps using

z-score
How far above/below average in “typical gaps.” 0=average, +1 solid notch, +2 elite. Two-thirds sit –1…+1. Cap ±4.
era-z
Compared only to players he faced that season. A 1997 center and 2026 guard share one honest space because each is scored vs own league first.
mask
“Never measured” vs “measured zero.” Tracking pre-2013 = masked, not guessed.
per 100
What he does with ball he gets — not per-game inflated by pace. 30 pts ≈37 per 100.

MTNN v4 — the glass-box behind every guess

17 stat families each run through own residual tower → fuse into 48-number fingerprint (L2-normalized, cosine = similarity) → decode into archetype 8 / position 5 / skill grades 18×(48→16→1) + per-100 forecast. See it live: Inside the Network → click any node to probe family share and signed grad×input pushes.

Input 120 feats, mask m∈{0,1} h = cat([x·m, m]) → 2·d_in per family Tower 17×: Linear 2d_in→160 LN GELU → 160→32 LN + skip ×2 → 32-d (total 544) + season_emb 12-d → concat 556 → 556→128 GELU LN → 128→48 L2 Heads MLP 48→64→k | Skills 18×(48→16→1) | Next profile 14-d forecast Params ~224K 57% fusion hidden • Checkpoint mtnn_v4_phase_b 2.26MB → ONNX 549KB • WASM 2MB distilled Truthful flow W1380 H780 COLS 110/230/320/400/480/560/720/800/900/1120 1:1 input→tower cat box B1h/B1o/B2h/B2o L2 badge

Training: era-honest σ per season, Procrustes RᵀR=I chained to 1996 root frame drift ↓18%, RealMLP robust scaling, leak-free split via pipeline/leakfree.py so recall isn't inflated. Asset mtnn_embeddings.f32 48-d per season powers Chimera/Era Twin/Team labs.

The map

3D league map in-game projects 14-d (or 48-d MTNN) down to 3 axes via PCA, minmax [0,1] for rendering. Client projection = exact affine recovery of build-time PCA + minmax, not re-fit. Axes named from correlations:

Okabe-Ito AAA + shape + icon + text triple-encoding: color never alone. In /model the 3D map uses auto-rotate slow, drag, scroll zoom, hover name+archetype, orange selected.

Archetypes — 8 global player types

8 k-means clusters at build time on same 14-d era-normalized vectors, seeded reproducible. Names = strongest centroid traits, not scouting:

Client never re-derives membership — uses pipeline's label, verified all 12,392 via harness V2. Trend Research adds layer 2: K=8 re-fit within five era windows, named top-2 sigma, lineage = nearest predecessor-era centroid cosine in Procrustes root frame.

layer 1: per-season share 8 global k-means (labels from vectors.json, no re-fit); layer 2: k-means K=8 re-fit within five era windows (seeded), named from top-2 centroid sigmas; lineage = nearest predecessor-era centroid cosine in Procrustes root frame; shares of charted MIN≥800 players — stated scope

The Deadline — midseason team change

Midseason move = in-season TEAM_ID change with ≥15 games and ≥12 MPG both sides, 2015-16 → 2025-26 game logs (nba_api PlayerGameLogs). Deltas: per-36 points, context-adjusted plus-minus, points-per-shot-attempt proxy before vs after.

333 movers analyzed → daily-set pool 25 thrived / 25 cratered. Composite score = stated blend, not truth claim. “Midseason move” ≠ officially reported trade; project has only team-change dates.

Fader or Finisher

Split each player-season at own game-sequence midpoint (not calendar ASB) with ≥25 games and ≥12 MPG required both halves, per-36 rates, 2015-16 → 2025-26. Quiz pool 600 rounds limited to unambiguous deltas 1.5–6.0 per-36 swing, so ties/noise never appear.

The Skills Lens — 0–99 grades + badges

Every charted season graded 0–99 on twelve skills. Each skill = fixed, published linear composite of same era-z per-100 features (e.g. Perimeter Shooting = 0.55·FG3A + 0.45·FG3%), percentile within own season pool — so every era same distribution, 90 in 1997 = 90 in 2026. Badges 90+, gold 97+.

Fused chimera on reveal graded same composites vs pooled all-era quantile table (assets/skill_probe.json) — blend never existed as real season, so reference = all 12,392.

MTNN two scores — plain English

recall@10 — “can it find same guy next year?”
Hide next season, ask 10 nearest to this. Recall = how often own next season in top10. Higher better, 1.0 = always.
purity@20 — “do neighbors look like him?”
20 nearest from other eras, fraction sharing archetype. Higher = groups roles not decades. ~0.12 = random (8 types).
held-out — leak fix
Earlier training included pairs used to score recall → inflated to ~1.0. Fixed via pipeline/leakfree.py: trains only on players not graded later. Honest numbers lower and real.

Next-season profile: MTNN next_profile head reads embedding and predicts next season's 14-d era-z vector. Prior seasons show predicted vs actual MAE. Latest 2025-26 shows prediction only — labeled as such. Asset: assets/next_profile_eval.json.

Wide-matrix (masked 2015-16+): Post Play, Transition, Motor, Shooting/Rim/Disruption Gravity from tracking+hustle — stated proxies, not Second Spectrum. Pre-2013 shows “not tracked this era.”

Data sources

The League Drift — Procrustes

Trend Research asks not “how good” but “how much did 14-d vector space itself rotate, season to season.” Quoted verbatim from assets/drift.json:

orthogonal Procrustes on consecutive-season shared players (>=30); rotation = mean principal angle of Q vs identity; residual = normalized Frobenius after alignment; no scaling (z-spaces pre-normalized); chained transforms map any season into 1996-97 root frame; axisDrift = 1-|Q_ii|, stated proxy

Chained root-frame powers Trend Research drift charts; Era Twin uses promoted MTNN embedding for cross-decade matching. See interactive: What changed this season? uses gauge + compass + ranked shift bars.

Career Shapes — how roles move over a career

Trend Research Career Shapes asks: not “what archetype was this season” but “how did own archetype label move across whole career.” Careers ≥4 charted seasons; each keeps per-season global label (never career-static):

careers >=4 charted seasons; per-season global archetype labels (never career-static); taxonomy rule-based as documented (stable >=75% modal; reinvention = one sustained >=75%/>=75% switch; late-bloom = switch at >=60% career index; migrator = 3+ archetypes none >=60%); era comparison by career-midpoint decade; correlates observed with selection effects — trajectory class is outcome, not assignment

The accuracy harness — blocks deploy on fail

pipeline/verify_accuracy.py gates every deploy — non-zero exit blocks shipping:

MTNN extras: recall@10 leak-free, purity@20, next_profile MAE. Bundle ~105KB gz <300KB target, ONNX 549KB. Visual harness: AAA Okabe-Ito, 18px/1.65, 56px tabs safe-area.

Attribution, license & limitations — stated plainly

Attribution: stats from stats.nba.com and Basketball-Reference; derived, era-normalized aggregates, not verbatim scrapes.

License: code all-rights-reserved pending license file. Source data subject to stats.nba.com / BR terms.

What this site is not:

Solo personal project, no connection to employer, built with public/free-tier only • Videos relevant: MTNNFlow.mp4 + ChimeraEquation.mp4 only • Manim source: workspace/manim_dumbmodels/ • Free-tier Vercel cleanUrls true.