CultLib/packages/cultmath/README.md
Meta Vens 68b74eaee3 cultmath README: Shader Tooling names the strict-form gate and the overridable site capacity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:58:59 +02:00

222 lines
14 KiB
Markdown

# CultMath
CultMath is the project name. Its clean .NET package identity is
`GameCult.Math`, owned and released from the CultLib monorepo.
Clean-room math shaped for people who think in HLSL.
CultMath provides lowercase vector types, shader-style intrinsics, and native
hot-path kernels so math-heavy renderer code can move between C#, Rust, and HLSL
without turning every line into a translation exercise. It is not a
Unity.Mathematics clone. It is a small, Aquarium-born library built from public
shader semantics.
## Current Surface
- `float2`, `float3`, `float4`
- component-wise arithmetic
- scalar-to-vector splats
- every HLSL swizzle (`xyzw` and `rgba`, repeats readable, non-repeating
masks writable) and every HLSL mixed constructor, generated by
`tools/generate-swizzles.ps1`
- conversions to and from `System.Numerics`
- `Random`, a compact deterministic XORShift stream with scalar and vector draws
- `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using
`System.Numerics.Vector<float>` when available, including radial falloff
acceleration and semi-implicit Euler integration
- `math` intrinsics: `radians`, `degrees`, `abs`, `floor`, `ceil`, `frac`,
`min`, `max`, `clamp`, `saturate`, `lerp`, `step`, `smoothstep`, `dot`,
`cross`, `length`, `distance`, `normalize`, `reflect`, `csum`, `erf`,
`erfinv` (scalar only), exponential
`decay`/`damp`, intercept/segment helpers (`distance_to_segment` in 2D and 3D, with and without `out closest`, and `segment_closest`), Catmull-Rom, Bezier curves, and
deterministic value-noise primitives
- Site cells: `power_cells` and `multiplicative_cells` return the two nearest sites of a point as `CultSiteCells`,
`soft_gabriel_gate` gates a pair of sites by the empty-disc margin. Each is a loop over the shader steps
`cultmath_power_score` / `cultmath_multiplicative_score`, `cultmath_site_cells_empty`, `cultmath_site_cells_insert`,
`cultmath_site_cells_border`, `cultmath_power_border_step` (the exact power cell border, `power_border`) and `cultmath_gabriel_margin`; a shader loops over its own buffer.
- Weighted lattice cells: `weighted_lattice_cells` returns `float4(F1, F2, smooth, id)` over a jittered,
drifting lattice whose cell weights come from the exact power border of the body span at each cell centre, with a stretched
metric and an exact ring search over the stated range (`LatticeMaxBoost`, `LatticeMaxStretch`, `LatticeMaxSmoothing`). It composes the
public steps `lattice_site`, `lattice_weight`, `lattice_distance` and `lattice_smooth_step`. The shaders carry the four steps as
`cultmath_lattice_site`, `cultmath_lattice_weight`, `cultmath_lattice_distance` and `cultmath_lattice_smooth_step`, and the whole function as `cultmath_weighted_lattice_cells(p, time, bodies,
count, ...)` over a `float4 bodies[CULTMATH_MAX_SITES]` array (128 by default; define the macro before including to change it);
`cultmath_power_border(p, sites, count)` is the matching border. The argument checks become the shader caller's preconditions.
- `shaders/CultMath.hlsl`, a canonical HLSL mirror include for shader-side
parity. HLSL already owns `float2`, `float3`, and `float4`; the include
exposes `cultmath_*` functions for shared semantics HLSL does not provide.
It includes `shaders/CultMath.Phacelle.hlsl` (MPL-2.0),
`shaders/CultMath.Cycles.hlsl` (Apache-2.0) and
`shaders/CultMath.Interval.hlsl` and `shaders/CultMath.Affine.hlsl` from the same
directory, so ship all five.
- Cycles node semantics (Apache-2.0): `cycles_*` functions that compute what
Blender's Cycles renderer computes for the Math node operations whose results
differ from a plain intrinsic (`cycles_divide`, `cycles_power`, `cycles_modulo`,
`cycles_wrap`, `cycles_pingpong`, `cycles_smooth_min`, ...), Map Range
(`cycles_map_range_linear`, `_stepped`, `_smoothstep`, `_smootherstep`), Clamp
(`cycles_clamp_minmax`, `cycles_clamp_range`), Mix (`cycles_mix_float`,
`cycles_mix_vector` and every colour blend mode as `cycles_mix_<mode>`), RGB to BW,
and `cycles_ramp_index`, where a Color Ramp reads its table. `cultmath_cycles_*` in
HLSL and GLSL. Operands are finite floats, and the tests hold every function to
the results of Blender 5.2.2's own CPU Cycles (`tools/cycles-oracle`).
- interval arithmetic for culling empty space: `iv_*` functions
over `float2(lo, hi)` (`iv_add`, `iv_mul`, `iv_exp`, `iv_smoothstep`, ...)
and `iv_snoise_ball`/`iv_fbm_ball`, which bound `snoise` and `fbm_grad` over a
ball, and `iv_frustum_ball`, the ball around a screen tile's rays over a depth
segment, so one probe serves the whole tile (its radius carries its own
float32 rounding bound). The arithmetic functions return an interval built to
contain the function's value over their operand intervals, so a raymarch
can skip a segment whose `hi` is below its cutoff; `cultmath_iv_*` in HLSL.
The noise functions rest on empirical constants with a margin, not a proof:
`iv_snoise_ball(float3)` on `SNOISE_LIPSCHITZ` (`iv_fbm_ball` through it),
`iv_snoise_ball(float2)` on `SNOISE2_LIPSCHITZ`, and `af_snoise` on
`SNOISE_HESSIAN` and `SNOISE_LIPSCHITZ` (`af_fbm` through it); the enclosure
tests sample them. `iv_frustum_ball` and `af_frustum_ball` read no noise
constant: the ball is geometry plus a rounding widening, and the noise call
it feeds carries the constant.
The bounds compose with a consumer's own analytic envelope (a height fog, a
carved sphere): its interval over the segment proves empty space in one probe,
and the noise ball is consulted only near the surface. See `docs/design.md`,
"Intervals".
- `shaders/CultMath.glsl` (MIT), the same `cultmath_*` library in GLSL ES 3.00
for WebGL2, generated from `CultMath.hlsl` (never edited by hand), and
`shaders/CultMath.Phacelle.glsl` (MPL-2.0), generated from
`CultMath.Phacelle.hlsl`, which holds `cultmath_phacelle` alone, and
`shaders/CultMath.Cycles.glsl` (Apache-2.0), generated from
`CultMath.Cycles.hlsl`, which holds the `cultmath_cycles_*` functions. GLSL has no
`#include`: a WebGL consumer concatenates `CultMath.glsl` after its own
`#version 300 es` line and `precision highp float; precision highp int;`
(the library declares no precision, and its hashes need 32-bit integers),
then `CultMath.Phacelle.glsl` only if it calls `cultmath_phacelle` and
`CultMath.Cycles.glsl` only if it calls a `cultmath_cycles_` function, then its
own shader source.
- Span primitives (`math.Span.cs`, `cultmath_*` in HLSL and GLSL): `snoise_hessian`,
the analytic Hessian of `snoise(float3)` on the same lattice, hash and tie order as
`snoise_grad` (a symmetric `float3x3`; GLSL `mat3`), and `integral_clamp_linear(a,
b, lo, hi, t0, t1)`, the closed-form integral over `[t0, t1]` of `clamp(a + b t,
lo, hi)`; and the octave stages `span_fbm(path, stage,
octaves, lacunarity, gain)` and `span_ridged` (shader twins `cultmath_span_fbm` and `cultmath_span_ridged`,
whose callers build the `SpanStage` themselves), over a `SpanPath` and a `SpanStage` (`SpanStage.Identity`
is the plain fbm in C#), which return a `SpanNoise` (`field` as `float4(gradient, value)`,
`curvature`, `fold`) for a stretched, offset, shifted octave sum over a span. Three measured bounds describe `snoise`: `SNOISE_LIPSCHITZ` (gradient
length, `NoiseBoundTests.MeasureLipschitz`), `SNOISE_HESSIAN` (Hessian spectral
norm, `MeasureHessian`) and `SNOISE_THIRD` (third-derivative operator norm,
`MeasureThird`); each is 1.10 times a seeded maximum refined by ascent, and a
fast test pins it. They are empirical with a margin, not proofs.
- `Voronoi.SampleTones`, a C# batch surface that calls the Rust
`cultmath-core` native kernel when `cultmath_core` is available and falls back
to the managed parity path otherwise.
## Native Core
The canonical hot-path body lives in `native/cultmath-core`. It exports a C ABI
for batch kernels such as `cultmath_apollonian_voronoi_tones`.
Runtime wrappers own syntax and integration. Rust owns optimized primitive
kernel behavior and parity fixtures.
## Example
```csharp
using CultMath;
float3 normal = math.normalize(new float3(0.25f, 1.0f, -0.1f));
float rim = math.saturate(1.0f - math.dot(normal, new float3(0.0f, 0.0f, -1.0f)));
float glow = math.smoothstep(0.2f, 1.0f, rim);
float grain = math.value_noise_bicubic(math.float2(12.0f, 3.5f));
```
```hlsl
#include "CultMath.hlsl"
float3 normal = normalize(float3(0.25, 1.0, -0.1));
float rim = cultmath_saturate(1.0 - dot(normal, float3(0.0, 0.0, -1.0)));
float glow = cultmath_smoothstep(0.2, 1.0, rim);
float grain = cultmath_value_noise_bicubic(float2(12.0, 3.5));
```
## Provenance
The small spline, damping, and pursuit helpers were promoted from Aetheria's
local `AetheriaMath` surface. The value-noise family follows public shader
literature from Inigo Quilez's noise and mini-spline articles, expressed in
CultMath names with tests instead of copied project-local helper drift.
## Build
```powershell
dotnet build packages/cultmath/src/CultMath/CultMath.csproj
dotnet test packages/cultmath/tests/CultMath.Tests/CultMath.Tests.csproj
cargo test --manifest-path packages/cultmath/native/cultmath-core/Cargo.toml
cargo build --release --manifest-path packages/cultmath/native/cultmath-core/Cargo.toml
.\packages\cultmath\scripts\build-unity-package.ps1
```
## Unity package
CultMath's Unity 2021.3 surface is the tracked precompiled package at
`packages/cultmath/unity/org.gamecult.cultmath`. Unity consumes `CultMath.dll` and the numeric
`CultMath.hlsl`; it does not compile the repository's current-language C# source
tree and does not require a consumer `csc.rsp` workaround.
```json
"org.gamecult.cultmath": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath"
```
The repository root is not a Unity package. Build the tracked package with
`packages/cultmath/scripts/build-unity-package.ps1`; the script fresh-builds the netstandard2.1
assembly and rejects stale binaries, missing metadata, unexpected assemblies,
or leaked C# source.
Before tagging `cultmath-unity-v*`, run the import and dispatch smoke on a Windows machine with a GPU,
alone: `packages/cultmath/scripts/unity-smoke/run-unity-smoke.ps1 -UnityExe <path to Unity.exe>`. It
builds a scratch project outside the repository that references the package by a `file:` path,
dispatches a compute shader that includes `CultMath.Interval.hlsl` and `CultMath.Affine.hlsl`, requires
a deliberately broken control shader to be rejected, and deletes the project afterwards.
## Shader Tooling
CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`:
```powershell
.\packages\cultmath\tools\get-dxc.ps1
.\packages\cultmath\tools\compile-hlsl.ps1
.\packages\cultmath\tools\get-spirv-cross.ps1
.\packages\cultmath\tools\compile-hlsl-spirv.ps1 `
-ShaderPath E:\Projects\Odin\crates\muninn-move-tracker\shaders\MoveSphereCandidate.comp.hlsl `
-OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.spv `
-IncludePath E:\Projects\CultLib\packages\cultmath\shaders
.\packages\cultmath\tools\compile-spirv-metal.ps1 `
-SpirvPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.spv `
-OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.metal
```
`tools/glsl/build-glsl.sh <outdir>` (Linux, repository root) builds the library GLSL from the HLSL with dxc, SPIRV-Cross and SPIRV-Tools and writes `core.glsl`, `cycles.glsl` and `phacelle.glsl` bodies into `<outdir>`. It does not yet own the committed GLSL; `GlslLowering` still does. `GLSL_DIR=<outdir> tools/device/run-lavapipe.sh` runs that output on lavapipe and compares it with the HLSL results bit for bit instead of judging the committed files. That comparison is in strict form (dxc `-Gis` and a `NoContraction` decoration on every float result of both modules), and a consumer overrides `CULTMATH_MAX_SITES` in the built GLSL the same way as in HLSL: define it before including `core.glsl`.
`compile-hlsl.ps1` compiles both `CultMath.hlsl` copies as a `lib_6_3` library with the x64 DXC; a warning fails it too (`-WX`).
DXC emits Vulkan SPIR-V with `-spirv`. `-TargetEnv vulkan1.2` is exposed by the
script, but the current Windows DXC release compiled this shader successfully
with DXC's default Vulkan target while rejecting the dotted target-env value.
The SPIR-V script applies default Vulkan register shifts for cbuffers, textures,
UAVs, and samplers so cross-compiled Metal buffers do not alias by resource
class. SPIRV-Cross emits Metal Shading Language from SPIR-V on Windows; building
an iPad-ready `.metallib` still belongs to Apple's Metal compiler on an Apple
toolchain.
The lavapipe conforming run executes every fixture family through the HLSL (dxc) and the committed GLSL
(glslang) on Mesa lavapipe and judges the results in `GlslMirrorTests.DeviceResultsHoldFixture`. It needs a Linux
container with apt: from the repo root, `tools/stopgap/ygg-verify.sh <repo> <rev> mcr.microsoft.com/dotnet/sdk:10.0
'bash packages/cultmath/tools/device/run-lavapipe.sh'`. `WRITE_BOUNDS=1` in front of the script re-records the
device's bounds file and prints it between `BOUNDS-BEGIN` and `BOUNDS-END`.
## Doctrine
The goal is mechanical sympathy between CPU-side authoring, native kernels, and
GPU-side shader math. If an intrinsic exists here, it should behave like the
shader concept unless a runtime's numeric rules make that impossible. Weird
deviations get tests and documentation, not folklore.
CultMath is still not a shader generator. Projects should include
`shaders/CultMath.hlsl` directly or add that directory to their shader include
path. If a shared operation needs CPU/GPU parity, put the primitive here and
test it here before copying local math helpers into a renderer or daemon.