diff --git a/.gitignore b/.gitignore
index 3779002..c9b141d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ target/
**/target/
/native/cultmath-core/target.meta
.tools/
+/artifacts/
diff --git a/README.md b/README.md
index b471c7f..02d4775 100644
--- a/README.md
+++ b/README.md
@@ -73,21 +73,24 @@ dotnet build CultMath.slnx
dotnet test CultMath.slnx
cargo test --manifest-path native/cultmath-core/Cargo.toml
cargo build --release --manifest-path native/cultmath-core/Cargo.toml
+.\scripts\build-unity-package.ps1
```
-## Unity compiler
+## Unity package
-CultMath uses C# 10 record structs and file-scoped namespaces. Unity projects
-must select the compiler's latest language mode with an `Assets/csc.rsp` file:
+CultMath's Unity 2021.3 surface is the tracked precompiled package at
+`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.
-```text
--langversion:latest
+```json
+"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath"
```
-The package's Unity smoke project verifies this contract against Unity 6. The
-setting is already present in current GameCult Unity clients; it belongs to the
-consumer project because Unity only reads compiler response files from its
-`Assets` root.
+The repository root is not a Unity package. Build the tracked package with
+`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.
## Shader Tooling
diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1
new file mode 100644
index 0000000..329cedb
--- /dev/null
+++ b/scripts/build-unity-package.ps1
@@ -0,0 +1,107 @@
+param(
+ [string] $Configuration = "Release",
+ [string] $OutputDirectory = "artifacts\unity\org.gamecult.cultmath"
+)
+
+$ErrorActionPreference = "Stop"
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+$projectPath = Join-Path $repoRoot "src\CultMath\CultMath.csproj"
+$templateRoot = Join-Path $repoRoot "unity\org.gamecult.cultmath"
+$buildRoot = Join-Path $repoRoot "artifacts\cultmath-unity-build"
+$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) {
+ $OutputDirectory
+} else {
+ Join-Path $repoRoot $OutputDirectory
+}
+$freshAssembly = Join-Path $buildRoot "CultMath.dll"
+$freshSymbols = Join-Path $buildRoot "CultMath.pdb"
+$trackedPluginRoot = Join-Path $templateRoot "Runtime\Plugins"
+$trackedAssembly = Join-Path $trackedPluginRoot "CultMath.dll"
+$trackedSymbols = Join-Path $trackedPluginRoot "CultMath.pdb"
+
+if (Test-Path -LiteralPath $buildRoot) {
+ Remove-Item -LiteralPath $buildRoot -Recurse -Force
+}
+if (Test-Path -LiteralPath $outputRoot) {
+ Remove-Item -LiteralPath $outputRoot -Recurse -Force
+}
+
+dotnet build $projectPath -c $Configuration -f netstandard2.1 -o $buildRoot `
+ --disable-build-servers -p:UseSharedCompilation=false -p:ContinuousIntegrationBuild=true -m:1
+if ($LASTEXITCODE -ne 0) {
+ throw "CultMath Unity assembly build failed with exit code $LASTEXITCODE"
+}
+
+foreach ($path in @($freshAssembly, $freshSymbols)) {
+ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
+ throw "CultMath Unity build is missing $path"
+ }
+}
+
+$requiredTemplateFiles = @(
+ "LICENSE.md",
+ "README.md",
+ "THIRD-PARTY-NOTICES.md",
+ "package.json",
+ "Runtime\CultMath.asmdef",
+ "Runtime\CultMath.asmdef.meta",
+ "Runtime\Plugins\CultMath.dll",
+ "Runtime\Plugins\CultMath.dll.meta",
+ "Runtime\Plugins\CultMath.pdb",
+ "Runtime\Plugins\CultMath.pdb.meta",
+ "Shaders\CultMath.hlsl",
+ "Shaders\CultMath.hlsl.meta"
+)
+foreach ($relativePath in $requiredTemplateFiles) {
+ if (-not (Test-Path -LiteralPath (Join-Path $templateRoot $relativePath) -PathType Leaf)) {
+ throw "Tracked CultMath Unity package is incomplete: missing $relativePath"
+ }
+}
+
+$unityAssetFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File | Where-Object {
+ $_.Extension -in @(".asmdef", ".dll", ".pdb", ".hlsl")
+})
+$missingAssetMetas = @($unityAssetFiles | Where-Object { -not (Test-Path -LiteralPath ($_.FullName + ".meta")) })
+$missingDirectoryMetas = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -Directory | Where-Object {
+ -not (Test-Path -LiteralPath ($_.FullName + ".meta"))
+})
+if ($missingAssetMetas.Count -ne 0 -or $missingDirectoryMetas.Count -ne 0) {
+ $missing = @($missingAssetMetas.FullName) + @($missingDirectoryMetas.FullName)
+ throw "Tracked CultMath Unity package has assets without stable .meta files: $($missing -join ', ')"
+}
+
+$manifest = Get-Content -LiteralPath (Join-Path $templateRoot "package.json") -Raw | ConvertFrom-Json
+$expectedFileVersion = "$($manifest.version).0"
+$trackedFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($trackedAssembly).FileVersion
+$freshFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($freshAssembly).FileVersion
+if ($trackedFileVersion -ne $expectedFileVersion -or $freshFileVersion -ne $expectedFileVersion) {
+ throw "CultMath Unity assemblies must match package version $expectedFileVersion; tracked=$trackedFileVersion fresh=$freshFileVersion"
+}
+
+foreach ($pair in @(@($freshAssembly, $trackedAssembly), @($freshSymbols, $trackedSymbols))) {
+ $freshHash = (Get-FileHash -LiteralPath $pair[0] -Algorithm SHA256).Hash
+ $trackedHash = (Get-FileHash -LiteralPath $pair[1] -Algorithm SHA256).Hash
+ if ($freshHash -ne $trackedHash) {
+ throw "Tracked Unity artifact is stale: $($pair[1]) differs from a fresh deterministic build"
+ }
+}
+
+$trackedSourceFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.cs")
+if ($trackedSourceFiles.Count -ne 0) {
+ throw "CultMath Unity package must not compile repository C# sources: $($trackedSourceFiles.FullName -join ', ')"
+}
+
+New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
+Copy-Item -Path (Join-Path $templateRoot "*") -Destination $outputRoot -Recurse -Force
+
+$stagedAssemblies = @(Get-ChildItem -LiteralPath (Join-Path $outputRoot "Runtime\Plugins") -Filter "*.dll")
+$stagedSources = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.cs")
+if ($stagedAssemblies.Count -ne 1 -or $stagedAssemblies[0].Name -ne "CultMath.dll" -or $stagedSources.Count -ne 0) {
+ throw "CultMath package must contain one owned DLL and no C# source files"
+}
+
+Write-Host "CultMath Unity package: $outputRoot"
+Write-Host "Package: $($manifest.name)@$($manifest.version)"
+Write-Host "Managed assemblies: $($stagedAssemblies.Count)"
+Write-Host "C# source files: $($stagedSources.Count)"
diff --git a/unity/Runtime/CultMath.Unity.asmdef b/unity/Runtime/CultMath.Unity.asmdef
deleted file mode 100644
index 632c4e4..0000000
--- a/unity/Runtime/CultMath.Unity.asmdef
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "CultMath.Unity",
- "rootNamespace": "CultMath.Unity",
- "references": [
- "CultMath"
- ],
- "includePlatforms": [],
- "excludePlatforms": [],
- "allowUnsafeCode": false,
- "overrideReferences": false,
- "precompiledReferences": [],
- "autoReferenced": true,
- "defineConstraints": [],
- "versionDefines": [],
- "noEngineReferences": false
-}
diff --git a/unity/org.gamecult.cultmath/LICENSE.md b/unity/org.gamecult.cultmath/LICENSE.md
new file mode 100644
index 0000000..5e50f2d
--- /dev/null
+++ b/unity/org.gamecult.cultmath/LICENSE.md
@@ -0,0 +1,10 @@
+# CultMath License
+
+CultMath is licensed under the Mozilla Public License 2.0 (`MPL-2.0`).
+
+The complete license text is available from the Mozilla Foundation at
+. Source and modification history for the
+precompiled assembly in this package are available in the GameCult/CultMath
+repository at the corresponding package tag.
+
+Third-party code retains its original license. See `THIRD-PARTY-NOTICES.md`.
diff --git a/unity/org.gamecult.cultmath/README.md b/unity/org.gamecult.cultmath/README.md
new file mode 100644
index 0000000..1802ae9
--- /dev/null
+++ b/unity/org.gamecult.cultmath/README.md
@@ -0,0 +1,28 @@
+# CultMath for Unity
+
+This is CultMath's Unity 2021.3-compatible Git-UPM surface. It contains the
+precompiled `CultMath.dll`, portable symbols, and the numeric
+`Shaders/CultMath.hlsl` mirror.
+
+Unity does not compile CultMath's repository source tree. That source uses the
+current C# language and remains owned by the normal .NET project. The package
+facade references only the tracked precompiled assembly, so consumers do not
+need `csc.rsp` language overrides.
+
+Build and inspect the package from the repository root:
+
+```powershell
+.\scripts\build-unity-package.ps1
+```
+
+The builder fresh-builds CultMath for `netstandard2.1`, verifies the tracked
+DLL/PDB and stable Unity metadata, and stages an inspectable package under
+`artifacts/unity/org.gamecult.cultmath`.
+
+Consume the repository package with:
+
+```json
+"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath"
+```
+
+Production consumers should pin that URL to a commit or release tag.
diff --git a/unity/Runtime.meta b/unity/org.gamecult.cultmath/Runtime.meta
similarity index 77%
rename from unity/Runtime.meta
rename to unity/org.gamecult.cultmath/Runtime.meta
index 8e0678f..b0e7659 100644
--- a/unity/Runtime.meta
+++ b/unity/org.gamecult.cultmath/Runtime.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 8226365c03c5e1a438408b7e42b98740
+guid: a31bf2bb35e54dd1ad1c765337682769
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef
new file mode 100644
index 0000000..72c968e
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "CultMath",
+ "rootNamespace": "CultMath",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": true,
+ "precompiledReferences": [
+ "CultMath.dll"
+ ],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": true
+}
diff --git a/unity/Runtime/CultMath.Unity.asmdef.meta b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta
similarity index 76%
rename from unity/Runtime/CultMath.Unity.asmdef.meta
rename to unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta
index 2dd3a36..0ec09c2 100644
--- a/unity/Runtime/CultMath.Unity.asmdef.meta
+++ b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: d1906c397c420ae4eb222a3f09f36843
+guid: 4b5c49d478124399a5310d7a9d8ed166
AssemblyDefinitionImporter:
externalObjects: {}
userData:
diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins.meta b/unity/org.gamecult.cultmath/Runtime/Plugins.meta
new file mode 100644
index 0000000..fe8acaf
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Runtime/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f84156013e814c14b73f668b6610b9e0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll
new file mode 100644
index 0000000..8cdb670
Binary files /dev/null and b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll differ
diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta
new file mode 100644
index 0000000..d9935b3
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta
@@ -0,0 +1,21 @@
+fileFormatVersion: 2
+guid: 063b8a850f0f474aa7e89c4167f97a19
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 1
+ validateReferences: 1
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb
new file mode 100644
index 0000000..ed00f36
Binary files /dev/null and b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb differ
diff --git a/package.json.meta b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta
similarity index 60%
rename from package.json.meta
rename to unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta
index 23ed71c..e18fedb 100644
--- a/package.json.meta
+++ b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta
@@ -1,6 +1,6 @@
fileFormatVersion: 2
-guid: 1abf066549a826c4ba7a30d6676735c0
-PackageManifestImporter:
+guid: a8cc555f643a4d7eb4dd340025b208f6
+DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
diff --git a/unity/org.gamecult.cultmath/Shaders.meta b/unity/org.gamecult.cultmath/Shaders.meta
new file mode 100644
index 0000000..1e7e711
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Shaders.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ca5d773061ed49b3a173526048d40cb3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl
new file mode 100644
index 0000000..4c00fb6
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl
@@ -0,0 +1,302 @@
+#ifndef CULTMATH_HLSL
+#define CULTMATH_HLSL
+
+// CultMath HLSL mirror.
+//
+// HLSL already owns the float2/float3/float4 type names and most primitive
+// intrinsics. This include names the CultMath semantic surface explicitly so
+// shader code, C# wrappers, and Rust/native parity fixtures can share the same
+// small vocabulary without local helper drift.
+
+static const float CULTMATH_PI = 3.14159265358979323846;
+static const float CULTMATH_TAU = 6.28318530717958647692;
+static const float CULTMATH_HALF_PI = 1.57079632679489661923;
+static const float CULTMATH_NORMALIZE_EPSILON = 1.0e-20;
+
+float cultmath_radians(float degrees_value) { return degrees_value * (CULTMATH_PI / 180.0); }
+float cultmath_degrees(float radians_value) { return radians_value * (180.0 / CULTMATH_PI); }
+
+float cultmath_frac(float value) { return value - floor(value); }
+float2 cultmath_frac(float2 value) { return value - floor(value); }
+float3 cultmath_frac(float3 value) { return value - floor(value); }
+float4 cultmath_frac(float4 value) { return value - floor(value); }
+
+float cultmath_clamp(float value, float minimum, float maximum) { return min(max(value, minimum), maximum); }
+float2 cultmath_clamp(float2 value, float2 minimum, float2 maximum) { return min(max(value, minimum), maximum); }
+float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
+float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
+
+float cultmath_saturate(float value) { return cultmath_clamp(value, 0.0, 1.0); }
+float2 cultmath_saturate(float2 value) { return cultmath_clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }
+float3 cultmath_saturate(float3 value) { return cultmath_clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); }
+float4 cultmath_saturate(float4 value) { return cultmath_clamp(value, float4(0.0, 0.0, 0.0, 0.0), float4(1.0, 1.0, 1.0, 1.0)); }
+
+float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
+float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
+float3 cultmath_lerp(float3 start, float3 end, float3 amount) { return start + (end - start) * amount; }
+float4 cultmath_lerp(float4 start, float4 end, float4 amount) { return start + (end - start) * amount; }
+
+float cultmath_step(float edge, float value) { return value < edge ? 0.0 : 1.0; }
+float2 cultmath_step(float2 edge, float2 value) { return float2(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y)); }
+float3 cultmath_step(float3 edge, float3 value) { return float3(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z)); }
+float4 cultmath_step(float4 edge, float4 value) { return float4(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z), cultmath_step(edge.w, value.w)); }
+
+float cultmath_smoothstep(float minimum, float maximum, float value)
+{
+ float t = cultmath_saturate((value - minimum) / (maximum - minimum));
+ return t * t * (3.0 - 2.0 * t);
+}
+
+float2 cultmath_smoothstep(float2 minimum, float2 maximum, float2 value)
+{
+ float2 t = cultmath_saturate((value - minimum) / (maximum - minimum));
+ return t * t * (float2(3.0, 3.0) - float2(2.0, 2.0) * t);
+}
+
+float3 cultmath_smoothstep(float3 minimum, float3 maximum, float3 value)
+{
+ float3 t = cultmath_saturate((value - minimum) / (maximum - minimum));
+ return t * t * (float3(3.0, 3.0, 3.0) - float3(2.0, 2.0, 2.0) * t);
+}
+
+float4 cultmath_smoothstep(float4 minimum, float4 maximum, float4 value)
+{
+ float4 t = cultmath_saturate((value - minimum) / (maximum - minimum));
+ return t * t * (float4(3.0, 3.0, 3.0, 3.0) - float4(2.0, 2.0, 2.0, 2.0) * t);
+}
+
+float cultmath_smoothstep01(float value) { return cultmath_smoothstep(0.0, 1.0, value); }
+
+float cultmath_smootherstep(float value)
+{
+ value = cultmath_saturate(value);
+ return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
+}
+
+float2 cultmath_simplex_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
+float3 cultmath_simplex_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
+float3 cultmath_simplex_permute(float3 value) { return cultmath_simplex_mod289(((value * 34.0) + 1.0) * value); }
+
+float cultmath_simplex_noise(float2 value)
+{
+ const float4 c = float4(
+ 0.211324865405187,
+ 0.366025403784439,
+ -0.577350269189626,
+ 0.024390243902439);
+ float2 i = floor(value + dot(value, c.yy));
+ float2 x0 = value - i + dot(i, c.xx);
+ float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0);
+ float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z);
+
+ i = cultmath_simplex_mod289(i);
+ float3 p = cultmath_simplex_permute(
+ cultmath_simplex_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
+ float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
+ m *= m;
+ m *= m;
+
+ float3 x = 2.0 * frac(p * c.w) - 1.0;
+ float3 h = abs(x) - 0.5;
+ float3 ox = floor(x + 0.5);
+ float3 a0 = x - ox;
+ m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
+
+ float3 g = float3(
+ a0.x * x0.x + h.x * x0.y,
+ a0.y * x12.x + h.y * x12.y,
+ a0.z * x12.z + h.z * x12.w);
+ return 130.0 * dot(m, g);
+}
+
+float cultmath_lengthsq(float2 value) { return dot(value, value); }
+float cultmath_lengthsq(float3 value) { return dot(value, value); }
+float cultmath_lengthsq(float4 value) { return dot(value, value); }
+
+float cultmath_distance(float2 left, float2 right) { return length(left - right); }
+float cultmath_distance(float3 left, float3 right) { return length(left - right); }
+float cultmath_distance(float4 left, float4 right) { return length(left - right); }
+
+float2 cultmath_normalize(float2 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); }
+float3 cultmath_normalize(float3 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); }
+float4 cultmath_normalize(float4 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); }
+
+float2 cultmath_reflect(float2 incident, float2 normal_value)
+{
+ return incident - 2.0 * dot(normal_value, incident) * normal_value;
+}
+
+float3 cultmath_reflect(float3 incident, float3 normal_value)
+{
+ return incident - 2.0 * dot(normal_value, incident) * normal_value;
+}
+
+float2 cultmath_rotate(float2 value, float radians_value)
+{
+ float s = sin(radians_value);
+ float c = cos(radians_value);
+ return float2(value.x * c - value.y * s, value.x * s + value.y * c);
+}
+
+float2 cultmath_rotate_degrees(float2 value, float degrees_value)
+{
+ return cultmath_rotate(value, cultmath_radians(degrees_value));
+}
+
+float cultmath_csum(float2 value) { return value.x + value.y; }
+float cultmath_csum(float3 value) { return value.x + value.y + value.z; }
+float cultmath_csum(float4 value) { return value.x + value.y + value.z + value.w; }
+
+float cultmath_decay(float source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
+float2 cultmath_decay(float2 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
+float3 cultmath_decay(float3 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
+float4 cultmath_decay(float4 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
+
+float cultmath_damp(float start, float end, float lambda_value, float dt)
+{
+ return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt));
+}
+
+float2 cultmath_damp(float2 start, float2 end, float lambda_value, float dt)
+{
+ return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt));
+}
+
+float3 cultmath_damp(float3 start, float3 end, float lambda_value, float dt)
+{
+ return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt));
+}
+
+float4 cultmath_damp(float4 start, float4 end, float lambda_value, float dt)
+{
+ return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt));
+}
+
+float cultmath_catmullrom(float p0, float p1, float p2, float p3, float t)
+{
+ float t2 = t * t;
+ float t3 = t2 * t;
+ return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
+}
+
+float2 cultmath_catmullrom(float2 p0, float2 p1, float2 p2, float2 p3, float t)
+{
+ float t2 = t * t;
+ float t3 = t2 * t;
+ return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
+}
+
+float3 cultmath_catmullrom(float3 p0, float3 p1, float3 p2, float3 p3, float t)
+{
+ float t2 = t * t;
+ float t3 = t2 * t;
+ return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
+}
+
+float4 cultmath_catmullrom(float4 p0, float4 p1, float4 p2, float4 p3, float t)
+{
+ float t2 = t * t;
+ float t3 = t2 * t;
+ return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
+}
+
+float cultmath_quadratic_bezier(float p0, float p1, float p2, float t)
+{
+ t = cultmath_saturate(t);
+ float inv = 1.0 - t;
+ return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2;
+}
+
+float3 cultmath_quadratic_bezier(float3 p0, float3 p1, float3 p2, float t)
+{
+ t = cultmath_saturate(t);
+ float inv = 1.0 - t;
+ return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2;
+}
+
+float cultmath_cubic_bezier(float p0, float p1, float p2, float p3, float t)
+{
+ t = cultmath_saturate(t);
+ float inv = 1.0 - t;
+ return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3;
+}
+
+float3 cultmath_cubic_bezier(float3 p0, float3 p1, float3 p2, float3 p3, float t)
+{
+ t = cultmath_saturate(t);
+ float inv = 1.0 - t;
+ return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3;
+}
+
+float cultmath_hash(float value) { return cultmath_frac(sin(value) * 43758.5453); }
+float cultmath_hash(float2 value) { return cultmath_hash(dot(value, float2(127.1, 311.7))); }
+float cultmath_hash(float3 value) { return cultmath_hash(dot(value, float3(127.1, 311.7, 74.7))); }
+
+float cultmath_value_noise(float2 position)
+{
+ float2 cell = floor(position);
+ float2 local = cultmath_frac(position);
+ float2 u = local * local * (float2(3.0, 3.0) - float2(2.0, 2.0) * local);
+
+ float a = cultmath_hash(cell);
+ float b = cultmath_hash(cell + float2(1.0, 0.0));
+ float c = cultmath_hash(cell + float2(0.0, 1.0));
+ float d = cultmath_hash(cell + float2(1.0, 1.0));
+
+ return cultmath_lerp(cultmath_lerp(a, b, u.x), cultmath_lerp(c, d, u.x), u.y);
+}
+
+float cultmath_value_noise_bicubic(float2 position)
+{
+ float2 cell = floor(position);
+ float2 local = cultmath_frac(position);
+
+ float y0 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, -1.0)), cultmath_hash(cell + float2(0.0, -1.0)), cultmath_hash(cell + float2(1.0, -1.0)), cultmath_hash(cell + float2(2.0, -1.0)), local.x);
+ float y1 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 0.0)), cultmath_hash(cell + float2(0.0, 0.0)), cultmath_hash(cell + float2(1.0, 0.0)), cultmath_hash(cell + float2(2.0, 0.0)), local.x);
+ float y2 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 1.0)), cultmath_hash(cell + float2(0.0, 1.0)), cultmath_hash(cell + float2(1.0, 1.0)), cultmath_hash(cell + float2(2.0, 1.0)), local.x);
+ float y3 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 2.0)), cultmath_hash(cell + float2(0.0, 2.0)), cultmath_hash(cell + float2(1.0, 2.0)), cultmath_hash(cell + float2(2.0, 2.0)), local.x);
+
+ return cultmath_catmullrom(y0, y1, y2, y3, local.y);
+}
+
+float cultmath_value_noise_texture(Texture2D noise_texture, SamplerState noise_sampler, float2 uv, float scale)
+{
+ return noise_texture.SampleLevel(noise_sampler, uv * scale, 0.0).r;
+}
+
+float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, SamplerState noise_sampler, float2 uv, float scale, float2 texel_size)
+{
+ float2 position = uv * scale;
+ float2 cell = floor(position);
+ float2 local = cultmath_frac(position);
+ float2 base_uv = cell / scale;
+
+ float row0 = cultmath_catmullrom(
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, -1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, -1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, -1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, -1.0), 0.0).r,
+ local.x);
+ float row1 = cultmath_catmullrom(
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 0.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 0.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 0.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 0.0), 0.0).r,
+ local.x);
+ float row2 = cultmath_catmullrom(
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 1.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 1.0), 0.0).r,
+ local.x);
+ float row3 = cultmath_catmullrom(
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 2.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 2.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 2.0), 0.0).r,
+ noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 2.0), 0.0).r,
+ local.x);
+
+ return cultmath_catmullrom(row0, row1, row2, row3, local.y);
+}
+
+#endif
diff --git a/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta
new file mode 100644
index 0000000..d3b6d2b
--- /dev/null
+++ b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a46136cb4acb4ff2bc47b6a4f786e4b3
+ShaderIncludeImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md
new file mode 100644
index 0000000..c283df2
--- /dev/null
+++ b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md
@@ -0,0 +1,11 @@
+# Third-Party Notices
+
+## 2D Simplex Noise
+
+`math.simplex_noise` and `cultmath_simplex_noise` port the textureless 2D
+simplex-noise function by Ian McEwan and Ashima Arts, as distributed by Stefan
+Gustavson's `webgl-noise` project and Unity Mathematics.
+
+- Copyright 2011 Ashima Arts.
+- Upstream:
+- License: MIT.
diff --git a/package.json b/unity/org.gamecult.cultmath/package.json
similarity index 52%
rename from package.json
rename to unity/org.gamecult.cultmath/package.json
index 6da2a17..e45d0a3 100644
--- a/package.json
+++ b/unity/org.gamecult.cultmath/package.json
@@ -3,12 +3,15 @@
"displayName": "CultMath",
"version": "0.1.0",
"unity": "2021.3",
- "description": "Clean-room C# math primitives shaped for HLSL-style renderer and simulation code.",
+ "description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.",
+ "license": "MPL-2.0",
+ "author": {
+ "name": "GameCult"
+ },
"keywords": [
"GameCult",
"Math",
+ "HLSL",
"Simulation"
- ],
- "license": "MPL-2.0",
- "category": "Scripting"
+ ]
}