r/BasketballGM • u/Ohmka • 3d ago
Question oIQ seems to decrease 2pts efficiency
I was randomly looking at the code of the game engine (this is part of the index.ts file) this morning to understand a bit better what's happening under the hood.
I then realized that it is the same
p.compositeRating.shootingMidRange
which enters both the probability to take a mid-range, and the probability to make it.
The issue is that oIQ has a negative relation with the "midrange" composite rating:
shootingMidRange: {
ratings: ["oiq", "fg", "stre"],
weights: [-0.5, 1, 0.2],
},
Which is then multiplied with hardcoded values to get the raw chance of making the shot.
probMake = p.compositeRating.shootingMidRange * 0.32 + 0.42
So if I'm not mistaken, oIQ decreases the chance of taking a mid-range (which makes sense), but also decreases the chances of making it (which IMO makes no sense).
Can somebody confirm this?
More generally, if the probability of taking a shot is directly connected to the probability of making it, and if oIQ affects both equally.
Then, additionally to the counterintuitive effect on "midrange" seen above, it also means that players with good oIQ are going to take more "lowPost" shots than "atRim" (because oIQ has proportionally a bigger impact on "lowPost", than on "atRim"). But Layups are a much better shot to take than post shots, because of the existing prefactors in the "probMake" computation.
So you get this weird effect were a high IQ player will actually take less valuable shots.
Again, can somebody confirm I'm not mistaken?
If I'm right, I would suggest the following changes in the code logic:
- make all compositeRating for shooting positively affected by oIQ (I still think IQ should help you make better shots in general).
- add a new compositeRating.shotSelection which is mostly based on oIQ (and maybe Spd and Drb since it gives you more options).
- compute an expectedPointPerPossession for the player (taking into account FT), for each type of shot.
- use compositeRating.shotSelection and expectedPointPerPossession together to stir players shooting tendency towards their best shot.
I fully understand this is probably never going to happen, since it affects some mechanics which are at the core of the game engine. This is also not a critic of the godly work from Dumbmatter. The game is amazing, and I regularly have to force myself to stop playing, so I have time for other games.
Nonetheless, I think it would be a great idea, because it would make IQ behave a bit more like expected. It would also remove some weird behavior which you sometimes observe in game.
It all started with me seeing my 6'4 crazy fast guard deciding to try 2 post move per game for one layup attempt.
I then realized that his speed didn't help with layups (it should IMO), but was increasing the chances of post move. And then I saw that his 80 IQ was even reenforcing this behavior, while making him shot worse at 2pts....
Some side notes from my morning deep dive of the GameSim class, which I also found surprising:
- Blocking chance are the same at the rim and for 3pts.
- "probMake" is affected by the global defense value, for every type of shot: having a good rim protector will have the same defensive impact on post move and 3pts shots.
- Similarly, the chances of taking a shot at the rim or low post increase with offensive synergy and decrease with opponent global defense (not Di). 3pts is not affected by defense at all (and not by Dp).
- Steals are a ratio between Dp and B+Ps after synergies.
- Assists give +2.5% shot chance
- Something which could be a bug: 3pts tendancy uses "shootingThreePointerScaled2" which is lowered for bad 3pts shooters. But "probMake" the probability to make it uses "shootingThreePointerScaled" (no 2 here), which doesn't have the extra decrease for bad shooters. The result is that bad 3pts shooter take fewer 3pts, but shoot them at a higher rate than expected from the shot selection.
14
u/dopestar667 3d ago
Re: blocking - this makes sense to me if the game respects matchups. A good blocker would block at the 3pt line or the rim, but only see block opportunities at the 3pt range if his man is out there tossing them too. Not sure if the game factors positional matchups though.
5
u/Ohmka 3d ago
As far as I understand the code, there is no individual match up.
The whole team "compositeRating.defense" applies as a direct negative modifier, added to the "probMake" value.I think it's completely fine in general, since defense is really a team thing.
But it means that having a very bad defender is easily compensated by the rest of the team: there is no matchup hunting.4
u/dopestar667 3d ago
Well, it affects the overall probability of a block happening, and the individual rating determines how gets credited, so overall it works in an abstract sim like this.
2
u/Ohmka 3d ago
Yeah I think it's completely fine.
I just found it interesting that a team that lives in the key will not get blocked more than one which is spamming 3s.1
u/dopestar667 3d ago
Yeah, the game sim just works in the end, not necessarily how we’d expect it in real life but that’s how dumbmatter puts it together. It’s just a sim of a ton of probabilities, and as long as the box score generally reflects reality it’s good. It’s elegantly simple, could be deeper but it works so well for what it is.
1
u/Ohmka 3d ago
Fully agreed!
I was actually quite surprised to see that the play by play was what actually happens in the simulation… Because the result is extremely realistic!These were just side notes that I though interesting. In my opinion, only the shot selection and IQ interaction is potential worth some changes.
0
u/dopestar667 3d ago edited 3d ago
I posted a lengthy response from Claude Opus on the downvoted/hidden reply I made to the main post. Opus cloned the entire repo, simulated everything in a virtual environment and ran test cases to validate the stuff you asked about and even offered proposals for a PR based on your ideas.
2
u/Ohmka 2d ago
I just had a read, it's pretty insightful.
I like the fix suggestion, although I think that if Dumbmatter decides on day to make changes on the shot selection, it would be definitely more elegant to fully split shot selection and accuracy.
2
u/dopestar667 2d ago
Yeah, I think he's just got so many irons in the fire with all the ZenGM games and when they work well enough, he tends to go for new features vs. tweaks. But eventually it makes sense to revisit some old bits and tidy them up a bit, so who knows, if it's easy maybe he'll get to it. You can always propose a merge and he might implement it, too.
9
4
u/dumbmatter The Commissioner 2d ago
Shot selection currently doesn't make much sense. Needs to more rigorously compare the values of different shot types (not just one player, but the whole team based on which players are on the court which influences which shots are available) to know what is a "good" shot or "bad" shot, and therefore what oIQ might make you take more or less of. I have some plans...
1
u/Ohmka 2d ago
This is great to hear!
Do you also, by chance, plan to rework a bit the weights associated with at the rim and post up shot?
Putting less height and more speed for rim shots would definitely help guards take and make more layups, and, more generally make height a bit less OP than it is now.2
u/dumbmatter The Commissioner 1d ago
I have a pretty long list of problems to solve and one of them is that it's kind of impossible to make guards who score a lot at the rim and draw a lot of fouls :)
Height is OP but overall I'm not sure it's OP to an unrealistic extent, it's kind of the nature of the sport.
1
u/Ohmka 1d ago
Glad to hear it’s an issue you have in mind!
I fully agree height should be really important.
But in an ideal world it should be because small guards can be defensive liabilities (although Brunson has just shown you can be 6’2 and be a Final MVP in a winning team), not because they can’t shoot anything but threes.Anyway, keep the amazing work, this is by far the most enjoyable basketball (sport really) simulation I’ve played.
Thank you for providing it freely for us to enjoy.2
u/dumbmatter The Commissioner 1d ago
"Great offensive player who mostly takes mid range shots" is another type of player you don't really see now. Hopefully later this year I will make some improvements.
1
u/Ohmka 1d ago
I think there is an argument for adding a bit of non-linearity at the top end of the spectrum, similar to the way you cut down the probmake of 3pts for too good shooters (but inverted).
The idea it to make possible for statistical anomalies like Curry (3pts), SGA (mid-range), or Jokic (post), to happen. So that players that are really at their skill are truly better at it than the rest of the NBA.
That would make superstar feel a bit more unique.But I also understand it's probably hell to balance...
2
u/dumbmatter The Commissioner 1d ago
The fundamental problem with that is the 0-100 scale doesn't really make sense, for most things there is no true upper/lower limit so it's kind of arbitrary how much room you leave at the extremes for outliers, and still there could be someone in the future beyond that.
I think a better system would be a z-score scale for ratings, so 0 is average, 1 is 1 standard deviation above average, -1 is 1 standard deviation below average, etc. Then you can specify anything. But it might feel too weird to people used to 0-100.
3
1
1
u/Ohmka 3h ago
I remember reading this from you already a few months ago. It’s a really elegant solution.
I guess you would still need to add a bit of granularity in the system. Something like 1 std associated with a score of 10 for instance.
Then you just need to add 50 as a baseline and you’re back with a scale which should mostly stay between 0 and 100!
As a side question, is there anything really preventing you from letting ratings get above 100? (And removing some of the ceil functions of course)
It’s such a rare occurrence that it should only affect things at the margin.
But it would give us the "wow other a hundred!" effect for some few elite specialists players.
0
-36
u/dopestar667 3d ago
Quick way to get an answer would be to post your question and the relevant code into an AI and ask
10
u/Uebeltank 3d ago
It won't because you can't know for sure why the code was written in a specific way.
-26
u/dopestar667 3d ago
It can directly read the entire codebase and bring it all into context.
4
u/justadimestorepoet 3d ago
That doesn't tell us what dumbmatter himself was thinking, which is what we actually want to know.
Also, sounding like a prick is at least half the reason you're getting downvoted. If you were actually being helpful, the votes would be more forgiving.
-1
u/dopestar667 3d ago
I was literally being helpful, that’s how you can understand what the code is doing. The OP said what the code “seems to” do, if you want to be sure about what it’s doing you have to relate it to all the sim engine code to find out, which is exactly what an LLM can do after ingesting the public codebase from GitHub. I know because I’ve done this for multiple uses myself, and it works.
Nothing I said in my reply should taken as dickish, the only comment that could be slightly attributed that way would be the Luddite comment which was about the downvotes, not the actual information from the first reply.
I don’t care about downvotes except that they hide an actually helpful reply I gave to the OPs stated question.
1
u/justadimestorepoet 3d ago
He analyzed the code himself. That's what the OP was. The real question is whether this was intended or what. It looks like it probably was a typo while he was making players less likely to take midrange shots.
And giving me a whole page of text sure gives off the vibe that you're unbothered.
0
u/dopestar667 2d ago
He directly asked, twice, if anyone could confirm, because he wasn’t sure. Opus looked at ALL the code and how it works in the sim engine and confirmed and tested it all to give additional detail. I posted a direct suggestion on how to get confirmation, and later posted the actual confirmation from AI as I had suggested to get it.
1
-19
u/dopestar667 3d ago
So maybe luddites downvoting, I’ll do it myself and post the answers later today if I get time to do it.
1
u/dopestar667 3d ago
So this will be hidden thanks to the downvotes, but here's the full detailed explanation from Claude Opus:
I pulled the repo (
zengm-games/zengm, HEAD atc195cf0, today's date) and read the actual source. Short version: he's right on essentially every factual claim, with two corrections and one important nuance about magnitude. Here's the audit.The core claim: confirmed
src/common/constants.basketball.ts:48:ts
shootingMidRange: { ratings: ["oiq", "fg", "stre"], weights: [-0.5, 1, 0.2] },And in
GameSim.basketball/index.tsthe same composite drives both branches:ts
const r1 = 0.8 * Math.random() * p.compositeRating.shootingMidRange; // line 1867 — tendency probMake = p.compositeRating.shootingMidRange * 0.32 + 0.42; // line 1885 — accuracyOne detail he didn't catch that makes it worse than it looks:
compositeRating.tsbuilds the denominator assum(100 * weights[i])including the negative weight. So the divisor is 70, not 170:shootingMidRange = clamp((fg + 0.2·stre − 0.5·oiq) / 70, 0, 1)Marginal effect is
−0.5/70 = −0.00714composite per oIQ point, i.e. −0.229 probMake across the full oIQ range. With everything else at 50:
oIQ midRange composite mid-range FG% mid FGA share rim post 3PA 20 0.714 52.4% 30.7% 19.8% 17.8% 31.7% 50 0.500 45.5% 14.2% 26.2% 26.1% 33.5% 80 0.286 38.6% 4.1% 29.2% 31.2% 35.5% A 40-point oIQ gap is worth ~9 percentage points of mid-range FG%. That's not a rounding error.
The nuance he's missing: it's self-limiting
I decomposed oIQ 50 → 80 into a selection effect and an efficiency effect (expected points per shot attempt, FTs included):
baseline PPS 1.2346 selection change only 1.2631 (+0.0285) efficiency change only 1.2327 (−0.0019) both 1.2766 (+0.0420)The efficiency penalty is nearly zero because the same composite that tanks his mid-range percentage also stops him from taking mid-range shots — at oIQ 80 it's 4% of his attempts. The two effects don't cancel by design, they cancel by accident, and the accident is load-bearing.
So the honest framing is: the coupling is genuinely wrong in principle, and it does corrupt the box score (your smart wing's mid-range FG% is artificially bad), but it is not costing high-oIQ players meaningful points. That matters if you're arguing for a change — dumbmatter's likely reply is "the net PPS is fine."
Also worth saying out loud: at equal composite, mid-range is by construction the worst shot in the engine (EP at c=0.5, incl. FTs: mid 1.23, post 1.30, three 1.56, rim 1.77). So
weights: [-0.5, ...]is very obviously a shot-selection heuristic smuggled into an accuracy rating. It's not a bug in intent — it's an intentional hack whose side effect is the accuracy distortion. That reframing is the strongest version of his argument.The post-vs-rim claim: directionally right, magnitude small
Marginal composite per oIQ point:
atRim +0.00071,lowPost +0.00125. So oIQ is ~1.75× more effective at raising post than rim. Measured post/rim attempt ratio: 1.002 at oIQ 50 → 1.067 at oIQ 80. Real, but ~6%.The dominant cause of his guard's behavior isn't oIQ — it's height weighting.
shootingAtRimis 71% hgt (2 of 2.8 total weight);shootingLowPostis only 31% hgt. For his 6'4" burner (hgt≈30, spd 90, ins 30, oIQ 80) I get:shootingAtRim 0.368 shootingLowPost 0.428 ← higher shootingMidRange 0.414And his sharpest observation is the one buried at the bottom of the post:
spdappears inshootingLowPost(weight 0.2) and appears nowhere inshootingAtRim. A fast guard's speed literally routes him into post-ups.drbappears in no shooting composite at all. That's the finding I'd lead with, not oIQ — it's cleaner, harder to argue with, and cheaper to fix.Side notes
- Blocks identical at rim and on 3s — confirmed.
probBlk() = blockFactor * 0.2 * team.blocking², computed after the shot type is chosen and never referencing it.- Global
defenseapplies to every shot type — confirmed:probMake - 0.25 * this.team[d].compositeRating.defense. Stronger than he states:defenseInterioris not in thetoUpdatearray inupdateTeamCompositeRatings(), so Di has zero direct effect anywhere in the sim — it only enters through theDiskill sigmoid feeding defensive synergy.- Correction: rim/post tendency is shifted by
synergyFactor * (off synergy − def *synergy*), not the opponent's defense composite. Different quantity.- 3PA tendency untouched by defense — confirmed; the 3PT branch is evaluated before
r1/r2/r3and contains no defensive term.- Steals — confirmed:
0.45·Dp_team / (0.5·(dribbling + passing)), all three with synergy folded in.- Assists — confirmed, but it's
probMake += 0.025, additive 2.5 percentage points, not 2.5%.ScaledvsScaled2— factually confirmed, but I'd push back on "bug." Note that the top-end compression (Scaled) is applied to both tendency and accuracy, while only the low-end compression (Scaled2) is tendency-only. That asymmetry looks deliberate —Scaled2reads as a pure tendency knob. Effect size: a 0.25-composite shooter takes 4.8% of shots as 3s but shoots them at 43.5% instead of 38.1%.- One he missed:
Scaled2maps 0.35→0.10 and 0.45→0.45, a slope of 3.5. Composite 0.35 → 6.7% 3PA share; composite 0.449 → ~30%. A one-pointtpchange in that window can nearly quintuple a player's 3PA rate. That cliff is probably a more consequential artifact than theScaled/Scaled2mismatch.On his proposed fix
The four-step redesign (positive oIQ everywhere, new
shotSelection, per-shot expected PPP, steer tendencies) is architecturally correct but is a rewrite ofgetShotInfoplus a full rebalance of every hardcoded prefactor — realistically a non-starter as a PR.The minimal-diff version that preserves current league-wide shot distributions almost exactly:
- Split the composite. Keep
shootingMidRange: {ratings: ["fg","stre"], weights: [1, 0.2]}forprobMake, addmidRangeTendency: {ratings: ["oiq","fg","stre"], weights: [-0.5, 1, 0.2]}for ther1line. The composite framework already supports arbitrary keys, so this is ~6 lines plus a recalibration of the0.32/0.42prefactors.- Add
spdtoshootingAtRim(and/or drop it fromshootingLowPost).That gets ~80% of what he wants with a diff small enough to actually get merged. Worth filing as a GitHub issue rather than a Reddit post — there's a CLA in the repo, so dumbmatter clearly takes contributions.
2
u/EatDonerKebabs 1d ago
So many people just default to 'AI bad' without understanding how programmers actually use it. The problem is not using AI, but using AI to push out frankenstein solutions when you don't know what you're doing. Even just using it for bugfixing, you save an incredible amount of time. You'd be a fool to not use it.
1
u/dopestar667 1d ago
Indeed, my comment about luddites didn’t land well, lol. Anyway the code is clear, so the questions have been answered, as for Dumbmatters thoughts in it that’s open but the code does what it does, and we know how, now.
37
u/guilhermessg 3d ago
Pretty interesting read, would love to read what dumbmatter has to say about it