From 4d3cabec5a425e8db657f9ab26ab557d8d67d299 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 11:14:39 +0200 Subject: [PATCH] Imagination pass 4: interval-ops r3. The frustum ball widened by its derived float32 bound, the analytic constant-density body, one shipped void (Q9), (c) held to 90% of its ceiling, LOD weights exercised, Stryker kills demonstrated Resolves Soul's five findings on cut-interval-ops.h2 and carries the rulings operator-wells-contract-vs-ceiling and operator-analytic-wall-body. glsl-lowering r2 and site-ground r3 now depend on interval-ops r3; the site-ground spec names the one shipped void with (Rc, e) derived from (Rh, S) and the body-start word. Co-Authored-By: Claude Fable 5.1 --- ...rval-ground-cut-glsl-lowering.r2.spec.json | 4 +- ...erval-ground-cut-interval-ops.r3.spec.json | 268 ++++++++++++++++++ ...terval-ground-cut-site-ground.r3.spec.json | 19 +- docs/cultmath-interval-ground-cut.md | 243 +++++++++++++++- 4 files changed, 514 insertions(+), 20 deletions(-) create mode 100644 docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json diff --git a/docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json b/docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json index 6d3082a7..abb57ab5 100644 --- a/docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json +++ b/docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json @@ -7,10 +7,10 @@ "branch": "hands/cultmath-glsl-lowering", "base": "c9bd003b", "depends_on": [ - "cultmath-tapes:cut_spec:cut-interval-ops.r2" + "cultmath-tapes:cut_spec:cut-interval-ops.r3" ], "first": [ - "Follow-up on cut report cultmath-tapes:cut_report:cut-glsl-lowering.h1 (head 5ae207e8, worktree F:\\Projects\\CultLib-glsl). Rulings in force: operator-split-phacelle, operator-compiler-downloads, self-exp-measure-in-site. Map: docs/cultmath-interval-ground-cut.md, section 'Pass 3'. First action: rebase hands/cultmath-glsl-lowering onto the head of hands/cultmath-interval-ops after its r2 lands (the base above is r1's head; the report records the real base). Regenerate the GLSL and the fixture on the rebased HLSL before any edit, so the first commit is the regeneration alone and shows what interval-ops r2 changed (cultmath_iv_frustum_ball is new; its fixture family is exact-tolerance geometry).", + "Follow-up on cut report cultmath-tapes:cut_report:cut-glsl-lowering.h1 (head 5ae207e8, worktree F:\\Projects\\CultLib-glsl). Rulings in force: operator-split-phacelle, operator-compiler-downloads, self-exp-measure-in-site. Map: docs/cultmath-interval-ground-cut.md, section 'Pass 3'. First action: rebase hands/cultmath-glsl-lowering onto the head of hands/cultmath-interval-ops after its r3 lands, not r2's (r3 widens cultmath_iv_frustum_ball's radius by its float32 bound, so the fixture family regenerated against r2 would be stale; the base above is r1's head; the report records the real base). Regenerate the GLSL and the fixture on the rebased HLSL before any edit, so the first commit is the regeneration alone and shows what interval-ops r2 and r3 changed (cultmath_iv_frustum_ball is new and carries the r3 widening; its fixture family is exact-tolerance geometry).", "The split (operator-split-phacelle). Transformation step 1 becomes: each #include is inlined in place, except an include whose file is under its own licence, which is lowered on its own into its own output file. The list of such includes is one table in GlslLowering, SeparateFiles = { \"CultMath.Phacelle.hlsl\" -> \"CultMath.Phacelle.glsl\" }, with the reason beside the entry (file-level MPL-2.0). That is an enumerated rule of the lowering, documented as step 1 in design.md; it is not a per-function special case, and nothing else is keyed by name. Lower(root) returns the two texts; Lower(string) stays for the inlined body if that is simpler, but there is one resolver (ReadShaderSource, generalised to accept the include predicate), never a second.", "shaders/CultMath.glsl: the provenance line says it is generated from CultMath.hlsl and CultMath.Interval.hlsl, MIT, and that cultmath_phacelle is in CultMath.Phacelle.glsl, to be concatenated after this file by consumers that call it. The dropped include line leaves nothing behind. shaders/CultMath.Phacelle.glsl: its own provenance line (generated from CultMath.Phacelle.hlsl, MPL-2.0, requires CultMath.glsl before it), the MPL-2.0 header and attribution exactly as the HLSL carries them, the CultPhasor struct and cultmath_phacelle, guard CULTMATH_PHACELLE_GLSL. Both files pinned by CommittedGlslEqualsLowering; CULTMATH_WRITE_GLSL=1 writes both.", "Pin tests over both files: NoHlslOnlyTokensSurvive runs on each; EveryPublicFunctionIsLowered takes the union of both files' definitions against the HLSL (minus texture samplers) and additionally asserts cultmath_phacelle and CultPhasor appear only in the Phacelle file; a new PhacelleFileCompilesOnlyAfterTheLibrary is not needed, the compile check below proves it.", diff --git a/docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json b/docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json new file mode 100644 index 00000000..d008f90f --- /dev/null +++ b/docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json @@ -0,0 +1,268 @@ +{ + "campaign": "cultmath-tapes", + "cut": "interval-ops", + "revision": 3, + "title": "Interval ops r3: the frustum ball widened by its derived float32 bound, the analytic constant-density body, one shipped void, (c) held to its ceiling, LOD weights exercised, Stryker kills demonstrated", + "repo": "GameCult/CultLib", + "branch": "hands/cultmath-interval-ops", + "base": "43183276", + "depends_on": [], + "first": [ + "Follow-up on cut report cultmath-tapes:cut_report:cut-interval-ops.h2 (head 43183276, worktree F:\\Projects\\CultLib-iv) and Soul's verdict cultmath-tapes:verdict:cut-interval-ops.s1 with its five findings, each resolved below in order. New rulings in force: operator-wells-contract-vs-ceiling ((c)'s contract is at least 90% of the probe-free ceiling, not 2x) and operator-analytic-wall-body (where the envelope interval proves the density constant, one closed-form Beer-Lambert step). Map: docs/cultmath-interval-ground-cut.md, 'Pass 4'. r2's ops, field models, tile march and scenarios stand; this revision touches math.Interval.cs, both tracked CultMath.Interval.hlsl copies, NoiseBoundTests.cs, SnoiseBall tests in the same file, design.md and README only. Nothing new in src beyond the radius widening.", + "Finding 1, frustum-ball-float-crossing (Medium). The ball is widened, in C# and both HLSL copies: radius += (|c.x| + |c.y| + |c.z| + radius) * 2^-20 (the literal 9.5367431640625e-7, exact in float32), after the r2 radius. Derivation, u = 2^-24: the centre c = (m_c z_m, z_m) carries at most two roundings per component (z_m = fl(z0 + z1) * 0.5, then the product), so |c_float - c_exact| <= 2u |c|_1; the radius carries at most seven first-order roundings (z1 - z0; the axis sqrt at 3u; the product; z1 * footprint; the sum; + warp), so r_exact <= r_float (1 + 7u); the widening's own addition rounds once more. 2^-20 = 16u covers 8u (|c|_1 + r) twice over. fma contraction removes roundings and only tightens, so dxc's HLSL and any driver's GLSL stay covered. The sentence 'the two vectors are never parallel, so the inequality is strict and float32 cannot cross it' leaves math.Interval.cs:179-180, the HLSL and design.md:412; the rule and its derivation take its place.", + "(Finding 1, continued.) TileBallEnclosesEveryRaySegment gains Soul's extreme families beside the r2 domain, 2,000 cases each, 8 footprint corners at both depth ends with the flow pointed outward along the corner's direction from the centre, the distance computed in double from the float ball and the float ray (point (m z, z) formed in double from the float m and z), no tolerance: z0 == z1; thin (z1 - z0 = 1e-6 z1); |m_c| up to 1000; far (z up to 1e7); z0 = 0 with z1 down to 1e-3; f down to 1; warp up to 1e6. The derivation pin becomes two-sided: r_exact <= ball.w <= r_exact + 2^-19 (r_exact + |c|_1), r_exact in double, so a ball that forgets the widening fails and a ball that grows past the rule fails. The hand mutations of r2 stay; a fourth, the widening dropped, must fail the degenerate family alone. WarpedPointsStayEnclosed is unchanged.", + "Finding 2, aetheria-far-plane-miscited (Low). FogField's grid becomes Quadratic(0.3, 2048, 256), cited as the Main Camera (Aetheria d3075eb0 Assets/Scenes/ARPG.unity:31777 m_Name Main Camera, :31830 far clip plane 2048; VolumeCloudRenderer.cs:36 RequireComponent(Camera)); the comment at NoiseBoundTests.cs:594 no longer names the Tint camera's 1000. Soul re-measured (c) at far 2048: 1.39-1.51x at warp D, 2.46-2.74x at warp 0, ceiling within 1-2% of the ratio; the verdict on (c) does not move. Print every (a)-(c) row again at 2048.", + "Finding 3, shipped-void-two-scenes (Medium). ONE shipped void, authored as (Rh, S) per the map's own rule, everything else derived; the map, the site-ground r3 spec and the test agree on it: Rh = 198, S = 50, so Rc = Rh + S = 248 and e = -ln CARVE / ln(1 - (Rh / Rc)^2) = 0.40 (CARVE 1.5); fade band [Rh - S, Rh], A = 20, F0 = 0.01, D = 10, K = 1/30. This is the scene r2 measured (2.21x) and the Footprint-LOD rule's; the map's site section (Rc 640, e 4, a 442-wide ramp) was the other scene and is superseded. The honest choice changes the look (a 50-wide noisy wall with a kinked outer edge, not a soft pseudo-Gaussian wall), so it is Q9 in the map, a fork for the operator with (a) recommended. Hands codes (a); another ruling moves only the shipped constants and the site section. VoidField's doc comment derives (Rc, e) from (Rh, S) and names it the one shipped void.", + "Finding 4, lod-weights-never-exercised (Low). VoidField takes a focal length. One configuration reaches the weights: '(e) LOD far' Rh = 4000, S = 50, camera low, f = 467 (1080 rows at a 98 degree vertical FOV): theta = 1/467, so the fine weight (wavelength 25) is below 1 past z = 1460 and 0 past 2920, the coarse weight (100) below 1 past 5840, and the grid (|camera - c_v| + Rc + 6/K, about 7990) reaches both; 4 z theta exceeds 12.5 past z = 1460, so LodStep is footprint-set there too. It joins EnvelopeBoundEnclosesDensity's field list (the truncated check is no longer vacuous) and the saving test prints it, not asserted. Hand mutation that must fail the truncated check on it: w(z0) for w(z1) in WeightBound.", + "(Finding 4, continued: the deviation.) Recorded in the test's doc and design.md: the bound takes each octave weight over [w(z1), 1], not the spec's [w(z1), w(z0)], because one Bound serves the full field (w = 1) and the truncated one (w in [w(z1), w(z0)], w decreasing in z). [w(z1), 1] contains both, iv_mul is monotone in its interval argument, so the composed interval is a superset of either field's and encloses both. The slack is only in far tiles whose step the footprint already sets.", + "Finding 5, stryker-timeout-survivor (Low). Mutant 2525 (iv_sqr a.x >= 0 to a.x > 0) is equivalent: at a.x == 0 both branches return the same ends ((0, h) either way, (0, 0) when a.y == 0); the report says so by its diff, since numbers are per run. New enclosure cases kill the mirror-only survivors semantically: SnoiseBallEnclosesPoints and FbmBallEnclosesPoints draw radii LogUniform(1e-3, 1e3), so L / r in iv_snoise_ball (too tight once r > 1: at r = 100 the mutant's bound is 0.2 wide while snoise over the ball spans [-1, 1]) dies to an enclosure test, not only to the HLSL mirror. The mutants Soul listed as mirror-only (iv_scale s >= 0 to s > 0; the two iv_abs mutants) are hand-run and each either named equivalent with the one-line argument (signed zero or a boundary both branches map alike) or killed by one added point case in IntervalTests.", + "(Finding 5, continued.) Stryker runs again as r2 did, --since:43183276; every timeout in math.Interval.cs is hand-run (patch from the mutation report, dotnet test minus IntervalSkipHalvesEvaluations) and the report lists killed, survived and equivalent by diff. The report claims only demonstrated kills; a timeout is not a kill.", + "The analytic body (ruling operator-analytic-wall-body), consumer-side in the test and the site, no new CultMath function. When it applies: the Scenario's Bound over the remaining range [z, z_end] of the grid, gated, is a point interval with positive value: the envelope with the free noise interval [-1.5, 1.5] (coverage in [-1, 1] plus half the detail) proves fade = 1 and d_lo + A (fade n)_lo >= Rc, so every point of every ray of the tile over the rest of the grid has density exactly K for any noise value; or, generically, lo == hi > 0 from whichever path the bound took. Only then does the march integrate T *= exp(-K ext (z_end - z) |dir|) in one step and terminate (the grid end is past opacity by construction). For (a)-(d) no Bound is a positive point (the fog's density varies with s'), so the step never applies there, and the test asserts bodyStart = -1 for every (a)-(d) tile.", + "(Analytic body, continued.) MarchTile returns (dense cells, bodyStart, bodyDensity). As r2 on cell ranges; when a single cell is found not provably empty and its own gated bound is a positive point (the cell is provably constant), one more probe over [grid[j], grid[^1]]: if that is a positive point too, bodyStart = j, bodyDensity = its value, and every remaining cell is appended dense without probing (adding cells is always sound; it also drops the body's probes). The mask's meaning is unchanged, so the masked fixed-step march's exact agreement with the reference stands as the soundness pin. MarchLod: inside unmasked runs as r2; on reaching a cell >= bodyStart it takes the one analytic step over [z, grid end], counts one step, and stops.", + "(Analytic body, the grid end.) Every void configuration's grid ends at |camera - c_v| + Rc + 6/K (was 5/K): the body starts at d >= Rc + A 1.5 = Rc + 30 plus box slack, so 6/K leaves at least 4.9 of optical depth, T <= 0.0075 < 0.02, and 'unfinished rays 0' still prints. This is the grid end, not the field; the reference stops by opacity and its count does not move.", + "(Analytic body, the pin.) New test AnalyticBodyMatchesFineMarch over the (e) grid, the shipped void and the LOD-far configuration, 2,000 seeded tiles at N in {1, 4, 8, 16}: (1) wherever MarchTile reports bodyStart b: the Bound over [grid[b], grid end] is a positive point; the full and the truncated Density at 64 random (pixel, sub-pixel, cell >= b, depth in cell) points equal bodyDensity exactly, no tolerance; (2) the analytic factor exp(-bodyDensity ext L) over the body equals the fixed-step product of the body cells' Integrate factors (no early-out, same cells) within 1e-4 relative; (3) for every (a)-(d) tile bodyStart = -1. Hand mutation that must fail (1): in VoidField.Compose, CarveRadius for HollowRadius in the lo test (the body claimed from the ramp). The site's mirror of this (site-ground r3): the pre-pass writes the body-start cell in the texel's fourth word, the main pass takes the same one step; its browser check is the dense-reference agreement already in that spec.", + "Contracts after r3, asserted at N = 8 in IntervalSkipHalvesEvaluations, else one Assert.Skip naming saving-2x-scenarios with every shortfall: (a) at least 2x combined cost (the lower of warp 0 and D), measured 16384x. (c) at least 0.90 of its probe-free ceiling (Ratio / Ceiling, the lower of warp 0 and D), measured 0.99 at warp D and 0.96 at warp 0 in r2; the ruling's reading is that the ceiling is what intervals control and the fog band's cost is the field's. (e) at least 2x fewer LOD steps per pixel than the fixed-step reference at the ONE shipped void (200 tiles), the masked fixed-step march agreeing exactly and the LOD march within 0.02 of the reference. (b), (d) and the (e) grid incl. LOD-far: printed, not asserted. Nothing is tuned toward any contract: the fields, the grids (beyond the stated 6/K end), e = 0.2 and the seeds stay.", + "Predictions to confirm or refute (Imagination's rule from r2's printed rows: the LOD steps that evaluated noise are about snoise / 2.8 plus two pre-band steps; the rest were constant-body steps, which the analytic step makes one). Shipped void: steps per pixel 36.34 (ref) to about 12.5 (was 16.45), 2.6-3.3x fewer; snoise per pixel about 26.6, unchanged; combined cost about 1.38x (was 1.34x); envelope probes per tile about 50 (was 59.1). Grid, low camera, ref/LOD steps: S = 10 about 5-6x at Rh 100-400 and 11x at Rh 1000 (was 2.1-3.4x and 6.8x); S = 50 about 2.5x, 2.8x, 3.5x, 5.2x for Rh 100, 200, 400, 1000 (was 1.8, 2.2, 2.9, 4.5); S = 150 unchanged (rays end inside the 150-wide ramp before the body is proven). Rh 1000 centred S 10: about 30x (was 12.4x). LOD-far: printed, none predicted. If the shipped point lands under 2.6x, say which assumption failed: the pre-band's 0-snoise steps, or a body start later than Rc + 30 by box slack.", + "Compile and mutation checks as r2: Yggdrasil dotnet test (counts and every IV-REPORT line pasted); Stryker --since:43183276 -m math.Interval.cs from a plain clone with every timeout hand-run as in Finding 5; Starfire FXC cs_5_0 over every cultmath_iv_* on both HLSL copies and tools/get-dxc.ps1 then dxc -T lib_6_3 -HV 2021 on both, exit 0 (Soul could not re-run these for r2; they are re-run here because the HLSL changes). Carried and still out of scope, named in the report: the Unity CultMath.dll is not rebuilt; the Unity HLSL copies stay one blob with packages/cultmath/shaders; public asfloat(uint) stays. Main moved by docs only since 43183276's base (Imagination passes 3-3d and 4); say so. glsl-lowering r2 rebases onto this revision's head, not r2's (its fixture family for cultmath_iv_frustum_ball changes with the widening); its spec now says so." + ], + "deletes": [ + { + "path": "packages/cultmath/src/CultMath/math.Interval.cs", + "lines": 2, + "note": ":179-180 the sentence 'The two vectors are never parallel, so the triangle inequality is strict' and its float32 claim; replaced by the widening rule and its derivation." + }, + { + "path": "packages/cultmath/docs/design.md", + "lines": 1, + "note": ":412 'the two vectors are never parallel, so the bound is strict'; replaced by the rule." + }, + { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "lines": 1, + "note": ":594 the Tint camera citation (ARPG.unity:635-636, far 1000); replaced by the Main Camera's :31830, far 2048." + }, + { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "lines": 3, + "note": ":1181-1194 the 2x test on ratios[c] and the skip message's wording; replaced by the 0.90-of-ceiling test and a message naming each shortfall." + }, + { + "path": "packages/cultmath/docs/design.md", + "lines": 12, + "note": ":461-497 the r2 numbers (1.41x, 16.45, 2.21x, the (c) shortfall paragraph); replaced by r3's printed numbers and the three contracts." + } + ], + "keeps_moves": [ + "Every iv_* op, iv_snoise_ball, iv_fbm_ball, SNOISE_LIPSCHITZ and its provenance tests: untouched. iv_frustum_ball's centre and the r2 radius terms: untouched; only the widening term is appended.", + "FogField, SlabField, WarpedNoise, Compose, the gated probe (BestBall), MarchTile's range hierarchy, the masked fixed-step march and Run: kept; MarchTile gains the body return and MarchLod the one analytic step.", + "VoidField's field, Density, Bound, Weight, WeightBound and LodStep: kept; the constructor gains the focal length and the grid end moves to 6/K; the shipped point stays Shipped() = (198, 50, shipped camera).", + "TileBallEnclosesEveryRaySegment's r2 domain, EnvelopeBoundEnclosesDensity, IntervalTests, TightnessReport, MeasureLipschitz, LipschitzConstantPinsSampledGradients: kept; the first two gain cases.", + "HlslSourceCompatibilityTests: the mirror count stays 53; cultmath_iv_frustum_ball's new text must match the C# bit for bit." + ], + "adds": [ + "math.Interval.cs and both CultMath.Interval.hlsl: the widening line in iv_frustum_ball / cultmath_iv_frustum_ball, and the rule in the doc comment.", + "NoiseBoundTests.cs: the extreme families and the two-sided derivation pin in TileBallEnclosesEveryRaySegment; VoidField focal parameter and the LOD-far configuration; MarchTile's (bodyStart, bodyDensity); MarchLod's analytic step; AnalyticBodyMatchesFineMarch; the (c) ceiling contract; radii to 1e3 in the two ball tests; the WeightBound deviation in the doc comment; the shipped-void derivation in VoidField's doc comment.", + "IntervalTests.cs: at most three point cases for the mirror-only mutants that are not equivalent (Finding 5).", + "design.md 'Intervals': the widening rule and derivation; the analytic-body paragraph (condition, pre-pass word, one step, the pin); the WeightBound deviation; r3's printed numbers under the three contracts; the one shipped void with (Rc, e) derived.", + "README.md :41: one clause that iv_frustum_ball's radius carries its float32 rounding bound." + ], + "file_changes": [ + { + "location": { + "path": "packages/cultmath/src/CultMath/math.Interval.cs", + "line": 183 + }, + "change": "iv_frustum_ball: after the r2 radius, radius += (abs(cx) + abs(cy) + abs(zm) + radius) * 9.5367431640625e-7f; doc comment :168-182 states the rule and derivation in place of the strictness sentence." + }, + { + "location": { + "path": "packages/cultmath/shaders/CultMath.Interval.hlsl", + "line": 110 + }, + "change": "cultmath_iv_frustum_ball: the same widening line; both tracked copies (packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Interval.hlsl identical blob)." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 128 + }, + "change": "TileBallEnclosesEveryRaySegment: the seven extreme families, outward flow at corners, double distance, the two-sided pin; the file header lists the fourth hand mutation (widening dropped) and the WeightBound and Compose mutations." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 58 + }, + "change": "SnoiseBallEnclosesPoints and FbmBallEnclosesPoints: radius LogUniform(1e-3, 1e3)." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 594 + }, + "change": "FogField: Grid = Quadratic(0.3, 2048, 256), Main Camera citation." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 829 + }, + "change": "VoidField: focal parameter (default 935); LodFar() = (4000, 50, low, f 467); grid end reach + Rc + 6/K; doc comment with the (Rh, S) -> (Rc, e) derivation, the one shipped void, the [w(z1), 1] deviation." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 1009 + }, + "change": "MarchTile returns (cells, bodyStart, bodyDensity) with the one confirming probe; MarchLod takes the analytic step at bodyStart; Run and RunStats carry it; ReportVoid prints body steps." + }, + { + "location": { + "path": "packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs", + "line": 1160 + }, + "change": "IntervalSkipHalvesEvaluations: (c) asserted as Ratio >= 0.90 Ceiling; the LOD-far row printed; the (e) grid printed with the analytic body; AnalyticBodyMatchesFineMarch added beside it." + }, + { + "location": { + "path": "packages/cultmath/docs/design.md", + "line": 403 + }, + "change": ":403-430 the frustum-ball paragraph gains the widening rule; :412 strictness sentence deleted; :461-497 the saving section rewritten with r3's printed numbers, the three contracts, the analytic body and the shipped void." + }, + { + "location": { + "path": "packages/cultmath/README.md", + "line": 41 + }, + "change": "iv_frustum_ball: 'its radius carries its own float32 rounding bound'." + } + ], + "authority_map": { + "owner": "CultMath owns the interval ops, the noise bounds and the frustum ball, whose enclosure promise now covers its own float32 rounding (the widening is part of the function, not of any consumer). The consumer (the test's Scenario, the site's pre-pass) owns the envelope, the composed bound, the body proof and the Beer-Lambert step. The saving test owns the proving-ground numbers; the operator owns the shipped void (Q9) and the contracts (the two rulings).", + "inputs": [ + "the r2 surface unchanged; a tile's geometry and warp; a scenario's grid, wells, camera, focal length and units, Aetheria's Main Camera far plane 2048 cited by line", + "the one shipped void as (Rh, S) = (198, 50); every other void constant derived from it" + ], + "outputs": [ + "iv_frustum_ball / cultmath_iv_frustum_ball: a float32 ball that encloses every exact point of every ray of the tile over the segment, warped, under float32 evaluation of the ball itself", + "per tile: the dense cells and, when the envelope proves the rest constant, the body-start cell and its density; printed numbers per scenario including the body steps" + ], + "derived_state": [ + "the widening is derived from the computed centre and radius (2^-20 of |c|_1 + r), never a tuned epsilon; a consumer adds nothing for the ball's own rounding and remains responsible for its world transform", + "(Rc, e) are derived from (Rh, S): Rc = Rh + S, e = -ln CARVE / ln(1 - (Rh/Rc)^2); no void constant is authored twice", + "the body start is derived from the same Bound the mask is derived from, by one more probe; the main pass reads it, never decides it", + "the (c) contract value is derived from the run (0.90 of its own ceiling); no absolute target is written for (c)" + ], + "forbidden_writers": [ + "no change to SNOISE_LIPSCHITZ, iv_snoise_ball, iv_fbm_ball, the noise shape, e = 0.2, the seeds or any field toward a contract; the grid end moves to 6/K for termination only", + "no analytic step from a pointwise density or from the noise ball alone: only a point-interval Bound over the whole remaining range proves the body", + "no early-out of the pre-pass on transmittance; no probe in the main pass (the body-start cell is read, its constant is one pointwise envelope value)", + "no second shipped void anywhere: the map's site section, the site-ground spec and VoidField.Shipped() name (198, 50) and derive the rest; no number in a doc or comment that the committed test did not print in this run", + "no new CultMath function; PowerPulse, the box, the composition and the Beer-Lambert step stay consumer-side; no tape, prune, affine or choice token in the interval files" + ], + "shared_paths": [ + "the masked fixed-step march still integrates exactly the dense march's cells (the soundness pin); only MarchLod takes the analytic step, and AnalyticBodyMatchesFineMarch ties that step to the fixed-step product over the same cells", + "one Bound serves the enclosure tests, the mask, the body proof and the gated probe; no second composition and no second constancy test", + "the widening lives once in C# and is mirrored bit for bit into both HLSL copies by the existing mirror test; GLSL follows by the lowering (glsl-lowering r2 rebases onto this head)" + ], + "deletion_line": "The false strictness claim leaves the C# doc, the HLSL and design.md before the widening is written; the Tint camera citation leaves before the Main Camera's is written; the (c) 2x assertion and the second shipped void leave before the ceiling contract and the one void are written. Net in src: one line and a doc comment. In tests: one new pin test, seven case families, one configuration, the body return; what they buy is intervals-enclose holding for the ball's own float32 evaluation, the analytic body the ruling asked for with its soundness pinned, LOD weights actually exercised, and a mutation report that claims only what it demonstrated." + }, + "verification": { + "builds": [ + "Yggdrasil: dotnet test packages/cultmath/tests/CultMath.Tests green (MeasureLipschitz/MeasureMonotonicity excluded by trait); paste counts and every IV-REPORT line, (a)-(d) at far 2048, the (e) grid with body steps, LOD-far, the shipped headline", + "Yggdrasil: dotnet stryker --since:43183276 -m math.Interval.cs from a plain clone; every timeout hand-run; killed, survived and equivalent listed by diff; 0 undemonstrated", + "Starfire: FXC cs_5_0 kernel over every cultmath_iv_* including cultmath_iv_frustum_ball, both HLSL copies, exit 0", + "Starfire: tools/get-dxc.ps1 (pinned) then dxc -T lib_6_3 -HV 2021 on both HLSL copies, exit 0, no diagnostics" + ], + "tests": [ + { + "name": "NoiseBoundTests.TileBallEnclosesEveryRaySegment", + "pins": "every exact point of every ray of the tile, corners at both depth ends with an outward flow, lies inside the float32 ball, in the r2 domain and in the seven extreme families, distance in double, no tolerance; the radius is the derivation plus at most 2^-19 (r + |c|_1) and at least the derivation." + }, + { + "name": "NoiseBoundTests.EnvelopeBoundEnclosesDensity", + "pins": "the composed bound encloses the full and the footprint-truncated density at every warped point of 2,000 slices, including the LOD-far configuration where octave weights are strictly between 0 and 1 and at 0." + }, + { + "name": "NoiseBoundTests.AnalyticBodyMatchesFineMarch", + "pins": "a body start is reported only where the Bound over the whole remaining range is a positive point and every sampled density there equals it exactly; the analytic factor equals the fixed-step product within 1e-4 relative; no (a)-(d) tile reports a body." + }, + { + "name": "NoiseBoundTests.IntervalSkipHalvesEvaluations", + "pins": "(a) at least 2x combined cost; (c) at least 0.90 of its probe-free ceiling; (e) at the one shipped void at least 2x fewer LOD steps per pixel, the masked fixed-step march exact, the LOD march within 0.02; every row printed including body steps; one Skip naming saving-2x-scenarios on any shortfall." + }, + { + "name": "NoiseBoundTests.SnoiseBallEnclosesPoints and FbmBallEnclosesPoints", + "pins": "the ball bounds enclose their functions for radii up to 1e3, so a bound tightened for r > 1 (L / r) dies to an enclosure test." + }, + { + "name": "IntervalTests.EveryOpEnclosesItsPoints (existing, plus point cases)", + "pins": "r1's enclosure for every op still holds; any non-equivalent mirror-only mutant from Finding 5 has a point case that kills it." + }, + { + "name": "HlslSourceCompatibilityTests.EveryMirrorFunctionMatchesCSharpMath (existing)", + "pins": "the widened cultmath_iv_frustum_ball matches iv_frustum_ball bit for bit; 53 functions compared." + } + ], + "negative": [ + { + "pattern": "never parallel|cannot cross|strict", + "scope": "math.Interval.cs iv_frustum_ball doc comment, CultMath.Interval.hlsl, design.md 'Intervals' frustum-ball paragraph: 0 matches." + }, + { + "pattern": "635-636|far 1000|Tint", + "scope": "NoiseBoundTests.cs: 0 matches; ':31830' and '2048' appear in FogField's citation." + }, + { + "pattern": "Rc = 640|e = 4\\b|Rc ~ 220|e ~ 0\\.6|about 220|about 0\\.6", + "scope": "NoiseBoundTests.cs and design.md: 0 matches; the shipped void is derived from (198, 50) as Rc = 248, e = 0.40." + }, + { + "pattern": "tape|prune|affine|choice|PowerPulse|pulse|envelope|Scenario|body|Beer", + "scope": "math.Interval.cs and CultMath.Interval.hlsl: 0 matches (case-insensitive); the analytic body is consumer-side." + }, + { + "pattern": "Assert\\.Skip", + "scope": "NoiseBoundTests.cs: at most one, in IntervalSkipHalvesEvaluations, naming saving-2x-scenarios; 0 if (a), (c) and (e) meet their contracts." + }, + { + "pattern": "10\\.099261|0\\.35f", + "scope": "git diff 43183276..HEAD -- packages/cultmath/src: the constant is not touched; the slab's F0 stays in (d)." + }, + { + "pattern": "git diff --name-only 43183276..HEAD", + "scope": "only packages/cultmath/**; both tracked CultMath.Interval.hlsl copies change together and stay one blob." + }, + { + "pattern": "git diff 43183276..HEAD -- packages/cultmath/src | grep '^[-+]' | grep -v '^[-+][-+]' | grep -v '///' | wc -l", + "scope": "at most 4 changed code lines in src: the widening line in iv_frustum_ball and nothing else." + } + ], + "operator": [] + }, + "estimate": { + "lines_added": 260, + "lines_removed": 40, + "dependencies_added": [], + "dependencies_removed": [], + "formats_added": [], + "formats_removed": [], + "targets_added": [], + "targets_removed": [] + }, + "rulings": [ + "cultmath-tapes:ruling:operator-wells-contract-vs-ceiling", + "cultmath-tapes:ruling:operator-analytic-wall-body", + "cultmath-tapes:ruling:operator-tile-amortized-march", + "cultmath-tapes:ruling:operator-measure-with-empty-space", + "cultmath-tapes:ruling:operator-footprint-lod-march", + "cultmath-tapes:ruling:operator-ground-void-brush", + "cultmath-tapes:ruling:operator-void-true-cavity", + "cultmath-tapes:ruling:operator-compiler-downloads", + "cultmath-tapes:ruling:operator-campaign-home" + ], + "questions": [ + "Q9 in the map: the one shipped void. (a, recommended) Rh 198, S 50, derived Rc 248, e 0.40, the scene r2 measured; (b) the site section's Rc 640, e 4; (c) Rh 198, S 100. Hands codes (a)." + ] +} diff --git a/docs/cultmath-interval-ground-cut-site-ground.r3.spec.json b/docs/cultmath-interval-ground-cut-site-ground.r3.spec.json index 52e1b38a..83558f7b 100644 --- a/docs/cultmath-interval-ground-cut-site-ground.r3.spec.json +++ b/docs/cultmath-interval-ground-cut-site-ground.r3.spec.json @@ -9,19 +9,20 @@ "depends_on": [ "site-masthead:cut_spec:wash-home-only.r1", "cultmath-tapes:cut_spec:glsl-lowering.r2", - "cultmath-tapes:cut_spec:interval-ops.r2" + "cultmath-tapes:cut_spec:interval-ops.r3" ], "first": [ "Map: F:/Projects/CultLib/docs/cultmath-interval-ground-cut.md, 'Pass 3' (the void scene, the composed bound, the pre-pass, the budget), then 'The shader design' for what pass 3 does not restate (sampled wash, palette input, pausing, contrast). Rulings in force: cultmath-tapes:ruling:operator-ground-void-brush (the scene), operator-tile-amortized-march (the march), site-masthead:ruling:operator-chromeless-column; wash-home-only's authority map. Re-pin base to main after wash-home-only lands. Stardust and DoF stay the next cut; leave the named composite slot. r2's token, readback, controller, vendoring, parity and fallback items stand unless this revision names them.", "The original, read once (Aetheria at e6181c11, 2021-03-13, Assets/Scenes/Main Menu.unity; Prefabs/RPG/Planets/Sun.prefab; Materials/Brushes/Gravity Well.mat, Main Menu Sun Fog Tint.mat, Main Menu Ambient Fog Tint.mat, Sector Boundary Fog.mat; Materials/Fog Marching Main Menu.mat; Shaders/Raymarching/VolumeRender.shader at that commit; Shaders/Brushes/PowerBrush.shader). What survives, in the original's units: the brush is PowerBrush (1 - x^2)^16 with depth 40 over radius 256 (half depth at r = 52, pseudo-Gaussian sigma about 45), the sun's own gravity-well child; a wide boundary bowl (depth 75, power 0.25, radius 1500) is flat across the scene and acts as a constant 75; floor offset -16.46, floor blend 39.6, so the fog top is at y = -91 outside the void and dips to -131 at the sun; the sun sphere (radius 12.5) sits at y = -130, the bottom of the void.", "What survives (continued): the sun is a child of 'Center', which rotates 5 degrees per second about y (72 s period), at orbit radius 175; the camera is a Cinemachine vcam following the sun (framing transposer, distance 150, damping 10, handheld noise amplitude 1 at frequency 0.25) and looking at 'Look Target' (0, -130, 0), the orbit centre at the sun's depth; the saved camera sits at y = -27, 64 above the fog top. The lighting was authored, not marched: the sun's 'Fog Tint' brush (1 - x^2)^3, depth 2, radius 128, colour (1, 0.4, 0.1) orange, painted into the tint map, times a scene-wide ambient tint (0.18, 0.59, 1) blue, depth 0.01. Stardust: 256^2 particles, spacing 2, floor -60, ceiling 0, height exponent 800 / (ceiling + wellDepth) so particles climb higher where the well is deeper (the void fills with stardust). Noise: strength 150, frequency 0.01, safety 50. Not surviving: the 2021 d() details, the tint map's runtime contents, the video.", - "The field (ruling operator-void-true-cavity: a spherical hollow inside a cloud volume that surrounds it on every side, not the 2021 bowl). Units are the original's. Outer volume: an unbounded uniform fill, chosen over a shell because every ray then ends in a wall and the march terminates by opacity, never by a far plane; Z1 = 2 Rc bounds the pre-pass range only. The carve is the pseudo-Gaussian brush g(d) = (1 - (d / Rc)^2)^e, d = |p - c_v|, density = K max(0, 1 - CARVE g(d')), authored by two knobs: the hollow radius Rh (density exactly 0 inside; Rh = Rc sqrt(1 - CARVE^(-1/e))) and the ramp width S (density full at Rh + S; set by the exponent: e = 4 gives S = 1.3 Rh, e = 0.25 gives S = 0.12 Rh). Shipped: Rh = 198, S = 50 (so e about 0.6, Rc about 220), CARVE = 1.5, K = 1/30: a wall is opaque within about 150 units of entry. Noise displaces the wall only: fade(d) = smoothstep(Rh - S, Rh, d), d' = d + A fade(d) n(warp(p)), A = 20, F0 = 0.01.", + "The field (ruling operator-void-true-cavity: a spherical hollow inside a cloud volume that surrounds it on every side, not the 2021 bowl). Units are the original's. Outer volume: an unbounded uniform fill, chosen over a shell because every ray then ends in a wall and the march terminates by opacity, never by a far plane; Z1 = |camera - c_v| + Rc + 6/K (614 at the shipped point) bounds the pre-pass range and is past opacity for every ray, so the analytic body step below always terminates. The carve is the pseudo-Gaussian brush g(d) = (1 - (d / Rc)^2)^e, d = |p - c_v|, density = K max(0, 1 - CARVE g(d')), authored by two knobs: the hollow radius Rh (density exactly 0 inside; Rh = Rc sqrt(1 - CARVE^(-1/e))) and the ramp width S (density full at Rh + S; set by the exponent: e = 4 gives S = 1.3 Rh, e = 0.25 gives S = 0.12 Rh).", + "Shipped (Q9 in the map, option (a) pending the operator): Rh = 198, S = 50, so Rc = Rh + S = 248 and e = -ln CARVE / ln(1 - (Rh / Rc)^2) = 0.40; CARVE = 1.5, K = 1/30: a wall is opaque within about 150 units of full density. The ramp and the noise fade band are both S wide; nothing void-shaped is authored twice (interval-ops r3, finding shipped-void-two-scenes). Noise displaces the wall only: fade(d) = smoothstep(Rh - S, Rh, d), d' = d + A fade(d) n(warp(p)), A = 20, F0 = 0.01.", "Scene motion. The sun orbits the origin at radius 175 in xz with period 72 s (5 degrees per second, the original's Center pivot). The cavity centre is c_v = sun + (0, Rh - 12.5, 0): the sun sphere (radius 12.5) rests on the hollow's bottom and the hollow moves with it. The camera follows the sun 120 behind it along the orbit's tangent and 45 above it, so it sits 186 from c_v, 12 inside the hollow (the handheld drift is 2 units, two low-frequency snoise on time), low in the cavity, looking at the sun's position plus (0, 60, 0) so the lit lower wall and the dark upper wall share the frame. The 2021 follow distance 150 and height 25 do not fit a hollow a camera can stand in; 120 and 45 are the re-derivation, and the report says so. worldAt(t) in ground.js is the one owner of sun, cavity and camera, uploaded as uniforms; the shaders never integrate motion.", "The flow field, authored around the void: flow(xz) = SWIRL * tangent(xz - sun.xz) * (1 - (r / Rw)^2)^2 + GLOBAL * (snoise(xz Ff + o1), snoise(xz Ff + o2)) (the swirl is largest mid-slope and zero at the rim and the centre; the global term is r2's). The flow texture is a world-space window of side 4 Rw centred on the sun, quarter of the canvas resolution, re-rendered every frame; warp, hue, motion vectors and (next cut) the stardust read it, so D4 holds with a moving scene. D = FLOW_MAX * PERIOD / 2 as before, with FLOW_MAX the authored maximum of the swirl plus the global amplitude.", - "The march (tile pre-pass, interval-ops r2's design). Pre-pass at W/8 x H/8: per tile, the loop over the slab's cell ranges (uniform cells, StepDense = min(10, S / 4), 128 cells to Z1 = 2 Rc along +view depth): the envelope probe first (exact box: y and xz ranges by cultmath_iv_mul of the depth and slope intervals, d's interval is the box's least and greatest distance to c_v, exact; g, fade and the carve are monotone in it), then the noise ball (cultmath_iv_frustum_ball with D, cultmath_iv_snoise_ball) only when fade.hi > 0 and L r F0 leaves room to prove anything; writes the 96-bit mask of cells not proven empty to an RGBA32UI texel.", - "(March, continued.) Main pass: texelFetch the mask, then march the unmasked range with the footprint step Delta(z) and the octave weights (next item), the per-frame sample count capped by the budget S_budget and the start jittered so TAA completes what one frame does not; masked cells are skipped exactly; integrate transmittance and the depth-weighted mean distance, apply the light. Every tile's interior cells retire in a few envelope probes and its wall cells are marked; there are no sky tiles. Behind a fully transmissive pixel the resolve shows --gamecult-nebula-base; by construction only an unfinished ray (budget exhausted before the wall, measured, must be under 1%) reaches it.", + "The march (tile pre-pass, interval-ops r2's design). Pre-pass at W/8 x H/8: per tile, the loop over the slab's cell ranges (uniform cells, StepDense = min(10, S / 4) = 10, 62 cells to Z1 = 614 along +view depth; the mask has 96 bits): the envelope probe first (exact box: y and xz ranges by cultmath_iv_mul of the depth and slope intervals, d's interval is the box's least and greatest distance to c_v, exact; g, fade and the carve are monotone in it), then the noise ball (cultmath_iv_frustum_ball with D, cultmath_iv_snoise_ball) only when fade.hi > 0 and L r F0 leaves room to prove anything; writes the 96-bit mask of cells not proven empty to an RGBA32UI texel's first three words, and in the fourth word the body-start cell: the first cell whose own bound is a positive point and whose slice to Z1 is one too (the envelope with the free noise interval proves density exactly K for the rest of the range), 0xFFFFFFFF when none (interval-ops r3, ruling operator-analytic-wall-body).", + "(March, continued.) Main pass: texelFetch the mask, then march the unmasked range with the footprint step Delta(z) and the octave weights (next item), the per-frame sample count capped by the budget S_budget and the start jittered so TAA completes what one frame does not; masked cells are skipped exactly; at the body-start cell take one analytic Beer-Lambert step, T *= exp(-K (Z1 - z) |dir|) with K the field's pointwise envelope value there (no probe in the main pass), and stop; integrate transmittance and the depth-weighted mean distance, apply the light. Every tile's interior cells retire in a few envelope probes and its wall cells are marked; there are no sky tiles. Behind a fully transmissive pixel the resolve shows --gamecult-nebula-base; by construction only an unfinished ray (budget exhausted before the wall, measured, must be under 1%) reaches it.", "Footprint LOD (the operator's challenge, map 'Footprint LOD'): theta_px = 2 tan(fov/2) / H; the footprint at distance z is z theta_px. Step: Delta(z) = max(k z theta_px, min(S / 4, lambda_fine(z) / 2)), k = 4: never finer than four footprints, otherwise the finer of the two content limits (four samples across the ramp, two per wavelength of the finest live octave). Octaves: octave i of wavelength lambda_i carries weight w_i(z) = 1 - smoothstep(lambda_i / (2m), lambda_i / m, z theta_px), m = 4: one smooth blend per octave, no popping; TAA hides per-frame sparsity. The weights are part of the field, so the bound encloses the truncated field exactly: the noise interval over a slice is the sum over octaves of iv_mul([w_i(z1), w_i(z0)], iv_snoise_ball(c F_i, r F_i)), w_i monotone in z. At the shipped parameters (wavelengths 100 and 25, z <= 2 Rc) every weight is 1 in the frustum and the content limits set the step; the footprint bites when S < 4 k z theta_px.", - "Where the scene sits and what to turn (Imagination's model, scratchpad cavity_model.py; the test and the browser confirm): at the shipped point the LOD march needs about 6 steps and 12 snoise per pixel for a converged frame, against about 35 steps for a fixed-step march; the step is set by S / 4 and the detail octave's half-wavelength, not by distance, because footprints are under 4 units within the frustum. What makes it cheaper without losing the mood, in order: (1) a lower per-frame S_budget, which LOD and the mask make nearly lossless (S_budget = 4 converges in about three frames at the walls); (2) a thinner ramp S (S = 10 is 3.2x on cost but reads as a surface; 25-50 keeps the fog); (3) a longer detail wavelength or no detail octave near the far wall (the 12.5-unit half-wavelength is the floor for S >= 50); (4) a larger hollow (Rh = 1000: 2.5x) costs nothing per pixel and changes the composition. Camera offset barely matters at these scales.", + "Where the scene sits and what to turn (Imagination's model, scratchpad cavity_model.py; the test and the browser confirm): interval-ops r2 measured the shipped point at 16.45 LOD steps and 26.6 snoise per pixel against 36.3 fixed steps (2.21x); r3's analytic body is predicted to bring the steps to about 12.5 (2.6-3.3x), snoise unchanged; the wall's step is set by S / 4 and the detail half-wavelength, not by distance; the browser confirms or refutes. Cheaper without losing the mood, in order: (1) a lower per-frame S_budget, which LOD and the mask make nearly lossless (S_budget = 4 converges in about three frames at the walls); (2) a thinner ramp S (S = 10 is 3.2x on cost but reads as a surface; 25-50 keeps the fog); (3) a longer detail wavelength or no detail octave near the far wall (the 12.5-unit half-wavelength is the floor for S >= 50); (4) a larger hollow (Rh = 1000: 2.5x) costs nothing per pixel and changes the composition. Camera offset barely matters at these scales.", "Lighting, the cheapest version that keeps it moody (the original's own mechanism, made 3D): at each sample, light = SUN_COLOUR * (1 - (d / Rl)^2)^3 for d = |p - sun| < Rl, Rl = Rh = 198 (the original's 128 scaled with the hollow), plus AMBIENT * ambientTint; colour = density * (light + ambient) * hue(flow). No light march: zero extra snoise, one length and one pow per sample. The lower wall within Rl of the sun glows and the upper wall stays dark because the pulse is zero beyond Rl, which is the moody split; the sun inside the hollow is occluded by nothing, so the sun itself is a small additive disc drawn in the resolve at its projected position, occluded by the accumulated transmittance along its ray. Not in this cut, measured before it is ever added: one envelope-only transmittance sample toward the sun per primary sample (self-shadowing by the bowl's geometry), about +25% march cost; it is desktop-only if ever.", "TAA with a moving camera. ground.js uploads this frame's and the previous frame's view-projection. For each pixel the resolve reconstructs the world point at the march's depth-weighted mean distance (written by the main pass into a second history channel, R16F), moves it back by the field motion (p + flow(p) * dt: the pattern moves at -flow), projects it with the previous VP and fetches history at that uv. The same 3x3 min/max clamp, alpha = max(1/frames, 1/32), out-of-bounds alpha 1; history reset on resize, first frame and budget step. Transmittance, colour and the depth channel are all reprojected by the one motion vector. ?ground=debug-mv shows |mv| with the camera moving and the flow frozen, and with the flow moving and the camera frozen, so both terms are seen separately.", "Stardust distribution and the sun disc are the next cut (site-stardust), authored to the void: particles spawned in the bowl's volume with the original's height rule (more headroom where the well is deeper), lit by the same pulse, moving with the swirl. This cut leaves the composite slot and the depth history they need.", @@ -49,8 +50,8 @@ "adds": [ "site/quartz/components/GameCultGround.tsx: as r2.", "site/quartz/static/ground/ground.js (~500 lines): r2's passes plus worldAt(t) (sun, void, camera), the two view-projections, the pre-pass, the mask texture, the depth history, the sun-disc composite and the light-0-at writer.", - "site/quartz/static/ground/flow.frag.glsl (world-space window, swirl plus global), prepass.frag.glsl (the tile loop, mask out), cloud.frag.glsl (masked sampling, light, depth out), taa.frag.glsl (camera plus flow reprojection), downsample.frag.glsl.", - "site/quartz/static/ground/CultMath.glsl and glsl-parity.json, vendored from CultLib at interval-ops r2's head or later (cultmath_iv_frustum_ball present).", + "site/quartz/static/ground/flow.frag.glsl (world-space window, swirl plus global), prepass.frag.glsl (the tile loop, mask and body-start out), cloud.frag.glsl (masked sampling, light, depth out), taa.frag.glsl (camera plus flow reprojection), downsample.frag.glsl.", + "site/quartz/static/ground/CultMath.glsl and glsl-parity.json, vendored from CultLib at interval-ops r3's head or later (cultmath_iv_frustum_ball with its float32 widening present).", "scripts/ground/check-cultmath-glsl.ps1: the vendoring drift check.", "custom.scss and docs/brand-design-language.md: as r2, with the void composition named in the brand doc's ground paragraph." ], @@ -233,11 +234,13 @@ "cultmath-tapes:ruling:operator-ground-void-brush", "cultmath-tapes:ruling:operator-void-true-cavity", "cultmath-tapes:ruling:operator-tile-amortized-march", + "cultmath-tapes:ruling:operator-analytic-wall-body", "cultmath-tapes:ruling:operator-split-phacelle", "site-masthead:ruling:operator-chromeless-column" ], "questions": [ "Q4 in the map: palette input tokens (a, recommended)", - "Q5 in the map: carry the last sampled wash across page loads (a, recommended)" + "Q5 in the map: carry the last sampled wash across page loads (a, recommended)", + "Q9 in the map: the one shipped void (a, recommended: Rh 198, S 50, Rc 248, e 0.40); ruled in interval-ops r3's fork, this spec follows it" ] } diff --git a/docs/cultmath-interval-ground-cut.md b/docs/cultmath-interval-ground-cut.md index 1cdfb4c0..c68dfb01 100644 --- a/docs/cultmath-interval-ground-cut.md +++ b/docs/cultmath-interval-ground-cut.md @@ -1,12 +1,13 @@ # CultMath intervals and the gamecult.org ground: cut map -Status: Imagination pass 3, 2026-10-02. Pass 1's Q1–Q3 are answered (below); -the Self opened the `cultmath-tapes` campaign from pass 2; pass 3 (section -"Pass 3" near the end) 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. Where pass 3 and an earlier section disagree, pass 3 -wins. Where this map and the Body disagree, the Body wins and this map is -stale. +Status: Imagination pass 4, 2026-10-03. Pass 1's Q1–Q3 are answered (below); +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 +an earlier section disagree, the later pass wins. Where this map and the Body +disagree, the Body wins and this map is stale. Pinned HEADs (every `file:line` below is against these): @@ -409,7 +410,9 @@ Specs: `docs/cultmath-interval-ground-cut-glsl-lowering.spec.json`, `docs/cultmath-interval-ground-cut-site-stardust.spec.json`. Revision 2 of the two CultLib cuts (pass 3): `docs/cultmath-interval-ground-cut-interval-ops.r2.spec.json`, -`docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json`. +`docs/cultmath-interval-ground-cut-glsl-lowering.r2.spec.json`. Revision 3 of +`interval-ops` (pass 4): `docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json`; +`glsl-lowering` r2 and `site-ground` r3 now depend on it. ## Standing design decisions (means; Self may overrule) @@ -559,7 +562,9 @@ The cavity is parametrised by the hollow radius `Rh` and the ramp width `S`; the brush `(1 − (d/Rc)²)^e` with `CARVE` gives `Rh = Rc √(1 − CARVE^(−1/e))` and `S` from the exponent (`e = 4` gives `S ≈ 1.3 Rh`, `e = 0.25` gives `S ≈ 0.12 Rh`), so `(Rh, S)` are the knobs and `(Rc, e)` derived. Shipped: -`Rh = 198`, `S = 50` (`e ≈ 0.6`, `Rc ≈ 220`), `K = 1/30`. +`Rh = 198`, `S = 50`, so `Rc = Rh + S = 248` and `e = −ln CARVE / ln(1 − +(Rh/Rc)²) = 0.40`, `K = 1/30` (pass 4 corrects this line's `e ≈ 0.6, Rc ≈ +220`, which did not satisfy the formula; Q9 holds the look question). Imagination's model (scratchpad `cavity_model.py`; the test confirms or refutes), 1080p, `N = 8`, per pixel for a converged frame; "ref" is the @@ -632,7 +637,9 @@ ruled (Q7, `operator-void-true-cavity`) that the site's void is a true 3D hollow inside a cloud volume that surrounds it on every side, including above. Everything else recovered carries over. -**The site scene** (`site-ground` r3): the original's units. Outer volume: an +**The site scene** (`site-ground` r3; the brush parameters in this paragraph, +`Rc = 640`, `e = 4`, are superseded by pass 4's one shipped void, `Rh = 198`, +`S = 50`, `Rc = 248`, `e = 0.40`, pending Q9): the original's units. Outer volume: an unbounded uniform fill (chosen over a shell: every ray then ends in a wall and the march terminates by opacity, never by a far plane; `Z1 = 2 Rc` bounds the pre-pass range only). The carve: `d = |p − c_v|`, `g(d) = (1 − @@ -714,6 +721,9 @@ envelope gate (the proving-ground contract's second half). ### The split Phacelle and the compile checks (glsl-lowering r2) +Pass 4: the glsl branch rebases onto interval-ops **r3**'s head, because r3 +changes `cultmath_iv_frustum_ball`'s radius and the fixture family with it. + - Transformation step 1 gains one licence-keyed entry: an include under its own licence is lowered into its own file. `CultMath.glsl` (MIT: `CultMath.hlsl` + `CultMath.Interval.hlsl`) and `CultMath.Phacelle.glsl` (MPL-2.0, @@ -733,8 +743,221 @@ Carried, still out of scope: the Unity `CultMath.dll` rebuild (no release in the sequence); the fixture's ulp bounds on WebGL2 (site cut); Stryker's timeouts are reported as timeouts, not kills. +## Pass 4: Soul's five findings, the analytic body, the one shipped void + +Pinned: CultLib `main` `780d8cfa` (docs only since `d0ea37f8`); +`hands/cultmath-interval-ops` at `43183276` (report `cut-interval-ops.h2`, +verdict `cut-interval-ops.s1`, worktree `F:\Projects\CultLib-iv`). Rulings new +in this pass: `operator-wells-contract-vs-ceiling` ((c) is held to at least 90% +of its probe-free ceiling, not 2x: the ceiling is what intervals control; the +fog band's cost belongs to the field) and `operator-analytic-wall-body` (where +the envelope interval proves the density constant, one closed-form +Beer-Lambert step instead of sampling at the content limits). Spec: +`docs/cultmath-interval-ground-cut-interval-ops.r3.spec.json`. + +### What r2 measured + +| Scenario | r2 result | Pass 3 prediction | +| --- | --- | --- | +| (a) height fog | 16384x | > 50x | +| (b) inside the fog | 0.99x | 0.8–1.0x | +| (c) Aetheria wells | 1.41x at warp D (3.32x at warp 0); probe-free ceiling 1.42x (3.46x) | 2–4x | +| (d) uniform slab | 1.23x | 1.3–1.5x | +| (e) void, shipped | steps per pixel 36.34 → 16.45 (2.21x fewer), cost 1.34x, snoise 26.6 | 35 → 6 (5–8x), cost 1.7x | + +Two things the pass 3 model got wrong, both now owned by r3: the footprint +step kept its content limits (`S/4`, the detail half-wavelength 12.5) inside +the wall's constant-density body, which the model had costed at two steps; and +the noise band does not end at `Rc`: with `A = 20` and the free noise interval +`[−1.5, 1.5]`, the density is provably `K` only from `d ≥ Rc + 30`. The ruling +answers the first; the second is content and stays. + +### Soul's findings and what r3 does with each + +| Finding | Severity | r3 | +| --- | --- | --- | +| `frustum-ball-float-crossing` | Medium | the radius is widened by a derived float32 bound (below); seven extreme families join the enclosure test; the strictness sentence leaves the C#, the HLSL and `design.md` | +| `aetheria-far-plane-miscited` | Low | `far = 2048`, the Main Camera's (`ARPG.unity:31777`, `:31830`); the Tint camera's 1000 was the wrong camera | +| `shipped-void-two-scenes` | Medium | one shipped void, `(Rh, S) = (198, 50)`, `(Rc, e) = (248, 0.40)` derived; the map, the site-ground spec and the test agree; the look question is Q9 | +| `lod-weights-never-exercised` | Low | configuration "LOD far" (`Rh = 4000`, `S = 50`, low camera, `f = 467`) reaches weights in `(0, 1)` and at 0; the `[w(z1), 1]` bound is recorded as a deviation with its soundness argument | +| `stryker-timeout-survivor` | Low | 2525 named equivalent; ball radii to `1e3` so `L/r` dies to an enclosure test; every timeout hand-run; the report claims only demonstrated kills | + +### The float widening rule (finding 1) + +`iv_frustum_ball` returns a float32 ball; its promise is that every exact +point `(m z, z)` of every ray of the tile, `m` in the footprint, `z ∈ [z0, +z1]`, moved by at most the warp, lies inside it. Soul showed the r2 ball misses +such points by up to `5.7e-4 r` when `z0 == z1`, `3.3e-4 r` at `z ≈ 1e7` and +19% of `r` at `|m_c| ≈ 1000`: the triangle inequality is an equality on the +degenerate slice and the centre's own rounding crosses it. The rule, in C# and +both HLSL copies, after the r2 radius: + +```text +radius += (|c.x| + |c.y| + |c.z| + radius) · 2^-20 (9.5367431640625e-7, exact) +``` + +Derivation with `u = 2^-24`: the centre `c = (m_c z_m, z_m)` carries at most +two roundings per component (`z_m = fl(z0 + z1) · 0.5`, then the product), so +`|c_float − c_exact| ≤ 2u |c|₁`; the radius carries at most seven first-order +roundings (`z1 − z0`; the axis `sqrt(mx² + my² + 1)` at `3u`; the product; `z1 +· footprint`; the sum; `+ warp`), so `r_exact ≤ r_float (1 + 7u)`; the +widening's own addition rounds once more. `2^-20 = 16u` covers `8u (|c|₁ + r)` +twice over. fma contraction removes roundings and only tightens, so dxc's HLSL +and any driver's GLSL are covered by the same bound. Cost at the shipped +scales: `|c|₁ ≈ 600`, `r ≈ 5` → `6e-4` units, 0.01% of the radius. The +enclosure test computes distances in double from the float ball and the float +ray, no tolerance, over the r2 domain and the extreme families (`z0 == z1`; +`z1 − z0 = 1e-6 z1`; `|m_c| ≤ 1000`; `z ≤ 1e7`; `z0 = 0, z1 ≥ 1e-3`; `f ≥ 1`; +warp to `1e6`), corners at both depth ends with the flow pointed outward. The +derivation pin becomes two-sided (`r_exact ≤ ball.w ≤ r_exact + 2^-19 (r_exact ++ |c|₁)`), so a ball that forgets the widening fails and one that grows past +the rule fails. The caller's world transform and its own point rounding remain +the caller's; the function says so in its doc instead of a false proof. + +### The one shipped void (finding 3) and Q9 + +The map held two voids: the Footprint-LOD section's `(Rh, S) = (198, 50)` +(with an `e ≈ 0.6, Rc ≈ 220` that did not satisfy its own formula) and the site +section's `Rc = 640, e = 4` (a 442-wide ramp with a 50-wide fade band). Hands +implemented the first with the arithmetic corrected (`Rc = Rh + S = 248`, `e = +0.40`) and measured 2.21x; Soul measured the second at 1.93x. Under the +analytic body the second gains nothing: across a 442-wide ramp the optical +depth exceeds 4 before the density is provably constant, so rays end inside the +ramp and the body is never proven. The map's own rule says `(Rh, S)` are the +knobs, so the honest choice is the first; it changes the look relative to the +site section, hence Q9 (below). For `CARVE = 1.5` the ramp width is tied to the +exponent (`S/Rh = 1/√(1 − 1.5^(−1/e)) − 1`: `e = 0.4 → 0.25`, `e = 0.7 → +0.5`, `e = 1 → 0.73`, `e = 4 → 2.2`), so "pseudo-Gaussian" and "narrow" cannot +both be had without a third knob, and a narrow wall at `e = 4` needs +`CARVE ≈ 58`, which is a hard wall again. The ramp width is the look-cost knob. + +### The analytic body (ruling `operator-analytic-wall-body`) + +Consumer-side, no new CultMath function; the saving test and the site mirror +each other. + +- **When it applies.** The consumer's `Bound` over the whole remaining range + `[z, z_end]`, gated, is a point interval with positive value: for the void, + the envelope with the free noise interval `[−1.5, 1.5]` proves `fade = 1` + and `d_lo + A · (fade n)_lo ≥ Rc`, so every point of every ray of the tile + over the rest of the grid has density exactly `K` for any noise value. For + (a)–(d) no bound is a positive point (the fog's density varies with `s'`), + so the step never applies there, and the test asserts it. +- **Pre-pass.** `MarchTile` returns `(dense cells, bodyStart, bodyDensity)`: + when a single cell is not provably empty and its own bound is a positive + point, one more probe over `[z_j, z_end]`; if that is a positive point too, + `bodyStart = j` and every remaining cell is appended dense without probing + (always sound; it also drops the body's probes). The site writes the cell in + the mask texel's fourth word, `0xFFFFFFFF` when none. +- **Main pass.** Unmasked cells before `bodyStart` as before; at `bodyStart`, + `T *= exp(−K · (z_end − z) · |dir|)`, one step, stop. The grid end for every + void configuration becomes `|camera − c_v| + Rc + 6/K` (was `5/K`): the + body starts at `d ≥ Rc + 30` plus box slack, so `6/K` leaves at least 4.9 of + optical depth and `T ≤ 0.0075`. No probe in the main pass: the constant is + the field's pointwise envelope value at that cell. +- **Pin.** `AnalyticBodyMatchesFineMarch`: wherever a body is reported, the + bound over the rest of the grid is a positive point and the full and the + truncated density at 64 sampled points of the body equal it exactly; the + analytic factor equals the fixed-step product over the same cells within + `1e-4` relative; no (a)–(d) tile reports a body. The masked fixed-step march + stays the mask's soundness pin (exact agreement with the reference); only + the LOD march takes the step. Hand mutation that must fail: in + `VoidField.Compose`, `CarveRadius → HollowRadius` in the lo test. + +### Footprint LOD, exercised (finding 4) + +At `f = 935` every octave weight is 1 inside every r2 grid (the fine weight +drops below 1 past `z = 2922`; the largest grid ends at 2240). `VoidField` +takes a focal length, and one configuration reaches the weights: `Rh = 4000`, +`S = 50`, low camera, `f = 467` (a 98° vertical FOV at 1080 rows): fine weight +below 1 past 1460 and 0 past 2920, coarse below 1 past 5840, grid to ≈ 7990; +the footprint term `4 z θ` also exceeds 12.5 past 1460, so `LodStep` is +footprint-set there. It joins the enclosure test's field list and is printed by +the saving test, not asserted. Deviation recorded: the bound takes each weight +over `[w(z1), 1]`, not `[w(z1), w(z0)]`, because one `Bound` serves the full +field (`w = 1`) and the truncated one (`w ∈ [w(z1), w(z0)]`); `[w(z1), 1]` +contains both and `iv_mul` is monotone in its interval argument, so the +composed interval encloses both fields; the slack is only in far tiles whose +step the footprint already sets. Hand mutation that must fail the truncated +check there: `w(z0)` for `w(z1)`. + +### Contracts after r3 (asserted at `N = 8`) + +- (a): at least 2x combined cost. Measured 16384x. +- (c): at least 0.90 of its probe-free ceiling (`Ratio / Ceiling`, the lower + of warp 0 and D). Measured 0.99 at warp D, 0.96 at warp 0. +- (e): at least 2x fewer LOD steps per pixel than the fixed-step reference at + the one shipped void; the masked march exact; the LOD march within 0.02. +- (b), (d), the (e) grid and LOD-far: printed only. + +### Predictions (Imagination's rule, not a measurement) + +From r2's printed rows: the LOD steps that evaluated noise are about +`snoise / 2.8` plus two pre-band steps; the rest were constant-body steps, +which the analytic step makes one. Ref/LOD steps: + +| `Rh`, camera | `S = 10` r2 → r3 | `S = 50` r2 → r3 | `S = 150` r2 → r3 | +| --- | --- | --- | --- | +| 100, low | 2.1 → 5.3x | 1.8 → 2.5x | 1.6 → 1.6x | +| 200, low | 2.7 → 5.8x | 2.2 → 2.8x | 1.8 → 1.8x | +| 400, low | 3.4 → 6.2x | 2.9 → 3.5x | 2.2 → 2.2x | +| 1000, low | 6.8 → 10.9x | 4.5 → 5.2x | 3.3 → 3.3x | +| 1000, centred | 12.4 → 30x | | | +| **shipped** (198, low, 50) | | **2.21 → ≈ 2.9x (12.5 steps), cost 1.34 → 1.38x, snoise 26.6 unchanged** | | + +At `S = 150` rays end inside the ramp before the body is proven, so nothing +changes there. If the shipped point lands under 2.6x, the report says which +assumption failed: the pre-band's 0-snoise steps, or a body start later than +`Rc + 30` by box slack. + +### Authority map delta (against pass 3's) + +- `iv_frustum_ball` is now the owner of its own float32 rounding: the + widening is part of the function. Consumers add nothing for it and remain + owners of their world transform. +- The body proof is derived from the same `Bound` the mask is derived from, by + one more probe; the main pass reads it and never decides it. The Beer-Lambert + step is the consumer's integration, not a CultMath function. +- `(Rc, e)` are no longer authored; they are derived from `(Rh, S)`. The site + section's `Rc = 640, e = 4` is no longer an owner of the look (Q9 pending). +- (c)'s contract is derived from its own run (0.90 of the ceiling); no absolute + target is written for (c). +- Deletion line: the false strictness sentence, the Tint camera citation, the + (c) 2x assertion and the second shipped void leave before their replacements + are written. Net in `src`: one line and a doc comment. + +### Dependent specs + +`glsl-lowering` r2 now depends on `interval-ops` r3 and rebases onto its head +(the fixture family for `cultmath_iv_frustum_ball` changes with the widening). +`site-ground` r3 now depends on `interval-ops` r3; its field item carries the +one shipped void with `(Rc, e)` derived, its pre-pass writes the body-start +cell in the texel's fourth word, its main pass takes the one analytic step, +and `Z1 = |camera − c_v| + Rc + 6/K` (614 at the shipped point, 62 cells of +10; the mask has 96 bits). Both files are edited in place on `main`; the +mind's copies need re-admission by Self. + ## Questions +**Q9. The one shipped void** (pass 4; finding `shipped-void-two-scenes`). +Authored as `(Rh, S)` with `(Rc, e)` derived; `CARVE = 1.5`, `A = 20`, +`F0 = 0.01`, `K = 1/30`, the camera as site-ground r3. +- (a) `Rh = 198`, `S = 50` → `Rc = 248`, `e = 0.40`. The scene r2 measured + (2.21x; predicted ≈ 2.9x with the analytic body). A 50-wide noisy wall whose + outer edge has a kink (`e < 1`), softened by the ±20 noise displacement and + the TAA. **Recommended**: the map's own rule, the measured scene and the + ruling's analytic body all point at it. +- (b) `Rc = 640`, `e = 4` (`Rh = 198`, a 442-wide pseudo-Gaussian ramp, fade + band 50): the site section's soft wall. Measured 1.93x; the body is never + proven, so the analytic step never fires; the (e) contract fails unless it + is dropped to printed or the detail octave is removed where the ramp is + wide, both of which are the operator's to decide. +- (c) `Rh = 198`, `S = 100` → `Rc = 298`, `e = 0.70`: the compromise, a + softer 100-wide wall; predicted ≈ 2.4x; not in the r2 grid, so unmeasured. + +Hands codes (a); another ruling moves only the shipped constants here, in the +r3 spec and in the site-ground spec. + **Q6. The site ground's envelope look.** Answered outside the options by `cultmath-tapes:ruling:operator-ground-void-brush`: the old main menu's void (pass 3, "The site ground: the void").