From 32ba1bfa53923a6ca3ec689835d38bd7fe61f6cc Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 21:06:41 +0200 Subject: [PATCH 1/4] Affine forms map: Pass 5 probe results, authority map, and the tape target's pointer Co-Authored-By: Claude Opus 5.5 --- docs/cultmath-interval-ground-cut.md | 279 ++++++++++++++++++++++++++- docs/cultmath-tape-target.md | 3 + 2 files changed, 281 insertions(+), 1 deletion(-) diff --git a/docs/cultmath-interval-ground-cut.md b/docs/cultmath-interval-ground-cut.md index c68dfb01..3e09e35e 100644 --- a/docs/cultmath-interval-ground-cut.md +++ b/docs/cultmath-interval-ground-cut.md @@ -5,7 +5,8 @@ the Self opened the `cultmath-tapes` campaign from pass 2; pass 3 revises the two CultLib cuts after their first Hands reports and the operator's challenge to the march, and changes the site field to envelope-then-noise; pass 4 (section "Pass 4") revises `interval-ops` to r3 after its second Hands report -and Soul's verdict, and names the one shipped void (Q9). Where a later pass and +and Soul's verdict, and names the one shipped void (Q9). Pass 5 (section "Pass 5") maps +affine forms against CultLib `main` at `a7966142`. Where a later pass and an earlier section disagree, the later pass wins. Where this map and the Body disagree, the Body wins and this map is stale. @@ -1007,3 +1008,279 @@ beside the two CultLib r2 specs. - `:root:has(body[data-slug])` for the ritual seeds: supported in every current browser; a browser without `:has` gets the default seeds, which is acceptable for a fallback of a fallback. + +## Pass 5: affine forms (ruling `affine-forms-unparked`) + +Imagination pass 5, 2026-10-03. It maps the affine-arithmetic piece of the +tape target, unparked on its own by ruling +`cultmath-tapes:ruling:affine-forms-unparked`. It answers follow-up +`cultmath-tapes:follow_up:wells-oracle-shortfall-affine-forms`. No tape, +pruning or interpreter is built; tape steps 2-5 stay parked under +`tape-target-unparks-when-asura-stable`. Pinned head: CultLib `main` at +`a7966142`. Every `file:line` in this section is against that commit. + +### What was measured (body facts) + +The probe is a renamed copy of `NoiseBoundTests.cs` with five more bounds for +`FogField`. It lives on the local branch `scratch/imagination-affine-probe` +(`5fa8e9ce`). That branch was never pushed to origin and is not for merging. +It was run on Yggdrasil through the ygg-verify stopgap (`dotnet/sdk:10.0`, +about 3 minutes). It re-runs `Run(FogField.Wells(w), 8, 64, 0x5A7E + 8)`, so +the tiles, rays and seeds are those of the landed (c) row at `N = 8`. V0 +reproduces the landed figures exactly (0.378 and 0.194). + +Constants the probe needed, each sampled over 2e5 seeded points with a 1.25 +margin (probe grade, not provenance): +- the Frobenius norm of the Hessian of `snoise(float3)`, max 55.15, so + `M = 68.94`; +- the gradient length of `snoise(float2)`, max 7.35, so `L2 = 9.19`; +- for comparison, the mean `|snoise_grad|` is 2.59, against `L = 10.10`. + +The bounds: +- **V0**: the landed bound. +- **V1**: V0 with the detail octave's ball no longer enlarged by the warp. The + detail reads the unwarped point (`NoiseBoundTests.cs:860`, `Noise.Detail(p, ...)` of the unwarped `p`), yet `Bound` + hands it `ball.w`, which carries `D` (`:875`, `:890`). +- **V2**: V1 with the warp centred. Each phase's coverage ball is centred at + `c + flow(c) shift_k`, with radius `r + |shift_k| rho`, where + `rho = FlowAmplitude L2 FlowFrequency r sqrt 2` bounds how far the flow moves + over the ball. This is the mean-value form of the warp: interval arithmetic + only, no affine noise. +- **V3**: V2 with the centred form of the noise, + `n(c) +- min(L R, |g(c)| R + M R^2 / 2)` from one `snoise_grad`. +- **V4**: V3 with a reduced affine form. Three shared symbols (depth `t`, + slopes `u` and `v`) carry the envelope's plane term and every noise term's + linear part, and one error symbol takes the rest. It applies where the fade + is the point 1, and is intersected with V3. +- **V5**: V4 with one shared symbol, depth `t`, plus one error symbol: + Gamito's `t0 + t1 e1 + t2 e2`. + +A gradient evaluation is counted as 2 snoise, an estimate. V3 to V5 gate a +probe on the best result any centre value and gradient could give. + +Range ceiling. A cell is range-nonempty if the density is positive at any of +5 x 5 slopes over the tile's slope square (corners included) times 9 depths +over the cell, or if it is in the oracle mask. Its efficiency is the oracle +march's cost over the cost of marching the range mask, with probes free. The +sampling can miss nonzero points, so this figure is an upper estimate. No +sound enclosure can beat it. + +| (c), `N = 8` | efficiency (oracle) | cost ratio | dense cells/ray | probe overhead | cull | +| --- | --- | --- | --- | --- | --- | +| warp 0, V0 landed interval | 0.378 | 2.74x | 12.40 | 0.961 | 0.952 | +| warp 0, V2 centred interval | 0.368 | 2.67x | 12.40 | 0.937 | 0.952 | +| warp 0, V3 centred noise | 0.466 | 3.38x | 8.67 | 0.868 | 0.974 | +| warp 0, V5 affine, one symbol | **0.511** | **3.71x** | 7.81 | 0.859 | 0.979 | +| warp 0, V4 affine, three symbols | 0.534 | 3.88x | 7.37 | 0.853 | 0.981 | +| warp 0, range ceiling | 0.859 | | | | 0.995 | +| warp 60, V0 landed interval | 0.194 | 1.39x | 31.55 | 0.993 | 0.842 | +| warp 60, V1 detail unwarped | 0.205 | 1.47x | 27.74 | 0.963 | 0.864 | +| warp 60, V2 centred interval | 0.347 | 2.49x | 13.71 | 0.938 | 0.945 | +| warp 60, V3 centred noise | 0.441 | 3.16x | 9.52 | 0.870 | 0.969 | +| warp 60, V5 affine, one symbol | **0.477** | **3.42x** | 8.71 | 0.864 | 0.974 | +| warp 60, V4 affine, three symbols | 0.502 | 3.60x | 8.16 | 0.856 | 0.977 | +| warp 60, range ceiling | 0.854 | | | | 0.995 | + +Every variant's transmittance equals the dense march's (max |dT| 0). No +variant's pre-pass proved a cell empty where any of the 225 samples per cell +was nonzero (0 enclosure violations). + +What this establishes: + +1. **The oracle cannot be reached by any sound bound.** The oracle mask + samples one depth per ray. A bound must cover the whole slice of the tile, + so the best any exact enclosure could do is about 0.86 of the oracle. That + matches the 0.57-0.85 that conservative visibility methods reach against + exact sets (`docs/research/culling-efficiency-oracle-prior-art.md`). An + oracle target of 0.90 is unreachable by construction. +2. **The warp diagnosis was half right.** At warp `D` most of the gap is the + warp enlarging the radius by its maximum (V0 to V2: 0.194 to 0.347). That + is fixed by evaluating the flow at the ball's centre and enlarging the + radius only by how far the flow can move across the ball. It takes no + affine noise, and it costs one more snoise per probe, for the second phase + ball. The detail octave's spurious warp (V1) is worth little on its own. +3. **Affine noise buys the rest:** the centred form, then the shared depth + symbol. Against the centred interval, affine with one symbol closes about a fifth + of the oracle gap at warp 0 and at warp `D` (efficiency 0.368 to 0.511 and + 0.347 to 0.477). Its combined cost ratio is 1.39x and 1.37x the centred + interval's, and 1.35x and 2.46x the landed interval's. It reaches 0.59 and + 0.56 of the range ceiling. +4. **Three symbols buy about 5% over one** (0.534 against 0.511, 0.502 + against 0.477). One symbol fits a `float3`. Three need a struct or a + `float4` plus a `float`, and the HLSL mirror's harness compares struct + returns but has no struct parameters (`packages/cultmath/docs/design.md:126-135`). +5. **The probe overhead falls** from 0.96 to 0.86 under every centred or + affine bound. Each probe costs a gradient and proves more, so the total + cost still drops by 1.35x to 2.46x. Today's (c) assertion, overhead at + least 0.90 (`NoiseBoundTests.cs:1558`, `:1588`), would reject the cheaper + march. That is question `affine-wells-contract`. + +### The model + +An affine form is a transient value, not a persistent kind. The persistent +things this pass adds or changes: + +| Thing | Identity | Lifecycle | Authority | +| --- | --- | --- | --- | +| `SNOISE_HESSIAN` (C#), `CULTMATH_SNOISE_HESSIAN` (HLSL, lowered to GLSL) | The constant's name; one value in `math.Affine.cs`, mirrored | Measured once by `NoiseBoundTests.MeasureHessian` (slow, explicit), as `SNOISE_LIPSCHITZ` is. It is re-measured and re-pinned whenever the `snoise` kernel changes. | `MeasureHessian` sets it; `HessianConstantPinsSampledCurvature` pins it; no consumer carries its own | +| `af_*` families in `fixtures/glsl-parity.json` | Family signature string, appended after the last existing family | Regenerated only by `CULTMATH_WRITE_GLSL=1`; earlier cases stay byte-identical | `GlslMirrorTests` generator | +| The (c) contracts | `IntervalSkipHalvesEvaluations` assertions | Changed only by a ruling | Operator (question `affine-wells-contract`) | +| `SNOISE2_LIPSCHITZ` (C#), mirrored in HLSL | The constant's name in `math.Interval.cs`, beside `SNOISE_LIPSCHITZ`, with an `iv_snoise_ball(float2, float)` overload | Measured by `MeasureLipschitz2` (slow, explicit) and pinned, as `L` is | CultMath. The (c) field's flow is `snoise(float2)`, and a bound on a CultMath function belongs to CultMath, not to a test-local helper. A consumer's own flow, such as the site's texture, keeps its own variation bound | + +### Representation + +**A reduced affine form with one shared symbol: `float3(x0, a, e)`, meaning +`x0 + a·ε + e·δ`.** Here `ε ∈ [-1, 1]` is shared by every form over the same +region, and `δ ∈ [-1, 1]` is a private error, with `e ≥ 0`. +- This is Messine's AF1 as Gamito and Maddock use it for gradient noise, + `t0 + t1 e1 + t2 e2`. +- Sharp and Jacobson's measurements show that this fixed-symbol form is the + fast one on GPUs: 1.0x against 8.4x for full affine. +- It fits one `float3` register, as an interval fits a `float2`. It needs no + struct parameter, so the mirror, lowering and fixture machinery take it + unchanged. +- V4 against V5 prices the two extra symbols at about 5% efficiency. + +For a tile probe `ε` is the depth parameter: `z = z_m + h ε` with +`h = (z1 - z0) / 2`. The footprint goes into the error. For a single ray it is +Gamito's form exactly. + +A point in space is not a new type. It is the three arguments +`(centre: float3, axis: float3, radius: float)`, the set +`{centre + axis ε + w : |w| ≤ radius}`. `af_snoise` and `af_fbm` take a point +in that form, and `af_frustum_axis` and `af_frustum_ball` produce one for a +tile slice. + +Ops, named by the (c) consumer, plus `af_mul` because affine arithmetic is +defined by its product, as `iv_mul` is for intervals: +- `af_point`, `af_symbol(x0, a)`, `af_from_iv`, `af_range` (the bridge to + `iv_*`); +- `af_add`, `af_sub`, `af_neg`, `af_scale`, `af_add_iv`, `af_mul`; +- `af_snoise`, `af_fbm`; +- `af_frustum_axis`, `af_frustum_ball`. + +No others until a consumer names one, which is the rule `design.md` already +states for intervals. + +The interval side gains `SNOISE2_LIPSCHITZ` and the overload +`iv_snoise_ball(float2, float)`. The (c) field's flow is `snoise(float2)`, and +centring the warp needs a bound on it, which belongs to its owner. + +`af_snoise(centre, axis, radius)` makes one `snoise_grad` call. With +`R = |axis| + radius`, it returns the narrower of two sound forms: +- the Lipschitz form `(n, 0, L R)`; +- the centred form `(n, g · axis, |g| radius + M R² / 2)`. + +`e` absorbs the op's own float rounding and an allowance for a GPU's gradient +error. The allowance is derived in the doc comment and measured on WebGL2 by +the fixture's enclosure check, as `iv_frustum_ball`'s is. + +The warp stays the consumer's, so CultMath owns no flow. The rule, stated in +`design.md` and tested for the (c) field: a warp whose value at the centre is +known, and whose variation over the ball is at most `ρ`, is enclosed by moving +the centre by `w(c)` and adding `ρ` to the radius. Interval balls benefit from +it too (V2). + +### Authority map + +- **Owner.** `src/CultMath/math.Affine.cs` owns affine-form semantics on the + CPU. `shaders/CultMath.Affine.hlsl` mirrors it bit for bit, and is the shader + semantic authority. `shaders/CultMath.glsl` is derived from the HLSL. +- **Inputs.** + - `snoise_grad`, whose `.w` is bit-equal to `snoise`; + - `SNOISE_LIPSCHITZ`; + - `SNOISE_HESSIAN`; + - the caller's forms and points. +- **Outputs.** `float3` forms, and `float2` intervals through `af_range`. +- **Derived state.** + - The GLSL text is generated by `GlslLowering`, unchanged. + - The fixture families are generated. + - The (c) efficiency rows are printed measurements, not state. +- **Forbidden writers.** + - A consumer linearising noise itself: the probe's V4 and V5 did this by + hand, and Hands must call `af_snoise`. + - A second curvature constant anywhere. + - Hand-edited `CultMath.glsl`. + - An affine or interval evaluator over `CultMath.Expr` (the parked tape's, + see collisions below). + - A flow or envelope function in CultMath. Consumers compose those, as for + intervals. +- **Shared paths.** The test field's `FogField`, the site ground and + Aetheria's nebula all reach affine noise through `af_snoise` or `af_fbm`, + and a tile slice through `af_frustum_*`. +- **Deletion line.** + - In `FogField.Bound` (`NoiseBoundTests.cs:875`), the warp-enlarged ball is + replaced by the centred warp, and the detail ball stops carrying the warp + (`:890`). + - The (c) probe-overhead assertion (`:1558`, `:1588`) is replaced as the + ruling on `affine-wells-contract` says. + - `design.md`'s "What they do not: ... affine forms" (`:584-587`) and "That + gap belongs to the tape target's affine forms" (`:623-624`) are rewritten. + - The tape target's open item on representation is closed. + +### Cut order + +1. **`affine-forms`, the one she can see.** + - It adds C# `math.Affine.cs` and `AffineTests`, plus `SNOISE_HESSIAN`'s + provenance. + - The (c) field gains the centred warp and the affine bound. + - (c) is re-measured with three bound rows on the same tiles: landed + interval, centred interval, affine. The range ceiling is printed beside + the oracle. + - An explicit, env-gated test writes a cull-map PNG of one (c) frame: dense + cells per ray for each bound, and the transmittance. Self publishes it. + - `design.md` is updated. + - It is blocked on `affine-wells-contract` only for the assertion's shape. +2. **`affine-shaders`.** + - It adds `CultMath.Affine.hlsl` and its Unity copy and `.meta`, the + include, and the regenerated `CultMath.glsl`. + - Fixture families are appended, with an enclosure check for `af_snoise` + and `af_frustum_ball`. + - The dxc and glslang compiles and the WebGL2 golden-fixture run happen on + Starfire. + - Both cuts share the branch `hands/cultmath-affine-forms` and merge + together after cut 2's Soul pass. Main never carries a C#-only `af_*`, so + three-runtimes-agree holds at every merged commit. + +Writing the C# before the HLSL inverts the usual order, because HLSL is the +semantic authority. The cost is small: the ops are plain arithmetic in the +common subset. Cut 2 changes the C# if the HLSL dialect forces something, +never the reverse. + +### Verification venue + +Heavy work runs on Yggdrasil through the stopgap: `dotnet test`, Stryker scoped +to `math.Affine.cs`, the (c) re-measure and the cull-map render. Starfire runs +only the Windows and GPU legs, one job at a time: +- `dxc -T lib_6_3 -HV 2021` on `CultMath.hlsl`; +- `glslangValidator` 16.6.0 on the lowered GLSL (ruling + `operator-compiler-downloads`); +- the WebGL2 golden-fixture run on the GTX 1070, under ANGLE D3D11 in a + browser. It is seeded from the glsl-lowering Soul pass's `gpu.js` + (follow-up `glsl-browser-parity-run`). + +### Collisions + +- **`cycles-converters` r1** edits the same seams: + - the include list (`CultMath.hlsl:509-510`); + - `HlslMirrorTests.cs:64-67`; + - `build-unity-package.ps1:82-87`; + - the fixture's family list. + + Whichever merges second rebases, regenerates `CultMath.glsl` and the fixture + (`CULTMATH_WRITE_GLSL=1`), and appends its families after the first's. + Nothing semantic collides. +- **`expr-ir` r1 and question `material-ir-in-cultmath`.** No file collides, + and neither cut waits on the other. + - Under `cultmath-ir-now`, the parked tape's step 2 will add interval and + affine evaluators over `ExprGraph`, and those will call `iv_*` and `af_*` + as op semantics. That is one more reason the affine ops are plain value + functions over `float3`, with no struct state. + - Under `aetheria-local`, nothing here changes. + - The `expr-ir` spec's negative check forbids `Interval(Evaluator)?` under + `Expr`. Self may add `Affine` to it at its next revision. +- **`site-ground` r3** (not dispatched): the void (e) and the site's flow + texture are not measured here. Follow-up `site-ground-affine-and-centred-warp` + carries both. Centring the warp in the site needs a bound on the flow + texture's variation, and the texture stays the one motion owner (it is + sampled at the centre, never re-derived). diff --git a/docs/cultmath-tape-target.md b/docs/cultmath-tape-target.md index b1121270..6bb2be89 100644 --- a/docs/cultmath-tape-target.md +++ b/docs/cultmath-tape-target.md @@ -108,6 +108,9 @@ source. - Run the (a) spike first, or go straight to (c)? - Pick the interval representation (plain intervals or affine arithmetic) and the noise bound strategy. + Affine forms for noise and warp were unparked on 2026-10-03 (ruling + `cultmath-tapes:ruling:affine-forms-unparked`): one shared symbol, mapped in + `docs/cultmath-interval-ground-cut.md`, "Pass 5". - Choose the bytecode register model for the HLSL interpreter, given GPU register pressure. - Decide whether the brush-grammar station project lives in Aetheria or in From a2af4056000ce2d1217484b2d5119e880c73306e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 21:26:17 +0200 Subject: [PATCH 2/4] Affine forms map: Pass 5b, the deep well (ruling wells-scale-order-of-magnitude) (c)'s wells are 1.1-2.5x the 60-unit noise band deep; a 10x well (depth and half-depth radius 600) measured on the scratch probe: interval already skips most empty space, affine gains 1.14x/1.16x over the centred interval. (c) becomes the deep well; the mass-derived wells stay as a printed row. Co-Authored-By: Claude Opus 5.5 --- docs/cultmath-interval-ground-cut.md | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/cultmath-interval-ground-cut.md b/docs/cultmath-interval-ground-cut.md index 3e09e35e..2bfa0a2b 100644 --- a/docs/cultmath-interval-ground-cut.md +++ b/docs/cultmath-interval-ground-cut.md @@ -1284,3 +1284,73 @@ only the Windows and GPU legs, one job at a time: carries both. Centring the warp in the site needs a bound on the flow texture's variation, and the texture stays the one motion owner (it is sampled at the centre, never re-derived). + +### Pass 5b: the deep well (ruling `wells-scale-order-of-magnitude`) + +The operator's words: "There should be a well pushing down with both a radius +and depth an order of magnitude larger than the span of the bit that's +expensive to sample, the fog gradient, otherwise there's no meaningful skipping +being measured". + +**Her diagnosis holds.** The expensive band is the stretch of the +pre-distortion height `s` where only the noise decides whether there is +density. +- `FogField` has `F = -20` and `A = 20`, and the noise reaches ±1.5. With the + fade at 1, a slice is provably empty only where `s >= F + 1.5 A = 10`. +- It is dense whatever the noise does where `s < F - 1.5 A = -50`. +- So the band is `G = 60` units. + +Today's (c) wells, from `NoiseBoundTests.cs:753-776`: +- **Depths** are `d = 30 M^0.175`: 67, 101, 150 and 101. That is 1.1 to 2.5 + times `G`. +- **Half-depth radii** are about `0.103 x 500 M^0.25` for a `PowerPulse` + exponent of 16: 163, 290, 515 and 290. That is 2.7 to 8.6 times `G`. +- **The zone bowl** is 64 deep, 1.07 times `G`. + +No well is ten times `G` deep. The camera sits just above the band +(`y = max(0, S + 10 - h)`) and grazes it, so most of the measured cost is the +band itself, and little of it is empty space. + +**The probe.** The probe added a field, `(c') deep well`, on the scratch +branch at `bf0148b8`: +- the zone bowl plus one well at its centre, with depth 600 and half-depth + radius 600 (`PowerPulse` exponent 2, scale 2220), so each is `10 G`; +- the camera's xz within 300 of the centre, at `y = F - h / 2`, halfway + between the sunken fog surface and the rim; +- slopes as (c), `m_y` in [-0.25, 0.1]. + +A level ray crosses about 600 units of empty well before the wall, and the +band there is about 73 units along the ray. The run used the same run +parameters, seeds and constants as Pass 5, on Yggdrasil. + +| `N = 8` | efficiency vs oracle | cost ratio | dense cells/ray | probe overhead | cull | +| --- | --- | --- | --- | --- | --- | +| deep well, warp 0, landed interval | 0.591 | 3.10x | 4.14 | 0.943 | 0.986 | +| deep well, warp 0, centred interval | 0.579 | 3.04x | 4.14 | 0.924 | 0.986 | +| deep well, warp 0, affine (one symbol) | **0.662** | **3.47x** | 3.27 | 0.879 | 0.993 | +| deep well, warp 0, range ceiling | 0.859 | (oracle ceiling 5.24x) | | | 0.997 | +| deep well, warp 60, landed interval | 0.462 | 2.45x | 6.58 | 0.970 | 0.967 | +| deep well, warp 60, centred interval | 0.555 | 2.95x | 4.34 | 0.926 | 0.984 | +| deep well, warp 60, affine (one symbol) | **0.644** | **3.42x** | 3.33 | 0.880 | 0.992 | +| deep well, warp 60, range ceiling | 0.848 | (oracle ceiling 5.31x) | | | 0.996 | + +There were no enclosure violations, and transmittance equals the dense march +in every row. + +What changes: +- Where the empty space is real, the interval march already skips most of it. + It reaches 0.59 at warp 0, and 0.46, or 0.56 with the warp centred, at warp + `D`. +- Affine's gain over the centred interval shrinks to 1.14x and 1.16x in + combined cost, against 1.39x and 1.37x on the band-dominated wells. +- Against the landed interval it is 1.12x and 1.40x. +- Affine reaches 0.77 and 0.76 of the range ceiling. What it wins is in the + wall's band, which is where the shallow wells spend everything. +- Probe overhead is 0.88 in both rows, still under the 0.90 rule. + +Decision for the cut (`affine-forms` r2): **(c) becomes the deep well, which +carries the contract.** The four mass-derived wells are kept as a printed row, +`(c-band)`. They are not the skipping measurement the ruling asks for, but they +are Aetheria's own geometry and the band-dominated case, where affine gains +most. Dropping them would hide where affine pays. The cull-map image renders a +deep-well camera at warp `D`. From c6fc478a302e3b41076afa7db29e117e5fe677be Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 21:28:37 +0200 Subject: [PATCH 3/4] Affine forms map: Pass 5b names the re-asked contract question and spec r2 Co-Authored-By: Claude Opus 5.5 --- docs/cultmath-interval-ground-cut.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cultmath-interval-ground-cut.md b/docs/cultmath-interval-ground-cut.md index 2bfa0a2b..f6c62ec1 100644 --- a/docs/cultmath-interval-ground-cut.md +++ b/docs/cultmath-interval-ground-cut.md @@ -1354,3 +1354,7 @@ carries the contract.** The four mass-derived wells are kept as a printed row, are Aetheria's own geometry and the band-dominated case, where affine gains most. Dropping them would hide where affine pays. The cull-map image renders a deep-well camera at warp `D`. + +The contract question is re-asked against these numbers as +`cultmath-tapes:question:affine-wells-contract-r2`. The first question was +withdrawn in its favour, and `affine-forms` r2 supersedes r1. From e201f9054a10e557011b43c7cbee738964ea4ae7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 4 Oct 2026 03:04:52 +0200 Subject: [PATCH 4/4] Affine forms map: Pass 6, the snoise diagonal tie (cut snoise-tie r1, affine-shaders r2) The cyclic step in Ashima's 3D simplex degenerates on the lattice diagonals, including the origin and every (t,t,t). The fix is one strict compare, giving simplexnoise1234's total tie order. Probes show it is bit-identical off the z = x tie set and continuous on it. SNOISE_LIPSCHITZ re-measures to 7.9640074, and SNOISE_HESSIAN does not move. Merge order: snoise-tie first, then the affine branch merges main. Co-Authored-By: Claude Opus 5.5 --- docs/cultmath-interval-ground-cut.md | 138 +++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/docs/cultmath-interval-ground-cut.md b/docs/cultmath-interval-ground-cut.md index f6c62ec1..56c96f7a 100644 --- a/docs/cultmath-interval-ground-cut.md +++ b/docs/cultmath-interval-ground-cut.md @@ -1358,3 +1358,141 @@ deep-well camera at warp `D`. The contract question is re-asked against these numbers as `cultmath-tapes:question:affine-wells-contract-r2`. The first question was withdrawn in its favour, and `affine-forms` r2 supersedes r1. + +## Pass 6: the snoise diagonal tie (finding `snoise-diagonal-tie-discontinuity`) + +Soul's affine pass found that `snoise(float3)` and `snoise_grad` jump by up to +0.72 where the three components of `x0` compare equal +(`cultmath-tapes:finding:cut-affine-forms.s1.snoise-diagonal-tie-discontinuity`). +The defect predates every cut in this campaign. It came with the Ashima kernel +and survived the 0.3.0 kernel change. The cut is +`cultmath-tapes:cut_spec:cut-snoise-tie.r1`. + +### Mechanism (probed) + +`g = step(x0.yzx, x0.xyz)` makes three pairwise comparisons, all non-strict: x +wins its tie with y, y wins its tie with z, and z wins its tie with x. That is a +cycle, not an order. When all three components are equal, `g = (1,1,1)`, so +`i1 = (0,0,0)` and `i2 = (1,1,1)`, and the four corners are not a simplex. + +- Corner 1 is then the origin vertex again, with the offset `x0 + 1/6`. Near a + lattice vertex that offset is short (`|x1|^2 = 1/12`), so the origin's + gradient is counted a second time. +- The tie set is the union of the lattice diagonals `v + s(1,1,1)`. It has + measure zero in space, but consumers sample it. Every point `(t, t, t)` lies + on it exactly in float32, for every `t`, because the three components are + computed identically. So does the origin: `snoise(0,0,0)` returns -0.4358730, + where the correct value is 0. `HlslSemanticsTests.cs:447` pins the wrong + value as golden. A consumer that samples `snoise(float3(t))` or + `cultmath_snoise(t.xxx)` reads a different function along the whole line. +- With the 0.5 kernel, a lattice vertex outside the four summed corners never + contributes on a face, because its squared distance from the face is at + least 0.5. So any total order of the three comparisons gives the same exact + value on every tie. The fix only has to make the comparisons an order; which + order it picks does not matter. + +### Prior art + +- Gustavson's reference C, `simplexnoise1234.c`, picks the 3D simplex with an + if-chain on `x0>=y0`, `y0>=z0` and `x0>=z0` and their complements. Ties go x + over y, y over z and x over z: a total order. +- Gustavson and McEwan's later `psrdnoise3.glsl` (JCGT 2022) replaces Ashima's + line with `step(f0.xyx, f0.yzz)` and a re-pairing. Its comment says ties + resolve "in priority order": also a total order, z over y over x. +- Upstream `webgl-noise` `noise3D.glsl` still has the cyclic `step`. Soul found + no fix in its issues or its git history (`log -S` on the line). + +**The fix** makes one comparison strict, z against x: +`g = (step(x0.y, x0.x), step(x0.z, x0.y), 1 - step(x0.z, x0.x))`. Ties then go +x over y, y over z and x over z, which is exactly `simplexnoise1234`'s order. +The probe's double-precision reference uses that if-chain verbatim, and the +patched float kernel agrees with it to within float noise. + +Other total orders are equivalent mutants: one strict compare placed on x, or +two strict compares. Only two spellings are degenerate, all-strict and today's +all-non-strict, and the cut's test kills both. + +### What the probes measured + +Both jobs ran on Yggdrasil, on main `a7966142` and on the affine head +`4ae986a5`. Each patched C# and both HLSL copies in the container, and +regenerated GLSL and the fixture. + +| Probe | Old rule | Strict-z rule | +| --- | --- | --- | +| Random points, \|p\| <= 4, 256, 4096 (1e6 each): old and new bit-equal | | 1e6 of 1e6 at every scale | +| Random points, \|p\| <= 65536 (1e6) | | 23 differ, each on an exact z = x tie, at ulp level | +| Lattice-diagonal points and their +-2 ulp neighbourhoods, \|I\| <= 8, 64, 512, 4096 (500k each, about 10.5k exact triple ties each): max \|f32 - ref64\| | 0.772, 0.768, 0.775, 0.774 | 2.9e-6, 2.2e-5, 1.8e-4, 1.6e-3 (float32 at that magnitude) | +| Pairwise faces x0 = (a,a,b) and permutations (108k), \|I\| <= 8 / 512 | 2.5e-6 / 1.3e-4 | the same | +| Jump inside a 1e-4 ball at Soul's five witnesses | 0.21 to 0.72 | 0.0016 to 0.0018 (smooth bound 7.6 x 2 sqrt3 x 1e-4 = 0.0026) | +| Soul's vertex scan, worst jump, \|L\| <= 8, 32, 128 | 0.72, 0.65, 0.65 | 3.7e-4, 1.5e-3, 5.7e-3; 0 of 400 beyond the smooth bound at every scale | +| `af_snoise` around the old Lipschitz spike (2000 forms x 21 points) | 926 misses, worst 38 e | 0 misses, worst -0.72 e | +| `MeasureLipschitz` (seed 0x11B5) | refined 9.181147, L = 10.099261 | refined 7.2400064, x 1.10 = **7.9640074**, witness (229.92526, 73.37927, -66.14595) | +| `MeasureHessian` (affine head) | 50.954895, giving 56.050385 | unchanged, same witness | +| `snoise_grad(p).w == snoise(p)` in a 1e-4 ball about the old witness | | 0 mismatches | + +The full suite passes once four things change together: the tie fix, `L` +re-pinned, the witness moved, and the origin golden set to 0. It passes +288 of 288 on main and 300 of 300 on the affine head. + +With only the tie fix and nothing re-pinned, exactly two tests fail: + +- `LipschitzConstantPinsSampledGradients`, the ratio pin, failing as + designed; +- the origin golden. + +`HlslSourceCompatibilityTests` passes. Its special values include all-equal +triples, so it pins the HLSL tie rule bit for bit against C#. + +Fixture bytes: + +- The tie fix alone changes no fixture case. The 256 random points per family + never land on a tie. +- Re-pinning `L` changes 41 cases, all in `iv_snoise_ball(vec3)` and + `iv_fbm_ball`. These are the cases whose bound is not clamped to `[-1, 1]`. +- Every other family is byte-identical. +- `CultMath.glsl` changes 3 lines: the two tie lines and the constant. + +### What follows + +- **`SNOISE_LIPSCHITZ` narrows by 21%,** from 10.099261 to 7.9640074. Every + Lipschitz-form bound narrows with it: `iv_snoise_ball`, `iv_fbm_ball`, the + Lipschitz form inside `af_snoise`, and the march's skipping. + - The (c) rows in Passes 5 and 5b were measured at the old `L`. They are + conservative, not wrong. The affine branch re-measures them after it + merges main. + - `SNOISE_HESSIAN` does not move. +- **Release.** `math.snoise` returns different values on the tie set, which + includes the origin and every `(t, t, t)`, points ordinary consumers sample. + - The precedent is `asura:ruling:qn1-snoise-upstream`: a public snoise value + change ships as a minor bump while the package is below 1.0, filed under + Breaking. So the next `cultmath-unity` release after this cut is 0.4.0, + not 0.3.x. + - The intervals and `L` were never released (`cultmath-unity-v0.3.0` + predates `math.Interval.cs`), so re-pinning `L` is not a release change. + - The release is not part of this cut. Follow-up + `cultmath-tapes:follow_up:snoise-tie-release` carries it, together with + Aetheria's pin bump to that release. +- **Consumers.** + - gamecult-site vendors no CultMath GLSL yet, because `site-ground` has not + landed. It gets the fixed GLSL when it does. + - Aetheria pins 0.2.4, the old 0.6 kernel, which has the same cyclic `step` + line. Its pin bump (`asura:follow_up:aetheria-snoise-pin-bump`) should + target the release that carries this cut. + - No other runtime under `packages/` (Rust, TS, Python) carries snoise. + +### Merge order + +1. `snoise-tie` branches from main `a7966142` as + `hands/cultmath-snoise-tie`. It gets its own Soul gate and merges to main + first: it is small, independent, and fixes a defect in released code. Its + two fixture families go after `phacelle`. +2. `hands/cultmath-affine-forms` then merges main with a merge commit. It never + rebases, because the branch is shared. The job's edits to + `math.Interval.cs`, `NoiseBoundTests.cs` and `design.md` applied cleanly to + the affine head. The affine fix batch's (c) re-measure and the affine Soul + pass run on the merged tree, so they see the new `L`. +3. `affine-shaders` is revised to r2, which supersedes r1. It depends on + `snoise-tie` and starts from the merged tree, so its `af_*` families are + appended after the tie families, and its device enclosure checks run on the + fixed `cultmath_snoise_grad`. Nothing else in it changes.