Dev.to · 5 min read

I Pulled the Weight Table Out of a Basketball Sim. The Positions Form a Line.

I Pulled the Weight Table Out of a Basketball Sim. The Positions Form a Line.

Build a Hooper is a browser game where you draft thirteen attributes onto one player and simulate a season. Your overall rating is a weighted average of those thirteen, and the weights change by position — the game publishes the whole matrix on its attribute page. Thirteen rows, five columns, every column summing to exactly 100. That is a constraint-satisfaction problem with a lot of freedom in it, so I got curious about how the numbers were actually distributed. Key Takeaways Distance between any two position columns grows monotonically with how far apart those positions are on the floor. The matrix encodes a one-dimensional spectrum. Small forward is the flattest column: 12 of 13 attributes carry ≥5% weight. Center is the spikiest: only 8 do. The spread per attribute ranges from 1.5x (Strength) to 13x (Blocks), and the high-spread attributes are exactly the ones that define position identity. None of this is stated anywhere in the game. It falls out of the table. The matrix Attribute PG SG SF PF C ---------------------- --- --- --- --- --- Three-Point Shooting 10% 13% 10% 7% 4% Midrange 7% 10% 9% 7% 4% Finishing 7% 8% 9% 11% 12% Dunking 3% 5% 6% 8% 10% Ball Handle 15% 11% 8% 4% 2% Passing / Vision 17% 9% 8% 6% 5% Perimeter Defense 10% 12% 12% 7% 3% Interior Defense 2% 3% 7% 13% 18% Blocks 1% 2% 4% 9% 13% Rebounding 3% 4% 7% 12% 15% Athleticism 10% 10% 10% 8% 6% Strength / Physicality 5% 4% 5% 6% 6% Clutch / IQ 10% 9% 5% 2% 2% Five columns, each summing to 100. Thirteen attributes competing for that budget. The positions sit on a line Treat each column as a 13-dimensional vector and take the L1 distance between pairs: const W = { '3PT':[10,13,10,7,4], 'MID':[7,10,9,7,4], 'FIN':[7,8,9,11,12], 'DNK':[3,5,6,8,10], 'HAN':[15,11,8,4,2], 'PAS':[17,9,8,6,5], 'PDEF':[10,12,12,7,3], 'IDEF':[2,3,7,13,18], 'BLK':[1,2,4,9,13], 'REB':[3,4,7,12,15], 'ATH':[10,10,10,8,6], 'STR':[5,4,5,6,6], 'CLU':[10,9,5,2,2], }; const POS = ['PG','SG','SF','PF','C']; const dist = (a, b) => Object.values(W).reduce((s, v) => s + Math.abs(v[a] - v[b]), 0); for (let a = 0; a < 5; a++) for (let b = a + 1; b < 5; b++) console.log(POS[a], POS[b], dist(a, b), '| gap', b - a); Group the results by how many slots apart the two positions are: Slots apart Pairs Distances Mean 1 PG-SG, SG-SF, SF-PF, PF-C 28, 24, 42, 30 31 2 PG-SF, SG-PF, SF-C 42, 66, 72 60 3 PG-PF, SG-C 76, 96 86 4 PG-C 106 106 Monotonic, and close to linear — roughly 28 points of L1 distance per slot. Adjacent positions are near-duplicates of each other; the two ends are maximally far apart. That is not what you get if you assign thirteen weights per position by feel. It is what you get when the designer has a mental model of position as a continuum — guard through big — and places each column along it. The five discrete labels are a UI convenience on top of a one-dimensional axis. SG and SF are the closest pair in the whole matrix at 24. Which tracks: the modern wing is exactly where those two roles have collapsed into each other. Small forward is the generalist, and the math says so Rank the columns by concentration — Herfindahl index over the weights, plus how many attributes clear a 5% floor: Position Top weight Top 3 sum HHI Attributes ≥5% C 18% 46% 0.1108 8 PG 17% 42% 0.1060 9 SG 13% 36% 0.0930 9 PF 13% 36% 0.0882 11 SF 12% 32% 0.0834 12 Small forward is the flattest distribution in the table: twelve of thirteen attributes carry at least 5%, and nothing exceeds 12%. Center is the opposite — Interior Defense, Rebounding and Blocks alone are 46%, and five attributes are effectively dead weight. In play terms, drafting a center is a narrow optimization problem and drafting a small forward is a broad one. The game never says that. The Herfindahl index does. Which attributes carry position identity Per-attribute spread, high to low: Attribute Low → High Ratio Blocks 1% → 13% 13.0x Interior Defense 2% → 18% 9.0x Ball Handle 2% → 15% 7.5x Rebounding 3% → 15% 5.0x Clutch / IQ 2% → 10% 5.0x ... Finishing 7% → 12% 1.7x Athleticism 6% → 10% 1.7x Strength / Physicality 4% → 6% 1.5x The high-spread attributes are precisely the ones that define what a position is — you cannot describe a center without rim protection and rebounding, or a point guard without handle. The low-spread ones (Strength, Athleticism, Finishing) are the baseline athletic floor that every basketball player needs some of. So the matrix has a clean two-layer structure: a mostly-flat base that applies to everyone, and a spiky overlay that encodes role. That is a reasonable pattern to steal for any RPG-ish stat system — it gives you meaningful class identity without making any stat useless. Where the ratings come from Weights are only half of it. The actual numbers you steal come from whichever player you pick on a given spin, and the rosters are not equal: the team pool runs 83 team-seasons, 53 of them classics carrying the highest ratings in the game, topping out with the 1970-71 Bucks at 99 and the 1995-96 Bulls and 2016-17 Warriors at 98. Which means a real draft decision is two-sided — how heavily is this attribute weighted for the position I might get, and is this roster good enough that spending a pick here beats waiting for a rare pull. The weight table tells you the first half. Only the spin tells you the second. The caveat Everything above is derived from the published weights, not from the simulation engine. Overall rating drives possessions, but an 82-game season plus a Play-In plus a playoff bracket puts a lot of variance downstream of that single number. A well-optimized build loses plenty of runs. The matrix tells you how to build. It does not tell you how it ends. Build a Hooper is an unofficial basketball simulator. Not affiliated with the NBA; no official logos, photos, or uniforms.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Sports News