fix(noise): variable-persistence's octave shift was an alias of ZERO - 1.8e-3 to 1.1e-5 (#162) - #167
Merged
Merged
Conversation
…- 1.8e-3 to 1.1e-5 (#162) Second instance of the defect fixed in #166, found by taking that finding's own prediction seriously: `variablePersistenceMultioctaveNoise` carried a fitted per-octave x shift of `-7936`, and `-7936 == -31 * 256`. The basis lattice has period 256 per axis, so that names the same field as a shift of zero. The old comment's own "false minima near -4864 / -3840" are `-19*256` and `-15*256` - every candidate the scan surfaced was a multiple of 256, which is what a completely flat fit direction looks like from the inside. The independence checks (input-scale, seed, offset_x, persistence) could not discriminate either, because the period is independent of all of them. `VariablePersistenceMultioctaveNoise::run` settles it: the octave loop reloads the x and y offsets from the same two constant slots (+0xa2c, +0xa30) every iteration and has no counter-scaled term at all. There is no per-octave shift; octaves decorrelate through lacunarity alone. `OCTAVE_SHIFT` is deleted, not set to 0. Same pairing as #166 - neither half is an improvement alone, and here the mismatched combination is spectacularly bad: f64 + shift -7936 (shipped) 1.847e-3 61/266 f32-exact f64 + no shift 1.847e-3 61/266 <- a literal no-op f32 + shift -7936 3.629e-1 45/266 <- 196x WORSE than doing nothing f32 + no shift 1.144e-5 66/266 <- 161x better The third row is the sharpest illustration of why this class of bug hides: k*(-7936) at octave 5 lands near -39680, where an f32 ulp is ~3.9e-3. The remaining 1.1e-5 is not a modelling gap - it is basisNoise's f32 floor amplified by this op's 2^N * output_scale gain. `worst/gain` is 1.2e-7 to 2.4e-7 (one to two f32 ulps) in every one of the seven cases, including those with offset_x of 5000 and 40000. The spec now asserts that ratio per case as well as the absolute worst, so a future regression that scales with gain cannot hide inside a loose absolute bound. The spec's near-field/far-field split is gone with the shift that motivated it: it thresholded on a `maxNoiseX(...) < 500` computed from k*(-7936) and allowed the far domain 100x more error. With no shift, the offset_x=40000 case is no worse than the ones at the origin. Guards added mirroring #166: all three pre-fix models must still fail the tolerance. No downstream statistic moved; `pnpm run verify` exits 0 (1736 passed). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01KTCpQXFZ79UoVGunewFyL7
…period out 17.17 - 256 = -238.83 exactly. The fitting note warned that a narrow U scan finds a 'false local min near -238.8', blamed near-collisions in the basis field, and advised scanning wide - which is precisely what walked seven more periods out to -1774.83 and caused #166. Every minimum either scan ever reported, in both files, was an alias of the truth: -238.83 here, and -4864 / -3840 (=-19*256 / -15*256) in the variable-persistence notes. Advice corrected to the opposite: scan narrow, take the smallest-magnitude representative. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01KTCpQXFZ79UoVGunewFyL7
This was referenced Aug 5, 2026
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.
Second instance of the defect fixed in #166, found by taking that finding's own prediction seriously.
The finding
variablePersistenceMultioctaveNoisecarried a fitted per-octave x shift of-7936. The basis lattice has period 256 per axis, andso it names the same field as a shift of zero. The old comment's own warning about "false minima near -4864 / -3840" gives it away - those are
-19*256and-15*256. Every candidate the scan surfaced was a multiple of 256, which is what a completely flat fit direction looks like from the inside. The independence checks the notes were proud of (input-scale, seed, offset_x, persistence) could not discriminate either, because the period is independent of all of them.VariablePersistenceMultioctaveNoise::runsettles it: the octave loop reloads the x and y offsets from the same two constant slots (+0xa2c,+0xa30) on every iteration and has no counter-scaled term at all. Octaves decorrelate through lacunarity alone.OCTAVE_SHIFTis deleted, not set to 0.Same pairing as #166, and here the mismatch is spectacular
That third row is the sharpest illustration of why this class of bug hides:
k*(-7936)at octave 5 lands near -39680, where an f32 ulp is ~3.9e-3. Anyone reproducing the game's arithmetic without first questioning the constant would conclude f32 was the wrong theory.What is left is not a modelling gap
The remaining 1.1e-5 is
basisNoise's f32 floor amplified by this op's2^N × output_scalegain.worst/gainis 1.2e-7 to 2.4e-7 - one to two f32 ulps - in every one of the seven cases, including those withoffset_xof 5000 and 40000. The spec now asserts that ratio per case as well as the absolute worst, so a regression that scales with gain can't hide inside a loose absolute bound.Spec changes
The near-field/far-field split is gone along with the shift that motivated it - it thresholded on
maxNoiseX(...) < 500computed fromk*(-7936)and allowed the far domain 100x more error. With no shift, theoffset_x=40000case is no worse than the ones at the origin.Guards mirror #166: all three pre-fix models must still fail the tolerance.
No downstream statistic moved.
pnpm run verifyexits 0 (1736 passed).🤖 Generated with Claude Code
https://claude.ai/code/session_01KTCpQXFZ79UoVGunewFyL7