2026-08-26 17:35:51 +02:00
# 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
2026-09-14 13:57:03 +02:00
- every HLSL swizzle (`xyzw` and `rgba` , repeats readable, non-repeating
masks writable) and every HLSL mixed constructor, generated by
`tools/generate-swizzles.ps1`
2026-08-26 17:35:51 +02:00
- 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` ,
2026-09-22 21:29:41 +02:00
`cross` , `length` , `distance` , `normalize` , `reflect` , `csum` , `erf` ,
`erfinv` (scalar only), exponential
2026-10-16 19:15:55 +02:00
`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
2026-08-26 17:35:51 +02:00
deterministic value-noise primitives
2026-10-16 18:27:09 +02:00
- 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` ,
2026-10-16 19:15:55 +02:00
`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.
2026-10-17 11:31:46 +02:00
- Weighted lattice cells: `weighted_lattice_cells` returns `float4(F1, F2, smooth, id)` over a jittered,
2026-10-17 07:29:51 +02:00
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
2026-10-17 11:31:46 +02:00
public steps `lattice_site` , `lattice_weight` , `lattice_distance` and `lattice_smooth_step` . The shaders carry the four steps as
2026-10-17 16:09:18 +02:00
`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.
2026-08-26 17:35:51 +02:00
- `shaders/CultMath.hlsl` , a canonical HLSL mirror include for shader-side
parity. HLSL already owns `float2` , `float3` , and `float4` ; the include
2026-09-14 18:04:04 +02:00
exposes `cultmath_*` functions for shared semantics HLSL does not provide.
2026-10-12 04:07:08 +02:00
It includes `shaders/CultMath.Phacelle.hlsl` (MPL-2.0),
`shaders/CultMath.Cycles.hlsl` (Apache-2.0) and
2026-10-04 09:47:49 +02:00
`shaders/CultMath.Interval.hlsl` and `shaders/CultMath.Affine.hlsl` from the same
2026-10-12 04:07:08 +02:00
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` ).
2026-10-09 23:37:24 +02:00
- interval arithmetic for culling empty space: `iv_*` functions
2026-10-02 19:56:30 +02:00
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
2026-10-03 00:08:35 +02:00
ball, and `iv_frustum_ball` , the ball around a screen tile's rays over a depth
2026-10-03 11:37:30 +02:00
segment, so one probe serves the whole tile (its radius carries its own
2026-10-09 23:37:24 +02:00
float32 rounding bound). The arithmetic functions return an interval built to
contain the function's value over their operand intervals, so a raymarch
2026-10-03 00:08:35 +02:00
can skip a segment whose `hi` is below its cutoff; `cultmath_iv_*` in HLSL.
2026-10-09 23:37:24 +02:00
The noise functions rest on empirical constants with a margin, not a proof:
2026-10-10 00:04:07 +02:00
`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.
2026-10-03 00:08:35 +02:00
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".
2026-10-03 13:40:16 +02:00
- `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
2026-10-12 04:07:08 +02:00
`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
2026-10-03 13:40:16 +02:00
`#include` : a WebGL consumer concatenates `CultMath.glsl` after its own
2026-10-02 20:09:45 +02:00
`#version 300 es` line and `precision highp float; precision highp int;`
(the library declares no precision, and its hashes need 32-bit integers),
2026-10-12 04:07:08 +02:00
then `CultMath.Phacelle.glsl` only if it calls `cultmath_phacelle` and
`CultMath.Cycles.glsl` only if it calls a `cultmath_cycles_` function, then its
2026-10-03 13:40:16 +02:00
own shader source.
2026-10-14 06:53:08 +02:00
- 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,
2026-10-16 03:02:13 +02:00
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)` ,
2026-10-14 20:48:43 +02:00
`curvature` , `fold` ) for a stretched, offset, shifted octave sum over a span. Three measured bounds describe `snoise` : `SNOISE_LIPSCHITZ` (gradient
2026-10-14 06:53:08 +02:00
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.
2026-08-26 17:35:51 +02:00
- `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"
2026-09-14 18:04:04 +02:00
float3 normal = normalize(float3(0.25, 1.0, -0.1));
2026-08-26 17:35:51 +02:00
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
2026-08-26 17:47:06 +02:00
`packages/cultmath/unity/org.gamecult.cultmath` . Unity consumes `CultMath.dll` and the numeric
2026-08-26 17:35:51 +02:00
`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
2026-08-26 17:47:06 +02:00
`packages/cultmath/scripts/build-unity-package.ps1` ; the script fresh-builds the netstandard2.1
2026-08-26 17:35:51 +02:00
assembly and rejects stale binaries, missing metadata, unexpected assemblies,
or leaked C# source.
CultMath: commit the Unity import and dispatch smoke, with its broken control
packages/cultmath/scripts/unity-smoke/ holds the good compute shader, the
deliberately broken control, the EditMode test (FindKernel, dispatch, readback),
and run-unity-smoke.ps1, which builds a scratch project outside the repo with a
file: path to the package, takes the Unity path as a parameter, requires all
three tests to pass, and deletes the project. The package README's Unity section
names it as the pre-tag step. Not -nographics: GetComputeShaderMessageCount
reports 0 for a broken shader until FindKernel or Dispatch compiles it. Ran once
on Starfire with Unity 6000.3.24f1: 3 passed, control rejected. Builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:41:04 +02:00
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.
2026-08-26 17:35:51 +02:00
## Shader Tooling
2026-08-26 17:47:06 +02:00
CultMath keeps portable DXC outside git under `packages/cultmath/.tools/` :
2026-08-26 17:35:51 +02:00
```powershell
2026-08-26 17:47:06 +02:00
.\packages\cultmath\tools\get-dxc.ps1
2026-10-16 01:01:54 +02:00
.\packages\cultmath\tools\compile-hlsl.ps1
2026-08-26 17:47:06 +02:00
.\packages\cultmath\tools\get-spirv-cross.ps1
.\packages\cultmath\tools\compile-hlsl-spirv.ps1 `
2026-08-26 17:35:51 +02:00
-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 `
2026-08-26 17:47:06 +02:00
-IncludePath E:\Projects\CultLib\packages\cultmath\shaders
.\packages\cultmath\tools\compile-spirv-metal.ps1 `
2026-08-26 17:35:51 +02:00
-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
```
2026-10-17 21:58:59 +02:00
`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` .
2026-10-17 13:32:37 +02:00
2026-10-16 03:02:13 +02:00
`compile-hlsl.ps1` compiles both `CultMath.hlsl` copies as a `lib_6_3` library with the x64 DXC; a warning fails it too (`-WX` ).
2026-10-16 01:01:54 +02:00
2026-08-26 17:35:51 +02:00
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.
2026-10-16 20:06:55 +02:00
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` .
2026-08-26 17:35:51 +02:00
## 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.