fix(noise): fastCbrt's exponent is f32(1/3), with the exact oracle fastApprox never had (#163) - #164
Merged
Merged
Conversation
… exact oracle (#161, #163) Adds the f32-EXACT ground truth fastApprox never had, and uses it to settle two open questions about the file. The probe samples the noise machine's own `^` operator as `x ^ <exponent>` rather than going through the resource spot_height chain, so a 1e-5 effect is visible instead of being buried under a dozen other f32 steps. 4 series x 123 positions, compared with toBe after Math.fround - no tolerance anywhere. #163: fastCbrt passed a double 1/3 where the game's Math::powSafe(float, float) takes an f32 exponent (the multiply is `fmul s0, s0, s1`). At the 24 fixture positions chosen because the two candidates differ, the double scores 0/24 and f32(1/3) scores 24/24. #161: the fix also recovers most of the absolute-error regression that made commit 9b49ebb look like a trade-off. regularPatches, before -> after: iron-ore/123456 abs 0.8159 -> 0.6491 uranium-ore/123456 abs 0.4790 -> 0.4790 iron-ore/777771 abs 0.8096 -> 0.6493 uranium-ore/777771 abs 0.4755 -> 0.4724 Three of four are now better than the ORIGINAL pre-9b49ebb baseline on absolute error as well as relative; ABS_TOL headroom goes 0.18 -> 0.35. So "relative improves, absolute regresses" was not an inherent cost of the rounding change - it was a second, independent bug in the same file showing through, which a tolerance-based suite cannot distinguish. Two findings that were NOT predicted, both now pinned at 123/123: - `x ^ 0.5` is an EXACT sqrt, not fastapprox. The spec first asserted fastapprox and the game refuted it at the first position. - An integral exponent takes powSafe's fcvtzs/scvtf fast path and is exact exponentiation by squaring, never touching fastapprox. So `^` has three behaviours, and a Lua `^ 0.5` or `^ <integer>` must not be ported as fastPow. Both existing sites were already correct; nothing recorded why until now. pnpm run verify exits 0 (200 files / 1726 tests) both with and without the fastCbrt fix, which is exactly the weakness #162 tracks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DutxVPSqj12PtwNE46RtSs
The oracle-fastpow fixture was captured after the per-operation rounding change, so on its own it confirms the current implementation rather than choosing between the two. This carries the pre-9b49ebb single-rounding implementation verbatim from 9b49ebb^ and asserts it FAILS the committed values at many positions. The two disagree on ~30% of inputs and the fixture's positions were chosen to include them, so the guard is not thin. It is the same shape as the existing double-1/3 guard: an exact-match test is only worth as much as the proof that a wrong model would miss. Answers the part of #161 that the fixture alone left open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DutxVPSqj12PtwNE46RtSs
wormeyman
added a commit
that referenced
this pull request
Aug 5, 2026
…e-7 to EXACTLY 0 (#162) (#165) The formula was already right; the precision of its intermediate steps was the whole error. The noise machine evaluates in f32 registers, and reproducing that takes the residual to exactly 0 on all 404 committed oracle samples (three sweeps: 121 + 121 + 162). The f64 form matched only 285 of them. Same class of fix as fastApprox's per-operation rounding in #164. The spec's ceiling was the thing that hid this. It asserted `worst < 8e-3` against an actual worst of ~9.5e-7 - a ceiling ~8400x looser than the real error, which would have accepted almost any regression. The three sweeps now assert f32 equality with `toBe`, plus a guard that the f64 arithmetic FAILS the fixture, so the exact assertions cannot quietly become vacuous. Two hand-derived assertions moved from precision 10 to 6, and this is not a loosening: they compare against exact decimals like -2 and -1.0, but intermediates such as `0.5 - 0.6` and `1 - 1.2` are not representable at f32, so the game's own answer is -2.000000476837158. The exact decimal is the wrong target for an f32 computation. Asserting the exact f32 result instead would mean recomputing `f32(5 * f32(f32(1) - f32(1.2)))` in the test - re-implementing the function and checking it against itself - so those stay formula-SHAPE guards (which axis drives the min, that pmax=inf does not clamp) while bit-exactness lives in the oracle sweeps. This changes shipped tile-autoplace output (sand-1's coastal term). pnpm run verify exits 0: 200 files / 1728 tests. Claude-Session: https://claude.ai/code/session_01DutxVPSqj12PtwNE46RtSs Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #163. Closes #161. First conversion of the kind #162 asks for.
fastApproxis consumed by the resourcespot_heightchain and had no exact ground truth - every fixture over it compares with a tolerance wide enough to hide the ~1e-5 effects that were actually in question. This adds that ground truth and then uses it.The probe
Samples the noise machine's own
^operator directly, asx ^ <exponent>routed onto elevation, instead of inferring it through the patch chain where a dozen other f32 steps can mask the effect. 4 series x 123 positions, compared withtoBeafterMath.fround- no tolerance anywhere.Positions are deliberately adversarial, because a plain grid does not discriminate: 12 evenly spaced points scored 12/12 for both candidate cube-root exponents, and only
Math.cbrt(0/12) failed. The committed set includes points where a double1/3differs fromf32(1/3), and points where the pre-9b49ebbsingle-rounding fastapprox differs from the per-operation rounding that replaced it (~30% of inputs).#163: the exponent
fastCbrtpassed a JS double1/3. The game reaches this throughMath::powSafe(float, float)- both parameters arefloat, and the multiply isfmul s0, s0, s1at single precision - so it computes withf32(1/3) = 0.3333333432674408. Wrong on ~3.0% of inputs, by up to 7.8e-3 absolute.At the 24 positions chosen because the two candidates differ: the double scores 0/24,
f32(1/3)scores 24/24. Settled against the game, not the disassembly.#161: it recovers most of the absolute-error regression
9b49ebbimproved relative error sharply but regressed worst-absolute on 3 of 4regularPatchescases, which read as the price of a change the binary requires. It was not - it was a second, independent bug in the same file showing through:9b49ebb9b49ebbThree of four are now better than the original baseline on absolute error as well as relative.
ABS_TOLheadroom goes 0.18 -> 0.35.Two findings that were NOT predicted
Both now pinned at 123/123:
x ^ 0.5is an exactsqrt, not fastapprox. The spec first asserted fastapprox here, on the assumption that any non-integral exponent reaches the pair, and the game refuted it at the very first position.powSafe'sfcvtzs/scvtffast path - exact exponentiation by squaring, never touching fastapprox.So
^has three behaviours, and a Lua^ 0.5or^ <integer>must not be ported asfastPow. Both existing sites are already correct (resourceMath.tswritesregular_rq_factor ^ 2as a plain multiplication); nothing recorded why until now.That last point also closes a natural-looking inference:
fastPow's other two call sites pass an integeroctaves, so "the game uses squaring for integers, those must be wrong too" is very tempting. It is false - swapping the multioctave norm to squaring makes its oracle error 20x worse (2.630e-5 -> 5.332e-4, failing both gates), so that normalisation does not route throughpowSafeat all. The test carries both halves.Worth knowing before merging
pnpm run verifyexits 0 both with and without thefastCbrtfix (200 files / 1726 tests). The only thing in the repo that can see this change is the new spec. That is the weakness #162 tracks, and it is why the fixture came first here rather than the one-line fix.Captured on 2.1.12 build 87038,
refs:sync --checkin sync. 2.1.13 shipped the same day; its changelog has no map-generation, noise, terrain, autoplace, cliff or map-exchange entries.