From 38b79a8d897ac5e2f3b3b24313a051b8b9a47a30 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 8 May 2026 01:49:44 +0100 Subject: [PATCH 001/203] Seed HLSL-shaped math library --- .gitignore | 7 ++ CultMath.slnx | 8 ++ README.md | 43 ++++++++ docs/design.md | 21 ++++ global.json | 6 ++ src/CultMath/CultMath.csproj | 21 ++++ src/CultMath/float2.cs | 26 +++++ src/CultMath/float3.cs | 31 ++++++ src/CultMath/float4.cs | 31 ++++++ src/CultMath/math.cs | 114 +++++++++++++++++++++ tests/CultMath.Tests/CultMath.Tests.csproj | 20 ++++ tests/CultMath.Tests/MathTests.cs | 38 +++++++ 12 files changed, 366 insertions(+) create mode 100644 .gitignore create mode 100644 CultMath.slnx create mode 100644 README.md create mode 100644 docs/design.md create mode 100644 global.json create mode 100644 src/CultMath/CultMath.csproj create mode 100644 src/CultMath/float2.cs create mode 100644 src/CultMath/float3.cs create mode 100644 src/CultMath/float4.cs create mode 100644 src/CultMath/math.cs create mode 100644 tests/CultMath.Tests/CultMath.Tests.csproj create mode 100644 tests/CultMath.Tests/MathTests.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..59e67361 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +**/bin/ +**/obj/ +artifacts/ +*.user +*.suo +.vs/ +.idea/ diff --git a/CultMath.slnx b/CultMath.slnx new file mode 100644 index 00000000..95834573 --- /dev/null +++ b/CultMath.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3b1809d --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# CultMath + +Clean-room C# math shaped for people who think in HLSL. + +CultMath provides lowercase vector types and shader-style intrinsics so +math-heavy renderer code can move between C# 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 and plain .NET. + +## Current Surface + +- `float2`, `float3`, `float4` +- component-wise arithmetic +- scalar-to-vector splats +- swizzles for common lanes +- conversions to and from `System.Numerics` +- `math` intrinsics: `radians`, `degrees`, `abs`, `floor`, `ceil`, `frac`, + `min`, `max`, `clamp`, `saturate`, `lerp`, `step`, `smoothstep`, `dot`, + `cross`, `length`, `distance`, `normalize`, and `reflect` + +## 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); +``` + +## Build + +```powershell +dotnet build CultMath.slnx +dotnet test CultMath.slnx +``` + +## Doctrine + +The goal is mechanical sympathy between CPU-side authoring and GPU-side shader +math. If an intrinsic exists here, it should behave like the shader concept +unless .NET numeric rules make that impossible. Weird deviations get tests and +documentation, not folklore. diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 00000000..62102739 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,21 @@ +# Design + +CultMath exists to make renderer math readable in both places it needs to live: +C# host code and HLSL shader code. + +## Rules + +- Keep type names HLSL-shaped: `float2`, `float3`, `float4`, and `math`. +- Implement public shader semantics directly; do not copy Unity.Mathematics. +- Prefer small immutable value types. +- Prefer component-wise overloads over clever generic machinery until the API + earns that complexity. +- Keep the first surface boring and exact. Noise, matrices, quaternions, and + packing helpers come later. + +## Non-Goals + +- Compiling C# to shaders. +- Replacing `System.Numerics` for general .NET work. +- Recreating every Unity.Mathematics type because a spreadsheet somewhere says + “coverage.” That way lies decorative obesity. diff --git a/global.json b/global.json new file mode 100644 index 00000000..99b1511d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.202", + "rollForward": "latestFeature" + } +} diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj new file mode 100644 index 00000000..f06b8bed --- /dev/null +++ b/src/CultMath/CultMath.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + CultMath + CultMath + CultMath + Clean-room C# math shaped for HLSL-style renderer code. + GameCult + https://github.com/GameCult/CultMath + README.md + $(NoWarn);CS8981 + + + + + + + diff --git a/src/CultMath/float2.cs b/src/CultMath/float2.cs new file mode 100644 index 00000000..1f1d66b1 --- /dev/null +++ b/src/CultMath/float2.cs @@ -0,0 +1,26 @@ +namespace CultMath; + +public readonly record struct float2(float x, float y) +{ + public static readonly float2 zero = new(0.0f, 0.0f); + public static readonly float2 one = new(1.0f, 1.0f); + + public float this[int index] => index switch + { + 0 => x, + 1 => y, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator float2(float value) => new(value, value); + public static explicit operator System.Numerics.Vector2(float2 value) => new(value.x, value.y); + public static explicit operator float2(System.Numerics.Vector2 value) => new(value.X, value.Y); + + public static float2 operator +(float2 value) => value; + public static float2 operator -(float2 value) => new(-value.x, -value.y); + public static float2 operator +(float2 left, float2 right) => new(left.x + right.x, left.y + right.y); + public static float2 operator -(float2 left, float2 right) => new(left.x - right.x, left.y - right.y); + public static float2 operator *(float2 left, float2 right) => new(left.x * right.x, left.y * right.y); + public static float2 operator /(float2 left, float2 right) => new(left.x / right.x, left.y / right.y); + public static float2 operator %(float2 left, float2 right) => new(left.x % right.x, left.y % right.y); +} diff --git a/src/CultMath/float3.cs b/src/CultMath/float3.cs new file mode 100644 index 00000000..037e8838 --- /dev/null +++ b/src/CultMath/float3.cs @@ -0,0 +1,31 @@ +namespace CultMath; + +public readonly record struct float3(float x, float y, float z) +{ + public static readonly float3 zero = new(0.0f, 0.0f, 0.0f); + public static readonly float3 one = new(1.0f, 1.0f, 1.0f); + + public float2 xy => new(x, y); + public float2 xz => new(x, z); + public float2 yz => new(y, z); + + public float this[int index] => index switch + { + 0 => x, + 1 => y, + 2 => z, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator float3(float value) => new(value, value, value); + public static explicit operator System.Numerics.Vector3(float3 value) => new(value.x, value.y, value.z); + public static explicit operator float3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z); + + public static float3 operator +(float3 value) => value; + public static float3 operator -(float3 value) => new(-value.x, -value.y, -value.z); + public static float3 operator +(float3 left, float3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z); + public static float3 operator -(float3 left, float3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z); + public static float3 operator *(float3 left, float3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z); + public static float3 operator /(float3 left, float3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z); + public static float3 operator %(float3 left, float3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z); +} diff --git a/src/CultMath/float4.cs b/src/CultMath/float4.cs new file mode 100644 index 00000000..d9ec0982 --- /dev/null +++ b/src/CultMath/float4.cs @@ -0,0 +1,31 @@ +namespace CultMath; + +public readonly record struct float4(float x, float y, float z, float w) +{ + public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f); + public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f); + + public float2 xy => new(x, y); + public float3 xyz => new(x, y, z); + + public float this[int index] => index switch + { + 0 => x, + 1 => y, + 2 => z, + 3 => w, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator float4(float value) => new(value, value, value, value); + public static explicit operator System.Numerics.Vector4(float4 value) => new(value.x, value.y, value.z, value.w); + public static explicit operator float4(System.Numerics.Vector4 value) => new(value.X, value.Y, value.Z, value.W); + + public static float4 operator +(float4 value) => value; + public static float4 operator -(float4 value) => new(-value.x, -value.y, -value.z, -value.w); + public static float4 operator +(float4 left, float4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); + public static float4 operator -(float4 left, float4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); + public static float4 operator *(float4 left, float4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w); + public static float4 operator /(float4 left, float4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w); + public static float4 operator %(float4 left, float4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w); +} diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs new file mode 100644 index 00000000..6b0cf455 --- /dev/null +++ b/src/CultMath/math.cs @@ -0,0 +1,114 @@ +namespace CultMath; + +public static class math +{ + public const float PI = MathF.PI; + public const float TAU = MathF.PI * 2.0f; + public const float HALF_PI = MathF.PI * 0.5f; + + public static float radians(float degrees) => degrees * (PI / 180.0f); + public static float degrees(float radians) => radians * (180.0f / PI); + + public static float abs(float value) => MathF.Abs(value); + public static float2 abs(float2 value) => new(abs(value.x), abs(value.y)); + public static float3 abs(float3 value) => new(abs(value.x), abs(value.y), abs(value.z)); + public static float4 abs(float4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w)); + + public static float floor(float value) => MathF.Floor(value); + public static float2 floor(float2 value) => new(floor(value.x), floor(value.y)); + public static float3 floor(float3 value) => new(floor(value.x), floor(value.y), floor(value.z)); + public static float4 floor(float4 value) => new(floor(value.x), floor(value.y), floor(value.z), floor(value.w)); + + public static float ceil(float value) => MathF.Ceiling(value); + public static float2 ceil(float2 value) => new(ceil(value.x), ceil(value.y)); + public static float3 ceil(float3 value) => new(ceil(value.x), ceil(value.y), ceil(value.z)); + public static float4 ceil(float4 value) => new(ceil(value.x), ceil(value.y), ceil(value.z), ceil(value.w)); + + public static float frac(float value) => value - floor(value); + public static float2 frac(float2 value) => value - floor(value); + public static float3 frac(float3 value) => value - floor(value); + public static float4 frac(float4 value) => value - floor(value); + + public static float min(float left, float right) => MathF.Min(left, right); + public static float2 min(float2 left, float2 right) => new(min(left.x, right.x), min(left.y, right.y)); + public static float3 min(float3 left, float3 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z)); + public static float4 min(float4 left, float4 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z), min(left.w, right.w)); + + public static float max(float left, float right) => MathF.Max(left, right); + public static float2 max(float2 left, float2 right) => new(max(left.x, right.x), max(left.y, right.y)); + public static float3 max(float3 left, float3 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z)); + public static float4 max(float4 left, float4 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z), max(left.w, right.w)); + + public static float clamp(float value, float minimum, float maximum) => min(max(value, minimum), maximum); + public static float2 clamp(float2 value, float2 minimum, float2 maximum) => min(max(value, minimum), maximum); + public static float3 clamp(float3 value, float3 minimum, float3 maximum) => min(max(value, minimum), maximum); + public static float4 clamp(float4 value, float4 minimum, float4 maximum) => min(max(value, minimum), maximum); + + public static float saturate(float value) => clamp(value, 0.0f, 1.0f); + public static float2 saturate(float2 value) => clamp(value, 0.0f, 1.0f); + public static float3 saturate(float3 value) => clamp(value, 0.0f, 1.0f); + public static float4 saturate(float4 value) => clamp(value, 0.0f, 1.0f); + + public static float lerp(float start, float end, float amount) => start + (end - start) * amount; + public static float2 lerp(float2 start, float2 end, float2 amount) => start + (end - start) * amount; + public static float3 lerp(float3 start, float3 end, float3 amount) => start + (end - start) * amount; + public static float4 lerp(float4 start, float4 end, float4 amount) => start + (end - start) * amount; + + public static float step(float edge, float value) => value < edge ? 0.0f : 1.0f; + public static float2 step(float2 edge, float2 value) => new(step(edge.x, value.x), step(edge.y, value.y)); + public static float3 step(float3 edge, float3 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z)); + public static float4 step(float4 edge, float4 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z), step(edge.w, value.w)); + + public static float smoothstep(float minimum, float maximum, float value) + { + var t = saturate((value - minimum) / (maximum - minimum)); + return t * t * (3.0f - 2.0f * t); + } + + public static float2 smoothstep(float2 minimum, float2 maximum, float2 value) + { + var t = saturate((value - minimum) / (maximum - minimum)); + return t * t * (3.0f - 2.0f * t); + } + + public static float3 smoothstep(float3 minimum, float3 maximum, float3 value) + { + var t = saturate((value - minimum) / (maximum - minimum)); + return t * t * (3.0f - 2.0f * t); + } + + public static float4 smoothstep(float4 minimum, float4 maximum, float4 value) + { + var t = saturate((value - minimum) / (maximum - minimum)); + return t * t * (3.0f - 2.0f * t); + } + + public static float dot(float2 left, float2 right) => left.x * right.x + left.y * right.y; + public static float dot(float3 left, float3 right) => left.x * right.x + left.y * right.y + left.z * right.z; + public static float dot(float4 left, float4 right) => left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w; + + public static float3 cross(float3 left, float3 right) => + new( + left.y * right.z - left.z * right.y, + left.z * right.x - left.x * right.z, + left.x * right.y - left.y * right.x); + + public static float lengthsq(float2 value) => dot(value, value); + public static float lengthsq(float3 value) => dot(value, value); + public static float lengthsq(float4 value) => dot(value, value); + + public static float length(float2 value) => MathF.Sqrt(lengthsq(value)); + public static float length(float3 value) => MathF.Sqrt(lengthsq(value)); + public static float length(float4 value) => MathF.Sqrt(lengthsq(value)); + + public static float distance(float2 left, float2 right) => length(left - right); + public static float distance(float3 left, float3 right) => length(left - right); + public static float distance(float4 left, float4 right) => length(left - right); + + public static float2 normalize(float2 value) => value / MathF.Max(length(value), 1.0e-20f); + public static float3 normalize(float3 value) => value / MathF.Max(length(value), 1.0e-20f); + public static float4 normalize(float4 value) => value / MathF.Max(length(value), 1.0e-20f); + + public static float2 reflect(float2 incident, float2 normal) => incident - 2.0f * dot(normal, incident) * normal; + public static float3 reflect(float3 incident, float3 normal) => incident - 2.0f * dot(normal, incident) * normal; +} diff --git a/tests/CultMath.Tests/CultMath.Tests.csproj b/tests/CultMath.Tests/CultMath.Tests.csproj new file mode 100644 index 00000000..1e77eba1 --- /dev/null +++ b/tests/CultMath.Tests/CultMath.Tests.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs new file mode 100644 index 00000000..80b0ed95 --- /dev/null +++ b/tests/CultMath.Tests/MathTests.cs @@ -0,0 +1,38 @@ +using CultMath; +using Xunit; + +namespace CultMath.Tests; + +public sealed class MathTests +{ + [Fact] + public void FracMatchesShaderStylePositiveAndNegativeValues() + { + Assert.Equal(0.25f, math.frac(1.25f)); + Assert.Equal(0.75f, math.frac(-1.25f)); + } + + [Fact] + public void VectorOperationsAreComponentWise() + { + var value = new float3(1.0f, 2.0f, 3.0f) * new float3(4.0f, 5.0f, 6.0f); + + Assert.Equal(new float3(4.0f, 10.0f, 18.0f), value); + } + + [Fact] + public void NormalizeProducesUnitLength() + { + var value = math.normalize(new float3(3.0f, 4.0f, 0.0f)); + + Assert.Equal(1.0f, math.length(value), precision: 5); + } + + [Fact] + public void SmoothstepUsesHermiteRamp() + { + Assert.Equal(0.0f, math.smoothstep(0.0f, 1.0f, -1.0f)); + Assert.Equal(0.5f, math.smoothstep(0.0f, 1.0f, 0.5f), precision: 5); + Assert.Equal(1.0f, math.smoothstep(0.0f, 1.0f, 2.0f)); + } +} From 5cef6d951e3bf8bf49c89d81675a88605b5aa599 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 25 May 2026 20:33:04 +0100 Subject: [PATCH 002/203] Add shader-shaped vector constructors and trig intrinsics --- src/CultMath/float3.cs | 5 +++++ src/CultMath/float4.cs | 10 ++++++++++ src/CultMath/math.cs | 8 ++++++++ tests/CultMath.Tests/MathTests.cs | 17 +++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/src/CultMath/float3.cs b/src/CultMath/float3.cs index 037e8838..70cfa02b 100644 --- a/src/CultMath/float3.cs +++ b/src/CultMath/float3.cs @@ -5,6 +5,11 @@ public readonly record struct float3(float x, float y, float z) public static readonly float3 zero = new(0.0f, 0.0f, 0.0f); public static readonly float3 one = new(1.0f, 1.0f, 1.0f); + public float3(float2 xy, float z) + : this(xy.x, xy.y, z) + { + } + public float2 xy => new(x, y); public float2 xz => new(x, z); public float2 yz => new(y, z); diff --git a/src/CultMath/float4.cs b/src/CultMath/float4.cs index d9ec0982..c7bfb404 100644 --- a/src/CultMath/float4.cs +++ b/src/CultMath/float4.cs @@ -5,6 +5,16 @@ public readonly record struct float4(float x, float y, float z, float w) public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f); public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f); + public float4(float2 xy, float z, float w) + : this(xy.x, xy.y, z, w) + { + } + + public float4(float3 xyz, float w) + : this(xyz.x, xyz.y, xyz.z, w) + { + } + public float2 xy => new(x, y); public float3 xyz => new(x, y, z); diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index 6b0cf455..6d44f900 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -8,6 +8,14 @@ public static class math public static float radians(float degrees) => degrees * (PI / 180.0f); public static float degrees(float radians) => radians * (180.0f / PI); + public static float sin(float value) => MathF.Sin(value); + public static float cos(float value) => MathF.Cos(value); + public static float tan(float value) => MathF.Tan(value); + public static float asin(float value) => MathF.Asin(value); + public static float acos(float value) => MathF.Acos(value); + public static float atan(float value) => MathF.Atan(value); + public static float atan2(float y, float x) => MathF.Atan2(y, x); + public static float sqrt(float value) => MathF.Sqrt(value); public static float abs(float value) => MathF.Abs(value); public static float2 abs(float2 value) => new(abs(value.x), abs(value.y)); diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 80b0ed95..8576a770 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -20,6 +20,14 @@ public sealed class MathTests Assert.Equal(new float3(4.0f, 10.0f, 18.0f), value); } + [Fact] + public void VectorConstructorsAcceptLowerDimensionVectors() + { + Assert.Equal(new float3(1.0f, 2.0f, 3.0f), new float3(new float2(1.0f, 2.0f), 3.0f)); + Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float2(1.0f, 2.0f), 3.0f, 4.0f)); + Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); + } + [Fact] public void NormalizeProducesUnitLength() { @@ -35,4 +43,13 @@ public sealed class MathTests Assert.Equal(0.5f, math.smoothstep(0.0f, 1.0f, 0.5f), precision: 5); Assert.Equal(1.0f, math.smoothstep(0.0f, 1.0f, 2.0f)); } + + [Fact] + public void TrigIntrinsicsUseRadians() + { + Assert.Equal(1.0f, math.sin(math.HALF_PI), precision: 5); + Assert.Equal(-1.0f, math.cos(math.PI), precision: 5); + Assert.Equal(1.0f, math.tan(math.PI * 0.25f), precision: 5); + Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5); + } } From bfe356fe92e26a641d4db0ba592f23d7ee00396b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 3 Jun 2026 02:58:56 +0100 Subject: [PATCH 003/203] Add Rust-backed Voronoi batch kernel --- .gitignore | 9 +- README.md | 32 +++-- docs/design.md | 7 +- native/cultmath-core/Cargo.lock | 7 ++ native/cultmath-core/Cargo.toml | 13 ++ native/cultmath-core/src/lib.rs | 188 +++++++++++++++++++++++++++++ src/CultMath/Color32.cs | 4 + src/CultMath/CultMath.csproj | 1 + src/CultMath/CultMathTone.cs | 10 ++ src/CultMath/Voronoi.cs | 190 ++++++++++++++++++++++++++++++ tests/CultMath.Tests/MathTests.cs | 16 +++ 11 files changed, 461 insertions(+), 16 deletions(-) create mode 100644 native/cultmath-core/Cargo.lock create mode 100644 native/cultmath-core/Cargo.toml create mode 100644 native/cultmath-core/src/lib.rs create mode 100644 src/CultMath/Color32.cs create mode 100644 src/CultMath/CultMathTone.cs create mode 100644 src/CultMath/Voronoi.cs diff --git a/.gitignore b/.gitignore index 59e67361..6eae4393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ +bin/ +obj/ **/bin/ **/obj/ -artifacts/ -*.user -*.suo -.vs/ -.idea/ +target/ +**/target/ diff --git a/README.md b/README.md index c3b1809d..b693d258 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # CultMath -Clean-room C# math shaped for people who think in HLSL. +Clean-room math shaped for people who think in HLSL. -CultMath provides lowercase vector types and shader-style intrinsics so -math-heavy renderer code can move between C# 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 and plain .NET. +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 @@ -17,6 +18,17 @@ small, Aquarium-born library built from public shader semantics and plain .NET. - `math` intrinsics: `radians`, `degrees`, `abs`, `floor`, `ceil`, `frac`, `min`, `max`, `clamp`, `saturate`, `lerp`, `step`, `smoothstep`, `dot`, `cross`, `length`, `distance`, `normalize`, and `reflect` +- `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 @@ -33,11 +45,13 @@ float glow = math.smoothstep(0.2f, 1.0f, rim); ```powershell 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 ``` ## Doctrine -The goal is mechanical sympathy between CPU-side authoring and GPU-side shader -math. If an intrinsic exists here, it should behave like the shader concept -unless .NET numeric rules make that impossible. Weird deviations get tests and -documentation, not folklore. +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. diff --git a/docs/design.md b/docs/design.md index 62102739..3c87fb51 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,11 +1,14 @@ # Design -CultMath exists to make renderer math readable in both places it needs to live: -C# host code and HLSL shader code. +CultMath exists to make renderer math readable in every place it needs to live: +Rust hot kernels, C# host code, and HLSL shader code. ## Rules - Keep type names HLSL-shaped: `float2`, `float3`, `float4`, and `math`. +- Keep Rust as the owner of native hot-path kernels and parity fixtures. +- Keep C#, shader, and scripting surfaces as runtime wrappers, not competing + math authorities. - Implement public shader semantics directly; do not copy Unity.Mathematics. - Prefer small immutable value types. - Prefer component-wise overloads over clever generic machinery until the API diff --git a/native/cultmath-core/Cargo.lock b/native/cultmath-core/Cargo.lock new file mode 100644 index 00000000..09598b97 --- /dev/null +++ b/native/cultmath-core/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cultmath-core" +version = "0.1.0" diff --git a/native/cultmath-core/Cargo.toml b/native/cultmath-core/Cargo.toml new file mode 100644 index 00000000..cf7819a0 --- /dev/null +++ b/native/cultmath-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cultmath-core" +version = "0.1.0" +edition = "2021" + +[lib] +name = "cultmath_core" +crate-type = ["cdylib", "staticlib"] + +[profile.release] +lto = "thin" +codegen-units = 1 +panic = "abort" diff --git a/native/cultmath-core/src/lib.rs b/native/cultmath-core/src/lib.rs new file mode 100644 index 00000000..5a484608 --- /dev/null +++ b/native/cultmath-core/src/lib.rs @@ -0,0 +1,188 @@ +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct CultMathColor32 { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +#[no_mangle] +pub unsafe extern "C" fn cultmath_apollonian_voronoi_tones( + xs: *const f32, + ys: *const f32, + tones: *const u8, + spans: *const f32, + count: usize, + resolution_y: f32, + frame_index: i32, + out_colors: *mut CultMathColor32, +) -> i32 { + if xs.is_null() || ys.is_null() || tones.is_null() || spans.is_null() || out_colors.is_null() { + return -1; + } + + let xs = unsafe { std::slice::from_raw_parts(xs, count) }; + let ys = unsafe { std::slice::from_raw_parts(ys, count) }; + let tones = unsafe { std::slice::from_raw_parts(tones, count) }; + let spans = unsafe { std::slice::from_raw_parts(spans, count) }; + let out_colors = unsafe { std::slice::from_raw_parts_mut(out_colors, count) }; + for index in 0..count { + out_colors[index] = tone_color( + xs[index], + ys[index], + resolution_y, + frame_index, + tones[index], + spans[index], + ); + } + + 0 +} + +fn tone_color(px: f32, py: f32, resolution_y: f32, frame_index: i32, tone: u8, span: f32) -> CultMathColor32 { + let sample = stable_sample(px, py, resolution_y, frame_index, tone, span); + let luminance = saturate(sample.0 * 0.299 + sample.1 * 0.587 + sample.2 * 0.114); + match tone { + 3 => { + let glow = 0.62 + luminance * 0.38; + color(255.0 * glow, 96.0 * glow, 14.0 * glow) + } + 4 => { + let glow = 0.68 + luminance * 0.32; + color(245.0 * glow, 248.0 * glow, 252.0 * glow) + } + _ => { + let (gain, lift) = match tone { + 1 => (0.25, 6.0), + 2 => (0.95, 20.0), + _ => (0.16, 2.0), + }; + color( + lift + sample.0 * 255.0 * gain, + lift + sample.1 * 255.0 * gain, + lift + sample.2 * 255.0 * gain, + ) + } + } +} + +fn stable_sample(px: f32, py: f32, resolution_y: f32, frame_index: i32, tone: u8, span: f32) -> (f32, f32, f32) { + let seed = px * 12.9898 + py * 78.233 + hash1((tone as f32 + 1.0) * 19.19); + let jitter_x = (hash1(seed) - 0.5) * span; + let jitter_y = (hash1(seed + 37.17) - 0.5) * span; + voronoi(px + jitter_x, py + jitter_y, resolution_y, frame_index) +} + +fn voronoi(px: f32, py: f32, resolution_y: f32, frame_index: i32) -> (f32, f32, f32) { + let time = frame_index as f32 / 120.0; + let scale = 6.0; + let x = scale * px / resolution_y.max(1.0); + let y = scale * py / resolution_y.max(1.0); + let nx = x.floor(); + let ny = y.floor(); + let fx = frac(x); + let fy = frac(y); + let mut distance = 8.0; + let mut red = 0.0; + let mut green = 0.0; + let mut blue = 0.0; + let smoothness = 0.005; + + for j in -2..=2 { + for i in -2..=2 { + let gx = i as f32; + let gy = j as f32; + let (mut ox, mut oy) = hash2(nx + gx, ny + gy); + let weight = ox * 0.5 + 0.5; + ox = 0.5 + 0.5 * (time + 6.2831 * ox).sin(); + oy = 0.5 + 0.5 * (time + 6.2831 * oy).sin(); + let dx = (gx - fx + ox).abs(); + let dy = (gy - fy + oy).abs(); + let d = dx.max(dy) * weight; + let seed = hash1((nx + gx) * 7.0 + (ny + gy) * 113.0); + let candidate_red = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 2.0).sin(); + let candidate_green = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 3.0).sin(); + let candidate_blue = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 2.0).sin(); + let h = smoothstep(0.0, 1.0, 0.5 + 0.5 * (distance - d) / smoothness); + let correction = h * (1.0 - h) * smoothness / (1.0 + 3.0 * smoothness); + distance = lerp(distance, d, h) - correction; + red = lerp(red, candidate_red, h) - correction; + green = lerp(green, candidate_green, h) - correction; + blue = lerp(blue, candidate_blue, h) - correction; + } + } + + let edge_dimming = 1.0 - 0.1 * smoothstep(0.04, 0.05, distance); + ( + (red * edge_dimming).max(0.0), + (green * edge_dimming).max(0.0), + (blue * edge_dimming).max(0.0), + ) +} + +fn hash1(value: f32) -> f32 { + frac(value.sin() * 43758.5453) +} + +fn hash2(x: f32, y: f32) -> (f32, f32) { + ( + frac((x * 127.1 + y * 311.7).sin() * 43758.5453), + frac((x * 269.5 + y * 183.3).sin() * 43758.5453), + ) +} + +fn frac(value: f32) -> f32 { + value - value.floor() +} + +fn saturate(value: f32) -> f32 { + value.clamp(0.0, 1.0) +} + +fn smoothstep(edge0: f32, edge1: f32, value: f32) -> f32 { + let t = saturate((value - edge0) / (edge1 - edge0)); + t * t * (3.0 - 2.0 * t) +} + +fn lerp(start: f32, end: f32, amount: f32) -> f32 { + start + (end - start) * amount +} + +fn color(r: f32, g: f32, b: f32) -> CultMathColor32 { + CultMathColor32 { + r: r.clamp(0.0, 255.0) as u8, + g: g.clamp(0.0, 255.0) as u8, + b: b.clamp(0.0, 255.0) as u8, + a: 255, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn batch_sampler_writes_colors() { + let xs = [0.0, 20.0, 100.0]; + let ys = [0.0, 30.0, 140.0]; + let tones = [0, 3, 4]; + let spans = [1920.0, 8.0, 8.0]; + let mut out = [CultMathColor32::default(); 3]; + let rc = unsafe { + cultmath_apollonian_voronoi_tones( + xs.as_ptr(), + ys.as_ptr(), + tones.as_ptr(), + spans.as_ptr(), + xs.len(), + 1080.0, + 12, + out.as_mut_ptr(), + ) + }; + assert_eq!(rc, 0); + assert!(out.iter().any(|color| color.r != 0 || color.g != 0 || color.b != 0)); + } +} diff --git a/src/CultMath/Color32.cs b/src/CultMath/Color32.cs new file mode 100644 index 00000000..e9a2b481 --- /dev/null +++ b/src/CultMath/Color32.cs @@ -0,0 +1,4 @@ +namespace CultMath; + +[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] +public readonly record struct Color32(byte r, byte g, byte b, byte a = 255); diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index f06b8bed..83f6fb9d 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -12,6 +12,7 @@ https://github.com/GameCult/CultMath README.md $(NoWarn);CS8981 + true diff --git a/src/CultMath/CultMathTone.cs b/src/CultMath/CultMathTone.cs new file mode 100644 index 00000000..25dfffeb --- /dev/null +++ b/src/CultMath/CultMathTone.cs @@ -0,0 +1,10 @@ +namespace CultMath; + +public enum CultMathTone : byte +{ + Background = 0, + Panel = 1, + Edge = 2, + Header = 3, + Body = 4, +} diff --git a/src/CultMath/Voronoi.cs b/src/CultMath/Voronoi.cs new file mode 100644 index 00000000..52e6acb6 --- /dev/null +++ b/src/CultMath/Voronoi.cs @@ -0,0 +1,190 @@ +using System.Runtime.InteropServices; + +namespace CultMath; + +public static class Voronoi +{ + public static bool NativeAvailable { get; private set; } = true; + + public static Color32 SampleTone(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span) + { + Span xs = stackalloc[] { x }; + Span ys = stackalloc[] { y }; + Span tones = stackalloc[] { tone }; + Span spans = stackalloc[] { span }; + Span colors = stackalloc Color32[1]; + SampleTones(xs, ys, tones, spans, resolutionY, frameIndex, colors); + return colors[0]; + } + + public static void SampleTones( + ReadOnlySpan xs, + ReadOnlySpan ys, + ReadOnlySpan tones, + ReadOnlySpan spans, + float resolutionY, + int frameIndex, + Span colors) + { + if (xs.Length != ys.Length || xs.Length != tones.Length || xs.Length != spans.Length || xs.Length != colors.Length) + { + throw new ArgumentException("CultMath Voronoi batch spans must have equal length."); + } + + if (xs.IsEmpty) + { + return; + } + + if (NativeAvailable && TrySampleNative(xs, ys, tones, spans, resolutionY, frameIndex, colors)) + { + return; + } + + for (var index = 0; index < xs.Length; index++) + { + colors[index] = SampleManaged(xs[index], ys[index], resolutionY, frameIndex, tones[index], spans[index]); + } + } + + private static unsafe bool TrySampleNative( + ReadOnlySpan xs, + ReadOnlySpan ys, + ReadOnlySpan tones, + ReadOnlySpan spans, + float resolutionY, + int frameIndex, + Span colors) + { + try + { + fixed (float* xPtr = xs) + fixed (float* yPtr = ys) + fixed (CultMathTone* tonePtr = tones) + fixed (float* spanPtr = spans) + fixed (Color32* colorPtr = colors) + { + var result = Native.cultmath_apollonian_voronoi_tones( + xPtr, + yPtr, + (byte*)tonePtr, + spanPtr, + (nuint)xs.Length, + resolutionY, + frameIndex, + colorPtr); + if (result == 0) + { + return true; + } + } + } + catch (DllNotFoundException) + { + NativeAvailable = false; + } + catch (EntryPointNotFoundException) + { + NativeAvailable = false; + } + + return false; + } + + private static Color32 SampleManaged(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span) + { + var sample = StableSample(x, y, resolutionY, frameIndex, tone, span); + var luminance = math.saturate(sample.x * 0.299f + sample.y * 0.587f + sample.z * 0.114f); + if (tone == CultMathTone.Header) + { + var glow = 0.62f + luminance * 0.38f; + return new Color32((byte)(255 * glow), (byte)(96 * glow), (byte)(14 * glow)); + } + + if (tone == CultMathTone.Body) + { + var glow = 0.68f + luminance * 0.32f; + return new Color32((byte)(245 * glow), (byte)(248 * glow), (byte)(252 * glow)); + } + + var gain = tone switch + { + CultMathTone.Panel => 0.25f, + CultMathTone.Edge => 0.95f, + _ => 0.16f, + }; + var lift = tone switch + { + CultMathTone.Panel => 6.0f, + CultMathTone.Edge => 20.0f, + _ => 2.0f, + }; + return new Color32( + (byte)Math.Clamp(lift + sample.x * 255.0f * gain, 0, 255), + (byte)Math.Clamp(lift + sample.y * 255.0f * gain, 0, 255), + (byte)Math.Clamp(lift + sample.z * 255.0f * gain, 0, 255)); + } + + private static float3 StableSample(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span) + { + var seed = x * 12.9898f + y * 78.233f + Hash1(((byte)tone + 1.0f) * 19.19f); + var jitter = new float2((Hash1(seed) - 0.5f) * span, (Hash1(seed + 37.17f) - 0.5f) * span); + return SampleField(x + jitter.x, y + jitter.y, resolutionY, frameIndex); + } + + private static float3 SampleField(float px, float py, float resolutionY, int frameIndex) + { + var time = frameIndex / 120.0f; + var p = new float2(6.0f * px / Math.Max(1.0f, resolutionY), 6.0f * py / Math.Max(1.0f, resolutionY)); + var n = math.floor(p); + var f = math.frac(p); + var distance = 8.0f; + var color = float3.zero; + const float smoothness = 0.005f; + for (var j = -2; j <= 2; j++) + { + for (var i = -2; i <= 2; i++) + { + var g = new float2(i, j); + var o = Hash2(n + g); + var weight = o.x * 0.5f + 0.5f; + o = 0.5f + 0.5f * new float2(math.sin(time + 6.2831f * o.x), math.sin(time + 6.2831f * o.y)); + var d2 = math.abs(g - f + o); + var d = Math.Max(d2.x, d2.y) * weight; + var seed = Hash1(math.dot(n + g, new float2(7.0f, 113.0f))); + var candidate = 0.5f + 0.5f * new float3( + math.sin(seed * 2.5f + 3.5f + 2.0f), + math.sin(seed * 2.5f + 3.5f + 3.0f), + math.sin(seed * 2.5f + 3.5f + 2.0f)); + var h = math.smoothstep(0.0f, 1.0f, 0.5f + 0.5f * (distance - d) / smoothness); + var correction = h * (1.0f - h) * smoothness / (1.0f + 3.0f * smoothness); + distance = math.lerp(distance, d, h) - correction; + color = math.lerp(color, candidate, h) - correction; + } + } + + return math.max(float3.zero, color * (1.0f - 0.1f * math.smoothstep(0.04f, 0.05f, distance))); + } + + private static float Hash1(float value) => math.frac(math.sin(value) * 43758.5453f); + + private static float2 Hash2(float2 value) + { + var p = new float2(math.dot(value, new float2(127.1f, 311.7f)), math.dot(value, new float2(269.5f, 183.3f))); + return math.frac(new float2(math.sin(p.x) * 43758.5453f, math.sin(p.y) * 43758.5453f)); + } + + private static unsafe partial class Native + { + [DllImport("cultmath_core", CallingConvention = CallingConvention.Cdecl)] + public static extern int cultmath_apollonian_voronoi_tones( + float* xs, + float* ys, + byte* tones, + float* spans, + nuint count, + float resolutionY, + int frameIndex, + Color32* outColors); + } +} diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 8576a770..2e0cea93 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -52,4 +52,20 @@ public sealed class MathTests Assert.Equal(1.0f, math.tan(math.PI * 0.25f), precision: 5); Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5); } + + [Fact] + public void VoronoiBatchProducesToneMappedColors() + { + var xs = new[] { 0.0f, 24.0f, 96.0f }; + var ys = new[] { 0.0f, 36.0f, 144.0f }; + var tones = new[] { CultMathTone.Background, CultMathTone.Header, CultMathTone.Body }; + var spans = new[] { 1920.0f, 8.0f, 8.0f }; + var colors = new Color32[xs.Length]; + + Voronoi.SampleTones(xs, ys, tones, spans, 1080.0f, 12, colors); + + Assert.NotEqual(new Color32(), colors[0]); + Assert.True(colors[1].r > colors[1].g); + Assert.True(colors[2].r > 120 && colors[2].g > 120 && colors[2].b > 120); + } } From b79b6d047fc850fa7ed38e0f4cc673b25ab50b23 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 3 Jun 2026 04:08:48 +0100 Subject: [PATCH 004/203] Broaden Voronoi color spectrum --- native/cultmath-core/src/lib.rs | 37 +++++++++++++++++---- src/CultMath/Voronoi.cs | 58 +++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/native/cultmath-core/src/lib.rs b/native/cultmath-core/src/lib.rs index 5a484608..46e7ced6 100644 --- a/native/cultmath-core/src/lib.rs +++ b/native/cultmath-core/src/lib.rs @@ -55,9 +55,9 @@ fn tone_color(px: f32, py: f32, resolution_y: f32, frame_index: i32, tone: u8, s } _ => { let (gain, lift) = match tone { - 1 => (0.25, 6.0), - 2 => (0.95, 20.0), - _ => (0.16, 2.0), + 1 => (0.42, 14.0), + 2 => (1.05, 24.0), + _ => (0.24, 4.0), }; color( lift + sample.0 * 255.0 * gain, @@ -102,9 +102,8 @@ fn voronoi(px: f32, py: f32, resolution_y: f32, frame_index: i32) -> (f32, f32, let dy = (gy - fy + oy).abs(); let d = dx.max(dy) * weight; let seed = hash1((nx + gx) * 7.0 + (ny + gy) * 113.0); - let candidate_red = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 2.0).sin(); - let candidate_green = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 3.0).sin(); - let candidate_blue = 0.5 + 0.5 * (seed * 2.5 + 3.5 + 2.0).sin(); + let hue = frac((nx + gx) * 0.173 + (ny + gy) * 0.379 + seed * 0.431); + let (candidate_red, candidate_green, candidate_blue) = pastel_spectrum(hue); let h = smoothstep(0.0, 1.0, 0.5 + 0.5 * (distance - d) / smoothness); let correction = h * (1.0 - h) * smoothness / (1.0 + 3.0 * smoothness); distance = lerp(distance, d, h) - correction; @@ -150,6 +149,32 @@ fn lerp(start: f32, end: f32, amount: f32) -> f32 { start + (end - start) * amount } +fn pastel_spectrum(hue: f32) -> (f32, f32, f32) { + hsv_to_rgb(hue, 0.52, 0.98) +} + +fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { + let h = frac(hue) * 6.0; + let c = value * saturation; + let x = c * (1.0 - ((h % 2.0) - 1.0).abs()); + let m = value - c; + let (r, g, b) = if h < 1.0 { + (c, x, 0.0) + } else if h < 2.0 { + (x, c, 0.0) + } else if h < 3.0 { + (0.0, c, x) + } else if h < 4.0 { + (0.0, x, c) + } else if h < 5.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + + (r + m, g + m, b + m) +} + fn color(r: f32, g: f32, b: f32) -> CultMathColor32 { CultMathColor32 { r: r.clamp(0.0, 255.0) as u8, diff --git a/src/CultMath/Voronoi.cs b/src/CultMath/Voronoi.cs index 52e6acb6..9791a8e0 100644 --- a/src/CultMath/Voronoi.cs +++ b/src/CultMath/Voronoi.cs @@ -109,15 +109,15 @@ public static class Voronoi var gain = tone switch { - CultMathTone.Panel => 0.25f, - CultMathTone.Edge => 0.95f, - _ => 0.16f, + CultMathTone.Panel => 0.42f, + CultMathTone.Edge => 1.05f, + _ => 0.24f, }; var lift = tone switch { - CultMathTone.Panel => 6.0f, - CultMathTone.Edge => 20.0f, - _ => 2.0f, + CultMathTone.Panel => 14.0f, + CultMathTone.Edge => 24.0f, + _ => 4.0f, }; return new Color32( (byte)Math.Clamp(lift + sample.x * 255.0f * gain, 0, 255), @@ -152,10 +152,8 @@ public static class Voronoi var d2 = math.abs(g - f + o); var d = Math.Max(d2.x, d2.y) * weight; var seed = Hash1(math.dot(n + g, new float2(7.0f, 113.0f))); - var candidate = 0.5f + 0.5f * new float3( - math.sin(seed * 2.5f + 3.5f + 2.0f), - math.sin(seed * 2.5f + 3.5f + 3.0f), - math.sin(seed * 2.5f + 3.5f + 2.0f)); + var hue = math.frac((n.x + g.x) * 0.173f + (n.y + g.y) * 0.379f + seed * 0.431f); + var candidate = PastelSpectrum(hue); var h = math.smoothstep(0.0f, 1.0f, 0.5f + 0.5f * (distance - d) / smoothness); var correction = h * (1.0f - h) * smoothness / (1.0f + 3.0f * smoothness); distance = math.lerp(distance, d, h) - correction; @@ -168,6 +166,46 @@ public static class Voronoi private static float Hash1(float value) => math.frac(math.sin(value) * 43758.5453f); + private static float3 PastelSpectrum(float hue) + { + return HsvToRgb(hue, 0.52f, 0.98f); + } + + private static float3 HsvToRgb(float hue, float saturation, float value) + { + var h = math.frac(hue) * 6.0f; + var c = value * saturation; + var x = c * (1.0f - Math.Abs(h % 2.0f - 1.0f)); + var m = value - c; + float3 rgb; + if (h < 1.0f) + { + rgb = new float3(c, x, 0.0f); + } + else if (h < 2.0f) + { + rgb = new float3(x, c, 0.0f); + } + else if (h < 3.0f) + { + rgb = new float3(0.0f, c, x); + } + else if (h < 4.0f) + { + rgb = new float3(0.0f, x, c); + } + else if (h < 5.0f) + { + rgb = new float3(x, 0.0f, c); + } + else + { + rgb = new float3(c, 0.0f, x); + } + + return rgb + m; + } + private static float2 Hash2(float2 value) { var p = new float2(math.dot(value, new float2(127.1f, 311.7f)), math.dot(value, new float2(269.5f, 183.3f))); From f535801cd0ceb273fabb96a9bd8ad508489ef28c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 10 Jun 2026 21:22:43 +0100 Subject: [PATCH 005/203] Promote shader math primitives --- README.md | 30 ++- docs/design.md | 9 +- shaders/CultMath.hlsl | 254 ++++++++++++++++++++++++ src/CultMath/CultMath.csproj | 1 + src/CultMath/math.cs | 202 +++++++++++++++++++ tests/CultMath.Tests/HlslMirrorTests.cs | 63 ++++++ tests/CultMath.Tests/MathTests.cs | 48 +++++ 7 files changed, 604 insertions(+), 3 deletions(-) create mode 100644 shaders/CultMath.hlsl create mode 100644 tests/CultMath.Tests/HlslMirrorTests.cs diff --git a/README.md b/README.md index b693d258..ef69df80 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,13 @@ shader semantics. - conversions to and from `System.Numerics` - `math` intrinsics: `radians`, `degrees`, `abs`, `floor`, `ceil`, `frac`, `min`, `max`, `clamp`, `saturate`, `lerp`, `step`, `smoothstep`, `dot`, - `cross`, `length`, `distance`, `normalize`, and `reflect` + `cross`, `length`, `distance`, `normalize`, `reflect`, `csum`, exponential + `decay`/`damp`, intercept/segment helpers, Catmull-Rom, Bezier curves, and + deterministic value-noise primitives +- `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, including CultMath's + safe `normalize` contract. - `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. @@ -38,8 +44,25 @@ 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 = cultmath_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 @@ -55,3 +78,8 @@ 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. diff --git a/docs/design.md b/docs/design.md index 3c87fb51..8250ba78 100644 --- a/docs/design.md +++ b/docs/design.md @@ -9,16 +9,21 @@ Rust hot kernels, C# host code, and HLSL shader code. - Keep Rust as the owner of native hot-path kernels and parity fixtures. - Keep C#, shader, and scripting surfaces as runtime wrappers, not competing math authorities. +- Keep `shaders/CultMath.hlsl` as the canonical shader mirror for shared + intrinsics. Project-local shader helpers should move here once more than one + organ needs them or once CPU/GPU parity matters. - Implement public shader semantics directly; do not copy Unity.Mathematics. - Prefer small immutable value types. - Prefer component-wise overloads over clever generic machinery until the API earns that complexity. -- Keep the first surface boring and exact. Noise, matrices, quaternions, and - packing helpers come later. +- Keep the surface boring and exact. Noise primitives are allowed when they are + source-grounded, shader-shaped, and shared by more than one project. Matrices, + quaternions, and packing helpers still need an owner before they enter. ## Non-Goals - Compiling C# to shaders. +- Generating HLSL from Rust or Rust from HLSL. - Replacing `System.Numerics` for general .NET work. - Recreating every Unity.Mathematics type because a spreadsheet somewhere says “coverage.” That way lies decorative obesity. diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl new file mode 100644 index 00000000..3cbb3fdd --- /dev/null +++ b/shaders/CultMath.hlsl @@ -0,0 +1,254 @@ +#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); +} + +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; +} + +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/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index 83f6fb9d..f43adce3 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -17,6 +17,7 @@ + diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index 6d44f900..817aa7fd 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -6,6 +6,16 @@ public static class math public const float TAU = MathF.PI * 2.0f; public const float HALF_PI = MathF.PI * 0.5f; + public static float2 float2(float x, float y) => new(x, y); + public static float2 float2(float value) => new(value, value); + public static float3 float3(float x, float y, float z) => new(x, y, z); + public static float3 float3(float2 xy, float z) => new(xy, z); + public static float3 float3(float value) => new(value, value, value); + public static float4 float4(float x, float y, float z, float w) => new(x, y, z, w); + public static float4 float4(float2 xy, float z, float w) => new(xy, z, w); + public static float4 float4(float3 xyz, float w) => new(xyz, w); + public static float4 float4(float value) => new(value, value, value, value); + public static float radians(float degrees) => degrees * (PI / 180.0f); public static float degrees(float radians) => radians * (180.0f / PI); public static float sin(float value) => MathF.Sin(value); @@ -16,6 +26,10 @@ public static class math public static float atan(float value) => MathF.Atan(value); public static float atan2(float y, float x) => MathF.Atan2(y, x); public static float sqrt(float value) => MathF.Sqrt(value); + public static float exp(float value) => MathF.Exp(value); + public static float2 exp(float2 value) => new(exp(value.x), exp(value.y)); + public static float3 exp(float3 value) => new(exp(value.x), exp(value.y), exp(value.z)); + public static float4 exp(float4 value) => new(exp(value.x), exp(value.y), exp(value.z), exp(value.w)); public static float abs(float value) => MathF.Abs(value); public static float2 abs(float2 value) => new(abs(value.x), abs(value.y)); @@ -119,4 +133,192 @@ public static class math public static float2 reflect(float2 incident, float2 normal) => incident - 2.0f * dot(normal, incident) * normal; public static float3 reflect(float3 incident, float3 normal) => incident - 2.0f * dot(normal, incident) * normal; + + public static float csum(float2 value) => value.x + value.y; + public static float csum(float3 value) => value.x + value.y + value.z; + public static float csum(float4 value) => value.x + value.y + value.z + value.w; + + public static float decay(float source, float lambda, float dt) => source * exp(-lambda * dt); + public static float2 decay(float2 source, float lambda, float dt) => source * exp(-lambda * dt); + public static float3 decay(float3 source, float lambda, float dt) => source * exp(-lambda * dt); + public static float4 decay(float4 source, float lambda, float dt) => source * exp(-lambda * dt); + public static float2 decay(float2 source, float2 lambda, float dt) => source * exp(-lambda * dt); + public static float3 decay(float3 source, float3 lambda, float dt) => source * exp(-lambda * dt); + public static float4 decay(float4 source, float4 lambda, float dt) => source * exp(-lambda * dt); + + public static float damp(float start, float end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt)); + public static float2 damp(float2 start, float2 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt)); + public static float3 damp(float3 start, float3 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt)); + public static float4 damp(float4 start, float4 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt)); + + public static float first_order_intercept_time(float shotSpeed, float3 targetRelativePosition, float3 targetRelativeVelocity) + { + var velocitySquared = lengthsq(targetRelativeVelocity); + if (velocitySquared < 0.001f) + { + return 0.0f; + } + + var a = velocitySquared - shotSpeed * shotSpeed; + if (abs(a) < 0.001f) + { + var time = -lengthsq(targetRelativePosition) / (2.0f * dot(targetRelativeVelocity, targetRelativePosition)); + return max(time, 0.0f); + } + + var b = 2.0f * dot(targetRelativeVelocity, targetRelativePosition); + var c = lengthsq(targetRelativePosition); + var determinant = b * b - 4.0f * a * c; + + if (determinant > 0.0f) + { + var root = sqrt(determinant); + var t1 = (-b + root) / (2.0f * a); + var t2 = (-b - root) / (2.0f * a); + if (t1 > 0.0f) + { + return t2 > 0.0f ? min(t1, t2) : t1; + } + + return max(t2, 0.0f); + } + + if (determinant < 0.0f) + { + return 0.0f; + } + + return max(-b / (2.0f * a), 0.0f); + } + + public static float3 first_order_intercept( + float3 shooterPosition, + float3 shooterVelocity, + float shotSpeed, + float3 targetPosition, + float3 targetVelocity) + { + var targetRelativePosition = targetPosition - shooterPosition; + var targetRelativeVelocity = targetVelocity - shooterVelocity; + var time = first_order_intercept_time(shotSpeed, targetRelativePosition, targetRelativeVelocity); + return targetPosition + time * targetRelativeVelocity; + } + + public static float distance_to_segment(float2 point, float2 start, float2 end, out float2 closest) + { + var segment = end - start; + var segmentLengthSq = lengthsq(segment); + if (segmentLengthSq <= 0.0f) + { + closest = start; + return distance(point, start); + } + + var t = saturate(dot(point - start, segment) / segmentLengthSq); + closest = start + segment * t; + return distance(point, closest); + } + + public static float catmullrom(float p0, float p1, float p2, float p3, float t) + { + var t2 = t * t; + var t3 = t2 * t; + return 0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + public static float2 catmullrom(float2 p0, float2 p1, float2 p2, float2 p3, float t) => + 0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t)); + + public static float3 catmullrom(float3 p0, float3 p1, float3 p2, float3 p3, float t) => + 0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t)); + + public static float4 catmullrom(float4 p0, float4 p1, float4 p2, float4 p3, float t) => + 0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t)); + + public static float quadratic_bezier(float p0, float p1, float p2, float t) + { + t = saturate(t); + var inv = 1.0f - t; + return inv * inv * p0 + 2.0f * inv * t * p1 + t * t * p2; + } + + public static float3 quadratic_bezier(float3 p0, float3 p1, float3 p2, float t) + { + t = saturate(t); + var inv = 1.0f - t; + return inv * inv * p0 + 2.0f * inv * t * p1 + t * t * p2; + } + + public static float cubic_bezier(float p0, float p1, float p2, float p3, float t) + { + t = saturate(t); + var inv = 1.0f - t; + return inv * inv * inv * p0 + 3.0f * inv * inv * t * p1 + 3.0f * inv * t * t * p2 + t * t * t * p3; + } + + public static float3 cubic_bezier(float3 p0, float3 p1, float3 p2, float3 p3, float t) + { + t = saturate(t); + var inv = 1.0f - t; + return inv * inv * inv * p0 + 3.0f * inv * inv * t * p1 + 3.0f * inv * t * t * p2 + t * t * t * p3; + } + + public static float smoothstep01(float value) => smoothstep(0.0f, 1.0f, value); + + public static float smootherstep(float value) + { + value = saturate(value); + return value * value * value * (value * (value * 6.0f - 15.0f) + 10.0f); + } + + public static float hash(float value) => frac(sin(value) * 43758.5453f); + public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f))); + public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f))); + + public static float value_noise(float2 position) + { + var cell = floor(position); + var local = frac(position); + var u = local * local * (3.0f - 2.0f * local); + + var a = hash(cell); + var b = hash(cell + new float2(1.0f, 0.0f)); + var c = hash(cell + new float2(0.0f, 1.0f)); + var d = hash(cell + new float2(1.0f, 1.0f)); + + return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y); + } + + public static float value_noise_bicubic(float2 position) + { + var cell = floor(position); + var local = frac(position); + + var y0 = catmullrom( + hash(cell + new float2(-1.0f, -1.0f)), + hash(cell + new float2(0.0f, -1.0f)), + hash(cell + new float2(1.0f, -1.0f)), + hash(cell + new float2(2.0f, -1.0f)), + local.x); + var y1 = catmullrom( + hash(cell + new float2(-1.0f, 0.0f)), + hash(cell + new float2(0.0f, 0.0f)), + hash(cell + new float2(1.0f, 0.0f)), + hash(cell + new float2(2.0f, 0.0f)), + local.x); + var y2 = catmullrom( + hash(cell + new float2(-1.0f, 1.0f)), + hash(cell + new float2(0.0f, 1.0f)), + hash(cell + new float2(1.0f, 1.0f)), + hash(cell + new float2(2.0f, 1.0f)), + local.x); + var y3 = catmullrom( + hash(cell + new float2(-1.0f, 2.0f)), + hash(cell + new float2(0.0f, 2.0f)), + hash(cell + new float2(1.0f, 2.0f)), + hash(cell + new float2(2.0f, 2.0f)), + local.x); + + return catmullrom(y0, y1, y2, y3, local.y); + } } diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs new file mode 100644 index 00000000..91a95f4d --- /dev/null +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -0,0 +1,63 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class HlslMirrorTests +{ + [Fact] + public void HlslMirrorPublishesCultMathPrimitives() + { + var include = File.ReadAllText(Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl")); + var requiredSymbols = new[] + { + "cultmath_radians", + "cultmath_degrees", + "cultmath_frac", + "cultmath_clamp", + "cultmath_saturate", + "cultmath_lerp", + "cultmath_step", + "cultmath_smoothstep", + "cultmath_smootherstep", + "cultmath_lengthsq", + "cultmath_distance", + "cultmath_normalize", + "cultmath_reflect", + "cultmath_csum", + "cultmath_decay", + "cultmath_damp", + "cultmath_catmullrom", + "cultmath_quadratic_bezier", + "cultmath_cubic_bezier", + "cultmath_hash", + "cultmath_value_noise", + "cultmath_value_noise_bicubic", + "cultmath_value_noise_texture", + "cultmath_value_noise_texture_bicubic", + }; + + foreach (var symbol in requiredSymbols) + { + Assert.Contains(symbol, include); + } + + Assert.Contains("CULTMATH_NORMALIZE_EPSILON", include); + Assert.Contains("max(length(value), CULTMATH_NORMALIZE_EPSILON)", include); + } + + private static string GetRepositoryRoot() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory is not null) + { + if (File.Exists(Path.Combine(directory.FullName, "CultMath.slnx"))) + { + return directory.FullName; + } + + directory = directory.Parent; + } + + throw new InvalidOperationException("Could not locate CultMath repository root."); + } +} diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 2e0cea93..18fa203a 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -23,6 +23,9 @@ public sealed class MathTests [Fact] public void VectorConstructorsAcceptLowerDimensionVectors() { + Assert.Equal(new float2(1.0f, 2.0f), math.float2(1.0f, 2.0f)); + Assert.Equal(new float3(1.0f, 1.0f, 1.0f), math.float3(1.0f)); + Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); Assert.Equal(new float3(1.0f, 2.0f, 3.0f), new float3(new float2(1.0f, 2.0f), 3.0f)); Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float2(1.0f, 2.0f), 3.0f, 4.0f)); Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); @@ -42,6 +45,7 @@ public sealed class MathTests Assert.Equal(0.0f, math.smoothstep(0.0f, 1.0f, -1.0f)); Assert.Equal(0.5f, math.smoothstep(0.0f, 1.0f, 0.5f), precision: 5); Assert.Equal(1.0f, math.smoothstep(0.0f, 1.0f, 2.0f)); + Assert.Equal(0.5f, math.smootherstep(0.5f), precision: 5); } [Fact] @@ -53,6 +57,50 @@ public sealed class MathTests Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5); } + [Fact] + public void AetheriaPrimitivesRemainSmallAndDeterministic() + { + Assert.Equal(6.0f, math.csum(new float3(1.0f, 2.0f, 3.0f))); + Assert.Equal(MathF.Exp(-2.0f), math.decay(1.0f, 2.0f, 1.0f), precision: 5); + Assert.Equal(1.0f - MathF.Exp(-2.0f), math.damp(0.0f, 1.0f, 2.0f, 1.0f), precision: 5); + + Assert.Equal(15.0f, math.catmullrom(0.0f, 10.0f, 20.0f, 30.0f, 0.5f), precision: 5); + Assert.Equal(0.25f, math.quadratic_bezier(0.0f, 0.0f, 1.0f, 0.5f), precision: 5); + Assert.Equal(0.5f, math.cubic_bezier(0.0f, 0.0f, 1.0f, 1.0f, 0.5f), precision: 5); + } + + [Fact] + public void SegmentDistanceReportsClosestPoint() + { + var distance = math.distance_to_segment(new float2(2.0f, 3.0f), new float2(0.0f, 0.0f), new float2(4.0f, 0.0f), out var closest); + + Assert.Equal(3.0f, distance, precision: 5); + Assert.Equal(new float2(2.0f, 0.0f), closest); + } + + [Fact] + public void FirstOrderInterceptSolvesSimpleClosingTarget() + { + var time = math.first_order_intercept_time( + 2.0f, + new float3(10.0f, 0.0f, 0.0f), + new float3(-1.0f, 0.0f, 0.0f)); + + Assert.Equal(10.0f / 3.0f, time, precision: 5); + } + + [Fact] + public void ValueNoiseIsDeterministicAndSmoothAtCellCorners() + { + var position = new float2(12.25f, -4.5f); + + Assert.Equal(math.value_noise(position), math.value_noise(position)); + Assert.Equal(math.hash(new float2(3.0f, 5.0f)), math.value_noise(new float2(3.0f, 5.0f)), precision: 5); + + var bicubic = math.value_noise_bicubic(position); + Assert.True(bicubic > -0.25f && bicubic < 1.25f); + } + [Fact] public void VoronoiBatchProducesToneMappedColors() { From 2c9f81fbf1abec2228569bb5d672aad76b5f1d8e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 10 Jun 2026 21:30:49 +0100 Subject: [PATCH 006/203] Add HLSL SPIR-V tool scripts --- .gitignore | 1 + README.md | 16 ++++++++++ tools/compile-hlsl-spirv.ps1 | 58 ++++++++++++++++++++++++++++++++++++ tools/get-dxc.ps1 | 42 ++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 tools/compile-hlsl-spirv.ps1 create mode 100644 tools/get-dxc.ps1 diff --git a/.gitignore b/.gitignore index 6eae4393..5ae20c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ obj/ **/obj/ target/ **/target/ +.tools/ diff --git a/README.md b/README.md index ef69df80..7627b773 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,22 @@ cargo test --manifest-path native/cultmath-core/Cargo.toml cargo build --release --manifest-path native/cultmath-core/Cargo.toml ``` +## Shader Tooling + +CultMath keeps portable DXC outside git under `.tools/`: + +```powershell +.\tools\get-dxc.ps1 +.\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\CultMath\shaders +``` + +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. + ## Doctrine The goal is mechanical sympathy between CPU-side authoring, native kernels, and diff --git a/tools/compile-hlsl-spirv.ps1 b/tools/compile-hlsl-spirv.ps1 new file mode 100644 index 00000000..98270cb0 --- /dev/null +++ b/tools/compile-hlsl-spirv.ps1 @@ -0,0 +1,58 @@ +param( + [Parameter(Mandatory = $true)] + [string]$ShaderPath, + + [Parameter(Mandatory = $true)] + [string]$OutputPath, + + [string]$EntryPoint = "main", + [string]$Profile = "cs_6_0", + [string[]]$IncludePath = @(), + [string]$TargetEnv = "", + [string]$DxcPath = "" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +if (-not $DxcPath) { + $DxcPath = Join-Path $repoRoot ".tools\dxc\current\bin\x64\dxc.exe" +} + +if (-not (Test-Path $DxcPath)) { + throw "DXC not found at '$DxcPath'. Run tools\get-dxc.ps1 first or pass -DxcPath." +} + +$resolvedShader = Resolve-Path $ShaderPath +$resolvedIncludes = @((Join-Path $repoRoot "shaders")) +foreach ($path in $IncludePath) { + $resolvedIncludes += (Resolve-Path $path) +} + +$outputDirectory = Split-Path -Parent $OutputPath +if ($outputDirectory) { + New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null +} + +$arguments = @( + "-T", $Profile, + "-E", $EntryPoint, + "-spirv" +) + +if ($TargetEnv) { + $arguments += "-fspv-target-env=$TargetEnv" +} + +foreach ($include in $resolvedIncludes) { + $arguments += @("-I", $include) +} + +$arguments += @($resolvedShader, "-Fo", $OutputPath) + +& $DxcPath @arguments +if ($LASTEXITCODE -ne 0) { + throw "dxc.exe failed with exit code $LASTEXITCODE." +} + +Get-Item -LiteralPath $OutputPath diff --git a/tools/get-dxc.ps1 b/tools/get-dxc.ps1 new file mode 100644 index 00000000..2f3363f3 --- /dev/null +++ b/tools/get-dxc.ps1 @@ -0,0 +1,42 @@ +param( + [string]$InstallRoot = ".tools\dxc" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$installPath = Join-Path $repoRoot $InstallRoot +New-Item -ItemType Directory -Force -Path $installPath | Out-Null + +$release = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest" ` + -Headers @{ "User-Agent" = "CultMath-DXC-Bootstrap" } + +$asset = $release.assets | + Where-Object { $_.name -like "dxc_*.zip" } | + Select-Object -First 1 + +if (-not $asset) { + throw "No Windows dxc_*.zip asset found on the latest DirectXShaderCompiler release." +} + +$zipPath = Join-Path $installPath $asset.name +Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue + +& curl.exe -L --retry 5 --retry-delay 2 --fail -o $zipPath $asset.browser_download_url +if ($LASTEXITCODE -ne 0) { + throw "curl.exe failed with exit code $LASTEXITCODE." +} + +$currentPath = Join-Path $installPath "current" +Remove-Item -LiteralPath $currentPath -Recurse -Force -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Force -Path $currentPath | Out-Null +Expand-Archive -LiteralPath $zipPath -DestinationPath $currentPath -Force + +$dxcPath = Join-Path $currentPath "bin\x64\dxc.exe" +if (-not (Test-Path $dxcPath)) { + throw "DXC x64 executable was not found after extraction." +} + +& $dxcPath --version +Write-Host "DXC installed at $dxcPath" From 788a75dba537be7b475e3b240a5189363272eb1d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 11 Jun 2026 06:57:22 +0100 Subject: [PATCH 007/203] Add SPIR-V to Metal tooling --- README.md | 9 +++++++++ tools/compile-hlsl-spirv.ps1 | 11 +++++++++++ tools/compile-spirv-metal.ps1 | 34 ++++++++++++++++++++++++++++++++++ tools/get-spirv-cross.ps1 | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 tools/compile-spirv-metal.ps1 create mode 100644 tools/get-spirv-cross.ps1 diff --git a/README.md b/README.md index 7627b773..d6623e46 100644 --- a/README.md +++ b/README.md @@ -78,15 +78,24 @@ CultMath keeps portable DXC outside git under `.tools/`: ```powershell .\tools\get-dxc.ps1 +.\tools\get-spirv-cross.ps1 .\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\CultMath\shaders +.\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 ``` 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. ## Doctrine diff --git a/tools/compile-hlsl-spirv.ps1 b/tools/compile-hlsl-spirv.ps1 index 98270cb0..280e08d5 100644 --- a/tools/compile-hlsl-spirv.ps1 +++ b/tools/compile-hlsl-spirv.ps1 @@ -9,6 +9,10 @@ param( [string]$Profile = "cs_6_0", [string[]]$IncludePath = @(), [string]$TargetEnv = "", + [int]$VulkanCBufferShift = 0, + [int]$VulkanTextureShift = 10, + [int]$VulkanUavShift = 20, + [int]$VulkanSamplerShift = 30, [string]$DxcPath = "" ) @@ -44,6 +48,13 @@ if ($TargetEnv) { $arguments += "-fspv-target-env=$TargetEnv" } +$arguments += @( + "-fvk-b-shift", $VulkanCBufferShift.ToString(), "0", + "-fvk-t-shift", $VulkanTextureShift.ToString(), "0", + "-fvk-u-shift", $VulkanUavShift.ToString(), "0", + "-fvk-s-shift", $VulkanSamplerShift.ToString(), "0" +) + foreach ($include in $resolvedIncludes) { $arguments += @("-I", $include) } diff --git a/tools/compile-spirv-metal.ps1 b/tools/compile-spirv-metal.ps1 new file mode 100644 index 00000000..490e6331 --- /dev/null +++ b/tools/compile-spirv-metal.ps1 @@ -0,0 +1,34 @@ +param( + [Parameter(Mandatory = $true)] + [string]$SpirvPath, + + [Parameter(Mandatory = $true)] + [string]$OutputPath, + + [int]$MslVersion = 23000, + [string]$SpirvCrossPath = "" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +if (-not $SpirvCrossPath) { + $SpirvCrossPath = Join-Path $repoRoot ".tools\spirv-cross\build\spirv-cross.exe" +} + +if (-not (Test-Path $SpirvCrossPath)) { + throw "SPIRV-Cross not found at '$SpirvCrossPath'. Run tools\get-spirv-cross.ps1 first or pass -SpirvCrossPath." +} + +$resolvedSpirv = Resolve-Path $SpirvPath +$outputDirectory = Split-Path -Parent $OutputPath +if ($outputDirectory) { + New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null +} + +& $SpirvCrossPath $resolvedSpirv --msl --msl-version $MslVersion --output $OutputPath +if ($LASTEXITCODE -ne 0) { + throw "spirv-cross failed with exit code $LASTEXITCODE." +} + +Get-Item -LiteralPath $OutputPath diff --git a/tools/get-spirv-cross.ps1 b/tools/get-spirv-cross.ps1 new file mode 100644 index 00000000..f5105b9a --- /dev/null +++ b/tools/get-spirv-cross.ps1 @@ -0,0 +1,34 @@ +param( + [string]$InstallRoot = ".tools\spirv-cross" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$installPath = Join-Path $repoRoot $InstallRoot +$sourcePath = Join-Path $installPath "src" +$buildPath = Join-Path $installPath "build" + +New-Item -ItemType Directory -Force -Path $installPath | Out-Null + +if (-not (Test-Path $sourcePath)) { + git clone --depth 1 https://github.com/KhronosGroup/SPIRV-Cross.git $sourcePath +} + +cmake ` + -S $sourcePath ` + -B $buildPath ` + -G Ninja ` + -DSPIRV_CROSS_CLI=ON ` + -DSPIRV_CROSS_ENABLE_TESTS=OFF ` + -DSPIRV_CROSS_ENABLE_C_API=OFF + +cmake --build $buildPath --target spirv-cross --config Release + +$spirvCrossPath = Join-Path $buildPath "spirv-cross.exe" +if (-not (Test-Path $spirvCrossPath)) { + throw "SPIRV-Cross executable was not found after build." +} + +& $spirvCrossPath --version +Write-Host "SPIRV-Cross installed at $spirvCrossPath" From 26d9996d7b41e9fae09ba47976137e58259a4be9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 17 Jun 2026 02:53:02 +0100 Subject: [PATCH 008/203] Add SIMD SoA batch math kernels --- README.md | 3 + src/CultMath/BatchMath.cs | 147 ++++++++++++++++++++++++++++++ tests/CultMath.Tests/MathTests.cs | 37 ++++++++ 3 files changed, 187 insertions(+) create mode 100644 src/CultMath/BatchMath.cs diff --git a/README.md b/README.md index d6623e46..1050e4ec 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ shader semantics. - scalar-to-vector splats - swizzles for common lanes - conversions to and from `System.Numerics` +- `BatchMath` SoA kernels over `Span` / `ReadOnlySpan` using + `System.Numerics.Vector` 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`, exponential diff --git a/src/CultMath/BatchMath.cs b/src/CultMath/BatchMath.cs new file mode 100644 index 00000000..df62e07c --- /dev/null +++ b/src/CultMath/BatchMath.cs @@ -0,0 +1,147 @@ +using System.Numerics; + +namespace CultMath; + +public static class BatchMath +{ + private const float MinimumDistanceSquared = 1.0e-8f; + + public static int LaneCount => Vector.Count; + + public static bool IsHardwareAccelerated => Vector.IsHardwareAccelerated; + + public static void Clear(Span values) => values.Clear(); + + public static void AddRadialFalloffAcceleration2D( + ReadOnlySpan positionX, + ReadOnlySpan positionY, + float centerX, + float centerY, + float strength, + float radius, + Span accelerationX, + Span accelerationY) + { + ValidateEqualLengths(positionX.Length, positionY.Length, accelerationX.Length, accelerationY.Length); + + if (radius <= 0.0f || strength == 0.0f || positionX.Length == 0) + { + return; + } + + var radiusVector = new Vector(radius); + var centerXVector = new Vector(centerX); + var centerYVector = new Vector(centerY); + var strengthVector = new Vector(strength); + var one = Vector.One; + var zero = Vector.Zero; + var minimumDistanceSquared = new Vector(MinimumDistanceSquared); + + var i = 0; + var lanes = Vector.Count; + for (; i <= positionX.Length - lanes; i += lanes) + { + var px = new Vector(positionX.Slice(i, lanes)); + var py = new Vector(positionY.Slice(i, lanes)); + var dx = centerXVector - px; + var dy = centerYVector - py; + var distanceSquared = Vector.Max(dx * dx + dy * dy, minimumDistanceSquared); + var distance = Vector.SquareRoot(distanceSquared); + var inRange = Vector.LessThanOrEqual(distance, radiusVector); + var falloff = Vector.Max(zero, one - distance / radiusVector); + var magnitude = strengthVector * falloff; + var ax = Vector.ConditionalSelect(inRange, dx / distance * magnitude, zero); + var ay = Vector.ConditionalSelect(inRange, dy / distance * magnitude, zero); + + (new Vector(accelerationX.Slice(i, lanes)) + ax).CopyTo(accelerationX.Slice(i, lanes)); + (new Vector(accelerationY.Slice(i, lanes)) + ay).CopyTo(accelerationY.Slice(i, lanes)); + } + + for (; i < positionX.Length; i++) + { + var dx = centerX - positionX[i]; + var dy = centerY - positionY[i]; + var distanceSquared = MathF.Max(dx * dx + dy * dy, MinimumDistanceSquared); + var distance = MathF.Sqrt(distanceSquared); + if (distance > radius) + { + continue; + } + + var falloff = MathF.Max(0.0f, 1.0f - distance / radius); + var magnitude = strength * falloff; + accelerationX[i] += dx / distance * magnitude; + accelerationY[i] += dy / distance * magnitude; + } + } + + public static void IntegrateSemiImplicitEuler2D( + float deltaTime, + ReadOnlySpan dynamicMask, + Span positionX, + Span positionY, + Span velocityX, + Span velocityY, + ReadOnlySpan accelerationX, + ReadOnlySpan accelerationY) + { + if (deltaTime <= 0.0f) + { + throw new ArgumentOutOfRangeException(nameof(deltaTime), "Delta time must be positive."); + } + + ValidateEqualLengths( + dynamicMask.Length, + positionX.Length, + positionY.Length, + velocityX.Length, + velocityY.Length, + accelerationX.Length, + accelerationY.Length); + + var delta = new Vector(deltaTime); + var i = 0; + var lanes = Vector.Count; + for (; i <= positionX.Length - lanes; i += lanes) + { + var mask = new Vector(dynamicMask.Slice(i, lanes)); + var vx = new Vector(velocityX.Slice(i, lanes)); + var vy = new Vector(velocityY.Slice(i, lanes)); + var px = new Vector(positionX.Slice(i, lanes)); + var py = new Vector(positionY.Slice(i, lanes)); + var ax = new Vector(accelerationX.Slice(i, lanes)); + var ay = new Vector(accelerationY.Slice(i, lanes)); + + vx += ax * delta * mask; + vy += ay * delta * mask; + px += vx * delta * mask; + py += vy * delta * mask; + + vx.CopyTo(velocityX.Slice(i, lanes)); + vy.CopyTo(velocityY.Slice(i, lanes)); + px.CopyTo(positionX.Slice(i, lanes)); + py.CopyTo(positionY.Slice(i, lanes)); + } + + for (; i < positionX.Length; i++) + { + var mask = dynamicMask[i]; + velocityX[i] += accelerationX[i] * deltaTime * mask; + velocityY[i] += accelerationY[i] * deltaTime * mask; + positionX[i] += velocityX[i] * deltaTime * mask; + positionY[i] += velocityY[i] * deltaTime * mask; + } + } + + private static void ValidateEqualLengths(params int[] lengths) + { + var length = lengths[0]; + for (var i = 1; i < lengths.Length; i++) + { + if (lengths[i] != length) + { + throw new ArgumentException("CultMath batch spans must have equal length."); + } + } + } +} diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 18fa203a..44dd1290 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -116,4 +116,41 @@ public sealed class MathTests Assert.True(colors[1].r > colors[1].g); Assert.True(colors[2].r > 120 && colors[2].g > 120 && colors[2].b > 120); } + + [Fact] + public void BatchRadialFalloffAccelerationMatchesScalarContract() + { + var xs = new[] { 0.0f, 5.0f, 10.0f }; + var ys = new[] { 0.0f, 0.0f, 0.0f }; + var ax = new float[xs.Length]; + var ay = new float[xs.Length]; + + BatchMath.AddRadialFalloffAcceleration2D(xs, ys, 10.0f, 0.0f, 10.0f, 20.0f, ax, ay); + + Assert.True(BatchMath.LaneCount >= 4); + Assert.Equal(5.0f, ax[0], precision: 5); + Assert.Equal(7.5f, ax[1], precision: 5); + Assert.Equal(0.0f, ax[2], precision: 5); + Assert.Equal(0.0f, ay[0], precision: 5); + } + + [Fact] + public void BatchEulerIntegrationHonorsDynamicMask() + { + var dynamicMask = new[] { 1.0f, 0.0f }; + var px = new[] { 0.0f, 0.0f }; + var py = new[] { 0.0f, 0.0f }; + var vx = new[] { 1.0f, 1.0f }; + var vy = new[] { 0.0f, 0.0f }; + var ax = new[] { 2.0f, 2.0f }; + var ay = new[] { 0.0f, 4.0f }; + + BatchMath.IntegrateSemiImplicitEuler2D(0.5f, dynamicMask, px, py, vx, vy, ax, ay); + + Assert.Equal(2.0f, vx[0], precision: 5); + Assert.Equal(1.0f, px[0], precision: 5); + Assert.Equal(1.0f, vx[1], precision: 5); + Assert.Equal(0.0f, px[1], precision: 5); + Assert.Equal(0.0f, py[1], precision: 5); + } } From ab6a2c6456ec4b48311857daf24494f76cd1ca78 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 19:33:59 +0100 Subject: [PATCH 009/203] Complete portable CultMath runtime surface --- CultMath.slnx.meta | 7 + Directory.Build.props | 6 + Directory.Build.props.meta | 7 + README.md.meta | 7 + docs.meta | 8 ++ docs/design.md.meta | 7 + global.json.meta | 7 + native.meta | 8 ++ native/cultmath-core.meta | 8 ++ native/cultmath-core/Cargo.lock.meta | 7 + native/cultmath-core/Cargo.toml.meta | 7 + native/cultmath-core/src.meta | 8 ++ native/cultmath-core/src/lib.rs.meta | 7 + package.json | 14 ++ package.json.meta | 7 + shaders.meta | 8 ++ shaders/CultMath.hlsl | 84 +++++++++++ shaders/CultMath.hlsl.meta | 7 + src.meta | 8 ++ src/CultMath.meta | 8 ++ src/CultMath/BatchMath.cs | 128 +++++++++++++++-- src/CultMath/BatchMath.cs.meta | 2 + src/CultMath/Color32.cs.meta | 2 + src/CultMath/CultMath.asmdef | 14 ++ src/CultMath/CultMath.asmdef.meta | 7 + src/CultMath/CultMath.csproj | 3 +- src/CultMath/CultMath.csproj.meta | 7 + src/CultMath/CultMathTone.cs.meta | 2 + src/CultMath/IsExternalInit.cs | 5 + src/CultMath/IsExternalInit.cs.meta | 2 + src/CultMath/Random.cs | 62 ++++++++ src/CultMath/Random.cs.meta | 2 + src/CultMath/SphericalErosion.cs | 108 ++++++++++++++ src/CultMath/Voronoi.cs | 1 + src/CultMath/Voronoi.cs.meta | 2 + src/CultMath/bool2.cs | 18 +++ src/CultMath/bool2.cs.meta | 2 + src/CultMath/double2.cs | 25 ++++ src/CultMath/double2.cs.meta | 2 + src/CultMath/double3.cs | 36 +++++ src/CultMath/double3.cs.meta | 2 + src/CultMath/float2.cs | 4 +- src/CultMath/float2.cs.meta | 2 + src/CultMath/float3.cs | 49 ++++++- src/CultMath/float3.cs.meta | 2 + src/CultMath/float4.cs | 21 ++- src/CultMath/float4.cs.meta | 2 + src/CultMath/int2.cs | 29 ++++ src/CultMath/int2.cs.meta | 2 + src/CultMath/math.cs | 52 +++++++ src/CultMath/math.cs.meta | 2 + src/CultMath/quaternion.cs | 68 +++++++++ src/CultMath/quaternion.cs.meta | 2 + src/CultMath/rect.cs | 21 +++ src/CultMath/rect.cs.meta | 2 + tests.meta | 8 ++ tests/CultMath.Tests.meta | 8 ++ .../CultMath.Tests/CultMath.Tests.csproj.meta | 7 + tests/CultMath.Tests/HlslMirrorTests.cs | 9 ++ tests/CultMath.Tests/HlslMirrorTests.cs.meta | 2 + tests/CultMath.Tests/MathTests.cs | 134 ++++++++++++++++++ tests/CultMath.Tests/MathTests.cs.meta | 2 + tests/CultMath.Tests/SphericalErosionTests.cs | 27 ++++ tools.meta | 8 ++ tools/compile-hlsl-spirv.ps1.meta | 7 + tools/compile-spirv-metal.ps1.meta | 7 + tools/get-dxc.ps1.meta | 7 + tools/get-spirv-cross.ps1.meta | 7 + 68 files changed, 1130 insertions(+), 23 deletions(-) create mode 100644 CultMath.slnx.meta create mode 100644 Directory.Build.props create mode 100644 Directory.Build.props.meta create mode 100644 README.md.meta create mode 100644 docs.meta create mode 100644 docs/design.md.meta create mode 100644 global.json.meta create mode 100644 native.meta create mode 100644 native/cultmath-core.meta create mode 100644 native/cultmath-core/Cargo.lock.meta create mode 100644 native/cultmath-core/Cargo.toml.meta create mode 100644 native/cultmath-core/src.meta create mode 100644 native/cultmath-core/src/lib.rs.meta create mode 100644 package.json create mode 100644 package.json.meta create mode 100644 shaders.meta create mode 100644 shaders/CultMath.hlsl.meta create mode 100644 src.meta create mode 100644 src/CultMath.meta create mode 100644 src/CultMath/BatchMath.cs.meta create mode 100644 src/CultMath/Color32.cs.meta create mode 100644 src/CultMath/CultMath.asmdef create mode 100644 src/CultMath/CultMath.asmdef.meta create mode 100644 src/CultMath/CultMath.csproj.meta create mode 100644 src/CultMath/CultMathTone.cs.meta create mode 100644 src/CultMath/IsExternalInit.cs create mode 100644 src/CultMath/IsExternalInit.cs.meta create mode 100644 src/CultMath/Random.cs create mode 100644 src/CultMath/Random.cs.meta create mode 100644 src/CultMath/SphericalErosion.cs create mode 100644 src/CultMath/Voronoi.cs.meta create mode 100644 src/CultMath/bool2.cs create mode 100644 src/CultMath/bool2.cs.meta create mode 100644 src/CultMath/double2.cs create mode 100644 src/CultMath/double2.cs.meta create mode 100644 src/CultMath/double3.cs create mode 100644 src/CultMath/double3.cs.meta create mode 100644 src/CultMath/float2.cs.meta create mode 100644 src/CultMath/float3.cs.meta create mode 100644 src/CultMath/float4.cs.meta create mode 100644 src/CultMath/int2.cs create mode 100644 src/CultMath/int2.cs.meta create mode 100644 src/CultMath/math.cs.meta create mode 100644 src/CultMath/quaternion.cs create mode 100644 src/CultMath/quaternion.cs.meta create mode 100644 src/CultMath/rect.cs create mode 100644 src/CultMath/rect.cs.meta create mode 100644 tests.meta create mode 100644 tests/CultMath.Tests.meta create mode 100644 tests/CultMath.Tests/CultMath.Tests.csproj.meta create mode 100644 tests/CultMath.Tests/HlslMirrorTests.cs.meta create mode 100644 tests/CultMath.Tests/MathTests.cs.meta create mode 100644 tests/CultMath.Tests/SphericalErosionTests.cs create mode 100644 tools.meta create mode 100644 tools/compile-hlsl-spirv.ps1.meta create mode 100644 tools/compile-spirv-metal.ps1.meta create mode 100644 tools/get-dxc.ps1.meta create mode 100644 tools/get-spirv-cross.ps1.meta diff --git a/CultMath.slnx.meta b/CultMath.slnx.meta new file mode 100644 index 00000000..a33f7974 --- /dev/null +++ b/CultMath.slnx.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d310c959fd7a1d24a981ea6766ee3c59 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..4df13bd3 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + $(MSBuildThisFileDirectory).tools\dotnet\bin\$(MSBuildProjectName)\ + $(MSBuildThisFileDirectory).tools\dotnet\obj\$(MSBuildProjectName)\ + + diff --git a/Directory.Build.props.meta b/Directory.Build.props.meta new file mode 100644 index 00000000..f41d119d --- /dev/null +++ b/Directory.Build.props.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e041df73afea0094cae9b8d5dcc5e4eb +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 00000000..4411da72 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 867daebafe0ce48479cf043461ee6bdf +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/docs.meta b/docs.meta new file mode 100644 index 00000000..ee5fab29 --- /dev/null +++ b/docs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: de535b89253410b41b5989f712b80af4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/docs/design.md.meta b/docs/design.md.meta new file mode 100644 index 00000000..0d31ca6a --- /dev/null +++ b/docs/design.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 07021a8be816e3448a9490c10845b0aa +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/global.json.meta b/global.json.meta new file mode 100644 index 00000000..6c2c0eec --- /dev/null +++ b/global.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 28e4ce693c46cbd4b990c5790745d62f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native.meta b/native.meta new file mode 100644 index 00000000..f4135336 --- /dev/null +++ b/native.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9d1cb2ac88c530b4d9da456523115e12 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native/cultmath-core.meta b/native/cultmath-core.meta new file mode 100644 index 00000000..48a2ef76 --- /dev/null +++ b/native/cultmath-core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a42d65478346c354c8b4f353c5581b49 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native/cultmath-core/Cargo.lock.meta b/native/cultmath-core/Cargo.lock.meta new file mode 100644 index 00000000..9ca3ec60 --- /dev/null +++ b/native/cultmath-core/Cargo.lock.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f1c365f7eead2684ba621124e653bdab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native/cultmath-core/Cargo.toml.meta b/native/cultmath-core/Cargo.toml.meta new file mode 100644 index 00000000..8875de09 --- /dev/null +++ b/native/cultmath-core/Cargo.toml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cdd2d4eab053b764b9a06d36b080e43e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native/cultmath-core/src.meta b/native/cultmath-core/src.meta new file mode 100644 index 00000000..8e5232db --- /dev/null +++ b/native/cultmath-core/src.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7de448b7e258d2c448bb7391051cc628 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/native/cultmath-core/src/lib.rs.meta b/native/cultmath-core/src/lib.rs.meta new file mode 100644 index 00000000..2fa8aee8 --- /dev/null +++ b/native/cultmath-core/src/lib.rs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b06c69c432a633f4584830d6b28d4473 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 00000000..6da2a17a --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "org.gamecult.cultmath", + "displayName": "CultMath", + "version": "0.1.0", + "unity": "2021.3", + "description": "Clean-room C# math primitives shaped for HLSL-style renderer and simulation code.", + "keywords": [ + "GameCult", + "Math", + "Simulation" + ], + "license": "MPL-2.0", + "category": "Scripting" +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 00000000..23ed71cf --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1abf066549a826c4ba7a30d6676735c0 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/shaders.meta b/shaders.meta new file mode 100644 index 00000000..46f9d445 --- /dev/null +++ b/shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3bed74ebcba445543800218dbe185034 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index 3cbb3fdd..07b2b8a9 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -95,6 +95,18 @@ 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; } @@ -251,4 +263,76 @@ float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, Sampl return cultmath_catmullrom(row0, row1, row2, row3, local.y); } +struct CultMathSphericalErosionParameters +{ + float frequency; float amplitude; float lacunarity; float gain; + float slope_strength; float detail; float seed; int octaves; +}; + +struct CultMathSphericalErosionSample +{ + float height_offset; float ridge; float gully; float3 gradient; +}; + +float cultmath_spherical_cell_wave(float3 position, float3 flow, float3 cell, float seed) +{ + float3 pivot = cell + float3(cultmath_hash(cell + seed + 11.7), cultmath_hash(cell.yzx + seed + 37.1), cultmath_hash(cell.zyx + seed + 73.9)); + return dot(position - pivot, flow) * 6.28318530718 + cultmath_hash(cell + seed + 101.3) * 6.28318530718; +} + +float cultmath_spherical_directional_wave(float3 position, float3 flow, float seed) +{ + float3 cell = floor(position); + float3 local = cultmath_frac(position); + float3 weights = local * local * (3.0 - 2.0 * local); + float z0 = cultmath_lerp( + cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,0), seed), weights.x), + cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,0), seed), weights.x), weights.y); + float z1 = cultmath_lerp( + cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,1), seed), weights.x), + cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,1), seed), weights.x), weights.y); + return cultmath_lerp(z0, z1, weights.z); +} + +float3 cultmath_spherical_stable_tangent(float3 position) +{ + float3 axis = abs(position.z) < 0.8 ? float3(0,0,1) : float3(0,1,0); + return cultmath_normalize(cross(axis, position)); +} + +CultMathSphericalErosionSample cultmath_spherical_erosion(float3 unit_position, float base_height, float3 base_gradient, CultMathSphericalErosionParameters p) +{ + float3 position = cultmath_normalize(unit_position); + float3 gradient = base_gradient - position * dot(base_gradient, position); + float slope = length(gradient); + float3 flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position); + float frequency = max(p.frequency, 0.001); + float amplitude = p.amplitude; + float combi_mask = 1.0 - pow(1.0 - cultmath_saturate(slope * p.slope_strength), 2.0); + float fade_target = cultmath_saturate(base_height * 0.5 + 0.5) * 2.0 - 1.0; + CultMathSphericalErosionSample result = (CultMathSphericalErosionSample)0; + [loop] for (int octave = 0; octave < min(max(p.octaves, 0), 8); octave++) + { + float phase = cultmath_spherical_directional_wave(position * frequency, flow, p.seed + octave * 19.19); + float wave = cos(phase); + float wave_slope = sin(phase); + float faded = cultmath_lerp(fade_target, wave, combi_mask); + result.height_offset += faded * amplitude; + result.ridge = max(result.ridge, cultmath_saturate(faded)); + result.gully = max(result.gully, cultmath_saturate(-faded)); + float3 across = cultmath_normalize(cross(position, flow)); + gradient += across * (sign(wave_slope) * amplitude * frequency * combi_mask); + gradient -= position * dot(gradient, position); + slope = length(gradient); + flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position); + float new_mask = 1.0 - pow(1.0 - cultmath_saturate(abs(wave_slope) * 1.35), 2.0); + combi_mask = (1.0 - pow(1.0 - cultmath_saturate(combi_mask), max(p.detail, 0.001))) * new_mask; + fade_target = faded; + frequency *= max(p.lacunarity, 1.01); + amplitude *= cultmath_saturate(p.gain); + } + result.gradient = gradient; + return result; +} + #endif diff --git a/shaders/CultMath.hlsl.meta b/shaders/CultMath.hlsl.meta new file mode 100644 index 00000000..d6b8542e --- /dev/null +++ b/shaders/CultMath.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 33174674795143c4a905408718f956f8 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src.meta b/src.meta new file mode 100644 index 00000000..3e88fcb9 --- /dev/null +++ b/src.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 64f48d0cae0810e4ba9903e9094fe4c6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/CultMath.meta b/src/CultMath.meta new file mode 100644 index 00000000..381b726d --- /dev/null +++ b/src/CultMath.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e857b457b93397d4fb4ad8481cb72e4c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/CultMath/BatchMath.cs b/src/CultMath/BatchMath.cs index df62e07c..04c042d9 100644 --- a/src/CultMath/BatchMath.cs +++ b/src/CultMath/BatchMath.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; namespace CultMath; @@ -39,10 +40,19 @@ public static class BatchMath var i = 0; var lanes = Vector.Count; + var pxBuffer = new float[lanes]; + var pyBuffer = new float[lanes]; + var axBuffer = new float[lanes]; + var ayBuffer = new float[lanes]; for (; i <= positionX.Length - lanes; i += lanes) { - var px = new Vector(positionX.Slice(i, lanes)); - var py = new Vector(positionY.Slice(i, lanes)); + CopySpanToArray(positionX.Slice(i, lanes), pxBuffer); + CopySpanToArray(positionY.Slice(i, lanes), pyBuffer); + CopySpanToArray(accelerationX.Slice(i, lanes), axBuffer); + CopySpanToArray(accelerationY.Slice(i, lanes), ayBuffer); + + var px = new Vector(pxBuffer); + var py = new Vector(pyBuffer); var dx = centerXVector - px; var dy = centerYVector - py; var distanceSquared = Vector.Max(dx * dx + dy * dy, minimumDistanceSquared); @@ -53,8 +63,10 @@ public static class BatchMath var ax = Vector.ConditionalSelect(inRange, dx / distance * magnitude, zero); var ay = Vector.ConditionalSelect(inRange, dy / distance * magnitude, zero); - (new Vector(accelerationX.Slice(i, lanes)) + ax).CopyTo(accelerationX.Slice(i, lanes)); - (new Vector(accelerationY.Slice(i, lanes)) + ay).CopyTo(accelerationY.Slice(i, lanes)); + (new Vector(axBuffer) + ax).CopyTo(axBuffer); + (new Vector(ayBuffer) + ay).CopyTo(ayBuffer); + CopyArrayToSpan(axBuffer, accelerationX.Slice(i, lanes)); + CopyArrayToSpan(ayBuffer, accelerationY.Slice(i, lanes)); } for (; i < positionX.Length; i++) @@ -75,6 +87,35 @@ public static class BatchMath } } + public static void AddRadialFalloffAcceleration2D( + ReadOnlySpan positions, + float2 center, + float strength, + float radius, + Span acceleration) + { + ValidateEqualLengths(positions.Length, acceleration.Length); + + if (radius <= 0.0f || strength == 0.0f || positions.Length == 0) + { + return; + } + + for (var i = 0; i < positions.Length; i++) + { + var delta = center - positions[i]; + var distanceSquared = MathF.Max(delta.x * delta.x + delta.y * delta.y, MinimumDistanceSquared); + var distance = MathF.Sqrt(distanceSquared); + if (distance > radius) + { + continue; + } + + var falloff = MathF.Max(0.0f, 1.0f - distance / radius); + acceleration[i] += delta / distance * (strength * falloff); + } + } + public static void IntegrateSemiImplicitEuler2D( float deltaTime, ReadOnlySpan dynamicMask, @@ -102,25 +143,44 @@ public static class BatchMath var delta = new Vector(deltaTime); var i = 0; var lanes = Vector.Count; + var maskBuffer = new float[lanes]; + var pxBuffer = new float[lanes]; + var pyBuffer = new float[lanes]; + var vxBuffer = new float[lanes]; + var vyBuffer = new float[lanes]; + var axBuffer = new float[lanes]; + var ayBuffer = new float[lanes]; for (; i <= positionX.Length - lanes; i += lanes) { - var mask = new Vector(dynamicMask.Slice(i, lanes)); - var vx = new Vector(velocityX.Slice(i, lanes)); - var vy = new Vector(velocityY.Slice(i, lanes)); - var px = new Vector(positionX.Slice(i, lanes)); - var py = new Vector(positionY.Slice(i, lanes)); - var ax = new Vector(accelerationX.Slice(i, lanes)); - var ay = new Vector(accelerationY.Slice(i, lanes)); + CopySpanToArray(dynamicMask.Slice(i, lanes), maskBuffer); + CopySpanToArray(velocityX.Slice(i, lanes), vxBuffer); + CopySpanToArray(velocityY.Slice(i, lanes), vyBuffer); + CopySpanToArray(positionX.Slice(i, lanes), pxBuffer); + CopySpanToArray(positionY.Slice(i, lanes), pyBuffer); + CopySpanToArray(accelerationX.Slice(i, lanes), axBuffer); + CopySpanToArray(accelerationY.Slice(i, lanes), ayBuffer); + + var mask = new Vector(maskBuffer); + var vx = new Vector(vxBuffer); + var vy = new Vector(vyBuffer); + var px = new Vector(pxBuffer); + var py = new Vector(pyBuffer); + var ax = new Vector(axBuffer); + var ay = new Vector(ayBuffer); vx += ax * delta * mask; vy += ay * delta * mask; px += vx * delta * mask; py += vy * delta * mask; - vx.CopyTo(velocityX.Slice(i, lanes)); - vy.CopyTo(velocityY.Slice(i, lanes)); - px.CopyTo(positionX.Slice(i, lanes)); - py.CopyTo(positionY.Slice(i, lanes)); + vx.CopyTo(vxBuffer); + vy.CopyTo(vyBuffer); + px.CopyTo(pxBuffer); + py.CopyTo(pyBuffer); + CopyArrayToSpan(vxBuffer, velocityX.Slice(i, lanes)); + CopyArrayToSpan(vyBuffer, velocityY.Slice(i, lanes)); + CopyArrayToSpan(pxBuffer, positionX.Slice(i, lanes)); + CopyArrayToSpan(pyBuffer, positionY.Slice(i, lanes)); } for (; i < positionX.Length; i++) @@ -133,6 +193,28 @@ public static class BatchMath } } + public static void IntegrateSemiImplicitEuler2D( + float deltaTime, + ReadOnlySpan dynamicMask, + Span position, + Span velocity, + ReadOnlySpan acceleration) + { + if (deltaTime <= 0.0f) + { + throw new ArgumentOutOfRangeException(nameof(deltaTime), "Delta time must be positive."); + } + + ValidateEqualLengths(dynamicMask.Length, position.Length, velocity.Length, acceleration.Length); + + for (var i = 0; i < position.Length; i++) + { + var mask = dynamicMask[i]; + velocity[i] += acceleration[i] * deltaTime * mask; + position[i] += velocity[i] * deltaTime * mask; + } + } + private static void ValidateEqualLengths(params int[] lengths) { var length = lengths[0]; @@ -144,4 +226,20 @@ public static class BatchMath } } } + + private static void CopySpanToArray(ReadOnlySpan source, float[] destination) + { + for (var i = 0; i < source.Length; i++) + { + destination[i] = source[i]; + } + } + + private static void CopyArrayToSpan(float[] source, Span destination) + { + for (var i = 0; i < destination.Length; i++) + { + destination[i] = source[i]; + } + } } diff --git a/src/CultMath/BatchMath.cs.meta b/src/CultMath/BatchMath.cs.meta new file mode 100644 index 00000000..fe7c6033 --- /dev/null +++ b/src/CultMath/BatchMath.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d4771320e190bdd408dd296c293a9d0e \ No newline at end of file diff --git a/src/CultMath/Color32.cs.meta b/src/CultMath/Color32.cs.meta new file mode 100644 index 00000000..71e1ba55 --- /dev/null +++ b/src/CultMath/Color32.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bfa14a9aacb8ac4448761568643f2bc6 \ No newline at end of file diff --git a/src/CultMath/CultMath.asmdef b/src/CultMath/CultMath.asmdef new file mode 100644 index 00000000..449549f6 --- /dev/null +++ b/src/CultMath/CultMath.asmdef @@ -0,0 +1,14 @@ +{ + "name": "CultMath", + "rootNamespace": "CultMath", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": true +} diff --git a/src/CultMath/CultMath.asmdef.meta b/src/CultMath/CultMath.asmdef.meta new file mode 100644 index 00000000..5a26d2c5 --- /dev/null +++ b/src/CultMath/CultMath.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f0b66b909d13e18468f7aa56221531e9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index f43adce3..01901b6b 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -1,9 +1,10 @@ - net8.0 + net8.0;netstandard2.1 enable enable + latest CultMath CultMath CultMath diff --git a/src/CultMath/CultMath.csproj.meta b/src/CultMath/CultMath.csproj.meta new file mode 100644 index 00000000..78fc304d --- /dev/null +++ b/src/CultMath/CultMath.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 77e1a0a2512910f41885650a7d0d6f60 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/CultMath/CultMathTone.cs.meta b/src/CultMath/CultMathTone.cs.meta new file mode 100644 index 00000000..f41d1475 --- /dev/null +++ b/src/CultMath/CultMathTone.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d4bfc66f9f7e992448f884c2c338d50a \ No newline at end of file diff --git a/src/CultMath/IsExternalInit.cs b/src/CultMath/IsExternalInit.cs new file mode 100644 index 00000000..b05eb4a1 --- /dev/null +++ b/src/CultMath/IsExternalInit.cs @@ -0,0 +1,5 @@ +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit +{ +} diff --git a/src/CultMath/IsExternalInit.cs.meta b/src/CultMath/IsExternalInit.cs.meta new file mode 100644 index 00000000..ca761bcb --- /dev/null +++ b/src/CultMath/IsExternalInit.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: de2b3c3e7f03c354aa2b84fbbc55b497 \ No newline at end of file diff --git a/src/CultMath/Random.cs b/src/CultMath/Random.cs new file mode 100644 index 00000000..9931a87f --- /dev/null +++ b/src/CultMath/Random.cs @@ -0,0 +1,62 @@ +namespace CultMath; + +public struct Random +{ + private const uint DefaultSeed = 0x6E624EB7u; + + public uint state; + + public Random(uint seed) + { + state = seed == 0 ? DefaultSeed : seed; + } + + public static Random CreateFromIndex(uint index) => new(index + DefaultSeed); + + public void InitState(uint seed) + { + state = seed == 0 ? DefaultSeed : seed; + } + + public uint NextUInt() + { + var x = state == 0 ? DefaultSeed : state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + state = x == 0 ? DefaultSeed : x; + return state; + } + + public int NextInt(int max) => NextInt(0, max); + + public int NextInt(int min, int max) + { + if (max <= min) + { + return min; + } + + return min + (int)(NextUInt() % (uint)(max - min)); + } + + public float NextFloat() => (NextUInt() >> 8) * (1.0f / 16777216.0f); + + public float NextFloat(float max) => NextFloat() * max; + + public float NextFloat(float min, float max) => min + NextFloat() * (max - min); + + public float2 NextFloat2() => new(NextFloat(), NextFloat()); + + public float2 NextFloat2(float min, float max) => new(NextFloat(min, max), NextFloat(min, max)); + + public float2 NextFloat2(float2 min, float2 max) => min + NextFloat2() * (max - min); + + public float2 NextFloat2Direction() + { + var angle = NextFloat(0.0f, math.TAU); + return new float2(math.cos(angle), math.sin(angle)); + } + + public float3 NextFloat3(float3 min, float3 max) => min + new float3(NextFloat(), NextFloat(), NextFloat()) * (max - min); +} diff --git a/src/CultMath/Random.cs.meta b/src/CultMath/Random.cs.meta new file mode 100644 index 00000000..55032ded --- /dev/null +++ b/src/CultMath/Random.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c1a557259026da4ca20b54264852c13 \ No newline at end of file diff --git a/src/CultMath/SphericalErosion.cs b/src/CultMath/SphericalErosion.cs new file mode 100644 index 00000000..93ddb0ec --- /dev/null +++ b/src/CultMath/SphericalErosion.cs @@ -0,0 +1,108 @@ +namespace CultMath; + +public readonly record struct SphericalErosionParameters( + float Frequency, + float Amplitude, + float Lacunarity, + float Gain, + float SlopeStrength, + float Detail, + float Seed, + int Octaves) +{ + public static SphericalErosionParameters Default => new(18.0f, 0.035f, 2.03f, 0.52f, 6.0f, 1.35f, 0.0f, 5); +} + +public readonly record struct SphericalErosionSample(float HeightOffset, float Ridge, float Gully, float3 Gradient); + +/// +/// Stateless, point-evaluable erosion-like synthesis on a unit sphere. +/// The matching shader body is cultmath_spherical_erosion in CultMath.hlsl. +/// Position and gradient are planet-space; the gradient is projected tangent +/// internally so cube faces and tile request order never enter the result. +/// +public static class SphericalErosion +{ + public static SphericalErosionSample Sample( + float3 unitPosition, + float baseHeight, + float3 baseGradient, + SphericalErosionParameters parameters) + { + var position = math.normalize(unitPosition); + var gradient = baseGradient - position * math.dot(baseGradient, position); + var slope = math.length(gradient); + var flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position); + var frequency = math.max(parameters.Frequency, 0.001f); + var amplitude = parameters.Amplitude; + var combiMask = EaseOut(math.saturate(slope * parameters.SlopeStrength)); + var fadeTarget = math.saturate(baseHeight * 0.5f + 0.5f) * 2.0f - 1.0f; + var heightOffset = 0.0f; + var ridge = 0.0f; + var gully = 0.0f; + + var octaveCount = math.min(math.max(parameters.Octaves, 0), 8); + for (var octave = 0; octave < octaveCount; octave++) + { + var phase = DirectionalCellWave(position * frequency, flow, parameters.Seed + octave * 19.19f); + var wave = math.cos(phase); + var waveSlope = math.sin(phase); + var faded = math.lerp(fadeTarget, wave, combiMask); + heightOffset += faded * amplitude; + ridge = math.max(ridge, math.saturate(faded)); + gully = math.max(gully, math.saturate(-faded)); + + var across = math.normalize(math.cross(position, flow)); + gradient += across * (math.sign(waveSlope) * amplitude * frequency * combiMask); + gradient -= position * math.dot(gradient, position); + slope = math.length(gradient); + flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position); + + var newMask = EaseOut(math.saturate(math.abs(waveSlope) * 1.35f)); + combiMask = PowInverse(combiMask, parameters.Detail) * newMask; + fadeTarget = faded; + frequency *= math.max(parameters.Lacunarity, 1.01f); + amplitude *= math.saturate(parameters.Gain); + } + + return new SphericalErosionSample(heightOffset, ridge, gully, gradient); + } + + private static float DirectionalCellWave(float3 position, float3 flow, float seed) + { + var cell = math.floor(position); + var local = math.frac(position); + var weights = local * local * (3.0f - 2.0f * local); + var x00 = CellWave(position, flow, cell + new float3(0, 0, 0), seed); + var x10 = CellWave(position, flow, cell + new float3(1, 0, 0), seed); + var x01 = CellWave(position, flow, cell + new float3(0, 1, 0), seed); + var x11 = CellWave(position, flow, cell + new float3(1, 1, 0), seed); + var y00 = math.lerp(x00, x10, weights.x); + var y10 = math.lerp(x01, x11, weights.x); + var z0 = math.lerp(y00, y10, weights.y); + var x00z = CellWave(position, flow, cell + new float3(0, 0, 1), seed); + var x10z = CellWave(position, flow, cell + new float3(1, 0, 1), seed); + var x01z = CellWave(position, flow, cell + new float3(0, 1, 1), seed); + var x11z = CellWave(position, flow, cell + new float3(1, 1, 1), seed); + var z1 = math.lerp(math.lerp(x00z, x10z, weights.x), math.lerp(x01z, x11z, weights.x), weights.y); + return math.lerp(z0, z1, weights.z); + } + + private static float CellWave(float3 position, float3 flow, float3 cell, float seed) + { + var pivot = cell + new float3( + math.hash(cell + seed + 11.7f), + math.hash(cell.yzx + seed + 37.1f), + math.hash(cell.zyx + seed + 73.9f)); + return math.dot(position - pivot, flow) * math.TAU + math.hash(cell + seed + 101.3f) * math.TAU; + } + + private static float3 StableTangent(float3 position) + { + var axis = math.abs(position.z) < 0.8f ? new float3(0, 0, 1) : new float3(0, 1, 0); + return math.normalize(math.cross(axis, position)); + } + + private static float EaseOut(float value) => 1.0f - (1.0f - value) * (1.0f - value); + private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), math.max(power, 0.001f)); +} diff --git a/src/CultMath/Voronoi.cs b/src/CultMath/Voronoi.cs index 9791a8e0..38b52d81 100644 --- a/src/CultMath/Voronoi.cs +++ b/src/CultMath/Voronoi.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; namespace CultMath; diff --git a/src/CultMath/Voronoi.cs.meta b/src/CultMath/Voronoi.cs.meta new file mode 100644 index 00000000..6c738085 --- /dev/null +++ b/src/CultMath/Voronoi.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb530f1428ac8ff43939b986986ae959 \ No newline at end of file diff --git a/src/CultMath/bool2.cs b/src/CultMath/bool2.cs new file mode 100644 index 00000000..95ab29eb --- /dev/null +++ b/src/CultMath/bool2.cs @@ -0,0 +1,18 @@ +using System; + +namespace CultMath; + +public record struct bool2(bool x, bool y) +{ + public static readonly bool2 @false = new(false, false); + public static readonly bool2 @true = new(true, true); + + public bool this[int index] => index switch + { + 0 => x, + 1 => y, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator bool2(bool value) => new(value, value); +} diff --git a/src/CultMath/bool2.cs.meta b/src/CultMath/bool2.cs.meta new file mode 100644 index 00000000..477ff144 --- /dev/null +++ b/src/CultMath/bool2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 725efb2725e987449a3782452f5a95e0 \ No newline at end of file diff --git a/src/CultMath/double2.cs b/src/CultMath/double2.cs new file mode 100644 index 00000000..000c3cb0 --- /dev/null +++ b/src/CultMath/double2.cs @@ -0,0 +1,25 @@ +using System; + +namespace CultMath; + +public record struct double2(double x, double y) +{ + public static readonly double2 zero = new(0.0, 0.0); + public static readonly double2 one = new(1.0, 1.0); + + public double this[int index] => index switch + { + 0 => x, + 1 => y, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator double2(double value) => new(value, value); + + public static double2 operator +(double2 value) => value; + public static double2 operator -(double2 value) => new(-value.x, -value.y); + public static double2 operator +(double2 left, double2 right) => new(left.x + right.x, left.y + right.y); + public static double2 operator -(double2 left, double2 right) => new(left.x - right.x, left.y - right.y); + public static double2 operator *(double2 left, double2 right) => new(left.x * right.x, left.y * right.y); + public static double2 operator /(double2 left, double2 right) => new(left.x / right.x, left.y / right.y); +} diff --git a/src/CultMath/double2.cs.meta b/src/CultMath/double2.cs.meta new file mode 100644 index 00000000..61243b2a --- /dev/null +++ b/src/CultMath/double2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bbe739a5f0216c3459762d2b5ee7401a \ No newline at end of file diff --git a/src/CultMath/double3.cs b/src/CultMath/double3.cs new file mode 100644 index 00000000..b1427505 --- /dev/null +++ b/src/CultMath/double3.cs @@ -0,0 +1,36 @@ +using System; + +namespace CultMath; + +public record struct double3(double x, double y, double z) +{ + public static readonly double3 zero = new(0.0, 0.0, 0.0); + public static readonly double3 one = new(1.0, 1.0, 1.0); + + public double2 xy + { + get => new(x, y); + set + { + x = value.x; + y = value.y; + } + } + + public double this[int index] => index switch + { + 0 => x, + 1 => y, + 2 => z, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator double3(double value) => new(value, value, value); + + public static double3 operator +(double3 value) => value; + public static double3 operator -(double3 value) => new(-value.x, -value.y, -value.z); + public static double3 operator +(double3 left, double3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z); + public static double3 operator -(double3 left, double3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z); + public static double3 operator *(double3 left, double3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z); + public static double3 operator /(double3 left, double3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z); +} diff --git a/src/CultMath/double3.cs.meta b/src/CultMath/double3.cs.meta new file mode 100644 index 00000000..302d13d8 --- /dev/null +++ b/src/CultMath/double3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1cbaafd219703ee43ae279aa5cbf407f \ No newline at end of file diff --git a/src/CultMath/float2.cs b/src/CultMath/float2.cs index 1f1d66b1..5298494b 100644 --- a/src/CultMath/float2.cs +++ b/src/CultMath/float2.cs @@ -1,6 +1,8 @@ +using System; + namespace CultMath; -public readonly record struct float2(float x, float y) +public record struct float2(float x, float y) { public static readonly float2 zero = new(0.0f, 0.0f); public static readonly float2 one = new(1.0f, 1.0f); diff --git a/src/CultMath/float2.cs.meta b/src/CultMath/float2.cs.meta new file mode 100644 index 00000000..c82f69ae --- /dev/null +++ b/src/CultMath/float2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fa066a0aed5494f499d462a2ebc51e50 \ No newline at end of file diff --git a/src/CultMath/float3.cs b/src/CultMath/float3.cs index 70cfa02b..e9833ae4 100644 --- a/src/CultMath/float3.cs +++ b/src/CultMath/float3.cs @@ -1,6 +1,8 @@ +using System; + namespace CultMath; -public readonly record struct float3(float x, float y, float z) +public record struct float3(float x, float y, float z) { public static readonly float3 zero = new(0.0f, 0.0f, 0.0f); public static readonly float3 one = new(1.0f, 1.0f, 1.0f); @@ -10,9 +12,48 @@ public readonly record struct float3(float x, float y, float z) { } - public float2 xy => new(x, y); - public float2 xz => new(x, z); - public float2 yz => new(y, z); + public float2 xy + { + get => new(x, y); + set + { + x = value.x; + y = value.y; + } + } + + public float2 xz + { + get => new(x, z); + set + { + x = value.x; + z = value.y; + } + } + + public float2 yz + { + get => new(y, z); + set + { + y = value.x; + z = value.y; + } + } + + public float2 zy + { + get => new(z, y); + set + { + z = value.x; + y = value.y; + } + } + + public float3 yzx => new(y, z, x); + public float3 zyx => new(z, y, x); public float this[int index] => index switch { diff --git a/src/CultMath/float3.cs.meta b/src/CultMath/float3.cs.meta new file mode 100644 index 00000000..3c649c4a --- /dev/null +++ b/src/CultMath/float3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 58a355dd1cf6b9f48a7595eea0c0e1f2 \ No newline at end of file diff --git a/src/CultMath/float4.cs b/src/CultMath/float4.cs index c7bfb404..b6eed097 100644 --- a/src/CultMath/float4.cs +++ b/src/CultMath/float4.cs @@ -1,6 +1,8 @@ +using System; + namespace CultMath; -public readonly record struct float4(float x, float y, float z, float w) +public record struct float4(float x, float y, float z, float w) { public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f); public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f); @@ -15,7 +17,22 @@ public readonly record struct float4(float x, float y, float z, float w) { } - public float2 xy => new(x, y); + public float4(float x, float3 yzw) + : this(x, yzw.x, yzw.y, yzw.z) + { + } + + public float2 xy + { + get => new(x, y); + set + { + x = value.x; + y = value.y; + } + } + public float3 xyw => new(x, y, w); + public float3 yzx => new(y, z, x); public float3 xyz => new(x, y, z); public float this[int index] => index switch diff --git a/src/CultMath/float4.cs.meta b/src/CultMath/float4.cs.meta new file mode 100644 index 00000000..206444e0 --- /dev/null +++ b/src/CultMath/float4.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fe24fbdab33538849bc6be0afbb0a692 \ No newline at end of file diff --git a/src/CultMath/int2.cs b/src/CultMath/int2.cs new file mode 100644 index 00000000..7c2f350b --- /dev/null +++ b/src/CultMath/int2.cs @@ -0,0 +1,29 @@ +using System; + +namespace CultMath; + +public record struct int2(int x, int y) +{ + public static readonly int2 zero = new(0, 0); + public static readonly int2 one = new(1, 1); + + public int this[int index] => index switch + { + 0 => x, + 1 => y, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public static implicit operator int2(int value) => new(value, value); + public static implicit operator float2(int2 value) => new(value.x, value.y); + public static explicit operator System.Numerics.Vector2(int2 value) => new(value.x, value.y); + public static explicit operator int2(System.Numerics.Vector2 value) => new((int)value.X, (int)value.Y); + + public static int2 operator +(int2 value) => value; + public static int2 operator -(int2 value) => new(-value.x, -value.y); + public static int2 operator +(int2 left, int2 right) => new(left.x + right.x, left.y + right.y); + public static int2 operator -(int2 left, int2 right) => new(left.x - right.x, left.y - right.y); + public static int2 operator *(int2 left, int2 right) => new(left.x * right.x, left.y * right.y); + public static int2 operator /(int2 left, int2 right) => new(left.x / right.x, left.y / right.y); + public static int2 operator %(int2 left, int2 right) => new(left.x % right.x, left.y % right.y); +} diff --git a/src/CultMath/int2.cs.meta b/src/CultMath/int2.cs.meta new file mode 100644 index 00000000..5920328a --- /dev/null +++ b/src/CultMath/int2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 577b174a998aaa4478ec26e7a6782f46 \ No newline at end of file diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index 817aa7fd..874554ad 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public static class math @@ -8,13 +10,20 @@ public static class math public static float2 float2(float x, float y) => new(x, y); public static float2 float2(float value) => new(value, value); + public static bool2 bool2(bool x, bool y) => new(x, y); + public static bool2 bool2(bool value) => new(value, value); + public static int2 int2(int x, int y) => new(x, y); + public static int2 int2(int value) => new(value, value); public static float3 float3(float x, float y, float z) => new(x, y, z); public static float3 float3(float2 xy, float z) => new(xy, z); public static float3 float3(float value) => new(value, value, value); public static float4 float4(float x, float y, float z, float w) => new(x, y, z, w); public static float4 float4(float2 xy, float z, float w) => new(xy, z, w); public static float4 float4(float3 xyz, float w) => new(xyz, w); + public static float4 float4(float x, float3 yzw) => new(x, yzw); public static float4 float4(float value) => new(value, value, value, value); + public static rect rect(float2 min, float2 max) => new(min, max); + public static rect rect(float minX, float minY, float maxX, float maxY) => new(minX, minY, maxX, maxY); public static float radians(float degrees) => degrees * (PI / 180.0f); public static float degrees(float radians) => radians * (180.0f / PI); @@ -26,16 +35,26 @@ public static class math public static float atan(float value) => MathF.Atan(value); public static float atan2(float y, float x) => MathF.Atan2(y, x); public static float sqrt(float value) => MathF.Sqrt(value); + public static float pow(float value, float power) => MathF.Pow(value, power); + public static bool isinf(float value) => float.IsInfinity(value); public static float exp(float value) => MathF.Exp(value); + public static double exp(double value) => Math.Exp(value); public static float2 exp(float2 value) => new(exp(value.x), exp(value.y)); public static float3 exp(float3 value) => new(exp(value.x), exp(value.y), exp(value.z)); public static float4 exp(float4 value) => new(exp(value.x), exp(value.y), exp(value.z), exp(value.w)); + public static double2 exp(double2 value) => new(exp(value.x), exp(value.y)); + public static double3 exp(double3 value) => new(exp(value.x), exp(value.y), exp(value.z)); public static float abs(float value) => MathF.Abs(value); public static float2 abs(float2 value) => new(abs(value.x), abs(value.y)); public static float3 abs(float3 value) => new(abs(value.x), abs(value.y), abs(value.z)); public static float4 abs(float4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w)); + public static float sign(float value) => MathF.Sign(value); + public static float2 sign(float2 value) => new(sign(value.x), sign(value.y)); + public static float3 sign(float3 value) => new(sign(value.x), sign(value.y), sign(value.z)); + public static float4 sign(float4 value) => new(sign(value.x), sign(value.y), sign(value.z), sign(value.w)); + public static float floor(float value) => MathF.Floor(value); public static float2 floor(float2 value) => new(floor(value.x), floor(value.y)); public static float3 floor(float3 value) => new(floor(value.x), floor(value.y), floor(value.z)); @@ -52,11 +71,13 @@ public static class math public static float4 frac(float4 value) => value - floor(value); public static float min(float left, float right) => MathF.Min(left, right); + public static int min(int left, int right) => Math.Min(left, right); public static float2 min(float2 left, float2 right) => new(min(left.x, right.x), min(left.y, right.y)); public static float3 min(float3 left, float3 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z)); public static float4 min(float4 left, float4 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z), min(left.w, right.w)); public static float max(float left, float right) => MathF.Max(left, right); + public static int max(int left, int right) => Math.Max(left, right); public static float2 max(float2 left, float2 right) => new(max(left.x, right.x), max(left.y, right.y)); public static float3 max(float3 left, float3 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z)); public static float4 max(float4 left, float4 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z), max(left.w, right.w)); @@ -72,9 +93,20 @@ public static class math public static float4 saturate(float4 value) => clamp(value, 0.0f, 1.0f); public static float lerp(float start, float end, float amount) => start + (end - start) * amount; + public static double lerp(double start, double end, double amount) => start + (end - start) * amount; public static float2 lerp(float2 start, float2 end, float2 amount) => start + (end - start) * amount; public static float3 lerp(float3 start, float3 end, float3 amount) => start + (end - start) * amount; public static float4 lerp(float4 start, float4 end, float4 amount) => start + (end - start) * amount; + public static double2 lerp(double2 start, double2 end, double2 amount) => start + (end - start) * amount; + public static double3 lerp(double3 start, double3 end, double3 amount) => start + (end - start) * amount; + + public static float unlerp(float start, float end, float value) => (value - start) / (end - start); + public static double unlerp(double start, double end, double value) => (value - start) / (end - start); + public static float2 unlerp(float2 start, float2 end, float2 value) => (value - start) / (end - start); + public static float3 unlerp(float3 start, float3 end, float3 value) => (value - start) / (end - start); + public static float4 unlerp(float4 start, float4 end, float4 value) => (value - start) / (end - start); + public static double2 unlerp(double2 start, double2 end, double2 value) => (value - start) / (end - start); + public static double3 unlerp(double3 start, double3 end, double3 value) => (value - start) / (end - start); public static float step(float edge, float value) => value < edge ? 0.0f : 1.0f; public static float2 step(float2 edge, float2 value) => new(step(edge.x, value.x), step(edge.y, value.y)); @@ -130,10 +162,30 @@ public static class math public static float2 normalize(float2 value) => value / MathF.Max(length(value), 1.0e-20f); public static float3 normalize(float3 value) => value / MathF.Max(length(value), 1.0e-20f); public static float4 normalize(float4 value) => value / MathF.Max(length(value), 1.0e-20f); + public static quaternion normalize(quaternion value) + { + var length = MathF.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z + value.w * value.w); + if (length <= 1.0e-20f) + { + return quaternion.identity; + } + + var invLength = 1.0f / length; + return new quaternion(value.x * invLength, value.y * invLength, value.z * invLength, value.w * invLength); + } public static float2 reflect(float2 incident, float2 normal) => incident - 2.0f * dot(normal, incident) * normal; public static float3 reflect(float3 incident, float3 normal) => incident - 2.0f * dot(normal, incident) * normal; + public static float2 rotate(float2 value, float radians) + { + var s = sin(radians); + var c = cos(radians); + return new float2(value.x * c - value.y * s, value.x * s + value.y * c); + } + + public static float2 rotate_degrees(float2 value, float degrees) => rotate(value, radians(degrees)); + public static float csum(float2 value) => value.x + value.y; public static float csum(float3 value) => value.x + value.y + value.z; public static float csum(float4 value) => value.x + value.y + value.z + value.w; diff --git a/src/CultMath/math.cs.meta b/src/CultMath/math.cs.meta new file mode 100644 index 00000000..9222af7d --- /dev/null +++ b/src/CultMath/math.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0d9b4cacc40d169429ee743d19577cb7 \ No newline at end of file diff --git a/src/CultMath/quaternion.cs b/src/CultMath/quaternion.cs new file mode 100644 index 00000000..fe8dda8e --- /dev/null +++ b/src/CultMath/quaternion.cs @@ -0,0 +1,68 @@ +namespace CultMath; + +public record struct quaternion(float x, float y, float z, float w) +{ + public static readonly quaternion identity = new(0.0f, 0.0f, 0.0f, 1.0f); + + public static quaternion LookRotation(float3 forward, float3 up) + { + forward = math.normalize(forward); + up = math.normalize(up); + + var right = math.normalize(math.cross(up, forward)); + up = math.cross(forward, right); + + var m00 = right.x; + var m01 = up.x; + var m02 = forward.x; + var m10 = right.y; + var m11 = up.y; + var m12 = forward.y; + var m20 = right.z; + var m21 = up.z; + var m22 = forward.z; + + var trace = m00 + m11 + m22; + if (trace > 0.0f) + { + var s = math.sqrt(trace + 1.0f) * 2.0f; + return math.normalize(new quaternion( + (m21 - m12) / s, + (m02 - m20) / s, + (m10 - m01) / s, + 0.25f * s)); + } + + if (m00 > m11 && m00 > m22) + { + var s = math.sqrt(1.0f + m00 - m11 - m22) * 2.0f; + return math.normalize(new quaternion( + 0.25f * s, + (m01 + m10) / s, + (m02 + m20) / s, + (m21 - m12) / s)); + } + + if (m11 > m22) + { + var s = math.sqrt(1.0f + m11 - m00 - m22) * 2.0f; + return math.normalize(new quaternion( + (m01 + m10) / s, + 0.25f * s, + (m12 + m21) / s, + (m02 - m20) / s)); + } + + { + var s = math.sqrt(1.0f + m22 - m00 - m11) * 2.0f; + return math.normalize(new quaternion( + (m02 + m20) / s, + (m12 + m21) / s, + 0.25f * s, + (m10 - m01) / s)); + } + } + + public static implicit operator quaternion(float4 value) => new(value.x, value.y, value.z, value.w); + public static implicit operator float4(quaternion value) => new(value.x, value.y, value.z, value.w); +} diff --git a/src/CultMath/quaternion.cs.meta b/src/CultMath/quaternion.cs.meta new file mode 100644 index 00000000..ae6e8a11 --- /dev/null +++ b/src/CultMath/quaternion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12f3627d9250dc348be9c1a1056ec7d0 \ No newline at end of file diff --git a/src/CultMath/rect.cs b/src/CultMath/rect.cs new file mode 100644 index 00000000..67b210b9 --- /dev/null +++ b/src/CultMath/rect.cs @@ -0,0 +1,21 @@ +namespace CultMath; + +public readonly record struct rect(float2 min, float2 max) +{ + public rect(float minX, float minY, float maxX, float maxY) + : this(math.float2(math.min(minX, maxX), math.min(minY, maxY)), + math.float2(math.max(minX, maxX), math.max(minY, maxY))) + { + } + + public float2 size => max - min; + public float2 center => (min + max) * 0.5f; + + public bool Contains(float2 point) + { + return point.x >= min.x && + point.x <= max.x && + point.y >= min.y && + point.y <= max.y; + } +} diff --git a/src/CultMath/rect.cs.meta b/src/CultMath/rect.cs.meta new file mode 100644 index 00000000..9f7598ed --- /dev/null +++ b/src/CultMath/rect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b00dc5f108967db47ac2f54a47033e33 \ No newline at end of file diff --git a/tests.meta b/tests.meta new file mode 100644 index 00000000..a35356c4 --- /dev/null +++ b/tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4ca61dedbfb3c5d46b0acaf23202b5c2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/CultMath.Tests.meta b/tests/CultMath.Tests.meta new file mode 100644 index 00000000..3f1451c6 --- /dev/null +++ b/tests/CultMath.Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bcee65f13f85daa45b0cf3fdaa901779 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/CultMath.Tests/CultMath.Tests.csproj.meta b/tests/CultMath.Tests/CultMath.Tests.csproj.meta new file mode 100644 index 00000000..709184bb --- /dev/null +++ b/tests/CultMath.Tests/CultMath.Tests.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 636051db121e19146a0baf92b790415c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 91a95f4d..ed9119b7 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -4,6 +4,14 @@ namespace CultMath.Tests; public sealed class HlslMirrorTests { + [Fact] + public void HlslMirrorPublishesSphericalErosionKernel() + { + var source = File.ReadAllText(FindHlslMirror()); + Assert.Contains("cultmath_spherical_erosion", source); + Assert.Contains("CultMathSphericalErosionParameters", source); + } + [Fact] public void HlslMirrorPublishesCultMathPrimitives() { @@ -23,6 +31,7 @@ public sealed class HlslMirrorTests "cultmath_distance", "cultmath_normalize", "cultmath_reflect", + "cultmath_rotate", "cultmath_csum", "cultmath_decay", "cultmath_damp", diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs.meta b/tests/CultMath.Tests/HlslMirrorTests.cs.meta new file mode 100644 index 00000000..1fcf44b3 --- /dev/null +++ b/tests/CultMath.Tests/HlslMirrorTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3d7eb9df849ea0148a71419bbfe2352c \ No newline at end of file diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 44dd1290..bb816d47 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -23,6 +23,7 @@ public sealed class MathTests [Fact] public void VectorConstructorsAcceptLowerDimensionVectors() { + Assert.Equal(new int2(1, 2), math.int2(1, 2)); Assert.Equal(new float2(1.0f, 2.0f), math.float2(1.0f, 2.0f)); Assert.Equal(new float3(1.0f, 1.0f, 1.0f), math.float3(1.0f)); Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); @@ -31,6 +32,112 @@ public sealed class MathTests Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); } + [Fact] + public void RectNormalizesBoundsAndContainsPoints() + { + var viewport = math.rect(10.0f, 20.0f, -5.0f, -2.0f); + + Assert.Equal(new float2(-5.0f, -2.0f), viewport.min); + Assert.Equal(new float2(10.0f, 20.0f), viewport.max); + Assert.Equal(new float2(15.0f, 22.0f), viewport.size); + Assert.Equal(new float2(2.5f, 9.0f), viewport.center); + Assert.True(viewport.Contains(new float2(0.0f, 0.0f))); + Assert.False(viewport.Contains(new float2(11.0f, 0.0f))); + } + + [Fact] + public void VectorsExposeCommonReadOnlySwizzles() + { + Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).yzx); + Assert.Equal(new float3(3.0f, 2.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).zyx); + Assert.Equal(new float2(3.0f, 2.0f), new float3(1.0f, 2.0f, 3.0f).zy); + Assert.Equal(new float3(1.0f, 2.0f, 4.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).xyw); + Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).yzx); + Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(1.0f, new float3(2.0f, 3.0f, 4.0f))); + Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(1.0f, new float3(2.0f, 3.0f, 4.0f))); + } + + [Fact] + public void VectorsSupportUnityStyleFieldAndSwizzleMutation() + { + var position = new float3(1.0f, 2.0f, 3.0f); + position.y = 5.0f; + position.xz = new float2(8.0f, 13.0f); + + Assert.Equal(new float3(8.0f, 5.0f, 13.0f), position); + + var bounds = new float4(1.0f, 2.0f, 3.0f, 4.0f); + bounds.xy = new float2(-1.0f, -2.0f); + + Assert.Equal(new float4(-1.0f, -2.0f, 3.0f, 4.0f), bounds); + } + + [Fact] + public void DoubleVectorsSupportDampedMath() + { + var value = new double3(1.0, 2.0, 3.0); + value.xy = new double2(5.0, 8.0); + + Assert.Equal(new double3(5.0, 8.0, 3.0), value); + Assert.Equal(new double2(Math.Exp(-2.0), Math.Exp(-4.0)), math.exp(new double2(-2.0, -4.0))); + Assert.Equal(new double3(2.0, 4.0, 6.0), math.lerp(new double3(0.0, 0.0, 0.0), new double3(4.0, 8.0, 12.0), new double3(0.5, 0.5, 0.5))); + } + + [Fact] + public void Int2SupportsGridArithmeticAndFloatPromotion() + { + var cell = new int2(2, 3); + + Assert.Equal(new int2(3, 5), cell + new int2(1, 2)); + Assert.Equal(new int2(4, 6), cell * new int2(2, 2)); + Assert.Equal(3, cell[1]); + + float2 promoted = cell; + Assert.Equal(new float2(2.0f, 3.0f), promoted); + } + + [Fact] + public void Bool2SupportsHullGridStyleStorage() + { + bool2 conductivity = true; + + conductivity.y = false; + + Assert.True(conductivity.x); + Assert.False(conductivity[1]); + Assert.Equal(new bool2(false, true), math.bool2(false, true)); + } + + [Fact] + public void QuaternionLookRotationProducesUnitOrientation() + { + var rotation = quaternion.LookRotation(new float3(0.0f, 0.0f, 1.0f), new float3(0.0f, 1.0f, 0.0f)); + + Assert.Equal(quaternion.identity, rotation); + Assert.Equal(1.0f, math.length((float4)quaternion.LookRotation(new float3(1.0f, 0.0f, 0.0f), new float3(0.0f, 1.0f, 0.0f))), precision: 5); + } + + [Fact] + public void RandomIsDeterministicAndUnityShaped() + { + var a = new Random(1234); + var b = new Random(1234); + + Assert.Equal(a.NextUInt(), b.NextUInt()); + Assert.Equal(a.NextFloat(), b.NextFloat()); + + var bounded = a.NextFloat(-2.0f, 4.0f); + Assert.True(bounded >= -2.0f && bounded < 4.0f); + + var direction = a.NextFloat2Direction(); + Assert.Equal(1.0f, math.length(direction), precision: 5); + + var point = a.NextFloat3(new float3(-1.0f, -2.0f, -3.0f), new float3(1.0f, 2.0f, 3.0f)); + Assert.True(point.x >= -1.0f && point.x < 1.0f); + Assert.True(point.y >= -2.0f && point.y < 2.0f); + Assert.True(point.z >= -3.0f && point.z < 3.0f); + } + [Fact] public void NormalizeProducesUnitLength() { @@ -55,12 +162,39 @@ public sealed class MathTests Assert.Equal(-1.0f, math.cos(math.PI), precision: 5); Assert.Equal(1.0f, math.tan(math.PI * 0.25f), precision: 5); Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5); + Assert.Equal(2, math.min(2, 5)); + Assert.Equal(5, math.max(2, 5)); + Assert.True(math.isinf(float.PositiveInfinity)); + } + + [Fact] + public void SignIsComponentWiseForSimulationVectors() + { + Assert.Equal(-1.0f, math.sign(-12.0f)); + Assert.Equal(0.0f, math.sign(0.0f)); + Assert.Equal(1.0f, math.sign(12.0f)); + Assert.Equal(new float3(-1.0f, 0.0f, 1.0f), math.sign(new float3(-2.0f, 0.0f, 4.0f))); + } + + [Fact] + public void RotateTurnsFloat2CounterClockwiseInRadians() + { + var rotated = math.rotate(new float2(1.0f, 0.0f), math.HALF_PI); + + Assert.Equal(0.0f, rotated.x, precision: 5); + Assert.Equal(1.0f, rotated.y, precision: 5); + + var rotatedDegrees = math.rotate_degrees(new float2(0.0f, 1.0f), 90.0f); + Assert.Equal(-1.0f, rotatedDegrees.x, precision: 5); + Assert.Equal(0.0f, rotatedDegrees.y, precision: 5); } [Fact] public void AetheriaPrimitivesRemainSmallAndDeterministic() { Assert.Equal(6.0f, math.csum(new float3(1.0f, 2.0f, 3.0f))); + Assert.Equal(0.25f, math.unlerp(10.0f, 30.0f, 15.0f)); + Assert.Equal(new float2(0.25f, 0.75f), math.unlerp(new float2(10.0f, 10.0f), new float2(30.0f, 30.0f), new float2(15.0f, 25.0f))); Assert.Equal(MathF.Exp(-2.0f), math.decay(1.0f, 2.0f, 1.0f), precision: 5); Assert.Equal(1.0f - MathF.Exp(-2.0f), math.damp(0.0f, 1.0f, 2.0f, 1.0f), precision: 5); diff --git a/tests/CultMath.Tests/MathTests.cs.meta b/tests/CultMath.Tests/MathTests.cs.meta new file mode 100644 index 00000000..277df5a3 --- /dev/null +++ b/tests/CultMath.Tests/MathTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c7a709e5345da214aa650e8c796fcaff \ No newline at end of file diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs b/tests/CultMath.Tests/SphericalErosionTests.cs new file mode 100644 index 00000000..8ae04859 --- /dev/null +++ b/tests/CultMath.Tests/SphericalErosionTests.cs @@ -0,0 +1,27 @@ +namespace CultMath.Tests; + +public sealed class SphericalErosionTests +{ + [Fact] + public void SampleIsDeterministicAndScaleInvariant() + { + var parameters = SphericalErosionParameters.Default with { Seed = 42.0f }; + var a = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); + var b = SphericalErosion.Sample(new float3(3.1f, -7.4f, 5.9f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); + + Assert.Equal(a, b); + Assert.InRange(a.HeightOffset, -0.08f, 0.08f); + Assert.InRange(a.Ridge, 0.0f, 1.0f); + Assert.InRange(a.Gully, 0.0f, 1.0f); + } + + [Fact] + public void NearbySamplesRemainContinuousAcrossAChartBoundary() + { + var parameters = SphericalErosionParameters.Default; + var left = SphericalErosion.Sample(math.normalize(new float3(1.0f, 0.99999f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters); + var right = SphericalErosion.Sample(math.normalize(new float3(0.99999f, 1.0f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters); + + Assert.InRange(math.abs(left.HeightOffset - right.HeightOffset), 0.0f, 0.0002f); + } +} diff --git a/tools.meta b/tools.meta new file mode 100644 index 00000000..3594fa6a --- /dev/null +++ b/tools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 697162910ffd9614ba9655d31af9afa4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/compile-hlsl-spirv.ps1.meta b/tools/compile-hlsl-spirv.ps1.meta new file mode 100644 index 00000000..31e7948a --- /dev/null +++ b/tools/compile-hlsl-spirv.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b47e7016a99a5d148bc44c399c13c4d3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/compile-spirv-metal.ps1.meta b/tools/compile-spirv-metal.ps1.meta new file mode 100644 index 00000000..2e7e74c2 --- /dev/null +++ b/tools/compile-spirv-metal.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6150c8f461e471b4c8641fd32d1984ef +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/get-dxc.ps1.meta b/tools/get-dxc.ps1.meta new file mode 100644 index 00000000..c9ab0d82 --- /dev/null +++ b/tools/get-dxc.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 86d0b9a1006042745b7fff523831049c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/get-spirv-cross.ps1.meta b/tools/get-spirv-cross.ps1.meta new file mode 100644 index 00000000..12ccdd8e --- /dev/null +++ b/tools/get-spirv-cross.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dcf41da07502f4b439f8fc5ad25d16c9 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 430cbc9695aba7a03f1f87c82ed84d1eba61a9e5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 19:49:33 +0100 Subject: [PATCH 010/203] Add faithful advanced erosion filter core --- README.md | 5 + THIRD-PARTY-NOTICES.md | 21 +++ shaders/AdvancedErosionFilter.hlsl | 112 +++++++++++++++ shaders/CultMath.hlsl | 2 + src/CultMath/AdvancedErosionFilter.cs | 129 ++++++++++++++++++ .../AdvancedErosionFilterTests.cs | 44 ++++++ tests/CultMath.Tests/HlslMirrorTests.cs | 13 +- tests/CultMath.Tests/SphericalErosionTests.cs | 2 + 8 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 THIRD-PARTY-NOTICES.md create mode 100644 shaders/AdvancedErosionFilter.hlsl create mode 100644 src/CultMath/AdvancedErosionFilter.cs create mode 100644 tests/CultMath.Tests/AdvancedErosionFilterTests.cs diff --git a/README.md b/README.md index 1050e4ec..1228736a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ 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. +The advanced erosion filter is an MPL-2.0 adaptation of Rune Skovbo Johansen's +published filter. Its faithful planar core is isolated in +`AdvancedErosionFilter.cs` and `AdvancedErosionFilter.hlsl`; spherical mapping +is a separate CultMath concern. See `THIRD-PARTY-NOTICES.md`. + ## Build ```powershell diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md new file mode 100644 index 00000000..c8976207 --- /dev/null +++ b/THIRD-PARTY-NOTICES.md @@ -0,0 +1,21 @@ +# Third-Party Notices + +## Advanced Terrain Erosion Filter + +`src/CultMath/AdvancedErosionFilter.cs` and +`shaders/AdvancedErosionFilter.hlsl` adapt the mathematical procedure from +Rune Skovbo Johansen's **Advanced Terrain Erosion Filter**: + +- Copyright 2025 Rune Skovbo Johansen. +- Upstream: +- Explanation: +- License: Mozilla Public License 2.0, + + +The CultMath files retain the MPL notice and separate the faithful planar +filter from CultMath's spherical adaptation. Modifications include C#/HLSL +type lowering, explicit bounded octave counts, finite zero-rounding behavior, +and CultMath naming. + +The technique builds on earlier work credited by the upstream author to Fewes +and Clay John. The hash form follows the public shader math of Inigo Quilez. diff --git a/shaders/AdvancedErosionFilter.hlsl b/shaders/AdvancedErosionFilter.hlsl new file mode 100644 index 00000000..6c35dd47 --- /dev/null +++ b/shaders/AdvancedErosionFilter.hlsl @@ -0,0 +1,112 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain +// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn + +#ifndef CULTMATH_ADVANCED_EROSION_FILTER_HLSL +#define CULTMATH_ADVANCED_EROSION_FILTER_HLSL + +struct CultMathAdvancedErosionParameters +{ + float scale; float strength; float gully_weight; float detail; + float4 rounding; float4 onset; float2 assumed_slope; + float cell_scale; float normalization; int octaves; float lacunarity; float gain; +}; + +struct CultMathAdvancedErosionResult +{ + float3 delta; float magnitude; float ridge_map; float fade_target; +}; + +float2 cultmath_erosion_hash2(float2 input) +{ + float2 k = float2(0.3183099, 0.3678794); + float2 x = input * k + k.yx; + return -1.0 + 2.0 * frac(16.0 * k * frac(x.x * x.y * (x.x + x.y))); +} + +float4 cultmath_phacelle_noise(float2 position, float2 normal_direction, float frequency, float offset_cycles, float normalization) +{ + float2 side_direction = float2(-normal_direction.y, normal_direction.x) * frequency * 6.28318530718; + float offset = offset_cycles * 6.28318530718; + float2 cell = floor(position); + float2 local = frac(position); + float2 phase = 0.0; + float weight_sum = 0.0; + [unroll] for (int x = -1; x <= 2; x++) + [unroll] for (int y = -1; y <= 2; y++) + { + float2 grid_offset = float2(x, y); + float2 grid_point = cell + grid_offset; + float2 random_offset = cultmath_erosion_hash2(grid_point) * 0.5; + float2 delta = local - grid_offset - random_offset; + float weight = max(0.0, exp(-dot(delta, delta) * 2.0) - 0.01111); + weight_sum += weight; + float wave = dot(delta, side_direction) + offset; + phase += float2(cos(wave), sin(wave)) * weight; + } + float2 interpolated = phase / max(weight_sum, 1.0e-20); + float magnitude = max(1.0 - saturate(normalization), length(interpolated)); + return float4(interpolated / max(magnitude, 1.0e-20), side_direction); +} + +float cultmath_erosion_pow_inverse(float value, float power) { return 1.0 - pow(1.0 - saturate(value), power); } +float cultmath_erosion_ease_out(float value) { float inverse = 1.0 - saturate(value); return 1.0 - inverse * inverse; } +float cultmath_erosion_smooth_start(float value, float smoothing) +{ + return smoothing <= 1.0e-10 || value >= smoothing ? value - 0.5 * smoothing : 0.5 * value * value / smoothing; +} +float2 cultmath_erosion_safe_normalize(float2 value) +{ + float value_length = length(value); + return abs(value_length) > 1.0e-10 ? value / value_length : value; +} + +CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p) +{ + float strength = p.strength * p.scale; + fade_target = clamp(fade_target, -1.0, 1.0); + float3 input_value = base_height_and_slope; + float3 height_and_slope = input_value; + float frequency = 1.0 / max(p.scale * p.cell_scale, 1.0e-10); + float2 slope = height_and_slope.yz; + float slope_length = max(length(slope), 1.0e-10); + float magnitude = 0.0; + float rounding_multiplier = 1.0; + float input_rounding = lerp(p.rounding.y, p.rounding.x, saturate(fade_target + 0.5)) * p.rounding.z; + float combined_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(slope_length * p.onset.x, input_rounding * p.onset.x)); + float ridge_mask = cultmath_erosion_ease_out(slope_length * p.onset.z); + float ridge_fade = fade_target; + float2 gully_slope = lerp(slope, slope / slope_length * p.assumed_slope.x, p.assumed_slope.y); + + [loop] for (int octave = 0; octave < min(max(p.octaves, 0), 8); octave++) + { + float4 phacelle = cultmath_phacelle_noise(position * frequency, cultmath_erosion_safe_normalize(gully_slope), p.cell_scale, 0.25, p.normalization); + float2 derivative_direction = phacelle.zw * -frequency; + float sloping = abs(phacelle.y); + gully_slope += sign(phacelle.y) * derivative_direction * strength * p.gully_weight; + float3 gullies = float3(phacelle.x, phacelle.y * derivative_direction.x, phacelle.y * derivative_direction.y); + float3 faded = lerp(float3(fade_target, 0.0, 0.0), gullies * p.gully_weight, combined_mask); + height_and_slope += faded * strength; + magnitude += strength; + fade_target = faded.x; + float octave_rounding = lerp(p.rounding.y, p.rounding.x, saturate(phacelle.x + 0.5)) * rounding_multiplier; + float new_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(sloping * p.onset.y, octave_rounding * p.onset.y)); + combined_mask = cultmath_erosion_pow_inverse(combined_mask, p.detail) * new_mask; + ridge_fade = lerp(ridge_fade, gullies.x, ridge_mask); + ridge_mask *= cultmath_erosion_ease_out(sloping * p.onset.w); + strength *= p.gain; + frequency *= p.lacunarity; + rounding_multiplier *= p.rounding.w; + } + + CultMathAdvancedErosionResult result; + result.delta = height_and_slope - input_value; + result.magnitude = magnitude; + result.ridge_map = ridge_fade * (1.0 - ridge_mask); + result.fade_target = fade_target; + return result; +} + +#endif diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index 07b2b8a9..c55a1790 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -263,6 +263,8 @@ float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, Sampl return cultmath_catmullrom(row0, row1, row2, row3, local.y); } +#include "AdvancedErosionFilter.hlsl" + struct CultMathSphericalErosionParameters { float frequency; float amplitude; float lacunarity; float gain; diff --git a/src/CultMath/AdvancedErosionFilter.cs b/src/CultMath/AdvancedErosionFilter.cs new file mode 100644 index 00000000..f004c327 --- /dev/null +++ b/src/CultMath/AdvancedErosionFilter.cs @@ -0,0 +1,129 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain +// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn + +namespace CultMath; + +public readonly record struct AdvancedErosionParameters( + float Scale, + float Strength, + float GullyWeight, + float Detail, + float4 Rounding, + float4 Onset, + float2 AssumedSlope, + float CellScale, + float Normalization, + int Octaves, + float Lacunarity, + float Gain) +{ + public static AdvancedErosionParameters Default => new( + 0.15f, 0.22f, 0.5f, 1.5f, + new float4(0.1f, 0.0f, 0.1f, 2.0f), + new float4(1.25f, 1.25f, 2.8f, 1.5f), + new float2(0.7f, 1.0f), + 0.7f, 0.5f, 5, 2.0f, 0.5f); +} + +public readonly record struct AdvancedErosionResult(float3 Delta, float Magnitude, float RidgeMap, float FadeTarget); + +public static class AdvancedErosionFilter +{ + public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p) + { + var strength = p.Strength * p.Scale; + fadeTarget = math.clamp(fadeTarget, -1.0f, 1.0f); + var input = baseHeightAndSlope; + var heightAndSlope = input; + var frequency = 1.0f / math.max(p.Scale * p.CellScale, 1.0e-10f); + var slope = new float2(heightAndSlope.y, heightAndSlope.z); + var slopeLength = math.max(math.length(slope), 1.0e-10f); + var magnitude = 0.0f; + var roundingMultiplier = 1.0f; + + var inputRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(fadeTarget + 0.5f)) * p.Rounding.z; + var combinedMask = EaseOut(SmoothStart(slopeLength * p.Onset.x, inputRounding * p.Onset.x)); + var ridgeMask = EaseOut(slopeLength * p.Onset.z); + var ridgeFade = fadeTarget; + var gullySlope = math.lerp(slope, slope / slopeLength * p.AssumedSlope.x, p.AssumedSlope.y); + + var octaves = math.min(math.max(p.Octaves, 0), 8); + for (var octave = 0; octave < octaves; octave++) + { + var phacelle = Phacelle(position * frequency, SafeNormalize(gullySlope), p.CellScale, 0.25f, p.Normalization); + var derivativeDirection = new float2(phacelle.z, phacelle.w) * -frequency; + var sloping = math.abs(phacelle.y); + gullySlope += math.sign(phacelle.y) * derivativeDirection * strength * p.GullyWeight; + + var gullies = new float3(phacelle.x, phacelle.y * derivativeDirection.x, phacelle.y * derivativeDirection.y); + var faded = math.lerp(new float3(fadeTarget, 0.0f, 0.0f), gullies * p.GullyWeight, combinedMask); + heightAndSlope += faded * strength; + magnitude += strength; + fadeTarget = faded.x; + + var octaveRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(phacelle.x + 0.5f)) * roundingMultiplier; + var newMask = EaseOut(SmoothStart(sloping * p.Onset.y, octaveRounding * p.Onset.y)); + combinedMask = PowInverse(combinedMask, p.Detail) * newMask; + + ridgeFade = math.lerp(ridgeFade, gullies.x, ridgeMask); + ridgeMask *= EaseOut(sloping * p.Onset.w); + strength *= p.Gain; + frequency *= p.Lacunarity; + roundingMultiplier *= p.Rounding.w; + } + + return new AdvancedErosionResult(heightAndSlope - input, magnitude, ridgeFade * (1.0f - ridgeMask), fadeTarget); + } + + public static float4 Phacelle(float2 position, float2 normalDirection, float frequency, float offsetCycles, float normalization) + { + var sideDirection = new float2(-normalDirection.y, normalDirection.x) * frequency * math.TAU; + var offset = offsetCycles * math.TAU; + var cell = math.floor(position); + var local = math.frac(position); + var phase = float2.zero; + var weightSum = 0.0f; + + for (var x = -1; x <= 2; x++) + for (var y = -1; y <= 2; y++) + { + var gridOffset = new float2(x, y); + var gridPoint = cell + gridOffset; + var randomOffset = Hash2(gridPoint) * 0.5f; + var delta = local - gridOffset - randomOffset; + var weight = math.max(0.0f, math.exp(-math.dot(delta, delta) * 2.0f) - 0.01111f); + weightSum += weight; + var wave = math.dot(delta, sideDirection) + offset; + phase += new float2(math.cos(wave), math.sin(wave)) * weight; + } + + var interpolated = phase / math.max(weightSum, 1.0e-20f); + var magnitude = math.max(1.0f - math.saturate(normalization), math.length(interpolated)); + var normalized = interpolated / math.max(magnitude, 1.0e-20f); + return new float4(normalized, sideDirection.x, sideDirection.y); + } + + private static float2 Hash2(float2 input) + { + var k = new float2(0.3183099f, 0.3678794f); + var x = input * k + new float2(k.y, k.x); + var scalar = math.frac(x.x * x.y * (x.x + x.y)); + return -float2.one + 2.0f * math.frac(16.0f * k * scalar); + } + + private static float2 SafeNormalize(float2 value) + { + var length = math.length(value); + return math.abs(length) > 1.0e-10f ? value / length : value; + } + + private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), power); + private static float EaseOut(float value) { var inverse = 1.0f - math.saturate(value); return 1.0f - inverse * inverse; } + private static float SmoothStart(float value, float smoothing) => smoothing <= 1.0e-10f || value >= smoothing + ? value - 0.5f * smoothing + : 0.5f * value * value / smoothing; +} diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs new file mode 100644 index 00000000..cccebd64 --- /dev/null +++ b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs @@ -0,0 +1,44 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class AdvancedErosionFilterTests +{ + [Fact] + public void DefaultReferenceFixtureIsStable() + { + var result = AdvancedErosionFilter.Sample( + new float2(0.42f, 0.31f), + new float3(0.5f, 0.1f, -0.2f), + 0.0f, + AdvancedErosionParameters.Default); + + Assert.Equal(0.0639375f, result.Magnitude, precision: 7); + Assert.True(float.IsFinite(result.Delta.x)); + Assert.True(float.IsFinite(result.Delta.y)); + Assert.True(float.IsFinite(result.Delta.z)); + Assert.InRange(result.RidgeMap, -1.0f, 1.0f); + } + + [Fact] + public void FlatInputAndZeroRoundingRemainFinite() + { + var parameters = AdvancedErosionParameters.Default with { Rounding = float4.zero }; + var result = AdvancedErosionFilter.Sample(new float2(0.25f, 0.75f), float3.zero, 0.0f, parameters); + + Assert.True(float.IsFinite(result.Delta.x)); + Assert.True(float.IsFinite(result.RidgeMap)); + Assert.True(float.IsFinite(result.FadeTarget)); + } + + [Fact] + public void PhacellePartialNormalizationIsFiniteAtEndpoints() + { + foreach (var normalization in new[] { 0.0f, 0.5f, 1.0f }) + { + var sample = AdvancedErosionFilter.Phacelle(new float2(1.7f, -3.2f), new float2(0.6f, -0.8f), 0.7f, 0.25f, normalization); + Assert.True(float.IsFinite(sample.x)); + Assert.True(float.IsFinite(sample.y)); + } + } +} diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index ed9119b7..0e904a32 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -7,11 +7,22 @@ public sealed class HlslMirrorTests [Fact] public void HlslMirrorPublishesSphericalErosionKernel() { - var source = File.ReadAllText(FindHlslMirror()); + var source = File.ReadAllText(Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl")); Assert.Contains("cultmath_spherical_erosion", source); Assert.Contains("CultMathSphericalErosionParameters", source); } + [Fact] + public void HlslMirrorPublishesFaithfulAdvancedErosionKernel() + { + var mirror = Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl"); + var source = File.ReadAllText(mirror); + Assert.Contains("AdvancedErosionFilter.hlsl", source); + var advanced = File.ReadAllText(Path.Combine(Path.GetDirectoryName(mirror)!, "AdvancedErosionFilter.hlsl")); + Assert.Contains("cultmath_phacelle_noise", advanced); + Assert.Contains("cultmath_advanced_erosion_filter", advanced); + } + [Fact] public void HlslMirrorPublishesCultMathPrimitives() { diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs b/tests/CultMath.Tests/SphericalErosionTests.cs index 8ae04859..53d37bce 100644 --- a/tests/CultMath.Tests/SphericalErosionTests.cs +++ b/tests/CultMath.Tests/SphericalErosionTests.cs @@ -1,3 +1,5 @@ +using Xunit; + namespace CultMath.Tests; public sealed class SphericalErosionTests From 33c017b284c11a0b54e54dc8aefef2a920a90a55 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 19:57:24 +0100 Subject: [PATCH 011/203] Make erosion cell hashing cross-device deterministic --- THIRD-PARTY-NOTICES.md | 5 ++++- shaders/AdvancedErosionFilter.hlsl | 11 ++++++++--- src/CultMath/AdvancedErosionFilter.cs | 14 ++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index c8976207..8339c5fe 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -15,7 +15,10 @@ Rune Skovbo Johansen's **Advanced Terrain Erosion Filter**: The CultMath files retain the MPL notice and separate the faithful planar filter from CultMath's spherical adaptation. Modifications include C#/HLSL type lowering, explicit bounded octave counts, finite zero-rounding behavior, -and CultMath naming. +and CultMath naming. CultMath replaces the reference floating hash with a +shared integer avalanche hash so C# and GPU evaluation select identical cell +pivots across devices; this changes the stochastic pattern, not the filter +mechanics. The technique builds on earlier work credited by the upstream author to Fewes and Clay John. The hash form follows the public shader math of Inigo Quilez. diff --git a/shaders/AdvancedErosionFilter.hlsl b/shaders/AdvancedErosionFilter.hlsl index 6c35dd47..38c3ef19 100644 --- a/shaders/AdvancedErosionFilter.hlsl +++ b/shaders/AdvancedErosionFilter.hlsl @@ -19,11 +19,16 @@ struct CultMathAdvancedErosionResult float3 delta; float magnitude; float ridge_map; float fade_target; }; +float cultmath_erosion_hash_unit(int2 cell, uint salt) +{ + uint value = (uint)cell.x * 0x8da6b343u ^ (uint)cell.y * 0xd8163841u ^ salt; + value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16; + return (value & 0x00ffffffu) * (1.0 / 16777216.0); +} float2 cultmath_erosion_hash2(float2 input) { - float2 k = float2(0.3183099, 0.3678794); - float2 x = input * k + k.yx; - return -1.0 + 2.0 * frac(16.0 * k * frac(x.x * x.y * (x.x + x.y))); + int2 cell = (int2)input; + return float2(cultmath_erosion_hash_unit(cell, 0x68bc21ebu), cultmath_erosion_hash_unit(cell, 0x02e5be93u)) * 2.0 - 1.0; } float4 cultmath_phacelle_noise(float2 position, float2 normal_direction, float frequency, float offset_cycles, float normalization) diff --git a/src/CultMath/AdvancedErosionFilter.cs b/src/CultMath/AdvancedErosionFilter.cs index f004c327..6780223f 100644 --- a/src/CultMath/AdvancedErosionFilter.cs +++ b/src/CultMath/AdvancedErosionFilter.cs @@ -109,10 +109,16 @@ public static class AdvancedErosionFilter private static float2 Hash2(float2 input) { - var k = new float2(0.3183099f, 0.3678794f); - var x = input * k + new float2(k.y, k.x); - var scalar = math.frac(x.x * x.y * (x.x + x.y)); - return -float2.one + 2.0f * math.frac(16.0f * k * scalar); + var x = (int)input.x; + var y = (int)input.y; + return new float2(HashUnit(x, y, 0x68bc21ebu), HashUnit(x, y, 0x02e5be93u)) * 2.0f - float2.one; + } + + private static float HashUnit(int x, int y, uint salt) + { + var value = unchecked((uint)x * 0x8da6b343u ^ (uint)y * 0xd8163841u ^ salt); + value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16; + return (value & 0x00ffffffu) * (1.0f / 16777216.0f); } private static float2 SafeNormalize(float2 value) From ddeac557cb2dbc9b47e14010ce5e4b9ce1252c98 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 20:02:45 +0100 Subject: [PATCH 012/203] Add physical erosion frequency bands --- shaders/AdvancedErosionFilter.hlsl | 42 ++++++++++++-- src/CultMath/AdvancedErosionFilter.cs | 14 +++-- src/CultMath/ErosionFrequencyBands.cs | 57 +++++++++++++++++++ .../ErosionFrequencyBandsTests.cs | 34 +++++++++++ 4 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 src/CultMath/ErosionFrequencyBands.cs create mode 100644 tests/CultMath.Tests/ErosionFrequencyBandsTests.cs diff --git a/shaders/AdvancedErosionFilter.hlsl b/shaders/AdvancedErosionFilter.hlsl index 38c3ef19..a0a350e5 100644 --- a/shaders/AdvancedErosionFilter.hlsl +++ b/shaders/AdvancedErosionFilter.hlsl @@ -19,6 +19,28 @@ struct CultMathAdvancedErosionResult float3 delta; float magnitude; float ridge_map; float fade_target; }; +struct CultMathErosionBandSelection +{ + int active_octaves; float final_octave_weight; float finest_included_wavelength; float unresolved_height_bound; +}; + +CultMathErosionBandSelection cultmath_select_erosion_bands(float base_wavelength, float sample_spacing, int maximum_octaves, float lacunarity, float base_amplitude, float gain, float transition_ratio) +{ + base_wavelength=max(base_wavelength,1.0e-6); sample_spacing=max(sample_spacing,0.0); lacunarity=max(lacunarity,1.01); transition_ratio=max(transition_ratio,1.01); maximum_octaves=min(max(maximum_octaves,0),16); + float nyquist=sample_spacing*2.0, wavelength=base_wavelength, final_weight=0.0, finest=0.0; int active=0; + [loop] for(int octave=0; octave=active) omitted+=amplitude; else if(index==active-1) omitted+=amplitude*(1.0-final_weight); amplitude*=abs(gain); + } + CultMathErosionBandSelection result; result.active_octaves=active; result.final_octave_weight=active>0?final_weight:0.0; result.finest_included_wavelength=finest; result.unresolved_height_bound=omitted; return result; +} + float cultmath_erosion_hash_unit(int2 cell, uint salt) { uint value = (uint)cell.x * 0x8da6b343u ^ (uint)cell.y * 0xd8163841u ^ salt; @@ -68,7 +90,7 @@ float2 cultmath_erosion_safe_normalize(float2 value) return abs(value_length) > 1.0e-10 ? value / value_length : value; } -CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p) +CultMathAdvancedErosionResult cultmath_advanced_erosion_filter_banded(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p, CultMathErosionBandSelection band) { float strength = p.strength * p.scale; fade_target = clamp(fade_target, -1.0, 1.0); @@ -85,7 +107,8 @@ CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float ridge_fade = fade_target; float2 gully_slope = lerp(slope, slope / slope_length * p.assumed_slope.x, p.assumed_slope.y); - [loop] for (int octave = 0; octave < min(max(p.octaves, 0), 8); octave++) + int octave_count=min(min(max(p.octaves,0),8),max(band.active_octaves,0)); + [loop] for (int octave = 0; octave < octave_count; octave++) { float4 phacelle = cultmath_phacelle_noise(position * frequency, cultmath_erosion_safe_normalize(gully_slope), p.cell_scale, 0.25, p.normalization); float2 derivative_direction = phacelle.zw * -frequency; @@ -93,14 +116,15 @@ CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, gully_slope += sign(phacelle.y) * derivative_direction * strength * p.gully_weight; float3 gullies = float3(phacelle.x, phacelle.y * derivative_direction.x, phacelle.y * derivative_direction.y); float3 faded = lerp(float3(fade_target, 0.0, 0.0), gullies * p.gully_weight, combined_mask); - height_and_slope += faded * strength; - magnitude += strength; + float octave_weight=octave==octave_count-1?saturate(band.final_octave_weight):1.0; + height_and_slope += faded * strength * octave_weight; + magnitude += strength * octave_weight; fade_target = faded.x; float octave_rounding = lerp(p.rounding.y, p.rounding.x, saturate(phacelle.x + 0.5)) * rounding_multiplier; float new_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(sloping * p.onset.y, octave_rounding * p.onset.y)); combined_mask = cultmath_erosion_pow_inverse(combined_mask, p.detail) * new_mask; - ridge_fade = lerp(ridge_fade, gullies.x, ridge_mask); - ridge_mask *= cultmath_erosion_ease_out(sloping * p.onset.w); + ridge_fade = lerp(ridge_fade, lerp(ridge_fade, gullies.x, ridge_mask), octave_weight); + ridge_mask *= lerp(1.0, cultmath_erosion_ease_out(sloping * p.onset.w), octave_weight); strength *= p.gain; frequency *= p.lacunarity; rounding_multiplier *= p.rounding.w; @@ -114,4 +138,10 @@ CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, return result; } +CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p) +{ + CultMathErosionBandSelection band; band.active_octaves=p.octaves; band.final_octave_weight=1.0; band.finest_included_wavelength=0.0; band.unresolved_height_bound=0.0; + return cultmath_advanced_erosion_filter_banded(position, base_height_and_slope, fade_target, p, band); +} + #endif diff --git a/src/CultMath/AdvancedErosionFilter.cs b/src/CultMath/AdvancedErosionFilter.cs index 6780223f..65db3671 100644 --- a/src/CultMath/AdvancedErosionFilter.cs +++ b/src/CultMath/AdvancedErosionFilter.cs @@ -34,6 +34,9 @@ public readonly record struct AdvancedErosionResult(float3 Delta, float Magnitud public static class AdvancedErosionFilter { public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p) + => Sample(position, baseHeightAndSlope, fadeTarget, p, new ErosionBandSelection(p.Octaves, 1.0f, 0.0f, 0.0f)); + + public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p, ErosionBandSelection band) { var strength = p.Strength * p.Scale; fadeTarget = math.clamp(fadeTarget, -1.0f, 1.0f); @@ -51,7 +54,7 @@ public static class AdvancedErosionFilter var ridgeFade = fadeTarget; var gullySlope = math.lerp(slope, slope / slopeLength * p.AssumedSlope.x, p.AssumedSlope.y); - var octaves = math.min(math.max(p.Octaves, 0), 8); + var octaves = math.min(math.min(math.max(p.Octaves, 0), 8), math.max(band.ActiveOctaves, 0)); for (var octave = 0; octave < octaves; octave++) { var phacelle = Phacelle(position * frequency, SafeNormalize(gullySlope), p.CellScale, 0.25f, p.Normalization); @@ -61,16 +64,17 @@ public static class AdvancedErosionFilter var gullies = new float3(phacelle.x, phacelle.y * derivativeDirection.x, phacelle.y * derivativeDirection.y); var faded = math.lerp(new float3(fadeTarget, 0.0f, 0.0f), gullies * p.GullyWeight, combinedMask); - heightAndSlope += faded * strength; - magnitude += strength; + var octaveWeight = octave == octaves - 1 ? math.saturate(band.FinalOctaveWeight) : 1.0f; + heightAndSlope += faded * strength * octaveWeight; + magnitude += strength * octaveWeight; fadeTarget = faded.x; var octaveRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(phacelle.x + 0.5f)) * roundingMultiplier; var newMask = EaseOut(SmoothStart(sloping * p.Onset.y, octaveRounding * p.Onset.y)); combinedMask = PowInverse(combinedMask, p.Detail) * newMask; - ridgeFade = math.lerp(ridgeFade, gullies.x, ridgeMask); - ridgeMask *= EaseOut(sloping * p.Onset.w); + ridgeFade = math.lerp(ridgeFade, math.lerp(ridgeFade, gullies.x, ridgeMask), octaveWeight); + ridgeMask *= math.lerp(1.0f, EaseOut(sloping * p.Onset.w), octaveWeight); strength *= p.Gain; frequency *= p.Lacunarity; roundingMultiplier *= p.Rounding.w; diff --git a/src/CultMath/ErosionFrequencyBands.cs b/src/CultMath/ErosionFrequencyBands.cs new file mode 100644 index 00000000..3c63eb62 --- /dev/null +++ b/src/CultMath/ErosionFrequencyBands.cs @@ -0,0 +1,57 @@ +namespace CultMath; + +public readonly record struct ErosionBandSelection( + int ActiveOctaves, + float FinalOctaveWeight, + float FinestIncludedWavelength, + float UnresolvedHeightBound) +{ + public bool HasSamples => ActiveOctaves > 0; +} + +public static class ErosionFrequencyBands +{ + public static ErosionBandSelection Select( + float baseWavelength, + float sampleSpacing, + int maximumOctaves, + float lacunarity, + float baseAmplitude, + float gain, + float transitionRatio = 2.0f) + { + baseWavelength = math.max(baseWavelength, 1.0e-6f); + sampleSpacing = math.max(sampleSpacing, 0.0f); + lacunarity = math.max(lacunarity, 1.01f); + transitionRatio = math.max(transitionRatio, 1.01f); + maximumOctaves = math.min(math.max(maximumOctaves, 0), 16); + var nyquistWavelength = sampleSpacing * 2.0f; + var wavelength = baseWavelength; + var active = 0; + var finalWeight = 0.0f; + var finest = 0.0f; + + for (var octave = 0; octave < maximumOctaves; octave++) + { + var weight = nyquistWavelength <= 0.0f + ? 1.0f + : math.smoothstep(nyquistWavelength, nyquistWavelength * transitionRatio, wavelength); + if (weight <= 0.0f) break; + active++; + finalWeight = weight; + finest = wavelength; + wavelength /= lacunarity; + } + + var omittedAmplitude = 0.0f; + var amplitude = math.abs(baseAmplitude); + for (var octave = 0; octave < maximumOctaves; octave++) + { + if (octave >= active) omittedAmplitude += amplitude; + else if (octave == active - 1) omittedAmplitude += amplitude * (1.0f - finalWeight); + amplitude *= math.abs(gain); + } + + return new ErosionBandSelection(active, active > 0 ? finalWeight : 0.0f, finest, omittedAmplitude); + } +} diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs new file mode 100644 index 00000000..f57213a4 --- /dev/null +++ b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs @@ -0,0 +1,34 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class ErosionFrequencyBandsTests +{ + [Fact] + public void RadiusIndependentWorldWavelengthSelectsSameBands() + { + var small = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f); + var large = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f); + Assert.Equal(small, large); + Assert.Equal(3, small.ActiveOctaves); + } + + [Fact] + public void TransitionPartiallyWeightsLastResolvableOctave() + { + var selection = ErosionFrequencyBands.Select(1_000.0f, 50.0f, 8, 2.0f, 1.0f, 0.5f); + Assert.Equal(4, selection.ActiveOctaves); + Assert.InRange(selection.FinalOctaveWeight, 0.0f, 1.0f); + Assert.True(selection.UnresolvedHeightBound > 0.0f); + } + + [Fact] + public void BandedFilterPreservesLowOctaveResultExactly() + { + var p = AdvancedErosionParameters.Default; + var band = new ErosionBandSelection(2, 1.0f, 0.0f, 0.0f); + var banded = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p, band); + var truncated = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p with { Octaves = 2 }); + Assert.Equal(truncated, banded); + } +} From e870663fe038d5ed532a48243b93bb376796ab2d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 00:41:25 +0100 Subject: [PATCH 013/203] Make spherical erosion diagnostics finite --- src/CultMath/float3.cs | 2 ++ tests/CultMath.Tests/SphericalErosionTests.cs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CultMath/float3.cs b/src/CultMath/float3.cs index e9833ae4..4a4efeba 100644 --- a/src/CultMath/float3.cs +++ b/src/CultMath/float3.cs @@ -67,6 +67,8 @@ public record struct float3(float x, float y, float z) public static explicit operator System.Numerics.Vector3(float3 value) => new(value.x, value.y, value.z); public static explicit operator float3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z); + public override readonly string ToString() => $"float3({x:R}, {y:R}, {z:R})"; + public static float3 operator +(float3 value) => value; public static float3 operator -(float3 value) => new(-value.x, -value.y, -value.z); public static float3 operator +(float3 left, float3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z); diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs b/tests/CultMath.Tests/SphericalErosionTests.cs index 53d37bce..f634ba6d 100644 --- a/tests/CultMath.Tests/SphericalErosionTests.cs +++ b/tests/CultMath.Tests/SphericalErosionTests.cs @@ -9,9 +9,14 @@ public sealed class SphericalErosionTests { var parameters = SphericalErosionParameters.Default with { Seed = 42.0f }; var a = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); + var repeated = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); var b = SphericalErosion.Sample(new float3(3.1f, -7.4f, 5.9f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); - Assert.Equal(a, b); + Assert.Equal(a, repeated); + Assert.InRange(math.abs(a.HeightOffset-b.HeightOffset),0.0f,1.0e-6f); + Assert.InRange(math.abs(a.Ridge-b.Ridge),0.0f,1.0e-6f); + Assert.InRange(math.abs(a.Gully-b.Gully),0.0f,5.0e-5f); + Assert.InRange(math.length(a.Gradient-b.Gradient),0.0f,5.0e-6f); Assert.InRange(a.HeightOffset, -0.08f, 0.08f); Assert.InRange(a.Ridge, 0.0f, 1.0f); Assert.InRange(a.Gully, 0.0f, 1.0f); From 15f27173ddf4bf6d5d0380b0d79cd644ce42f6e8 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 00:45:25 +0100 Subject: [PATCH 014/203] Share planetary radial refinement semantics --- shaders/CultMath.hlsl | 19 ++++++++++++ src/CultMath/PlanetaryRadialRefinement.cs | 28 ++++++++++++++++++ .../PlanetaryRadialRefinementTests.cs | 29 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/CultMath/PlanetaryRadialRefinement.cs create mode 100644 tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index c55a1790..e7281c99 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -337,4 +337,23 @@ CultMathSphericalErosionSample cultmath_spherical_erosion(float3 unit_position, return result; } +float3 cultmath_planetary_radial_refinement_step( + float3 position, + float3 ray_direction, + float3 center, + float target_radius, + float minimum_derivative, + float maximum_correction) +{ + float3 local=position-center; + float radius=length(local); + float3 radial=local/max(radius,0.0001); + float error=radius-target_radius; + float derivative=dot(ray_direction,radial); + float safe_minimum=max(minimum_derivative,0.0001); + float safe_derivative=abs(derivative)>safe_minimum?derivative:(derivative<0?-safe_minimum:safe_minimum); + float correction=clamp(-error/safe_derivative,-abs(maximum_correction),abs(maximum_correction)); + return position+ray_direction*correction; +} + #endif diff --git a/src/CultMath/PlanetaryRadialRefinement.cs b/src/CultMath/PlanetaryRadialRefinement.cs new file mode 100644 index 00000000..7188a513 --- /dev/null +++ b/src/CultMath/PlanetaryRadialRefinement.cs @@ -0,0 +1,28 @@ +namespace CultMath; + +public static class PlanetaryRadialRefinement +{ + public static float3 Step( + float3 position, + float3 rayDirection, + float3 center, + float targetRadius, + float minimumDerivative = 0.2f, + float maximumCorrection = 0.08f) + { + var local = position - center; + var radius = math.length(local); + var radial = local / math.max(radius, 0.0001f); + var error = radius - targetRadius; + var derivative = math.dot(rayDirection, radial); + var safeMinimum = math.max(minimumDerivative, 0.0001f); + var safeDerivative = math.abs(derivative) > safeMinimum + ? derivative + : derivative < 0.0f ? -safeMinimum : safeMinimum; + var correction = math.clamp( + -error / safeDerivative, + -math.abs(maximumCorrection), + math.abs(maximumCorrection)); + return position + rayDirection * correction; + } +} diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs new file mode 100644 index 00000000..d0263b67 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs @@ -0,0 +1,29 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryRadialRefinementTests +{ + [Fact] + public void FourBoundedStepsConvergeFromTheRasterCaptureShell() + { + var center=new float3(0.3f,-0.2f,0.7f); + var radial=math.normalize(new float3(0.2f,-0.4f,0.9f)); + var targetRadius=3.85f; + var target=center+radial*targetRadius; + var origin=target+math.normalize(radial+new float3(0.12f,-0.05f,0.02f))*6.0f; + var position=center+radial*(targetRadius+0.30f); + var ray=math.normalize(position-origin); + for(var step=0;step<4;step++) + position=PlanetaryRadialRefinement.Step(position,ray,center,targetRadius); + Assert.InRange(math.abs(math.length(position-center)-targetRadius),0.0f,1.0e-4f); + } + + [Fact] + public void StepClampsWeakDerivativeCorrections() + { + var position=new float3(0,0,4.1f); + var result=PlanetaryRadialRefinement.Step(position,new float3(1,0,0),float3.zero,3.8f); + Assert.InRange(math.length(result-position),0.07999f,0.08001f); + } +} From 42fef7cdd70e8d8b98fab56a58cfd1a361120db7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 02:14:11 +0100 Subject: [PATCH 015/203] Build portable planetary field runtime --- README.md | 6 + docs/design.md | 6 + docs/planetary.md | 117 +++++++ shaders/CultMath.hlsl | 1 + shaders/Planetary.hlsl | 260 ++++++++++++++ src/CultMath/CultMath.csproj | 2 + src/CultMath/PlanetaryField.cs | 213 ++++++++++++ src/CultMath/PlanetaryPages.cs | 185 ++++++++++ src/CultMath/PlanetaryPatch.cs | 42 +++ src/CultMath/PlanetaryProjection.cs | 320 ++++++++++++++++++ src/CultMath/PlanetaryQueries.cs | 191 +++++++++++ src/CultMath/PlanetaryResidency.cs | 144 ++++++++ src/CultMath/PlanetaryTopology.cs | 170 ++++++++++ tests/CultMath.Tests/HlslMirrorTests.cs | 16 + tests/CultMath.Tests/PlanetaryFieldTests.cs | 80 +++++ tests/CultMath.Tests/PlanetaryPageTests.cs | 57 ++++ tests/CultMath.Tests/PlanetaryPatchTests.cs | 40 +++ .../PlanetaryProjectionTests.cs | 73 ++++ tests/CultMath.Tests/PlanetaryQueryTests.cs | 46 +++ .../CultMath.Tests/PlanetaryResidencyTests.cs | 57 ++++ .../CultMath.Tests/PlanetaryTopologyTests.cs | 55 +++ unity/Runtime/CultMath.Unity.asmdef | 16 + unity/Runtime/PlanetaryPageUpload.cs | 81 +++++ unity/Runtime/PlanetaryPatchMeshAdapter.cs | 36 ++ 24 files changed, 2214 insertions(+) create mode 100644 docs/planetary.md create mode 100644 shaders/Planetary.hlsl create mode 100644 src/CultMath/PlanetaryField.cs create mode 100644 src/CultMath/PlanetaryPages.cs create mode 100644 src/CultMath/PlanetaryPatch.cs create mode 100644 src/CultMath/PlanetaryProjection.cs create mode 100644 src/CultMath/PlanetaryQueries.cs create mode 100644 src/CultMath/PlanetaryResidency.cs create mode 100644 src/CultMath/PlanetaryTopology.cs create mode 100644 tests/CultMath.Tests/PlanetaryFieldTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryPageTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryPatchTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryProjectionTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryQueryTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryResidencyTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryTopologyTests.cs create mode 100644 unity/Runtime/CultMath.Unity.asmdef create mode 100644 unity/Runtime/PlanetaryPageUpload.cs create mode 100644 unity/Runtime/PlanetaryPatchMeshAdapter.cs diff --git a/README.md b/README.md index 1228736a..29eb115f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,12 @@ shader semantics. - `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. +- `PlanetaryField`, a scale-aware CPU/HLSL spherical field contract with + deterministic identity, cube-sphere topology, residual pages, LOD residency, + ray/path/region queries, coarse patches, and projected map-tile baking. See + `docs/planetary.md`. +- `CultMath.Unity`, an engine-facing assembly containing only Unity mesh and + page-upload adapters for the canonical planetary contracts. ## Native Core diff --git a/docs/design.md b/docs/design.md index 8250ba78..f4ca5b49 100644 --- a/docs/design.md +++ b/docs/design.md @@ -19,6 +19,12 @@ Rust hot kernels, C# host code, and HLSL shader code. - Keep the surface boring and exact. Noise primitives are allowed when they are source-grounded, shader-shaped, and shared by more than one project. Matrices, quaternions, and packing helpers still need an owner before they enter. +- Planetary geometry is a scale-aware field query, not a renderer asset. CPU + simulation is the reference consumer; pages, patches, and maps are derived + lowerings carrying field identity and unresolved error. +- Application base fields and gameplay meaning enter through narrow inputs. + CultMath does not own biomes, resources, traversability, render materials, or + committed game consequences. ## Non-Goals diff --git a/docs/planetary.md b/docs/planetary.md new file mode 100644 index 00000000..8196c1c7 --- /dev/null +++ b/docs/planetary.md @@ -0,0 +1,117 @@ +# Planetary Field Runtime + +CultMath owns the deterministic, renderer-independent planetary field. The same +definition can be queried by a CPU simulation, baked into residual pages, +sampled through a map projection, or evaluated by HLSL. Render resources and +gameplay interpretation remain consumer responsibilities. + +## Authority + +- `PlanetaryFieldDefinition` identifies radius, seed, erosion parameters, and a + version that includes the consumer-owned base-field version. +- `IPlanetaryBaseField` supplies deterministic application-authored base + evidence without making CultMath depend on an application. +- `PlanetaryQueryScale` makes the physical footprint and accepted unresolved + error explicit. There is deliberately no scale-free authoritative + `HeightAt` call. +- `PlanetarySurfaceSample` carries displacement, tangent gradient, normal, + slope, ridge/gully evidence, resolved wavelength, and unresolved bound. +- pages, residency, patches, and map tiles are disposable lowerings of that + contract. + +The C# surface targets .NET and Unity-compatible `netstandard2.1`. The matching +HLSL lives in `shaders/Planetary.hlsl` and is included by `CultMath.hlsl`. + +## Point and batch queries + +```csharp +var erosion = AdvancedErosionParameters.Default with +{ + Scale = radius * 0.075f, + Strength = 0.12f, + Octaves = 7, +}; +var field = PlanetaryFieldDefinition.Create( + baseFieldVersion: 17, + radius, + seed: 42, + erosion); +var scale = PlanetaryQueryScale.AtFootprint( + footprintMeters: 10, + maximumUnresolvedHeight: 0.5f); + +PlanetaryBaseFieldSample basis = source.Sample(direction); +PlanetarySurfaceSample sample = PlanetaryField.Sample( + field, direction, basis, scale); +``` + +`PlanetaryField.SampleBatch` writes into a caller-owned span. `SamplePosition` +accepts `double3`, reduces the astronomical position to a stable unit direction, +and then evaluates the same float field contract. + +Higher-level CPU queries include bounded ray intersection, segment clearance, +great-circle profiles, and region summaries. Their answers remain field queries; +only a consuming simulation can turn them into committed game consequences. + +## Cube-sphere topology and pages + +`PlanetaryTopology` owns the tangent/QSC cube-sphere mapping, inverse face +coordinates, tile selection, local page coordinates, and radial-graph surface +normal. `PlanetaryPageSampling` owns bordered directions, nominal spacing, +bilinear sampling, and summaries. + +`PlanetaryPageBaker` evaluates a root page at its physical sample spacing. A +child stores `child filtered field - parent filtered field`. A +`PlanetaryPageSetSampling` query adds every containing ancestor contribution +and applies transition weights only to residuals. + +`PlanetaryLodSelector` selects six roots plus one camera-facing ancestor chain +from the unresolved-height error. `PlanetaryResidualResidency` owns arrival and +departure presentation weights, content and presentation versions, and nothing +about field truth. + +## Patches + +`PlanetaryPatch.CreateFace` and `CreateCubeSphere` produce renderer-neutral +coarse meshes from the canonical topology. HLSL calls +`cultmath_planetary_face_direction` for procedural vertices and +`cultmath_planetary_radial_refinement_step` for bounded pixel refinement. + +Renderers still own cameras, clip space, depth, materials, buffers, dispatch, +and draw submission. + +The optional `CultMath.Unity` assembly converts a canonical face patch into a +Unity `Mesh` and converts a residency snapshot into Unity-native page input and +metadata payloads. It owns no field parameters, page selection, or simulation +state. Unity render code remains responsible for allocating/binding graphics +buffers and dispatching application-specific base-field entry points. + +## Maps + +`PlanetaryProjection` supplies forward and inverse transforms for: + +- equirectangular; +- Web Mercator with an explicit polar cutoff; +- Equal Earth; +- orthographic; +- azimuthal equidistant and equal-area; +- cube atlas; +- local tangent/gnomonic views. + +`PlanetaryMapTileBaker` maps each valid tile sample through the inverse +projection and canonical field query. `PlanetaryMapTileKey` includes field, +projection, layer, layout, and query-scale identity. Palettes, contours, labels, +and strategic overlays do not enter CultMath. + +## Numeric contract + +Integer hashes and published field versions are exact. Basic arithmetic follows +CultMath's matched C#/HLSL semantics. Hardware transcendental functions such as +`atan` and `tan` use measured tolerances rather than fictional bit identity. +Every filtered terrain query carries an unresolved-height bound so consumers can +reject insufficient fidelity explicitly. + +Current D3D12 parity evidence covers the advanced erosion kernel and the QSC +forward/inverse topology. The remaining production page and patch shaders use +the common functions and retain their seam, residual, summary, and radial-hit +integration probes in Fensalir. diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index e7281c99..cf76c8c2 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -264,6 +264,7 @@ float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, Sampl } #include "AdvancedErosionFilter.hlsl" +#include "Planetary.hlsl" struct CultMathSphericalErosionParameters { diff --git a/shaders/Planetary.hlsl b/shaders/Planetary.hlsl new file mode 100644 index 00000000..729c6332 --- /dev/null +++ b/shaders/Planetary.hlsl @@ -0,0 +1,260 @@ +#ifndef CULTMATH_PLANETARY_HLSL +#define CULTMATH_PLANETARY_HLSL + +static const float CULTMATH_PLANETARY_QUARTER_PI = 0.78539816339744830962; + +float3 cultmath_planetary_face_direction(int face, float2 coordinate) +{ + float2 tangent = tan(coordinate * CULTMATH_PLANETARY_QUARTER_PI); + float3 cube = face == 0 ? float3(1.0, tangent.y, -tangent.x) + : face == 1 ? float3(-1.0, tangent.y, tangent.x) + : face == 2 ? float3(tangent.x, 1.0, -tangent.y) + : face == 3 ? float3(tangent.x, -1.0, tangent.y) + : face == 4 ? float3(tangent.x, tangent.y, 1.0) + : float3(-tangent.x, tangent.y, -1.0); + return cultmath_normalize(cube); +} + +float2 cultmath_planetary_face_coordinate(float3 direction, out int face) +{ + float3 d = cultmath_normalize(direction); + float3 a = abs(d); + float2 cube_uv; + if (a.x >= a.y && a.x >= a.z) + { + if (d.x >= 0.0) { face = 0; cube_uv = float2(-d.z, d.y) / a.x; } + else { face = 1; cube_uv = float2(d.z, d.y) / a.x; } + } + else if (a.y >= a.z) + { + if (d.y >= 0.0) { face = 2; cube_uv = float2(d.x, -d.z) / a.y; } + else { face = 3; cube_uv = float2(d.x, d.z) / a.y; } + } + else + { + if (d.z >= 0.0) { face = 4; cube_uv = d.xy / a.z; } + else { face = 5; cube_uv = float2(-d.x, d.y) / a.z; } + } + return atan(cube_uv) / CULTMATH_PLANETARY_QUARTER_PI; +} + +bool cultmath_planetary_page_local(float3 direction, float4 tile_address, float active, out float2 local) +{ + int face; + float2 uv = cultmath_planetary_face_coordinate(direction, face); + local = 0.0; + if (active < 0.5 || abs((float)face - tile_address.x) > 0.25) return false; + float axis_tiles = exp2(tile_address.y); + float2 scaled = (uv * 0.5 + 0.5) * axis_tiles; + float2 canonical = floor(min(scaled, axis_tiles - 1.0e-5)); + if (any(abs(canonical - tile_address.zw) > 0.25)) return false; + local = scaled - tile_address.zw; + return all(local >= 0.0) && all(local <= 1.0); +} + +float3 cultmath_planetary_surface_normal(float3 direction, float3 world_distance_tangent_gradient) +{ + direction = cultmath_normalize(direction); + float3 tangent_gradient = world_distance_tangent_gradient - direction * dot(world_distance_tangent_gradient, direction); + return cultmath_normalize(direction - tangent_gradient); +} + +float2 cultmath_planetary_equirectangular_forward(float3 direction) +{ + direction = cultmath_normalize(direction); + return float2(atan2(direction.y, direction.x) / CULTMATH_PI, asin(clamp(direction.z, -1.0, 1.0)) / CULTMATH_HALF_PI); +} + +float3 cultmath_planetary_equirectangular_inverse(float2 coordinate) +{ + float longitude = coordinate.x * CULTMATH_PI; + float latitude = coordinate.y * CULTMATH_HALF_PI; + float cos_latitude = cos(latitude); + return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); +} + +static const float CULTMATH_EQUAL_EARTH_A1 = 1.340264; +static const float CULTMATH_EQUAL_EARTH_A2 = -0.081106; +static const float CULTMATH_EQUAL_EARTH_A3 = 0.000893; +static const float CULTMATH_EQUAL_EARTH_A4 = 0.003796; +static const float CULTMATH_EQUAL_EARTH_M = 0.8660254037844386; +static const float CULTMATH_EQUAL_EARTH_X_MAX = 2.706629; +static const float CULTMATH_EQUAL_EARTH_Y_MAX = 1.3173628; + +float cultmath_equal_earth_y(float theta) +{ + float theta2 = theta * theta; + float theta6 = theta2 * theta2 * theta2; + return theta * (CULTMATH_EQUAL_EARTH_A1 + CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (CULTMATH_EQUAL_EARTH_A3 + CULTMATH_EQUAL_EARTH_A4 * theta2)); +} + +float2 cultmath_planetary_equal_earth_forward(float3 direction) +{ + direction = cultmath_normalize(direction); + float longitude = atan2(direction.y, direction.x); + float latitude = asin(clamp(direction.z, -1.0, 1.0)); + float theta = asin(CULTMATH_EQUAL_EARTH_M * sin(latitude)); + float theta2 = theta * theta; + float theta6 = theta2 * theta2 * theta2; + float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2)); + return float2(longitude * cos(theta) / denominator / CULTMATH_EQUAL_EARTH_X_MAX, cultmath_equal_earth_y(theta) / CULTMATH_EQUAL_EARTH_Y_MAX); +} + +float3 cultmath_planetary_equal_earth_inverse(float2 coordinate) +{ + float target_y = coordinate.y * CULTMATH_EQUAL_EARTH_Y_MAX; + float theta = target_y / CULTMATH_EQUAL_EARTH_A1; + [unroll] for (int iteration = 0; iteration < 8; iteration++) + { + float theta2 = theta * theta; + float theta6 = theta2 * theta2 * theta2; + float derivative = CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2); + theta -= (cultmath_equal_earth_y(theta) - target_y) / derivative; + } + float latitude = asin(clamp(sin(theta) / CULTMATH_EQUAL_EARTH_M, -1.0, 1.0)); + float theta2 = theta * theta; + float theta6 = theta2 * theta2 * theta2; + float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2)); + float longitude = coordinate.x * CULTMATH_EQUAL_EARTH_X_MAX * denominator / cos(theta); + float cos_latitude = cos(latitude); + return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); +} + +struct CultMathPlanetaryFieldDefinition +{ + float radius; + int seed; + CultMathAdvancedErosionParameters erosion; +}; + +struct CultMathPlanetaryBaseFieldSample +{ + float radial_displacement; + float3 radial_gradient; + float field_value; + float3 field_gradient; + float fade_target; +}; + +struct CultMathPlanetarySurfaceSample +{ + float radial_displacement; + float3 tangent_gradient; + float3 surface_normal; + float slope; + float ridge; + float gully; + float finest_resolved_wavelength; + float unresolved_height_bound; +}; + +struct CultMathPlanetaryPageSample +{ + float4 height_gradient; + float2 masks; +}; + +CultMathPlanetaryPageSample cultmath_planetary_page_lerp( + CultMathPlanetaryPageSample a, + CultMathPlanetaryPageSample b, + CultMathPlanetaryPageSample c, + CultMathPlanetaryPageSample d, + float2 fraction) +{ + CultMathPlanetaryPageSample result; + result.height_gradient = lerp(lerp(a.height_gradient, b.height_gradient, fraction.x), lerp(c.height_gradient, d.height_gradient, fraction.x), fraction.y); + result.masks = lerp(lerp(a.masks, b.masks, fraction.x), lerp(c.masks, d.masks, fraction.x), fraction.y); + return result; +} + +CultMathPlanetaryPageSample cultmath_planetary_residual_sample( + CultMathPlanetarySurfaceSample child, + CultMathPlanetarySurfaceSample parent, + bool has_parent) +{ + CultMathPlanetaryPageSample result; + result.height_gradient = float4(child.radial_displacement, child.tangent_gradient); + if (has_parent) result.height_gradient -= float4(parent.radial_displacement, parent.tangent_gradient); + result.masks = float2(child.ridge, child.gully); + return result; +} + +float4 cultmath_planetary_summary_empty() +{ + return float4(3.402823466e+38, -3.402823466e+38, 0.0, 0.0); +} + +float4 cultmath_planetary_summary_accumulate(float4 summary, float4 height_gradient, float unresolved_height) +{ + summary.x = min(summary.x, height_gradient.x); + summary.y = max(summary.y, height_gradient.x); + summary.z = max(summary.z, length(height_gradient.yzw)); + summary.w = max(summary.w, max(unresolved_height, 0.0)); + return summary; +} + +float cultmath_planetary_pow4(float value) { float square = value * value; return square * square; } + +CultMathPlanetarySurfaceSample cultmath_planetary_field_sample( + CultMathPlanetaryFieldDefinition definition, + float3 unit_direction, + CultMathPlanetaryBaseFieldSample base_sample, + float footprint_meters) +{ + float3 direction = cultmath_normalize(unit_direction); + float3 radial_gradient = base_sample.radial_gradient - direction * dot(base_sample.radial_gradient, direction); + float3 field_gradient = base_sample.field_gradient - direction * dot(base_sample.field_gradient, direction); + CultMathAdvancedErosionParameters p = definition.erosion; + CultMathErosionBandSelection band = cultmath_select_erosion_bands( + p.scale * p.cell_scale, footprint_meters, p.octaves, p.lacunarity, + p.strength * p.scale, p.gain, 2.0); + float3 powers = float3( + cultmath_planetary_pow4(abs(direction.x)), + cultmath_planetary_pow4(abs(direction.y)), + cultmath_planetary_pow4(abs(direction.z))); + float power_sum = max(powers.x + powers.y + powers.z, 1.0e-6); + float3 weights = powers / power_sum; + float3 world = direction * definition.radius; + float seed = (float)definition.seed; + CultMathAdvancedErosionResult xy = cultmath_advanced_erosion_filter_banded( + world.xy + float2(713.0 + seed * 0.754877666, -291.0 + seed * 0.569840296), + float3(base_sample.field_value, field_gradient.x, field_gradient.y), base_sample.fade_target, p, band); + CultMathAdvancedErosionResult yz = cultmath_advanced_erosion_filter_banded( + world.yz + float2(-431.0 + seed * 0.438289027, 887.0 + seed * 0.328438163), + float3(base_sample.field_value, field_gradient.y, field_gradient.z), base_sample.fade_target, p, band); + CultMathAdvancedErosionResult zx = cultmath_advanced_erosion_filter_banded( + world.zx + float2(197.0 + seed * 0.219783071, 557.0 + seed * 0.145898034), + float3(base_sample.field_value, field_gradient.z, field_gradient.x), base_sample.fade_target, p, band); + float erosion_height = xy.delta.x * weights.z + yz.delta.x * weights.x + zx.delta.x * weights.y; + float3 erosion_gradient = float3( + xy.delta.y * weights.z + zx.delta.z * weights.y, + xy.delta.z * weights.z + yz.delta.y * weights.x, + yz.delta.z * weights.x + zx.delta.y * weights.y); + float3 power_gradient_x = float3(4.0 * direction.x * direction.x * direction.x / definition.radius, 0.0, 0.0); + float3 power_gradient_y = float3(0.0, 4.0 * direction.y * direction.y * direction.y / definition.radius, 0.0); + float3 power_gradient_z = float3(0.0, 0.0, 4.0 * direction.z * direction.z * direction.z / definition.radius); + power_gradient_x -= direction * dot(power_gradient_x, direction); + power_gradient_y -= direction * dot(power_gradient_y, direction); + power_gradient_z -= direction * dot(power_gradient_z, direction); + float3 power_sum_gradient = power_gradient_x + power_gradient_y + power_gradient_z; + float inverse_power_sum_squared = 1.0 / (power_sum * power_sum); + float3 weight_gradient_x = (power_gradient_x * power_sum - power_sum_gradient * powers.x) * inverse_power_sum_squared; + float3 weight_gradient_y = (power_gradient_y * power_sum - power_sum_gradient * powers.y) * inverse_power_sum_squared; + float3 weight_gradient_z = (power_gradient_z * power_sum - power_sum_gradient * powers.z) * inverse_power_sum_squared; + erosion_gradient += yz.delta.x * weight_gradient_x + zx.delta.x * weight_gradient_y + xy.delta.x * weight_gradient_z; + float3 gradient = radial_gradient + erosion_gradient; + gradient -= direction * dot(gradient, direction); + float ridge_evidence = xy.ridge_map * weights.z + yz.ridge_map * weights.x + zx.ridge_map * weights.y; + CultMathPlanetarySurfaceSample result; + result.radial_displacement = base_sample.radial_displacement + erosion_height; + result.tangent_gradient = gradient; + result.surface_normal = cultmath_planetary_surface_normal(direction, gradient); + result.slope = length(gradient); + result.ridge = saturate(ridge_evidence * 0.5 + 0.5); + result.gully = saturate(0.5 - ridge_evidence * 0.5); + result.finest_resolved_wavelength = band.finest_included_wavelength; + result.unresolved_height_bound = band.unresolved_height_bound; + return result; +} + +#endif diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index 01901b6b..61b57c98 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -19,6 +19,8 @@ + + diff --git a/src/CultMath/PlanetaryField.cs b/src/CultMath/PlanetaryField.cs new file mode 100644 index 00000000..13f36c0a --- /dev/null +++ b/src/CultMath/PlanetaryField.cs @@ -0,0 +1,213 @@ +namespace CultMath; + +public readonly record struct PlanetaryQueryScale(float FootprintMeters, float MaximumUnresolvedHeight) +{ + public static PlanetaryQueryScale AtFootprint(float footprintMeters, float maximumUnresolvedHeight = float.PositiveInfinity) + => new PlanetaryQueryScale(footprintMeters, maximumUnresolvedHeight).Validate(); + + public PlanetaryQueryScale Validate() + { + if (!float.IsFinite(FootprintMeters) || FootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(FootprintMeters)); + if (float.IsNaN(MaximumUnresolvedHeight) || MaximumUnresolvedHeight < 0) throw new ArgumentOutOfRangeException(nameof(MaximumUnresolvedHeight)); + return this; + } +} + +public readonly record struct PlanetaryFieldDefinition( + ulong FieldVersion, + float Radius, + int Seed, + AdvancedErosionParameters Erosion) +{ + public static PlanetaryFieldDefinition Create( + ulong baseFieldVersion, + float radius, + int seed, + AdvancedErosionParameters erosion) + => new PlanetaryFieldDefinition(PlanetaryFieldIdentity.Compute(baseFieldVersion, radius, seed, erosion), radius, seed, erosion).Validate(); + + public PlanetaryFieldDefinition Validate() + { + if (FieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion)); + if (!float.IsFinite(Radius) || Radius <= 0) throw new ArgumentOutOfRangeException(nameof(Radius)); + if (!float.IsFinite(Erosion.Scale) || Erosion.Scale <= 0) throw new ArgumentOutOfRangeException(nameof(Erosion)); + return this; + } +} + +public static class PlanetaryFieldIdentity +{ + public static ulong Compute(ulong baseFieldVersion, float radius, int seed, in AdvancedErosionParameters erosion) + { + if (baseFieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(baseFieldVersion)); + const ulong offset = 14695981039346656037UL; + const ulong prime = 1099511628211UL; + var hash = offset; + Add(baseFieldVersion); + Add((uint)BitConverter.SingleToInt32Bits(radius)); Add((uint)seed); + Add((uint)BitConverter.SingleToInt32Bits(erosion.Scale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Strength)); + Add((uint)BitConverter.SingleToInt32Bits(erosion.GullyWeight)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Detail)); + AddFloat4(erosion.Rounding); AddFloat4(erosion.Onset); + Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.x)); Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.y)); + Add((uint)BitConverter.SingleToInt32Bits(erosion.CellScale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Normalization)); + Add((uint)erosion.Octaves); Add((uint)BitConverter.SingleToInt32Bits(erosion.Lacunarity)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Gain)); + return hash == 0 ? 1 : hash; + + void Add(ulong value) => hash = unchecked((hash ^ value) * prime); + void AddFloat4(float4 value) + { + Add((uint)BitConverter.SingleToInt32Bits(value.x)); Add((uint)BitConverter.SingleToInt32Bits(value.y)); + Add((uint)BitConverter.SingleToInt32Bits(value.z)); Add((uint)BitConverter.SingleToInt32Bits(value.w)); + } + } +} + +/// +/// Application-supplied base evidence at one unit spherical direction. +/// FieldValue and FieldGradient drive erosion. RadialDisplacement and +/// RadialGradient describe the uneroded surface in metres. +/// +public readonly record struct PlanetaryBaseFieldSample( + float RadialDisplacement, + float3 RadialGradient, + float FieldValue, + float3 FieldGradient, + float FadeTarget); + +public readonly record struct PlanetarySurfaceSample( + ulong FieldVersion, + float3 UnitDirection, + float Radius, + float RadialDisplacement, + float3 TangentGradient, + float3 SurfaceNormal, + float Slope, + float Ridge, + float Gully, + float FinestResolvedWavelength, + float UnresolvedHeightBound) +{ + public float SurfaceRadius => Radius + RadialDisplacement; + public float3 PlanetLocalPosition => UnitDirection * SurfaceRadius; + public bool Meets(PlanetaryQueryScale scale) => UnresolvedHeightBound <= scale.MaximumUnresolvedHeight; +} + +public static class PlanetaryField +{ + public static PlanetarySurfaceSample Sample( + in PlanetaryFieldDefinition definition, + float3 unitDirection, + in PlanetaryBaseFieldSample baseSample, + in PlanetaryQueryScale scale) + { + definition.Validate(); + scale.Validate(); + PlanetaryTopology.ValidateDirection(unitDirection); + PlanetaryTopology.ValidateFinite(baseSample.RadialGradient, nameof(baseSample)); + PlanetaryTopology.ValidateFinite(baseSample.FieldGradient, nameof(baseSample)); + if (!float.IsFinite(baseSample.RadialDisplacement) || !float.IsFinite(baseSample.FieldValue) || !float.IsFinite(baseSample.FadeTarget)) + throw new ArgumentOutOfRangeException(nameof(baseSample)); + + var direction = math.normalize(unitDirection); + var radialGradient = Tangent(direction, baseSample.RadialGradient); + var fieldGradient = Tangent(direction, baseSample.FieldGradient); + var p = definition.Erosion; + var band = ErosionFrequencyBands.Select( + p.Scale * p.CellScale, + scale.FootprintMeters, + p.Octaves, + p.Lacunarity, + p.Strength * p.Scale, + p.Gain); + + var powers = new float3( + Pow4(MathF.Abs(direction.x)), + Pow4(MathF.Abs(direction.y)), + Pow4(MathF.Abs(direction.z))); + var powerSum = math.max(powers.x + powers.y + powers.z, 1.0e-6f); + var weights = powers / powerSum; + var world = direction * definition.Radius; + var seed = definition.Seed; + var xy = AdvancedErosionFilter.Sample( + world.xy + new float2(713 + seed * 0.754877666f, -291 + seed * 0.569840296f), + new float3(baseSample.FieldValue, fieldGradient.x, fieldGradient.y), baseSample.FadeTarget, p, band); + var yz = AdvancedErosionFilter.Sample( + world.yz + new float2(-431 + seed * 0.438289027f, 887 + seed * 0.328438163f), + new float3(baseSample.FieldValue, fieldGradient.y, fieldGradient.z), baseSample.FadeTarget, p, band); + var zx = AdvancedErosionFilter.Sample( + new float2(world.z, world.x) + new float2(197 + seed * 0.219783071f, 557 + seed * 0.145898034f), + new float3(baseSample.FieldValue, fieldGradient.z, fieldGradient.x), baseSample.FadeTarget, p, band); + + var erosionHeight = xy.Delta.x * weights.z + yz.Delta.x * weights.x + zx.Delta.x * weights.y; + var erosionGradient = new float3( + xy.Delta.y * weights.z + zx.Delta.z * weights.y, + xy.Delta.z * weights.z + yz.Delta.y * weights.x, + yz.Delta.z * weights.x + zx.Delta.y * weights.y); + var powerGradientX = Tangent(direction, new float3(4 * direction.x * direction.x * direction.x / definition.Radius, 0, 0)); + var powerGradientY = Tangent(direction, new float3(0, 4 * direction.y * direction.y * direction.y / definition.Radius, 0)); + var powerGradientZ = Tangent(direction, new float3(0, 0, 4 * direction.z * direction.z * direction.z / definition.Radius)); + var sumGradient = powerGradientX + powerGradientY + powerGradientZ; + var weightGradientX = (powerGradientX * powerSum - sumGradient * powers.x) / (powerSum * powerSum); + var weightGradientY = (powerGradientY * powerSum - sumGradient * powers.y) / (powerSum * powerSum); + var weightGradientZ = (powerGradientZ * powerSum - sumGradient * powers.z) / (powerSum * powerSum); + erosionGradient += yz.Delta.x * weightGradientX + zx.Delta.x * weightGradientY + xy.Delta.x * weightGradientZ; + var gradient = Tangent(direction, radialGradient + erosionGradient); + var ridgeEvidence = xy.RidgeMap * weights.z + yz.RidgeMap * weights.x + zx.RidgeMap * weights.y; + var ridge = math.saturate(ridgeEvidence * 0.5f + 0.5f); + var gully = math.saturate(0.5f - ridgeEvidence * 0.5f); + var displacement = baseSample.RadialDisplacement + erosionHeight; + + return new( + definition.FieldVersion, + direction, + definition.Radius, + displacement, + gradient, + PlanetaryTopology.SurfaceNormal(direction, gradient), + math.length(gradient), + ridge, + gully, + band.FinestIncludedWavelength, + band.UnresolvedHeightBound); + } + + public static void SampleBatch( + in PlanetaryFieldDefinition definition, + ReadOnlySpan directions, + in PlanetaryQueryScale scale, + TSource source, + Span destination) + where TSource : IPlanetaryBaseField + { + if (destination.Length < directions.Length) throw new ArgumentException("Destination is shorter than directions.", nameof(destination)); + for (var i = 0; i < directions.Length; i++) + { + var direction = math.normalize(directions[i]); + var baseSample = source.Sample(direction); + destination[i] = Sample(definition, direction, baseSample, scale); + } + } + + public static PlanetarySurfaceSample SamplePosition( + in PlanetaryFieldDefinition definition, + double3 planetLocalPosition, + in PlanetaryQueryScale scale, + TSource source) + where TSource : IPlanetaryBaseField + { + if (!double.IsFinite(planetLocalPosition.x) || !double.IsFinite(planetLocalPosition.y) || !double.IsFinite(planetLocalPosition.z)) + throw new ArgumentOutOfRangeException(nameof(planetLocalPosition)); + var length = Math.Sqrt(planetLocalPosition.x * planetLocalPosition.x + planetLocalPosition.y * planetLocalPosition.y + planetLocalPosition.z * planetLocalPosition.z); + if (length < 1.0e-12) throw new ArgumentOutOfRangeException(nameof(planetLocalPosition)); + var direction = new float3((float)(planetLocalPosition.x / length), (float)(planetLocalPosition.y / length), (float)(planetLocalPosition.z / length)); + return Sample(definition, direction, source.Sample(direction), scale); + } + + private static float3 Tangent(float3 direction, float3 gradient) => gradient - direction * math.dot(gradient, direction); + private static float Pow4(float value) { var square = value * value; return square * square; } +} + +public interface IPlanetaryBaseField +{ + PlanetaryBaseFieldSample Sample(float3 unitDirection); +} diff --git a/src/CultMath/PlanetaryPages.cs b/src/CultMath/PlanetaryPages.cs new file mode 100644 index 00000000..c9cff51f --- /dev/null +++ b/src/CultMath/PlanetaryPages.cs @@ -0,0 +1,185 @@ +namespace CultMath; + +public readonly record struct PlanetaryPageLayout(PlanetaryTileAddress Tile, int InteriorSize, int BorderSize) +{ + public int StorageSize => checked(InteriorSize + BorderSize * 2); + + public PlanetaryPageLayout Validate() + { + if (InteriorSize < 2) throw new ArgumentOutOfRangeException(nameof(InteriorSize)); + if (BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(BorderSize)); + return this; + } +} + +public readonly record struct PlanetaryPageSummary( + float MinimumHeight, + float MaximumHeight, + float MaximumSlope, + float UnresolvedHeightBound, + float AngularTexelSize); + +public readonly record struct PlanetaryPageSample(float Height, float3 TangentGradient, float Ridge, float Gully); + +public static class PlanetaryPageSampling +{ + public static float3 Direction(PlanetaryPageLayout page, int storageX, int storageY) + { + page.Validate(); + if ((uint)storageX >= (uint)page.StorageSize || (uint)storageY >= (uint)page.StorageSize) + throw new ArgumentOutOfRangeException(nameof(storageX)); + var localU = (storageX - page.BorderSize) / (double)(page.InteriorSize - 1); + var localV = (storageY - page.BorderSize) / (double)(page.InteriorSize - 1); + return DirectionAtLocal(page, localU, localV); + } + + public static float3 DirectionAtLocal(PlanetaryPageLayout page, double localU, double localV) + { + page.Validate(); + if (!double.IsFinite(localU) || !double.IsFinite(localV)) throw new ArgumentOutOfRangeException(nameof(localU)); + var count = page.Tile.AxisTileCount; + return PlanetaryTopology.Direction(new( + page.Tile.Face, + -1.0 + 2.0 * (page.Tile.X + localU) / count, + -1.0 + 2.0 * (page.Tile.Y + localV) / count)); + } + + public static float AngularTexelSize(PlanetaryPageLayout page) + { + var center = page.BorderSize + (page.InteriorSize - 1) / 2; + var a = Direction(page, center, center); + var b = Direction(page, Math.Min(center + 1, page.StorageSize - 1), center); + return MathF.Acos(math.clamp(math.dot(a, b), -1.0f, 1.0f)); + } + + public static float NominalAngularTexelSize(PlanetaryPageLayout page) + { + page.Validate(); + return MathF.PI * 0.5f / (page.Tile.AxisTileCount * (page.InteriorSize - 1)); + } + + public static PlanetaryPageSample Bilinear( + ReadOnlySpan samples, + PlanetaryPageLayout page, + float2 local) + { + page.Validate(); + if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples)); + var texel = local * (page.InteriorSize - 1) + page.BorderSize; + var x0 = Math.Clamp((int)MathF.Floor(texel.x), 0, page.StorageSize - 1); + var y0 = Math.Clamp((int)MathF.Floor(texel.y), 0, page.StorageSize - 1); + var x1 = Math.Min(x0 + 1, page.StorageSize - 1); + var y1 = Math.Min(y0 + 1, page.StorageSize - 1); + var tx = math.frac(texel.x); + var ty = math.frac(texel.y); + var a = Lerp(samples[y0 * page.StorageSize + x0], samples[y0 * page.StorageSize + x1], tx); + var b = Lerp(samples[y1 * page.StorageSize + x0], samples[y1 * page.StorageSize + x1], tx); + return Lerp(a, b, ty); + } + + public static PlanetaryPageSummary Summarize(ReadOnlySpan samples, PlanetaryPageLayout page, float unresolvedHeightBound) + { + page.Validate(); + if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples)); + var min = float.PositiveInfinity; + var max = float.NegativeInfinity; + var maxSlope = 0.0f; + foreach (var sample in samples) + { + min = MathF.Min(min, sample.Height); + max = MathF.Max(max, sample.Height); + maxSlope = MathF.Max(maxSlope, math.length(sample.TangentGradient)); + } + return new(min, max, maxSlope, MathF.Max(unresolvedHeightBound, 0), AngularTexelSize(page)); + } + + private static PlanetaryPageSample Lerp(PlanetaryPageSample a, PlanetaryPageSample b, float t) => new( + math.lerp(a.Height, b.Height, t), + math.lerp(a.TangentGradient, b.TangentGradient, t), + math.lerp(a.Ridge, b.Ridge, t), + math.lerp(a.Gully, b.Gully, t)); +} + +public readonly record struct PlanetaryBakedPage( + PlanetaryPageLayout Layout, + PlanetaryPageSample[] Samples, + PlanetaryPageSummary Summary); + +public static class PlanetaryPageBaker +{ + public static PlanetaryBakedPage Bake( + in PlanetaryFieldDefinition field, + PlanetaryPageLayout layout, + TSource source) + where TSource : IPlanetaryBaseField + { + layout.Validate(); field.Validate(); + var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * field.Radius; + var parentSpacing = layout.Tile.Level == 0 + ? 0 + : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * field.Radius; + var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize]; + var unresolved = 0.0f; + for (var y = 0; y < layout.StorageSize; y++) + for (var x = 0; x < layout.StorageSize; x++) + { + var direction = PlanetaryPageSampling.Direction(layout, x, y); + var baseSample = source.Sample(direction); + var child = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(spacing)); + var height = child.RadialDisplacement; + var gradient = child.TangentGradient; + if (parentSpacing > 0) + { + var parent = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(parentSpacing)); + height -= parent.RadialDisplacement; + gradient -= parent.TangentGradient; + } + samples[y * layout.StorageSize + x] = new(height, gradient, child.Ridge, child.Gully); + unresolved = MathF.Max(unresolved, child.UnresolvedHeightBound); + } + return new(layout, samples, PlanetaryPageSampling.Summarize(samples, layout, unresolved)); + } +} + +public readonly record struct PlanetaryResidentPage(PlanetaryBakedPage Page, float Blend) +{ + public PlanetaryResidentPage Validate() + { + if (!float.IsFinite(Blend) || Blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(Blend)); + Page.Layout.Validate(); + if (Page.Samples.Length != Page.Layout.StorageSize * Page.Layout.StorageSize) throw new ArgumentException("Page payload does not match layout."); + return this; + } +} + +public static class PlanetaryPageSetSampling +{ + public static bool TrySample(ReadOnlySpan pages, float3 direction, out PlanetaryPageSample sample) + { + PlanetaryTopology.ValidateDirection(direction); + var height = 0.0f; + var gradient = float3.zero; + var ridge = 0.0f; + var gully = 0.0f; + var found = false; + var deepestLevel = -1; + foreach (var resident in pages) + { + resident.Validate(); + if (!PlanetaryTopology.TryLocalCoordinate(direction, resident.Page.Layout.Tile, out var local)) continue; + var contribution = PlanetaryPageSampling.Bilinear(resident.Page.Samples, resident.Page.Layout, local); + var blend = math.saturate(resident.Blend); + height += contribution.Height * blend; + gradient += contribution.TangentGradient * blend; + if (resident.Page.Layout.Tile.Level >= deepestLevel) + { + ridge = found ? math.lerp(ridge, contribution.Ridge, blend) : contribution.Ridge; + gully = found ? math.lerp(gully, contribution.Gully, blend) : contribution.Gully; + deepestLevel = resident.Page.Layout.Tile.Level; + } + found = true; + } + sample = new(height, gradient, ridge, gully); + return found; + } +} diff --git a/src/CultMath/PlanetaryPatch.cs b/src/CultMath/PlanetaryPatch.cs new file mode 100644 index 00000000..0fd3cbb6 --- /dev/null +++ b/src/CultMath/PlanetaryPatch.cs @@ -0,0 +1,42 @@ +namespace CultMath; + +public readonly record struct PlanetaryPatchVertex(float2 LocalCoordinate, float3 UnitDirection); + +public readonly record struct PlanetaryPatchMesh( + PlanetaryCubeFace Face, + int CellsPerAxis, + PlanetaryPatchVertex[] Vertices, + int[] Indices); + +public static class PlanetaryPatch +{ + public static PlanetaryPatchMesh CreateFace(PlanetaryCubeFace face, int cellsPerAxis) + { + if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face)); + if (cellsPerAxis < 1 || cellsPerAxis > 4096) throw new ArgumentOutOfRangeException(nameof(cellsPerAxis)); + var axisVertices = cellsPerAxis + 1; + var vertices = new PlanetaryPatchVertex[checked(axisVertices * axisVertices)]; + for (var y = 0; y < axisVertices; y++) + for (var x = 0; x < axisVertices; x++) + { + var local = new float2(x / (float)cellsPerAxis, y / (float)cellsPerAxis); + vertices[y * axisVertices + x] = new(local, PlanetaryTopology.Direction(new(face, local.x * 2 - 1, local.y * 2 - 1))); + } + var indices = new int[checked(cellsPerAxis * cellsPerAxis * 6)]; + var write = 0; + for (var y = 0; y < cellsPerAxis; y++) + for (var x = 0; x < cellsPerAxis; x++) + { + var lowerLeft = y * axisVertices + x; + var lowerRight = lowerLeft + 1; + var upperLeft = lowerLeft + axisVertices; + var upperRight = upperLeft + 1; + indices[write++] = lowerLeft; indices[write++] = lowerRight; indices[write++] = upperRight; + indices[write++] = lowerLeft; indices[write++] = upperRight; indices[write++] = upperLeft; + } + return new(face, cellsPerAxis, vertices, indices); + } + + public static PlanetaryPatchMesh[] CreateCubeSphere(int cellsPerAxis) + => ((PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace))).Select(face => CreateFace(face, cellsPerAxis)).ToArray(); +} diff --git a/src/CultMath/PlanetaryProjection.cs b/src/CultMath/PlanetaryProjection.cs new file mode 100644 index 00000000..f849fe20 --- /dev/null +++ b/src/CultMath/PlanetaryProjection.cs @@ -0,0 +1,320 @@ +namespace CultMath; + +public enum PlanetaryProjectionKind +{ + Equirectangular, + WebMercator, + EqualEarth, + Orthographic, + AzimuthalEquidistant, + AzimuthalEqualArea, + CubeAtlas, + LocalTangent, +} + +public readonly record struct PlanetaryProjectionParameters( + PlanetaryProjectionKind Kind, + double CenterLongitude = 0, + double CenterLatitude = 0, + double Scale = 1) +{ + public PlanetaryProjectionParameters Validate() + { + if (!double.IsFinite(CenterLongitude) || !double.IsFinite(CenterLatitude) || !double.IsFinite(Scale) || Scale <= 0) + throw new ArgumentOutOfRangeException(nameof(Scale)); + if (CenterLatitude is < -Math.PI / 2 or > Math.PI / 2) throw new ArgumentOutOfRangeException(nameof(CenterLatitude)); + return this; + } +} + +public static class PlanetaryProjection +{ + private const double EqualEarthA1 = 1.340264; + private const double EqualEarthA2 = -0.081106; + private const double EqualEarthA3 = 0.000893; + private const double EqualEarthA4 = 0.003796; + private static readonly double EqualEarthM = Math.Sqrt(3) / 2; + private static readonly double EqualEarthXMax = Math.PI / (EqualEarthM * EqualEarthA1); + private static readonly double EqualEarthYMax = EqualEarthY(Math.Asin(EqualEarthM)); + public static readonly double WebMercatorMaximumLatitude = Math.Atan(Math.Sinh(Math.PI)); + + public static bool TryForward(float3 direction, PlanetaryProjectionParameters parameters, out double2 coordinate) + { + parameters.Validate(); + PlanetaryTopology.ValidateDirection(direction); + var d = math.normalize(direction); + if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas) + { + var face = PlanetaryTopology.FaceCoordinate(d); + var index = (int)face.Face; + var column = index % 3; + var row = index / 3; + coordinate = new( + (-1 + (column + (face.U + 1) * 0.5) * (2.0 / 3.0)) / parameters.Scale, + (1 - (row + (face.V + 1) * 0.5)) / parameters.Scale); + return true; + } + + var longitude = Math.Atan2(d.y, d.x); + var latitude = Math.Asin(Math.Clamp(d.z, -1, 1)); + var deltaLongitude = WrapLongitude(longitude - parameters.CenterLongitude); + var centered = CenteredForward(deltaLongitude, latitude, parameters, out coordinate); + coordinate /= parameters.Scale; + return centered; + } + + public static bool TryInverse(double2 coordinate, PlanetaryProjectionParameters parameters, out float3 direction) + { + parameters.Validate(); + if (!double.IsFinite(coordinate.x) || !double.IsFinite(coordinate.y)) + { + direction = float3.zero; + return false; + } + coordinate *= parameters.Scale; + if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas) + return TryInverseCubeAtlas(coordinate, out direction); + + if (!CenteredInverse(coordinate, parameters, out var deltaLongitude, out var latitude)) + { + direction = float3.zero; + return false; + } + var longitude = WrapLongitude(deltaLongitude + parameters.CenterLongitude); + var cosLatitude = Math.Cos(latitude); + direction = math.normalize(new float3( + (float)(cosLatitude * Math.Cos(longitude)), + (float)(cosLatitude * Math.Sin(longitude)), + (float)Math.Sin(latitude))); + return true; + } + + private static bool CenteredForward(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate) + { + switch (p.Kind) + { + case PlanetaryProjectionKind.Equirectangular: + coordinate = new(longitude / Math.PI, latitude / (Math.PI * 0.5)); + return true; + case PlanetaryProjectionKind.WebMercator: + if (Math.Abs(latitude) > WebMercatorMaximumLatitude) { coordinate = double2.zero; return false; } + coordinate = new(longitude / Math.PI, Math.Asinh(Math.Tan(latitude)) / Math.PI); + return true; + case PlanetaryProjectionKind.EqualEarth: + { + var theta = Math.Asin(EqualEarthM * Math.Sin(latitude)); + var theta2 = theta * theta; + var theta6 = theta2 * theta2 * theta2; + var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2)); + coordinate = new(longitude * Math.Cos(theta) / denominator / EqualEarthXMax, EqualEarthY(theta) / EqualEarthYMax); + return true; + } + case PlanetaryProjectionKind.Orthographic: + case PlanetaryProjectionKind.AzimuthalEquidistant: + case PlanetaryProjectionKind.AzimuthalEqualArea: + case PlanetaryProjectionKind.LocalTangent: + return ForwardAzimuthal(longitude, latitude, p, out coordinate); + default: + throw new ArgumentOutOfRangeException(nameof(p)); + } + } + + private static bool CenteredInverse(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude) + { + longitude = latitude = 0; + switch (p.Kind) + { + case PlanetaryProjectionKind.Equirectangular: + if (Math.Abs(coordinate.y) > 1) return false; + longitude = coordinate.x * Math.PI; latitude = coordinate.y * Math.PI * 0.5; return Math.Abs(coordinate.x) <= 1; + case PlanetaryProjectionKind.WebMercator: + if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false; + longitude = coordinate.x * Math.PI; latitude = Math.Atan(Math.Sinh(coordinate.y * Math.PI)); return true; + case PlanetaryProjectionKind.EqualEarth: + { + if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false; + var targetY = coordinate.y * EqualEarthYMax; + var theta = targetY / EqualEarthA1; + for (var i = 0; i < 8; i++) + { + var theta2 = theta * theta; + var theta6 = theta2 * theta2 * theta2; + var f = EqualEarthY(theta) - targetY; + var derivative = EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2); + theta -= f / derivative; + } + var sinLatitude = Math.Sin(theta) / EqualEarthM; + if (Math.Abs(sinLatitude) > 1.0000000001) return false; + latitude = Math.Asin(Math.Clamp(sinLatitude, -1, 1)); + var thetaSquared = theta * theta; + var thetaSixth = thetaSquared * thetaSquared * thetaSquared; + var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * thetaSquared + thetaSixth * (7 * EqualEarthA3 + 9 * EqualEarthA4 * thetaSquared)); + longitude = coordinate.x * EqualEarthXMax * denominator / Math.Cos(theta); + return Math.Abs(longitude) <= Math.PI + 1.0e-10; + } + case PlanetaryProjectionKind.Orthographic: + case PlanetaryProjectionKind.AzimuthalEquidistant: + case PlanetaryProjectionKind.AzimuthalEqualArea: + case PlanetaryProjectionKind.LocalTangent: + return InverseAzimuthal(coordinate, p, out longitude, out latitude); + default: + throw new ArgumentOutOfRangeException(nameof(p)); + } + } + + private static bool ForwardAzimuthal(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate) + { + var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude); + var sin = Math.Sin(latitude); var cos = Math.Cos(latitude); var cosLon = Math.Cos(longitude); + var cosC = Math.Clamp(sin0 * sin + cos0 * cos * cosLon, -1, 1); + var rawX = cos * Math.Sin(longitude); + var rawY = cos0 * sin - sin0 * cos * cosLon; + double factor; + switch (p.Kind) + { + case PlanetaryProjectionKind.Orthographic: + if (cosC < 0) { coordinate = double2.zero; return false; } + factor = 1; break; + case PlanetaryProjectionKind.AzimuthalEquidistant: + { + var c = Math.Acos(cosC); factor = c < 1.0e-12 ? 1 : c / Math.Sin(c); factor /= Math.PI; break; + } + case PlanetaryProjectionKind.AzimuthalEqualArea: + factor = Math.Sqrt(2 / Math.Max(1 + cosC, 1.0e-20)) * 0.5; break; + case PlanetaryProjectionKind.LocalTangent: + if (cosC <= 0) { coordinate = double2.zero; return false; } + factor = 1 / cosC; break; + default: throw new ArgumentOutOfRangeException(nameof(p)); + } + coordinate = new(rawX * factor, rawY * factor); + return true; + } + + private static bool InverseAzimuthal(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude) + { + longitude = latitude = 0; + var rho = Math.Sqrt(coordinate.x * coordinate.x + coordinate.y * coordinate.y); + double c; + switch (p.Kind) + { + case PlanetaryProjectionKind.Orthographic: if (rho > 1) return false; c = Math.Asin(Math.Clamp(rho, 0, 1)); break; + case PlanetaryProjectionKind.AzimuthalEquidistant: if (rho > 1) return false; c = rho * Math.PI; break; + case PlanetaryProjectionKind.AzimuthalEqualArea: if (rho > 1) return false; c = 2 * Math.Asin(Math.Clamp(rho, 0, 1)); break; + case PlanetaryProjectionKind.LocalTangent: c = Math.Atan(rho); break; + default: throw new ArgumentOutOfRangeException(nameof(p)); + } + if (rho < 1.0e-12) { latitude = p.CenterLatitude; return true; } + var sinC = Math.Sin(c); var cosC = Math.Cos(c); + var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude); + latitude = Math.Asin(Math.Clamp(cosC * sin0 + coordinate.y * sinC * cos0 / rho, -1, 1)); + longitude = Math.Atan2(coordinate.x * sinC, rho * cos0 * cosC - coordinate.y * sin0 * sinC); + return true; + } + + private static bool TryInverseCubeAtlas(double2 coordinate, out float3 direction) + { + direction = float3.zero; + if (coordinate.x is < -1 or > 1 || coordinate.y is < -1 or > 1) return false; + var column = Math.Min((int)((coordinate.x + 1) * 1.5), 2); + var row = Math.Min((int)((1 - coordinate.y)), 1); + var u = ((coordinate.x + 1) * 1.5 - column) * 2 - 1; + var v = ((1 - coordinate.y) - row) * 2 - 1; + direction = PlanetaryTopology.Direction(new((PlanetaryCubeFace)(row * 3 + column), u, v)); + return true; + } + + private static double EqualEarthY(double theta) + { + var theta2 = theta * theta; + var theta6 = theta2 * theta2 * theta2; + return theta * (EqualEarthA1 + EqualEarthA2 * theta2 + theta6 * (EqualEarthA3 + EqualEarthA4 * theta2)); + } + + private static double WrapLongitude(double longitude) + { + longitude %= Math.PI * 2; + if (longitude > Math.PI) longitude -= Math.PI * 2; + if (longitude < -Math.PI) longitude += Math.PI * 2; + return longitude; + } +} + +public readonly record struct PlanetaryMapTileLayout( + PlanetaryProjectionParameters Projection, + int Level, + int X, + int Y, + int InteriorSize, + int BorderSize) +{ + public int StorageSize => checked(InteriorSize + BorderSize * 2); + + public PlanetaryMapTileLayout Validate() + { + Projection.Validate(); + if (Level is < 0 or > 30) throw new ArgumentOutOfRangeException(nameof(Level)); + var count = 1 << Level; + if ((uint)X >= (uint)count || (uint)Y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(X)); + if (InteriorSize < 2 || BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(InteriorSize)); + return this; + } +} + +public static class PlanetaryMapTileSampling +{ + public static bool TryDirection(PlanetaryMapTileLayout tile, int storageX, int storageY, out float3 direction) + { + tile.Validate(); + if ((uint)storageX >= (uint)tile.StorageSize || (uint)storageY >= (uint)tile.StorageSize) throw new ArgumentOutOfRangeException(nameof(storageX)); + var count = 1 << tile.Level; + var localU = (storageX - tile.BorderSize) / (double)(tile.InteriorSize - 1); + var localV = (storageY - tile.BorderSize) / (double)(tile.InteriorSize - 1); + var map = new double2(-1 + 2 * (tile.X + localU) / count, 1 - 2 * (tile.Y + localV) / count); + return PlanetaryProjection.TryInverse(map, tile.Projection, out direction); + } +} + +public readonly record struct PlanetaryMapTileKey( + ulong FieldVersion, + uint ProjectionVersion, + uint LayerId, + PlanetaryMapTileLayout Layout, + PlanetaryQueryScale QueryScale) +{ + public PlanetaryMapTileKey Validate() + { + if (FieldVersion == 0 || ProjectionVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion)); + Layout.Validate(); QueryScale.Validate(); + return this; + } +} + +public readonly record struct PlanetarySurfaceMapTile( + PlanetaryMapTileKey Key, + PlanetarySurfaceSample[] Samples, + bool[] Validity); + +public static class PlanetaryMapTileBaker +{ + public static PlanetarySurfaceMapTile Bake( + in PlanetaryFieldDefinition field, + PlanetaryMapTileKey key, + TSource source) + where TSource : IPlanetaryBaseField + { + field.Validate(); key.Validate(); + if (field.FieldVersion != key.FieldVersion) throw new ArgumentException("Tile field version does not match definition.", nameof(key)); + var count = key.Layout.StorageSize * key.Layout.StorageSize; + var samples = new PlanetarySurfaceSample[count]; + var validity = new bool[count]; + for (var y = 0; y < key.Layout.StorageSize; y++) + for (var x = 0; x < key.Layout.StorageSize; x++) + { + var index = y * key.Layout.StorageSize + x; + if (!PlanetaryMapTileSampling.TryDirection(key.Layout, x, y, out var direction)) continue; + samples[index] = PlanetaryField.Sample(field, direction, source.Sample(direction), key.QueryScale); + validity[index] = true; + } + return new(key, samples, validity); + } +} diff --git a/src/CultMath/PlanetaryQueries.cs b/src/CultMath/PlanetaryQueries.cs new file mode 100644 index 00000000..1f11ca77 --- /dev/null +++ b/src/CultMath/PlanetaryQueries.cs @@ -0,0 +1,191 @@ +namespace CultMath; + +public readonly record struct PlanetaryRadialBounds(float MinimumDisplacement, float MaximumDisplacement) +{ + public PlanetaryRadialBounds Validate() + { + if (!float.IsFinite(MinimumDisplacement) || !float.IsFinite(MaximumDisplacement) || MinimumDisplacement > MaximumDisplacement) + throw new ArgumentOutOfRangeException(nameof(MinimumDisplacement)); + return this; + } +} + +public readonly record struct PlanetaryRayHit( + double Distance, + double3 PlanetLocalPosition, + PlanetarySurfaceSample Surface); + +public readonly record struct PlanetaryPathSample(double Distance, PlanetarySurfaceSample Surface); + +public readonly record struct PlanetaryRegionSummary( + float MinimumDisplacement, + float MaximumDisplacement, + float MaximumSlope, + float MaximumUnresolvedHeight); + +public static class PlanetaryQueries +{ + public static bool TryIntersectRay( + in PlanetaryFieldDefinition field, + double3 rayOrigin, + double3 rayDirection, + double maximumDistance, + in PlanetaryQueryScale scale, + in PlanetaryRadialBounds bounds, + TSource source, + out PlanetaryRayHit hit) + where TSource : IPlanetaryBaseField + { + field.Validate(); scale.Validate(); bounds.Validate(); + ValidateFinite(rayOrigin, nameof(rayOrigin)); ValidateFinite(rayDirection, nameof(rayDirection)); + if (!double.IsFinite(maximumDistance) || maximumDistance <= 0) throw new ArgumentOutOfRangeException(nameof(maximumDistance)); + var directionLength = Length(rayDirection); + if (directionLength < 1.0e-15) throw new ArgumentOutOfRangeException(nameof(rayDirection)); + var direction = rayDirection / directionLength; + var outerRadius = field.Radius + bounds.MaximumDisplacement + scale.MaximumUnresolvedHeight; + if (!double.IsFinite(outerRadius)) outerRadius = field.Radius + bounds.MaximumDisplacement; + if (!TrySphereInterval(rayOrigin, direction, outerRadius, out var enter, out var leave)) { hit = default; return false; } + var start = Math.Max(enter, 0); + var end = Math.Min(leave, maximumDistance); + if (start > end) { hit = default; return false; } + + var previousDistance = start; + var previous = SignedSurfaceDistance(field, rayOrigin + direction * previousDistance, scale, source, out var previousSample); + if (previous <= 0) + { + hit = new(previousDistance, rayOrigin + direction * previousDistance, previousSample); + return true; + } + var minimumStep = Math.Max(scale.FootprintMeters * 0.5, field.Radius * 1.0e-7); + for (var iteration = 0; iteration < 512 && previousDistance < end; iteration++) + { + var nextDistance = Math.Min(previousDistance + Math.Max(previous * 0.5, minimumStep), end); + var next = SignedSurfaceDistance(field, rayOrigin + direction * nextDistance, scale, source, out var nextSample); + if (next <= 0) + { + var low = previousDistance; + var high = nextDistance; + var surface = nextSample; + for (var refine = 0; refine < 32; refine++) + { + var middle = (low + high) * 0.5; + var value = SignedSurfaceDistance(field, rayOrigin + direction * middle, scale, source, out var middleSample); + if (value > 0) low = middle; + else { high = middle; surface = middleSample; } + } + var position = rayOrigin + direction * high; + hit = new(high, position, surface); + return true; + } + if (nextDistance >= end) break; + previousDistance = nextDistance; + previous = next; + previousSample = nextSample; + } + hit = default; + return false; + } + + public static double SegmentClearance( + in PlanetaryFieldDefinition field, + double3 start, + double3 end, + double sampleSpacing, + in PlanetaryQueryScale scale, + TSource source) + where TSource : IPlanetaryBaseField + { + ValidateFinite(start, nameof(start)); ValidateFinite(end, nameof(end)); + if (!double.IsFinite(sampleSpacing) || sampleSpacing <= 0) throw new ArgumentOutOfRangeException(nameof(sampleSpacing)); + var delta = end - start; + var distance = Length(delta); + var steps = Math.Max((int)Math.Ceiling(distance / sampleSpacing), 1); + var minimum = double.PositiveInfinity; + for (var i = 0; i <= steps; i++) + { + var position = start + delta * (i / (double)steps); + minimum = Math.Min(minimum, SignedSurfaceDistance(field, position, scale, source, out _)); + } + return minimum; + } + + public static PlanetaryPathSample[] SampleGreatCircle( + in PlanetaryFieldDefinition field, + float3 startDirection, + float3 endDirection, + int segmentCount, + in PlanetaryQueryScale scale, + TSource source) + where TSource : IPlanetaryBaseField + { + if (segmentCount < 1) throw new ArgumentOutOfRangeException(nameof(segmentCount)); + PlanetaryTopology.ValidateDirection(startDirection); PlanetaryTopology.ValidateDirection(endDirection); + var a = math.normalize(startDirection); var b = math.normalize(endDirection); + var angle = MathF.Acos(math.clamp(math.dot(a, b), -1, 1)); + var sinAngle = MathF.Sin(angle); + var result = new PlanetaryPathSample[segmentCount + 1]; + for (var i = 0; i <= segmentCount; i++) + { + var t = i / (float)segmentCount; + var direction = sinAngle < 1.0e-6f + ? math.normalize(math.lerp(a, b, t)) + : math.normalize(a * (MathF.Sin((1 - t) * angle) / sinAngle) + b * (MathF.Sin(t * angle) / sinAngle)); + var sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale); + result[i] = new(angle * field.Radius * t, sample); + } + return result; + } + + public static PlanetaryRegionSummary Summarize( + in PlanetaryFieldDefinition field, + ReadOnlySpan directions, + in PlanetaryQueryScale scale, + TSource source) + where TSource : IPlanetaryBaseField + { + if (directions.IsEmpty) throw new ArgumentException("At least one direction is required.", nameof(directions)); + var min = float.PositiveInfinity; var max = float.NegativeInfinity; var slope = 0.0f; var unresolved = 0.0f; + foreach (var direction in directions) + { + var normalized = math.normalize(direction); + var sample = PlanetaryField.Sample(field, normalized, source.Sample(normalized), scale); + min = MathF.Min(min, sample.RadialDisplacement); + max = MathF.Max(max, sample.RadialDisplacement); + slope = MathF.Max(slope, sample.Slope); + unresolved = MathF.Max(unresolved, sample.UnresolvedHeightBound); + } + return new(min, max, slope, unresolved); + } + + private static double SignedSurfaceDistance( + in PlanetaryFieldDefinition field, + double3 position, + in PlanetaryQueryScale scale, + TSource source, + out PlanetarySurfaceSample sample) + where TSource : IPlanetaryBaseField + { + var radius = Length(position); + if (radius < 1.0e-15) { sample = default; return double.NegativeInfinity; } + var direction = new float3((float)(position.x / radius), (float)(position.y / radius), (float)(position.z / radius)); + sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale); + return radius - sample.SurfaceRadius; + } + + private static bool TrySphereInterval(double3 origin, double3 direction, double radius, out double enter, out double leave) + { + var b = Dot(origin, direction); + var c = Dot(origin, origin) - radius * radius; + var discriminant = b * b - c; + if (discriminant < 0) { enter = leave = 0; return false; } + var root = Math.Sqrt(discriminant); + enter = -b - root; leave = -b + root; return leave >= 0; + } + + private static double Dot(double3 a, double3 b) => a.x * b.x + a.y * b.y + a.z * b.z; + private static double Length(double3 value) => Math.Sqrt(Dot(value, value)); + private static void ValidateFinite(double3 value, string name) + { + if (!double.IsFinite(value.x) || !double.IsFinite(value.y) || !double.IsFinite(value.z)) throw new ArgumentOutOfRangeException(name); + } +} diff --git a/src/CultMath/PlanetaryResidency.cs b/src/CultMath/PlanetaryResidency.cs new file mode 100644 index 00000000..e5dca115 --- /dev/null +++ b/src/CultMath/PlanetaryResidency.cs @@ -0,0 +1,144 @@ +namespace CultMath; + +public readonly record struct PlanetaryLodParameters( + int MaximumLevel, + int PageInteriorSize, + int PageBorderSize, + float ViewportHeightPixels, + float MinimumFootprintMeters) +{ + public PlanetaryLodParameters Validate() + { + if (MaximumLevel is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(MaximumLevel)); + _ = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), PageInteriorSize, PageBorderSize).Validate(); + if (!float.IsFinite(ViewportHeightPixels) || ViewportHeightPixels <= 0) throw new ArgumentOutOfRangeException(nameof(ViewportHeightPixels)); + if (!float.IsFinite(MinimumFootprintMeters) || MinimumFootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(MinimumFootprintMeters)); + return this; + } +} + +public static class PlanetaryLodSelector +{ + public static int SelectLevel( + in PlanetaryFieldDefinition field, + float cameraDistanceFromCenter, + in PlanetaryLodParameters parameters) + { + field.Validate(); parameters.Validate(); + if (!float.IsFinite(cameraDistanceFromCenter) || cameraDistanceFromCenter < 0) throw new ArgumentOutOfRangeException(nameof(cameraDistanceFromCenter)); + var altitude = MathF.Max(cameraDistanceFromCenter - field.Radius, 0.001f); + var footprint = MathF.Max(altitude / parameters.ViewportHeightPixels * 2, parameters.MinimumFootprintMeters); + var erosion = field.Erosion; + for (var level = 0; level <= parameters.MaximumLevel; level++) + { + var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, level, 0, 0), parameters.PageInteriorSize, parameters.PageBorderSize); + var spacing = PlanetaryPageSampling.NominalAngularTexelSize(page) * field.Radius; + var unresolved = ErosionFrequencyBands.Select( + erosion.Scale * erosion.CellScale, spacing, erosion.Octaves, + erosion.Lacunarity, erosion.Strength * erosion.Scale, erosion.Gain).UnresolvedHeightBound; + if (unresolved <= footprint) return level; + } + return parameters.MaximumLevel; + } + + public static PlanetaryTileAddress[] SelectAncestorChain( + in PlanetaryFieldDefinition field, + float3 cameraDirection, + float cameraDistanceFromCenter, + in PlanetaryLodParameters parameters) + { + PlanetaryTopology.ValidateDirection(cameraDirection); + var level = SelectLevel(field, cameraDistanceFromCenter, parameters); + var result = new PlanetaryTileAddress[6 + level]; + for (var face = 0; face < 6; face++) result[face] = new((PlanetaryCubeFace)face, 0, 0, 0); + var leaf = PlanetaryTopology.TileAt(cameraDirection, level); + for (var currentLevel = level; currentLevel >= 1; currentLevel--) + { + result[6 + currentLevel - 1] = leaf; + leaf = leaf.Parent(); + } + return result; + } +} + +public readonly record struct PlanetaryResidentTile(PlanetaryTileAddress Tile, float Blend, bool Departing); + +public readonly record struct PlanetaryResidencySnapshot( + ulong ContentVersion, + ulong PresentationVersion, + PlanetaryResidentTile[] Tiles); + +/// Owns presentation residency only. It never owns field content. +public sealed class PlanetaryResidualResidency +{ + private readonly Dictionary residents = []; + private readonly Dictionary arrivals = []; + private readonly Dictionary departures = []; + private float lastTime = float.NegativeInfinity; + + public void Reset() + { + residents.Clear(); arrivals.Clear(); departures.Clear(); lastTime = float.NegativeInfinity; + } + + public PlanetaryResidencySnapshot Update(ReadOnlySpan desired, float timeSeconds, float transitionSeconds) + { + if (!float.IsFinite(timeSeconds)) throw new ArgumentOutOfRangeException(nameof(timeSeconds)); + if (!float.IsFinite(transitionSeconds) || transitionSeconds < 0) throw new ArgumentOutOfRangeException(nameof(transitionSeconds)); + if (timeSeconds < lastTime) Reset(); + lastTime = timeSeconds; + + var desiredKeys = new HashSet(); + foreach (var tile in desired) + { + var key = tile.StableKey; + desiredKeys.Add(key); + residents[key] = tile; + if (!arrivals.ContainsKey(key)) arrivals[key] = timeSeconds; + departures.Remove(key); + } + + foreach (var pair in residents.ToArray()) + { + if (pair.Value.Level == 0 || desiredKeys.Contains(pair.Key)) continue; + if (!departures.ContainsKey(pair.Key)) + { + var startingBlend = transitionSeconds == 0 + ? 1 + : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1); + departures[pair.Key] = (timeSeconds, startingBlend); + } + if (transitionSeconds > 0 && timeSeconds - departures[pair.Key].Time < transitionSeconds) continue; + residents.Remove(pair.Key); arrivals.Remove(pair.Key); departures.Remove(pair.Key); + } + + var ordered = residents + .OrderBy(pair => pair.Value.Level) + .ThenBy(pair => pair.Value.Face) + .ThenBy(pair => pair.Value.X) + .ThenBy(pair => pair.Value.Y) + .ToArray(); + var tiles = new PlanetaryResidentTile[ordered.Length]; + for (var i = 0; i < ordered.Length; i++) + { + var pair = ordered[i]; + var departing = departures.TryGetValue(pair.Key, out var departure); + var blend = pair.Value.Level == 0 || transitionSeconds == 0 + ? 1 + : departing + ? departure.StartingBlend * Math.Clamp(1 - (timeSeconds - departure.Time) / transitionSeconds, 0, 1) + : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1); + tiles[i] = new(pair.Value, blend, departing); + } + return new(Hash(ordered.Select(pair => pair.Key)), Hash(tiles.Select(tile => tile.Tile.StableKey ^ (uint)BitConverter.SingleToInt32Bits(tile.Blend))), tiles); + } + + private static ulong Hash(IEnumerable values) + { + const ulong offset = 14695981039346656037UL; + const ulong prime = 1099511628211UL; + var hash = offset; + foreach (var value in values) hash = unchecked((hash ^ value) * prime); + return hash == 0 ? 1 : hash; + } +} diff --git a/src/CultMath/PlanetaryTopology.cs b/src/CultMath/PlanetaryTopology.cs new file mode 100644 index 00000000..c8535079 --- /dev/null +++ b/src/CultMath/PlanetaryTopology.cs @@ -0,0 +1,170 @@ +namespace CultMath; + +public enum PlanetaryCubeFace +{ + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ, +} + +public readonly record struct PlanetaryFaceCoordinate(PlanetaryCubeFace Face, double U, double V) +{ + public PlanetaryFaceCoordinate Validate(bool allowOutsideFace = false) + { + if (!double.IsFinite(U) || !double.IsFinite(V)) throw new ArgumentOutOfRangeException(nameof(U)); + if (!allowOutsideFace && (U is < -1.0 or > 1.0 || V is < -1.0 or > 1.0)) + throw new ArgumentOutOfRangeException(nameof(U), "Face coordinates must be in [-1, 1]."); + if (!Enum.IsDefined(typeof(PlanetaryCubeFace), Face)) throw new ArgumentOutOfRangeException(nameof(Face)); + return this; + } +} + +public readonly record struct PlanetaryTileAddress +{ + public const int MaxLevel = 30; + + public PlanetaryTileAddress(PlanetaryCubeFace face, int level, int x, int y) + { + if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face)); + if (level is < 0 or > MaxLevel) throw new ArgumentOutOfRangeException(nameof(level)); + var count = 1 << level; + if ((uint)x >= (uint)count) throw new ArgumentOutOfRangeException(nameof(x)); + if ((uint)y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(y)); + Face = face; + Level = level; + X = x; + Y = y; + } + + public PlanetaryCubeFace Face { get; } + public int Level { get; } + public int X { get; } + public int Y { get; } + public int AxisTileCount => 1 << Level; + + public PlanetaryTileAddress Parent() => Level == 0 + ? throw new InvalidOperationException("Root cube tile has no parent.") + : new(Face, Level - 1, X >> 1, Y >> 1); + + public PlanetaryTileAddress Child(int childX, int childY) + { + if ((uint)childX > 1) throw new ArgumentOutOfRangeException(nameof(childX)); + if ((uint)childY > 1) throw new ArgumentOutOfRangeException(nameof(childY)); + if (Level == MaxLevel) throw new InvalidOperationException($"Level {MaxLevel} cannot be subdivided."); + return new(Face, Level + 1, (X << 1) | childX, (Y << 1) | childY); + } + + public PlanetaryFaceCoordinate PositionAt(double localU, double localV) + { + if (!double.IsFinite(localU) || localU is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localU)); + if (!double.IsFinite(localV) || localV is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localV)); + var count = AxisTileCount; + return new(Face, -1.0 + 2.0 * (X + localU) / count, -1.0 + 2.0 * (Y + localV) / count); + } + + public ulong StableKey + { + get + { + const ulong offset = 14695981039346656037UL; + const ulong prime = 1099511628211UL; + var hash = offset; + hash = unchecked((hash ^ (uint)Face) * prime); + hash = unchecked((hash ^ (uint)Level) * prime); + hash = unchecked((hash ^ (uint)X) * prime); + hash = unchecked((hash ^ (uint)Y) * prime); + return hash == 0 ? 1 : hash; + } + } + public override string ToString() => $"{Face}/L{Level:D2}/{X}/{Y}"; +} + +public static class PlanetaryTopology +{ + private const float QuarterPi = MathF.PI * 0.25f; + + public static float3 Direction(PlanetaryFaceCoordinate coordinate) + { + coordinate.Validate(allowOutsideFace: true); + var u = MathF.Tan((float)coordinate.U * QuarterPi); + var v = MathF.Tan((float)coordinate.V * QuarterPi); + var cube = coordinate.Face switch + { + PlanetaryCubeFace.PositiveX => new float3(1, v, -u), + PlanetaryCubeFace.NegativeX => new float3(-1, v, u), + PlanetaryCubeFace.PositiveY => new float3(u, 1, -v), + PlanetaryCubeFace.NegativeY => new float3(u, -1, v), + PlanetaryCubeFace.PositiveZ => new float3(u, v, 1), + PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1), + _ => throw new ArgumentOutOfRangeException(nameof(coordinate)), + }; + return math.normalize(cube); + } + + public static PlanetaryFaceCoordinate FaceCoordinate(float3 direction) + { + ValidateDirection(direction); + var d = math.normalize(direction); + var a = math.abs(d); + if (a.x >= a.y && a.x >= a.z) + return d.x >= 0 + ? FromCubeUv(PlanetaryCubeFace.PositiveX, -d.z / a.x, d.y / a.x) + : FromCubeUv(PlanetaryCubeFace.NegativeX, d.z / a.x, d.y / a.x); + if (a.y >= a.z) + return d.y >= 0 + ? FromCubeUv(PlanetaryCubeFace.PositiveY, d.x / a.y, -d.z / a.y) + : FromCubeUv(PlanetaryCubeFace.NegativeY, d.x / a.y, d.z / a.y); + return d.z >= 0 + ? FromCubeUv(PlanetaryCubeFace.PositiveZ, d.x / a.z, d.y / a.z) + : FromCubeUv(PlanetaryCubeFace.NegativeZ, -d.x / a.z, d.y / a.z); + } + + public static PlanetaryTileAddress TileAt(float3 direction, int level) + { + if (level is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(level)); + var face = FaceCoordinate(direction); + var count = 1 << level; + var x = Math.Clamp((int)((face.U * 0.5 + 0.5) * count), 0, count - 1); + var y = Math.Clamp((int)((face.V * 0.5 + 0.5) * count), 0, count - 1); + return new(face.Face, level, x, y); + } + + public static bool TryLocalCoordinate(float3 direction, PlanetaryTileAddress tile, out float2 local) + { + var face = FaceCoordinate(direction); + if (face.Face != tile.Face) { local = float2.zero; return false; } + var scaledU = (face.U * 0.5 + 0.5) * tile.AxisTileCount; + var scaledV = (face.V * 0.5 + 0.5) * tile.AxisTileCount; + local = new((float)(scaledU - tile.X), (float)(scaledV - tile.Y)); + return local.x is >= 0 and <= 1 && local.y is >= 0 and <= 1; + } + + public static float3 SurfaceNormal(float3 unitDirection, float3 worldDistanceTangentGradient) + { + ValidateDirection(unitDirection); + ValidateFinite(worldDistanceTangentGradient, nameof(worldDistanceTangentGradient)); + var direction = math.normalize(unitDirection); + var tangent = worldDistanceTangentGradient - direction * math.dot(worldDistanceTangentGradient, direction); + return math.normalize(direction - tangent); + } + + private static PlanetaryFaceCoordinate FromCubeUv(PlanetaryCubeFace face, float cubeU, float cubeV) => new( + face, + MathF.Atan(cubeU) / QuarterPi, + MathF.Atan(cubeV) / QuarterPi); + + internal static void ValidateDirection(float3 direction) + { + ValidateFinite(direction, nameof(direction)); + if (math.length(direction) < 1.0e-12f) throw new ArgumentOutOfRangeException(nameof(direction)); + } + + internal static void ValidateFinite(float3 value, string name) + { + if (!float.IsFinite(value.x) || !float.IsFinite(value.y) || !float.IsFinite(value.z)) + throw new ArgumentOutOfRangeException(name); + } +} diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 0e904a32..567b038b 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -65,6 +65,22 @@ public sealed class HlslMirrorTests Assert.Contains("max(length(value), CULTMATH_NORMALIZE_EPSILON)", include); } + [Fact] + public void HlslMirrorPublishesPlanetaryTopologyAndField() + { + var root = GetRepositoryRoot(); + var include = File.ReadAllText(Path.Combine(root, "shaders", "CultMath.hlsl")); + var planetary = File.ReadAllText(Path.Combine(root, "shaders", "Planetary.hlsl")); + Assert.Contains("Planetary.hlsl", include); + Assert.Contains("cultmath_planetary_face_direction", planetary); + Assert.Contains("cultmath_planetary_face_coordinate", planetary); + Assert.Contains("cultmath_planetary_page_local", planetary); + Assert.Contains("cultmath_planetary_field_sample", planetary); + Assert.Contains("cultmath_planetary_equirectangular_forward", planetary); + Assert.Contains("cultmath_planetary_equal_earth_forward", planetary); + Assert.Contains("CultMathPlanetarySurfaceSample", planetary); + } + private static string GetRepositoryRoot() { var directory = new DirectoryInfo(AppContext.BaseDirectory); diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs b/tests/CultMath.Tests/PlanetaryFieldTests.cs new file mode 100644 index 00000000..4dc75325 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryFieldTests.cs @@ -0,0 +1,80 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryFieldTests +{ + private static readonly PlanetaryFieldDefinition Definition = new( + 0x7f59_6d11_02ab_9c31UL, + 8.4f, + 42, + AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 }); + + [Fact] + public void SampleIsDeterministicScaleAwareAndTangent() + { + var direction = math.normalize(new float3(0.31f, -0.72f, 0.61f)); + var source = new FixtureField(); + var coarse = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.1f)); + var fine = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f)); + var repeated = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f)); + + Assert.Equal(fine, repeated); + Assert.Equal(Definition.FieldVersion, fine.FieldVersion); + Assert.True(fine.UnresolvedHeightBound <= coarse.UnresolvedHeightBound); + Assert.True(fine.FinestResolvedWavelength <= coarse.FinestResolvedWavelength); + Assert.InRange(MathF.Abs(math.dot(fine.UnitDirection, fine.TangentGradient)), 0, 2.0e-5f); + Assert.Equal(1, math.length(fine.SurfaceNormal), 5); + } + + [Fact] + public void BatchAndDoublePositionUseThePointContract() + { + var directions = new[] + { + math.normalize(new float3(1, 2, 3)), + math.normalize(new float3(-2, 0.5f, 1)), + math.normalize(new float3(0.1f, -0.2f, -1)), + }; + var output = new PlanetarySurfaceSample[directions.Length]; + var source = new FixtureField(); + var scale = PlanetaryQueryScale.AtFootprint(0.05f); + PlanetaryField.SampleBatch(Definition, directions, scale, source, output); + + for (var i = 0; i < directions.Length; i++) + { + var normalized = math.normalize(directions[i]); + var expected = PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), scale); + Assert.InRange(MathF.Abs(expected.RadialDisplacement - output[i].RadialDisplacement), 0, 1.0e-6f); + Assert.InRange(math.distance(expected.TangentGradient, output[i].TangentGradient), 0, 1.0e-6f); + } + + var d = directions[0]; + var position = new double3(d.x * 8.0e11, d.y * 8.0e11, d.z * 8.0e11); + var positioned = PlanetaryField.SamplePosition(Definition, position, scale, source); + Assert.InRange(math.distance(positioned.UnitDirection, d), 0, 2.0e-6f); + } + + [Fact] + public void FieldIdentityChangesWithEveryLoadBearingInput() + { + var erosion = AdvancedErosionParameters.Default; + var baseline = PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion); + Assert.Equal(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion).FieldVersion); + Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(4, 8.4f, 7, erosion).FieldVersion); + Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.5f, 7, erosion).FieldVersion); + Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 8, erosion).FieldVersion); + Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion with { Gain = 0.49f }).FieldVersion); + } + + private readonly struct FixtureField : IPlanetaryBaseField + { + public PlanetaryBaseFieldSample Sample(float3 direction) + { + var value = direction.x * 0.6f + direction.y * direction.z * 0.3f; + var fieldGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f); + fieldGradient -= direction * math.dot(fieldGradient, direction); + return new(value * 0.1f, fieldGradient * 0.1f, value, fieldGradient, math.clamp(value, -1, 1)); + } + } +} diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs b/tests/CultMath.Tests/PlanetaryPageTests.cs new file mode 100644 index 00000000..c4e4045b --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryPageTests.cs @@ -0,0 +1,57 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryPageTests +{ + [Fact] + public void BilinearSamplingAndSummaryUseOnePageContract() + { + var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 3, 1); + var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize]; + for (var y = 0; y < layout.StorageSize; y++) + for (var x = 0; x < layout.StorageSize; x++) + samples[y * layout.StorageSize + x] = new(x + y * 10, new float3(x, y, 0), x / 10.0f, y / 10.0f); + + var sample = PlanetaryPageSampling.Bilinear(samples, layout, new float2(0.25f, 0.75f)); + Assert.Equal(26.5f, sample.Height, 4); + Assert.Equal(new float3(1.5f, 2.5f, 0), sample.TangentGradient); + var summary = PlanetaryPageSampling.Summarize(samples, layout, 0.125f); + Assert.Equal(0, summary.MinimumHeight); + Assert.Equal(44, summary.MaximumHeight); + Assert.Equal(0.125f, summary.UnresolvedHeightBound); + Assert.True(summary.MaximumSlope > 0); + } + + [Fact] + public void ChildPageStoresOnlyResidualAndComposesBackToDirectField() + { + var field = new PlanetaryFieldDefinition(7, 8.4f, 3, AdvancedErosionParameters.Default with + { + Scale = 0.63f, + Strength = 0.12f, + Octaves = 7, + }); + var source = new FixtureField(); + var root = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 17, 2), source); + var child = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2), source); + var direction = PlanetaryPageSampling.DirectionAtLocal(child.Layout, 0.5, 0.5); + var pages = new[] { new PlanetaryResidentPage(root, 1), new PlanetaryResidentPage(child, 1) }; + Assert.True(PlanetaryPageSetSampling.TrySample(pages, direction, out var composed)); + var spacing = PlanetaryPageSampling.NominalAngularTexelSize(child.Layout) * field.Radius; + var direct = PlanetaryField.Sample(field, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(spacing)); + Assert.InRange(MathF.Abs(composed.Height - direct.RadialDisplacement), 0, 2.0e-5f); + Assert.InRange(math.distance(composed.TangentGradient, direct.TangentGradient), 0, 3.0e-5f); + } + + private readonly struct FixtureField : IPlanetaryBaseField + { + public PlanetaryBaseFieldSample Sample(float3 direction) + { + var value = direction.x * 0.4f - direction.y * 0.2f + direction.z * 0.5f; + var gradient = new float3(0.4f, -0.2f, 0.5f); + gradient -= direction * math.dot(gradient, direction); + return new(value * 0.08f, gradient * 0.08f, value, gradient, value); + } + } +} diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs b/tests/CultMath.Tests/PlanetaryPatchTests.cs new file mode 100644 index 00000000..6564c773 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryPatchTests.cs @@ -0,0 +1,40 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryPatchTests +{ + [Fact] + public void CubeSpherePatchMeshUsesCanonicalTopology() + { + var meshes = PlanetaryPatch.CreateCubeSphere(8); + Assert.Equal(6, meshes.Length); + foreach (var mesh in meshes) + { + Assert.Equal(81, mesh.Vertices.Length); + Assert.Equal(8 * 8 * 6, mesh.Indices.Length); + Assert.All(mesh.Vertices, vertex => Assert.Equal(1, math.length(vertex.UnitDirection), 5)); + foreach (var index in mesh.Indices) Assert.InRange(index, 0, mesh.Vertices.Length - 1); + } + } + + [Fact] + public void NeighborFaceCornersAreSingleValued() + { + var directions = PlanetaryPatch.CreateCubeSphere(1) + .SelectMany(mesh => mesh.Vertices.Select(vertex => vertex.UnitDirection)) + .ToArray(); + var groups = directions.GroupBy(direction => new + { + X = Math.Sign(direction.x), + Y = Math.Sign(direction.y), + Z = Math.Sign(direction.z), + }); + Assert.Equal(8, groups.Count()); + Assert.All(groups, group => + { + var first = group.First(); + Assert.All(group, direction => Assert.InRange(math.distance(first, direction), 0, 1.0e-6f)); + }); + } +} diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs b/tests/CultMath.Tests/PlanetaryProjectionTests.cs new file mode 100644 index 00000000..ad811d6e --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryProjectionTests.cs @@ -0,0 +1,73 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryProjectionTests +{ + [Theory] + [InlineData(PlanetaryProjectionKind.Equirectangular)] + [InlineData(PlanetaryProjectionKind.WebMercator)] + [InlineData(PlanetaryProjectionKind.EqualEarth)] + [InlineData(PlanetaryProjectionKind.Orthographic)] + [InlineData(PlanetaryProjectionKind.AzimuthalEquidistant)] + [InlineData(PlanetaryProjectionKind.AzimuthalEqualArea)] + [InlineData(PlanetaryProjectionKind.LocalTangent)] + public void ProjectionRoundTripsVisibleDirections(PlanetaryProjectionKind kind) + { + var parameters = new PlanetaryProjectionParameters(kind, 0.31, -0.22); + foreach (var longitude in new[] { -0.4, -0.1, 0.2, 0.65 }) + foreach (var latitude in new[] { -0.6, -0.15, 0.3, 0.7 }) + { + var direction = Direction(longitude + parameters.CenterLongitude, latitude); + if (!PlanetaryProjection.TryForward(direction, parameters, out var map)) continue; + Assert.True(PlanetaryProjection.TryInverse(map, parameters, out var roundTrip)); + Assert.InRange(math.distance(direction, roundTrip), 0, 3.0e-5f); + } + } + + [Fact] + public void CubeAtlasRoundTripsEveryFaceInterior() + { + var projection = new PlanetaryProjectionParameters(PlanetaryProjectionKind.CubeAtlas); + foreach (var face in Enum.GetValues()) + { + var direction = PlanetaryTopology.Direction(new(face, 0.23, -0.41)); + Assert.True(PlanetaryProjection.TryForward(direction, projection, out var map)); + Assert.True(PlanetaryProjection.TryInverse(map, projection, out var roundTrip)); + Assert.InRange(math.distance(direction, roundTrip), 0, 2.0e-6f); + } + } + + [Fact] + public void MercatorRejectsPolarSingularity() + { + var northPole = new float3(0, 0, 1); + Assert.False(PlanetaryProjection.TryForward(northPole, new(PlanetaryProjectionKind.WebMercator), out _)); + } + + [Fact] + public void MapTileBakerCarriesFieldIdentityAndProjectionValidity() + { + var field = new PlanetaryFieldDefinition(99, 10, 0, AdvancedErosionParameters.Default); + var layout = new PlanetaryMapTileLayout( + new(PlanetaryProjectionKind.Orthographic), 0, 0, 0, 9, 1); + var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 7, layout, PlanetaryQueryScale.AtFootprint(0.1f)); + var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); + Assert.Equal(key, tile.Key); + Assert.Equal(layout.StorageSize * layout.StorageSize, tile.Samples.Length); + Assert.Contains(tile.Validity, valid => valid); + Assert.Contains(tile.Validity, valid => !valid); + Assert.All(tile.Samples.Where((_, index) => tile.Validity[index]), sample => Assert.Equal(field.FieldVersion, sample.FieldVersion)); + } + + private static float3 Direction(double longitude, double latitude) + { + var cos = Math.Cos(latitude); + return new((float)(cos * Math.Cos(longitude)), (float)(cos * Math.Sin(longitude)), (float)Math.Sin(latitude)); + } + + private readonly struct FlatField : IPlanetaryBaseField + { + public PlanetaryBaseFieldSample Sample(float3 unitDirection) => new(0, float3.zero, 0, float3.zero, 0); + } +} diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs b/tests/CultMath.Tests/PlanetaryQueryTests.cs new file mode 100644 index 00000000..48d865f4 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryQueryTests.cs @@ -0,0 +1,46 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryQueryTests +{ + private static readonly PlanetaryFieldDefinition Field = new(12, 10, 0, AdvancedErosionParameters.Default with { Strength = 0, Octaves = 0 }); + private static readonly PlanetaryQueryScale Scale = PlanetaryQueryScale.AtFootprint(0.1f, 0); + + [Fact] + public void RayIntersectionAndClearanceUseTheCpuSurfaceContract() + { + var source = new BulgedSphere(); + Assert.True(PlanetaryQueries.TryIntersectRay( + Field, new double3(0, 0, 20), new double3(0, 0, -1), 30, + Scale, new PlanetaryRadialBounds(-1, 1), source, out var hit)); + Assert.InRange(hit.Distance, 8.99999, 9.00001); + Assert.InRange(hit.PlanetLocalPosition.z, 10.99999, 11.00001); + var clearance = PlanetaryQueries.SegmentClearance( + Field, new double3(-2, 0, 12), new double3(2, 0, 12), 0.1, Scale, source); + Assert.True(clearance > 0.8); + } + + [Fact] + public void GreatCircleAndRegionQueriesReturnVersionedSamples() + { + var source = new BulgedSphere(); + var path = PlanetaryQueries.SampleGreatCircle(Field, new float3(1, 0, 0), new float3(0, 1, 0), 8, Scale, source); + Assert.Equal(9, path.Length); + Assert.Equal(MathF.PI * 5, path[^1].Distance, 4); + Assert.All(path, point => Assert.Equal(Field.FieldVersion, point.Surface.FieldVersion)); + var summary = PlanetaryQueries.Summarize(Field, path.Select(point => point.Surface.UnitDirection).ToArray(), Scale, source); + Assert.True(summary.MaximumDisplacement >= summary.MinimumDisplacement); + Assert.True(summary.MaximumSlope >= 0); + } + + private readonly struct BulgedSphere : IPlanetaryBaseField + { + public PlanetaryBaseFieldSample Sample(float3 direction) + { + var displacement = direction.z; + var gradient = new float3(0, 0, 1) - direction * direction.z; + return new(displacement, gradient, 0, float3.zero, 0); + } + } +} diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs b/tests/CultMath.Tests/PlanetaryResidencyTests.cs new file mode 100644 index 00000000..d2b89b7a --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryResidencyTests.cs @@ -0,0 +1,57 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryResidencyTests +{ + private static readonly PlanetaryFieldDefinition Field = new( + 1, 8.4f, 0, AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 }); + private static readonly PlanetaryLodParameters Lod = new(5, 17, 2, 720, 8.4f / 65536); + + [Fact] + public void SelectorReturnsSixRootsAndOneAncestorChain() + { + var direction = math.normalize(new float3(-0.2f, 0.91f, 0.37f)); + var selected = PlanetaryLodSelector.SelectAncestorChain(Field, direction, 8.45f, Lod); + Assert.Equal(6, selected.Count(tile => tile.Level == 0)); + var descendants = selected.Where(tile => tile.Level > 0).OrderBy(tile => tile.Level).ToArray(); + for (var i = 1; i < descendants.Length; i++) Assert.Equal(descendants[i - 1], descendants[i].Parent()); + if (descendants.Length > 0) Assert.Equal(PlanetaryTopology.TileAt(direction, descendants[^1].Level), descendants[^1]); + } + + [Fact] + public void ResidencyFadesResidualsWithoutChangingContentDuringPresentation() + { + var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0); + var child = root.Child(1, 0); + var residency = new PlanetaryResidualResidency(); + var arrival = residency.Update(new[] { root, child }, 0, 1); + var midpoint = residency.Update(new[] { root, child }, 0.5f, 1); + var settled = residency.Update(new[] { root, child }, 1, 1); + Assert.Equal(arrival.ContentVersion, midpoint.ContentVersion); + Assert.Equal(midpoint.ContentVersion, settled.ContentVersion); + Assert.Equal(0, arrival.Tiles.Single(tile => tile.Tile == child).Blend); + Assert.Equal(0.5f, midpoint.Tiles.Single(tile => tile.Tile == child).Blend); + Assert.Equal(1, settled.Tiles.Single(tile => tile.Tile == child).Blend); + + var leaving = residency.Update(new[] { root }, 1.25f, 1); + Assert.True(leaving.Tiles.Single(tile => tile.Tile == child).Departing); + Assert.Equal(1, leaving.Tiles.Single(tile => tile.Tile == child).Blend); + var gone = residency.Update(new[] { root }, 2.25f, 1); + Assert.DoesNotContain(gone.Tiles, tile => tile.Tile == child); + } + + [Fact] + public void ReversingAnArrivalDepartsFromItsCurrentWeightWithoutJumping() + { + var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0); + var child = root.Child(0, 1); + var residency = new PlanetaryResidualResidency(); + _ = residency.Update(new[] { root, child }, 0, 1); + var arriving = residency.Update(new[] { root, child }, 0.25f, 1); + var departing = residency.Update(new[] { root }, 0.25f, 1); + Assert.Equal(0.25f, arriving.Tiles.Single(tile => tile.Tile == child).Blend); + Assert.Equal(0.25f, departing.Tiles.Single(tile => tile.Tile == child).Blend); + Assert.True(departing.Tiles.Single(tile => tile.Tile == child).Departing); + } +} diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs b/tests/CultMath.Tests/PlanetaryTopologyTests.cs new file mode 100644 index 00000000..6e6b0838 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryTopologyTests.cs @@ -0,0 +1,55 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryTopologyTests +{ + [Fact] + public void FaceDirectionRoundTripsAcrossEveryFace() + { + foreach (var face in Enum.GetValues()) + foreach (var u in new[] { -0.9, -0.25, 0.0, 0.4, 0.9 }) + foreach (var v in new[] { -0.85, -0.1, 0.3, 0.88 }) + { + var direction = PlanetaryTopology.Direction(new(face, u, v)); + var roundTrip = PlanetaryTopology.FaceCoordinate(direction); + Assert.Equal(face, roundTrip.Face); + Assert.Equal(u, roundTrip.U, 5); + Assert.Equal(v, roundTrip.V, 5); + } + } + + [Fact] + public void SiblingPagesShareBitStableBoundaryDirections() + { + var left = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 0, 0), 17, 2); + var right = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2); + for (var y = 0; y < left.InteriorSize; y++) + { + var a = PlanetaryPageSampling.Direction(left, left.BorderSize + left.InteriorSize - 1, left.BorderSize + y); + var b = PlanetaryPageSampling.Direction(right, right.BorderSize, right.BorderSize + y); + Assert.Equal(a, b); + } + } + + [Fact] + public void DirectionSelectsContainingTileAndLocalCoordinate() + { + var expected = new PlanetaryTileAddress(PlanetaryCubeFace.NegativeY, 5, 19, 7); + var direction = PlanetaryPageSampling.DirectionAtLocal(new(expected, 17, 2), 0.37, 0.61); + Assert.Equal(expected, PlanetaryTopology.TileAt(direction, expected.Level)); + Assert.True(PlanetaryTopology.TryLocalCoordinate(direction, expected, out var local)); + Assert.Equal(0.37f, local.x, 4); + Assert.Equal(0.61f, local.y, 4); + } + + [Fact] + public void BorderSamplesDoNotRequireNeighborResidency() + { + var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), 17, 3); + var edge = PlanetaryPageSampling.Direction(page, page.BorderSize + page.InteriorSize - 1, page.BorderSize + 8); + var border = PlanetaryPageSampling.Direction(page, page.StorageSize - 1, page.BorderSize + 8); + Assert.NotEqual(edge, border); + Assert.Equal(1, math.length(border), 5); + } +} diff --git a/unity/Runtime/CultMath.Unity.asmdef b/unity/Runtime/CultMath.Unity.asmdef new file mode 100644 index 00000000..632c4e4c --- /dev/null +++ b/unity/Runtime/CultMath.Unity.asmdef @@ -0,0 +1,16 @@ +{ + "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/Runtime/PlanetaryPageUpload.cs b/unity/Runtime/PlanetaryPageUpload.cs new file mode 100644 index 00000000..f0b168cd --- /dev/null +++ b/unity/Runtime/PlanetaryPageUpload.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace CultMath.Unity; + +[StructLayout(LayoutKind.Sequential)] +public readonly struct PlanetaryPageInputGpu +{ + public PlanetaryPageInputGpu(Vector4 directionRadius, Vector4 sampling) + { + DirectionRadius = directionRadius; + Sampling = sampling; + } + + public readonly Vector4 DirectionRadius; + public readonly Vector4 Sampling; +} + +[StructLayout(LayoutKind.Sequential)] +public readonly struct PlanetaryPageMetadataGpu +{ + public PlanetaryPageMetadataGpu(Vector4 address, Vector4 layout, Vector4 bounds, Vector4 state) + { + Address = address; + Layout = layout; + Bounds = bounds; + State = state; + } + + public readonly Vector4 Address; + public readonly Vector4 Layout; + public readonly Vector4 Bounds; + public readonly Vector4 State; +} + +public readonly record struct PlanetaryPageUpload( + PlanetaryPageInputGpu[] Inputs, + PlanetaryPageMetadataGpu[] Metadata, + int OutputSampleCount, + ulong ContentVersion, + ulong PresentationVersion); + +public static class PlanetaryPageUploadBuilder +{ + public static PlanetaryPageUpload Build( + in PlanetaryResidencySnapshot snapshot, + int interiorSize, + int borderSize, + float radius) + { + if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); + var inputs = new List(); + var metadata = new PlanetaryPageMetadataGpu[snapshot.Tiles.Length]; + foreach (var (resident, pageIndex) in snapshot.Tiles.Select((resident, index) => (resident, index))) + { + var layout = new PlanetaryPageLayout(resident.Tile, interiorSize, borderSize).Validate(); + var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * radius; + var parentSpacing = resident.Tile.Level == 0 + ? 0 + : PlanetaryPageSampling.NominalAngularTexelSize(new(resident.Tile.Parent(), interiorSize, borderSize)) * radius; + var outputOffset = inputs.Count; + for (var y = 0; y < layout.StorageSize; y++) + for (var x = 0; x < layout.StorageSize; x++) + { + var direction = PlanetaryPageSampling.Direction(layout, x, y); + inputs.Add(new( + new Vector4(direction.x, direction.y, direction.z, radius), + new Vector4(spacing, parentSpacing, 0, 0))); + } + metadata[pageIndex] = new( + new Vector4((int)resident.Tile.Face, resident.Tile.Level, resident.Tile.X, resident.Tile.Y), + new Vector4(outputOffset, layout.StorageSize, layout.InteriorSize, layout.BorderSize), + Vector4.zero, + new Vector4(1, resident.Blend, PlanetaryPageSampling.NominalAngularTexelSize(layout), spacing)); + } + return new(inputs.ToArray(), metadata, inputs.Count, snapshot.ContentVersion, snapshot.PresentationVersion); + } +} diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs b/unity/Runtime/PlanetaryPatchMeshAdapter.cs new file mode 100644 index 00000000..69a72a2a --- /dev/null +++ b/unity/Runtime/PlanetaryPatchMeshAdapter.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; + +namespace CultMath.Unity; + +public static class PlanetaryPatchMeshAdapter +{ + public static Mesh CreateFaceMesh(PlanetaryCubeFace face, int cellsPerAxis, float radius = 1) + { + if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); + var source = PlanetaryPatch.CreateFace(face, cellsPerAxis); + var vertices = new Vector3[source.Vertices.Length]; + var normals = new Vector3[source.Vertices.Length]; + var uv = new Vector2[source.Vertices.Length]; + for (var i = 0; i < source.Vertices.Length; i++) + { + var direction = source.Vertices[i].UnitDirection; + var normal = new Vector3(direction.x, direction.y, direction.z); + vertices[i] = normal * radius; + normals[i] = normal; + uv[i] = new Vector2(source.Vertices[i].LocalCoordinate.x, source.Vertices[i].LocalCoordinate.y); + } + var mesh = new Mesh + { + name = $"CultMath Planetary {face} {cellsPerAxis}x{cellsPerAxis}", + indexFormat = vertices.Length > ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16, + vertices = vertices, + normals = normals, + uv = uv, + triangles = source.Indices, + }; + mesh.RecalculateBounds(); + return mesh; + } +} From 102403efac25efea5986c1f114c11437ae1a6b03 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 02:52:13 +0100 Subject: [PATCH 016/203] Complete portable planetary presentation contracts --- .gitignore | 1 + CultMath.slnx | 3 + README.md | 14 ++ .../CultMath.PlanetaryViewer.Editor.asmdef | 18 +++ .../Editor/CultMathPlanetaryViewerMenu.cs | 26 ++++ Samples~/PlanetaryViewer/README.md | 16 ++ .../Runtime/CultMath.PlanetaryViewer.asmdef | 17 +++ .../Runtime/CultMathPlanetaryViewer.cs | 139 ++++++++++++++++++ .../Runtime/CultMathPlanetaryViewer.shader | 90 ++++++++++++ THIRD-PARTY-NOTICES.md.meta | 7 + .../CultMath.PlanetaryTileFixture.csproj | 11 ++ .../CultMath.PlanetaryTileFixture/Program.cs | 22 +++ docs/planetary.md | 29 +++- docs/planetary.md.meta | 7 + package.json | 9 +- scripts.meta | 8 + scripts/test-web-planetary-tile.ps1 | 9 ++ scripts/test-web-planetary-tile.ps1.meta | 7 + shaders/AdvancedErosionFilter.hlsl.meta | 7 + shaders/Planetary.hlsl | 132 +++++++++++++++++ shaders/Planetary.hlsl.meta | 7 + src/CultMath/AdvancedErosionFilter.cs.meta | 2 + src/CultMath/ErosionFrequencyBands.cs.meta | 2 + src/CultMath/PlanetaryField.cs | 2 + src/CultMath/PlanetaryField.cs.meta | 2 + src/CultMath/PlanetaryGpuPages.cs | 55 +++++++ src/CultMath/PlanetaryGpuPages.cs.meta | 2 + src/CultMath/PlanetaryMapTileEncoding.cs | 117 +++++++++++++++ src/CultMath/PlanetaryMapTileEncoding.cs.meta | 2 + src/CultMath/PlanetaryPages.cs | 2 + src/CultMath/PlanetaryPages.cs.meta | 2 + src/CultMath/PlanetaryPatch.cs | 3 + src/CultMath/PlanetaryPatch.cs.meta | 2 + src/CultMath/PlanetaryProjection.cs | 2 + src/CultMath/PlanetaryProjection.cs.meta | 2 + src/CultMath/PlanetaryQueries.cs | 2 + src/CultMath/PlanetaryQueries.cs.meta | 2 + .../PlanetaryRadialRefinement.cs.meta | 2 + src/CultMath/PlanetaryResidency.cs | 10 +- src/CultMath/PlanetaryResidency.cs.meta | 2 + src/CultMath/PlanetaryTopology.cs | 21 +++ src/CultMath/PlanetaryTopology.cs.meta | 2 + src/CultMath/SphericalErosion.cs.meta | 2 + .../AdvancedErosionFilterTests.cs.meta | 2 + .../ErosionFrequencyBandsTests.cs.meta | 2 + tests/CultMath.Tests/HlslMirrorTests.cs | 2 + .../PlanetaryFieldTests.cs.meta | 2 + tests/CultMath.Tests/PlanetaryGpuPageTests.cs | 25 ++++ .../PlanetaryGpuPageTests.cs.meta | 2 + .../PlanetaryMapTileEncodingTests.cs | 52 +++++++ .../PlanetaryMapTileEncodingTests.cs.meta | 2 + .../CultMath.Tests/PlanetaryPageTests.cs.meta | 2 + .../PlanetaryPatchTests.cs.meta | 2 + .../PlanetaryProjectionTests.cs.meta | 2 + .../PlanetaryQueryTests.cs.meta | 2 + .../PlanetaryRadialRefinementTests.cs.meta | 2 + .../PlanetaryResidencyTests.cs.meta | 2 + .../CultMath.Tests/PlanetaryTopologyTests.cs | 8 + .../PlanetaryTopologyTests.cs.meta | 2 + .../SphericalErosionTests.cs.meta | 2 + tools/CultMath.PlanetaryTileFixture.meta | 8 + unity.meta | 8 + unity/Runtime.meta | 8 + unity/Runtime/CultMath.Unity.asmdef.meta | 7 + unity/Runtime/PlanetaryPageUpload.cs | 52 ++++--- unity/Runtime/PlanetaryPageUpload.cs.meta | 2 + .../Runtime/PlanetaryPatchMeshAdapter.cs.meta | 2 + web.meta | 8 + web/planetary-tile.cross-runtime.test.mjs | 21 +++ ...planetary-tile.cross-runtime.test.mjs.meta | 7 + web/planetary-tile.test.mjs | 39 +++++ web/planetary-tile.test.mjs.meta | 7 + web/planetary-tile.ts | 89 +++++++++++ web/planetary-tile.ts.meta | 7 + 74 files changed, 1166 insertions(+), 31 deletions(-) create mode 100644 Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef create mode 100644 Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs create mode 100644 Samples~/PlanetaryViewer/README.md create mode 100644 Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef create mode 100644 Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs create mode 100644 Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader create mode 100644 THIRD-PARTY-NOTICES.md.meta create mode 100644 Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj create mode 100644 Tools~/CultMath.PlanetaryTileFixture/Program.cs create mode 100644 docs/planetary.md.meta create mode 100644 scripts.meta create mode 100644 scripts/test-web-planetary-tile.ps1 create mode 100644 scripts/test-web-planetary-tile.ps1.meta create mode 100644 shaders/AdvancedErosionFilter.hlsl.meta create mode 100644 shaders/Planetary.hlsl.meta create mode 100644 src/CultMath/AdvancedErosionFilter.cs.meta create mode 100644 src/CultMath/ErosionFrequencyBands.cs.meta create mode 100644 src/CultMath/PlanetaryField.cs.meta create mode 100644 src/CultMath/PlanetaryGpuPages.cs create mode 100644 src/CultMath/PlanetaryGpuPages.cs.meta create mode 100644 src/CultMath/PlanetaryMapTileEncoding.cs create mode 100644 src/CultMath/PlanetaryMapTileEncoding.cs.meta create mode 100644 src/CultMath/PlanetaryPages.cs.meta create mode 100644 src/CultMath/PlanetaryPatch.cs.meta create mode 100644 src/CultMath/PlanetaryProjection.cs.meta create mode 100644 src/CultMath/PlanetaryQueries.cs.meta create mode 100644 src/CultMath/PlanetaryRadialRefinement.cs.meta create mode 100644 src/CultMath/PlanetaryResidency.cs.meta create mode 100644 src/CultMath/PlanetaryTopology.cs.meta create mode 100644 src/CultMath/SphericalErosion.cs.meta create mode 100644 tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta create mode 100644 tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryFieldTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryGpuPageTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs create mode 100644 tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryPageTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryPatchTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryQueryTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta create mode 100644 tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta create mode 100644 tests/CultMath.Tests/SphericalErosionTests.cs.meta create mode 100644 tools/CultMath.PlanetaryTileFixture.meta create mode 100644 unity.meta create mode 100644 unity/Runtime.meta create mode 100644 unity/Runtime/CultMath.Unity.asmdef.meta create mode 100644 unity/Runtime/PlanetaryPageUpload.cs.meta create mode 100644 unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta create mode 100644 web.meta create mode 100644 web/planetary-tile.cross-runtime.test.mjs create mode 100644 web/planetary-tile.cross-runtime.test.mjs.meta create mode 100644 web/planetary-tile.test.mjs create mode 100644 web/planetary-tile.test.mjs.meta create mode 100644 web/planetary-tile.ts create mode 100644 web/planetary-tile.ts.meta diff --git a/.gitignore b/.gitignore index 5ae20c8e..3779002b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ obj/ **/obj/ target/ **/target/ +/native/cultmath-core/target.meta .tools/ diff --git a/CultMath.slnx b/CultMath.slnx index 95834573..05d17886 100644 --- a/CultMath.slnx +++ b/CultMath.slnx @@ -5,4 +5,7 @@ + + + diff --git a/README.md b/README.md index 29eb115f..c60a9912 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,20 @@ cargo test --manifest-path native/cultmath-core/Cargo.toml cargo build --release --manifest-path native/cultmath-core/Cargo.toml ``` +## Unity compiler + +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: + +```text +-langversion:latest +``` + +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. + ## Shader Tooling CultMath keeps portable DXC outside git under `.tools/`: diff --git a/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef b/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef new file mode 100644 index 00000000..10512326 --- /dev/null +++ b/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "CultMath.PlanetaryViewer.Editor", + "rootNamespace": "CultMath.Unity.Samples.Editor", + "references": [ + "CultMath.PlanetaryViewer" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs b/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs new file mode 100644 index 00000000..ade28c30 --- /dev/null +++ b/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs @@ -0,0 +1,26 @@ +using UnityEditor; +using UnityEngine; + +namespace CultMath.Unity.Samples.Editor; + +public static class CultMathPlanetaryViewerMenu +{ + [MenuItem("GameObject/CultMath/Planetary Field Viewer", false, 10)] + public static void CreateViewer() + { + var root = new GameObject("CultMath Planetary Field Viewer"); + Undo.RegisterCreatedObjectUndo(root, "Create CultMath Planetary Viewer"); + root.AddComponent(); + Selection.activeGameObject = root; + + if (Camera.main != null) return; + var cameraObject = new GameObject("Planetary Viewer Camera"); + Undo.RegisterCreatedObjectUndo(cameraObject, "Create Planetary Viewer Camera"); + cameraObject.tag = "MainCamera"; + cameraObject.transform.position = new Vector3(0, 0, -24); + cameraObject.transform.LookAt(root.transform); + var camera = cameraObject.AddComponent(); + camera.nearClipPlane = 0.01f; + camera.farClipPlane = 1000; + } +} diff --git a/Samples~/PlanetaryViewer/README.md b/Samples~/PlanetaryViewer/README.md new file mode 100644 index 00000000..ec4dcc3a --- /dev/null +++ b/Samples~/PlanetaryViewer/README.md @@ -0,0 +1,16 @@ +# Planetary Field Viewer + +Import this sample through Unity's Package Manager, then choose +`GameObject > CultMath > Planetary Field Viewer`. + +The host project must contain `Assets/csc.rsp` with +`-langversion:latest`, as documented in the CultMath package README. + +The generated six-face mesh uses `PlanetaryPatch`. Its shader calls +`cultmath_planetary_field_sample` from the packaged CultMath HLSL, while the +component exposes the matching CPU `Sample` method. Seed, radius, physical +sample footprint, erosion settings, and base-field equations are shared between +the two bodies. + +This is a renderer integration fixture, not a gameplay owner. It deliberately +contains no Aetheria state, biomes, resources, or simulation decisions. diff --git a/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef b/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef new file mode 100644 index 00000000..2d3676f4 --- /dev/null +++ b/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef @@ -0,0 +1,17 @@ +{ + "name": "CultMath.PlanetaryViewer", + "rootNamespace": "CultMath.Unity.Samples", + "references": [ + "CultMath", + "CultMath.Unity" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs new file mode 100644 index 00000000..06113728 --- /dev/null +++ b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace CultMath.Unity.Samples; + +[ExecuteAlways] +public sealed class CultMathPlanetaryViewer : MonoBehaviour +{ + [Min(0.001f)] public float Radius = 8.4f; + [Range(1, 256)] public int CellsPerFace = 64; + public int Seed = 42; + [Min(0.0001f)] public float SampleFootprint = 0.01f; + public Shader? PlanetShader; + public Vector3 LightDirection = new(-0.4f, 0.7f, -0.6f); + public Color LowColor = new(0.055f, 0.16f, 0.09f, 1); + public Color HighColor = new(0.62f, 0.48f, 0.28f, 1); + public Color RidgeColor = new(0.88f, 0.83f, 0.7f, 1); + + private readonly List meshes = new(); + private Material? material; + private int builtCells; + + public PlanetaryFieldDefinition Definition => PlanetaryFieldDefinition.Create( + 1, + Radius, + Seed, + ViewerErosion(Radius)); + + public PlanetarySurfaceSample Sample(float3 direction) + { + var source = new ViewerBaseField(Radius); + var normalized = math.normalize(direction); + return PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), PlanetaryQueryScale.AtFootprint(SampleFootprint)); + } + + private void OnEnable() => Rebuild(); + + private void OnValidate() + { + Radius = Mathf.Max(Radius, 0.001f); + CellsPerFace = Mathf.Clamp(CellsPerFace, 1, 256); + SampleFootprint = Mathf.Max(SampleFootprint, 0.0001f); + if (isActiveAndEnabled) Rebuild(); + } + + private void Update() + { + if (builtCells != CellsPerFace || material == null) Rebuild(); + ApplyMaterial(); + } + + private void Rebuild() + { + ClearGenerated(); + PlanetShader ??= Shader.Find("CultMath/Planetary Field Viewer"); + if (PlanetShader == null) return; + material = new Material(PlanetShader) { name = "CultMath Planetary Viewer Material", hideFlags = HideFlags.HideAndDontSave }; + foreach (var face in (PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace))) + { + var mesh = PlanetaryPatchMeshAdapter.CreateFaceMesh(face, CellsPerFace); + mesh.hideFlags = HideFlags.HideAndDontSave; + meshes.Add(mesh); + var child = new GameObject(face.ToString()) { hideFlags = HideFlags.DontSave }; + child.transform.SetParent(transform, false); + child.AddComponent().sharedMesh = mesh; + child.AddComponent().sharedMaterial = material; + } + builtCells = CellsPerFace; + ApplyMaterial(); + } + + private void ApplyMaterial() + { + if (material == null) return; + var p = ViewerErosion(Radius); + material.SetFloat("_Radius", Radius); + material.SetFloat("_SampleFootprint", SampleFootprint); + material.SetInt("_Seed", Seed); + material.SetFloat("_ErosionScale", p.Scale); + material.SetFloat("_ErosionStrength", p.Strength); + material.SetFloat("_GullyWeight", p.GullyWeight); + material.SetFloat("_Detail", p.Detail); + material.SetVector("_Rounding", new(p.Rounding.x, p.Rounding.y, p.Rounding.z, p.Rounding.w)); + material.SetVector("_Onset", new(p.Onset.x, p.Onset.y, p.Onset.z, p.Onset.w)); + material.SetVector("_AssumedSlope", new(p.AssumedSlope.x, p.AssumedSlope.y, 0, 0)); + material.SetFloat("_CellScale", p.CellScale); + material.SetFloat("_Normalization", p.Normalization); + material.SetInt("_Octaves", p.Octaves); + material.SetFloat("_Lacunarity", p.Lacunarity); + material.SetFloat("_Gain", p.Gain); + material.SetVector("_LightDirection", LightDirection.normalized); + material.SetColor("_LowColor", LowColor); + material.SetColor("_HighColor", HighColor); + material.SetColor("_RidgeColor", RidgeColor); + } + + private void OnDisable() => ClearGenerated(); + private void OnDestroy() => ClearGenerated(); + + private void ClearGenerated() + { + for (var i = transform.childCount - 1; i >= 0; i--) DestroyGenerated(transform.GetChild(i).gameObject); + foreach (var mesh in meshes) DestroyGenerated(mesh); + meshes.Clear(); + if (material != null) DestroyGenerated(material); + material = null; + builtCells = 0; + } + + private static void DestroyGenerated(UnityEngine.Object value) + { + if (Application.isPlaying) Destroy(value); + else DestroyImmediate(value); + } + + private static AdvancedErosionParameters ViewerErosion(float radius) => new( + radius * 0.075f, 0.12f, 0.58f, 1.45f, + new float4(0.1f, 0.015f, 0.1f, 2), + new float4(1.25f, 1.25f, 2.8f, 1.5f), + new float2(0.7f, 0.85f), 0.7f, 0.5f, 7, 2, 0.5f); + + private readonly struct ViewerBaseField : IPlanetaryBaseField + { + private readonly float radius; + public ViewerBaseField(float radius) => this.radius = radius; + + public PlanetaryBaseFieldSample Sample(float3 direction) + { + var value = direction.x * 0.6f + direction.y * direction.z * 0.3f; + var angularGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f); + angularGradient -= direction * math.dot(angularGradient, direction); + var fieldGradient = angularGradient / radius; + var radialDisplacement = value * radius * 0.035f; + var radialGradient = angularGradient * 0.035f; + return new(radialDisplacement, radialGradient, value, fieldGradient, math.clamp(value, -1, 1)); + } + } +} diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader new file mode 100644 index 00000000..b5f7274f --- /dev/null +++ b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader @@ -0,0 +1,90 @@ +Shader "CultMath/Planetary Field Viewer" +{ + Properties + { + _LowColor ("Low Color", Color) = (0.055, 0.16, 0.09, 1) + _HighColor ("High Color", Color) = (0.62, 0.48, 0.28, 1) + _RidgeColor ("Ridge Color", Color) = (0.88, 0.83, 0.7, 1) + } + SubShader + { + Tags { "RenderType"="Opaque" "Queue"="Geometry" } + Pass + { + Cull Back + ZWrite On + HLSLPROGRAM + #pragma target 5.0 + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + #include "Packages/org.gamecult.cultmath/shaders/CultMath.hlsl" + + float _Radius; + float _SampleFootprint; + int _Seed; + float _ErosionScale, _ErosionStrength, _GullyWeight, _Detail; + float4 _Rounding, _Onset, _AssumedSlope; + float _CellScale, _Normalization, _Lacunarity, _Gain; + int _Octaves; + float3 _LightDirection; + float4 _LowColor, _HighColor, _RidgeColor; + + struct Attributes { float4 vertex : POSITION; }; + struct Varyings + { + float4 position : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float height : TEXCOORD1; + float ridge : TEXCOORD2; + }; + + CultMathPlanetarySurfaceSample viewer_sample(float3 direction) + { + direction = normalize(direction); + float value = direction.x * 0.6 + direction.y * direction.z * 0.3; + float3 angular_gradient = float3(0.6, direction.z * 0.3, direction.y * 0.3); + angular_gradient -= direction * dot(angular_gradient, direction); + CultMathPlanetaryBaseFieldSample base_sample; + base_sample.radial_displacement = value * _Radius * 0.035; + base_sample.radial_gradient = angular_gradient * 0.035; + base_sample.field_value = value; + base_sample.field_gradient = angular_gradient / _Radius; + base_sample.fade_target = clamp(value, -1.0, 1.0); + CultMathAdvancedErosionParameters erosion; + erosion.scale = _ErosionScale; erosion.strength = _ErosionStrength; + erosion.gully_weight = _GullyWeight; erosion.detail = _Detail; + erosion.rounding = _Rounding; erosion.onset = _Onset; + erosion.assumed_slope = _AssumedSlope.xy; erosion.cell_scale = _CellScale; + erosion.normalization = _Normalization; erosion.octaves = _Octaves; + erosion.lacunarity = _Lacunarity; erosion.gain = _Gain; + CultMathPlanetaryFieldDefinition definition; + definition.radius = _Radius; definition.seed = _Seed; definition.erosion = erosion; + return cultmath_planetary_field_sample(definition, direction, base_sample, _SampleFootprint); + } + + Varyings vert(Attributes input) + { + float3 direction = normalize(input.vertex.xyz); + CultMathPlanetarySurfaceSample sample = viewer_sample(direction); + float3 local = direction * (_Radius + sample.radial_displacement); + Varyings output; + output.position = UnityObjectToClipPos(float4(local, 1)); + output.worldNormal = UnityObjectToWorldNormal(sample.surface_normal); + output.height = sample.radial_displacement / max(_Radius * 0.1, 1.0e-5); + output.ridge = sample.ridge; + return output; + } + + float4 frag(Varyings input) : SV_Target + { + float height = saturate(input.height * 0.5 + 0.5); + float3 albedo = lerp(_LowColor.rgb, _HighColor.rgb, height); + albedo = lerp(albedo, _RidgeColor.rgb, saturate((input.ridge - 0.55) * 2.5)); + float diffuse = saturate(dot(normalize(input.worldNormal), normalize(-_LightDirection))) * 0.82 + 0.08; + return float4(albedo * diffuse, 1); + } + ENDHLSL + } + } +} diff --git a/THIRD-PARTY-NOTICES.md.meta b/THIRD-PARTY-NOTICES.md.meta new file mode 100644 index 00000000..4b43ddaa --- /dev/null +++ b/THIRD-PARTY-NOTICES.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7c28c8631c7d4054eb751d7ce952df33 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj b/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj new file mode 100644 index 00000000..9a6f8bdd --- /dev/null +++ b/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj @@ -0,0 +1,11 @@ + + + Exe + net10.0 + enable + enable + + + + + diff --git a/Tools~/CultMath.PlanetaryTileFixture/Program.cs b/Tools~/CultMath.PlanetaryTileFixture/Program.cs new file mode 100644 index 00000000..1952fa2d --- /dev/null +++ b/Tools~/CultMath.PlanetaryTileFixture/Program.cs @@ -0,0 +1,22 @@ +using CultMath; + +if (args.Length != 1) throw new ArgumentException("Expected one output path."); +var field = PlanetaryFieldDefinition.Create(0x55aa, 10, 7, AdvancedErosionParameters.Default); +var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 0, 0, 0, 5, 1); +var key = new PlanetaryMapTileKey(field.FieldVersion, 3, 29, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.125f)); +var tile = PlanetaryMapTileBaker.Bake(field, key, new FixtureField()); +var path = Path.GetFullPath(args[0]); +Directory.CreateDirectory(Path.GetDirectoryName(path)!); +using var stream = File.Create(path); +PlanetaryMapTileEncoding.Write(stream, tile); + +readonly struct FixtureField : IPlanetaryBaseField +{ + public PlanetaryBaseFieldSample Sample(float3 direction) + { + var value = direction.x * 0.2f + direction.z * 0.4f; + var gradient = new float3(0.2f, 0, 0.4f); + gradient -= direction * math.dot(gradient, direction); + return new(value, gradient, value, gradient * 0.1f, value); + } +} diff --git a/docs/planetary.md b/docs/planetary.md index 8196c1c7..3503429b 100644 --- a/docs/planetary.md +++ b/docs/planetary.md @@ -80,11 +80,17 @@ coarse meshes from the canonical topology. HLSL calls Renderers still own cameras, clip space, depth, materials, buffers, dispatch, and draw submission. +`PlanetaryGpuPageBuilder` owns the renderer-neutral page input and metadata +payload. Host adapters may convert its `float4` values to native vector types, +but do not recalculate directions, spacing, addressing, or transition state. + The optional `CultMath.Unity` assembly converts a canonical face patch into a -Unity `Mesh` and converts a residency snapshot into Unity-native page input and -metadata payloads. It owns no field parameters, page selection, or simulation -state. Unity render code remains responsible for allocating/binding graphics -buffers and dispatching application-specific base-field entry points. +Unity `Mesh` and converts the shared GPU payload into Unity-native structs. It +owns no field parameters, page selection, or simulation state. The package's +Planetary Field Viewer sample renders the shared HLSL field and reports matching +CPU queries. Unity render code remains responsible for allocating/binding +graphics buffers and dispatching application-specific base-field entry points. +The host Unity project must set `-langversion:latest` in `Assets/csc.rsp`. ## Maps @@ -103,6 +109,12 @@ projection and canonical field query. `PlanetaryMapTileKey` includes field, projection, layer, layout, and query-scale identity. Palettes, contours, labels, and strategic overlays do not enter CultMath. +`PlanetaryMapTileEncoding` writes the resulting evidence as a versioned +little-endian `CMPT` binary asset. `web/planetary-tile.ts` decodes that exact +format into browser-native values without defining another terrain generator. +Typed CultMesh documents should carry the tile key and asset reference; the +binary payload is derived CDN/cache data, not committed game state. + ## Numeric contract Integer hashes and published field versions are exact. Basic arithmetic follows @@ -111,7 +123,8 @@ CultMath's matched C#/HLSL semantics. Hardware transcendental functions such as Every filtered terrain query carries an unresolved-height bound so consumers can reject insufficient fidelity explicitly. -Current D3D12 parity evidence covers the advanced erosion kernel and the QSC -forward/inverse topology. The remaining production page and patch shaders use -the common functions and retain their seam, residual, summary, and radial-hit -integration probes in Fensalir. +Current D3D12 parity evidence covers the advanced erosion kernel, QSC topology, +and forward/inverse transforms for every supported map projection, including +center and scale parameters. Production page and patch shaders use the common +functions and retain their seam, residual, summary, and radial-hit integration +probes in Fensalir. diff --git a/docs/planetary.md.meta b/docs/planetary.md.meta new file mode 100644 index 00000000..af364c25 --- /dev/null +++ b/docs/planetary.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 23a5751e4cececc4e99e024058d69add +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 6da2a17a..25643608 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,12 @@ "Simulation" ], "license": "MPL-2.0", - "category": "Scripting" + "category": "Scripting", + "samples": [ + { + "displayName": "Planetary Field Viewer", + "description": "Runnable Unity viewer using the canonical CultMath CPU and HLSL planetary field.", + "path": "Samples~/PlanetaryViewer" + } + ] } diff --git a/scripts.meta b/scripts.meta new file mode 100644 index 00000000..6f03c566 --- /dev/null +++ b/scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0ddff5b782cf14e43ae5cda048e040d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scripts/test-web-planetary-tile.ps1 b/scripts/test-web-planetary-tile.ps1 new file mode 100644 index 00000000..5221fb36 --- /dev/null +++ b/scripts/test-web-planetary-tile.ps1 @@ -0,0 +1,9 @@ +$ErrorActionPreference = "Stop" +$root = Split-Path -Parent $PSScriptRoot +$fixture = Join-Path $root ".tools\planetary-tile\fixture.cmpt" +dotnet run --project (Join-Path $root "Tools~\CultMath.PlanetaryTileFixture\CultMath.PlanetaryTileFixture.csproj") -- $fixture +if ($LASTEXITCODE -ne 0) { throw "Planetary tile fixture generation failed." } +node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.cross-runtime.test.mjs") $fixture +if ($LASTEXITCODE -ne 0) { throw "Planetary tile web decoding failed." } +node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.test.mjs") +if ($LASTEXITCODE -ne 0) { throw "Planetary tile decoder unit test failed." } diff --git a/scripts/test-web-planetary-tile.ps1.meta b/scripts/test-web-planetary-tile.ps1.meta new file mode 100644 index 00000000..ffd73234 --- /dev/null +++ b/scripts/test-web-planetary-tile.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 235e8f203bd1c4d40a8fa211273106ba +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/shaders/AdvancedErosionFilter.hlsl.meta b/shaders/AdvancedErosionFilter.hlsl.meta new file mode 100644 index 00000000..a354e330 --- /dev/null +++ b/shaders/AdvancedErosionFilter.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3252ac02e8853ef4b9d1fbc5204b5c6b +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/shaders/Planetary.hlsl b/shaders/Planetary.hlsl index 729c6332..81e312b7 100644 --- a/shaders/Planetary.hlsl +++ b/shaders/Planetary.hlsl @@ -120,6 +120,138 @@ float3 cultmath_planetary_equal_earth_inverse(float2 coordinate) return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); } +float cultmath_planetary_wrap_longitude(float longitude) +{ + longitude = fmod(longitude, CULTMATH_TAU); + if (longitude > CULTMATH_PI) longitude -= CULTMATH_TAU; + if (longitude < -CULTMATH_PI) longitude += CULTMATH_TAU; + return longitude; +} + +float3 cultmath_planetary_lon_lat_direction(float longitude, float latitude) +{ + float cos_latitude = cos(latitude); + return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); +} + +float cultmath_planetary_asinh(float value) { return log(value + sqrt(value * value + 1.0)); } +float cultmath_planetary_sinh(float value) { return 0.5 * (exp(value) - exp(-value)); } + +bool cultmath_planetary_azimuthal_forward(float3 direction, float2 center_lon_lat, int kind, out float2 coordinate) +{ + direction = cultmath_normalize(direction); + float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat.x); + float latitude = asin(clamp(direction.z, -1.0, 1.0)); + float sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y); + float sin_latitude = sin(latitude), cos_latitude = cos(latitude), cos_longitude = cos(longitude); + float cos_c = clamp(sin0 * sin_latitude + cos0 * cos_latitude * cos_longitude, -1.0, 1.0); + float2 raw = float2(cos_latitude * sin(longitude), cos0 * sin_latitude - sin0 * cos_latitude * cos_longitude); + float factor = 1.0; + if (kind == 3) + { + if (cos_c < 0.0) { coordinate = 0.0; return false; } + } + else if (kind == 4) + { + float c = acos(cos_c); factor = (c < 1.0e-6 ? 1.0 : c / sin(c)) / CULTMATH_PI; + } + else if (kind == 5) factor = sqrt(2.0 / max(1.0 + cos_c, 1.0e-20)) * 0.5; + else + { + if (cos_c <= 0.0) { coordinate = 0.0; return false; } + factor = 1.0 / cos_c; + } + coordinate = raw * factor; + return true; +} + +bool cultmath_planetary_azimuthal_inverse(float2 coordinate, float2 center_lon_lat, int kind, out float3 direction) +{ + float rho = length(coordinate); + float c; + if (kind == 3) { if (rho > 1.0) { direction = 0.0; return false; } c = asin(clamp(rho, 0.0, 1.0)); } + else if (kind == 4) { if (rho > 1.0) { direction = 0.0; return false; } c = rho * CULTMATH_PI; } + else if (kind == 5) { if (rho > 1.0) { direction = 0.0; return false; } c = 2.0 * asin(clamp(rho, 0.0, 1.0)); } + else c = atan(rho); + if (rho < 1.0e-6) { direction = cultmath_planetary_lon_lat_direction(center_lon_lat.x, center_lon_lat.y); return true; } + float sin_c = sin(c), cos_c = cos(c), sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y); + float latitude = asin(clamp(cos_c * sin0 + coordinate.y * sin_c * cos0 / rho, -1.0, 1.0)); + float delta_longitude = atan2(coordinate.x * sin_c, rho * cos0 * cos_c - coordinate.y * sin0 * sin_c); + direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(delta_longitude + center_lon_lat.x), latitude); + return true; +} + +bool cultmath_planetary_cube_atlas_forward(float3 direction, out float2 coordinate) +{ + int face; + float2 face_coordinate = cultmath_planetary_face_coordinate(direction, face); + int column = face % 3; + int row = face / 3; + coordinate = float2(-1.0 + (column + (face_coordinate.x + 1.0) * 0.5) * (2.0 / 3.0), 1.0 - (row + (face_coordinate.y + 1.0) * 0.5)); + return true; +} + +bool cultmath_planetary_cube_atlas_inverse(float2 coordinate, out float3 direction) +{ + if (any(coordinate < -1.0) || any(coordinate > 1.0)) { direction = 0.0; return false; } + int column = min((int)((coordinate.x + 1.0) * 1.5), 2); + int row = min((int)(1.0 - coordinate.y), 1); + float u = ((coordinate.x + 1.0) * 1.5 - column) * 2.0 - 1.0; + float v = ((1.0 - coordinate.y) - row) * 2.0 - 1.0; + direction = cultmath_planetary_face_direction(row * 3 + column, float2(u, v)); + return true; +} + +bool cultmath_planetary_projection_forward(float3 direction, int kind, float3 center_lon_lat_scale, out float2 coordinate) +{ + float scale = max(center_lon_lat_scale.z, 1.0e-20); + if (kind == 6) + { + bool valid = cultmath_planetary_cube_atlas_forward(direction, coordinate); + coordinate /= scale; return valid; + } + if (kind >= 3) + { + bool valid = cultmath_planetary_azimuthal_forward(direction, center_lon_lat_scale.xy, kind, coordinate); + coordinate /= scale; return valid; + } + direction = cultmath_normalize(direction); + float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat_scale.x); + float latitude = asin(clamp(direction.z, -1.0, 1.0)); + if (kind == 0) coordinate = float2(longitude / CULTMATH_PI, latitude / CULTMATH_HALF_PI); + else if (kind == 1) + { + const float maximum_latitude = 1.4844222297453324; + if (abs(latitude) > maximum_latitude) { coordinate = 0.0; return false; } + coordinate = float2(longitude / CULTMATH_PI, cultmath_planetary_asinh(tan(latitude)) / CULTMATH_PI); + } + else + { + float3 centered = cultmath_planetary_lon_lat_direction(longitude, latitude); + coordinate = cultmath_planetary_equal_earth_forward(centered); + } + coordinate /= scale; + return true; +} + +bool cultmath_planetary_projection_inverse(float2 coordinate, int kind, float3 center_lon_lat_scale, out float3 direction) +{ + coordinate *= max(center_lon_lat_scale.z, 1.0e-20); + if (kind == 6) return cultmath_planetary_cube_atlas_inverse(coordinate, direction); + if (kind >= 3) return cultmath_planetary_azimuthal_inverse(coordinate, center_lon_lat_scale.xy, kind, direction); + if (any(abs(coordinate) > 1.0)) { direction = 0.0; return false; } + float longitude, latitude; + if (kind == 0) { longitude = coordinate.x * CULTMATH_PI; latitude = coordinate.y * CULTMATH_HALF_PI; } + else if (kind == 1) { longitude = coordinate.x * CULTMATH_PI; latitude = atan(cultmath_planetary_sinh(coordinate.y * CULTMATH_PI)); } + else + { + float3 centered = cultmath_planetary_equal_earth_inverse(coordinate); + longitude = atan2(centered.y, centered.x); latitude = asin(clamp(centered.z, -1.0, 1.0)); + } + direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(longitude + center_lon_lat_scale.x), latitude); + return true; +} + struct CultMathPlanetaryFieldDefinition { float radius; diff --git a/shaders/Planetary.hlsl.meta b/shaders/Planetary.hlsl.meta new file mode 100644 index 00000000..f7771355 --- /dev/null +++ b/shaders/Planetary.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bb055ad5328888e479e90ae3fe62c3fe +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/CultMath/AdvancedErosionFilter.cs.meta b/src/CultMath/AdvancedErosionFilter.cs.meta new file mode 100644 index 00000000..52f7ca19 --- /dev/null +++ b/src/CultMath/AdvancedErosionFilter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bec15fdcbe97a7f44a24bdb3ff79e1e7 \ No newline at end of file diff --git a/src/CultMath/ErosionFrequencyBands.cs.meta b/src/CultMath/ErosionFrequencyBands.cs.meta new file mode 100644 index 00000000..cae99643 --- /dev/null +++ b/src/CultMath/ErosionFrequencyBands.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 05e7969589dd09b43bd0fda672d25a0b \ No newline at end of file diff --git a/src/CultMath/PlanetaryField.cs b/src/CultMath/PlanetaryField.cs index 13f36c0a..4d5596e5 100644 --- a/src/CultMath/PlanetaryField.cs +++ b/src/CultMath/PlanetaryField.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public readonly record struct PlanetaryQueryScale(float FootprintMeters, float MaximumUnresolvedHeight) diff --git a/src/CultMath/PlanetaryField.cs.meta b/src/CultMath/PlanetaryField.cs.meta new file mode 100644 index 00000000..3e83f637 --- /dev/null +++ b/src/CultMath/PlanetaryField.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b61d6354e3aab054e94721e77e619bfa \ No newline at end of file diff --git a/src/CultMath/PlanetaryGpuPages.cs b/src/CultMath/PlanetaryGpuPages.cs new file mode 100644 index 00000000..024d26b0 --- /dev/null +++ b/src/CultMath/PlanetaryGpuPages.cs @@ -0,0 +1,55 @@ +using System; + +namespace CultMath; + +public readonly record struct PlanetaryGpuPageInput(float4 DirectionRadius, float4 Sampling); + +public readonly record struct PlanetaryGpuPageMetadata(float4 Address, float4 Layout, float4 Bounds, float4 State); + +public readonly record struct PlanetaryGpuPageContent( + PlanetaryPageLayout Layout, + PlanetaryGpuPageInput[] Inputs, + float SampleSpacing, + float ParentSampleSpacing); + +public static class PlanetaryGpuPageBuilder +{ + public static PlanetaryGpuPageContent BuildContent(PlanetaryPageLayout layout, float radius) + { + layout.Validate(); + if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); + var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * radius; + var parentSpacing = layout.Tile.Level == 0 + ? 0 + : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * radius; + var inputs = new PlanetaryGpuPageInput[layout.StorageSize * layout.StorageSize]; + for (var y = 0; y < layout.StorageSize; y++) + for (var x = 0; x < layout.StorageSize; x++) + { + var direction = PlanetaryPageSampling.Direction(layout, x, y); + inputs[y * layout.StorageSize + x] = new( + new float4(direction, radius), + new float4(spacing, parentSpacing, 0, 0)); + } + return new(layout, inputs, spacing, parentSpacing); + } + + public static PlanetaryGpuPageMetadata Metadata( + in PlanetaryGpuPageContent content, + int outputOffset, + float blend, + float4 bounds = default) + { + content.Layout.Validate(); + if (content.Inputs == null || content.Inputs.Length != content.Layout.StorageSize * content.Layout.StorageSize) + throw new ArgumentException("GPU page content does not match its layout.", nameof(content)); + if (outputOffset < 0) throw new ArgumentOutOfRangeException(nameof(outputOffset)); + if (!float.IsFinite(blend) || blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(blend)); + var tile = content.Layout.Tile; + return new( + new float4((int)tile.Face, tile.Level, tile.X, tile.Y), + new float4(outputOffset, content.Layout.StorageSize, content.Layout.InteriorSize, content.Layout.BorderSize), + bounds, + new float4(1, blend, PlanetaryPageSampling.NominalAngularTexelSize(content.Layout), content.SampleSpacing)); + } +} diff --git a/src/CultMath/PlanetaryGpuPages.cs.meta b/src/CultMath/PlanetaryGpuPages.cs.meta new file mode 100644 index 00000000..7afe847c --- /dev/null +++ b/src/CultMath/PlanetaryGpuPages.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 42173c60766948a4e8c376687033532e \ No newline at end of file diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs b/src/CultMath/PlanetaryMapTileEncoding.cs new file mode 100644 index 00000000..ec556617 --- /dev/null +++ b/src/CultMath/PlanetaryMapTileEncoding.cs @@ -0,0 +1,117 @@ +using System; +using System.IO; +using System.Text; + +namespace CultMath; + +public static class PlanetaryMapTileEncoding +{ + public const uint FormatVersion = 1; + private static readonly byte[] Magic = Encoding.ASCII.GetBytes("CMPT"); + + public static void Write(Stream stream, in PlanetarySurfaceMapTile tile, bool leaveOpen = false) + { + if (stream == null || !stream.CanWrite) throw new ArgumentException("A writable stream is required.", nameof(stream)); + tile.Key.Validate(); + if (tile.Samples == null || tile.Validity == null || tile.Samples.Length != tile.Validity.Length) + throw new ArgumentException("Tile samples and validity must have equal lengths.", nameof(tile)); + var expectedCount = tile.Key.Layout.StorageSize * tile.Key.Layout.StorageSize; + if (tile.Samples.Length != expectedCount) throw new ArgumentException("Tile payload does not match its layout.", nameof(tile)); + + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen); + writer.Write(Magic); + writer.Write(FormatVersion); + writer.Write(tile.Key.FieldVersion); + writer.Write(tile.Key.ProjectionVersion); + writer.Write(tile.Key.LayerId); + var projection = tile.Key.Layout.Projection; + writer.Write((int)projection.Kind); + writer.Write(projection.CenterLongitude); + writer.Write(projection.CenterLatitude); + writer.Write(projection.Scale); + writer.Write(tile.Key.Layout.Level); + writer.Write(tile.Key.Layout.X); + writer.Write(tile.Key.Layout.Y); + writer.Write(tile.Key.Layout.InteriorSize); + writer.Write(tile.Key.Layout.BorderSize); + writer.Write(tile.Key.QueryScale.FootprintMeters); + writer.Write(tile.Key.QueryScale.MaximumUnresolvedHeight); + writer.Write(tile.Samples.Length); + for (var i = 0; i < tile.Samples.Length; i++) + { + writer.Write(tile.Validity[i]); + if (!tile.Validity[i]) continue; + WriteSample(writer, tile.Samples[i], tile.Key.FieldVersion); + } + } + + public static PlanetarySurfaceMapTile Read(Stream stream, bool leaveOpen = false) + { + if (stream == null || !stream.CanRead) throw new ArgumentException("A readable stream is required.", nameof(stream)); + using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen); + var magic = reader.ReadBytes(Magic.Length); + if (magic.Length != Magic.Length || !magic.AsSpan().SequenceEqual(Magic)) throw new InvalidDataException("Not a CultMath planetary tile."); + var version = reader.ReadUInt32(); + if (version != FormatVersion) throw new InvalidDataException($"Unsupported planetary tile version {version}."); + var fieldVersion = reader.ReadUInt64(); + var projectionVersion = reader.ReadUInt32(); + var layerId = reader.ReadUInt32(); + var kindValue = reader.ReadInt32(); + if (!Enum.IsDefined(typeof(PlanetaryProjectionKind), kindValue)) throw new InvalidDataException("Unknown planetary projection kind."); + var projection = new PlanetaryProjectionParameters( + (PlanetaryProjectionKind)kindValue, + reader.ReadDouble(), reader.ReadDouble(), reader.ReadDouble()).Validate(); + var layout = new PlanetaryMapTileLayout( + projection, + reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), + reader.ReadInt32(), reader.ReadInt32()).Validate(); + var scale = new PlanetaryQueryScale(reader.ReadSingle(), reader.ReadSingle()).Validate(); + var key = new PlanetaryMapTileKey(fieldVersion, projectionVersion, layerId, layout, scale).Validate(); + var count = reader.ReadInt32(); + if (count != layout.StorageSize * layout.StorageSize) throw new InvalidDataException("Planetary tile sample count does not match layout."); + var samples = new PlanetarySurfaceSample[count]; + var validity = new bool[count]; + for (var i = 0; i < count; i++) + { + validity[i] = reader.ReadBoolean(); + if (validity[i]) samples[i] = ReadSample(reader, fieldVersion); + } + if (stream.CanSeek && stream.Position != stream.Length) throw new InvalidDataException("Planetary tile has trailing bytes."); + return new(key, samples, validity); + } + + private static void WriteSample(BinaryWriter writer, PlanetarySurfaceSample sample, ulong fieldVersion) + { + if (sample.FieldVersion != fieldVersion) throw new ArgumentException("Sample field version does not match tile identity."); + Write(writer, sample.UnitDirection); + writer.Write(sample.Radius); + writer.Write(sample.RadialDisplacement); + Write(writer, sample.TangentGradient); + Write(writer, sample.SurfaceNormal); + writer.Write(sample.Slope); + writer.Write(sample.Ridge); + writer.Write(sample.Gully); + writer.Write(sample.FinestResolvedWavelength); + writer.Write(sample.UnresolvedHeightBound); + } + + private static PlanetarySurfaceSample ReadSample(BinaryReader reader, ulong fieldVersion) + { + var direction = ReadFloat3(reader); + var radius = reader.ReadSingle(); + var displacement = reader.ReadSingle(); + var gradient = ReadFloat3(reader); + var normal = ReadFloat3(reader); + return new( + fieldVersion, direction, radius, displacement, gradient, normal, + reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), + reader.ReadSingle(), reader.ReadSingle()); + } + + private static void Write(BinaryWriter writer, float3 value) + { + writer.Write(value.x); writer.Write(value.y); writer.Write(value.z); + } + + private static float3 ReadFloat3(BinaryReader reader) => new(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); +} diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs.meta b/src/CultMath/PlanetaryMapTileEncoding.cs.meta new file mode 100644 index 00000000..76484f65 --- /dev/null +++ b/src/CultMath/PlanetaryMapTileEncoding.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6898f6de57df20646a0b9077bb923592 \ No newline at end of file diff --git a/src/CultMath/PlanetaryPages.cs b/src/CultMath/PlanetaryPages.cs index c9cff51f..699a5c1d 100644 --- a/src/CultMath/PlanetaryPages.cs +++ b/src/CultMath/PlanetaryPages.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public readonly record struct PlanetaryPageLayout(PlanetaryTileAddress Tile, int InteriorSize, int BorderSize) diff --git a/src/CultMath/PlanetaryPages.cs.meta b/src/CultMath/PlanetaryPages.cs.meta new file mode 100644 index 00000000..91fdc27d --- /dev/null +++ b/src/CultMath/PlanetaryPages.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7af9a0354faa56348a2e07188739a0ae \ No newline at end of file diff --git a/src/CultMath/PlanetaryPatch.cs b/src/CultMath/PlanetaryPatch.cs index 0fd3cbb6..30c852ff 100644 --- a/src/CultMath/PlanetaryPatch.cs +++ b/src/CultMath/PlanetaryPatch.cs @@ -1,3 +1,6 @@ +using System; +using System.Linq; + namespace CultMath; public readonly record struct PlanetaryPatchVertex(float2 LocalCoordinate, float3 UnitDirection); diff --git a/src/CultMath/PlanetaryPatch.cs.meta b/src/CultMath/PlanetaryPatch.cs.meta new file mode 100644 index 00000000..efd8156a --- /dev/null +++ b/src/CultMath/PlanetaryPatch.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 04651571f7d240a418c1f6aafa217fa6 \ No newline at end of file diff --git a/src/CultMath/PlanetaryProjection.cs b/src/CultMath/PlanetaryProjection.cs index f849fe20..a82ebfdb 100644 --- a/src/CultMath/PlanetaryProjection.cs +++ b/src/CultMath/PlanetaryProjection.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public enum PlanetaryProjectionKind diff --git a/src/CultMath/PlanetaryProjection.cs.meta b/src/CultMath/PlanetaryProjection.cs.meta new file mode 100644 index 00000000..7b1e6654 --- /dev/null +++ b/src/CultMath/PlanetaryProjection.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5021202cf626f7442a57961255a8a217 \ No newline at end of file diff --git a/src/CultMath/PlanetaryQueries.cs b/src/CultMath/PlanetaryQueries.cs index 1f11ca77..54a9a3e9 100644 --- a/src/CultMath/PlanetaryQueries.cs +++ b/src/CultMath/PlanetaryQueries.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public readonly record struct PlanetaryRadialBounds(float MinimumDisplacement, float MaximumDisplacement) diff --git a/src/CultMath/PlanetaryQueries.cs.meta b/src/CultMath/PlanetaryQueries.cs.meta new file mode 100644 index 00000000..6fd11413 --- /dev/null +++ b/src/CultMath/PlanetaryQueries.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2ec43c567214b8247bda19b5c120c200 \ No newline at end of file diff --git a/src/CultMath/PlanetaryRadialRefinement.cs.meta b/src/CultMath/PlanetaryRadialRefinement.cs.meta new file mode 100644 index 00000000..32af2655 --- /dev/null +++ b/src/CultMath/PlanetaryRadialRefinement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 82e4db521edc253499220458b417fd1e \ No newline at end of file diff --git a/src/CultMath/PlanetaryResidency.cs b/src/CultMath/PlanetaryResidency.cs index e5dca115..6918ac57 100644 --- a/src/CultMath/PlanetaryResidency.cs +++ b/src/CultMath/PlanetaryResidency.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; + namespace CultMath; public readonly record struct PlanetaryLodParameters( @@ -71,9 +75,9 @@ public readonly record struct PlanetaryResidencySnapshot( /// Owns presentation residency only. It never owns field content. public sealed class PlanetaryResidualResidency { - private readonly Dictionary residents = []; - private readonly Dictionary arrivals = []; - private readonly Dictionary departures = []; + private readonly Dictionary residents = new(); + private readonly Dictionary arrivals = new(); + private readonly Dictionary departures = new(); private float lastTime = float.NegativeInfinity; public void Reset() diff --git a/src/CultMath/PlanetaryResidency.cs.meta b/src/CultMath/PlanetaryResidency.cs.meta new file mode 100644 index 00000000..24347624 --- /dev/null +++ b/src/CultMath/PlanetaryResidency.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0585ea15edd7f1c4fa272df4911a8d6f \ No newline at end of file diff --git a/src/CultMath/PlanetaryTopology.cs b/src/CultMath/PlanetaryTopology.cs index c8535079..57d562cc 100644 --- a/src/CultMath/PlanetaryTopology.cs +++ b/src/CultMath/PlanetaryTopology.cs @@ -1,3 +1,5 @@ +using System; + namespace CultMath; public enum PlanetaryCubeFace @@ -104,6 +106,14 @@ public static class PlanetaryTopology return math.normalize(cube); } + public static float3 NormalizedCubeDirection(PlanetaryFaceCoordinate coordinate) + { + coordinate.Validate(allowOutsideFace: true); + var u = (float)coordinate.U; + var v = (float)coordinate.V; + return math.normalize(CubeVector(coordinate.Face, u, v)); + } + public static PlanetaryFaceCoordinate FaceCoordinate(float3 direction) { ValidateDirection(direction); @@ -156,6 +166,17 @@ public static class PlanetaryTopology MathF.Atan(cubeU) / QuarterPi, MathF.Atan(cubeV) / QuarterPi); + private static float3 CubeVector(PlanetaryCubeFace face, float u, float v) => face switch + { + PlanetaryCubeFace.PositiveX => new float3(1, v, -u), + PlanetaryCubeFace.NegativeX => new float3(-1, v, u), + PlanetaryCubeFace.PositiveY => new float3(u, 1, -v), + PlanetaryCubeFace.NegativeY => new float3(u, -1, v), + PlanetaryCubeFace.PositiveZ => new float3(u, v, 1), + PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1), + _ => throw new ArgumentOutOfRangeException(nameof(face)), + }; + internal static void ValidateDirection(float3 direction) { ValidateFinite(direction, nameof(direction)); diff --git a/src/CultMath/PlanetaryTopology.cs.meta b/src/CultMath/PlanetaryTopology.cs.meta new file mode 100644 index 00000000..411a8bdb --- /dev/null +++ b/src/CultMath/PlanetaryTopology.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 88f6789c4384f7846ac4be37613fe5ed \ No newline at end of file diff --git a/src/CultMath/SphericalErosion.cs.meta b/src/CultMath/SphericalErosion.cs.meta new file mode 100644 index 00000000..82db7730 --- /dev/null +++ b/src/CultMath/SphericalErosion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59ec2db73065f6c4483ad90f5e17850e \ No newline at end of file diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta new file mode 100644 index 00000000..253ee425 --- /dev/null +++ b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a7c85309318ae3f4ba75ac28a4f661cf \ No newline at end of file diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta new file mode 100644 index 00000000..ef15b7ee --- /dev/null +++ b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 095f8ed13d2f2f74baefd23310395256 \ No newline at end of file diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 567b038b..5e1451cd 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -78,6 +78,8 @@ public sealed class HlslMirrorTests Assert.Contains("cultmath_planetary_field_sample", planetary); Assert.Contains("cultmath_planetary_equirectangular_forward", planetary); Assert.Contains("cultmath_planetary_equal_earth_forward", planetary); + Assert.Contains("cultmath_planetary_projection_forward", planetary); + Assert.Contains("cultmath_planetary_projection_inverse", planetary); Assert.Contains("CultMathPlanetarySurfaceSample", planetary); } diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta b/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta new file mode 100644 index 00000000..1710b5ff --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c747cfda80df21d41a57412d8d07c08a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs new file mode 100644 index 00000000..ea17b4b9 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryGpuPageTests +{ + [Fact] + public void UploadContentUsesCanonicalDirectionsSpacingAndMetadata() + { + var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.NegativeY, 3, 4, 2), 9, 2); + var content = PlanetaryGpuPageBuilder.BuildContent(layout, 8.4f); + Assert.Equal(layout.StorageSize * layout.StorageSize, content.Inputs.Length); + Assert.True(content.ParentSampleSpacing > content.SampleSpacing); + var first = content.Inputs[0]; + Assert.Equal(PlanetaryPageSampling.Direction(layout, 0, 0), first.DirectionRadius.xyz); + Assert.Equal(8.4f, first.DirectionRadius.w); + Assert.Equal(content.SampleSpacing, first.Sampling.x); + Assert.Equal(content.ParentSampleSpacing, first.Sampling.y); + + var metadata = PlanetaryGpuPageBuilder.Metadata(content, 37, 0.25f); + Assert.Equal(new float4((int)layout.Tile.Face, layout.Tile.Level, layout.Tile.X, layout.Tile.Y), metadata.Address); + Assert.Equal(new float4(37, layout.StorageSize, layout.InteriorSize, layout.BorderSize), metadata.Layout); + Assert.Equal(0.25f, metadata.State.y); + } +} diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta new file mode 100644 index 00000000..00f4a3a5 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 87266638e8010f546ab6c430a712868b \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs new file mode 100644 index 00000000..112cb31f --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs @@ -0,0 +1,52 @@ +using System.IO; +using Xunit; + +namespace CultMath.Tests; + +public sealed class PlanetaryMapTileEncodingTests +{ + [Fact] + public void BinaryTileRoundTripsIdentityValidityAndSurfaceEvidence() + { + var field = PlanetaryFieldDefinition.Create(5, 10, 3, AdvancedErosionParameters.Default); + var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 1, 1, 0, 5, 1); + var key = new PlanetaryMapTileKey(field.FieldVersion, 2, 17, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.1f)); + var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); + using var bytes = new MemoryStream(); + PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true); + bytes.Position = 0; + var decoded = PlanetaryMapTileEncoding.Read(bytes); + + Assert.Equal(tile.Key, decoded.Key); + Assert.Equal(tile.Validity, decoded.Validity); + for (var i = 0; i < tile.Samples.Length; i++) + if (tile.Validity[i]) Assert.Equal(tile.Samples[i], decoded.Samples[i]); + } + + [Fact] + public void DecoderRejectsForeignMagicAndVersions() + { + Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(new byte[] { 1, 2, 3, 4, 1, 0, 0, 0 }))); + var bytes = new byte[] { (byte)'C', (byte)'M', (byte)'P', (byte)'T', 99, 0, 0, 0 }; + Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(bytes))); + } + + [Fact] + public void DecoderRejectsTrailingBytes() + { + var field = PlanetaryFieldDefinition.Create(2, 1, 0, AdvancedErosionParameters.Default); + var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.Equirectangular), 0, 0, 0, 2, 0); + var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 0, layout, PlanetaryQueryScale.AtFootprint(1)); + var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); + using var bytes = new MemoryStream(); + PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true); + bytes.WriteByte(0xff); + bytes.Position = 0; + Assert.Throws(() => PlanetaryMapTileEncoding.Read(bytes)); + } + + private readonly struct FlatField : IPlanetaryBaseField + { + public PlanetaryBaseFieldSample Sample(float3 direction) => new(0, float3.zero, 0, float3.zero, 0); + } +} diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta new file mode 100644 index 00000000..027754e8 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e916ef83478d48b43ac99777f5e0f731 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryPageTests.cs.meta new file mode 100644 index 00000000..70a43864 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryPageTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c3ae5598d3b9164a9bd3a736a22ba9a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta b/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta new file mode 100644 index 00000000..85990473 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 36840ef44326f84409f850163b00e1f7 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta b/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta new file mode 100644 index 00000000..7f37f78f --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f370036bb1663524fb4a4d47dfd50401 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta b/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta new file mode 100644 index 00000000..d72b2cb6 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 93cf8771fdd520b41a4e931a6566a80a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta new file mode 100644 index 00000000..b9f3bea9 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d34c0018ee2b700448b16540eca8ebcf \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta b/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta new file mode 100644 index 00000000..be7de2b4 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ece3b02dbd3d7448baabee7f1fba0ab \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs b/tests/CultMath.Tests/PlanetaryTopologyTests.cs index 6e6b0838..759928d8 100644 --- a/tests/CultMath.Tests/PlanetaryTopologyTests.cs +++ b/tests/CultMath.Tests/PlanetaryTopologyTests.cs @@ -52,4 +52,12 @@ public sealed class PlanetaryTopologyTests Assert.NotEqual(edge, border); Assert.Equal(1, math.length(border), 5); } + + [Fact] + public void NormalizedCubeBaselineSharesFaceEdges() + { + var a = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveZ, 1, -0.4)); + var b = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveX, -1, -0.4)); + Assert.InRange(math.distance(a, b), 0, 1.0e-6f); + } } diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta b/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta new file mode 100644 index 00000000..b6930142 --- /dev/null +++ b/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be3b0ec07fe65344db889d1e98448113 \ No newline at end of file diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs.meta b/tests/CultMath.Tests/SphericalErosionTests.cs.meta new file mode 100644 index 00000000..00ff1de0 --- /dev/null +++ b/tests/CultMath.Tests/SphericalErosionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 60eb9e9e9f8ae2948b13403f5080168e \ No newline at end of file diff --git a/tools/CultMath.PlanetaryTileFixture.meta b/tools/CultMath.PlanetaryTileFixture.meta new file mode 100644 index 00000000..7f40b6ae --- /dev/null +++ b/tools/CultMath.PlanetaryTileFixture.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7197a2202273a9445b974bdd69109b1a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity.meta b/unity.meta new file mode 100644 index 00000000..de47a618 --- /dev/null +++ b/unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 366efeaef9f68224b80229332c7ecfc6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/Runtime.meta b/unity/Runtime.meta new file mode 100644 index 00000000..8e0678f8 --- /dev/null +++ b/unity/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8226365c03c5e1a438408b7e42b98740 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/Runtime/CultMath.Unity.asmdef.meta b/unity/Runtime/CultMath.Unity.asmdef.meta new file mode 100644 index 00000000..2dd3a36a --- /dev/null +++ b/unity/Runtime/CultMath.Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d1906c397c420ae4eb222a3f09f36843 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/Runtime/PlanetaryPageUpload.cs b/unity/Runtime/PlanetaryPageUpload.cs index f0b168cd..1a61a707 100644 --- a/unity/Runtime/PlanetaryPageUpload.cs +++ b/unity/Runtime/PlanetaryPageUpload.cs @@ -36,12 +36,28 @@ public readonly struct PlanetaryPageMetadataGpu public readonly Vector4 State; } -public readonly record struct PlanetaryPageUpload( - PlanetaryPageInputGpu[] Inputs, - PlanetaryPageMetadataGpu[] Metadata, - int OutputSampleCount, - ulong ContentVersion, - ulong PresentationVersion); +public readonly struct PlanetaryPageUpload +{ + public PlanetaryPageUpload( + PlanetaryPageInputGpu[] inputs, + PlanetaryPageMetadataGpu[] metadata, + int outputSampleCount, + ulong contentVersion, + ulong presentationVersion) + { + Inputs = inputs; + Metadata = metadata; + OutputSampleCount = outputSampleCount; + ContentVersion = contentVersion; + PresentationVersion = presentationVersion; + } + + public PlanetaryPageInputGpu[] Inputs { get; } + public PlanetaryPageMetadataGpu[] Metadata { get; } + public int OutputSampleCount { get; } + public ulong ContentVersion { get; } + public ulong PresentationVersion { get; } +} public static class PlanetaryPageUploadBuilder { @@ -57,25 +73,23 @@ public static class PlanetaryPageUploadBuilder foreach (var (resident, pageIndex) in snapshot.Tiles.Select((resident, index) => (resident, index))) { var layout = new PlanetaryPageLayout(resident.Tile, interiorSize, borderSize).Validate(); - var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * radius; - var parentSpacing = resident.Tile.Level == 0 - ? 0 - : PlanetaryPageSampling.NominalAngularTexelSize(new(resident.Tile.Parent(), interiorSize, borderSize)) * radius; + var content = PlanetaryGpuPageBuilder.BuildContent(layout, radius); var outputOffset = inputs.Count; - for (var y = 0; y < layout.StorageSize; y++) - for (var x = 0; x < layout.StorageSize; x++) + foreach (var input in content.Inputs) { - var direction = PlanetaryPageSampling.Direction(layout, x, y); inputs.Add(new( - new Vector4(direction.x, direction.y, direction.z, radius), - new Vector4(spacing, parentSpacing, 0, 0))); + ToUnity(input.DirectionRadius), + ToUnity(input.Sampling))); } + var commonMetadata = PlanetaryGpuPageBuilder.Metadata(content, outputOffset, resident.Blend); metadata[pageIndex] = new( - new Vector4((int)resident.Tile.Face, resident.Tile.Level, resident.Tile.X, resident.Tile.Y), - new Vector4(outputOffset, layout.StorageSize, layout.InteriorSize, layout.BorderSize), - Vector4.zero, - new Vector4(1, resident.Blend, PlanetaryPageSampling.NominalAngularTexelSize(layout), spacing)); + ToUnity(commonMetadata.Address), + ToUnity(commonMetadata.Layout), + ToUnity(commonMetadata.Bounds), + ToUnity(commonMetadata.State)); } return new(inputs.ToArray(), metadata, inputs.Count, snapshot.ContentVersion, snapshot.PresentationVersion); } + + private static Vector4 ToUnity(float4 value) => new(value.x, value.y, value.z, value.w); } diff --git a/unity/Runtime/PlanetaryPageUpload.cs.meta b/unity/Runtime/PlanetaryPageUpload.cs.meta new file mode 100644 index 00000000..87bcb61a --- /dev/null +++ b/unity/Runtime/PlanetaryPageUpload.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7edd3bb2edb2e6448a2d54fc082c27dc \ No newline at end of file diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta b/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta new file mode 100644 index 00000000..69af0abc --- /dev/null +++ b/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ad7b581e14ef774d8435069bb77ca6a \ No newline at end of file diff --git a/web.meta b/web.meta new file mode 100644 index 00000000..d65ff3e6 --- /dev/null +++ b/web.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: daf45471f8b5f4f44a2cb86e77e010db +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/web/planetary-tile.cross-runtime.test.mjs b/web/planetary-tile.cross-runtime.test.mjs new file mode 100644 index 00000000..2b457219 --- /dev/null +++ b/web/planetary-tile.cross-runtime.test.mjs @@ -0,0 +1,21 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { decodePlanetarySurfaceTile } from "./planetary-tile.ts"; + +if (process.argv.length !== 3) throw new Error("Expected a generated CMPT fixture path"); +const bytes = await readFile(process.argv[2]); +const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); +const tile = decodePlanetarySurfaceTile(arrayBuffer); +assert.equal(tile.header.projectionVersion, 3); +assert.equal(tile.header.layerId, 29); +assert.equal(tile.header.projectionKind, 2); +assert.equal(tile.header.interiorSize, 5); +assert.equal(tile.header.borderSize, 1); +assert.equal(tile.storageSize, 7); +assert.equal(tile.samples.length, 49); +assert.ok(tile.samples.some(sample => sample !== null)); +for (const sample of tile.samples) { + if (sample === null) continue; + assert.ok(Number.isFinite(sample.radialDisplacement)); + assert.ok(Number.isFinite(sample.unresolvedHeightBound)); +} diff --git a/web/planetary-tile.cross-runtime.test.mjs.meta b/web/planetary-tile.cross-runtime.test.mjs.meta new file mode 100644 index 00000000..3c38da54 --- /dev/null +++ b/web/planetary-tile.cross-runtime.test.mjs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7bc397e5660d2b6448919c41983d35e8 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/web/planetary-tile.test.mjs b/web/planetary-tile.test.mjs new file mode 100644 index 00000000..cb054f28 --- /dev/null +++ b/web/planetary-tile.test.mjs @@ -0,0 +1,39 @@ +import assert from "node:assert/strict"; +import { decodePlanetarySurfaceTile } from "./planetary-tile.ts"; + +const bytes = new ArrayBuffer(152); +const view = new DataView(bytes); +let offset = 0; +const u8 = value => view.setUint8(offset++, value); +const u32 = value => { view.setUint32(offset, value, true); offset += 4; }; +const i32 = value => { view.setInt32(offset, value, true); offset += 4; }; +const u64 = value => { view.setBigUint64(offset, value, true); offset += 8; }; +const f32 = value => { view.setFloat32(offset, value, true); offset += 4; }; +const f64 = value => { view.setFloat64(offset, value, true); offset += 8; }; + +for (const character of "CMPT") u8(character.charCodeAt(0)); +u32(1); u64(0x1234n); u32(2); u32(17); i32(0); +f64(0); f64(0); f64(1); +i32(0); i32(0); i32(0); i32(2); i32(0); +f32(10); f32(0.5); i32(4); +u8(1); +for (const value of [1, 0, 0]) f32(value); +f32(8.4); f32(0.25); +for (const value of [0, 0.1, 0]) f32(value); +for (const value of [0.99, -0.1, 0]) f32(value); +for (const value of [0.1, 0.8, 0.2, 2.5, 0.05]) f32(value); +u8(0); u8(0); u8(0); +assert.equal(offset, bytes.byteLength); + +const tile = decodePlanetarySurfaceTile(bytes); +assert.equal(tile.header.fieldVersion, 0x1234n); +assert.equal(tile.header.layerId, 17); +assert.equal(tile.storageSize, 2); +assert.equal(tile.samples.length, 4); +assert.equal(tile.samples[0]?.radialDisplacement, 0.25); +assert.equal(tile.samples[1], null); + +assert.throws(() => decodePlanetarySurfaceTile(bytes.slice(0, bytes.byteLength - 1)), /Truncated/); +const trailing = new Uint8Array(bytes.byteLength + 1); +trailing.set(new Uint8Array(bytes)); +assert.throws(() => decodePlanetarySurfaceTile(trailing.buffer), /trailing/); diff --git a/web/planetary-tile.test.mjs.meta b/web/planetary-tile.test.mjs.meta new file mode 100644 index 00000000..d622095b --- /dev/null +++ b/web/planetary-tile.test.mjs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 04c93ecab90d465499aa3d60722e6fc3 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/web/planetary-tile.ts b/web/planetary-tile.ts new file mode 100644 index 00000000..e68d70fa --- /dev/null +++ b/web/planetary-tile.ts @@ -0,0 +1,89 @@ +export const CULTMATH_PLANETARY_TILE_VERSION = 1; + +export interface PlanetaryTileHeader { + fieldVersion: bigint; + projectionVersion: number; + layerId: number; + projectionKind: number; + centerLongitude: number; + centerLatitude: number; + projectionScale: number; + level: number; + x: number; + y: number; + interiorSize: number; + borderSize: number; + footprintMeters: number; + maximumUnresolvedHeight: number; +} + +export interface PlanetaryTileSample { + direction: readonly [number, number, number]; + radius: number; + radialDisplacement: number; + tangentGradient: readonly [number, number, number]; + surfaceNormal: readonly [number, number, number]; + slope: number; + ridge: number; + gully: number; + finestResolvedWavelength: number; + unresolvedHeightBound: number; +} + +export interface PlanetarySurfaceTile { + header: PlanetaryTileHeader; + storageSize: number; + samples: readonly (PlanetaryTileSample | null)[]; +} + +export function decodePlanetarySurfaceTile(bytes: ArrayBuffer): PlanetarySurfaceTile { + const view = new DataView(bytes); + let offset = 0; + const u8 = () => view.getUint8(offset++); + const u32 = () => { const value = view.getUint32(offset, true); offset += 4; return value; }; + const i32 = () => { const value = view.getInt32(offset, true); offset += 4; return value; }; + const f32 = () => { const value = view.getFloat32(offset, true); offset += 4; return value; }; + const f64 = () => { const value = view.getFloat64(offset, true); offset += 8; return value; }; + const u64 = () => { const value = view.getBigUint64(offset, true); offset += 8; return value; }; + const vector3 = (): [number, number, number] => [f32(), f32(), f32()]; + const requireRemaining = (count: number) => { + if (offset + count > view.byteLength) throw new Error("Truncated CultMath planetary tile"); + }; + + requireRemaining(8); + if (String.fromCharCode(u8(), u8(), u8(), u8()) !== "CMPT") throw new Error("Not a CultMath planetary tile"); + const version = u32(); + if (version !== CULTMATH_PLANETARY_TILE_VERSION) throw new Error(`Unsupported CultMath planetary tile version ${version}`); + requireRemaining(76); + const header: PlanetaryTileHeader = { + fieldVersion: u64(), + projectionVersion: u32(), + layerId: u32(), + projectionKind: i32(), + centerLongitude: f64(), + centerLatitude: f64(), + projectionScale: f64(), + level: i32(), x: i32(), y: i32(), interiorSize: i32(), borderSize: i32(), + footprintMeters: f32(), maximumUnresolvedHeight: f32(), + }; + const count = i32(); + const storageSize = header.interiorSize + header.borderSize * 2; + if (count !== storageSize * storageSize) throw new Error("CultMath planetary tile sample count does not match layout"); + const samples: (PlanetaryTileSample | null)[] = new Array(count); + for (let index = 0; index < count; index++) { + requireRemaining(1); + if (u8() === 0) { samples[index] = null; continue; } + requireRemaining(64); + samples[index] = { + direction: vector3(), + radius: f32(), + radialDisplacement: f32(), + tangentGradient: vector3(), + surfaceNormal: vector3(), + slope: f32(), ridge: f32(), gully: f32(), + finestResolvedWavelength: f32(), unresolvedHeightBound: f32(), + }; + } + if (offset !== view.byteLength) throw new Error("CultMath planetary tile has trailing bytes"); + return { header, storageSize, samples }; +} diff --git a/web/planetary-tile.ts.meta b/web/planetary-tile.ts.meta new file mode 100644 index 00000000..337da3bd --- /dev/null +++ b/web/planetary-tile.ts.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f284e64628320844196b7aaea76475a4 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From cb481b4c75444426a693781a0e5b35ade08a8938 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 17 Jul 2026 07:45:28 +0100 Subject: [PATCH 017/203] Add Unity-compatible simplex noise semantics --- THIRD-PARTY-NOTICES.md | 10 ++++++ shaders/CultMath.hlsl | 36 ++++++++++++++++++++ src/CultMath/math.cs | 44 +++++++++++++++++++++++++ tests/CultMath.Tests/HlslMirrorTests.cs | 1 + tests/CultMath.Tests/MathTests.cs | 10 ++++++ 5 files changed, 101 insertions(+) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 8339c5fe..54e280f6 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,15 @@ # 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. + ## Advanced Terrain Erosion Filter `src/CultMath/AdvancedErosionFilter.cs` and diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index cf76c8c2..84d244bb 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -73,6 +73,42 @@ float cultmath_smootherstep(float 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); } diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index 874554ad..e15fbf08 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -327,6 +327,50 @@ public static class math public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f))); public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f))); + // Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so + // the C# and HLSL mirrors preserve the same float32 evaluation order. + public static float simplex_noise(float2 value) + { + var c = new float4( + 0.211324865405187f, + 0.366025403784439f, + -0.577350269189626f, + 0.024390243902439f); + var i = floor(value + dot(value, new float2(c.y, c.y))); + var x0 = value - i + dot(i, new float2(c.x, c.x)); + var i1 = x0.x > x0.y ? new float2(1.0f, 0.0f) : new float2(0.0f, 1.0f); + var x12 = new float4( + x0.x + c.x - i1.x, + x0.y + c.x - i1.y, + x0.x + c.z, + x0.y + c.z); + + i = simplex_mod289(i); + var p = simplex_permute(simplex_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f)); + var m = max(0.5f - new float3( + dot(x0, x0), + x12.x * x12.x + x12.y * x12.y, + x12.z * x12.z + x12.w * x12.w), 0.0f); + m = m * m; + m = m * m; + + var x = 2.0f * frac(p * c.w) - 1.0f; + var h = abs(x) - 0.5f; + var ox = floor(x + 0.5f); + var a0 = x - ox; + m *= 1.79284291400159f - 0.85373472095314f * (a0 * a0 + h * h); + + var g = new 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.0f * dot(m, g); + } + + private static float2 simplex_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f; + private static float3 simplex_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f; + private static float3 simplex_permute(float3 value) => simplex_mod289(((value * 34.0f) + 1.0f) * value); + public static float value_noise(float2 position) { var cell = floor(position); diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 5e1451cd..47f58971 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -51,6 +51,7 @@ public sealed class HlslMirrorTests "cultmath_cubic_bezier", "cultmath_hash", "cultmath_value_noise", + "cultmath_simplex_noise", "cultmath_value_noise_bicubic", "cultmath_value_noise_texture", "cultmath_value_noise_texture_bicubic", diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index bb816d47..6979bdd6 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -235,6 +235,16 @@ public sealed class MathTests Assert.True(bicubic > -0.25f && bicubic < 1.25f); } + [Theory] + [InlineData(0.0f, 0.0f, 0.0f)] + [InlineData(0.25f, -0.5f, -0.425866783f)] + [InlineData(12.25f, -4.5f, 0.258312f)] + [InlineData(536.5106f, 536.5106f, -0.6901103f)] + public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected) + { + Assert.Equal(expected, math.simplex_noise(new float2(x, y)), precision: 6); + } + [Fact] public void VoronoiBatchProducesToneMappedColors() { From 6a162606e4bed08de8b3250d708db9b841c92d1b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 22 Jul 2026 03:47:38 +0100 Subject: [PATCH 018/203] Transfer planetary geometry to GameCult.Geometry --- CultMath.slnx | 3 - README.md | 11 - .../CultMath.PlanetaryViewer.Editor.asmdef | 18 - .../Editor/CultMathPlanetaryViewerMenu.cs | 26 -- Samples~/PlanetaryViewer/README.md | 16 - .../Runtime/CultMath.PlanetaryViewer.asmdef | 17 - .../Runtime/CultMathPlanetaryViewer.cs | 139 ------- .../Runtime/CultMathPlanetaryViewer.shader | 90 ---- THIRD-PARTY-NOTICES.md | 23 - .../CultMath.PlanetaryTileFixture.csproj | 11 - .../CultMath.PlanetaryTileFixture/Program.cs | 22 - docs/design.md | 6 - docs/planetary.md | 130 ------ docs/planetary.md.meta | 7 - package.json | 9 +- scripts/test-web-planetary-tile.ps1 | 9 - scripts/test-web-planetary-tile.ps1.meta | 7 - shaders/AdvancedErosionFilter.hlsl | 147 ------- shaders/AdvancedErosionFilter.hlsl.meta | 7 - shaders/CultMath.hlsl | 94 ----- shaders/Planetary.hlsl | 392 ------------------ shaders/Planetary.hlsl.meta | 7 - src/CultMath/AdvancedErosionFilter.cs | 139 ------- src/CultMath/AdvancedErosionFilter.cs.meta | 2 - src/CultMath/CultMath.csproj | 2 - src/CultMath/ErosionFrequencyBands.cs | 57 --- src/CultMath/ErosionFrequencyBands.cs.meta | 2 - src/CultMath/PlanetaryField.cs | 215 ---------- src/CultMath/PlanetaryField.cs.meta | 2 - src/CultMath/PlanetaryGpuPages.cs | 55 --- src/CultMath/PlanetaryGpuPages.cs.meta | 2 - src/CultMath/PlanetaryMapTileEncoding.cs | 117 ------ src/CultMath/PlanetaryMapTileEncoding.cs.meta | 2 - src/CultMath/PlanetaryPages.cs | 187 --------- src/CultMath/PlanetaryPages.cs.meta | 2 - src/CultMath/PlanetaryPatch.cs | 45 -- src/CultMath/PlanetaryPatch.cs.meta | 2 - src/CultMath/PlanetaryProjection.cs | 322 -------------- src/CultMath/PlanetaryProjection.cs.meta | 2 - src/CultMath/PlanetaryQueries.cs | 193 --------- src/CultMath/PlanetaryQueries.cs.meta | 2 - src/CultMath/PlanetaryRadialRefinement.cs | 28 -- .../PlanetaryRadialRefinement.cs.meta | 2 - src/CultMath/PlanetaryResidency.cs | 148 ------- src/CultMath/PlanetaryResidency.cs.meta | 2 - src/CultMath/PlanetaryTopology.cs | 191 --------- src/CultMath/PlanetaryTopology.cs.meta | 2 - src/CultMath/SphericalErosion.cs | 108 ----- src/CultMath/SphericalErosion.cs.meta | 2 - src/CultMath/math.cs | 3 - src/CultMath/rect.cs | 21 - src/CultMath/rect.cs.meta | 2 - .../AdvancedErosionFilterTests.cs | 44 -- .../AdvancedErosionFilterTests.cs.meta | 2 - .../ErosionFrequencyBandsTests.cs | 34 -- .../ErosionFrequencyBandsTests.cs.meta | 2 - tests/CultMath.Tests/HlslMirrorTests.cs | 37 +- tests/CultMath.Tests/MathTests.cs | 13 - tests/CultMath.Tests/PlanetaryFieldTests.cs | 80 ---- .../PlanetaryFieldTests.cs.meta | 2 - tests/CultMath.Tests/PlanetaryGpuPageTests.cs | 25 -- .../PlanetaryGpuPageTests.cs.meta | 2 - .../PlanetaryMapTileEncodingTests.cs | 52 --- .../PlanetaryMapTileEncodingTests.cs.meta | 2 - tests/CultMath.Tests/PlanetaryPageTests.cs | 57 --- .../CultMath.Tests/PlanetaryPageTests.cs.meta | 2 - tests/CultMath.Tests/PlanetaryPatchTests.cs | 40 -- .../PlanetaryPatchTests.cs.meta | 2 - .../PlanetaryProjectionTests.cs | 73 ---- .../PlanetaryProjectionTests.cs.meta | 2 - tests/CultMath.Tests/PlanetaryQueryTests.cs | 46 -- .../PlanetaryQueryTests.cs.meta | 2 - .../PlanetaryRadialRefinementTests.cs | 29 -- .../PlanetaryRadialRefinementTests.cs.meta | 2 - .../CultMath.Tests/PlanetaryResidencyTests.cs | 57 --- .../PlanetaryResidencyTests.cs.meta | 2 - .../CultMath.Tests/PlanetaryTopologyTests.cs | 63 --- .../PlanetaryTopologyTests.cs.meta | 2 - tests/CultMath.Tests/SphericalErosionTests.cs | 34 -- .../SphericalErosionTests.cs.meta | 2 - tools/CultMath.PlanetaryTileFixture.meta | 8 - unity/Runtime/PlanetaryPageUpload.cs | 95 ----- unity/Runtime/PlanetaryPageUpload.cs.meta | 2 - unity/Runtime/PlanetaryPatchMeshAdapter.cs | 36 -- .../Runtime/PlanetaryPatchMeshAdapter.cs.meta | 2 - web/planetary-tile.cross-runtime.test.mjs | 21 - ...planetary-tile.cross-runtime.test.mjs.meta | 7 - web/planetary-tile.test.mjs | 39 -- web/planetary-tile.test.mjs.meta | 7 - web/planetary-tile.ts | 89 ---- web/planetary-tile.ts.meta | 7 - 91 files changed, 6 insertions(+), 4067 deletions(-) delete mode 100644 Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef delete mode 100644 Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs delete mode 100644 Samples~/PlanetaryViewer/README.md delete mode 100644 Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef delete mode 100644 Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs delete mode 100644 Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader delete mode 100644 Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj delete mode 100644 Tools~/CultMath.PlanetaryTileFixture/Program.cs delete mode 100644 docs/planetary.md delete mode 100644 docs/planetary.md.meta delete mode 100644 scripts/test-web-planetary-tile.ps1 delete mode 100644 scripts/test-web-planetary-tile.ps1.meta delete mode 100644 shaders/AdvancedErosionFilter.hlsl delete mode 100644 shaders/AdvancedErosionFilter.hlsl.meta delete mode 100644 shaders/Planetary.hlsl delete mode 100644 shaders/Planetary.hlsl.meta delete mode 100644 src/CultMath/AdvancedErosionFilter.cs delete mode 100644 src/CultMath/AdvancedErosionFilter.cs.meta delete mode 100644 src/CultMath/ErosionFrequencyBands.cs delete mode 100644 src/CultMath/ErosionFrequencyBands.cs.meta delete mode 100644 src/CultMath/PlanetaryField.cs delete mode 100644 src/CultMath/PlanetaryField.cs.meta delete mode 100644 src/CultMath/PlanetaryGpuPages.cs delete mode 100644 src/CultMath/PlanetaryGpuPages.cs.meta delete mode 100644 src/CultMath/PlanetaryMapTileEncoding.cs delete mode 100644 src/CultMath/PlanetaryMapTileEncoding.cs.meta delete mode 100644 src/CultMath/PlanetaryPages.cs delete mode 100644 src/CultMath/PlanetaryPages.cs.meta delete mode 100644 src/CultMath/PlanetaryPatch.cs delete mode 100644 src/CultMath/PlanetaryPatch.cs.meta delete mode 100644 src/CultMath/PlanetaryProjection.cs delete mode 100644 src/CultMath/PlanetaryProjection.cs.meta delete mode 100644 src/CultMath/PlanetaryQueries.cs delete mode 100644 src/CultMath/PlanetaryQueries.cs.meta delete mode 100644 src/CultMath/PlanetaryRadialRefinement.cs delete mode 100644 src/CultMath/PlanetaryRadialRefinement.cs.meta delete mode 100644 src/CultMath/PlanetaryResidency.cs delete mode 100644 src/CultMath/PlanetaryResidency.cs.meta delete mode 100644 src/CultMath/PlanetaryTopology.cs delete mode 100644 src/CultMath/PlanetaryTopology.cs.meta delete mode 100644 src/CultMath/SphericalErosion.cs delete mode 100644 src/CultMath/SphericalErosion.cs.meta delete mode 100644 src/CultMath/rect.cs delete mode 100644 src/CultMath/rect.cs.meta delete mode 100644 tests/CultMath.Tests/AdvancedErosionFilterTests.cs delete mode 100644 tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta delete mode 100644 tests/CultMath.Tests/ErosionFrequencyBandsTests.cs delete mode 100644 tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryFieldTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryFieldTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryGpuPageTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryPageTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryPageTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryPatchTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryPatchTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryProjectionTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryQueryTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryQueryTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryResidencyTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta delete mode 100644 tests/CultMath.Tests/PlanetaryTopologyTests.cs delete mode 100644 tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta delete mode 100644 tests/CultMath.Tests/SphericalErosionTests.cs delete mode 100644 tests/CultMath.Tests/SphericalErosionTests.cs.meta delete mode 100644 tools/CultMath.PlanetaryTileFixture.meta delete mode 100644 unity/Runtime/PlanetaryPageUpload.cs delete mode 100644 unity/Runtime/PlanetaryPageUpload.cs.meta delete mode 100644 unity/Runtime/PlanetaryPatchMeshAdapter.cs delete mode 100644 unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta delete mode 100644 web/planetary-tile.cross-runtime.test.mjs delete mode 100644 web/planetary-tile.cross-runtime.test.mjs.meta delete mode 100644 web/planetary-tile.test.mjs delete mode 100644 web/planetary-tile.test.mjs.meta delete mode 100644 web/planetary-tile.ts delete mode 100644 web/planetary-tile.ts.meta diff --git a/CultMath.slnx b/CultMath.slnx index 05d17886..95834573 100644 --- a/CultMath.slnx +++ b/CultMath.slnx @@ -5,7 +5,4 @@ - - - diff --git a/README.md b/README.md index c60a9912..b471c7fd 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,6 @@ shader semantics. - `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. -- `PlanetaryField`, a scale-aware CPU/HLSL spherical field contract with - deterministic identity, cube-sphere topology, residual pages, LOD residency, - ray/path/region queries, coarse patches, and projected map-tile baking. See - `docs/planetary.md`. -- `CultMath.Unity`, an engine-facing assembly containing only Unity mesh and - page-upload adapters for the canonical planetary contracts. ## Native Core @@ -72,11 +66,6 @@ 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. -The advanced erosion filter is an MPL-2.0 adaptation of Rune Skovbo Johansen's -published filter. Its faithful planar core is isolated in -`AdvancedErosionFilter.cs` and `AdvancedErosionFilter.hlsl`; spherical mapping -is a separate CultMath concern. See `THIRD-PARTY-NOTICES.md`. - ## Build ```powershell diff --git a/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef b/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef deleted file mode 100644 index 10512326..00000000 --- a/Samples~/PlanetaryViewer/Editor/CultMath.PlanetaryViewer.Editor.asmdef +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "CultMath.PlanetaryViewer.Editor", - "rootNamespace": "CultMath.Unity.Samples.Editor", - "references": [ - "CultMath.PlanetaryViewer" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} diff --git a/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs b/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs deleted file mode 100644 index ade28c30..00000000 --- a/Samples~/PlanetaryViewer/Editor/CultMathPlanetaryViewerMenu.cs +++ /dev/null @@ -1,26 +0,0 @@ -using UnityEditor; -using UnityEngine; - -namespace CultMath.Unity.Samples.Editor; - -public static class CultMathPlanetaryViewerMenu -{ - [MenuItem("GameObject/CultMath/Planetary Field Viewer", false, 10)] - public static void CreateViewer() - { - var root = new GameObject("CultMath Planetary Field Viewer"); - Undo.RegisterCreatedObjectUndo(root, "Create CultMath Planetary Viewer"); - root.AddComponent(); - Selection.activeGameObject = root; - - if (Camera.main != null) return; - var cameraObject = new GameObject("Planetary Viewer Camera"); - Undo.RegisterCreatedObjectUndo(cameraObject, "Create Planetary Viewer Camera"); - cameraObject.tag = "MainCamera"; - cameraObject.transform.position = new Vector3(0, 0, -24); - cameraObject.transform.LookAt(root.transform); - var camera = cameraObject.AddComponent(); - camera.nearClipPlane = 0.01f; - camera.farClipPlane = 1000; - } -} diff --git a/Samples~/PlanetaryViewer/README.md b/Samples~/PlanetaryViewer/README.md deleted file mode 100644 index ec4dcc3a..00000000 --- a/Samples~/PlanetaryViewer/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Planetary Field Viewer - -Import this sample through Unity's Package Manager, then choose -`GameObject > CultMath > Planetary Field Viewer`. - -The host project must contain `Assets/csc.rsp` with -`-langversion:latest`, as documented in the CultMath package README. - -The generated six-face mesh uses `PlanetaryPatch`. Its shader calls -`cultmath_planetary_field_sample` from the packaged CultMath HLSL, while the -component exposes the matching CPU `Sample` method. Seed, radius, physical -sample footprint, erosion settings, and base-field equations are shared between -the two bodies. - -This is a renderer integration fixture, not a gameplay owner. It deliberately -contains no Aetheria state, biomes, resources, or simulation decisions. diff --git a/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef b/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef deleted file mode 100644 index 2d3676f4..00000000 --- a/Samples~/PlanetaryViewer/Runtime/CultMath.PlanetaryViewer.asmdef +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "CultMath.PlanetaryViewer", - "rootNamespace": "CultMath.Unity.Samples", - "references": [ - "CultMath", - "CultMath.Unity" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs deleted file mode 100644 index 06113728..00000000 --- a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace CultMath.Unity.Samples; - -[ExecuteAlways] -public sealed class CultMathPlanetaryViewer : MonoBehaviour -{ - [Min(0.001f)] public float Radius = 8.4f; - [Range(1, 256)] public int CellsPerFace = 64; - public int Seed = 42; - [Min(0.0001f)] public float SampleFootprint = 0.01f; - public Shader? PlanetShader; - public Vector3 LightDirection = new(-0.4f, 0.7f, -0.6f); - public Color LowColor = new(0.055f, 0.16f, 0.09f, 1); - public Color HighColor = new(0.62f, 0.48f, 0.28f, 1); - public Color RidgeColor = new(0.88f, 0.83f, 0.7f, 1); - - private readonly List meshes = new(); - private Material? material; - private int builtCells; - - public PlanetaryFieldDefinition Definition => PlanetaryFieldDefinition.Create( - 1, - Radius, - Seed, - ViewerErosion(Radius)); - - public PlanetarySurfaceSample Sample(float3 direction) - { - var source = new ViewerBaseField(Radius); - var normalized = math.normalize(direction); - return PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), PlanetaryQueryScale.AtFootprint(SampleFootprint)); - } - - private void OnEnable() => Rebuild(); - - private void OnValidate() - { - Radius = Mathf.Max(Radius, 0.001f); - CellsPerFace = Mathf.Clamp(CellsPerFace, 1, 256); - SampleFootprint = Mathf.Max(SampleFootprint, 0.0001f); - if (isActiveAndEnabled) Rebuild(); - } - - private void Update() - { - if (builtCells != CellsPerFace || material == null) Rebuild(); - ApplyMaterial(); - } - - private void Rebuild() - { - ClearGenerated(); - PlanetShader ??= Shader.Find("CultMath/Planetary Field Viewer"); - if (PlanetShader == null) return; - material = new Material(PlanetShader) { name = "CultMath Planetary Viewer Material", hideFlags = HideFlags.HideAndDontSave }; - foreach (var face in (PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace))) - { - var mesh = PlanetaryPatchMeshAdapter.CreateFaceMesh(face, CellsPerFace); - mesh.hideFlags = HideFlags.HideAndDontSave; - meshes.Add(mesh); - var child = new GameObject(face.ToString()) { hideFlags = HideFlags.DontSave }; - child.transform.SetParent(transform, false); - child.AddComponent().sharedMesh = mesh; - child.AddComponent().sharedMaterial = material; - } - builtCells = CellsPerFace; - ApplyMaterial(); - } - - private void ApplyMaterial() - { - if (material == null) return; - var p = ViewerErosion(Radius); - material.SetFloat("_Radius", Radius); - material.SetFloat("_SampleFootprint", SampleFootprint); - material.SetInt("_Seed", Seed); - material.SetFloat("_ErosionScale", p.Scale); - material.SetFloat("_ErosionStrength", p.Strength); - material.SetFloat("_GullyWeight", p.GullyWeight); - material.SetFloat("_Detail", p.Detail); - material.SetVector("_Rounding", new(p.Rounding.x, p.Rounding.y, p.Rounding.z, p.Rounding.w)); - material.SetVector("_Onset", new(p.Onset.x, p.Onset.y, p.Onset.z, p.Onset.w)); - material.SetVector("_AssumedSlope", new(p.AssumedSlope.x, p.AssumedSlope.y, 0, 0)); - material.SetFloat("_CellScale", p.CellScale); - material.SetFloat("_Normalization", p.Normalization); - material.SetInt("_Octaves", p.Octaves); - material.SetFloat("_Lacunarity", p.Lacunarity); - material.SetFloat("_Gain", p.Gain); - material.SetVector("_LightDirection", LightDirection.normalized); - material.SetColor("_LowColor", LowColor); - material.SetColor("_HighColor", HighColor); - material.SetColor("_RidgeColor", RidgeColor); - } - - private void OnDisable() => ClearGenerated(); - private void OnDestroy() => ClearGenerated(); - - private void ClearGenerated() - { - for (var i = transform.childCount - 1; i >= 0; i--) DestroyGenerated(transform.GetChild(i).gameObject); - foreach (var mesh in meshes) DestroyGenerated(mesh); - meshes.Clear(); - if (material != null) DestroyGenerated(material); - material = null; - builtCells = 0; - } - - private static void DestroyGenerated(UnityEngine.Object value) - { - if (Application.isPlaying) Destroy(value); - else DestroyImmediate(value); - } - - private static AdvancedErosionParameters ViewerErosion(float radius) => new( - radius * 0.075f, 0.12f, 0.58f, 1.45f, - new float4(0.1f, 0.015f, 0.1f, 2), - new float4(1.25f, 1.25f, 2.8f, 1.5f), - new float2(0.7f, 0.85f), 0.7f, 0.5f, 7, 2, 0.5f); - - private readonly struct ViewerBaseField : IPlanetaryBaseField - { - private readonly float radius; - public ViewerBaseField(float radius) => this.radius = radius; - - public PlanetaryBaseFieldSample Sample(float3 direction) - { - var value = direction.x * 0.6f + direction.y * direction.z * 0.3f; - var angularGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f); - angularGradient -= direction * math.dot(angularGradient, direction); - var fieldGradient = angularGradient / radius; - var radialDisplacement = value * radius * 0.035f; - var radialGradient = angularGradient * 0.035f; - return new(radialDisplacement, radialGradient, value, fieldGradient, math.clamp(value, -1, 1)); - } - } -} diff --git a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader b/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader deleted file mode 100644 index b5f7274f..00000000 --- a/Samples~/PlanetaryViewer/Runtime/CultMathPlanetaryViewer.shader +++ /dev/null @@ -1,90 +0,0 @@ -Shader "CultMath/Planetary Field Viewer" -{ - Properties - { - _LowColor ("Low Color", Color) = (0.055, 0.16, 0.09, 1) - _HighColor ("High Color", Color) = (0.62, 0.48, 0.28, 1) - _RidgeColor ("Ridge Color", Color) = (0.88, 0.83, 0.7, 1) - } - SubShader - { - Tags { "RenderType"="Opaque" "Queue"="Geometry" } - Pass - { - Cull Back - ZWrite On - HLSLPROGRAM - #pragma target 5.0 - #pragma vertex vert - #pragma fragment frag - #include "UnityCG.cginc" - #include "Packages/org.gamecult.cultmath/shaders/CultMath.hlsl" - - float _Radius; - float _SampleFootprint; - int _Seed; - float _ErosionScale, _ErosionStrength, _GullyWeight, _Detail; - float4 _Rounding, _Onset, _AssumedSlope; - float _CellScale, _Normalization, _Lacunarity, _Gain; - int _Octaves; - float3 _LightDirection; - float4 _LowColor, _HighColor, _RidgeColor; - - struct Attributes { float4 vertex : POSITION; }; - struct Varyings - { - float4 position : SV_POSITION; - float3 worldNormal : TEXCOORD0; - float height : TEXCOORD1; - float ridge : TEXCOORD2; - }; - - CultMathPlanetarySurfaceSample viewer_sample(float3 direction) - { - direction = normalize(direction); - float value = direction.x * 0.6 + direction.y * direction.z * 0.3; - float3 angular_gradient = float3(0.6, direction.z * 0.3, direction.y * 0.3); - angular_gradient -= direction * dot(angular_gradient, direction); - CultMathPlanetaryBaseFieldSample base_sample; - base_sample.radial_displacement = value * _Radius * 0.035; - base_sample.radial_gradient = angular_gradient * 0.035; - base_sample.field_value = value; - base_sample.field_gradient = angular_gradient / _Radius; - base_sample.fade_target = clamp(value, -1.0, 1.0); - CultMathAdvancedErosionParameters erosion; - erosion.scale = _ErosionScale; erosion.strength = _ErosionStrength; - erosion.gully_weight = _GullyWeight; erosion.detail = _Detail; - erosion.rounding = _Rounding; erosion.onset = _Onset; - erosion.assumed_slope = _AssumedSlope.xy; erosion.cell_scale = _CellScale; - erosion.normalization = _Normalization; erosion.octaves = _Octaves; - erosion.lacunarity = _Lacunarity; erosion.gain = _Gain; - CultMathPlanetaryFieldDefinition definition; - definition.radius = _Radius; definition.seed = _Seed; definition.erosion = erosion; - return cultmath_planetary_field_sample(definition, direction, base_sample, _SampleFootprint); - } - - Varyings vert(Attributes input) - { - float3 direction = normalize(input.vertex.xyz); - CultMathPlanetarySurfaceSample sample = viewer_sample(direction); - float3 local = direction * (_Radius + sample.radial_displacement); - Varyings output; - output.position = UnityObjectToClipPos(float4(local, 1)); - output.worldNormal = UnityObjectToWorldNormal(sample.surface_normal); - output.height = sample.radial_displacement / max(_Radius * 0.1, 1.0e-5); - output.ridge = sample.ridge; - return output; - } - - float4 frag(Varyings input) : SV_Target - { - float height = saturate(input.height * 0.5 + 0.5); - float3 albedo = lerp(_LowColor.rgb, _HighColor.rgb, height); - albedo = lerp(albedo, _RidgeColor.rgb, saturate((input.ridge - 0.55) * 2.5)); - float diffuse = saturate(dot(normalize(input.worldNormal), normalize(-_LightDirection))) * 0.82 + 0.08; - return float4(albedo * diffuse, 1); - } - ENDHLSL - } - } -} diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 54e280f6..c283df2b 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -9,26 +9,3 @@ Gustavson's `webgl-noise` project and Unity Mathematics. - Copyright 2011 Ashima Arts. - Upstream: - License: MIT. - -## Advanced Terrain Erosion Filter - -`src/CultMath/AdvancedErosionFilter.cs` and -`shaders/AdvancedErosionFilter.hlsl` adapt the mathematical procedure from -Rune Skovbo Johansen's **Advanced Terrain Erosion Filter**: - -- Copyright 2025 Rune Skovbo Johansen. -- Upstream: -- Explanation: -- License: Mozilla Public License 2.0, - - -The CultMath files retain the MPL notice and separate the faithful planar -filter from CultMath's spherical adaptation. Modifications include C#/HLSL -type lowering, explicit bounded octave counts, finite zero-rounding behavior, -and CultMath naming. CultMath replaces the reference floating hash with a -shared integer avalanche hash so C# and GPU evaluation select identical cell -pivots across devices; this changes the stochastic pattern, not the filter -mechanics. - -The technique builds on earlier work credited by the upstream author to Fewes -and Clay John. The hash form follows the public shader math of Inigo Quilez. diff --git a/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj b/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj deleted file mode 100644 index 9a6f8bdd..00000000 --- a/Tools~/CultMath.PlanetaryTileFixture/CultMath.PlanetaryTileFixture.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - Exe - net10.0 - enable - enable - - - - - diff --git a/Tools~/CultMath.PlanetaryTileFixture/Program.cs b/Tools~/CultMath.PlanetaryTileFixture/Program.cs deleted file mode 100644 index 1952fa2d..00000000 --- a/Tools~/CultMath.PlanetaryTileFixture/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CultMath; - -if (args.Length != 1) throw new ArgumentException("Expected one output path."); -var field = PlanetaryFieldDefinition.Create(0x55aa, 10, 7, AdvancedErosionParameters.Default); -var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 0, 0, 0, 5, 1); -var key = new PlanetaryMapTileKey(field.FieldVersion, 3, 29, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.125f)); -var tile = PlanetaryMapTileBaker.Bake(field, key, new FixtureField()); -var path = Path.GetFullPath(args[0]); -Directory.CreateDirectory(Path.GetDirectoryName(path)!); -using var stream = File.Create(path); -PlanetaryMapTileEncoding.Write(stream, tile); - -readonly struct FixtureField : IPlanetaryBaseField -{ - public PlanetaryBaseFieldSample Sample(float3 direction) - { - var value = direction.x * 0.2f + direction.z * 0.4f; - var gradient = new float3(0.2f, 0, 0.4f); - gradient -= direction * math.dot(gradient, direction); - return new(value, gradient, value, gradient * 0.1f, value); - } -} diff --git a/docs/design.md b/docs/design.md index f4ca5b49..8250ba78 100644 --- a/docs/design.md +++ b/docs/design.md @@ -19,12 +19,6 @@ Rust hot kernels, C# host code, and HLSL shader code. - Keep the surface boring and exact. Noise primitives are allowed when they are source-grounded, shader-shaped, and shared by more than one project. Matrices, quaternions, and packing helpers still need an owner before they enter. -- Planetary geometry is a scale-aware field query, not a renderer asset. CPU - simulation is the reference consumer; pages, patches, and maps are derived - lowerings carrying field identity and unresolved error. -- Application base fields and gameplay meaning enter through narrow inputs. - CultMath does not own biomes, resources, traversability, render materials, or - committed game consequences. ## Non-Goals diff --git a/docs/planetary.md b/docs/planetary.md deleted file mode 100644 index 3503429b..00000000 --- a/docs/planetary.md +++ /dev/null @@ -1,130 +0,0 @@ -# Planetary Field Runtime - -CultMath owns the deterministic, renderer-independent planetary field. The same -definition can be queried by a CPU simulation, baked into residual pages, -sampled through a map projection, or evaluated by HLSL. Render resources and -gameplay interpretation remain consumer responsibilities. - -## Authority - -- `PlanetaryFieldDefinition` identifies radius, seed, erosion parameters, and a - version that includes the consumer-owned base-field version. -- `IPlanetaryBaseField` supplies deterministic application-authored base - evidence without making CultMath depend on an application. -- `PlanetaryQueryScale` makes the physical footprint and accepted unresolved - error explicit. There is deliberately no scale-free authoritative - `HeightAt` call. -- `PlanetarySurfaceSample` carries displacement, tangent gradient, normal, - slope, ridge/gully evidence, resolved wavelength, and unresolved bound. -- pages, residency, patches, and map tiles are disposable lowerings of that - contract. - -The C# surface targets .NET and Unity-compatible `netstandard2.1`. The matching -HLSL lives in `shaders/Planetary.hlsl` and is included by `CultMath.hlsl`. - -## Point and batch queries - -```csharp -var erosion = AdvancedErosionParameters.Default with -{ - Scale = radius * 0.075f, - Strength = 0.12f, - Octaves = 7, -}; -var field = PlanetaryFieldDefinition.Create( - baseFieldVersion: 17, - radius, - seed: 42, - erosion); -var scale = PlanetaryQueryScale.AtFootprint( - footprintMeters: 10, - maximumUnresolvedHeight: 0.5f); - -PlanetaryBaseFieldSample basis = source.Sample(direction); -PlanetarySurfaceSample sample = PlanetaryField.Sample( - field, direction, basis, scale); -``` - -`PlanetaryField.SampleBatch` writes into a caller-owned span. `SamplePosition` -accepts `double3`, reduces the astronomical position to a stable unit direction, -and then evaluates the same float field contract. - -Higher-level CPU queries include bounded ray intersection, segment clearance, -great-circle profiles, and region summaries. Their answers remain field queries; -only a consuming simulation can turn them into committed game consequences. - -## Cube-sphere topology and pages - -`PlanetaryTopology` owns the tangent/QSC cube-sphere mapping, inverse face -coordinates, tile selection, local page coordinates, and radial-graph surface -normal. `PlanetaryPageSampling` owns bordered directions, nominal spacing, -bilinear sampling, and summaries. - -`PlanetaryPageBaker` evaluates a root page at its physical sample spacing. A -child stores `child filtered field - parent filtered field`. A -`PlanetaryPageSetSampling` query adds every containing ancestor contribution -and applies transition weights only to residuals. - -`PlanetaryLodSelector` selects six roots plus one camera-facing ancestor chain -from the unresolved-height error. `PlanetaryResidualResidency` owns arrival and -departure presentation weights, content and presentation versions, and nothing -about field truth. - -## Patches - -`PlanetaryPatch.CreateFace` and `CreateCubeSphere` produce renderer-neutral -coarse meshes from the canonical topology. HLSL calls -`cultmath_planetary_face_direction` for procedural vertices and -`cultmath_planetary_radial_refinement_step` for bounded pixel refinement. - -Renderers still own cameras, clip space, depth, materials, buffers, dispatch, -and draw submission. - -`PlanetaryGpuPageBuilder` owns the renderer-neutral page input and metadata -payload. Host adapters may convert its `float4` values to native vector types, -but do not recalculate directions, spacing, addressing, or transition state. - -The optional `CultMath.Unity` assembly converts a canonical face patch into a -Unity `Mesh` and converts the shared GPU payload into Unity-native structs. It -owns no field parameters, page selection, or simulation state. The package's -Planetary Field Viewer sample renders the shared HLSL field and reports matching -CPU queries. Unity render code remains responsible for allocating/binding -graphics buffers and dispatching application-specific base-field entry points. -The host Unity project must set `-langversion:latest` in `Assets/csc.rsp`. - -## Maps - -`PlanetaryProjection` supplies forward and inverse transforms for: - -- equirectangular; -- Web Mercator with an explicit polar cutoff; -- Equal Earth; -- orthographic; -- azimuthal equidistant and equal-area; -- cube atlas; -- local tangent/gnomonic views. - -`PlanetaryMapTileBaker` maps each valid tile sample through the inverse -projection and canonical field query. `PlanetaryMapTileKey` includes field, -projection, layer, layout, and query-scale identity. Palettes, contours, labels, -and strategic overlays do not enter CultMath. - -`PlanetaryMapTileEncoding` writes the resulting evidence as a versioned -little-endian `CMPT` binary asset. `web/planetary-tile.ts` decodes that exact -format into browser-native values without defining another terrain generator. -Typed CultMesh documents should carry the tile key and asset reference; the -binary payload is derived CDN/cache data, not committed game state. - -## Numeric contract - -Integer hashes and published field versions are exact. Basic arithmetic follows -CultMath's matched C#/HLSL semantics. Hardware transcendental functions such as -`atan` and `tan` use measured tolerances rather than fictional bit identity. -Every filtered terrain query carries an unresolved-height bound so consumers can -reject insufficient fidelity explicitly. - -Current D3D12 parity evidence covers the advanced erosion kernel, QSC topology, -and forward/inverse transforms for every supported map projection, including -center and scale parameters. Production page and patch shaders use the common -functions and retain their seam, residual, summary, and radial-hit integration -probes in Fensalir. diff --git a/docs/planetary.md.meta b/docs/planetary.md.meta deleted file mode 100644 index af364c25..00000000 --- a/docs/planetary.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 23a5751e4cececc4e99e024058d69add -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/package.json b/package.json index 25643608..6da2a17a 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,5 @@ "Simulation" ], "license": "MPL-2.0", - "category": "Scripting", - "samples": [ - { - "displayName": "Planetary Field Viewer", - "description": "Runnable Unity viewer using the canonical CultMath CPU and HLSL planetary field.", - "path": "Samples~/PlanetaryViewer" - } - ] + "category": "Scripting" } diff --git a/scripts/test-web-planetary-tile.ps1 b/scripts/test-web-planetary-tile.ps1 deleted file mode 100644 index 5221fb36..00000000 --- a/scripts/test-web-planetary-tile.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$ErrorActionPreference = "Stop" -$root = Split-Path -Parent $PSScriptRoot -$fixture = Join-Path $root ".tools\planetary-tile\fixture.cmpt" -dotnet run --project (Join-Path $root "Tools~\CultMath.PlanetaryTileFixture\CultMath.PlanetaryTileFixture.csproj") -- $fixture -if ($LASTEXITCODE -ne 0) { throw "Planetary tile fixture generation failed." } -node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.cross-runtime.test.mjs") $fixture -if ($LASTEXITCODE -ne 0) { throw "Planetary tile web decoding failed." } -node --no-warnings --experimental-strip-types (Join-Path $root "web\planetary-tile.test.mjs") -if ($LASTEXITCODE -ne 0) { throw "Planetary tile decoder unit test failed." } diff --git a/scripts/test-web-planetary-tile.ps1.meta b/scripts/test-web-planetary-tile.ps1.meta deleted file mode 100644 index ffd73234..00000000 --- a/scripts/test-web-planetary-tile.ps1.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 235e8f203bd1c4d40a8fa211273106ba -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/shaders/AdvancedErosionFilter.hlsl b/shaders/AdvancedErosionFilter.hlsl deleted file mode 100644 index a0a350e5..00000000 --- a/shaders/AdvancedErosionFilter.hlsl +++ /dev/null @@ -1,147 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. -// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain -// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn - -#ifndef CULTMATH_ADVANCED_EROSION_FILTER_HLSL -#define CULTMATH_ADVANCED_EROSION_FILTER_HLSL - -struct CultMathAdvancedErosionParameters -{ - float scale; float strength; float gully_weight; float detail; - float4 rounding; float4 onset; float2 assumed_slope; - float cell_scale; float normalization; int octaves; float lacunarity; float gain; -}; - -struct CultMathAdvancedErosionResult -{ - float3 delta; float magnitude; float ridge_map; float fade_target; -}; - -struct CultMathErosionBandSelection -{ - int active_octaves; float final_octave_weight; float finest_included_wavelength; float unresolved_height_bound; -}; - -CultMathErosionBandSelection cultmath_select_erosion_bands(float base_wavelength, float sample_spacing, int maximum_octaves, float lacunarity, float base_amplitude, float gain, float transition_ratio) -{ - base_wavelength=max(base_wavelength,1.0e-6); sample_spacing=max(sample_spacing,0.0); lacunarity=max(lacunarity,1.01); transition_ratio=max(transition_ratio,1.01); maximum_octaves=min(max(maximum_octaves,0),16); - float nyquist=sample_spacing*2.0, wavelength=base_wavelength, final_weight=0.0, finest=0.0; int active=0; - [loop] for(int octave=0; octave=active) omitted+=amplitude; else if(index==active-1) omitted+=amplitude*(1.0-final_weight); amplitude*=abs(gain); - } - CultMathErosionBandSelection result; result.active_octaves=active; result.final_octave_weight=active>0?final_weight:0.0; result.finest_included_wavelength=finest; result.unresolved_height_bound=omitted; return result; -} - -float cultmath_erosion_hash_unit(int2 cell, uint salt) -{ - uint value = (uint)cell.x * 0x8da6b343u ^ (uint)cell.y * 0xd8163841u ^ salt; - value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16; - return (value & 0x00ffffffu) * (1.0 / 16777216.0); -} -float2 cultmath_erosion_hash2(float2 input) -{ - int2 cell = (int2)input; - return float2(cultmath_erosion_hash_unit(cell, 0x68bc21ebu), cultmath_erosion_hash_unit(cell, 0x02e5be93u)) * 2.0 - 1.0; -} - -float4 cultmath_phacelle_noise(float2 position, float2 normal_direction, float frequency, float offset_cycles, float normalization) -{ - float2 side_direction = float2(-normal_direction.y, normal_direction.x) * frequency * 6.28318530718; - float offset = offset_cycles * 6.28318530718; - float2 cell = floor(position); - float2 local = frac(position); - float2 phase = 0.0; - float weight_sum = 0.0; - [unroll] for (int x = -1; x <= 2; x++) - [unroll] for (int y = -1; y <= 2; y++) - { - float2 grid_offset = float2(x, y); - float2 grid_point = cell + grid_offset; - float2 random_offset = cultmath_erosion_hash2(grid_point) * 0.5; - float2 delta = local - grid_offset - random_offset; - float weight = max(0.0, exp(-dot(delta, delta) * 2.0) - 0.01111); - weight_sum += weight; - float wave = dot(delta, side_direction) + offset; - phase += float2(cos(wave), sin(wave)) * weight; - } - float2 interpolated = phase / max(weight_sum, 1.0e-20); - float magnitude = max(1.0 - saturate(normalization), length(interpolated)); - return float4(interpolated / max(magnitude, 1.0e-20), side_direction); -} - -float cultmath_erosion_pow_inverse(float value, float power) { return 1.0 - pow(1.0 - saturate(value), power); } -float cultmath_erosion_ease_out(float value) { float inverse = 1.0 - saturate(value); return 1.0 - inverse * inverse; } -float cultmath_erosion_smooth_start(float value, float smoothing) -{ - return smoothing <= 1.0e-10 || value >= smoothing ? value - 0.5 * smoothing : 0.5 * value * value / smoothing; -} -float2 cultmath_erosion_safe_normalize(float2 value) -{ - float value_length = length(value); - return abs(value_length) > 1.0e-10 ? value / value_length : value; -} - -CultMathAdvancedErosionResult cultmath_advanced_erosion_filter_banded(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p, CultMathErosionBandSelection band) -{ - float strength = p.strength * p.scale; - fade_target = clamp(fade_target, -1.0, 1.0); - float3 input_value = base_height_and_slope; - float3 height_and_slope = input_value; - float frequency = 1.0 / max(p.scale * p.cell_scale, 1.0e-10); - float2 slope = height_and_slope.yz; - float slope_length = max(length(slope), 1.0e-10); - float magnitude = 0.0; - float rounding_multiplier = 1.0; - float input_rounding = lerp(p.rounding.y, p.rounding.x, saturate(fade_target + 0.5)) * p.rounding.z; - float combined_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(slope_length * p.onset.x, input_rounding * p.onset.x)); - float ridge_mask = cultmath_erosion_ease_out(slope_length * p.onset.z); - float ridge_fade = fade_target; - float2 gully_slope = lerp(slope, slope / slope_length * p.assumed_slope.x, p.assumed_slope.y); - - int octave_count=min(min(max(p.octaves,0),8),max(band.active_octaves,0)); - [loop] for (int octave = 0; octave < octave_count; octave++) - { - float4 phacelle = cultmath_phacelle_noise(position * frequency, cultmath_erosion_safe_normalize(gully_slope), p.cell_scale, 0.25, p.normalization); - float2 derivative_direction = phacelle.zw * -frequency; - float sloping = abs(phacelle.y); - gully_slope += sign(phacelle.y) * derivative_direction * strength * p.gully_weight; - float3 gullies = float3(phacelle.x, phacelle.y * derivative_direction.x, phacelle.y * derivative_direction.y); - float3 faded = lerp(float3(fade_target, 0.0, 0.0), gullies * p.gully_weight, combined_mask); - float octave_weight=octave==octave_count-1?saturate(band.final_octave_weight):1.0; - height_and_slope += faded * strength * octave_weight; - magnitude += strength * octave_weight; - fade_target = faded.x; - float octave_rounding = lerp(p.rounding.y, p.rounding.x, saturate(phacelle.x + 0.5)) * rounding_multiplier; - float new_mask = cultmath_erosion_ease_out(cultmath_erosion_smooth_start(sloping * p.onset.y, octave_rounding * p.onset.y)); - combined_mask = cultmath_erosion_pow_inverse(combined_mask, p.detail) * new_mask; - ridge_fade = lerp(ridge_fade, lerp(ridge_fade, gullies.x, ridge_mask), octave_weight); - ridge_mask *= lerp(1.0, cultmath_erosion_ease_out(sloping * p.onset.w), octave_weight); - strength *= p.gain; - frequency *= p.lacunarity; - rounding_multiplier *= p.rounding.w; - } - - CultMathAdvancedErosionResult result; - result.delta = height_and_slope - input_value; - result.magnitude = magnitude; - result.ridge_map = ridge_fade * (1.0 - ridge_mask); - result.fade_target = fade_target; - return result; -} - -CultMathAdvancedErosionResult cultmath_advanced_erosion_filter(float2 position, float3 base_height_and_slope, float fade_target, CultMathAdvancedErosionParameters p) -{ - CultMathErosionBandSelection band; band.active_octaves=p.octaves; band.final_octave_weight=1.0; band.finest_included_wavelength=0.0; band.unresolved_height_bound=0.0; - return cultmath_advanced_erosion_filter_banded(position, base_height_and_slope, fade_target, p, band); -} - -#endif diff --git a/shaders/AdvancedErosionFilter.hlsl.meta b/shaders/AdvancedErosionFilter.hlsl.meta deleted file mode 100644 index a354e330..00000000 --- a/shaders/AdvancedErosionFilter.hlsl.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 3252ac02e8853ef4b9d1fbc5204b5c6b -ShaderIncludeImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index 84d244bb..4c00fb69 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -299,98 +299,4 @@ float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, Sampl return cultmath_catmullrom(row0, row1, row2, row3, local.y); } -#include "AdvancedErosionFilter.hlsl" -#include "Planetary.hlsl" - -struct CultMathSphericalErosionParameters -{ - float frequency; float amplitude; float lacunarity; float gain; - float slope_strength; float detail; float seed; int octaves; -}; - -struct CultMathSphericalErosionSample -{ - float height_offset; float ridge; float gully; float3 gradient; -}; - -float cultmath_spherical_cell_wave(float3 position, float3 flow, float3 cell, float seed) -{ - float3 pivot = cell + float3(cultmath_hash(cell + seed + 11.7), cultmath_hash(cell.yzx + seed + 37.1), cultmath_hash(cell.zyx + seed + 73.9)); - return dot(position - pivot, flow) * 6.28318530718 + cultmath_hash(cell + seed + 101.3) * 6.28318530718; -} - -float cultmath_spherical_directional_wave(float3 position, float3 flow, float seed) -{ - float3 cell = floor(position); - float3 local = cultmath_frac(position); - float3 weights = local * local * (3.0 - 2.0 * local); - float z0 = cultmath_lerp( - cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,0), seed), weights.x), - cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,0), seed), weights.x), weights.y); - float z1 = cultmath_lerp( - cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,1), seed), weights.x), - cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,1), seed), weights.x), weights.y); - return cultmath_lerp(z0, z1, weights.z); -} - -float3 cultmath_spherical_stable_tangent(float3 position) -{ - float3 axis = abs(position.z) < 0.8 ? float3(0,0,1) : float3(0,1,0); - return cultmath_normalize(cross(axis, position)); -} - -CultMathSphericalErosionSample cultmath_spherical_erosion(float3 unit_position, float base_height, float3 base_gradient, CultMathSphericalErosionParameters p) -{ - float3 position = cultmath_normalize(unit_position); - float3 gradient = base_gradient - position * dot(base_gradient, position); - float slope = length(gradient); - float3 flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position); - float frequency = max(p.frequency, 0.001); - float amplitude = p.amplitude; - float combi_mask = 1.0 - pow(1.0 - cultmath_saturate(slope * p.slope_strength), 2.0); - float fade_target = cultmath_saturate(base_height * 0.5 + 0.5) * 2.0 - 1.0; - CultMathSphericalErosionSample result = (CultMathSphericalErosionSample)0; - [loop] for (int octave = 0; octave < min(max(p.octaves, 0), 8); octave++) - { - float phase = cultmath_spherical_directional_wave(position * frequency, flow, p.seed + octave * 19.19); - float wave = cos(phase); - float wave_slope = sin(phase); - float faded = cultmath_lerp(fade_target, wave, combi_mask); - result.height_offset += faded * amplitude; - result.ridge = max(result.ridge, cultmath_saturate(faded)); - result.gully = max(result.gully, cultmath_saturate(-faded)); - float3 across = cultmath_normalize(cross(position, flow)); - gradient += across * (sign(wave_slope) * amplitude * frequency * combi_mask); - gradient -= position * dot(gradient, position); - slope = length(gradient); - flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position); - float new_mask = 1.0 - pow(1.0 - cultmath_saturate(abs(wave_slope) * 1.35), 2.0); - combi_mask = (1.0 - pow(1.0 - cultmath_saturate(combi_mask), max(p.detail, 0.001))) * new_mask; - fade_target = faded; - frequency *= max(p.lacunarity, 1.01); - amplitude *= cultmath_saturate(p.gain); - } - result.gradient = gradient; - return result; -} - -float3 cultmath_planetary_radial_refinement_step( - float3 position, - float3 ray_direction, - float3 center, - float target_radius, - float minimum_derivative, - float maximum_correction) -{ - float3 local=position-center; - float radius=length(local); - float3 radial=local/max(radius,0.0001); - float error=radius-target_radius; - float derivative=dot(ray_direction,radial); - float safe_minimum=max(minimum_derivative,0.0001); - float safe_derivative=abs(derivative)>safe_minimum?derivative:(derivative<0?-safe_minimum:safe_minimum); - float correction=clamp(-error/safe_derivative,-abs(maximum_correction),abs(maximum_correction)); - return position+ray_direction*correction; -} - #endif diff --git a/shaders/Planetary.hlsl b/shaders/Planetary.hlsl deleted file mode 100644 index 81e312b7..00000000 --- a/shaders/Planetary.hlsl +++ /dev/null @@ -1,392 +0,0 @@ -#ifndef CULTMATH_PLANETARY_HLSL -#define CULTMATH_PLANETARY_HLSL - -static const float CULTMATH_PLANETARY_QUARTER_PI = 0.78539816339744830962; - -float3 cultmath_planetary_face_direction(int face, float2 coordinate) -{ - float2 tangent = tan(coordinate * CULTMATH_PLANETARY_QUARTER_PI); - float3 cube = face == 0 ? float3(1.0, tangent.y, -tangent.x) - : face == 1 ? float3(-1.0, tangent.y, tangent.x) - : face == 2 ? float3(tangent.x, 1.0, -tangent.y) - : face == 3 ? float3(tangent.x, -1.0, tangent.y) - : face == 4 ? float3(tangent.x, tangent.y, 1.0) - : float3(-tangent.x, tangent.y, -1.0); - return cultmath_normalize(cube); -} - -float2 cultmath_planetary_face_coordinate(float3 direction, out int face) -{ - float3 d = cultmath_normalize(direction); - float3 a = abs(d); - float2 cube_uv; - if (a.x >= a.y && a.x >= a.z) - { - if (d.x >= 0.0) { face = 0; cube_uv = float2(-d.z, d.y) / a.x; } - else { face = 1; cube_uv = float2(d.z, d.y) / a.x; } - } - else if (a.y >= a.z) - { - if (d.y >= 0.0) { face = 2; cube_uv = float2(d.x, -d.z) / a.y; } - else { face = 3; cube_uv = float2(d.x, d.z) / a.y; } - } - else - { - if (d.z >= 0.0) { face = 4; cube_uv = d.xy / a.z; } - else { face = 5; cube_uv = float2(-d.x, d.y) / a.z; } - } - return atan(cube_uv) / CULTMATH_PLANETARY_QUARTER_PI; -} - -bool cultmath_planetary_page_local(float3 direction, float4 tile_address, float active, out float2 local) -{ - int face; - float2 uv = cultmath_planetary_face_coordinate(direction, face); - local = 0.0; - if (active < 0.5 || abs((float)face - tile_address.x) > 0.25) return false; - float axis_tiles = exp2(tile_address.y); - float2 scaled = (uv * 0.5 + 0.5) * axis_tiles; - float2 canonical = floor(min(scaled, axis_tiles - 1.0e-5)); - if (any(abs(canonical - tile_address.zw) > 0.25)) return false; - local = scaled - tile_address.zw; - return all(local >= 0.0) && all(local <= 1.0); -} - -float3 cultmath_planetary_surface_normal(float3 direction, float3 world_distance_tangent_gradient) -{ - direction = cultmath_normalize(direction); - float3 tangent_gradient = world_distance_tangent_gradient - direction * dot(world_distance_tangent_gradient, direction); - return cultmath_normalize(direction - tangent_gradient); -} - -float2 cultmath_planetary_equirectangular_forward(float3 direction) -{ - direction = cultmath_normalize(direction); - return float2(atan2(direction.y, direction.x) / CULTMATH_PI, asin(clamp(direction.z, -1.0, 1.0)) / CULTMATH_HALF_PI); -} - -float3 cultmath_planetary_equirectangular_inverse(float2 coordinate) -{ - float longitude = coordinate.x * CULTMATH_PI; - float latitude = coordinate.y * CULTMATH_HALF_PI; - float cos_latitude = cos(latitude); - return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); -} - -static const float CULTMATH_EQUAL_EARTH_A1 = 1.340264; -static const float CULTMATH_EQUAL_EARTH_A2 = -0.081106; -static const float CULTMATH_EQUAL_EARTH_A3 = 0.000893; -static const float CULTMATH_EQUAL_EARTH_A4 = 0.003796; -static const float CULTMATH_EQUAL_EARTH_M = 0.8660254037844386; -static const float CULTMATH_EQUAL_EARTH_X_MAX = 2.706629; -static const float CULTMATH_EQUAL_EARTH_Y_MAX = 1.3173628; - -float cultmath_equal_earth_y(float theta) -{ - float theta2 = theta * theta; - float theta6 = theta2 * theta2 * theta2; - return theta * (CULTMATH_EQUAL_EARTH_A1 + CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (CULTMATH_EQUAL_EARTH_A3 + CULTMATH_EQUAL_EARTH_A4 * theta2)); -} - -float2 cultmath_planetary_equal_earth_forward(float3 direction) -{ - direction = cultmath_normalize(direction); - float longitude = atan2(direction.y, direction.x); - float latitude = asin(clamp(direction.z, -1.0, 1.0)); - float theta = asin(CULTMATH_EQUAL_EARTH_M * sin(latitude)); - float theta2 = theta * theta; - float theta6 = theta2 * theta2 * theta2; - float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2)); - return float2(longitude * cos(theta) / denominator / CULTMATH_EQUAL_EARTH_X_MAX, cultmath_equal_earth_y(theta) / CULTMATH_EQUAL_EARTH_Y_MAX); -} - -float3 cultmath_planetary_equal_earth_inverse(float2 coordinate) -{ - float target_y = coordinate.y * CULTMATH_EQUAL_EARTH_Y_MAX; - float theta = target_y / CULTMATH_EQUAL_EARTH_A1; - [unroll] for (int iteration = 0; iteration < 8; iteration++) - { - float theta2 = theta * theta; - float theta6 = theta2 * theta2 * theta2; - float derivative = CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2); - theta -= (cultmath_equal_earth_y(theta) - target_y) / derivative; - } - float latitude = asin(clamp(sin(theta) / CULTMATH_EQUAL_EARTH_M, -1.0, 1.0)); - float theta2 = theta * theta; - float theta6 = theta2 * theta2 * theta2; - float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2)); - float longitude = coordinate.x * CULTMATH_EQUAL_EARTH_X_MAX * denominator / cos(theta); - float cos_latitude = cos(latitude); - return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); -} - -float cultmath_planetary_wrap_longitude(float longitude) -{ - longitude = fmod(longitude, CULTMATH_TAU); - if (longitude > CULTMATH_PI) longitude -= CULTMATH_TAU; - if (longitude < -CULTMATH_PI) longitude += CULTMATH_TAU; - return longitude; -} - -float3 cultmath_planetary_lon_lat_direction(float longitude, float latitude) -{ - float cos_latitude = cos(latitude); - return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude))); -} - -float cultmath_planetary_asinh(float value) { return log(value + sqrt(value * value + 1.0)); } -float cultmath_planetary_sinh(float value) { return 0.5 * (exp(value) - exp(-value)); } - -bool cultmath_planetary_azimuthal_forward(float3 direction, float2 center_lon_lat, int kind, out float2 coordinate) -{ - direction = cultmath_normalize(direction); - float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat.x); - float latitude = asin(clamp(direction.z, -1.0, 1.0)); - float sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y); - float sin_latitude = sin(latitude), cos_latitude = cos(latitude), cos_longitude = cos(longitude); - float cos_c = clamp(sin0 * sin_latitude + cos0 * cos_latitude * cos_longitude, -1.0, 1.0); - float2 raw = float2(cos_latitude * sin(longitude), cos0 * sin_latitude - sin0 * cos_latitude * cos_longitude); - float factor = 1.0; - if (kind == 3) - { - if (cos_c < 0.0) { coordinate = 0.0; return false; } - } - else if (kind == 4) - { - float c = acos(cos_c); factor = (c < 1.0e-6 ? 1.0 : c / sin(c)) / CULTMATH_PI; - } - else if (kind == 5) factor = sqrt(2.0 / max(1.0 + cos_c, 1.0e-20)) * 0.5; - else - { - if (cos_c <= 0.0) { coordinate = 0.0; return false; } - factor = 1.0 / cos_c; - } - coordinate = raw * factor; - return true; -} - -bool cultmath_planetary_azimuthal_inverse(float2 coordinate, float2 center_lon_lat, int kind, out float3 direction) -{ - float rho = length(coordinate); - float c; - if (kind == 3) { if (rho > 1.0) { direction = 0.0; return false; } c = asin(clamp(rho, 0.0, 1.0)); } - else if (kind == 4) { if (rho > 1.0) { direction = 0.0; return false; } c = rho * CULTMATH_PI; } - else if (kind == 5) { if (rho > 1.0) { direction = 0.0; return false; } c = 2.0 * asin(clamp(rho, 0.0, 1.0)); } - else c = atan(rho); - if (rho < 1.0e-6) { direction = cultmath_planetary_lon_lat_direction(center_lon_lat.x, center_lon_lat.y); return true; } - float sin_c = sin(c), cos_c = cos(c), sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y); - float latitude = asin(clamp(cos_c * sin0 + coordinate.y * sin_c * cos0 / rho, -1.0, 1.0)); - float delta_longitude = atan2(coordinate.x * sin_c, rho * cos0 * cos_c - coordinate.y * sin0 * sin_c); - direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(delta_longitude + center_lon_lat.x), latitude); - return true; -} - -bool cultmath_planetary_cube_atlas_forward(float3 direction, out float2 coordinate) -{ - int face; - float2 face_coordinate = cultmath_planetary_face_coordinate(direction, face); - int column = face % 3; - int row = face / 3; - coordinate = float2(-1.0 + (column + (face_coordinate.x + 1.0) * 0.5) * (2.0 / 3.0), 1.0 - (row + (face_coordinate.y + 1.0) * 0.5)); - return true; -} - -bool cultmath_planetary_cube_atlas_inverse(float2 coordinate, out float3 direction) -{ - if (any(coordinate < -1.0) || any(coordinate > 1.0)) { direction = 0.0; return false; } - int column = min((int)((coordinate.x + 1.0) * 1.5), 2); - int row = min((int)(1.0 - coordinate.y), 1); - float u = ((coordinate.x + 1.0) * 1.5 - column) * 2.0 - 1.0; - float v = ((1.0 - coordinate.y) - row) * 2.0 - 1.0; - direction = cultmath_planetary_face_direction(row * 3 + column, float2(u, v)); - return true; -} - -bool cultmath_planetary_projection_forward(float3 direction, int kind, float3 center_lon_lat_scale, out float2 coordinate) -{ - float scale = max(center_lon_lat_scale.z, 1.0e-20); - if (kind == 6) - { - bool valid = cultmath_planetary_cube_atlas_forward(direction, coordinate); - coordinate /= scale; return valid; - } - if (kind >= 3) - { - bool valid = cultmath_planetary_azimuthal_forward(direction, center_lon_lat_scale.xy, kind, coordinate); - coordinate /= scale; return valid; - } - direction = cultmath_normalize(direction); - float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat_scale.x); - float latitude = asin(clamp(direction.z, -1.0, 1.0)); - if (kind == 0) coordinate = float2(longitude / CULTMATH_PI, latitude / CULTMATH_HALF_PI); - else if (kind == 1) - { - const float maximum_latitude = 1.4844222297453324; - if (abs(latitude) > maximum_latitude) { coordinate = 0.0; return false; } - coordinate = float2(longitude / CULTMATH_PI, cultmath_planetary_asinh(tan(latitude)) / CULTMATH_PI); - } - else - { - float3 centered = cultmath_planetary_lon_lat_direction(longitude, latitude); - coordinate = cultmath_planetary_equal_earth_forward(centered); - } - coordinate /= scale; - return true; -} - -bool cultmath_planetary_projection_inverse(float2 coordinate, int kind, float3 center_lon_lat_scale, out float3 direction) -{ - coordinate *= max(center_lon_lat_scale.z, 1.0e-20); - if (kind == 6) return cultmath_planetary_cube_atlas_inverse(coordinate, direction); - if (kind >= 3) return cultmath_planetary_azimuthal_inverse(coordinate, center_lon_lat_scale.xy, kind, direction); - if (any(abs(coordinate) > 1.0)) { direction = 0.0; return false; } - float longitude, latitude; - if (kind == 0) { longitude = coordinate.x * CULTMATH_PI; latitude = coordinate.y * CULTMATH_HALF_PI; } - else if (kind == 1) { longitude = coordinate.x * CULTMATH_PI; latitude = atan(cultmath_planetary_sinh(coordinate.y * CULTMATH_PI)); } - else - { - float3 centered = cultmath_planetary_equal_earth_inverse(coordinate); - longitude = atan2(centered.y, centered.x); latitude = asin(clamp(centered.z, -1.0, 1.0)); - } - direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(longitude + center_lon_lat_scale.x), latitude); - return true; -} - -struct CultMathPlanetaryFieldDefinition -{ - float radius; - int seed; - CultMathAdvancedErosionParameters erosion; -}; - -struct CultMathPlanetaryBaseFieldSample -{ - float radial_displacement; - float3 radial_gradient; - float field_value; - float3 field_gradient; - float fade_target; -}; - -struct CultMathPlanetarySurfaceSample -{ - float radial_displacement; - float3 tangent_gradient; - float3 surface_normal; - float slope; - float ridge; - float gully; - float finest_resolved_wavelength; - float unresolved_height_bound; -}; - -struct CultMathPlanetaryPageSample -{ - float4 height_gradient; - float2 masks; -}; - -CultMathPlanetaryPageSample cultmath_planetary_page_lerp( - CultMathPlanetaryPageSample a, - CultMathPlanetaryPageSample b, - CultMathPlanetaryPageSample c, - CultMathPlanetaryPageSample d, - float2 fraction) -{ - CultMathPlanetaryPageSample result; - result.height_gradient = lerp(lerp(a.height_gradient, b.height_gradient, fraction.x), lerp(c.height_gradient, d.height_gradient, fraction.x), fraction.y); - result.masks = lerp(lerp(a.masks, b.masks, fraction.x), lerp(c.masks, d.masks, fraction.x), fraction.y); - return result; -} - -CultMathPlanetaryPageSample cultmath_planetary_residual_sample( - CultMathPlanetarySurfaceSample child, - CultMathPlanetarySurfaceSample parent, - bool has_parent) -{ - CultMathPlanetaryPageSample result; - result.height_gradient = float4(child.radial_displacement, child.tangent_gradient); - if (has_parent) result.height_gradient -= float4(parent.radial_displacement, parent.tangent_gradient); - result.masks = float2(child.ridge, child.gully); - return result; -} - -float4 cultmath_planetary_summary_empty() -{ - return float4(3.402823466e+38, -3.402823466e+38, 0.0, 0.0); -} - -float4 cultmath_planetary_summary_accumulate(float4 summary, float4 height_gradient, float unresolved_height) -{ - summary.x = min(summary.x, height_gradient.x); - summary.y = max(summary.y, height_gradient.x); - summary.z = max(summary.z, length(height_gradient.yzw)); - summary.w = max(summary.w, max(unresolved_height, 0.0)); - return summary; -} - -float cultmath_planetary_pow4(float value) { float square = value * value; return square * square; } - -CultMathPlanetarySurfaceSample cultmath_planetary_field_sample( - CultMathPlanetaryFieldDefinition definition, - float3 unit_direction, - CultMathPlanetaryBaseFieldSample base_sample, - float footprint_meters) -{ - float3 direction = cultmath_normalize(unit_direction); - float3 radial_gradient = base_sample.radial_gradient - direction * dot(base_sample.radial_gradient, direction); - float3 field_gradient = base_sample.field_gradient - direction * dot(base_sample.field_gradient, direction); - CultMathAdvancedErosionParameters p = definition.erosion; - CultMathErosionBandSelection band = cultmath_select_erosion_bands( - p.scale * p.cell_scale, footprint_meters, p.octaves, p.lacunarity, - p.strength * p.scale, p.gain, 2.0); - float3 powers = float3( - cultmath_planetary_pow4(abs(direction.x)), - cultmath_planetary_pow4(abs(direction.y)), - cultmath_planetary_pow4(abs(direction.z))); - float power_sum = max(powers.x + powers.y + powers.z, 1.0e-6); - float3 weights = powers / power_sum; - float3 world = direction * definition.radius; - float seed = (float)definition.seed; - CultMathAdvancedErosionResult xy = cultmath_advanced_erosion_filter_banded( - world.xy + float2(713.0 + seed * 0.754877666, -291.0 + seed * 0.569840296), - float3(base_sample.field_value, field_gradient.x, field_gradient.y), base_sample.fade_target, p, band); - CultMathAdvancedErosionResult yz = cultmath_advanced_erosion_filter_banded( - world.yz + float2(-431.0 + seed * 0.438289027, 887.0 + seed * 0.328438163), - float3(base_sample.field_value, field_gradient.y, field_gradient.z), base_sample.fade_target, p, band); - CultMathAdvancedErosionResult zx = cultmath_advanced_erosion_filter_banded( - world.zx + float2(197.0 + seed * 0.219783071, 557.0 + seed * 0.145898034), - float3(base_sample.field_value, field_gradient.z, field_gradient.x), base_sample.fade_target, p, band); - float erosion_height = xy.delta.x * weights.z + yz.delta.x * weights.x + zx.delta.x * weights.y; - float3 erosion_gradient = float3( - xy.delta.y * weights.z + zx.delta.z * weights.y, - xy.delta.z * weights.z + yz.delta.y * weights.x, - yz.delta.z * weights.x + zx.delta.y * weights.y); - float3 power_gradient_x = float3(4.0 * direction.x * direction.x * direction.x / definition.radius, 0.0, 0.0); - float3 power_gradient_y = float3(0.0, 4.0 * direction.y * direction.y * direction.y / definition.radius, 0.0); - float3 power_gradient_z = float3(0.0, 0.0, 4.0 * direction.z * direction.z * direction.z / definition.radius); - power_gradient_x -= direction * dot(power_gradient_x, direction); - power_gradient_y -= direction * dot(power_gradient_y, direction); - power_gradient_z -= direction * dot(power_gradient_z, direction); - float3 power_sum_gradient = power_gradient_x + power_gradient_y + power_gradient_z; - float inverse_power_sum_squared = 1.0 / (power_sum * power_sum); - float3 weight_gradient_x = (power_gradient_x * power_sum - power_sum_gradient * powers.x) * inverse_power_sum_squared; - float3 weight_gradient_y = (power_gradient_y * power_sum - power_sum_gradient * powers.y) * inverse_power_sum_squared; - float3 weight_gradient_z = (power_gradient_z * power_sum - power_sum_gradient * powers.z) * inverse_power_sum_squared; - erosion_gradient += yz.delta.x * weight_gradient_x + zx.delta.x * weight_gradient_y + xy.delta.x * weight_gradient_z; - float3 gradient = radial_gradient + erosion_gradient; - gradient -= direction * dot(gradient, direction); - float ridge_evidence = xy.ridge_map * weights.z + yz.ridge_map * weights.x + zx.ridge_map * weights.y; - CultMathPlanetarySurfaceSample result; - result.radial_displacement = base_sample.radial_displacement + erosion_height; - result.tangent_gradient = gradient; - result.surface_normal = cultmath_planetary_surface_normal(direction, gradient); - result.slope = length(gradient); - result.ridge = saturate(ridge_evidence * 0.5 + 0.5); - result.gully = saturate(0.5 - ridge_evidence * 0.5); - result.finest_resolved_wavelength = band.finest_included_wavelength; - result.unresolved_height_bound = band.unresolved_height_bound; - return result; -} - -#endif diff --git a/shaders/Planetary.hlsl.meta b/shaders/Planetary.hlsl.meta deleted file mode 100644 index f7771355..00000000 --- a/shaders/Planetary.hlsl.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: bb055ad5328888e479e90ae3fe62c3fe -ShaderIncludeImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/CultMath/AdvancedErosionFilter.cs b/src/CultMath/AdvancedErosionFilter.cs deleted file mode 100644 index 65db3671..00000000 --- a/src/CultMath/AdvancedErosionFilter.cs +++ /dev/null @@ -1,139 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Mathematical procedure adapted from Rune Skovbo Johansen's Advanced Terrain -// Erosion Filter (2025): https://www.shadertoy.com/view/wXcfWn - -namespace CultMath; - -public readonly record struct AdvancedErosionParameters( - float Scale, - float Strength, - float GullyWeight, - float Detail, - float4 Rounding, - float4 Onset, - float2 AssumedSlope, - float CellScale, - float Normalization, - int Octaves, - float Lacunarity, - float Gain) -{ - public static AdvancedErosionParameters Default => new( - 0.15f, 0.22f, 0.5f, 1.5f, - new float4(0.1f, 0.0f, 0.1f, 2.0f), - new float4(1.25f, 1.25f, 2.8f, 1.5f), - new float2(0.7f, 1.0f), - 0.7f, 0.5f, 5, 2.0f, 0.5f); -} - -public readonly record struct AdvancedErosionResult(float3 Delta, float Magnitude, float RidgeMap, float FadeTarget); - -public static class AdvancedErosionFilter -{ - public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p) - => Sample(position, baseHeightAndSlope, fadeTarget, p, new ErosionBandSelection(p.Octaves, 1.0f, 0.0f, 0.0f)); - - public static AdvancedErosionResult Sample(float2 position, float3 baseHeightAndSlope, float fadeTarget, AdvancedErosionParameters p, ErosionBandSelection band) - { - var strength = p.Strength * p.Scale; - fadeTarget = math.clamp(fadeTarget, -1.0f, 1.0f); - var input = baseHeightAndSlope; - var heightAndSlope = input; - var frequency = 1.0f / math.max(p.Scale * p.CellScale, 1.0e-10f); - var slope = new float2(heightAndSlope.y, heightAndSlope.z); - var slopeLength = math.max(math.length(slope), 1.0e-10f); - var magnitude = 0.0f; - var roundingMultiplier = 1.0f; - - var inputRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(fadeTarget + 0.5f)) * p.Rounding.z; - var combinedMask = EaseOut(SmoothStart(slopeLength * p.Onset.x, inputRounding * p.Onset.x)); - var ridgeMask = EaseOut(slopeLength * p.Onset.z); - var ridgeFade = fadeTarget; - var gullySlope = math.lerp(slope, slope / slopeLength * p.AssumedSlope.x, p.AssumedSlope.y); - - var octaves = math.min(math.min(math.max(p.Octaves, 0), 8), math.max(band.ActiveOctaves, 0)); - for (var octave = 0; octave < octaves; octave++) - { - var phacelle = Phacelle(position * frequency, SafeNormalize(gullySlope), p.CellScale, 0.25f, p.Normalization); - var derivativeDirection = new float2(phacelle.z, phacelle.w) * -frequency; - var sloping = math.abs(phacelle.y); - gullySlope += math.sign(phacelle.y) * derivativeDirection * strength * p.GullyWeight; - - var gullies = new float3(phacelle.x, phacelle.y * derivativeDirection.x, phacelle.y * derivativeDirection.y); - var faded = math.lerp(new float3(fadeTarget, 0.0f, 0.0f), gullies * p.GullyWeight, combinedMask); - var octaveWeight = octave == octaves - 1 ? math.saturate(band.FinalOctaveWeight) : 1.0f; - heightAndSlope += faded * strength * octaveWeight; - magnitude += strength * octaveWeight; - fadeTarget = faded.x; - - var octaveRounding = math.lerp(p.Rounding.y, p.Rounding.x, math.saturate(phacelle.x + 0.5f)) * roundingMultiplier; - var newMask = EaseOut(SmoothStart(sloping * p.Onset.y, octaveRounding * p.Onset.y)); - combinedMask = PowInverse(combinedMask, p.Detail) * newMask; - - ridgeFade = math.lerp(ridgeFade, math.lerp(ridgeFade, gullies.x, ridgeMask), octaveWeight); - ridgeMask *= math.lerp(1.0f, EaseOut(sloping * p.Onset.w), octaveWeight); - strength *= p.Gain; - frequency *= p.Lacunarity; - roundingMultiplier *= p.Rounding.w; - } - - return new AdvancedErosionResult(heightAndSlope - input, magnitude, ridgeFade * (1.0f - ridgeMask), fadeTarget); - } - - public static float4 Phacelle(float2 position, float2 normalDirection, float frequency, float offsetCycles, float normalization) - { - var sideDirection = new float2(-normalDirection.y, normalDirection.x) * frequency * math.TAU; - var offset = offsetCycles * math.TAU; - var cell = math.floor(position); - var local = math.frac(position); - var phase = float2.zero; - var weightSum = 0.0f; - - for (var x = -1; x <= 2; x++) - for (var y = -1; y <= 2; y++) - { - var gridOffset = new float2(x, y); - var gridPoint = cell + gridOffset; - var randomOffset = Hash2(gridPoint) * 0.5f; - var delta = local - gridOffset - randomOffset; - var weight = math.max(0.0f, math.exp(-math.dot(delta, delta) * 2.0f) - 0.01111f); - weightSum += weight; - var wave = math.dot(delta, sideDirection) + offset; - phase += new float2(math.cos(wave), math.sin(wave)) * weight; - } - - var interpolated = phase / math.max(weightSum, 1.0e-20f); - var magnitude = math.max(1.0f - math.saturate(normalization), math.length(interpolated)); - var normalized = interpolated / math.max(magnitude, 1.0e-20f); - return new float4(normalized, sideDirection.x, sideDirection.y); - } - - private static float2 Hash2(float2 input) - { - var x = (int)input.x; - var y = (int)input.y; - return new float2(HashUnit(x, y, 0x68bc21ebu), HashUnit(x, y, 0x02e5be93u)) * 2.0f - float2.one; - } - - private static float HashUnit(int x, int y, uint salt) - { - var value = unchecked((uint)x * 0x8da6b343u ^ (uint)y * 0xd8163841u ^ salt); - value ^= value >> 16; value *= 0x7feb352du; value ^= value >> 15; value *= 0x846ca68bu; value ^= value >> 16; - return (value & 0x00ffffffu) * (1.0f / 16777216.0f); - } - - private static float2 SafeNormalize(float2 value) - { - var length = math.length(value); - return math.abs(length) > 1.0e-10f ? value / length : value; - } - - private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), power); - private static float EaseOut(float value) { var inverse = 1.0f - math.saturate(value); return 1.0f - inverse * inverse; } - private static float SmoothStart(float value, float smoothing) => smoothing <= 1.0e-10f || value >= smoothing - ? value - 0.5f * smoothing - : 0.5f * value * value / smoothing; -} diff --git a/src/CultMath/AdvancedErosionFilter.cs.meta b/src/CultMath/AdvancedErosionFilter.cs.meta deleted file mode 100644 index 52f7ca19..00000000 --- a/src/CultMath/AdvancedErosionFilter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: bec15fdcbe97a7f44a24bdb3ff79e1e7 \ No newline at end of file diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index 61b57c98..01901b6b 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -19,8 +19,6 @@ - - diff --git a/src/CultMath/ErosionFrequencyBands.cs b/src/CultMath/ErosionFrequencyBands.cs deleted file mode 100644 index 3c63eb62..00000000 --- a/src/CultMath/ErosionFrequencyBands.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace CultMath; - -public readonly record struct ErosionBandSelection( - int ActiveOctaves, - float FinalOctaveWeight, - float FinestIncludedWavelength, - float UnresolvedHeightBound) -{ - public bool HasSamples => ActiveOctaves > 0; -} - -public static class ErosionFrequencyBands -{ - public static ErosionBandSelection Select( - float baseWavelength, - float sampleSpacing, - int maximumOctaves, - float lacunarity, - float baseAmplitude, - float gain, - float transitionRatio = 2.0f) - { - baseWavelength = math.max(baseWavelength, 1.0e-6f); - sampleSpacing = math.max(sampleSpacing, 0.0f); - lacunarity = math.max(lacunarity, 1.01f); - transitionRatio = math.max(transitionRatio, 1.01f); - maximumOctaves = math.min(math.max(maximumOctaves, 0), 16); - var nyquistWavelength = sampleSpacing * 2.0f; - var wavelength = baseWavelength; - var active = 0; - var finalWeight = 0.0f; - var finest = 0.0f; - - for (var octave = 0; octave < maximumOctaves; octave++) - { - var weight = nyquistWavelength <= 0.0f - ? 1.0f - : math.smoothstep(nyquistWavelength, nyquistWavelength * transitionRatio, wavelength); - if (weight <= 0.0f) break; - active++; - finalWeight = weight; - finest = wavelength; - wavelength /= lacunarity; - } - - var omittedAmplitude = 0.0f; - var amplitude = math.abs(baseAmplitude); - for (var octave = 0; octave < maximumOctaves; octave++) - { - if (octave >= active) omittedAmplitude += amplitude; - else if (octave == active - 1) omittedAmplitude += amplitude * (1.0f - finalWeight); - amplitude *= math.abs(gain); - } - - return new ErosionBandSelection(active, active > 0 ? finalWeight : 0.0f, finest, omittedAmplitude); - } -} diff --git a/src/CultMath/ErosionFrequencyBands.cs.meta b/src/CultMath/ErosionFrequencyBands.cs.meta deleted file mode 100644 index cae99643..00000000 --- a/src/CultMath/ErosionFrequencyBands.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 05e7969589dd09b43bd0fda672d25a0b \ No newline at end of file diff --git a/src/CultMath/PlanetaryField.cs b/src/CultMath/PlanetaryField.cs deleted file mode 100644 index 4d5596e5..00000000 --- a/src/CultMath/PlanetaryField.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; - -namespace CultMath; - -public readonly record struct PlanetaryQueryScale(float FootprintMeters, float MaximumUnresolvedHeight) -{ - public static PlanetaryQueryScale AtFootprint(float footprintMeters, float maximumUnresolvedHeight = float.PositiveInfinity) - => new PlanetaryQueryScale(footprintMeters, maximumUnresolvedHeight).Validate(); - - public PlanetaryQueryScale Validate() - { - if (!float.IsFinite(FootprintMeters) || FootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(FootprintMeters)); - if (float.IsNaN(MaximumUnresolvedHeight) || MaximumUnresolvedHeight < 0) throw new ArgumentOutOfRangeException(nameof(MaximumUnresolvedHeight)); - return this; - } -} - -public readonly record struct PlanetaryFieldDefinition( - ulong FieldVersion, - float Radius, - int Seed, - AdvancedErosionParameters Erosion) -{ - public static PlanetaryFieldDefinition Create( - ulong baseFieldVersion, - float radius, - int seed, - AdvancedErosionParameters erosion) - => new PlanetaryFieldDefinition(PlanetaryFieldIdentity.Compute(baseFieldVersion, radius, seed, erosion), radius, seed, erosion).Validate(); - - public PlanetaryFieldDefinition Validate() - { - if (FieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion)); - if (!float.IsFinite(Radius) || Radius <= 0) throw new ArgumentOutOfRangeException(nameof(Radius)); - if (!float.IsFinite(Erosion.Scale) || Erosion.Scale <= 0) throw new ArgumentOutOfRangeException(nameof(Erosion)); - return this; - } -} - -public static class PlanetaryFieldIdentity -{ - public static ulong Compute(ulong baseFieldVersion, float radius, int seed, in AdvancedErosionParameters erosion) - { - if (baseFieldVersion == 0) throw new ArgumentOutOfRangeException(nameof(baseFieldVersion)); - const ulong offset = 14695981039346656037UL; - const ulong prime = 1099511628211UL; - var hash = offset; - Add(baseFieldVersion); - Add((uint)BitConverter.SingleToInt32Bits(radius)); Add((uint)seed); - Add((uint)BitConverter.SingleToInt32Bits(erosion.Scale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Strength)); - Add((uint)BitConverter.SingleToInt32Bits(erosion.GullyWeight)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Detail)); - AddFloat4(erosion.Rounding); AddFloat4(erosion.Onset); - Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.x)); Add((uint)BitConverter.SingleToInt32Bits(erosion.AssumedSlope.y)); - Add((uint)BitConverter.SingleToInt32Bits(erosion.CellScale)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Normalization)); - Add((uint)erosion.Octaves); Add((uint)BitConverter.SingleToInt32Bits(erosion.Lacunarity)); Add((uint)BitConverter.SingleToInt32Bits(erosion.Gain)); - return hash == 0 ? 1 : hash; - - void Add(ulong value) => hash = unchecked((hash ^ value) * prime); - void AddFloat4(float4 value) - { - Add((uint)BitConverter.SingleToInt32Bits(value.x)); Add((uint)BitConverter.SingleToInt32Bits(value.y)); - Add((uint)BitConverter.SingleToInt32Bits(value.z)); Add((uint)BitConverter.SingleToInt32Bits(value.w)); - } - } -} - -/// -/// Application-supplied base evidence at one unit spherical direction. -/// FieldValue and FieldGradient drive erosion. RadialDisplacement and -/// RadialGradient describe the uneroded surface in metres. -/// -public readonly record struct PlanetaryBaseFieldSample( - float RadialDisplacement, - float3 RadialGradient, - float FieldValue, - float3 FieldGradient, - float FadeTarget); - -public readonly record struct PlanetarySurfaceSample( - ulong FieldVersion, - float3 UnitDirection, - float Radius, - float RadialDisplacement, - float3 TangentGradient, - float3 SurfaceNormal, - float Slope, - float Ridge, - float Gully, - float FinestResolvedWavelength, - float UnresolvedHeightBound) -{ - public float SurfaceRadius => Radius + RadialDisplacement; - public float3 PlanetLocalPosition => UnitDirection * SurfaceRadius; - public bool Meets(PlanetaryQueryScale scale) => UnresolvedHeightBound <= scale.MaximumUnresolvedHeight; -} - -public static class PlanetaryField -{ - public static PlanetarySurfaceSample Sample( - in PlanetaryFieldDefinition definition, - float3 unitDirection, - in PlanetaryBaseFieldSample baseSample, - in PlanetaryQueryScale scale) - { - definition.Validate(); - scale.Validate(); - PlanetaryTopology.ValidateDirection(unitDirection); - PlanetaryTopology.ValidateFinite(baseSample.RadialGradient, nameof(baseSample)); - PlanetaryTopology.ValidateFinite(baseSample.FieldGradient, nameof(baseSample)); - if (!float.IsFinite(baseSample.RadialDisplacement) || !float.IsFinite(baseSample.FieldValue) || !float.IsFinite(baseSample.FadeTarget)) - throw new ArgumentOutOfRangeException(nameof(baseSample)); - - var direction = math.normalize(unitDirection); - var radialGradient = Tangent(direction, baseSample.RadialGradient); - var fieldGradient = Tangent(direction, baseSample.FieldGradient); - var p = definition.Erosion; - var band = ErosionFrequencyBands.Select( - p.Scale * p.CellScale, - scale.FootprintMeters, - p.Octaves, - p.Lacunarity, - p.Strength * p.Scale, - p.Gain); - - var powers = new float3( - Pow4(MathF.Abs(direction.x)), - Pow4(MathF.Abs(direction.y)), - Pow4(MathF.Abs(direction.z))); - var powerSum = math.max(powers.x + powers.y + powers.z, 1.0e-6f); - var weights = powers / powerSum; - var world = direction * definition.Radius; - var seed = definition.Seed; - var xy = AdvancedErosionFilter.Sample( - world.xy + new float2(713 + seed * 0.754877666f, -291 + seed * 0.569840296f), - new float3(baseSample.FieldValue, fieldGradient.x, fieldGradient.y), baseSample.FadeTarget, p, band); - var yz = AdvancedErosionFilter.Sample( - world.yz + new float2(-431 + seed * 0.438289027f, 887 + seed * 0.328438163f), - new float3(baseSample.FieldValue, fieldGradient.y, fieldGradient.z), baseSample.FadeTarget, p, band); - var zx = AdvancedErosionFilter.Sample( - new float2(world.z, world.x) + new float2(197 + seed * 0.219783071f, 557 + seed * 0.145898034f), - new float3(baseSample.FieldValue, fieldGradient.z, fieldGradient.x), baseSample.FadeTarget, p, band); - - var erosionHeight = xy.Delta.x * weights.z + yz.Delta.x * weights.x + zx.Delta.x * weights.y; - var erosionGradient = new float3( - xy.Delta.y * weights.z + zx.Delta.z * weights.y, - xy.Delta.z * weights.z + yz.Delta.y * weights.x, - yz.Delta.z * weights.x + zx.Delta.y * weights.y); - var powerGradientX = Tangent(direction, new float3(4 * direction.x * direction.x * direction.x / definition.Radius, 0, 0)); - var powerGradientY = Tangent(direction, new float3(0, 4 * direction.y * direction.y * direction.y / definition.Radius, 0)); - var powerGradientZ = Tangent(direction, new float3(0, 0, 4 * direction.z * direction.z * direction.z / definition.Radius)); - var sumGradient = powerGradientX + powerGradientY + powerGradientZ; - var weightGradientX = (powerGradientX * powerSum - sumGradient * powers.x) / (powerSum * powerSum); - var weightGradientY = (powerGradientY * powerSum - sumGradient * powers.y) / (powerSum * powerSum); - var weightGradientZ = (powerGradientZ * powerSum - sumGradient * powers.z) / (powerSum * powerSum); - erosionGradient += yz.Delta.x * weightGradientX + zx.Delta.x * weightGradientY + xy.Delta.x * weightGradientZ; - var gradient = Tangent(direction, radialGradient + erosionGradient); - var ridgeEvidence = xy.RidgeMap * weights.z + yz.RidgeMap * weights.x + zx.RidgeMap * weights.y; - var ridge = math.saturate(ridgeEvidence * 0.5f + 0.5f); - var gully = math.saturate(0.5f - ridgeEvidence * 0.5f); - var displacement = baseSample.RadialDisplacement + erosionHeight; - - return new( - definition.FieldVersion, - direction, - definition.Radius, - displacement, - gradient, - PlanetaryTopology.SurfaceNormal(direction, gradient), - math.length(gradient), - ridge, - gully, - band.FinestIncludedWavelength, - band.UnresolvedHeightBound); - } - - public static void SampleBatch( - in PlanetaryFieldDefinition definition, - ReadOnlySpan directions, - in PlanetaryQueryScale scale, - TSource source, - Span destination) - where TSource : IPlanetaryBaseField - { - if (destination.Length < directions.Length) throw new ArgumentException("Destination is shorter than directions.", nameof(destination)); - for (var i = 0; i < directions.Length; i++) - { - var direction = math.normalize(directions[i]); - var baseSample = source.Sample(direction); - destination[i] = Sample(definition, direction, baseSample, scale); - } - } - - public static PlanetarySurfaceSample SamplePosition( - in PlanetaryFieldDefinition definition, - double3 planetLocalPosition, - in PlanetaryQueryScale scale, - TSource source) - where TSource : IPlanetaryBaseField - { - if (!double.IsFinite(planetLocalPosition.x) || !double.IsFinite(planetLocalPosition.y) || !double.IsFinite(planetLocalPosition.z)) - throw new ArgumentOutOfRangeException(nameof(planetLocalPosition)); - var length = Math.Sqrt(planetLocalPosition.x * planetLocalPosition.x + planetLocalPosition.y * planetLocalPosition.y + planetLocalPosition.z * planetLocalPosition.z); - if (length < 1.0e-12) throw new ArgumentOutOfRangeException(nameof(planetLocalPosition)); - var direction = new float3((float)(planetLocalPosition.x / length), (float)(planetLocalPosition.y / length), (float)(planetLocalPosition.z / length)); - return Sample(definition, direction, source.Sample(direction), scale); - } - - private static float3 Tangent(float3 direction, float3 gradient) => gradient - direction * math.dot(gradient, direction); - private static float Pow4(float value) { var square = value * value; return square * square; } -} - -public interface IPlanetaryBaseField -{ - PlanetaryBaseFieldSample Sample(float3 unitDirection); -} diff --git a/src/CultMath/PlanetaryField.cs.meta b/src/CultMath/PlanetaryField.cs.meta deleted file mode 100644 index 3e83f637..00000000 --- a/src/CultMath/PlanetaryField.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b61d6354e3aab054e94721e77e619bfa \ No newline at end of file diff --git a/src/CultMath/PlanetaryGpuPages.cs b/src/CultMath/PlanetaryGpuPages.cs deleted file mode 100644 index 024d26b0..00000000 --- a/src/CultMath/PlanetaryGpuPages.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; - -namespace CultMath; - -public readonly record struct PlanetaryGpuPageInput(float4 DirectionRadius, float4 Sampling); - -public readonly record struct PlanetaryGpuPageMetadata(float4 Address, float4 Layout, float4 Bounds, float4 State); - -public readonly record struct PlanetaryGpuPageContent( - PlanetaryPageLayout Layout, - PlanetaryGpuPageInput[] Inputs, - float SampleSpacing, - float ParentSampleSpacing); - -public static class PlanetaryGpuPageBuilder -{ - public static PlanetaryGpuPageContent BuildContent(PlanetaryPageLayout layout, float radius) - { - layout.Validate(); - if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); - var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * radius; - var parentSpacing = layout.Tile.Level == 0 - ? 0 - : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * radius; - var inputs = new PlanetaryGpuPageInput[layout.StorageSize * layout.StorageSize]; - for (var y = 0; y < layout.StorageSize; y++) - for (var x = 0; x < layout.StorageSize; x++) - { - var direction = PlanetaryPageSampling.Direction(layout, x, y); - inputs[y * layout.StorageSize + x] = new( - new float4(direction, radius), - new float4(spacing, parentSpacing, 0, 0)); - } - return new(layout, inputs, spacing, parentSpacing); - } - - public static PlanetaryGpuPageMetadata Metadata( - in PlanetaryGpuPageContent content, - int outputOffset, - float blend, - float4 bounds = default) - { - content.Layout.Validate(); - if (content.Inputs == null || content.Inputs.Length != content.Layout.StorageSize * content.Layout.StorageSize) - throw new ArgumentException("GPU page content does not match its layout.", nameof(content)); - if (outputOffset < 0) throw new ArgumentOutOfRangeException(nameof(outputOffset)); - if (!float.IsFinite(blend) || blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(blend)); - var tile = content.Layout.Tile; - return new( - new float4((int)tile.Face, tile.Level, tile.X, tile.Y), - new float4(outputOffset, content.Layout.StorageSize, content.Layout.InteriorSize, content.Layout.BorderSize), - bounds, - new float4(1, blend, PlanetaryPageSampling.NominalAngularTexelSize(content.Layout), content.SampleSpacing)); - } -} diff --git a/src/CultMath/PlanetaryGpuPages.cs.meta b/src/CultMath/PlanetaryGpuPages.cs.meta deleted file mode 100644 index 7afe847c..00000000 --- a/src/CultMath/PlanetaryGpuPages.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 42173c60766948a4e8c376687033532e \ No newline at end of file diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs b/src/CultMath/PlanetaryMapTileEncoding.cs deleted file mode 100644 index ec556617..00000000 --- a/src/CultMath/PlanetaryMapTileEncoding.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace CultMath; - -public static class PlanetaryMapTileEncoding -{ - public const uint FormatVersion = 1; - private static readonly byte[] Magic = Encoding.ASCII.GetBytes("CMPT"); - - public static void Write(Stream stream, in PlanetarySurfaceMapTile tile, bool leaveOpen = false) - { - if (stream == null || !stream.CanWrite) throw new ArgumentException("A writable stream is required.", nameof(stream)); - tile.Key.Validate(); - if (tile.Samples == null || tile.Validity == null || tile.Samples.Length != tile.Validity.Length) - throw new ArgumentException("Tile samples and validity must have equal lengths.", nameof(tile)); - var expectedCount = tile.Key.Layout.StorageSize * tile.Key.Layout.StorageSize; - if (tile.Samples.Length != expectedCount) throw new ArgumentException("Tile payload does not match its layout.", nameof(tile)); - - using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen); - writer.Write(Magic); - writer.Write(FormatVersion); - writer.Write(tile.Key.FieldVersion); - writer.Write(tile.Key.ProjectionVersion); - writer.Write(tile.Key.LayerId); - var projection = tile.Key.Layout.Projection; - writer.Write((int)projection.Kind); - writer.Write(projection.CenterLongitude); - writer.Write(projection.CenterLatitude); - writer.Write(projection.Scale); - writer.Write(tile.Key.Layout.Level); - writer.Write(tile.Key.Layout.X); - writer.Write(tile.Key.Layout.Y); - writer.Write(tile.Key.Layout.InteriorSize); - writer.Write(tile.Key.Layout.BorderSize); - writer.Write(tile.Key.QueryScale.FootprintMeters); - writer.Write(tile.Key.QueryScale.MaximumUnresolvedHeight); - writer.Write(tile.Samples.Length); - for (var i = 0; i < tile.Samples.Length; i++) - { - writer.Write(tile.Validity[i]); - if (!tile.Validity[i]) continue; - WriteSample(writer, tile.Samples[i], tile.Key.FieldVersion); - } - } - - public static PlanetarySurfaceMapTile Read(Stream stream, bool leaveOpen = false) - { - if (stream == null || !stream.CanRead) throw new ArgumentException("A readable stream is required.", nameof(stream)); - using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen); - var magic = reader.ReadBytes(Magic.Length); - if (magic.Length != Magic.Length || !magic.AsSpan().SequenceEqual(Magic)) throw new InvalidDataException("Not a CultMath planetary tile."); - var version = reader.ReadUInt32(); - if (version != FormatVersion) throw new InvalidDataException($"Unsupported planetary tile version {version}."); - var fieldVersion = reader.ReadUInt64(); - var projectionVersion = reader.ReadUInt32(); - var layerId = reader.ReadUInt32(); - var kindValue = reader.ReadInt32(); - if (!Enum.IsDefined(typeof(PlanetaryProjectionKind), kindValue)) throw new InvalidDataException("Unknown planetary projection kind."); - var projection = new PlanetaryProjectionParameters( - (PlanetaryProjectionKind)kindValue, - reader.ReadDouble(), reader.ReadDouble(), reader.ReadDouble()).Validate(); - var layout = new PlanetaryMapTileLayout( - projection, - reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), - reader.ReadInt32(), reader.ReadInt32()).Validate(); - var scale = new PlanetaryQueryScale(reader.ReadSingle(), reader.ReadSingle()).Validate(); - var key = new PlanetaryMapTileKey(fieldVersion, projectionVersion, layerId, layout, scale).Validate(); - var count = reader.ReadInt32(); - if (count != layout.StorageSize * layout.StorageSize) throw new InvalidDataException("Planetary tile sample count does not match layout."); - var samples = new PlanetarySurfaceSample[count]; - var validity = new bool[count]; - for (var i = 0; i < count; i++) - { - validity[i] = reader.ReadBoolean(); - if (validity[i]) samples[i] = ReadSample(reader, fieldVersion); - } - if (stream.CanSeek && stream.Position != stream.Length) throw new InvalidDataException("Planetary tile has trailing bytes."); - return new(key, samples, validity); - } - - private static void WriteSample(BinaryWriter writer, PlanetarySurfaceSample sample, ulong fieldVersion) - { - if (sample.FieldVersion != fieldVersion) throw new ArgumentException("Sample field version does not match tile identity."); - Write(writer, sample.UnitDirection); - writer.Write(sample.Radius); - writer.Write(sample.RadialDisplacement); - Write(writer, sample.TangentGradient); - Write(writer, sample.SurfaceNormal); - writer.Write(sample.Slope); - writer.Write(sample.Ridge); - writer.Write(sample.Gully); - writer.Write(sample.FinestResolvedWavelength); - writer.Write(sample.UnresolvedHeightBound); - } - - private static PlanetarySurfaceSample ReadSample(BinaryReader reader, ulong fieldVersion) - { - var direction = ReadFloat3(reader); - var radius = reader.ReadSingle(); - var displacement = reader.ReadSingle(); - var gradient = ReadFloat3(reader); - var normal = ReadFloat3(reader); - return new( - fieldVersion, direction, radius, displacement, gradient, normal, - reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), - reader.ReadSingle(), reader.ReadSingle()); - } - - private static void Write(BinaryWriter writer, float3 value) - { - writer.Write(value.x); writer.Write(value.y); writer.Write(value.z); - } - - private static float3 ReadFloat3(BinaryReader reader) => new(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); -} diff --git a/src/CultMath/PlanetaryMapTileEncoding.cs.meta b/src/CultMath/PlanetaryMapTileEncoding.cs.meta deleted file mode 100644 index 76484f65..00000000 --- a/src/CultMath/PlanetaryMapTileEncoding.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6898f6de57df20646a0b9077bb923592 \ No newline at end of file diff --git a/src/CultMath/PlanetaryPages.cs b/src/CultMath/PlanetaryPages.cs deleted file mode 100644 index 699a5c1d..00000000 --- a/src/CultMath/PlanetaryPages.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; - -namespace CultMath; - -public readonly record struct PlanetaryPageLayout(PlanetaryTileAddress Tile, int InteriorSize, int BorderSize) -{ - public int StorageSize => checked(InteriorSize + BorderSize * 2); - - public PlanetaryPageLayout Validate() - { - if (InteriorSize < 2) throw new ArgumentOutOfRangeException(nameof(InteriorSize)); - if (BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(BorderSize)); - return this; - } -} - -public readonly record struct PlanetaryPageSummary( - float MinimumHeight, - float MaximumHeight, - float MaximumSlope, - float UnresolvedHeightBound, - float AngularTexelSize); - -public readonly record struct PlanetaryPageSample(float Height, float3 TangentGradient, float Ridge, float Gully); - -public static class PlanetaryPageSampling -{ - public static float3 Direction(PlanetaryPageLayout page, int storageX, int storageY) - { - page.Validate(); - if ((uint)storageX >= (uint)page.StorageSize || (uint)storageY >= (uint)page.StorageSize) - throw new ArgumentOutOfRangeException(nameof(storageX)); - var localU = (storageX - page.BorderSize) / (double)(page.InteriorSize - 1); - var localV = (storageY - page.BorderSize) / (double)(page.InteriorSize - 1); - return DirectionAtLocal(page, localU, localV); - } - - public static float3 DirectionAtLocal(PlanetaryPageLayout page, double localU, double localV) - { - page.Validate(); - if (!double.IsFinite(localU) || !double.IsFinite(localV)) throw new ArgumentOutOfRangeException(nameof(localU)); - var count = page.Tile.AxisTileCount; - return PlanetaryTopology.Direction(new( - page.Tile.Face, - -1.0 + 2.0 * (page.Tile.X + localU) / count, - -1.0 + 2.0 * (page.Tile.Y + localV) / count)); - } - - public static float AngularTexelSize(PlanetaryPageLayout page) - { - var center = page.BorderSize + (page.InteriorSize - 1) / 2; - var a = Direction(page, center, center); - var b = Direction(page, Math.Min(center + 1, page.StorageSize - 1), center); - return MathF.Acos(math.clamp(math.dot(a, b), -1.0f, 1.0f)); - } - - public static float NominalAngularTexelSize(PlanetaryPageLayout page) - { - page.Validate(); - return MathF.PI * 0.5f / (page.Tile.AxisTileCount * (page.InteriorSize - 1)); - } - - public static PlanetaryPageSample Bilinear( - ReadOnlySpan samples, - PlanetaryPageLayout page, - float2 local) - { - page.Validate(); - if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples)); - var texel = local * (page.InteriorSize - 1) + page.BorderSize; - var x0 = Math.Clamp((int)MathF.Floor(texel.x), 0, page.StorageSize - 1); - var y0 = Math.Clamp((int)MathF.Floor(texel.y), 0, page.StorageSize - 1); - var x1 = Math.Min(x0 + 1, page.StorageSize - 1); - var y1 = Math.Min(y0 + 1, page.StorageSize - 1); - var tx = math.frac(texel.x); - var ty = math.frac(texel.y); - var a = Lerp(samples[y0 * page.StorageSize + x0], samples[y0 * page.StorageSize + x1], tx); - var b = Lerp(samples[y1 * page.StorageSize + x0], samples[y1 * page.StorageSize + x1], tx); - return Lerp(a, b, ty); - } - - public static PlanetaryPageSummary Summarize(ReadOnlySpan samples, PlanetaryPageLayout page, float unresolvedHeightBound) - { - page.Validate(); - if (samples.Length != page.StorageSize * page.StorageSize) throw new ArgumentException("Page sample count does not match layout.", nameof(samples)); - var min = float.PositiveInfinity; - var max = float.NegativeInfinity; - var maxSlope = 0.0f; - foreach (var sample in samples) - { - min = MathF.Min(min, sample.Height); - max = MathF.Max(max, sample.Height); - maxSlope = MathF.Max(maxSlope, math.length(sample.TangentGradient)); - } - return new(min, max, maxSlope, MathF.Max(unresolvedHeightBound, 0), AngularTexelSize(page)); - } - - private static PlanetaryPageSample Lerp(PlanetaryPageSample a, PlanetaryPageSample b, float t) => new( - math.lerp(a.Height, b.Height, t), - math.lerp(a.TangentGradient, b.TangentGradient, t), - math.lerp(a.Ridge, b.Ridge, t), - math.lerp(a.Gully, b.Gully, t)); -} - -public readonly record struct PlanetaryBakedPage( - PlanetaryPageLayout Layout, - PlanetaryPageSample[] Samples, - PlanetaryPageSummary Summary); - -public static class PlanetaryPageBaker -{ - public static PlanetaryBakedPage Bake( - in PlanetaryFieldDefinition field, - PlanetaryPageLayout layout, - TSource source) - where TSource : IPlanetaryBaseField - { - layout.Validate(); field.Validate(); - var spacing = PlanetaryPageSampling.NominalAngularTexelSize(layout) * field.Radius; - var parentSpacing = layout.Tile.Level == 0 - ? 0 - : PlanetaryPageSampling.NominalAngularTexelSize(new(layout.Tile.Parent(), layout.InteriorSize, layout.BorderSize)) * field.Radius; - var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize]; - var unresolved = 0.0f; - for (var y = 0; y < layout.StorageSize; y++) - for (var x = 0; x < layout.StorageSize; x++) - { - var direction = PlanetaryPageSampling.Direction(layout, x, y); - var baseSample = source.Sample(direction); - var child = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(spacing)); - var height = child.RadialDisplacement; - var gradient = child.TangentGradient; - if (parentSpacing > 0) - { - var parent = PlanetaryField.Sample(field, direction, baseSample, PlanetaryQueryScale.AtFootprint(parentSpacing)); - height -= parent.RadialDisplacement; - gradient -= parent.TangentGradient; - } - samples[y * layout.StorageSize + x] = new(height, gradient, child.Ridge, child.Gully); - unresolved = MathF.Max(unresolved, child.UnresolvedHeightBound); - } - return new(layout, samples, PlanetaryPageSampling.Summarize(samples, layout, unresolved)); - } -} - -public readonly record struct PlanetaryResidentPage(PlanetaryBakedPage Page, float Blend) -{ - public PlanetaryResidentPage Validate() - { - if (!float.IsFinite(Blend) || Blend is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(Blend)); - Page.Layout.Validate(); - if (Page.Samples.Length != Page.Layout.StorageSize * Page.Layout.StorageSize) throw new ArgumentException("Page payload does not match layout."); - return this; - } -} - -public static class PlanetaryPageSetSampling -{ - public static bool TrySample(ReadOnlySpan pages, float3 direction, out PlanetaryPageSample sample) - { - PlanetaryTopology.ValidateDirection(direction); - var height = 0.0f; - var gradient = float3.zero; - var ridge = 0.0f; - var gully = 0.0f; - var found = false; - var deepestLevel = -1; - foreach (var resident in pages) - { - resident.Validate(); - if (!PlanetaryTopology.TryLocalCoordinate(direction, resident.Page.Layout.Tile, out var local)) continue; - var contribution = PlanetaryPageSampling.Bilinear(resident.Page.Samples, resident.Page.Layout, local); - var blend = math.saturate(resident.Blend); - height += contribution.Height * blend; - gradient += contribution.TangentGradient * blend; - if (resident.Page.Layout.Tile.Level >= deepestLevel) - { - ridge = found ? math.lerp(ridge, contribution.Ridge, blend) : contribution.Ridge; - gully = found ? math.lerp(gully, contribution.Gully, blend) : contribution.Gully; - deepestLevel = resident.Page.Layout.Tile.Level; - } - found = true; - } - sample = new(height, gradient, ridge, gully); - return found; - } -} diff --git a/src/CultMath/PlanetaryPages.cs.meta b/src/CultMath/PlanetaryPages.cs.meta deleted file mode 100644 index 91fdc27d..00000000 --- a/src/CultMath/PlanetaryPages.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7af9a0354faa56348a2e07188739a0ae \ No newline at end of file diff --git a/src/CultMath/PlanetaryPatch.cs b/src/CultMath/PlanetaryPatch.cs deleted file mode 100644 index 30c852ff..00000000 --- a/src/CultMath/PlanetaryPatch.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Linq; - -namespace CultMath; - -public readonly record struct PlanetaryPatchVertex(float2 LocalCoordinate, float3 UnitDirection); - -public readonly record struct PlanetaryPatchMesh( - PlanetaryCubeFace Face, - int CellsPerAxis, - PlanetaryPatchVertex[] Vertices, - int[] Indices); - -public static class PlanetaryPatch -{ - public static PlanetaryPatchMesh CreateFace(PlanetaryCubeFace face, int cellsPerAxis) - { - if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face)); - if (cellsPerAxis < 1 || cellsPerAxis > 4096) throw new ArgumentOutOfRangeException(nameof(cellsPerAxis)); - var axisVertices = cellsPerAxis + 1; - var vertices = new PlanetaryPatchVertex[checked(axisVertices * axisVertices)]; - for (var y = 0; y < axisVertices; y++) - for (var x = 0; x < axisVertices; x++) - { - var local = new float2(x / (float)cellsPerAxis, y / (float)cellsPerAxis); - vertices[y * axisVertices + x] = new(local, PlanetaryTopology.Direction(new(face, local.x * 2 - 1, local.y * 2 - 1))); - } - var indices = new int[checked(cellsPerAxis * cellsPerAxis * 6)]; - var write = 0; - for (var y = 0; y < cellsPerAxis; y++) - for (var x = 0; x < cellsPerAxis; x++) - { - var lowerLeft = y * axisVertices + x; - var lowerRight = lowerLeft + 1; - var upperLeft = lowerLeft + axisVertices; - var upperRight = upperLeft + 1; - indices[write++] = lowerLeft; indices[write++] = lowerRight; indices[write++] = upperRight; - indices[write++] = lowerLeft; indices[write++] = upperRight; indices[write++] = upperLeft; - } - return new(face, cellsPerAxis, vertices, indices); - } - - public static PlanetaryPatchMesh[] CreateCubeSphere(int cellsPerAxis) - => ((PlanetaryCubeFace[])Enum.GetValues(typeof(PlanetaryCubeFace))).Select(face => CreateFace(face, cellsPerAxis)).ToArray(); -} diff --git a/src/CultMath/PlanetaryPatch.cs.meta b/src/CultMath/PlanetaryPatch.cs.meta deleted file mode 100644 index efd8156a..00000000 --- a/src/CultMath/PlanetaryPatch.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 04651571f7d240a418c1f6aafa217fa6 \ No newline at end of file diff --git a/src/CultMath/PlanetaryProjection.cs b/src/CultMath/PlanetaryProjection.cs deleted file mode 100644 index a82ebfdb..00000000 --- a/src/CultMath/PlanetaryProjection.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; - -namespace CultMath; - -public enum PlanetaryProjectionKind -{ - Equirectangular, - WebMercator, - EqualEarth, - Orthographic, - AzimuthalEquidistant, - AzimuthalEqualArea, - CubeAtlas, - LocalTangent, -} - -public readonly record struct PlanetaryProjectionParameters( - PlanetaryProjectionKind Kind, - double CenterLongitude = 0, - double CenterLatitude = 0, - double Scale = 1) -{ - public PlanetaryProjectionParameters Validate() - { - if (!double.IsFinite(CenterLongitude) || !double.IsFinite(CenterLatitude) || !double.IsFinite(Scale) || Scale <= 0) - throw new ArgumentOutOfRangeException(nameof(Scale)); - if (CenterLatitude is < -Math.PI / 2 or > Math.PI / 2) throw new ArgumentOutOfRangeException(nameof(CenterLatitude)); - return this; - } -} - -public static class PlanetaryProjection -{ - private const double EqualEarthA1 = 1.340264; - private const double EqualEarthA2 = -0.081106; - private const double EqualEarthA3 = 0.000893; - private const double EqualEarthA4 = 0.003796; - private static readonly double EqualEarthM = Math.Sqrt(3) / 2; - private static readonly double EqualEarthXMax = Math.PI / (EqualEarthM * EqualEarthA1); - private static readonly double EqualEarthYMax = EqualEarthY(Math.Asin(EqualEarthM)); - public static readonly double WebMercatorMaximumLatitude = Math.Atan(Math.Sinh(Math.PI)); - - public static bool TryForward(float3 direction, PlanetaryProjectionParameters parameters, out double2 coordinate) - { - parameters.Validate(); - PlanetaryTopology.ValidateDirection(direction); - var d = math.normalize(direction); - if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas) - { - var face = PlanetaryTopology.FaceCoordinate(d); - var index = (int)face.Face; - var column = index % 3; - var row = index / 3; - coordinate = new( - (-1 + (column + (face.U + 1) * 0.5) * (2.0 / 3.0)) / parameters.Scale, - (1 - (row + (face.V + 1) * 0.5)) / parameters.Scale); - return true; - } - - var longitude = Math.Atan2(d.y, d.x); - var latitude = Math.Asin(Math.Clamp(d.z, -1, 1)); - var deltaLongitude = WrapLongitude(longitude - parameters.CenterLongitude); - var centered = CenteredForward(deltaLongitude, latitude, parameters, out coordinate); - coordinate /= parameters.Scale; - return centered; - } - - public static bool TryInverse(double2 coordinate, PlanetaryProjectionParameters parameters, out float3 direction) - { - parameters.Validate(); - if (!double.IsFinite(coordinate.x) || !double.IsFinite(coordinate.y)) - { - direction = float3.zero; - return false; - } - coordinate *= parameters.Scale; - if (parameters.Kind == PlanetaryProjectionKind.CubeAtlas) - return TryInverseCubeAtlas(coordinate, out direction); - - if (!CenteredInverse(coordinate, parameters, out var deltaLongitude, out var latitude)) - { - direction = float3.zero; - return false; - } - var longitude = WrapLongitude(deltaLongitude + parameters.CenterLongitude); - var cosLatitude = Math.Cos(latitude); - direction = math.normalize(new float3( - (float)(cosLatitude * Math.Cos(longitude)), - (float)(cosLatitude * Math.Sin(longitude)), - (float)Math.Sin(latitude))); - return true; - } - - private static bool CenteredForward(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate) - { - switch (p.Kind) - { - case PlanetaryProjectionKind.Equirectangular: - coordinate = new(longitude / Math.PI, latitude / (Math.PI * 0.5)); - return true; - case PlanetaryProjectionKind.WebMercator: - if (Math.Abs(latitude) > WebMercatorMaximumLatitude) { coordinate = double2.zero; return false; } - coordinate = new(longitude / Math.PI, Math.Asinh(Math.Tan(latitude)) / Math.PI); - return true; - case PlanetaryProjectionKind.EqualEarth: - { - var theta = Math.Asin(EqualEarthM * Math.Sin(latitude)); - var theta2 = theta * theta; - var theta6 = theta2 * theta2 * theta2; - var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2)); - coordinate = new(longitude * Math.Cos(theta) / denominator / EqualEarthXMax, EqualEarthY(theta) / EqualEarthYMax); - return true; - } - case PlanetaryProjectionKind.Orthographic: - case PlanetaryProjectionKind.AzimuthalEquidistant: - case PlanetaryProjectionKind.AzimuthalEqualArea: - case PlanetaryProjectionKind.LocalTangent: - return ForwardAzimuthal(longitude, latitude, p, out coordinate); - default: - throw new ArgumentOutOfRangeException(nameof(p)); - } - } - - private static bool CenteredInverse(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude) - { - longitude = latitude = 0; - switch (p.Kind) - { - case PlanetaryProjectionKind.Equirectangular: - if (Math.Abs(coordinate.y) > 1) return false; - longitude = coordinate.x * Math.PI; latitude = coordinate.y * Math.PI * 0.5; return Math.Abs(coordinate.x) <= 1; - case PlanetaryProjectionKind.WebMercator: - if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false; - longitude = coordinate.x * Math.PI; latitude = Math.Atan(Math.Sinh(coordinate.y * Math.PI)); return true; - case PlanetaryProjectionKind.EqualEarth: - { - if (Math.Abs(coordinate.x) > 1 || Math.Abs(coordinate.y) > 1) return false; - var targetY = coordinate.y * EqualEarthYMax; - var theta = targetY / EqualEarthA1; - for (var i = 0; i < 8; i++) - { - var theta2 = theta * theta; - var theta6 = theta2 * theta2 * theta2; - var f = EqualEarthY(theta) - targetY; - var derivative = EqualEarthA1 + 3 * EqualEarthA2 * theta2 + theta6 * (7 * EqualEarthA3 + 9 * EqualEarthA4 * theta2); - theta -= f / derivative; - } - var sinLatitude = Math.Sin(theta) / EqualEarthM; - if (Math.Abs(sinLatitude) > 1.0000000001) return false; - latitude = Math.Asin(Math.Clamp(sinLatitude, -1, 1)); - var thetaSquared = theta * theta; - var thetaSixth = thetaSquared * thetaSquared * thetaSquared; - var denominator = EqualEarthM * (EqualEarthA1 + 3 * EqualEarthA2 * thetaSquared + thetaSixth * (7 * EqualEarthA3 + 9 * EqualEarthA4 * thetaSquared)); - longitude = coordinate.x * EqualEarthXMax * denominator / Math.Cos(theta); - return Math.Abs(longitude) <= Math.PI + 1.0e-10; - } - case PlanetaryProjectionKind.Orthographic: - case PlanetaryProjectionKind.AzimuthalEquidistant: - case PlanetaryProjectionKind.AzimuthalEqualArea: - case PlanetaryProjectionKind.LocalTangent: - return InverseAzimuthal(coordinate, p, out longitude, out latitude); - default: - throw new ArgumentOutOfRangeException(nameof(p)); - } - } - - private static bool ForwardAzimuthal(double longitude, double latitude, PlanetaryProjectionParameters p, out double2 coordinate) - { - var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude); - var sin = Math.Sin(latitude); var cos = Math.Cos(latitude); var cosLon = Math.Cos(longitude); - var cosC = Math.Clamp(sin0 * sin + cos0 * cos * cosLon, -1, 1); - var rawX = cos * Math.Sin(longitude); - var rawY = cos0 * sin - sin0 * cos * cosLon; - double factor; - switch (p.Kind) - { - case PlanetaryProjectionKind.Orthographic: - if (cosC < 0) { coordinate = double2.zero; return false; } - factor = 1; break; - case PlanetaryProjectionKind.AzimuthalEquidistant: - { - var c = Math.Acos(cosC); factor = c < 1.0e-12 ? 1 : c / Math.Sin(c); factor /= Math.PI; break; - } - case PlanetaryProjectionKind.AzimuthalEqualArea: - factor = Math.Sqrt(2 / Math.Max(1 + cosC, 1.0e-20)) * 0.5; break; - case PlanetaryProjectionKind.LocalTangent: - if (cosC <= 0) { coordinate = double2.zero; return false; } - factor = 1 / cosC; break; - default: throw new ArgumentOutOfRangeException(nameof(p)); - } - coordinate = new(rawX * factor, rawY * factor); - return true; - } - - private static bool InverseAzimuthal(double2 coordinate, PlanetaryProjectionParameters p, out double longitude, out double latitude) - { - longitude = latitude = 0; - var rho = Math.Sqrt(coordinate.x * coordinate.x + coordinate.y * coordinate.y); - double c; - switch (p.Kind) - { - case PlanetaryProjectionKind.Orthographic: if (rho > 1) return false; c = Math.Asin(Math.Clamp(rho, 0, 1)); break; - case PlanetaryProjectionKind.AzimuthalEquidistant: if (rho > 1) return false; c = rho * Math.PI; break; - case PlanetaryProjectionKind.AzimuthalEqualArea: if (rho > 1) return false; c = 2 * Math.Asin(Math.Clamp(rho, 0, 1)); break; - case PlanetaryProjectionKind.LocalTangent: c = Math.Atan(rho); break; - default: throw new ArgumentOutOfRangeException(nameof(p)); - } - if (rho < 1.0e-12) { latitude = p.CenterLatitude; return true; } - var sinC = Math.Sin(c); var cosC = Math.Cos(c); - var sin0 = Math.Sin(p.CenterLatitude); var cos0 = Math.Cos(p.CenterLatitude); - latitude = Math.Asin(Math.Clamp(cosC * sin0 + coordinate.y * sinC * cos0 / rho, -1, 1)); - longitude = Math.Atan2(coordinate.x * sinC, rho * cos0 * cosC - coordinate.y * sin0 * sinC); - return true; - } - - private static bool TryInverseCubeAtlas(double2 coordinate, out float3 direction) - { - direction = float3.zero; - if (coordinate.x is < -1 or > 1 || coordinate.y is < -1 or > 1) return false; - var column = Math.Min((int)((coordinate.x + 1) * 1.5), 2); - var row = Math.Min((int)((1 - coordinate.y)), 1); - var u = ((coordinate.x + 1) * 1.5 - column) * 2 - 1; - var v = ((1 - coordinate.y) - row) * 2 - 1; - direction = PlanetaryTopology.Direction(new((PlanetaryCubeFace)(row * 3 + column), u, v)); - return true; - } - - private static double EqualEarthY(double theta) - { - var theta2 = theta * theta; - var theta6 = theta2 * theta2 * theta2; - return theta * (EqualEarthA1 + EqualEarthA2 * theta2 + theta6 * (EqualEarthA3 + EqualEarthA4 * theta2)); - } - - private static double WrapLongitude(double longitude) - { - longitude %= Math.PI * 2; - if (longitude > Math.PI) longitude -= Math.PI * 2; - if (longitude < -Math.PI) longitude += Math.PI * 2; - return longitude; - } -} - -public readonly record struct PlanetaryMapTileLayout( - PlanetaryProjectionParameters Projection, - int Level, - int X, - int Y, - int InteriorSize, - int BorderSize) -{ - public int StorageSize => checked(InteriorSize + BorderSize * 2); - - public PlanetaryMapTileLayout Validate() - { - Projection.Validate(); - if (Level is < 0 or > 30) throw new ArgumentOutOfRangeException(nameof(Level)); - var count = 1 << Level; - if ((uint)X >= (uint)count || (uint)Y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(X)); - if (InteriorSize < 2 || BorderSize < 0 || BorderSize >= InteriorSize) throw new ArgumentOutOfRangeException(nameof(InteriorSize)); - return this; - } -} - -public static class PlanetaryMapTileSampling -{ - public static bool TryDirection(PlanetaryMapTileLayout tile, int storageX, int storageY, out float3 direction) - { - tile.Validate(); - if ((uint)storageX >= (uint)tile.StorageSize || (uint)storageY >= (uint)tile.StorageSize) throw new ArgumentOutOfRangeException(nameof(storageX)); - var count = 1 << tile.Level; - var localU = (storageX - tile.BorderSize) / (double)(tile.InteriorSize - 1); - var localV = (storageY - tile.BorderSize) / (double)(tile.InteriorSize - 1); - var map = new double2(-1 + 2 * (tile.X + localU) / count, 1 - 2 * (tile.Y + localV) / count); - return PlanetaryProjection.TryInverse(map, tile.Projection, out direction); - } -} - -public readonly record struct PlanetaryMapTileKey( - ulong FieldVersion, - uint ProjectionVersion, - uint LayerId, - PlanetaryMapTileLayout Layout, - PlanetaryQueryScale QueryScale) -{ - public PlanetaryMapTileKey Validate() - { - if (FieldVersion == 0 || ProjectionVersion == 0) throw new ArgumentOutOfRangeException(nameof(FieldVersion)); - Layout.Validate(); QueryScale.Validate(); - return this; - } -} - -public readonly record struct PlanetarySurfaceMapTile( - PlanetaryMapTileKey Key, - PlanetarySurfaceSample[] Samples, - bool[] Validity); - -public static class PlanetaryMapTileBaker -{ - public static PlanetarySurfaceMapTile Bake( - in PlanetaryFieldDefinition field, - PlanetaryMapTileKey key, - TSource source) - where TSource : IPlanetaryBaseField - { - field.Validate(); key.Validate(); - if (field.FieldVersion != key.FieldVersion) throw new ArgumentException("Tile field version does not match definition.", nameof(key)); - var count = key.Layout.StorageSize * key.Layout.StorageSize; - var samples = new PlanetarySurfaceSample[count]; - var validity = new bool[count]; - for (var y = 0; y < key.Layout.StorageSize; y++) - for (var x = 0; x < key.Layout.StorageSize; x++) - { - var index = y * key.Layout.StorageSize + x; - if (!PlanetaryMapTileSampling.TryDirection(key.Layout, x, y, out var direction)) continue; - samples[index] = PlanetaryField.Sample(field, direction, source.Sample(direction), key.QueryScale); - validity[index] = true; - } - return new(key, samples, validity); - } -} diff --git a/src/CultMath/PlanetaryProjection.cs.meta b/src/CultMath/PlanetaryProjection.cs.meta deleted file mode 100644 index 7b1e6654..00000000 --- a/src/CultMath/PlanetaryProjection.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 5021202cf626f7442a57961255a8a217 \ No newline at end of file diff --git a/src/CultMath/PlanetaryQueries.cs b/src/CultMath/PlanetaryQueries.cs deleted file mode 100644 index 54a9a3e9..00000000 --- a/src/CultMath/PlanetaryQueries.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; - -namespace CultMath; - -public readonly record struct PlanetaryRadialBounds(float MinimumDisplacement, float MaximumDisplacement) -{ - public PlanetaryRadialBounds Validate() - { - if (!float.IsFinite(MinimumDisplacement) || !float.IsFinite(MaximumDisplacement) || MinimumDisplacement > MaximumDisplacement) - throw new ArgumentOutOfRangeException(nameof(MinimumDisplacement)); - return this; - } -} - -public readonly record struct PlanetaryRayHit( - double Distance, - double3 PlanetLocalPosition, - PlanetarySurfaceSample Surface); - -public readonly record struct PlanetaryPathSample(double Distance, PlanetarySurfaceSample Surface); - -public readonly record struct PlanetaryRegionSummary( - float MinimumDisplacement, - float MaximumDisplacement, - float MaximumSlope, - float MaximumUnresolvedHeight); - -public static class PlanetaryQueries -{ - public static bool TryIntersectRay( - in PlanetaryFieldDefinition field, - double3 rayOrigin, - double3 rayDirection, - double maximumDistance, - in PlanetaryQueryScale scale, - in PlanetaryRadialBounds bounds, - TSource source, - out PlanetaryRayHit hit) - where TSource : IPlanetaryBaseField - { - field.Validate(); scale.Validate(); bounds.Validate(); - ValidateFinite(rayOrigin, nameof(rayOrigin)); ValidateFinite(rayDirection, nameof(rayDirection)); - if (!double.IsFinite(maximumDistance) || maximumDistance <= 0) throw new ArgumentOutOfRangeException(nameof(maximumDistance)); - var directionLength = Length(rayDirection); - if (directionLength < 1.0e-15) throw new ArgumentOutOfRangeException(nameof(rayDirection)); - var direction = rayDirection / directionLength; - var outerRadius = field.Radius + bounds.MaximumDisplacement + scale.MaximumUnresolvedHeight; - if (!double.IsFinite(outerRadius)) outerRadius = field.Radius + bounds.MaximumDisplacement; - if (!TrySphereInterval(rayOrigin, direction, outerRadius, out var enter, out var leave)) { hit = default; return false; } - var start = Math.Max(enter, 0); - var end = Math.Min(leave, maximumDistance); - if (start > end) { hit = default; return false; } - - var previousDistance = start; - var previous = SignedSurfaceDistance(field, rayOrigin + direction * previousDistance, scale, source, out var previousSample); - if (previous <= 0) - { - hit = new(previousDistance, rayOrigin + direction * previousDistance, previousSample); - return true; - } - var minimumStep = Math.Max(scale.FootprintMeters * 0.5, field.Radius * 1.0e-7); - for (var iteration = 0; iteration < 512 && previousDistance < end; iteration++) - { - var nextDistance = Math.Min(previousDistance + Math.Max(previous * 0.5, minimumStep), end); - var next = SignedSurfaceDistance(field, rayOrigin + direction * nextDistance, scale, source, out var nextSample); - if (next <= 0) - { - var low = previousDistance; - var high = nextDistance; - var surface = nextSample; - for (var refine = 0; refine < 32; refine++) - { - var middle = (low + high) * 0.5; - var value = SignedSurfaceDistance(field, rayOrigin + direction * middle, scale, source, out var middleSample); - if (value > 0) low = middle; - else { high = middle; surface = middleSample; } - } - var position = rayOrigin + direction * high; - hit = new(high, position, surface); - return true; - } - if (nextDistance >= end) break; - previousDistance = nextDistance; - previous = next; - previousSample = nextSample; - } - hit = default; - return false; - } - - public static double SegmentClearance( - in PlanetaryFieldDefinition field, - double3 start, - double3 end, - double sampleSpacing, - in PlanetaryQueryScale scale, - TSource source) - where TSource : IPlanetaryBaseField - { - ValidateFinite(start, nameof(start)); ValidateFinite(end, nameof(end)); - if (!double.IsFinite(sampleSpacing) || sampleSpacing <= 0) throw new ArgumentOutOfRangeException(nameof(sampleSpacing)); - var delta = end - start; - var distance = Length(delta); - var steps = Math.Max((int)Math.Ceiling(distance / sampleSpacing), 1); - var minimum = double.PositiveInfinity; - for (var i = 0; i <= steps; i++) - { - var position = start + delta * (i / (double)steps); - minimum = Math.Min(minimum, SignedSurfaceDistance(field, position, scale, source, out _)); - } - return minimum; - } - - public static PlanetaryPathSample[] SampleGreatCircle( - in PlanetaryFieldDefinition field, - float3 startDirection, - float3 endDirection, - int segmentCount, - in PlanetaryQueryScale scale, - TSource source) - where TSource : IPlanetaryBaseField - { - if (segmentCount < 1) throw new ArgumentOutOfRangeException(nameof(segmentCount)); - PlanetaryTopology.ValidateDirection(startDirection); PlanetaryTopology.ValidateDirection(endDirection); - var a = math.normalize(startDirection); var b = math.normalize(endDirection); - var angle = MathF.Acos(math.clamp(math.dot(a, b), -1, 1)); - var sinAngle = MathF.Sin(angle); - var result = new PlanetaryPathSample[segmentCount + 1]; - for (var i = 0; i <= segmentCount; i++) - { - var t = i / (float)segmentCount; - var direction = sinAngle < 1.0e-6f - ? math.normalize(math.lerp(a, b, t)) - : math.normalize(a * (MathF.Sin((1 - t) * angle) / sinAngle) + b * (MathF.Sin(t * angle) / sinAngle)); - var sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale); - result[i] = new(angle * field.Radius * t, sample); - } - return result; - } - - public static PlanetaryRegionSummary Summarize( - in PlanetaryFieldDefinition field, - ReadOnlySpan directions, - in PlanetaryQueryScale scale, - TSource source) - where TSource : IPlanetaryBaseField - { - if (directions.IsEmpty) throw new ArgumentException("At least one direction is required.", nameof(directions)); - var min = float.PositiveInfinity; var max = float.NegativeInfinity; var slope = 0.0f; var unresolved = 0.0f; - foreach (var direction in directions) - { - var normalized = math.normalize(direction); - var sample = PlanetaryField.Sample(field, normalized, source.Sample(normalized), scale); - min = MathF.Min(min, sample.RadialDisplacement); - max = MathF.Max(max, sample.RadialDisplacement); - slope = MathF.Max(slope, sample.Slope); - unresolved = MathF.Max(unresolved, sample.UnresolvedHeightBound); - } - return new(min, max, slope, unresolved); - } - - private static double SignedSurfaceDistance( - in PlanetaryFieldDefinition field, - double3 position, - in PlanetaryQueryScale scale, - TSource source, - out PlanetarySurfaceSample sample) - where TSource : IPlanetaryBaseField - { - var radius = Length(position); - if (radius < 1.0e-15) { sample = default; return double.NegativeInfinity; } - var direction = new float3((float)(position.x / radius), (float)(position.y / radius), (float)(position.z / radius)); - sample = PlanetaryField.Sample(field, direction, source.Sample(direction), scale); - return radius - sample.SurfaceRadius; - } - - private static bool TrySphereInterval(double3 origin, double3 direction, double radius, out double enter, out double leave) - { - var b = Dot(origin, direction); - var c = Dot(origin, origin) - radius * radius; - var discriminant = b * b - c; - if (discriminant < 0) { enter = leave = 0; return false; } - var root = Math.Sqrt(discriminant); - enter = -b - root; leave = -b + root; return leave >= 0; - } - - private static double Dot(double3 a, double3 b) => a.x * b.x + a.y * b.y + a.z * b.z; - private static double Length(double3 value) => Math.Sqrt(Dot(value, value)); - private static void ValidateFinite(double3 value, string name) - { - if (!double.IsFinite(value.x) || !double.IsFinite(value.y) || !double.IsFinite(value.z)) throw new ArgumentOutOfRangeException(name); - } -} diff --git a/src/CultMath/PlanetaryQueries.cs.meta b/src/CultMath/PlanetaryQueries.cs.meta deleted file mode 100644 index 6fd11413..00000000 --- a/src/CultMath/PlanetaryQueries.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2ec43c567214b8247bda19b5c120c200 \ No newline at end of file diff --git a/src/CultMath/PlanetaryRadialRefinement.cs b/src/CultMath/PlanetaryRadialRefinement.cs deleted file mode 100644 index 7188a513..00000000 --- a/src/CultMath/PlanetaryRadialRefinement.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CultMath; - -public static class PlanetaryRadialRefinement -{ - public static float3 Step( - float3 position, - float3 rayDirection, - float3 center, - float targetRadius, - float minimumDerivative = 0.2f, - float maximumCorrection = 0.08f) - { - var local = position - center; - var radius = math.length(local); - var radial = local / math.max(radius, 0.0001f); - var error = radius - targetRadius; - var derivative = math.dot(rayDirection, radial); - var safeMinimum = math.max(minimumDerivative, 0.0001f); - var safeDerivative = math.abs(derivative) > safeMinimum - ? derivative - : derivative < 0.0f ? -safeMinimum : safeMinimum; - var correction = math.clamp( - -error / safeDerivative, - -math.abs(maximumCorrection), - math.abs(maximumCorrection)); - return position + rayDirection * correction; - } -} diff --git a/src/CultMath/PlanetaryRadialRefinement.cs.meta b/src/CultMath/PlanetaryRadialRefinement.cs.meta deleted file mode 100644 index 32af2655..00000000 --- a/src/CultMath/PlanetaryRadialRefinement.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 82e4db521edc253499220458b417fd1e \ No newline at end of file diff --git a/src/CultMath/PlanetaryResidency.cs b/src/CultMath/PlanetaryResidency.cs deleted file mode 100644 index 6918ac57..00000000 --- a/src/CultMath/PlanetaryResidency.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace CultMath; - -public readonly record struct PlanetaryLodParameters( - int MaximumLevel, - int PageInteriorSize, - int PageBorderSize, - float ViewportHeightPixels, - float MinimumFootprintMeters) -{ - public PlanetaryLodParameters Validate() - { - if (MaximumLevel is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(MaximumLevel)); - _ = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), PageInteriorSize, PageBorderSize).Validate(); - if (!float.IsFinite(ViewportHeightPixels) || ViewportHeightPixels <= 0) throw new ArgumentOutOfRangeException(nameof(ViewportHeightPixels)); - if (!float.IsFinite(MinimumFootprintMeters) || MinimumFootprintMeters < 0) throw new ArgumentOutOfRangeException(nameof(MinimumFootprintMeters)); - return this; - } -} - -public static class PlanetaryLodSelector -{ - public static int SelectLevel( - in PlanetaryFieldDefinition field, - float cameraDistanceFromCenter, - in PlanetaryLodParameters parameters) - { - field.Validate(); parameters.Validate(); - if (!float.IsFinite(cameraDistanceFromCenter) || cameraDistanceFromCenter < 0) throw new ArgumentOutOfRangeException(nameof(cameraDistanceFromCenter)); - var altitude = MathF.Max(cameraDistanceFromCenter - field.Radius, 0.001f); - var footprint = MathF.Max(altitude / parameters.ViewportHeightPixels * 2, parameters.MinimumFootprintMeters); - var erosion = field.Erosion; - for (var level = 0; level <= parameters.MaximumLevel; level++) - { - var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, level, 0, 0), parameters.PageInteriorSize, parameters.PageBorderSize); - var spacing = PlanetaryPageSampling.NominalAngularTexelSize(page) * field.Radius; - var unresolved = ErosionFrequencyBands.Select( - erosion.Scale * erosion.CellScale, spacing, erosion.Octaves, - erosion.Lacunarity, erosion.Strength * erosion.Scale, erosion.Gain).UnresolvedHeightBound; - if (unresolved <= footprint) return level; - } - return parameters.MaximumLevel; - } - - public static PlanetaryTileAddress[] SelectAncestorChain( - in PlanetaryFieldDefinition field, - float3 cameraDirection, - float cameraDistanceFromCenter, - in PlanetaryLodParameters parameters) - { - PlanetaryTopology.ValidateDirection(cameraDirection); - var level = SelectLevel(field, cameraDistanceFromCenter, parameters); - var result = new PlanetaryTileAddress[6 + level]; - for (var face = 0; face < 6; face++) result[face] = new((PlanetaryCubeFace)face, 0, 0, 0); - var leaf = PlanetaryTopology.TileAt(cameraDirection, level); - for (var currentLevel = level; currentLevel >= 1; currentLevel--) - { - result[6 + currentLevel - 1] = leaf; - leaf = leaf.Parent(); - } - return result; - } -} - -public readonly record struct PlanetaryResidentTile(PlanetaryTileAddress Tile, float Blend, bool Departing); - -public readonly record struct PlanetaryResidencySnapshot( - ulong ContentVersion, - ulong PresentationVersion, - PlanetaryResidentTile[] Tiles); - -/// Owns presentation residency only. It never owns field content. -public sealed class PlanetaryResidualResidency -{ - private readonly Dictionary residents = new(); - private readonly Dictionary arrivals = new(); - private readonly Dictionary departures = new(); - private float lastTime = float.NegativeInfinity; - - public void Reset() - { - residents.Clear(); arrivals.Clear(); departures.Clear(); lastTime = float.NegativeInfinity; - } - - public PlanetaryResidencySnapshot Update(ReadOnlySpan desired, float timeSeconds, float transitionSeconds) - { - if (!float.IsFinite(timeSeconds)) throw new ArgumentOutOfRangeException(nameof(timeSeconds)); - if (!float.IsFinite(transitionSeconds) || transitionSeconds < 0) throw new ArgumentOutOfRangeException(nameof(transitionSeconds)); - if (timeSeconds < lastTime) Reset(); - lastTime = timeSeconds; - - var desiredKeys = new HashSet(); - foreach (var tile in desired) - { - var key = tile.StableKey; - desiredKeys.Add(key); - residents[key] = tile; - if (!arrivals.ContainsKey(key)) arrivals[key] = timeSeconds; - departures.Remove(key); - } - - foreach (var pair in residents.ToArray()) - { - if (pair.Value.Level == 0 || desiredKeys.Contains(pair.Key)) continue; - if (!departures.ContainsKey(pair.Key)) - { - var startingBlend = transitionSeconds == 0 - ? 1 - : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1); - departures[pair.Key] = (timeSeconds, startingBlend); - } - if (transitionSeconds > 0 && timeSeconds - departures[pair.Key].Time < transitionSeconds) continue; - residents.Remove(pair.Key); arrivals.Remove(pair.Key); departures.Remove(pair.Key); - } - - var ordered = residents - .OrderBy(pair => pair.Value.Level) - .ThenBy(pair => pair.Value.Face) - .ThenBy(pair => pair.Value.X) - .ThenBy(pair => pair.Value.Y) - .ToArray(); - var tiles = new PlanetaryResidentTile[ordered.Length]; - for (var i = 0; i < ordered.Length; i++) - { - var pair = ordered[i]; - var departing = departures.TryGetValue(pair.Key, out var departure); - var blend = pair.Value.Level == 0 || transitionSeconds == 0 - ? 1 - : departing - ? departure.StartingBlend * Math.Clamp(1 - (timeSeconds - departure.Time) / transitionSeconds, 0, 1) - : Math.Clamp((timeSeconds - arrivals[pair.Key]) / transitionSeconds, 0, 1); - tiles[i] = new(pair.Value, blend, departing); - } - return new(Hash(ordered.Select(pair => pair.Key)), Hash(tiles.Select(tile => tile.Tile.StableKey ^ (uint)BitConverter.SingleToInt32Bits(tile.Blend))), tiles); - } - - private static ulong Hash(IEnumerable values) - { - const ulong offset = 14695981039346656037UL; - const ulong prime = 1099511628211UL; - var hash = offset; - foreach (var value in values) hash = unchecked((hash ^ value) * prime); - return hash == 0 ? 1 : hash; - } -} diff --git a/src/CultMath/PlanetaryResidency.cs.meta b/src/CultMath/PlanetaryResidency.cs.meta deleted file mode 100644 index 24347624..00000000 --- a/src/CultMath/PlanetaryResidency.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 0585ea15edd7f1c4fa272df4911a8d6f \ No newline at end of file diff --git a/src/CultMath/PlanetaryTopology.cs b/src/CultMath/PlanetaryTopology.cs deleted file mode 100644 index 57d562cc..00000000 --- a/src/CultMath/PlanetaryTopology.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; - -namespace CultMath; - -public enum PlanetaryCubeFace -{ - PositiveX, - NegativeX, - PositiveY, - NegativeY, - PositiveZ, - NegativeZ, -} - -public readonly record struct PlanetaryFaceCoordinate(PlanetaryCubeFace Face, double U, double V) -{ - public PlanetaryFaceCoordinate Validate(bool allowOutsideFace = false) - { - if (!double.IsFinite(U) || !double.IsFinite(V)) throw new ArgumentOutOfRangeException(nameof(U)); - if (!allowOutsideFace && (U is < -1.0 or > 1.0 || V is < -1.0 or > 1.0)) - throw new ArgumentOutOfRangeException(nameof(U), "Face coordinates must be in [-1, 1]."); - if (!Enum.IsDefined(typeof(PlanetaryCubeFace), Face)) throw new ArgumentOutOfRangeException(nameof(Face)); - return this; - } -} - -public readonly record struct PlanetaryTileAddress -{ - public const int MaxLevel = 30; - - public PlanetaryTileAddress(PlanetaryCubeFace face, int level, int x, int y) - { - if (!Enum.IsDefined(typeof(PlanetaryCubeFace), face)) throw new ArgumentOutOfRangeException(nameof(face)); - if (level is < 0 or > MaxLevel) throw new ArgumentOutOfRangeException(nameof(level)); - var count = 1 << level; - if ((uint)x >= (uint)count) throw new ArgumentOutOfRangeException(nameof(x)); - if ((uint)y >= (uint)count) throw new ArgumentOutOfRangeException(nameof(y)); - Face = face; - Level = level; - X = x; - Y = y; - } - - public PlanetaryCubeFace Face { get; } - public int Level { get; } - public int X { get; } - public int Y { get; } - public int AxisTileCount => 1 << Level; - - public PlanetaryTileAddress Parent() => Level == 0 - ? throw new InvalidOperationException("Root cube tile has no parent.") - : new(Face, Level - 1, X >> 1, Y >> 1); - - public PlanetaryTileAddress Child(int childX, int childY) - { - if ((uint)childX > 1) throw new ArgumentOutOfRangeException(nameof(childX)); - if ((uint)childY > 1) throw new ArgumentOutOfRangeException(nameof(childY)); - if (Level == MaxLevel) throw new InvalidOperationException($"Level {MaxLevel} cannot be subdivided."); - return new(Face, Level + 1, (X << 1) | childX, (Y << 1) | childY); - } - - public PlanetaryFaceCoordinate PositionAt(double localU, double localV) - { - if (!double.IsFinite(localU) || localU is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localU)); - if (!double.IsFinite(localV) || localV is < 0.0 or > 1.0) throw new ArgumentOutOfRangeException(nameof(localV)); - var count = AxisTileCount; - return new(Face, -1.0 + 2.0 * (X + localU) / count, -1.0 + 2.0 * (Y + localV) / count); - } - - public ulong StableKey - { - get - { - const ulong offset = 14695981039346656037UL; - const ulong prime = 1099511628211UL; - var hash = offset; - hash = unchecked((hash ^ (uint)Face) * prime); - hash = unchecked((hash ^ (uint)Level) * prime); - hash = unchecked((hash ^ (uint)X) * prime); - hash = unchecked((hash ^ (uint)Y) * prime); - return hash == 0 ? 1 : hash; - } - } - public override string ToString() => $"{Face}/L{Level:D2}/{X}/{Y}"; -} - -public static class PlanetaryTopology -{ - private const float QuarterPi = MathF.PI * 0.25f; - - public static float3 Direction(PlanetaryFaceCoordinate coordinate) - { - coordinate.Validate(allowOutsideFace: true); - var u = MathF.Tan((float)coordinate.U * QuarterPi); - var v = MathF.Tan((float)coordinate.V * QuarterPi); - var cube = coordinate.Face switch - { - PlanetaryCubeFace.PositiveX => new float3(1, v, -u), - PlanetaryCubeFace.NegativeX => new float3(-1, v, u), - PlanetaryCubeFace.PositiveY => new float3(u, 1, -v), - PlanetaryCubeFace.NegativeY => new float3(u, -1, v), - PlanetaryCubeFace.PositiveZ => new float3(u, v, 1), - PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1), - _ => throw new ArgumentOutOfRangeException(nameof(coordinate)), - }; - return math.normalize(cube); - } - - public static float3 NormalizedCubeDirection(PlanetaryFaceCoordinate coordinate) - { - coordinate.Validate(allowOutsideFace: true); - var u = (float)coordinate.U; - var v = (float)coordinate.V; - return math.normalize(CubeVector(coordinate.Face, u, v)); - } - - public static PlanetaryFaceCoordinate FaceCoordinate(float3 direction) - { - ValidateDirection(direction); - var d = math.normalize(direction); - var a = math.abs(d); - if (a.x >= a.y && a.x >= a.z) - return d.x >= 0 - ? FromCubeUv(PlanetaryCubeFace.PositiveX, -d.z / a.x, d.y / a.x) - : FromCubeUv(PlanetaryCubeFace.NegativeX, d.z / a.x, d.y / a.x); - if (a.y >= a.z) - return d.y >= 0 - ? FromCubeUv(PlanetaryCubeFace.PositiveY, d.x / a.y, -d.z / a.y) - : FromCubeUv(PlanetaryCubeFace.NegativeY, d.x / a.y, d.z / a.y); - return d.z >= 0 - ? FromCubeUv(PlanetaryCubeFace.PositiveZ, d.x / a.z, d.y / a.z) - : FromCubeUv(PlanetaryCubeFace.NegativeZ, -d.x / a.z, d.y / a.z); - } - - public static PlanetaryTileAddress TileAt(float3 direction, int level) - { - if (level is < 0 or > PlanetaryTileAddress.MaxLevel) throw new ArgumentOutOfRangeException(nameof(level)); - var face = FaceCoordinate(direction); - var count = 1 << level; - var x = Math.Clamp((int)((face.U * 0.5 + 0.5) * count), 0, count - 1); - var y = Math.Clamp((int)((face.V * 0.5 + 0.5) * count), 0, count - 1); - return new(face.Face, level, x, y); - } - - public static bool TryLocalCoordinate(float3 direction, PlanetaryTileAddress tile, out float2 local) - { - var face = FaceCoordinate(direction); - if (face.Face != tile.Face) { local = float2.zero; return false; } - var scaledU = (face.U * 0.5 + 0.5) * tile.AxisTileCount; - var scaledV = (face.V * 0.5 + 0.5) * tile.AxisTileCount; - local = new((float)(scaledU - tile.X), (float)(scaledV - tile.Y)); - return local.x is >= 0 and <= 1 && local.y is >= 0 and <= 1; - } - - public static float3 SurfaceNormal(float3 unitDirection, float3 worldDistanceTangentGradient) - { - ValidateDirection(unitDirection); - ValidateFinite(worldDistanceTangentGradient, nameof(worldDistanceTangentGradient)); - var direction = math.normalize(unitDirection); - var tangent = worldDistanceTangentGradient - direction * math.dot(worldDistanceTangentGradient, direction); - return math.normalize(direction - tangent); - } - - private static PlanetaryFaceCoordinate FromCubeUv(PlanetaryCubeFace face, float cubeU, float cubeV) => new( - face, - MathF.Atan(cubeU) / QuarterPi, - MathF.Atan(cubeV) / QuarterPi); - - private static float3 CubeVector(PlanetaryCubeFace face, float u, float v) => face switch - { - PlanetaryCubeFace.PositiveX => new float3(1, v, -u), - PlanetaryCubeFace.NegativeX => new float3(-1, v, u), - PlanetaryCubeFace.PositiveY => new float3(u, 1, -v), - PlanetaryCubeFace.NegativeY => new float3(u, -1, v), - PlanetaryCubeFace.PositiveZ => new float3(u, v, 1), - PlanetaryCubeFace.NegativeZ => new float3(-u, v, -1), - _ => throw new ArgumentOutOfRangeException(nameof(face)), - }; - - internal static void ValidateDirection(float3 direction) - { - ValidateFinite(direction, nameof(direction)); - if (math.length(direction) < 1.0e-12f) throw new ArgumentOutOfRangeException(nameof(direction)); - } - - internal static void ValidateFinite(float3 value, string name) - { - if (!float.IsFinite(value.x) || !float.IsFinite(value.y) || !float.IsFinite(value.z)) - throw new ArgumentOutOfRangeException(name); - } -} diff --git a/src/CultMath/PlanetaryTopology.cs.meta b/src/CultMath/PlanetaryTopology.cs.meta deleted file mode 100644 index 411a8bdb..00000000 --- a/src/CultMath/PlanetaryTopology.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 88f6789c4384f7846ac4be37613fe5ed \ No newline at end of file diff --git a/src/CultMath/SphericalErosion.cs b/src/CultMath/SphericalErosion.cs deleted file mode 100644 index 93ddb0ec..00000000 --- a/src/CultMath/SphericalErosion.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace CultMath; - -public readonly record struct SphericalErosionParameters( - float Frequency, - float Amplitude, - float Lacunarity, - float Gain, - float SlopeStrength, - float Detail, - float Seed, - int Octaves) -{ - public static SphericalErosionParameters Default => new(18.0f, 0.035f, 2.03f, 0.52f, 6.0f, 1.35f, 0.0f, 5); -} - -public readonly record struct SphericalErosionSample(float HeightOffset, float Ridge, float Gully, float3 Gradient); - -/// -/// Stateless, point-evaluable erosion-like synthesis on a unit sphere. -/// The matching shader body is cultmath_spherical_erosion in CultMath.hlsl. -/// Position and gradient are planet-space; the gradient is projected tangent -/// internally so cube faces and tile request order never enter the result. -/// -public static class SphericalErosion -{ - public static SphericalErosionSample Sample( - float3 unitPosition, - float baseHeight, - float3 baseGradient, - SphericalErosionParameters parameters) - { - var position = math.normalize(unitPosition); - var gradient = baseGradient - position * math.dot(baseGradient, position); - var slope = math.length(gradient); - var flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position); - var frequency = math.max(parameters.Frequency, 0.001f); - var amplitude = parameters.Amplitude; - var combiMask = EaseOut(math.saturate(slope * parameters.SlopeStrength)); - var fadeTarget = math.saturate(baseHeight * 0.5f + 0.5f) * 2.0f - 1.0f; - var heightOffset = 0.0f; - var ridge = 0.0f; - var gully = 0.0f; - - var octaveCount = math.min(math.max(parameters.Octaves, 0), 8); - for (var octave = 0; octave < octaveCount; octave++) - { - var phase = DirectionalCellWave(position * frequency, flow, parameters.Seed + octave * 19.19f); - var wave = math.cos(phase); - var waveSlope = math.sin(phase); - var faded = math.lerp(fadeTarget, wave, combiMask); - heightOffset += faded * amplitude; - ridge = math.max(ridge, math.saturate(faded)); - gully = math.max(gully, math.saturate(-faded)); - - var across = math.normalize(math.cross(position, flow)); - gradient += across * (math.sign(waveSlope) * amplitude * frequency * combiMask); - gradient -= position * math.dot(gradient, position); - slope = math.length(gradient); - flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position); - - var newMask = EaseOut(math.saturate(math.abs(waveSlope) * 1.35f)); - combiMask = PowInverse(combiMask, parameters.Detail) * newMask; - fadeTarget = faded; - frequency *= math.max(parameters.Lacunarity, 1.01f); - amplitude *= math.saturate(parameters.Gain); - } - - return new SphericalErosionSample(heightOffset, ridge, gully, gradient); - } - - private static float DirectionalCellWave(float3 position, float3 flow, float seed) - { - var cell = math.floor(position); - var local = math.frac(position); - var weights = local * local * (3.0f - 2.0f * local); - var x00 = CellWave(position, flow, cell + new float3(0, 0, 0), seed); - var x10 = CellWave(position, flow, cell + new float3(1, 0, 0), seed); - var x01 = CellWave(position, flow, cell + new float3(0, 1, 0), seed); - var x11 = CellWave(position, flow, cell + new float3(1, 1, 0), seed); - var y00 = math.lerp(x00, x10, weights.x); - var y10 = math.lerp(x01, x11, weights.x); - var z0 = math.lerp(y00, y10, weights.y); - var x00z = CellWave(position, flow, cell + new float3(0, 0, 1), seed); - var x10z = CellWave(position, flow, cell + new float3(1, 0, 1), seed); - var x01z = CellWave(position, flow, cell + new float3(0, 1, 1), seed); - var x11z = CellWave(position, flow, cell + new float3(1, 1, 1), seed); - var z1 = math.lerp(math.lerp(x00z, x10z, weights.x), math.lerp(x01z, x11z, weights.x), weights.y); - return math.lerp(z0, z1, weights.z); - } - - private static float CellWave(float3 position, float3 flow, float3 cell, float seed) - { - var pivot = cell + new float3( - math.hash(cell + seed + 11.7f), - math.hash(cell.yzx + seed + 37.1f), - math.hash(cell.zyx + seed + 73.9f)); - return math.dot(position - pivot, flow) * math.TAU + math.hash(cell + seed + 101.3f) * math.TAU; - } - - private static float3 StableTangent(float3 position) - { - var axis = math.abs(position.z) < 0.8f ? new float3(0, 0, 1) : new float3(0, 1, 0); - return math.normalize(math.cross(axis, position)); - } - - private static float EaseOut(float value) => 1.0f - (1.0f - value) * (1.0f - value); - private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), math.max(power, 0.001f)); -} diff --git a/src/CultMath/SphericalErosion.cs.meta b/src/CultMath/SphericalErosion.cs.meta deleted file mode 100644 index 82db7730..00000000 --- a/src/CultMath/SphericalErosion.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 59ec2db73065f6c4483ad90f5e17850e \ No newline at end of file diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index e15fbf08..d0a56e76 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -22,9 +22,6 @@ public static class math public static float4 float4(float3 xyz, float w) => new(xyz, w); public static float4 float4(float x, float3 yzw) => new(x, yzw); public static float4 float4(float value) => new(value, value, value, value); - public static rect rect(float2 min, float2 max) => new(min, max); - public static rect rect(float minX, float minY, float maxX, float maxY) => new(minX, minY, maxX, maxY); - public static float radians(float degrees) => degrees * (PI / 180.0f); public static float degrees(float radians) => radians * (180.0f / PI); public static float sin(float value) => MathF.Sin(value); diff --git a/src/CultMath/rect.cs b/src/CultMath/rect.cs deleted file mode 100644 index 67b210b9..00000000 --- a/src/CultMath/rect.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CultMath; - -public readonly record struct rect(float2 min, float2 max) -{ - public rect(float minX, float minY, float maxX, float maxY) - : this(math.float2(math.min(minX, maxX), math.min(minY, maxY)), - math.float2(math.max(minX, maxX), math.max(minY, maxY))) - { - } - - public float2 size => max - min; - public float2 center => (min + max) * 0.5f; - - public bool Contains(float2 point) - { - return point.x >= min.x && - point.x <= max.x && - point.y >= min.y && - point.y <= max.y; - } -} diff --git a/src/CultMath/rect.cs.meta b/src/CultMath/rect.cs.meta deleted file mode 100644 index 9f7598ed..00000000 --- a/src/CultMath/rect.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b00dc5f108967db47ac2f54a47033e33 \ No newline at end of file diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs deleted file mode 100644 index cccebd64..00000000 --- a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class AdvancedErosionFilterTests -{ - [Fact] - public void DefaultReferenceFixtureIsStable() - { - var result = AdvancedErosionFilter.Sample( - new float2(0.42f, 0.31f), - new float3(0.5f, 0.1f, -0.2f), - 0.0f, - AdvancedErosionParameters.Default); - - Assert.Equal(0.0639375f, result.Magnitude, precision: 7); - Assert.True(float.IsFinite(result.Delta.x)); - Assert.True(float.IsFinite(result.Delta.y)); - Assert.True(float.IsFinite(result.Delta.z)); - Assert.InRange(result.RidgeMap, -1.0f, 1.0f); - } - - [Fact] - public void FlatInputAndZeroRoundingRemainFinite() - { - var parameters = AdvancedErosionParameters.Default with { Rounding = float4.zero }; - var result = AdvancedErosionFilter.Sample(new float2(0.25f, 0.75f), float3.zero, 0.0f, parameters); - - Assert.True(float.IsFinite(result.Delta.x)); - Assert.True(float.IsFinite(result.RidgeMap)); - Assert.True(float.IsFinite(result.FadeTarget)); - } - - [Fact] - public void PhacellePartialNormalizationIsFiniteAtEndpoints() - { - foreach (var normalization in new[] { 0.0f, 0.5f, 1.0f }) - { - var sample = AdvancedErosionFilter.Phacelle(new float2(1.7f, -3.2f), new float2(0.6f, -0.8f), 0.7f, 0.25f, normalization); - Assert.True(float.IsFinite(sample.x)); - Assert.True(float.IsFinite(sample.y)); - } - } -} diff --git a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta b/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta deleted file mode 100644 index 253ee425..00000000 --- a/tests/CultMath.Tests/AdvancedErosionFilterTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: a7c85309318ae3f4ba75ac28a4f661cf \ No newline at end of file diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs deleted file mode 100644 index f57213a4..00000000 --- a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class ErosionFrequencyBandsTests -{ - [Fact] - public void RadiusIndependentWorldWavelengthSelectsSameBands() - { - var small = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f); - var large = ErosionFrequencyBands.Select(1_000.0f, 100.0f, 8, 2.0f, 20.0f, 0.5f); - Assert.Equal(small, large); - Assert.Equal(3, small.ActiveOctaves); - } - - [Fact] - public void TransitionPartiallyWeightsLastResolvableOctave() - { - var selection = ErosionFrequencyBands.Select(1_000.0f, 50.0f, 8, 2.0f, 1.0f, 0.5f); - Assert.Equal(4, selection.ActiveOctaves); - Assert.InRange(selection.FinalOctaveWeight, 0.0f, 1.0f); - Assert.True(selection.UnresolvedHeightBound > 0.0f); - } - - [Fact] - public void BandedFilterPreservesLowOctaveResultExactly() - { - var p = AdvancedErosionParameters.Default; - var band = new ErosionBandSelection(2, 1.0f, 0.0f, 0.0f); - var banded = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p, band); - var truncated = AdvancedErosionFilter.Sample(new float2(0.42f, 0.31f), new float3(0.5f, 0.1f, -0.2f), 0.0f, p with { Octaves = 2 }); - Assert.Equal(truncated, banded); - } -} diff --git a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta b/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta deleted file mode 100644 index ef15b7ee..00000000 --- a/tests/CultMath.Tests/ErosionFrequencyBandsTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 095f8ed13d2f2f74baefd23310395256 \ No newline at end of file diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 47f58971..b9396bdf 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -5,22 +5,13 @@ namespace CultMath.Tests; public sealed class HlslMirrorTests { [Fact] - public void HlslMirrorPublishesSphericalErosionKernel() + public void HlslMirrorDoesNotPublishGeometryKernels() { var source = File.ReadAllText(Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl")); - Assert.Contains("cultmath_spherical_erosion", source); - Assert.Contains("CultMathSphericalErosionParameters", source); - } - - [Fact] - public void HlslMirrorPublishesFaithfulAdvancedErosionKernel() - { - var mirror = Path.Combine(GetRepositoryRoot(), "shaders", "CultMath.hlsl"); - var source = File.ReadAllText(mirror); - Assert.Contains("AdvancedErosionFilter.hlsl", source); - var advanced = File.ReadAllText(Path.Combine(Path.GetDirectoryName(mirror)!, "AdvancedErosionFilter.hlsl")); - Assert.Contains("cultmath_phacelle_noise", advanced); - Assert.Contains("cultmath_advanced_erosion_filter", advanced); + Assert.DoesNotContain("AdvancedErosionFilter.hlsl", source); + Assert.DoesNotContain("Planetary.hlsl", source); + Assert.DoesNotContain("spherical_erosion", source); + Assert.DoesNotContain("planetary_radial_refinement", source); } [Fact] @@ -66,24 +57,6 @@ public sealed class HlslMirrorTests Assert.Contains("max(length(value), CULTMATH_NORMALIZE_EPSILON)", include); } - [Fact] - public void HlslMirrorPublishesPlanetaryTopologyAndField() - { - var root = GetRepositoryRoot(); - var include = File.ReadAllText(Path.Combine(root, "shaders", "CultMath.hlsl")); - var planetary = File.ReadAllText(Path.Combine(root, "shaders", "Planetary.hlsl")); - Assert.Contains("Planetary.hlsl", include); - Assert.Contains("cultmath_planetary_face_direction", planetary); - Assert.Contains("cultmath_planetary_face_coordinate", planetary); - Assert.Contains("cultmath_planetary_page_local", planetary); - Assert.Contains("cultmath_planetary_field_sample", planetary); - Assert.Contains("cultmath_planetary_equirectangular_forward", planetary); - Assert.Contains("cultmath_planetary_equal_earth_forward", planetary); - Assert.Contains("cultmath_planetary_projection_forward", planetary); - Assert.Contains("cultmath_planetary_projection_inverse", planetary); - Assert.Contains("CultMathPlanetarySurfaceSample", planetary); - } - private static string GetRepositoryRoot() { var directory = new DirectoryInfo(AppContext.BaseDirectory); diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index 6979bdd6..ad268212 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -32,19 +32,6 @@ public sealed class MathTests Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f)); } - [Fact] - public void RectNormalizesBoundsAndContainsPoints() - { - var viewport = math.rect(10.0f, 20.0f, -5.0f, -2.0f); - - Assert.Equal(new float2(-5.0f, -2.0f), viewport.min); - Assert.Equal(new float2(10.0f, 20.0f), viewport.max); - Assert.Equal(new float2(15.0f, 22.0f), viewport.size); - Assert.Equal(new float2(2.5f, 9.0f), viewport.center); - Assert.True(viewport.Contains(new float2(0.0f, 0.0f))); - Assert.False(viewport.Contains(new float2(11.0f, 0.0f))); - } - [Fact] public void VectorsExposeCommonReadOnlySwizzles() { diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs b/tests/CultMath.Tests/PlanetaryFieldTests.cs deleted file mode 100644 index 4dc75325..00000000 --- a/tests/CultMath.Tests/PlanetaryFieldTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryFieldTests -{ - private static readonly PlanetaryFieldDefinition Definition = new( - 0x7f59_6d11_02ab_9c31UL, - 8.4f, - 42, - AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 }); - - [Fact] - public void SampleIsDeterministicScaleAwareAndTangent() - { - var direction = math.normalize(new float3(0.31f, -0.72f, 0.61f)); - var source = new FixtureField(); - var coarse = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.1f)); - var fine = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f)); - var repeated = PlanetaryField.Sample(Definition, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(0.01f)); - - Assert.Equal(fine, repeated); - Assert.Equal(Definition.FieldVersion, fine.FieldVersion); - Assert.True(fine.UnresolvedHeightBound <= coarse.UnresolvedHeightBound); - Assert.True(fine.FinestResolvedWavelength <= coarse.FinestResolvedWavelength); - Assert.InRange(MathF.Abs(math.dot(fine.UnitDirection, fine.TangentGradient)), 0, 2.0e-5f); - Assert.Equal(1, math.length(fine.SurfaceNormal), 5); - } - - [Fact] - public void BatchAndDoublePositionUseThePointContract() - { - var directions = new[] - { - math.normalize(new float3(1, 2, 3)), - math.normalize(new float3(-2, 0.5f, 1)), - math.normalize(new float3(0.1f, -0.2f, -1)), - }; - var output = new PlanetarySurfaceSample[directions.Length]; - var source = new FixtureField(); - var scale = PlanetaryQueryScale.AtFootprint(0.05f); - PlanetaryField.SampleBatch(Definition, directions, scale, source, output); - - for (var i = 0; i < directions.Length; i++) - { - var normalized = math.normalize(directions[i]); - var expected = PlanetaryField.Sample(Definition, normalized, source.Sample(normalized), scale); - Assert.InRange(MathF.Abs(expected.RadialDisplacement - output[i].RadialDisplacement), 0, 1.0e-6f); - Assert.InRange(math.distance(expected.TangentGradient, output[i].TangentGradient), 0, 1.0e-6f); - } - - var d = directions[0]; - var position = new double3(d.x * 8.0e11, d.y * 8.0e11, d.z * 8.0e11); - var positioned = PlanetaryField.SamplePosition(Definition, position, scale, source); - Assert.InRange(math.distance(positioned.UnitDirection, d), 0, 2.0e-6f); - } - - [Fact] - public void FieldIdentityChangesWithEveryLoadBearingInput() - { - var erosion = AdvancedErosionParameters.Default; - var baseline = PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion); - Assert.Equal(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion).FieldVersion); - Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(4, 8.4f, 7, erosion).FieldVersion); - Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.5f, 7, erosion).FieldVersion); - Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 8, erosion).FieldVersion); - Assert.NotEqual(baseline.FieldVersion, PlanetaryFieldDefinition.Create(3, 8.4f, 7, erosion with { Gain = 0.49f }).FieldVersion); - } - - private readonly struct FixtureField : IPlanetaryBaseField - { - public PlanetaryBaseFieldSample Sample(float3 direction) - { - var value = direction.x * 0.6f + direction.y * direction.z * 0.3f; - var fieldGradient = new float3(0.6f, direction.z * 0.3f, direction.y * 0.3f); - fieldGradient -= direction * math.dot(fieldGradient, direction); - return new(value * 0.1f, fieldGradient * 0.1f, value, fieldGradient, math.clamp(value, -1, 1)); - } - } -} diff --git a/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta b/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta deleted file mode 100644 index 1710b5ff..00000000 --- a/tests/CultMath.Tests/PlanetaryFieldTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: c747cfda80df21d41a57412d8d07c08a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs deleted file mode 100644 index ea17b4b9..00000000 --- a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryGpuPageTests -{ - [Fact] - public void UploadContentUsesCanonicalDirectionsSpacingAndMetadata() - { - var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.NegativeY, 3, 4, 2), 9, 2); - var content = PlanetaryGpuPageBuilder.BuildContent(layout, 8.4f); - Assert.Equal(layout.StorageSize * layout.StorageSize, content.Inputs.Length); - Assert.True(content.ParentSampleSpacing > content.SampleSpacing); - var first = content.Inputs[0]; - Assert.Equal(PlanetaryPageSampling.Direction(layout, 0, 0), first.DirectionRadius.xyz); - Assert.Equal(8.4f, first.DirectionRadius.w); - Assert.Equal(content.SampleSpacing, first.Sampling.x); - Assert.Equal(content.ParentSampleSpacing, first.Sampling.y); - - var metadata = PlanetaryGpuPageBuilder.Metadata(content, 37, 0.25f); - Assert.Equal(new float4((int)layout.Tile.Face, layout.Tile.Level, layout.Tile.X, layout.Tile.Y), metadata.Address); - Assert.Equal(new float4(37, layout.StorageSize, layout.InteriorSize, layout.BorderSize), metadata.Layout); - Assert.Equal(0.25f, metadata.State.y); - } -} diff --git a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta deleted file mode 100644 index 00f4a3a5..00000000 --- a/tests/CultMath.Tests/PlanetaryGpuPageTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 87266638e8010f546ab6c430a712868b \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs deleted file mode 100644 index 112cb31f..00000000 --- a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.IO; -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryMapTileEncodingTests -{ - [Fact] - public void BinaryTileRoundTripsIdentityValidityAndSurfaceEvidence() - { - var field = PlanetaryFieldDefinition.Create(5, 10, 3, AdvancedErosionParameters.Default); - var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.EqualEarth), 1, 1, 0, 5, 1); - var key = new PlanetaryMapTileKey(field.FieldVersion, 2, 17, layout, PlanetaryQueryScale.AtFootprint(0.25f, 0.1f)); - var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); - using var bytes = new MemoryStream(); - PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true); - bytes.Position = 0; - var decoded = PlanetaryMapTileEncoding.Read(bytes); - - Assert.Equal(tile.Key, decoded.Key); - Assert.Equal(tile.Validity, decoded.Validity); - for (var i = 0; i < tile.Samples.Length; i++) - if (tile.Validity[i]) Assert.Equal(tile.Samples[i], decoded.Samples[i]); - } - - [Fact] - public void DecoderRejectsForeignMagicAndVersions() - { - Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(new byte[] { 1, 2, 3, 4, 1, 0, 0, 0 }))); - var bytes = new byte[] { (byte)'C', (byte)'M', (byte)'P', (byte)'T', 99, 0, 0, 0 }; - Assert.Throws(() => PlanetaryMapTileEncoding.Read(new MemoryStream(bytes))); - } - - [Fact] - public void DecoderRejectsTrailingBytes() - { - var field = PlanetaryFieldDefinition.Create(2, 1, 0, AdvancedErosionParameters.Default); - var layout = new PlanetaryMapTileLayout(new(PlanetaryProjectionKind.Equirectangular), 0, 0, 0, 2, 0); - var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 0, layout, PlanetaryQueryScale.AtFootprint(1)); - var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); - using var bytes = new MemoryStream(); - PlanetaryMapTileEncoding.Write(bytes, tile, leaveOpen: true); - bytes.WriteByte(0xff); - bytes.Position = 0; - Assert.Throws(() => PlanetaryMapTileEncoding.Read(bytes)); - } - - private readonly struct FlatField : IPlanetaryBaseField - { - public PlanetaryBaseFieldSample Sample(float3 direction) => new(0, float3.zero, 0, float3.zero, 0); - } -} diff --git a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta b/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta deleted file mode 100644 index 027754e8..00000000 --- a/tests/CultMath.Tests/PlanetaryMapTileEncodingTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: e916ef83478d48b43ac99777f5e0f731 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs b/tests/CultMath.Tests/PlanetaryPageTests.cs deleted file mode 100644 index c4e4045b..00000000 --- a/tests/CultMath.Tests/PlanetaryPageTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryPageTests -{ - [Fact] - public void BilinearSamplingAndSummaryUseOnePageContract() - { - var layout = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 3, 1); - var samples = new PlanetaryPageSample[layout.StorageSize * layout.StorageSize]; - for (var y = 0; y < layout.StorageSize; y++) - for (var x = 0; x < layout.StorageSize; x++) - samples[y * layout.StorageSize + x] = new(x + y * 10, new float3(x, y, 0), x / 10.0f, y / 10.0f); - - var sample = PlanetaryPageSampling.Bilinear(samples, layout, new float2(0.25f, 0.75f)); - Assert.Equal(26.5f, sample.Height, 4); - Assert.Equal(new float3(1.5f, 2.5f, 0), sample.TangentGradient); - var summary = PlanetaryPageSampling.Summarize(samples, layout, 0.125f); - Assert.Equal(0, summary.MinimumHeight); - Assert.Equal(44, summary.MaximumHeight); - Assert.Equal(0.125f, summary.UnresolvedHeightBound); - Assert.True(summary.MaximumSlope > 0); - } - - [Fact] - public void ChildPageStoresOnlyResidualAndComposesBackToDirectField() - { - var field = new PlanetaryFieldDefinition(7, 8.4f, 3, AdvancedErosionParameters.Default with - { - Scale = 0.63f, - Strength = 0.12f, - Octaves = 7, - }); - var source = new FixtureField(); - var root = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 0, 0, 0), 17, 2), source); - var child = PlanetaryPageBaker.Bake(field, new(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2), source); - var direction = PlanetaryPageSampling.DirectionAtLocal(child.Layout, 0.5, 0.5); - var pages = new[] { new PlanetaryResidentPage(root, 1), new PlanetaryResidentPage(child, 1) }; - Assert.True(PlanetaryPageSetSampling.TrySample(pages, direction, out var composed)); - var spacing = PlanetaryPageSampling.NominalAngularTexelSize(child.Layout) * field.Radius; - var direct = PlanetaryField.Sample(field, direction, source.Sample(direction), PlanetaryQueryScale.AtFootprint(spacing)); - Assert.InRange(MathF.Abs(composed.Height - direct.RadialDisplacement), 0, 2.0e-5f); - Assert.InRange(math.distance(composed.TangentGradient, direct.TangentGradient), 0, 3.0e-5f); - } - - private readonly struct FixtureField : IPlanetaryBaseField - { - public PlanetaryBaseFieldSample Sample(float3 direction) - { - var value = direction.x * 0.4f - direction.y * 0.2f + direction.z * 0.5f; - var gradient = new float3(0.4f, -0.2f, 0.5f); - gradient -= direction * math.dot(gradient, direction); - return new(value * 0.08f, gradient * 0.08f, value, gradient, value); - } - } -} diff --git a/tests/CultMath.Tests/PlanetaryPageTests.cs.meta b/tests/CultMath.Tests/PlanetaryPageTests.cs.meta deleted file mode 100644 index 70a43864..00000000 --- a/tests/CultMath.Tests/PlanetaryPageTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7c3ae5598d3b9164a9bd3a736a22ba9a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs b/tests/CultMath.Tests/PlanetaryPatchTests.cs deleted file mode 100644 index 6564c773..00000000 --- a/tests/CultMath.Tests/PlanetaryPatchTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryPatchTests -{ - [Fact] - public void CubeSpherePatchMeshUsesCanonicalTopology() - { - var meshes = PlanetaryPatch.CreateCubeSphere(8); - Assert.Equal(6, meshes.Length); - foreach (var mesh in meshes) - { - Assert.Equal(81, mesh.Vertices.Length); - Assert.Equal(8 * 8 * 6, mesh.Indices.Length); - Assert.All(mesh.Vertices, vertex => Assert.Equal(1, math.length(vertex.UnitDirection), 5)); - foreach (var index in mesh.Indices) Assert.InRange(index, 0, mesh.Vertices.Length - 1); - } - } - - [Fact] - public void NeighborFaceCornersAreSingleValued() - { - var directions = PlanetaryPatch.CreateCubeSphere(1) - .SelectMany(mesh => mesh.Vertices.Select(vertex => vertex.UnitDirection)) - .ToArray(); - var groups = directions.GroupBy(direction => new - { - X = Math.Sign(direction.x), - Y = Math.Sign(direction.y), - Z = Math.Sign(direction.z), - }); - Assert.Equal(8, groups.Count()); - Assert.All(groups, group => - { - var first = group.First(); - Assert.All(group, direction => Assert.InRange(math.distance(first, direction), 0, 1.0e-6f)); - }); - } -} diff --git a/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta b/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta deleted file mode 100644 index 85990473..00000000 --- a/tests/CultMath.Tests/PlanetaryPatchTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 36840ef44326f84409f850163b00e1f7 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs b/tests/CultMath.Tests/PlanetaryProjectionTests.cs deleted file mode 100644 index ad811d6e..00000000 --- a/tests/CultMath.Tests/PlanetaryProjectionTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryProjectionTests -{ - [Theory] - [InlineData(PlanetaryProjectionKind.Equirectangular)] - [InlineData(PlanetaryProjectionKind.WebMercator)] - [InlineData(PlanetaryProjectionKind.EqualEarth)] - [InlineData(PlanetaryProjectionKind.Orthographic)] - [InlineData(PlanetaryProjectionKind.AzimuthalEquidistant)] - [InlineData(PlanetaryProjectionKind.AzimuthalEqualArea)] - [InlineData(PlanetaryProjectionKind.LocalTangent)] - public void ProjectionRoundTripsVisibleDirections(PlanetaryProjectionKind kind) - { - var parameters = new PlanetaryProjectionParameters(kind, 0.31, -0.22); - foreach (var longitude in new[] { -0.4, -0.1, 0.2, 0.65 }) - foreach (var latitude in new[] { -0.6, -0.15, 0.3, 0.7 }) - { - var direction = Direction(longitude + parameters.CenterLongitude, latitude); - if (!PlanetaryProjection.TryForward(direction, parameters, out var map)) continue; - Assert.True(PlanetaryProjection.TryInverse(map, parameters, out var roundTrip)); - Assert.InRange(math.distance(direction, roundTrip), 0, 3.0e-5f); - } - } - - [Fact] - public void CubeAtlasRoundTripsEveryFaceInterior() - { - var projection = new PlanetaryProjectionParameters(PlanetaryProjectionKind.CubeAtlas); - foreach (var face in Enum.GetValues()) - { - var direction = PlanetaryTopology.Direction(new(face, 0.23, -0.41)); - Assert.True(PlanetaryProjection.TryForward(direction, projection, out var map)); - Assert.True(PlanetaryProjection.TryInverse(map, projection, out var roundTrip)); - Assert.InRange(math.distance(direction, roundTrip), 0, 2.0e-6f); - } - } - - [Fact] - public void MercatorRejectsPolarSingularity() - { - var northPole = new float3(0, 0, 1); - Assert.False(PlanetaryProjection.TryForward(northPole, new(PlanetaryProjectionKind.WebMercator), out _)); - } - - [Fact] - public void MapTileBakerCarriesFieldIdentityAndProjectionValidity() - { - var field = new PlanetaryFieldDefinition(99, 10, 0, AdvancedErosionParameters.Default); - var layout = new PlanetaryMapTileLayout( - new(PlanetaryProjectionKind.Orthographic), 0, 0, 0, 9, 1); - var key = new PlanetaryMapTileKey(field.FieldVersion, 1, 7, layout, PlanetaryQueryScale.AtFootprint(0.1f)); - var tile = PlanetaryMapTileBaker.Bake(field, key, new FlatField()); - Assert.Equal(key, tile.Key); - Assert.Equal(layout.StorageSize * layout.StorageSize, tile.Samples.Length); - Assert.Contains(tile.Validity, valid => valid); - Assert.Contains(tile.Validity, valid => !valid); - Assert.All(tile.Samples.Where((_, index) => tile.Validity[index]), sample => Assert.Equal(field.FieldVersion, sample.FieldVersion)); - } - - private static float3 Direction(double longitude, double latitude) - { - var cos = Math.Cos(latitude); - return new((float)(cos * Math.Cos(longitude)), (float)(cos * Math.Sin(longitude)), (float)Math.Sin(latitude)); - } - - private readonly struct FlatField : IPlanetaryBaseField - { - public PlanetaryBaseFieldSample Sample(float3 unitDirection) => new(0, float3.zero, 0, float3.zero, 0); - } -} diff --git a/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta b/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta deleted file mode 100644 index 7f37f78f..00000000 --- a/tests/CultMath.Tests/PlanetaryProjectionTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: f370036bb1663524fb4a4d47dfd50401 \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs b/tests/CultMath.Tests/PlanetaryQueryTests.cs deleted file mode 100644 index 48d865f4..00000000 --- a/tests/CultMath.Tests/PlanetaryQueryTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryQueryTests -{ - private static readonly PlanetaryFieldDefinition Field = new(12, 10, 0, AdvancedErosionParameters.Default with { Strength = 0, Octaves = 0 }); - private static readonly PlanetaryQueryScale Scale = PlanetaryQueryScale.AtFootprint(0.1f, 0); - - [Fact] - public void RayIntersectionAndClearanceUseTheCpuSurfaceContract() - { - var source = new BulgedSphere(); - Assert.True(PlanetaryQueries.TryIntersectRay( - Field, new double3(0, 0, 20), new double3(0, 0, -1), 30, - Scale, new PlanetaryRadialBounds(-1, 1), source, out var hit)); - Assert.InRange(hit.Distance, 8.99999, 9.00001); - Assert.InRange(hit.PlanetLocalPosition.z, 10.99999, 11.00001); - var clearance = PlanetaryQueries.SegmentClearance( - Field, new double3(-2, 0, 12), new double3(2, 0, 12), 0.1, Scale, source); - Assert.True(clearance > 0.8); - } - - [Fact] - public void GreatCircleAndRegionQueriesReturnVersionedSamples() - { - var source = new BulgedSphere(); - var path = PlanetaryQueries.SampleGreatCircle(Field, new float3(1, 0, 0), new float3(0, 1, 0), 8, Scale, source); - Assert.Equal(9, path.Length); - Assert.Equal(MathF.PI * 5, path[^1].Distance, 4); - Assert.All(path, point => Assert.Equal(Field.FieldVersion, point.Surface.FieldVersion)); - var summary = PlanetaryQueries.Summarize(Field, path.Select(point => point.Surface.UnitDirection).ToArray(), Scale, source); - Assert.True(summary.MaximumDisplacement >= summary.MinimumDisplacement); - Assert.True(summary.MaximumSlope >= 0); - } - - private readonly struct BulgedSphere : IPlanetaryBaseField - { - public PlanetaryBaseFieldSample Sample(float3 direction) - { - var displacement = direction.z; - var gradient = new float3(0, 0, 1) - direction * direction.z; - return new(displacement, gradient, 0, float3.zero, 0); - } - } -} diff --git a/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta b/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta deleted file mode 100644 index d72b2cb6..00000000 --- a/tests/CultMath.Tests/PlanetaryQueryTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 93cf8771fdd520b41a4e931a6566a80a \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs deleted file mode 100644 index d0263b67..00000000 --- a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryRadialRefinementTests -{ - [Fact] - public void FourBoundedStepsConvergeFromTheRasterCaptureShell() - { - var center=new float3(0.3f,-0.2f,0.7f); - var radial=math.normalize(new float3(0.2f,-0.4f,0.9f)); - var targetRadius=3.85f; - var target=center+radial*targetRadius; - var origin=target+math.normalize(radial+new float3(0.12f,-0.05f,0.02f))*6.0f; - var position=center+radial*(targetRadius+0.30f); - var ray=math.normalize(position-origin); - for(var step=0;step<4;step++) - position=PlanetaryRadialRefinement.Step(position,ray,center,targetRadius); - Assert.InRange(math.abs(math.length(position-center)-targetRadius),0.0f,1.0e-4f); - } - - [Fact] - public void StepClampsWeakDerivativeCorrections() - { - var position=new float3(0,0,4.1f); - var result=PlanetaryRadialRefinement.Step(position,new float3(1,0,0),float3.zero,3.8f); - Assert.InRange(math.length(result-position),0.07999f,0.08001f); - } -} diff --git a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta b/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta deleted file mode 100644 index b9f3bea9..00000000 --- a/tests/CultMath.Tests/PlanetaryRadialRefinementTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: d34c0018ee2b700448b16540eca8ebcf \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs b/tests/CultMath.Tests/PlanetaryResidencyTests.cs deleted file mode 100644 index d2b89b7a..00000000 --- a/tests/CultMath.Tests/PlanetaryResidencyTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryResidencyTests -{ - private static readonly PlanetaryFieldDefinition Field = new( - 1, 8.4f, 0, AdvancedErosionParameters.Default with { Scale = 0.63f, Strength = 0.12f, Octaves = 7 }); - private static readonly PlanetaryLodParameters Lod = new(5, 17, 2, 720, 8.4f / 65536); - - [Fact] - public void SelectorReturnsSixRootsAndOneAncestorChain() - { - var direction = math.normalize(new float3(-0.2f, 0.91f, 0.37f)); - var selected = PlanetaryLodSelector.SelectAncestorChain(Field, direction, 8.45f, Lod); - Assert.Equal(6, selected.Count(tile => tile.Level == 0)); - var descendants = selected.Where(tile => tile.Level > 0).OrderBy(tile => tile.Level).ToArray(); - for (var i = 1; i < descendants.Length; i++) Assert.Equal(descendants[i - 1], descendants[i].Parent()); - if (descendants.Length > 0) Assert.Equal(PlanetaryTopology.TileAt(direction, descendants[^1].Level), descendants[^1]); - } - - [Fact] - public void ResidencyFadesResidualsWithoutChangingContentDuringPresentation() - { - var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0); - var child = root.Child(1, 0); - var residency = new PlanetaryResidualResidency(); - var arrival = residency.Update(new[] { root, child }, 0, 1); - var midpoint = residency.Update(new[] { root, child }, 0.5f, 1); - var settled = residency.Update(new[] { root, child }, 1, 1); - Assert.Equal(arrival.ContentVersion, midpoint.ContentVersion); - Assert.Equal(midpoint.ContentVersion, settled.ContentVersion); - Assert.Equal(0, arrival.Tiles.Single(tile => tile.Tile == child).Blend); - Assert.Equal(0.5f, midpoint.Tiles.Single(tile => tile.Tile == child).Blend); - Assert.Equal(1, settled.Tiles.Single(tile => tile.Tile == child).Blend); - - var leaving = residency.Update(new[] { root }, 1.25f, 1); - Assert.True(leaving.Tiles.Single(tile => tile.Tile == child).Departing); - Assert.Equal(1, leaving.Tiles.Single(tile => tile.Tile == child).Blend); - var gone = residency.Update(new[] { root }, 2.25f, 1); - Assert.DoesNotContain(gone.Tiles, tile => tile.Tile == child); - } - - [Fact] - public void ReversingAnArrivalDepartsFromItsCurrentWeightWithoutJumping() - { - var root = new PlanetaryTileAddress(PlanetaryCubeFace.PositiveX, 0, 0, 0); - var child = root.Child(0, 1); - var residency = new PlanetaryResidualResidency(); - _ = residency.Update(new[] { root, child }, 0, 1); - var arriving = residency.Update(new[] { root, child }, 0.25f, 1); - var departing = residency.Update(new[] { root }, 0.25f, 1); - Assert.Equal(0.25f, arriving.Tiles.Single(tile => tile.Tile == child).Blend); - Assert.Equal(0.25f, departing.Tiles.Single(tile => tile.Tile == child).Blend); - Assert.True(departing.Tiles.Single(tile => tile.Tile == child).Departing); - } -} diff --git a/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta b/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta deleted file mode 100644 index be7de2b4..00000000 --- a/tests/CultMath.Tests/PlanetaryResidencyTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 9ece3b02dbd3d7448baabee7f1fba0ab \ No newline at end of file diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs b/tests/CultMath.Tests/PlanetaryTopologyTests.cs deleted file mode 100644 index 759928d8..00000000 --- a/tests/CultMath.Tests/PlanetaryTopologyTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class PlanetaryTopologyTests -{ - [Fact] - public void FaceDirectionRoundTripsAcrossEveryFace() - { - foreach (var face in Enum.GetValues()) - foreach (var u in new[] { -0.9, -0.25, 0.0, 0.4, 0.9 }) - foreach (var v in new[] { -0.85, -0.1, 0.3, 0.88 }) - { - var direction = PlanetaryTopology.Direction(new(face, u, v)); - var roundTrip = PlanetaryTopology.FaceCoordinate(direction); - Assert.Equal(face, roundTrip.Face); - Assert.Equal(u, roundTrip.U, 5); - Assert.Equal(v, roundTrip.V, 5); - } - } - - [Fact] - public void SiblingPagesShareBitStableBoundaryDirections() - { - var left = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 0, 0), 17, 2); - var right = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveZ, 1, 1, 0), 17, 2); - for (var y = 0; y < left.InteriorSize; y++) - { - var a = PlanetaryPageSampling.Direction(left, left.BorderSize + left.InteriorSize - 1, left.BorderSize + y); - var b = PlanetaryPageSampling.Direction(right, right.BorderSize, right.BorderSize + y); - Assert.Equal(a, b); - } - } - - [Fact] - public void DirectionSelectsContainingTileAndLocalCoordinate() - { - var expected = new PlanetaryTileAddress(PlanetaryCubeFace.NegativeY, 5, 19, 7); - var direction = PlanetaryPageSampling.DirectionAtLocal(new(expected, 17, 2), 0.37, 0.61); - Assert.Equal(expected, PlanetaryTopology.TileAt(direction, expected.Level)); - Assert.True(PlanetaryTopology.TryLocalCoordinate(direction, expected, out var local)); - Assert.Equal(0.37f, local.x, 4); - Assert.Equal(0.61f, local.y, 4); - } - - [Fact] - public void BorderSamplesDoNotRequireNeighborResidency() - { - var page = new PlanetaryPageLayout(new(PlanetaryCubeFace.PositiveX, 0, 0, 0), 17, 3); - var edge = PlanetaryPageSampling.Direction(page, page.BorderSize + page.InteriorSize - 1, page.BorderSize + 8); - var border = PlanetaryPageSampling.Direction(page, page.StorageSize - 1, page.BorderSize + 8); - Assert.NotEqual(edge, border); - Assert.Equal(1, math.length(border), 5); - } - - [Fact] - public void NormalizedCubeBaselineSharesFaceEdges() - { - var a = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveZ, 1, -0.4)); - var b = PlanetaryTopology.NormalizedCubeDirection(new(PlanetaryCubeFace.PositiveX, -1, -0.4)); - Assert.InRange(math.distance(a, b), 0, 1.0e-6f); - } -} diff --git a/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta b/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta deleted file mode 100644 index b6930142..00000000 --- a/tests/CultMath.Tests/PlanetaryTopologyTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: be3b0ec07fe65344db889d1e98448113 \ No newline at end of file diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs b/tests/CultMath.Tests/SphericalErosionTests.cs deleted file mode 100644 index f634ba6d..00000000 --- a/tests/CultMath.Tests/SphericalErosionTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Xunit; - -namespace CultMath.Tests; - -public sealed class SphericalErosionTests -{ - [Fact] - public void SampleIsDeterministicAndScaleInvariant() - { - var parameters = SphericalErosionParameters.Default with { Seed = 42.0f }; - var a = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); - var repeated = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); - var b = SphericalErosion.Sample(new float3(3.1f, -7.4f, 5.9f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters); - - Assert.Equal(a, repeated); - Assert.InRange(math.abs(a.HeightOffset-b.HeightOffset),0.0f,1.0e-6f); - Assert.InRange(math.abs(a.Ridge-b.Ridge),0.0f,1.0e-6f); - Assert.InRange(math.abs(a.Gully-b.Gully),0.0f,5.0e-5f); - Assert.InRange(math.length(a.Gradient-b.Gradient),0.0f,5.0e-6f); - Assert.InRange(a.HeightOffset, -0.08f, 0.08f); - Assert.InRange(a.Ridge, 0.0f, 1.0f); - Assert.InRange(a.Gully, 0.0f, 1.0f); - } - - [Fact] - public void NearbySamplesRemainContinuousAcrossAChartBoundary() - { - var parameters = SphericalErosionParameters.Default; - var left = SphericalErosion.Sample(math.normalize(new float3(1.0f, 0.99999f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters); - var right = SphericalErosion.Sample(math.normalize(new float3(0.99999f, 1.0f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters); - - Assert.InRange(math.abs(left.HeightOffset - right.HeightOffset), 0.0f, 0.0002f); - } -} diff --git a/tests/CultMath.Tests/SphericalErosionTests.cs.meta b/tests/CultMath.Tests/SphericalErosionTests.cs.meta deleted file mode 100644 index 00ff1de0..00000000 --- a/tests/CultMath.Tests/SphericalErosionTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 60eb9e9e9f8ae2948b13403f5080168e \ No newline at end of file diff --git a/tools/CultMath.PlanetaryTileFixture.meta b/tools/CultMath.PlanetaryTileFixture.meta deleted file mode 100644 index 7f40b6ae..00000000 --- a/tools/CultMath.PlanetaryTileFixture.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7197a2202273a9445b974bdd69109b1a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/unity/Runtime/PlanetaryPageUpload.cs b/unity/Runtime/PlanetaryPageUpload.cs deleted file mode 100644 index 1a61a707..00000000 --- a/unity/Runtime/PlanetaryPageUpload.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using UnityEngine; - -namespace CultMath.Unity; - -[StructLayout(LayoutKind.Sequential)] -public readonly struct PlanetaryPageInputGpu -{ - public PlanetaryPageInputGpu(Vector4 directionRadius, Vector4 sampling) - { - DirectionRadius = directionRadius; - Sampling = sampling; - } - - public readonly Vector4 DirectionRadius; - public readonly Vector4 Sampling; -} - -[StructLayout(LayoutKind.Sequential)] -public readonly struct PlanetaryPageMetadataGpu -{ - public PlanetaryPageMetadataGpu(Vector4 address, Vector4 layout, Vector4 bounds, Vector4 state) - { - Address = address; - Layout = layout; - Bounds = bounds; - State = state; - } - - public readonly Vector4 Address; - public readonly Vector4 Layout; - public readonly Vector4 Bounds; - public readonly Vector4 State; -} - -public readonly struct PlanetaryPageUpload -{ - public PlanetaryPageUpload( - PlanetaryPageInputGpu[] inputs, - PlanetaryPageMetadataGpu[] metadata, - int outputSampleCount, - ulong contentVersion, - ulong presentationVersion) - { - Inputs = inputs; - Metadata = metadata; - OutputSampleCount = outputSampleCount; - ContentVersion = contentVersion; - PresentationVersion = presentationVersion; - } - - public PlanetaryPageInputGpu[] Inputs { get; } - public PlanetaryPageMetadataGpu[] Metadata { get; } - public int OutputSampleCount { get; } - public ulong ContentVersion { get; } - public ulong PresentationVersion { get; } -} - -public static class PlanetaryPageUploadBuilder -{ - public static PlanetaryPageUpload Build( - in PlanetaryResidencySnapshot snapshot, - int interiorSize, - int borderSize, - float radius) - { - if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); - var inputs = new List(); - var metadata = new PlanetaryPageMetadataGpu[snapshot.Tiles.Length]; - foreach (var (resident, pageIndex) in snapshot.Tiles.Select((resident, index) => (resident, index))) - { - var layout = new PlanetaryPageLayout(resident.Tile, interiorSize, borderSize).Validate(); - var content = PlanetaryGpuPageBuilder.BuildContent(layout, radius); - var outputOffset = inputs.Count; - foreach (var input in content.Inputs) - { - inputs.Add(new( - ToUnity(input.DirectionRadius), - ToUnity(input.Sampling))); - } - var commonMetadata = PlanetaryGpuPageBuilder.Metadata(content, outputOffset, resident.Blend); - metadata[pageIndex] = new( - ToUnity(commonMetadata.Address), - ToUnity(commonMetadata.Layout), - ToUnity(commonMetadata.Bounds), - ToUnity(commonMetadata.State)); - } - return new(inputs.ToArray(), metadata, inputs.Count, snapshot.ContentVersion, snapshot.PresentationVersion); - } - - private static Vector4 ToUnity(float4 value) => new(value.x, value.y, value.z, value.w); -} diff --git a/unity/Runtime/PlanetaryPageUpload.cs.meta b/unity/Runtime/PlanetaryPageUpload.cs.meta deleted file mode 100644 index 87bcb61a..00000000 --- a/unity/Runtime/PlanetaryPageUpload.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7edd3bb2edb2e6448a2d54fc082c27dc \ No newline at end of file diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs b/unity/Runtime/PlanetaryPatchMeshAdapter.cs deleted file mode 100644 index 69a72a2a..00000000 --- a/unity/Runtime/PlanetaryPatchMeshAdapter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.Rendering; - -namespace CultMath.Unity; - -public static class PlanetaryPatchMeshAdapter -{ - public static Mesh CreateFaceMesh(PlanetaryCubeFace face, int cellsPerAxis, float radius = 1) - { - if (!float.IsFinite(radius) || radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius)); - var source = PlanetaryPatch.CreateFace(face, cellsPerAxis); - var vertices = new Vector3[source.Vertices.Length]; - var normals = new Vector3[source.Vertices.Length]; - var uv = new Vector2[source.Vertices.Length]; - for (var i = 0; i < source.Vertices.Length; i++) - { - var direction = source.Vertices[i].UnitDirection; - var normal = new Vector3(direction.x, direction.y, direction.z); - vertices[i] = normal * radius; - normals[i] = normal; - uv[i] = new Vector2(source.Vertices[i].LocalCoordinate.x, source.Vertices[i].LocalCoordinate.y); - } - var mesh = new Mesh - { - name = $"CultMath Planetary {face} {cellsPerAxis}x{cellsPerAxis}", - indexFormat = vertices.Length > ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16, - vertices = vertices, - normals = normals, - uv = uv, - triangles = source.Indices, - }; - mesh.RecalculateBounds(); - return mesh; - } -} diff --git a/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta b/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta deleted file mode 100644 index 69af0abc..00000000 --- a/unity/Runtime/PlanetaryPatchMeshAdapter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 9ad7b581e14ef774d8435069bb77ca6a \ No newline at end of file diff --git a/web/planetary-tile.cross-runtime.test.mjs b/web/planetary-tile.cross-runtime.test.mjs deleted file mode 100644 index 2b457219..00000000 --- a/web/planetary-tile.cross-runtime.test.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; -import { decodePlanetarySurfaceTile } from "./planetary-tile.ts"; - -if (process.argv.length !== 3) throw new Error("Expected a generated CMPT fixture path"); -const bytes = await readFile(process.argv[2]); -const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); -const tile = decodePlanetarySurfaceTile(arrayBuffer); -assert.equal(tile.header.projectionVersion, 3); -assert.equal(tile.header.layerId, 29); -assert.equal(tile.header.projectionKind, 2); -assert.equal(tile.header.interiorSize, 5); -assert.equal(tile.header.borderSize, 1); -assert.equal(tile.storageSize, 7); -assert.equal(tile.samples.length, 49); -assert.ok(tile.samples.some(sample => sample !== null)); -for (const sample of tile.samples) { - if (sample === null) continue; - assert.ok(Number.isFinite(sample.radialDisplacement)); - assert.ok(Number.isFinite(sample.unresolvedHeightBound)); -} diff --git a/web/planetary-tile.cross-runtime.test.mjs.meta b/web/planetary-tile.cross-runtime.test.mjs.meta deleted file mode 100644 index 3c38da54..00000000 --- a/web/planetary-tile.cross-runtime.test.mjs.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 7bc397e5660d2b6448919c41983d35e8 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/web/planetary-tile.test.mjs b/web/planetary-tile.test.mjs deleted file mode 100644 index cb054f28..00000000 --- a/web/planetary-tile.test.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import assert from "node:assert/strict"; -import { decodePlanetarySurfaceTile } from "./planetary-tile.ts"; - -const bytes = new ArrayBuffer(152); -const view = new DataView(bytes); -let offset = 0; -const u8 = value => view.setUint8(offset++, value); -const u32 = value => { view.setUint32(offset, value, true); offset += 4; }; -const i32 = value => { view.setInt32(offset, value, true); offset += 4; }; -const u64 = value => { view.setBigUint64(offset, value, true); offset += 8; }; -const f32 = value => { view.setFloat32(offset, value, true); offset += 4; }; -const f64 = value => { view.setFloat64(offset, value, true); offset += 8; }; - -for (const character of "CMPT") u8(character.charCodeAt(0)); -u32(1); u64(0x1234n); u32(2); u32(17); i32(0); -f64(0); f64(0); f64(1); -i32(0); i32(0); i32(0); i32(2); i32(0); -f32(10); f32(0.5); i32(4); -u8(1); -for (const value of [1, 0, 0]) f32(value); -f32(8.4); f32(0.25); -for (const value of [0, 0.1, 0]) f32(value); -for (const value of [0.99, -0.1, 0]) f32(value); -for (const value of [0.1, 0.8, 0.2, 2.5, 0.05]) f32(value); -u8(0); u8(0); u8(0); -assert.equal(offset, bytes.byteLength); - -const tile = decodePlanetarySurfaceTile(bytes); -assert.equal(tile.header.fieldVersion, 0x1234n); -assert.equal(tile.header.layerId, 17); -assert.equal(tile.storageSize, 2); -assert.equal(tile.samples.length, 4); -assert.equal(tile.samples[0]?.radialDisplacement, 0.25); -assert.equal(tile.samples[1], null); - -assert.throws(() => decodePlanetarySurfaceTile(bytes.slice(0, bytes.byteLength - 1)), /Truncated/); -const trailing = new Uint8Array(bytes.byteLength + 1); -trailing.set(new Uint8Array(bytes)); -assert.throws(() => decodePlanetarySurfaceTile(trailing.buffer), /trailing/); diff --git a/web/planetary-tile.test.mjs.meta b/web/planetary-tile.test.mjs.meta deleted file mode 100644 index d622095b..00000000 --- a/web/planetary-tile.test.mjs.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 04c93ecab90d465499aa3d60722e6fc3 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/web/planetary-tile.ts b/web/planetary-tile.ts deleted file mode 100644 index e68d70fa..00000000 --- a/web/planetary-tile.ts +++ /dev/null @@ -1,89 +0,0 @@ -export const CULTMATH_PLANETARY_TILE_VERSION = 1; - -export interface PlanetaryTileHeader { - fieldVersion: bigint; - projectionVersion: number; - layerId: number; - projectionKind: number; - centerLongitude: number; - centerLatitude: number; - projectionScale: number; - level: number; - x: number; - y: number; - interiorSize: number; - borderSize: number; - footprintMeters: number; - maximumUnresolvedHeight: number; -} - -export interface PlanetaryTileSample { - direction: readonly [number, number, number]; - radius: number; - radialDisplacement: number; - tangentGradient: readonly [number, number, number]; - surfaceNormal: readonly [number, number, number]; - slope: number; - ridge: number; - gully: number; - finestResolvedWavelength: number; - unresolvedHeightBound: number; -} - -export interface PlanetarySurfaceTile { - header: PlanetaryTileHeader; - storageSize: number; - samples: readonly (PlanetaryTileSample | null)[]; -} - -export function decodePlanetarySurfaceTile(bytes: ArrayBuffer): PlanetarySurfaceTile { - const view = new DataView(bytes); - let offset = 0; - const u8 = () => view.getUint8(offset++); - const u32 = () => { const value = view.getUint32(offset, true); offset += 4; return value; }; - const i32 = () => { const value = view.getInt32(offset, true); offset += 4; return value; }; - const f32 = () => { const value = view.getFloat32(offset, true); offset += 4; return value; }; - const f64 = () => { const value = view.getFloat64(offset, true); offset += 8; return value; }; - const u64 = () => { const value = view.getBigUint64(offset, true); offset += 8; return value; }; - const vector3 = (): [number, number, number] => [f32(), f32(), f32()]; - const requireRemaining = (count: number) => { - if (offset + count > view.byteLength) throw new Error("Truncated CultMath planetary tile"); - }; - - requireRemaining(8); - if (String.fromCharCode(u8(), u8(), u8(), u8()) !== "CMPT") throw new Error("Not a CultMath planetary tile"); - const version = u32(); - if (version !== CULTMATH_PLANETARY_TILE_VERSION) throw new Error(`Unsupported CultMath planetary tile version ${version}`); - requireRemaining(76); - const header: PlanetaryTileHeader = { - fieldVersion: u64(), - projectionVersion: u32(), - layerId: u32(), - projectionKind: i32(), - centerLongitude: f64(), - centerLatitude: f64(), - projectionScale: f64(), - level: i32(), x: i32(), y: i32(), interiorSize: i32(), borderSize: i32(), - footprintMeters: f32(), maximumUnresolvedHeight: f32(), - }; - const count = i32(); - const storageSize = header.interiorSize + header.borderSize * 2; - if (count !== storageSize * storageSize) throw new Error("CultMath planetary tile sample count does not match layout"); - const samples: (PlanetaryTileSample | null)[] = new Array(count); - for (let index = 0; index < count; index++) { - requireRemaining(1); - if (u8() === 0) { samples[index] = null; continue; } - requireRemaining(64); - samples[index] = { - direction: vector3(), - radius: f32(), - radialDisplacement: f32(), - tangentGradient: vector3(), - surfaceNormal: vector3(), - slope: f32(), ridge: f32(), gully: f32(), - finestResolvedWavelength: f32(), unresolvedHeightBound: f32(), - }; - } - if (offset !== view.byteLength) throw new Error("CultMath planetary tile has trailing bytes"); - return { header, storageSize, samples }; -} diff --git a/web/planetary-tile.ts.meta b/web/planetary-tile.ts.meta deleted file mode 100644 index 337da3bd..00000000 --- a/web/planetary-tile.ts.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: f284e64628320844196b7aaea76475a4 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 69856d4f3aee6594bf5d770c43330f5b9322389c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 22 Jul 2026 04:29:59 +0100 Subject: [PATCH 019/203] Set CultMath release version 0.1.0 --- src/CultMath/CultMath.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index 01901b6b..73b3fd51 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -8,6 +8,7 @@ CultMath CultMath CultMath + 0.1.0 Clean-room C# math shaped for HLSL-style renderer code. GameCult https://github.com/GameCult/CultMath From 9a5597d233eb6c3ae25dccf17b77b7db99dfe17a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 22 Jul 2026 04:52:09 +0100 Subject: [PATCH 020/203] Add precompiled CultMath Unity package --- .gitignore | 1 + README.md | 21 +- scripts/build-unity-package.ps1 | 107 +++++++ unity/Runtime/CultMath.Unity.asmdef | 16 - unity/org.gamecult.cultmath/LICENSE.md | 10 + unity/org.gamecult.cultmath/README.md | 28 ++ .../{ => org.gamecult.cultmath}/Runtime.meta | 2 +- .../Runtime/CultMath.asmdef | 16 + .../Runtime/CultMath.asmdef.meta} | 2 +- .../Runtime/Plugins.meta | 8 + .../Runtime/Plugins/CultMath.dll | Bin 0 -> 44544 bytes .../Runtime/Plugins/CultMath.dll.meta | 21 ++ .../Runtime/Plugins/CultMath.pdb | Bin 0 -> 23424 bytes .../Runtime/Plugins/CultMath.pdb.meta | 4 +- unity/org.gamecult.cultmath/Shaders.meta | 8 + .../Shaders/CultMath.hlsl | 302 ++++++++++++++++++ .../Shaders/CultMath.hlsl.meta | 7 + .../THIRD-PARTY-NOTICES.md | 11 + .../org.gamecult.cultmath/package.json | 11 +- 19 files changed, 542 insertions(+), 33 deletions(-) create mode 100644 scripts/build-unity-package.ps1 delete mode 100644 unity/Runtime/CultMath.Unity.asmdef create mode 100644 unity/org.gamecult.cultmath/LICENSE.md create mode 100644 unity/org.gamecult.cultmath/README.md rename unity/{ => org.gamecult.cultmath}/Runtime.meta (77%) create mode 100644 unity/org.gamecult.cultmath/Runtime/CultMath.asmdef rename unity/{Runtime/CultMath.Unity.asmdef.meta => org.gamecult.cultmath/Runtime/CultMath.asmdef.meta} (76%) create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins.meta create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb rename package.json.meta => unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta (60%) create mode 100644 unity/org.gamecult.cultmath/Shaders.meta create mode 100644 unity/org.gamecult.cultmath/Shaders/CultMath.hlsl create mode 100644 unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta create mode 100644 unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md rename package.json => unity/org.gamecult.cultmath/package.json (52%) diff --git a/.gitignore b/.gitignore index 3779002b..c9b141d0 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 b471c7fd..02d47752 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 00000000..329cedb7 --- /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 632c4e4c..00000000 --- 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 00000000..5e50f2d6 --- /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 00000000..1802ae9a --- /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 8e0678f8..b0e76599 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 00000000..72c968e8 --- /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 2dd3a36a..0ec09c2f 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 00000000..fe8acafa --- /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 0000000000000000000000000000000000000000..8cdb6700145956da7fb303fc1d9b0a39f19c91a4 GIT binary patch literal 44544 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 literal 0 HcmV?d00001 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 23ed71cf..e18fedbe 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 00000000..1e7e7110 --- /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 00000000..4c00fb69 --- /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 00000000..d3b6d2b3 --- /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 00000000..c283df2b --- /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 6da2a17a..e45d0a35 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" + ] } From b43cf4904f855e3d1b6da8a949326aa031bc5219 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 22 Jul 2026 05:07:32 +0100 Subject: [PATCH 021/203] Fix CultMath Unity plugin references --- scripts/build-unity-package.ps1 | 20 ++++++++++++++---- unity/org.gamecult.cultmath/LICENSE.md.meta | 2 ++ unity/org.gamecult.cultmath/README.md | 4 ++-- unity/org.gamecult.cultmath/README.md.meta | 2 ++ .../Runtime/CultMath.asmdef | 16 -------------- .../Runtime/CultMath.asmdef.meta | 7 ------ .../Runtime/Plugins/CultMath.dll | Bin 44544 -> 44544 bytes .../Runtime/Plugins/CultMath.dll.meta | 2 +- .../Runtime/Plugins/CultMath.pdb | Bin 23424 -> 23420 bytes .../THIRD-PARTY-NOTICES.md.meta | 2 ++ unity/org.gamecult.cultmath/package.json.meta | 2 ++ 11 files changed, 27 insertions(+), 30 deletions(-) create mode 100644 unity/org.gamecult.cultmath/LICENSE.md.meta create mode 100644 unity/org.gamecult.cultmath/README.md.meta delete mode 100644 unity/org.gamecult.cultmath/Runtime/CultMath.asmdef delete mode 100644 unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta create mode 100644 unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md.meta create mode 100644 unity/org.gamecult.cultmath/package.json.meta diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1 index 329cedb7..f5fcacc0 100644 --- a/scripts/build-unity-package.ps1 +++ b/scripts/build-unity-package.ps1 @@ -41,11 +41,13 @@ foreach ($path in @($freshAssembly, $freshSymbols)) { $requiredTemplateFiles = @( "LICENSE.md", + "LICENSE.md.meta", "README.md", + "README.md.meta", "THIRD-PARTY-NOTICES.md", + "THIRD-PARTY-NOTICES.md.meta", "package.json", - "Runtime\CultMath.asmdef", - "Runtime\CultMath.asmdef.meta", + "package.json.meta", "Runtime\Plugins\CultMath.dll", "Runtime\Plugins\CultMath.dll.meta", "Runtime\Plugins\CultMath.pdb", @@ -72,6 +74,14 @@ if ($missingAssetMetas.Count -ne 0 -or $missingDirectoryMetas.Count -ne 0) { } $manifest = Get-Content -LiteralPath (Join-Path $templateRoot "package.json") -Raw | ConvertFrom-Json +$pluginMeta = Get-Content -LiteralPath ($trackedAssembly + ".meta") -Raw +if ($pluginMeta -notmatch '(?m)^\s*isExplicitlyReferenced:\s*0\s*$') { + throw "CultMath.dll must be auto-referenced; explicit-only plugins do not reach dependent Unity asmdefs" +} +$facadeDefinitions = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.asmdef") +if ($facadeDefinitions.Count -ne 0) { + throw "CultMath's precompiled DLL is the Unity assembly authority; empty asmdef facades are forbidden" +} $expectedFileVersion = "$($manifest.version).0" $trackedFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($trackedAssembly).FileVersion $freshFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($freshAssembly).FileVersion @@ -97,11 +107,13 @@ Copy-Item -Path (Join-Path $templateRoot "*") -Destination $outputRoot -Recurse $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" +$stagedFacades = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.asmdef") +if ($stagedAssemblies.Count -ne 1 -or $stagedAssemblies[0].Name -ne "CultMath.dll" -or $stagedSources.Count -ne 0 -or $stagedFacades.Count -ne 0) { + throw "CultMath package must contain one owned auto-referenced DLL, no C# source files, and no asmdef facade" } 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)" +Write-Host "Asmdef facades: $($stagedFacades.Count)" diff --git a/unity/org.gamecult.cultmath/LICENSE.md.meta b/unity/org.gamecult.cultmath/LICENSE.md.meta new file mode 100644 index 00000000..c234cd28 --- /dev/null +++ b/unity/org.gamecult.cultmath/LICENSE.md.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7172bce28532878409e57f5fbbb12719 \ No newline at end of file diff --git a/unity/org.gamecult.cultmath/README.md b/unity/org.gamecult.cultmath/README.md index 1802ae9a..223e16ce 100644 --- a/unity/org.gamecult.cultmath/README.md +++ b/unity/org.gamecult.cultmath/README.md @@ -6,8 +6,8 @@ precompiled `CultMath.dll`, portable symbols, and the numeric 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. +imports only the tracked auto-referenced precompiled assembly, so consumers do +not need `csc.rsp` language overrides or an asmdef propagation shim. Build and inspect the package from the repository root: diff --git a/unity/org.gamecult.cultmath/README.md.meta b/unity/org.gamecult.cultmath/README.md.meta new file mode 100644 index 00000000..0ea270c2 --- /dev/null +++ b/unity/org.gamecult.cultmath/README.md.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7429934026d747489a3876fd15576e5 \ No newline at end of file diff --git a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef deleted file mode 100644 index 72c968e8..00000000 --- a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "CultMath", - "rootNamespace": "CultMath", - "references": [], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": true, - "precompiledReferences": [ - "CultMath.dll" - ], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": true -} diff --git a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta deleted file mode 100644 index 0ec09c2f..00000000 --- a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 4b5c49d478124399a5310d7a9d8ed166 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll index 8cdb6700145956da7fb303fc1d9b0a39f19c91a4..91b51d8d33ed8fcbc53a66b07131e2442f17c464 100644 GIT binary patch delta 237 zcmZp;!_;txX+j6fe6!yD8+&SI3Md?&m2hM4B;O@kA8G1bU4B{@0G&^*Z;h*FX)EmP7`4b2lL=PZ4|A~n_c#^iuy0Rl^PQ$L;D z?&myz@rr5F%r?HBykVKI0#xv`JX8>X6bNim|0qwnx&Ye z87HQunweUfB&C_An423Un;07#8>E>gSsEJ|8CzH;PtIBTfaR_B{x6dQmIVlCu1PV9 zi%@ZzAR}tDy=kS#VxcJanfKBdd7u~~MxH;ca+gC&CngDHa< zLkfclLmGoILn1>e5SjtSEP*0PKrvIGoH-C0FeC#-jKR~ZW6WR#gvLNqEE$qF JZ(r@n1OOHGN4Wq1 diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta index d9935b36..917e3849 100644 --- a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta +++ b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta @@ -8,7 +8,7 @@ PluginImporter: defineConstraints: [] isPreloaded: 0 isOverridable: 0 - isExplicitlyReferenced: 1 + isExplicitlyReferenced: 0 validateReferences: 1 platformData: - first: diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb index ed00f36e044b3063d654af2d537f4821c4c253f6..711cf76c53ef3d56a5f07fa25acb9c8532c93e3d 100644 GIT binary patch delta 2168 zcmZwHdr(wm7{~GV?5cZV5vhbEO^nMfEXZX+1T7Q@uprbSico{yMP=|-Nr^+PNMeIY zAW2>tBzIjeg+Z8Dc}t5DvqYT4%1B8{i7X`@%}d|I@@HrF^ZuUMbIx;~**WvBXpwI3 zm8zEbmP8BTm#|>5SqQN$H2>LrnYLR9GKAQWP!-ldV0wV+2|lc(@CC`8}DMM`m7@1is)g-kohlk_xjv0sE3 zDjqbrj3s4(h(|Kgk%=6bQ6S&tn>ti1G01Za<7St!q|7#`oLq}K?8i~Gp&gy*LeD_6 zq+z0OpqWbCHn=E*R6?eOAA%8qiHJus(vgWAn1kf0Y${O@Bu}NLk&7}&O(V5fHpoV< z!8+Kn3!mW#I`9K}aTPb=9jq1)BLE>VAr4cKiVS37b})~vu_{K{)WRy)Lcgs#a_<>cnn*jQu!@HngJ?UFbm{`tdIm zAv&SK2zcXR1Rw+^#9=B@k%27Ch82Z)5yel`P0w)@<2cO{xj^QNE z;AdRKAGn5FkjCqT8XoY%7z802Q}NVzolRs>%)wkN!pnFK<)}n0>fpp~?8QN};#*wC zRosFuOeY?|X!s)pQJ9P*q#+ZtV8wzkn@%jHD8cLKg(6%hLNNu?FcUdg2pg(l$NSia z!}uB}VTjO)a7@G$6h_D|uDhs?aM?i0whVH5QaI6okVto7iX3<=-9=pFaFK-Rcm@`f zp#qz+6+6+0797NvXpi&|o#;Xj5=|b$hJMV5@(>pMgF6@+?IGr2DZ974ttf@Whokv< zb%D>MD^YSSiF$vKUliP$ek|}fSof~=QzVAG6IALqdw3MzJQVj(feD=;^;g zr!TZ3GlCkj-nqD!E>i+u|z zv&KKO>ap1&JC<+vO5OfsL*}DlySDDo&Th#1)LC5Gy>M~t?+$BLe4{mg?WxLnKRwjc zA9CdAwQD&w^~s-?d7oZ2-+#%yReisPraf0ZzV2eN#gYEBbLDZ{p)B-OKV#`AZo!+RzL`Y+iJ9Y>YM1WXd;Bu$c1lBcrVr z%iM??+D9Ug|EfuWQVg(9A2V0f5eW_U;&6$B@ZQ$8#K1tg4!f=hBdr3Mb` zLmF%5f@qm%H+2+aL!e1fv-DWtk%|&=E8t)ot%`fhW%DRyu=~CYe|GPA{XXAw@BMx6 z@7#0G@3#|DO}n(~rO+CS5aDU%6-7db==bf31vwFEyMhA~+c&*uyQM#Sr)^iJlok>p ziI4FaKF7sKNob-Z;jfiMCMvK~O9!D4&04n)N!drZsjXk82-PXX$izb)Lg?I-q|AR( zAQ`CR;c%ny62UsRa8ee7bS%IkeiDpVZjhm8``9q6%STGCeSb#;yK_N<_tK!&!9V8w}z$?qUoxVthmxqG3QXtjNIASb{t(i{Z8Pc14iGN0iF7P_YI# zB`K?#^h%6M$Tp%Dd(gn3t9$L|kKKv6ma2t0q22Z_8Jb;G~q?g|}dDNFH0&OZ0 zI&sZcBO<>6dHWthd*k~)k)Pq*8J)CB|0|me!gtL5U};C$1j>s z?;bv!-~Yxd$=^jxUA))1HFit+KI?Alj>BznUw7>2RE^*3=r8&1mY4eV!HAIR6)8Qs ztj2^V{53sOyW8pw%@?N}+BM#q8{C;+9Cq|whw4$|`E0RVY+SMB})OtGobmYQmD`(x9lF@Nw%wtJT+U$P?1W#=_cfcGlqx`A# zz-iXPmX^jv8y24X(PA9AR#!K2VsB^9lWPXrzpT6W)E@hgv9K!e+tq1v?msrz-|=7j zCttf_<{#{+O4a}Ul-1hZ8~fGI@9*3D`ENILvvZq9P9z8Ct!=a?w{~~=9uE5Iq^W2) zcgy|j*W1J9zOm`mgrk0!Y~PRT`!CLX%N(|PR@UV{+vSGWedDV)va#CCsBC`0oZKD8pzblxdhfVrbV;cxmL$8m#I$Vr@+3>Dxv0cq zPfj)zn@uK@p~O;@YD!ErrKA?Ss^g+U9*&TlddZn7IiHoBFG$XE$yqHqce_q4nC>y< Wcka2aWzB!!cPF_tuJqhg#s2}kT^1(* diff --git a/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md.meta b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md.meta new file mode 100644 index 00000000..a53f08d8 --- /dev/null +++ b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0c88669e4d1db604588be2a726b7d231 \ No newline at end of file diff --git a/unity/org.gamecult.cultmath/package.json.meta b/unity/org.gamecult.cultmath/package.json.meta new file mode 100644 index 00000000..337ba716 --- /dev/null +++ b/unity/org.gamecult.cultmath/package.json.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 33d8d4af49bf0f946ad00a375f26b1d1 \ No newline at end of file From c4543b9443e96d848260ef00254d2d3c0fa6a9d2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 7 Aug 2026 20:02:24 +0100 Subject: [PATCH 022/203] Add canonical min-max rectangle --- src/CultMath/CultMath.csproj | 2 +- src/CultMath/rect.cs | 59 ++++++++++++++++++++++++ src/CultMath/rect.cs.meta | 2 + tests/CultMath.Tests/MathTests.cs | 35 ++++++++++++++ unity/org.gamecult.cultmath/package.json | 2 +- 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/CultMath/rect.cs create mode 100644 src/CultMath/rect.cs.meta diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index 73b3fd51..dfa5475d 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -8,7 +8,7 @@ CultMath CultMath CultMath - 0.1.0 + 0.1.1 Clean-room C# math shaped for HLSL-style renderer code. GameCult https://github.com/GameCult/CultMath diff --git a/src/CultMath/rect.cs b/src/CultMath/rect.cs new file mode 100644 index 00000000..51e05c19 --- /dev/null +++ b/src/CultMath/rect.cs @@ -0,0 +1,59 @@ +namespace CultMath; + +/// +/// Axis-aligned 2D bounds stored canonically as normalized minimum and maximum +/// corners. Point containment and intersection include the boundary. +/// +public readonly record struct rect +{ + public rect(float minX, float minY, float maxX, float maxY) + : this(new float2(minX, minY), new float2(maxX, maxY)) + { + } + + public rect(float2 min, float2 max) + { + this.min = new float2(MathF.Min(min.x, max.x), MathF.Min(min.y, max.y)); + this.max = new float2(MathF.Max(min.x, max.x), MathF.Max(min.y, max.y)); + } + + public float2 min { get; } + public float2 max { get; } + + public float width => max.x - min.x; + public float height => max.y - min.y; + public float2 size => max - min; + public float2 center => (min + max) * 0.5f; + public float area => width * height; + public bool isEmpty => width == 0.0f || height == 0.0f; + + public bool Contains(float2 point) => + point.x >= min.x && point.x <= max.x && + point.y >= min.y && point.y <= max.y; + + public bool Contains(rect other) => + other.min.x >= min.x && other.max.x <= max.x && + other.min.y >= min.y && other.max.y <= max.y; + + public bool Intersects(rect other) => + min.x <= other.max.x && max.x >= other.min.x && + min.y <= other.max.y && max.y >= other.min.y; + + public rect Intersection(rect other) + { + if (!Intersects(other)) + return new rect(0.0f, 0.0f, 0.0f, 0.0f); + + return new rect( + MathF.Max(min.x, other.min.x), + MathF.Max(min.y, other.min.y), + MathF.Min(max.x, other.max.x), + MathF.Min(max.y, other.max.y)); + } + + public rect Encapsulate(float2 point) => new( + MathF.Min(min.x, point.x), + MathF.Min(min.y, point.y), + MathF.Max(max.x, point.x), + MathF.Max(max.y, point.y)); +} diff --git a/src/CultMath/rect.cs.meta b/src/CultMath/rect.cs.meta new file mode 100644 index 00000000..0b04a5ce --- /dev/null +++ b/src/CultMath/rect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5aab0d8e1ca94d42b85bba780a7ee449 diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index ad268212..8d42a0c1 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -199,6 +199,41 @@ public sealed class MathTests Assert.Equal(new float2(2.0f, 0.0f), closest); } + [Fact] + public void RectNormalizesAndDerivesFromMinMax() + { + var bounds = new rect(8.0f, 6.0f, 2.0f, -2.0f); + + Assert.Equal(new float2(2.0f, -2.0f), bounds.min); + Assert.Equal(new float2(8.0f, 6.0f), bounds.max); + Assert.Equal(new float2(6.0f, 8.0f), bounds.size); + Assert.Equal(new float2(5.0f, 2.0f), bounds.center); + Assert.Equal(48.0f, bounds.area); + } + + [Fact] + public void RectContainmentIncludesBoundary() + { + var bounds = new rect(-2.0f, -1.0f, 4.0f, 3.0f); + + Assert.True(bounds.Contains(new float2(-2.0f, -1.0f))); + Assert.True(bounds.Contains(new float2(4.0f, 3.0f))); + Assert.True(bounds.Contains(new rect(-1.0f, 0.0f, 1.0f, 2.0f))); + Assert.False(bounds.Contains(new float2(4.001f, 3.0f))); + } + + [Fact] + public void RectIntersectionTreatsTouchingEdgesAsContact() + { + var left = new rect(0.0f, 0.0f, 4.0f, 4.0f); + var touching = new rect(4.0f, 1.0f, 8.0f, 3.0f); + var separate = new rect(4.001f, 1.0f, 8.0f, 3.0f); + + Assert.True(left.Intersects(touching)); + Assert.Equal(0.0f, left.Intersection(touching).area); + Assert.False(left.Intersects(separate)); + } + [Fact] public void FirstOrderInterceptSolvesSimpleClosingTarget() { diff --git a/unity/org.gamecult.cultmath/package.json b/unity/org.gamecult.cultmath/package.json index e45d0a35..d7039df7 100644 --- a/unity/org.gamecult.cultmath/package.json +++ b/unity/org.gamecult.cultmath/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultmath", "displayName": "CultMath", - "version": "0.1.0", + "version": "0.1.1", "unity": "2021.3", "description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.", "license": "MPL-2.0", From fcdddda72f6aa33ae6eeff3bce6a2d3e26eb16f3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 7 Aug 2026 20:37:37 +0100 Subject: [PATCH 023/203] Rebuild Unity package with rectangle API --- src/CultMath/CultMath.csproj | 2 +- .../Runtime/Plugins/CultMath.dll | Bin 44544 -> 47104 bytes .../Runtime/Plugins/CultMath.pdb | Bin 23420 -> 23936 bytes unity/org.gamecult.cultmath/package.json | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CultMath/CultMath.csproj b/src/CultMath/CultMath.csproj index dfa5475d..4e0027db 100644 --- a/src/CultMath/CultMath.csproj +++ b/src/CultMath/CultMath.csproj @@ -8,7 +8,7 @@ CultMath CultMath CultMath - 0.1.1 + 0.1.2 Clean-room C# math shaped for HLSL-style renderer code. GameCult https://github.com/GameCult/CultMath diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll index 91b51d8d33ed8fcbc53a66b07131e2442f17c464..379dad44e209aea24d0cd34f7cb28a1a490d2ae8 100644 GIT binary patch delta 17237 zcmcJX349bq+W()bp6Twnb3srJ!#zZf5E2N8kZ?#sz@Qwgk%!DB!E_?mr*r*U$HPs-8N! zy1VAUz$qU1nlE3}YT=!D8P_lQ{FZ>g0*(OyA@qY_}UhHfZfQR3+`mC+0Nictl4#2FoF2X#uo=WtgZmi zM*^_m{HMCq4&qI2F-CI5R$hn=+r8$||2P9R87su6*Z>3c$9>FSgoS*Z4nmyhDluK+ zKrTu&w;O=%mlbZjvVw4x8=~pCH(`zBCj4$uQyhu0100%Q^Im{h?q7r5a>fC*&;6^T zXax55>Saa8>dFclk=)-*WxD-F89I~iGs%qjr=Y7Dw%gNRik5$>DAA4kpTH)$e=X|B zSJ^D<>(jGH5ffPb(yH)0?ZW2AsoifCpNto#v|3QoV_m1qpF+XNV02p=;u9%N3ZRQBjUTt;LJ;kx`REBPq728OnEg#MkzCk zgUlF-$b{k`Bax<}>$Eh75(!0DqU+OaJvc6=9vc%p-KPQH#DWjSG;Pw20bGyQr7~>- zhpAFB_^s7(Kk4y?z3JB|l`a3va>(H4uPl@ApER)pkr{t4nV(@m*tBAQ8U zh7R30QhLQLioC;<$12n7;r+F(hbtR}VOFo7{9053#Y}@Y6ldGSs1yp_>&i-T9Xjz+ zzJ6YJ^A;GOqd4mt%)ztX5a;^(@Q=Qhd{|=n9az{&q)2ilikR1Sdt&*W7<5z6YnM?E zMjJV?XCM6NODvy+1>$Y0{{}p2q`H4{B7%d!W{x&ijSm}qUOD6KvG#Dh&8JWx zUW?Vj@v2YtPZu#qtRoz+t9q<0R>dq0(D#n$Lagq8DVW+n#kn0j@{yxeegF30r2dn< zjm5J>cGJJJ_DuhH#%2UB<|d&w93Zs&fCj8daOi+$NxQM?aAzD-{lXG=h@K<7_K|9{ zgl7|a4fpR3{%Js~m|Ym6JFo`H9N>oFTLZ37n2FsOg%v`X%1Ar}dfNOmgO>*6vOU3m z1DCRy!FLC?WV?eG2liwy1iRfD_Pt11#1r=bHj@-=ljmo%YH!c;FxEYoKgfS`0zMj> z&%;!BT<$}AaK*IJDHCw20Al(B%u&!cbTBL(h>X7G{f6fB!Q*P=Nx_4IZZz-=11>6f z39sB$*i&2&FbiKUeEHCa|3MPI#0H}azJ}vV_;5<;DD21!tK(mqE*S4GFi#!pQXy6o zdXv4a%Lnr?tqQa3k7z1bD zPlamhi;}WSyQXZhBE9^bdlgvbUhf`=v(ssC0P~rb6!n2(+ zylLP;OxE9o;6&oou^2dIbGdUY%CnDk3Bu{c*e>j94%#-NJR$`*aUxbiWCINXNCQ1% zT|+UV!+fNdv_qOn4x}k?NSlnO!iWQbw-Y+?p~tWX18j%1x%fosM6rB|B}iIF)BtiQ zR!P(tH-sWfh^Q&vnFJjpY6a_v))2LYL)4kKsWT~X+MwWHbkUnmFq4Ujw-IH)QX-c} z#Ja&MCPs0QDt3o$Y$4KU4PYqTjElDw9CvNAwSq<34%ClbyHLN?_tHVmxx`XYo4GY|*97JuS3q3^kI;XH7^&Z4@ zjg;3Z>Fb!TpYsUnNZXO0`5F8H%ZTXP6n&ebqC%f6`Nq^roZ&J`?HBCc$dwWvq8f(mAl(Ic#eV_*V(%psC#nt6P}l zVd-`s%xN8=G4@25BqZw6}tZgs;I?trc$# zJBa2(h5H$MYly+e2#!g=;N5QbLnBFNk&>Ym(QB|rIR|YaPf|1Y3--1!f#?it=6>1U z4jv=A4`MtA?Cs$hq7@BPmvO|N0td|?j__@y&rH&nJ5Sov;2)AIou`ogEoqK+(w+`K zNLrwsLQ-!uXXYe3Y3~ePNguIONQsj6Vyp|?An8?h%0LvmVgPgW8BjrFjy?mLv=uQi z`cLg$Az9K{q?;g($Q=F6P$@}_{$|)lBu0PM-UAx96IsOQzp?j(Op|c9KiG}lFvvta zql(%WhD%EEYDfi=W?<|VD3vq^W4Azsq%7U7_JbNCJfogiH5;}|+T%`Cb6|(0i+ZBk zAKsL-%+W;6HQ;NBxrhUyUVC%AYrIX=Jm@CrjNU}O4f2W1c^V9xiOhK#40;E%-UV-K zbqFL#I*T+6nh=@uG#vU#67w`1jB$iwqB^J}Uuu0M$_fWMMwn)0D4^>Oxh@@qX(JJoTJDT&9i&P3}9dUy# zme(5ZX!TB*D1&G8(duMaO=M1Y8GJ!xPInnRoNCs);4N0C!V{9tB29xDB6GSc;0sA& zx^dguNz@awJz1R&LpzB#nzSy<)fq6Ag64G3hC@W+wo}Sy!}})Tbk9}qhL0tsc;_R1 zA!!E2=D^pI=3s0NT$Yrj->1%nlr*#3>7FI(Jm`vq?N{nqo)x$eyHy6WJdddXD3G+r zU9HZCB1sqZYIOnJEoqsfMqLP1l5&ymg@cmTcx%-A;Y&$p^cr5JcMmba4?`y+F~KjWK`15? z%lQ>`CCnr;=eH87i3~Bpvw0=#HjB~(KL#O5VuBxo*CmMwUIoV`i3wf>A4(eIo2xz! zpGYe7%||+G61H$iT@74ppYf{5@*P#5fO;kwdbaN!#EvppiovzeN77V`t%ZC^Gci^L z<&x%MtO_2Il!viuSSM*1#;W0ENmINZsO#W;NhL&IOESd5SO*yyqHp4I`bb?5y(DFM zK2tZqSd0FlZiJac=2h_|Y>=@%?(^!C@RX#B`g!#!cumqW$0fA}E=tNp+63*pn(eJI zyqDC?Fka#r{gV1DR7yJRzNBu0BShx9cn-Sa8*KA=VAE=$ucWhHoAx~95t-{^7d#|M ztczVxLnPLPTiXpk5Q%kR#Az=;qId%Y=EB$uxkO@JEa!XS4wGnIgkYK^u`WVzwF!cP`uYR8~S57B(3zQ>)dy$wwzUDUI+!VF1gkv@h1 zk+~91!zYr&N;nOdiNs3C*FFKGw3kRDmO_d4DJ&&2SHkD;h8e?CF-`j%J~Rne!a4Xm zd?G2u%aG1Wnt`z|;a`&GVC+k{EGbK$rJaS8-e$McJ#)2xKv&#<;GdZ8h1ypzNK%&P z0qq=2u;^j!pD;(#9``EkYlzglsISt#fl3)$=BU=bg*}pTkG_+D1L;N7JC0Jgqny|YL^ftSdfZpOMu z64T9C9+8;t=QPgd6N%a0ttl+nm!5y-blce$3RXfTgfu(bY0(l@W3NaO?^|_t*rJVg zCp#`_4II^6>_dy@Y998fq!7HPdD&MM{Z)%)-yz}nu{roijc0HRjS1hk{!L3@E{o1- z4Ol&kE@%x|6N`S*8nf0GrD`{@R7q{z8(9*&NzzK!M%I+|k@UX4ku_rj1R2=;R4m$@ z4UxfG47OmSC7nQO$%-VEVaZl(lBDM_)|ypF`Xj7mel}OqH=d0wncXYtdhc5N3g|&e zGqkn59jhcVDq*2>BWur|kimt{4ZH(;(xPfLg&nl$yxNg{ZBe$C%3Aj`XKkS~QSHQT zv1q88#_p2T07swBW?NLPc4m*7WIzMgd9@4MZw0fp40ch{UL0XprevEIl|0*UZ_& zYvgI8*aH&FoLiMK>@kZpJ)hNB6sH%mT^2RgOW13Y*4Z|)Qg+g!YV{6w-lFsBoh)X6 z+2A@`wl;~il{C}2)leq0bPN6ZRMx{HO)qDIEsE1;u!$Bm)@QT17Io0)u!k+`rW@=j zNz-vi3)nV`{QAA@1&cJ@Sj3K67^g2`pIX#de~^7|Q3rhm^9(dcG2PisU&)$D8s}Wg z{=_<2RHHt|@-4cgKF+Ev%G2;0k6X8n4(Zn0>fzJ<-QXsEuG9gy@e?x41_PJ_&P_dCbvwXC~Eh58O=^tG^5f1cec=}T>l zzLSlzs8HXlNPPl z|H{6zXp0_Z34_h96*+h5pR%?Vy`q24at71$uM&!!Z|G-Pi4}ZL|0lagQWINP{}-Dx z#7y(H=kNM=Y`3HZo(uXV)?%m`8?M_NKe2l(@;GdK(y&OZp~Jz4{2@YZ9A4gixP1Qo z8T%H`ry`1{;h&vd9Etp}q=QbIqaklF!i*hsHgqKMM4sNGxG(RoA(r6X%cPT)A$pT#O6JXKSgASPbucA>3pjUirsrU z-z`b(-aGSg7W-E(#D-!k0cfVPD2$9%q zjd%3o`-se~ZEyY=k-69E$8E);-hB-Wa7}ddrlF^S%)4daU}s&{B`A?F%c?Ibc zD5w6OX3RxQli0!|Xso2knkaggq)(lKf|8CgQF5K6bl+5EG!K~sUGSra(L80M+3haw zryXN>NYbN7WBH&`GZt`cH5}u3%pE3H+IBkf`BEZt6JEei5}E6wfH#>WN{V&yl4Cqi zmUI@Wh^G;m>!O5@kt8xBx~{Azh4sNN27!W}>cJ3M~1>OdFBWoBfKO z_Fwe$|4&B^>zs+nG<#Rq-{01`|D>Zv^i==%r#CWy0XXgaipj^Qag8+}J*H?oy1T#E zzS{6`{YNgUt6fM}J1ORAvVlM95{>SnY1fTdktG+?9GJDtQ{2jn>pwC@*IIhfsekW? zqsw1y^tk`}-$yU5@919t`pqi3yI0!L9U|Mf${p>BLbA-&NAPO{5I46gucl~^VfHcF z^-E9WvaCC!kyBPz>*s5tuH>(tdvWT`^}mvyV{PbBapk04=@F-Z)5s!S5oI`>qbJ3c z<*)6Dy8mChP~9`)zgq@R^zXa>Uu$LjUcVRyheCXE8VcM=570W-@AX8Vf{1;F9@dlb zsjt9`^vJ%3$AI5Z=szlBp0eoXqdkV%e6;I7_Z*?!j+yg+vCZ`_&IG3up~zj(|IPFJ z*THXC|Gy=vIo2z6Hm!p=T4B+)D9HgY_{jaAgLx0QGKINaJdArc1sc&(`fqyh{{QQ# zqZf`SX_KA!FZugGJKV{0cnr733T&oF>Nd1F>_=Ons0~8svBNtQt)wXx%`13ah=d*Hd}FpZXBA+3Y;s0^Z0i&G=s ziu_Vd|1X9oR(|xQ71@4?%I0)MPwYd~WG@_z9Y)|AG<+jQ7u7rTUhV{{0tfIZYJ-kw z+d-69L6p~E2|ZVTS<<+6(cjYWZz)rV&2xAfRiO^qs6%pixzLC9zCt;)p!FZUOn%wl zNOyPOB&(E8qjdP|PIvJ3yIUCbAQ>#3U(o)oVFdMZYejFw8EGDf+Z3?fGeH=9gzx5Q+6{MqKwSm9Y>&nD$^osDec`8hT zkMTRqBGS=V=vx>KU2Wf^_HcfW(d7zbMW}AnbWb7^Wm=);I@+PG@d{mrc30Nf+Y9xe zXEf?{`Xuz9(S`pT_dK+_IPn`X$kL5R*a$4T7WE(apsgGG;!DZ1lsrqxvr>2rzLH!k z$%Q|o#oz5PHdXxEl5Im>P1ROYnblOrfn{cM2bFP983%g~OU`E>xC1;Eqsvi6hFFTm zQZ$xzN6!Yzwt=#3plrqnb`ZZZ7{N;LOTrOsDcQ$K?;y_>vMcaw!V#=K&QlJv`_}Ri z%)rlW7o%=KI|09`8^LzzPosSv{Ug{i{2X@#+lOCMEk=EoFUC23mT#u!H&gSQsre$j zpSXr7J4p9RTb$RuG~oy66znCP1a0sGvjcQGCc#?e5wwNwMSVh9%u4Z#5z&ljQgp}- z3Gjtt$DgTxg_;TfMC}9Lpk~9rPzS)j@lzHf1Q+md04}2*24=IvTi~)eAPlOlHI&0C zS8MzkalX4f{?8-Fo>%2fz=8+0&XA)IzCr0_xtFT|gd>+bdBSx=tz=sE4$N1pvsMcw`A z@8CX7p5`nF!`xYnnqiM)w1DjSqzfrpP4;@UjbZNn>pks_aq zbOF-&q*dgvBDG+C z$P*%aAL#)l4!avBf0#US5RiWD3ebbAgziQ(JHb-WQWKO3)^so2_Qiff&{8ec0Snw(kk*) zksTrplP63z)DyLm>WK_VWM`6w5`{lZ3Js_XX=VdaGLvj0pCF$gKz4xaDzdA{hU-KH zxQYk)26) zCfR{bB3eb7lrHQ{(tOeYX%%URG)xMeDJSV?w)gX*c|D%P zhwyQ{m@ncx_zV1HzMmiF@9@9yPxvLSDltlPrM1#osZcg3JC(i4iQt>}v@`0{KepQB z>4}@^V%{6I!rKpZluu}1Jr_03H3)U5CiE;5dV;jVI|S`fKB0Z};i%D`CFCFA8xs{B zPvwR7)k{Qq!&~QC;uH1&-<={r`T*%j(rMnQ=)Z$hR=%RHpFC!UW>nIwwb&=>Mrut* zdz4RTUp;R&9xASp=q+7zrH5DO2b$=d-y`%cO?Wey*@B$05WIm7&=hOJ3yuC6?8LyNo!0%RSa4&8P;I~$o1>c#q6Bhm*q8&do z0rg`={4+g(M5qrrFr4M$3!rxFqD*F+TsmcVaLwYT@skT?4Tl-UlgbK9D`pP|^p6N0 zTHH=SfACv87ECLyz3u@wQ!o;YUy{ow7u4o1DPqpPQwj^FmQSBlP*J;O>08`Bp}1mP zd2q%<6O@^yMZpsfePt7#;Qr;cYR;63;%VizWh=%oa@HPsco0*2mrbcCC=Fh>a+Jd? zP+V9MoVN0gX4BAsKE;J)Q_3r*O-H+^0wxugRDei0vAA@?#M*N!J!Y!lrAIEY(%{+3 zVYQim8p938G$_V@HHP2sc_u?C{&Tb>{AXNA_^&YAqE5#5BC{f%dcrdk%M@Yhi4kw2 z@J_@Wr7!`H6%l{D@Ry_aj~J6Vsca$g6f9Q^)6kDUkHyBQZ~;1su|Zk*I+36hQ}o4d zOvMVb%}DU^$0o-CJbtYGeKR))1}-UlZ^n+N{#YJtu&P8;>&4y0Vu#euTlFIMshSd- z;pSSsxWEf6HlsFU&7EosEs3KYn_*94Ja&khgry^g3=KH?u=@J-53??*?Njhy2{34% z(!OK+)Ef)a($hMP@0ym@skm$BqKveR)XpizB`GPX>1jo&MV$&$N(wp`bS+8^PTCkw z132o9TLEm~AY5(uFB0*84{;P<+t84I43F{nYK^bkkKt>3aNd*6VtS`_8{4bdE4Ms1 zuJ8GmYR}aMw>>#9RXTU3N~g&EcxdtTEuphHQx4Spr5fu;{`LqvX+_iee9N(5zo+gD zdTRm>@r|VTMFd!UC*Sm)ZkaoG~{phtc~6LPqrdO`ya-;9rgeK delta 14912 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 diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb index 711cf76c53ef3d56a5f07fa25acb9c8532c93e3d..223b4de88b508404e9dd23578205c7692bc9427e 100644 GIT binary patch delta 3993 zcmZwJ3piBi9|!RF%nW1P!jjx)3FCenv$@r}wWyZV7F+916sBd6KWhltwpfhTMysrm zT4^^^Nv%}N{x|*iqKjQ!bWxOwM`3Hb{l5ph&C|2<_?+MSecyB5^PV|4=lM}8{{&Ue zUn?>r#4L}H#QB7X7-+8$r6r}VODFhTwIGCrYWBejas%y+bo;#z0{u`!ED3QGAp0Z> zV~~CM?h!twor_x|{UT4R>4OQ2OA&_vLPQzpbU0QPb6lO zUDFdOVWJrVR9f?_R@9Tw4SjG8Mqv^Zt(Axx7=t<3LJ%y6Fo=W%NC6S#z-HJA<cIbvaj9r6XwEuwXFa#VM4$%NzFa~q51s8A! zZwRoV&o}##Br|jZI3#$wPnks6%!o&&B||Z62MN?bJ?w*T;V5)K7o4-9?~YiRoVQUW zH{mXf!4$jzmaQsL0WHu2E?9y+xPmA6LJ%y6Fo=W%NC6S#z-HJAtW!MAV}I-m>A!FlM18!!x`FbU6rX|GC@K@;YI30Qy~@WBIoAP_=eEhK^n3hY(I zWGfmKuoLQGKOBN~I1Oi^7y98Q+=Fp=3Y3E?F#>b2gGKNj1jA~GgmoZMJU;#&XCr_0My`|F-x%Y&-^hF9vKGec z+4V#`nqPROIjEE==}?k>2Es{=b!E
  • U; zNYLyK#mLTRyTTD1umzd^2vO+#8L|)Z-{aem>BZ9dD8{$X?!O+n4Y_PKTjLdi&JsMe z-^bW~3OAYVs6xkTbX4L5bl!m+g`9(Y47m=O{?PZZT`p@PAD7Er$S05+X7`Um{$6&+ zc#yeFN-qxUxGqUC%pv^Or}>WCx`^?3K%pe!WuQ(?Wozd3zV;)I9iOhq^Y)Py9_$Gb zvXfHf*EPr$cZ&6fMQ%n5qI&8yhwuE}$oF5Nqo^C+$LN{Uw|1hEs~MQ?zeyCImKvQL zn3^Q|=VY6;x&A|j?tN~x3*S@Et8E(2P(EZQNi6!|ewc~L*o#WNQFnGqv?%fQ0m0mm zj&^QS#MI=a$Qt&Zr+0~O;7zrqi9SPv+C7iETt}R?O7eNS;=`V$M#C?D-NGsG)eosE zT_5(o#fkD|BPE@A7l}Zh=JfNfHqu&KXw}5+{YO8}VAea`ZD{m1e>LE1G-3OoI<_%n$>>Xou~ndI zaN(-LkJyFLg-L~*B&M)z1jZb@`bQqu34Sznpg7Ws@+O3RBYmtF9^ z7j0XbQeeBYci-vjC;v#PzV?#x{km4#blsqiA9UfVtZcA>U#znI*08IaI7a*Y*pJ=O zSKqP7e^hfrllg!ZGL_FAxDr=BdfUJ7;9hdedy8bpA(q|L9!EvSVAVPc^N`TO>!s`7 zZMxYTrea<&>3wRYM-6}C!WCB3<(;2@v9aQ1!_aWq&=a2G;QBwNy!Ux6+GUZoGCVZq zdk+(7q!VlXk}JDk*ag^&AL^WZDE@7-+BCU`H<6ZEYNylN^Wak3S=H4$-JR8&bEF=v zMFo2c7lwo+A7A8HY%WTlI$3;6;V@6FNYUebNcNW6D-R!f1_(J{?y|5jcCgx+niFs9 zrBUL0#WuUvwldj$sG-@jEmkS<)eku>k0iO_2OsAK-ruqC=V0CD?cdn0*>(Fr_2O#l z=8eT0I=)POwzZ=yXo|UQm4NrN=*JGtCBdeio?e}5ob}qCo4mi8v)J>`-Tl7|oI0R+ z?@V}2-{P7ui=MW7V}I5dYkN)Io_zVrQ8bqDY@vShO%;RsGaHV_99Waa=!sD}yK$6F zsZgw}9Md>qY(`3aswmymJ;&4`(!tu)UBb0<<;C({`7SYTe7;M(n;>ohe}S{WF+Rc3 z(b<(B=N#t}>zEKNh<1x}mV9EDOS#GZvfFPscLgQ=kdkIlQZXgnMoDWa=>bamgRDJB zpZV6mjLC+UJyI6a4#Yp} zd-@=jk~Ljal^o_9NbZLD|LeubPDy;jy(mLTLAZ!Al1zoK)y7-K()>b>Ia$LXHuSpl RKQoaee7J|=O4w^e{|97??IHjG delta 3501 zcmZwJ2Ut_r0tfJO6EMRti=a3NG6G>Q5f>_3L7XTW?uQlv>`v(pKVA_qbYP})tIL`qPfPuIhyLW%DP4A|DwHBEq2eQMg9vX;0&C0 zph!QK@e;IRM`**K%Mqq`TAd=Tg`otT8i)iXnB=0x{a zH&ce5hNQQXjtxqhBLqSyM8RZ;2N6iS2SyM>GT(__Ob?9Ku|Y{w>gA(c1r=}v8sQvV zh8Adr$KA7qV3v;V*;wR*lMd6FMQB{G10i_9U1!xPlK1fl&|+(GUaEK>{g|2AQDD#%Vbe z!&=x38rTJU;UFA`Cb$UK;0`>5XYf0`flpxQ#v?rV2CTsroWKJH!EgwMu`m&0-FQlp zh{|k`K?Y=j3JRbUHb5oRKrQTt!*CMLLNnZiU*HkEfLHJiD0d!Vg9%tde{cpr7zN|p zm3V7VnE|sv2LFMjkPpSM4l1Aq_P_x+22F4YoVJmLl;Ap#~sJj??nl!6AngM)Aaeu8Fj^5PLs7z`sJ#f$#x8j(^j zoed~ym3rnisL*P`)!T^pcU`qzHWRi; zE>HD#7pM2|AQY1o!mlmb`qJ@r`^THN^95IAo1Px2obl0~O?rdQluq4|2HxmPeFdF}NVx-nnfW(nVP#Gch1BjPx3=eKsAT9?`~Jl&?Y zRl22b>fy#$X+^E4bVGc<+@_A-|F8NM`G&jO466fK=6|(?U5ib4F)byi>)}$t%+)so z=CY)+uh-SaSB@zkwL0Sc+$B=cvY^J$;4$l~_g{LVt~W4VH8E@?_qN0=B0Xf0A~{W# zlo}zED*m}yt_A=6liBK+LLZ+TPV9RHtMA$G?!=5aD$|C?^|&ht2)mxnnwO-Q^JPFZ z|J|+ocEc-bZ?qJ;B`3w+%qx~g8u`t6Xy8=!Jfxw2HAs==x-b(gT|s|(8v)J-lAlwW$}vH6ld53GNS-TF!7 zxZv5acH@Q_RKb%~8FgJ1?NwJtZoF!mv-dK72eoVBQ~G+D=Cv~4nyB7iDO-HAc!8B* zYl$o(ZiVeB>AUgoJY6pS##O|IYVtq6NxFBV<>-mg)&3D-t$m7p&%Jt9Slp%=w!CMg zd5uw~Qe<)EWkX@(L3xxxFW(>SLR&ah(P>u&Myojq^4B*@G@nl>)9*EXu^qWLpSa3x_T!#@%Bc!+k=6h!!COCzxc<5f+qjE zXPT0sftzP7oE$$pZV4Y7Q$XhAnltqL<6ExftIlM-&PZ%9qB@j|1TG2gQ}aelcipjgyJh(H zv9*(jd+e&-!JRIyjX6}4nbS5;9`IBv39OSOuewq^`)=R8U9Kk^U%iS~SB3tlvbvTx z$A13jypCVpBI8QkD;{NvwNX=Qa?dM|-~3SZbYAp~m!D07-t6(WDcfs(Y@eA>RpZjs zRBAQeQhXslJzBleJ=!TC$H~-RBFOBZxn$pil1GbPZ zcx(@QYjsCl$iL21-%}kPKb-2T&W~46{nYQ{6D;~;&&pPD97sHaxR7uL$u1b6PD!Yu LY}CBz3X1#>Qw59x diff --git a/unity/org.gamecult.cultmath/package.json b/unity/org.gamecult.cultmath/package.json index d7039df7..80ceddad 100644 --- a/unity/org.gamecult.cultmath/package.json +++ b/unity/org.gamecult.cultmath/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultmath", "displayName": "CultMath", - "version": "0.1.1", + "version": "0.1.2", "unity": "2021.3", "description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.", "license": "MPL-2.0", From 6c848bd6c0d961251c6ed210fa64e80ae0466bea Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 17:21:58 +0200 Subject: [PATCH 024/203] Bind CultMesh authority identity to physical routes --- package-lock.json | 6 +- packages/cultmesh-browser/README.md | 11 +- packages/cultmesh-browser/package.json | 4 +- packages/cultmesh-browser/src/index.ts | 153 ++++++++++--- .../test/cultmesh-browser.test.ts | 160 ++++++++++++- .../cultmesh.session-accepted.schema.json | 18 ++ .../cultmesh.session-open.schema.json | 17 ++ ...ultmesh.verse-catalog-response.schema.json | 13 ++ packages/cultnet-ts/package.json | 2 +- packages/cultnet-ts/src/contracts.ts | 45 +++- packages/cultnet-ts/test/cultnet.test.ts | 19 ++ samples/eve-browser-network/Program.cs | 35 ++- samples/eve-browser-network/README.md | 3 + samples/eve-browser-network/browser/main.ts | 6 +- scripts/verify-eve-browser-network.mjs | 33 ++- .../CultMeshNativeQuicRealtimeTransport.cs | 31 ++- .../CultMeshQuicRealtimeTransport.cs | 32 ++- src/GameCult.Mesh/CultMeshClient.cs | 12 +- src/GameCult.Mesh/CultMeshContentSessions.cs | 43 +++- .../CultMeshContentTransports.cs | 2 +- src/GameCult.Mesh/CultMeshDiscoveryService.cs | 3 +- .../CultMeshRealtimeTransports.cs | 2 +- src/GameCult.Mesh/CultMeshSessionIdentity.cs | 213 ++++++++++++++++++ src/GameCult.Mesh/CultMeshSessions.cs | 187 ++++++++++----- src/GameCult.Mesh/CultMeshSnapshots.cs | 2 +- .../CultMeshTcpContentTransport.cs | 113 +++++++++- src/GameCult.Mesh/CultMeshVerse.cs | 98 +++++++- src/GameCult.Mesh/CultMeshVerseDiscovery.cs | 21 +- src/GameCult.Mesh/docs/public-api.md | 8 +- src/GameCult.Mesh/docs/transport-planes.md | 14 +- src/GameCult.Mesh/docs/verses.md | 13 +- .../CultNetOperationServer.cs | 10 + .../CultNetSchemaMessageSerialization.cs | 2 + .../CultNetSchemaMessages.cs | 47 ++++ ...ultMeshNativeQuicRealtimeTransportTests.cs | 10 +- .../CultMeshQuicRealtimeTransportTests.cs | 18 +- .../CultMeshBodySessionTests.cs | 12 +- .../CultMeshClientTests.cs | 49 +++- .../CultMeshContentSessionTests.cs | 57 ++++- .../CultMeshDiscoveryServiceTests.cs | 2 +- .../CultMeshSessionManagerTests.cs | 154 ++++++++++++- .../CultMeshTransportModularityTests.cs | 101 ++++++++- .../CultNetOperationServerTests.cs | 38 +++- .../NetworkingTests.cs | 15 +- tools/GameCult.TransportParity/Program.cs | 29 ++- 45 files changed, 1670 insertions(+), 193 deletions(-) create mode 100644 packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json create mode 100644 packages/cultnet-ts/contracts/cultmesh.session-open.schema.json create mode 100644 src/GameCult.Mesh/CultMeshSessionIdentity.cs diff --git a/package-lock.json b/package-lock.json index 74a753a5..1e955e9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -846,11 +846,11 @@ } }, "packages/cultmesh-browser": { - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "@msgpack/msgpack": "^3.1.3", - "cultnet-ts": "^0.1.1" + "cultnet-ts": "^0.2.0" }, "devDependencies": { "@types/node": "^24.0.0", @@ -878,7 +878,7 @@ } }, "packages/cultnet-ts": { - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "dependencies": { "@msgpack/msgpack": "^3.1.3", diff --git a/packages/cultmesh-browser/README.md b/packages/cultmesh-browser/README.md index 4f5c7e8b..59a44016 100644 --- a/packages/cultmesh-browser/README.md +++ b/packages/cultmesh-browser/README.md @@ -43,10 +43,19 @@ selection, reconnect, or resubscription. This is deliberately distinct from an Eve surface's `providerId`, which identifies the UI provider inside the Verse. The physical socket is a replaceable route, not either stable identity. +Odin returns exact authority-route bindings. The browser selects only routes +bound to the requested authority and `cultmesh.documents.v1`, then performs a +session-open handshake. It does not report `connected` until the peer proves +the exact Verse, authority runtime, protocol, and route generation. Operation +responses must also name that authority. Legacy catalogs are accepted only +when one authority runtime makes the endpoint binding unambiguous. + The browser handshake carries authentication through the host application's ordinary secure cookie or equivalent upgrade credential. The C# host adapter rejects endpoints without an authorization predicate unless anonymous local development is explicitly enabled. Run `scripts/verify-eve-browser-network.mjs --eve-root ` for the -real Chromium + C# headless chronology. +real Chromium + C# headless chronology. The witness publishes a lower-priority +decoy endpoint owned by another runtime and proves that neither client connects +to it before and after route rotation. diff --git a/packages/cultmesh-browser/package.json b/packages/cultmesh-browser/package.json index 41833562..c49062b0 100644 --- a/packages/cultmesh-browser/package.json +++ b/packages/cultmesh-browser/package.json @@ -1,6 +1,6 @@ { "name": "cultmesh-browser", - "version": "0.2.0", + "version": "0.3.0", "description": "Browser-safe CultMesh document leases and typed operations over CultNet WebSocket transport.", "type": "module", "main": "dist/index.js", @@ -41,7 +41,7 @@ }, "dependencies": { "@msgpack/msgpack": "^3.1.3", - "cultnet-ts": "^0.1.1" + "cultnet-ts": "^0.2.0" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/cultmesh-browser/src/index.ts b/packages/cultmesh-browser/src/index.ts index efeb4515..bbc253f4 100644 --- a/packages/cultmesh-browser/src/index.ts +++ b/packages/cultmesh-browser/src/index.ts @@ -12,6 +12,8 @@ import { type CultNetSnapshotResponseRawMessage, type CultMeshVerseCatalogRequestMessage, type CultMeshVerseCatalogResponseMessage, + type CultMeshSessionAcceptedMessage, + type CultMeshSessionOpenMessage, } from "cultnet-ts/contracts"; export interface CultMeshBrowserIdentity { @@ -21,7 +23,8 @@ export interface CultMeshBrowserIdentity { export interface CultMeshBrowserRoute extends CultMeshBrowserIdentity { endpoint: string; - generation?: string; + protocolId?: string; + generation: string; } export interface CultMeshBrowserRendezvous { @@ -173,6 +176,7 @@ export interface CultMeshOperationOptions { payloadSchema: string; payload: unknown; idempotencyKey?: string; + /** @deprecated The connected authority is the operation target. A different value is rejected. */ targetRuntimeId?: string; } @@ -338,6 +342,11 @@ export class CultMeshBrowserClient implements AsyncDisposable { requireText(options.serviceId, "serviceId"); requireText(options.operation, "operation"); requireText(options.payloadSchema, "payloadSchema"); + if (options.targetRuntimeId && options.targetRuntimeId !== this.identity.authorityRuntimeId) { + throw new Error( + `CultMesh operation target '${options.targetRuntimeId}' does not match connected authority '${this.identity.authorityRuntimeId}'.`, + ); + } await this.ensureConnected(); const messageId = options.idempotencyKey ?? this.#options.createId(); requireText(messageId, "operation idempotencyKey"); @@ -353,7 +362,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { payloadEncoding: "messagepack-base64", payload: bytesToBase64(encode(options.payload)), sourceRuntimeId: this.runtimeId, - ...(options.targetRuntimeId ? { targetRuntimeId: options.targetRuntimeId } : {}), + targetRuntimeId: this.identity.authorityRuntimeId, }; const response = new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -489,13 +498,67 @@ export class CultMeshBrowserClient implements AsyncDisposable { socket.binaryType = "arraybuffer"; try { await new Promise((resolve, reject) => { + let opened = false; + let settled = false; + const handshakeId = this.#options.createId(); + const finish = (error?: Error) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error); + else resolve(); + }; const timer = setTimeout( - () => reject(new Error(`CultMesh WebSocket open timed out for '${route.endpoint}'.`)), + () => finish(new Error( + opened + ? `CultMesh authority handshake timed out for '${route.endpoint}'.` + : `CultMesh WebSocket open timed out for '${route.endpoint}'.`, + )), this.#options.connectTimeoutMs, ); - socket.onopen = () => { clearTimeout(timer); resolve(); }; - socket.onerror = () => { clearTimeout(timer); reject(new Error(`CultMesh WebSocket could not open '${route.endpoint}'.`)); }; - socket.onclose = () => { clearTimeout(timer); reject(new Error(`CultMesh WebSocket closed while opening '${route.endpoint}'.`)); }; + socket.onopen = () => { + opened = true; + try { + sendSocketMessage(socket, { + schemaVersion: "cultnet.hello.v0", + runtimeId: this.runtimeId, + runtimeKind: "browser", + supportedMessageVersions: [ + "cultmesh.session_open.v1", + "cultmesh.session_accepted.v1", + "cultnet.database_subscribe.v0", + "cultnet.database_unsubscribe.v0", + "cultnet.database_change_raw.v0", + "cultnet.operation_request.v0", + "cultnet.operation_response.v0", + ], + }, this.#options.maxFrameBytes); + const request: CultMeshSessionOpenMessage = { + schemaVersion: "cultmesh.session_open.v1", + messageId: handshakeId, + sourceRuntimeId: this.runtimeId, + verseId: this.identity.verseId, + authorityRuntimeId: this.identity.authorityRuntimeId, + protocolId: route.protocolId ?? "cultmesh.documents.v1", + routeGeneration: route.generation, + }; + sendSocketMessage(socket, request, this.#options.maxFrameBytes); + } catch (error) { + finish(error instanceof Error ? error : new Error(String(error))); + } + }; + socket.onmessage = event => { + void decodeSocketMessage(event, this.#options.maxFrameBytes).then(message => { + if (message.schemaVersion === "cultnet.error.v0") { + finish(new Error(message.error)); + return; + } + if (message.schemaVersion !== "cultmesh.session_accepted.v1" || message.messageId !== handshakeId) return; + finish(validateSessionAcceptance(message, this.identity, route)); + }).catch(error => finish(error instanceof Error ? error : new Error(String(error)))); + }; + socket.onerror = () => finish(new Error(`CultMesh WebSocket could not open '${route.endpoint}'.`)); + socket.onclose = () => finish(new Error(`CultMesh WebSocket closed before proving authority at '${route.endpoint}'.`)); }); } catch (error) { socket.close(1000, "CultMesh route open failed"); @@ -511,18 +574,6 @@ export class CultMeshBrowserClient implements AsyncDisposable { socket.onclose = () => this.handleClosed(generation); this.setState("connected"); try { - this.send({ - schemaVersion: "cultnet.hello.v0", - runtimeId: this.runtimeId, - runtimeKind: "browser", - supportedMessageVersions: [ - "cultnet.database_subscribe.v0", - "cultnet.database_unsubscribe.v0", - "cultnet.database_change_raw.v0", - "cultnet.operation_request.v0", - "cultnet.operation_response.v0", - ], - }); await Promise.all([...this.#leases.values()].map(lease => this.subscribe(lease))); } catch (error) { if (this.#socket === socket) this.#socket = undefined; @@ -576,6 +627,15 @@ export class CultMeshBrowserClient implements AsyncDisposable { case "cultnet.operation_response.v0": { const pending = this.#pendingOperations.get(message.messageId); if (!pending) return; + if (message.sourceRuntimeId !== this.identity.authorityRuntimeId) { + this.#pendingOperations.delete(message.messageId); + clearTimeout(pending.timer); + pending.reject(new Error( + `CultMesh operation response came from '${message.sourceRuntimeId ?? "unknown"}', expected '${this.identity.authorityRuntimeId}'.`, + )); + this.#socket?.close(1008, "CultMesh authority identity changed"); + return; + } this.#pendingOperations.delete(message.messageId); clearTimeout(pending.timer); if (message.payloadSchema === cultNetOperationFailureSchema) { @@ -733,33 +793,72 @@ function selectOdinRoute( response: CultMeshVerseCatalogResponseMessage, identity: CultMeshBrowserIdentity, ): CultMeshBrowserRoute { - const matchingVerses = response.verses.filter(verse => - verse.verseId === identity.verseId && - verse.authorityRuntimeIds.includes(identity.authorityRuntimeId)); + const matchingVerses = response.verses.filter(verse => verse.verseId === identity.verseId); if (matchingVerses.length !== 1) { throw new Error( `Odin returned ${matchingVerses.length} routes for Verse '${identity.verseId}' authority runtime '${identity.authorityRuntimeId}'.`, ); } const verse = matchingVerses[0]; - const endpoints = verse.discoveryEndpoints.filter(endpoint => { + if (verse.authorityRoutes == null && verse.authorityRuntimeIds.length !== 1) { + throw new Error( + `Odin returned ambiguous legacy routes for Verse '${identity.verseId}'. Explicit authority route bindings are required.`, + ); + } + if (verse.authorityRoutes == null && verse.authorityRuntimeIds[0] !== identity.authorityRuntimeId) { + throw new Error( + `Odin advertised no route for authority runtime '${identity.authorityRuntimeId}'.`, + ); + } + const boundRoutes = verse.authorityRoutes ?? verse.discoveryEndpoints.map(endpoint => ({ + authorityRuntimeId: verse.authorityRuntimeIds[0], + endpoint, + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation: [verse.compatibility.transportVersion, verse.compatibility.rulesHash, endpoint].join(":"), + })); + const routes = boundRoutes.filter(route => + route.authorityRuntimeId === identity.authorityRuntimeId && + route.protocolIds.includes("cultmesh.documents.v1") && + (() => { try { - requireWebSocketEndpoint(endpoint, "Odin provider route"); + requireWebSocketEndpoint(route.endpoint, "Odin provider route"); return true; } catch { return false; } - }); - if (endpoints.length === 0) { + })()).sort((left, right) => left.priority - right.priority || left.endpoint.localeCompare(right.endpoint)); + if (routes.length === 0) { throw new Error(`Odin advertised no browser-compatible route for authority runtime '${identity.authorityRuntimeId}'.`); } + const route = routes[0]; return { ...identity, - endpoint: endpoints[0], - generation: [verse.compatibility.transportVersion, verse.compatibility.rulesHash, endpoints[0]].join(":"), + endpoint: route.endpoint, + protocolId: "cultmesh.documents.v1", + generation: route.generation, }; } +function validateSessionAcceptance( + message: CultMeshSessionAcceptedMessage, + identity: CultMeshBrowserIdentity, + route: CultMeshBrowserRoute, +): Error | undefined { + if (!message.accepted) return new Error(message.error ?? "CultMesh authority rejected the session."); + const protocolId = route.protocolId ?? "cultmesh.documents.v1"; + if (message.verseId !== identity.verseId || + message.authorityRuntimeId !== identity.authorityRuntimeId || + message.protocolId !== protocolId || + message.routeGeneration !== route.generation) { + return new Error( + `CultMesh route proved '${message.verseId}/${message.authorityRuntimeId}/${message.protocolId}/${message.routeGeneration}', ` + + `expected '${identity.verseId}/${identity.authorityRuntimeId}/${protocolId}/${route.generation}'.`, + ); + } + return undefined; +} + function requireText(value: string, field: string): void { if (!value || value.trim().length === 0) throw new Error(`CultMesh browser ${field} is required.`); } diff --git a/packages/cultmesh-browser/test/cultmesh-browser.test.ts b/packages/cultmesh-browser/test/cultmesh-browser.test.ts index 624bdf17..a785d47c 100644 --- a/packages/cultmesh-browser/test/cultmesh-browser.test.ts +++ b/packages/cultmesh-browser/test/cultmesh-browser.test.ts @@ -9,6 +9,7 @@ import { type CultNetMessage, type CultNetOperationRequestMessage, type CultNetRawDocumentRecord, + type CultMeshSessionOpenMessage, } from "cultnet-ts/contracts"; import { @@ -48,6 +49,46 @@ test("Odin rendezvous resolves stable authority-runtime identity and observes ro ); }); +test("Odin rendezvous never selects another authority's faster endpoint", async () => { + const odin = new FakeOdin("ws://127.0.0.1:4050/mesh"); + odin.decoyEndpoint = "ws://127.0.0.1:4049/decoy"; + const rendezvous = new CultMeshBrowserOdinRendezvous({ + endpoints: ["ws://127.0.0.1:4040/odin"], + runtimeId: "browser-route-binding-test", + socketFactory: () => odin.open(), + }); + + const route = await rendezvous.resolve({ + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-daemon", + }); + assert.equal(route.endpoint, "ws://127.0.0.1:4050/mesh"); +}); + +test("Odin rendezvous accepts one-authority legacy routes and rejects ambiguous legacy catalogs", async () => { + const odin = new FakeOdin("ws://127.0.0.1:4070/mesh"); + odin.omitAuthorityRoutes = true; + const rendezvous = new CultMeshBrowserOdinRendezvous({ + endpoints: ["ws://127.0.0.1:4040/odin"], + runtimeId: "browser-legacy-route-test", + socketFactory: () => odin.open(), + }); + + const route = await rendezvous.resolve({ + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-daemon", + }); + assert.equal(route.endpoint, "ws://127.0.0.1:4070/mesh"); + + odin.decoyEndpoint = "ws://127.0.0.1:4069/decoy"; + await assert.rejects( + rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon" }), + (error: unknown) => + error instanceof AggregateError && + error.errors.some(inner => inner instanceof Error && /ambiguous legacy routes/.test(inner.message)), + ); +}); + test("browser client leases provider state, invokes an operation, and follows route replacement", async () => { const first = new FakeProvider("ws://127.0.0.1:4101/mesh", 4); const second = new FakeProvider("ws://127.0.0.1:4102/mesh", 9); @@ -117,18 +158,33 @@ test("browser client rejects wrong rendezvous identity and has no document write assert.equal("setDocument" in CultMeshBrowserClient.prototype, false); }); +test("browser client rejects a route whose peer proves the wrong authority", async () => { + const provider = new FakeProvider("ws://127.0.0.1:4251/mesh", 1, false, "intruder-runtime"); + await assert.rejects( + CultMeshBrowserClient.connect({ + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-daemon", + runtimeId: "browser-peer-proof-test", + rendezvous: { resolve: async () => ({ ...provider.route, authorityRuntimeId: "sample.counter-daemon" }) }, + socketFactory: () => provider.open(), + }), + /proved .*intruder-runtime.*expected .*sample.counter-daemon/, + ); +}); + test("browser client bounds unanswered provider operations", async () => { const route = { verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", endpoint: "ws://127.0.0.1:4301/mesh", + generation: "route-4301", }; const client = await CultMeshBrowserClient.connect({ ...route, runtimeId: "browser-timeout-test", rendezvous: { resolve: async () => route }, requestTimeoutMs: 5, - socketFactory: () => new FakeSocket(() => undefined, () => undefined), + socketFactory: () => handshakeOnlySocket(route), }); await assert.rejects( client.invoke({ @@ -172,11 +228,39 @@ test("browser client rejects a correlated framework failure without waiting for await client.dispose(); }); +test("browser client rejects an operation response from another runtime", async () => { + const provider = new FakeProvider( + "ws://127.0.0.1:4371/mesh", + 1, + false, + "sample.counter-daemon", + "intruder-runtime", + ); + const client = await CultMeshBrowserClient.connect({ + ...provider.route, + runtimeId: "browser-response-authority-test", + rendezvous: { resolve: async () => provider.route }, + socketFactory: () => provider.open(), + }); + + await assert.rejects( + client.invoke({ + serviceId: "sample.counter", + operation: "increment", + payloadSchema: "sample.increment.v1", + payload: { amount: 1 }, + }), + /response came from 'intruder-runtime'/, + ); + await client.dispose(); +}); + test("browser client rejects oversized outbound schema messages", async () => { const route = { verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", endpoint: "ws://127.0.0.1:4401/mesh", + generation: "route-4401", }; await assert.rejects( CultMeshBrowserClient.connect({ @@ -220,8 +304,16 @@ class FakeProvider { #count: number; #subscriptions = new Map>(); #rejectOperations: boolean; + #runtimeId: string; + #responseRuntimeId: string; - constructor(endpoint: string, count: number, rejectOperations = false) { + constructor( + endpoint: string, + count: number, + rejectOperations = false, + runtimeId = "sample.counter-daemon", + responseRuntimeId = runtimeId, + ) { this.route = { verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", @@ -230,6 +322,8 @@ class FakeProvider { }; this.#count = count; this.#rejectOperations = rejectOperations; + this.#runtimeId = runtimeId; + this.#responseRuntimeId = responseRuntimeId; } get activeSubscriptionCount(): number { @@ -250,6 +344,9 @@ class FakeProvider { private receive(socket: FakeSocket, wire: Uint8Array): void { const message = parseCultNetMessage(decode(wire)); switch (message.schemaVersion) { + case "cultmesh.session_open.v1": + this.acceptSession(socket, message); + return; case "cultnet.database_subscribe.v0": this.subscribe(socket, message); return; @@ -264,6 +361,18 @@ class FakeProvider { } } + private acceptSession(socket: FakeSocket, message: CultMeshSessionOpenMessage): void { + socket.deliver({ + schemaVersion: "cultmesh.session_accepted.v1", + messageId: message.messageId, + accepted: true, + verseId: message.verseId, + authorityRuntimeId: this.#runtimeId, + protocolId: message.protocolId, + routeGeneration: message.routeGeneration, + }); + } + private subscribe(socket: FakeSocket, message: CultNetDatabaseSubscribeMessage): void { this.#subscriptions.get(socket)!.set(message.subscriptionId, message); socket.deliver({ @@ -274,6 +383,7 @@ class FakeProvider { } private invoke(socket: FakeSocket, message: CultNetOperationRequestMessage): void { + assert.equal(message.targetRuntimeId, this.#runtimeId); if (this.#rejectOperations) { socket.deliver({ schemaVersion: "cultnet.operation_response.v0", @@ -288,7 +398,7 @@ class FakeProvider { message: "Expected payload schema 'sample.increment.v1'.", })), diagnostics: ["Request schema did not match."], - sourceRuntimeId: "sample.counter-daemon", + sourceRuntimeId: this.#responseRuntimeId, }); return; } @@ -314,7 +424,7 @@ class FakeProvider { payloadSchema: "sample.increment_receipt.v1", payloadEncoding: "messagepack-base64", payload: bytesToBase64(encode({ count: this.#count })), - sourceRuntimeId: "sample.counter-daemon", + sourceRuntimeId: this.#responseRuntimeId, }); } @@ -328,13 +438,15 @@ class FakeProvider { storedAt: "2026-08-17T00:00:00Z", payloadEncoding: "messagepack", payload: encode({ count: this.#count }), - sourceRuntimeId: "sample.counter-daemon", + sourceRuntimeId: this.#runtimeId, }; } } class FakeOdin { providerEndpoint: string; + decoyEndpoint: string | undefined; + omitAuthorityRoutes = false; constructor(providerEndpoint: string) { this.providerEndpoint = providerEndpoint; @@ -359,7 +471,25 @@ class FakeOdin { optionalPluginIds: [], }, discoveryEndpoints: [this.providerEndpoint], - authorityRuntimeIds: ["sample.counter-daemon"], + authorityRuntimeIds: this.decoyEndpoint + ? ["sample.counter-daemon", "sample.decoy-daemon"] + : ["sample.counter-daemon"], + ...(!this.omitAuthorityRoutes ? { authorityRoutes: [ + ...(this.decoyEndpoint ? [{ + authorityRuntimeId: "sample.decoy-daemon", + endpoint: this.decoyEndpoint, + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation: "decoy-generation", + }] : []), + { + authorityRuntimeId: "sample.counter-daemon", + endpoint: this.providerEndpoint, + protocolIds: ["cultmesh.documents.v1"], + priority: 10, + generation: this.providerEndpoint, + }, + ] } : {}), }], }); }, () => undefined); @@ -367,6 +497,24 @@ class FakeOdin { } } +function handshakeOnlySocket(route: CultMeshBrowserRoute): FakeSocket { + let socket: FakeSocket; + socket = new FakeSocket(wire => { + const message = parseCultNetMessage(decode(wire)); + if (message.schemaVersion !== "cultmesh.session_open.v1") return; + socket.deliver({ + schemaVersion: "cultmesh.session_accepted.v1", + messageId: message.messageId, + accepted: true, + verseId: route.verseId, + authorityRuntimeId: route.authorityRuntimeId, + protocolId: message.protocolId, + routeGeneration: message.routeGeneration, + }); + }, () => undefined); + return socket; +} + class FakeSocket implements CultMeshBrowserSocket { binaryType: BinaryType = "arraybuffer"; readyState = 0; diff --git a/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json b/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json new file mode 100644 index 00000000..55a2f742 --- /dev/null +++ b/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.session-accepted.schema.json", + "title": "CultMesh Session Accepted", + "type": "object", + "required": ["schemaVersion", "messageId", "accepted", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration"], + "additionalProperties": false, + "properties": { + "schemaVersion": { "const": "cultmesh.session_accepted.v1" }, + "messageId": { "type": "string", "minLength": 1 }, + "accepted": { "type": "boolean" }, + "verseId": { "type": "string", "minLength": 1 }, + "authorityRuntimeId": { "type": "string", "minLength": 1 }, + "protocolId": { "type": "string", "minLength": 1 }, + "routeGeneration": { "type": "string" }, + "error": { "type": "string", "minLength": 1 } + } +} diff --git a/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json b/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json new file mode 100644 index 00000000..14d945ed --- /dev/null +++ b/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.session-open.schema.json", + "title": "CultMesh Session Open", + "type": "object", + "required": ["schemaVersion", "messageId", "sourceRuntimeId", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration"], + "additionalProperties": false, + "properties": { + "schemaVersion": { "const": "cultmesh.session_open.v1" }, + "messageId": { "type": "string", "minLength": 1 }, + "sourceRuntimeId": { "type": "string", "minLength": 1 }, + "verseId": { "type": "string", "minLength": 1 }, + "authorityRuntimeId": { "type": "string", "minLength": 1 }, + "protocolId": { "type": "string", "minLength": 1 }, + "routeGeneration": { "type": "string" } + } +} diff --git a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json index 21367d09..6e7551d5 100644 --- a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json +++ b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json @@ -18,6 +18,18 @@ "optionalPluginIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true } } }, + "authorityRoute": { + "type": "object", + "required": ["authorityRuntimeId", "endpoint", "protocolIds", "priority", "generation"], + "additionalProperties": false, + "properties": { + "authorityRuntimeId": { "type": "string", "minLength": 1 }, + "endpoint": { "type": "string", "minLength": 1 }, + "protocolIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, + "priority": { "type": "integer", "minimum": 0 }, + "generation": { "type": "string", "minLength": 1 } + } + }, "verse": { "type": "object", "required": ["verseId", "displayName", "authorityModel", "compatibility", "discoveryEndpoints", "authorityRuntimeIds"], @@ -29,6 +41,7 @@ "compatibility": { "$ref": "#/$defs/compatibility" }, "discoveryEndpoints": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, "authorityRuntimeIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, + "authorityRoutes": { "type": "array", "items": { "$ref": "#/$defs/authorityRoute" } }, "parentVerseId": { "type": "string", "minLength": 1 }, "description": { "type": "string" } } diff --git a/packages/cultnet-ts/package.json b/packages/cultnet-ts/package.json index 6ff29340..df129a42 100644 --- a/packages/cultnet-ts/package.json +++ b/packages/cultnet-ts/package.json @@ -1,6 +1,6 @@ { "name": "cultnet-ts", - "version": "0.1.1", + "version": "0.2.0", "description": "CultNet-style typed direct-pipe messaging and state replication for TypeScript runtimes.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index 1c3998ae..2d86ad27 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -30,6 +30,8 @@ import operationRequestSchema from "../contracts/cultnet.operation-request.schem import operationResponseSchema from "../contracts/cultnet.operation-response.schema.json"; import verseCatalogRequestSchema from "../contracts/cultmesh.verse-catalog-request.schema.json"; import verseCatalogResponseSchema from "../contracts/cultmesh.verse-catalog-response.schema.json"; +import sessionOpenSchema from "../contracts/cultmesh.session-open.schema.json"; +import sessionAcceptedSchema from "../contracts/cultmesh.session-accepted.schema.json"; import ghostlightAgentStateSchema from "../contracts/ghostlight.agent-state.schema.json"; export type CultNetWireContract = "cultnet.schema.v0" | "gamecult.networking.v0"; @@ -59,7 +61,9 @@ export type CultNetSchemaVersion = | "cultnet.operation_request.v0" | "cultnet.operation_response.v0" | "cultmesh.verse_catalog_request.v0" - | "cultmesh.verse_catalog_response.v0"; + | "cultmesh.verse_catalog_response.v0" + | "cultmesh.session_open.v1" + | "cultmesh.session_accepted.v1"; export type CultNetSchemaKind = "wire_message" | "document_payload" | "shared_contract"; export type CultNetRawPayloadEncoding = "messagepack"; @@ -350,10 +354,40 @@ export interface CultMeshVerseDescriptorMessage { compatibility: CultMeshVerseCompatibilityMessage; discoveryEndpoints: string[]; authorityRuntimeIds: string[]; + authorityRoutes?: CultMeshAuthorityRouteMessage[]; parentVerseId?: string; description?: string; } +export interface CultMeshAuthorityRouteMessage { + authorityRuntimeId: string; + endpoint: string; + protocolIds: string[]; + priority: number; + generation: string; +} + +export interface CultMeshSessionOpenMessage { + schemaVersion: "cultmesh.session_open.v1"; + messageId: string; + sourceRuntimeId: string; + verseId: string; + authorityRuntimeId: string; + protocolId: string; + routeGeneration: string; +} + +export interface CultMeshSessionAcceptedMessage { + schemaVersion: "cultmesh.session_accepted.v1"; + messageId: string; + accepted: boolean; + verseId: string; + authorityRuntimeId: string; + protocolId: string; + routeGeneration: string; + error?: string; +} + export interface CultMeshVerseCatalogRequestMessage { schemaVersion: "cultmesh.verse_catalog_request.v0"; messageId: string; @@ -618,7 +652,9 @@ export type CultNetMessage = | CultNetOperationRequestMessage | CultNetOperationResponseMessage | CultMeshVerseCatalogRequestMessage - | CultMeshVerseCatalogResponseMessage; + | CultMeshVerseCatalogResponseMessage + | CultMeshSessionOpenMessage + | CultMeshSessionAcceptedMessage; const ajv = new Ajv2020({ allErrors: true, @@ -651,6 +687,8 @@ const CULTNET_MESSAGE_SCHEMAS = [ operationResponseSchema, verseCatalogRequestSchema, verseCatalogResponseSchema, + sessionOpenSchema, + sessionAcceptedSchema, ] as const; const cultNetValidators = new Map(); @@ -1198,6 +1236,9 @@ function normalizeCultNetOptionalNulls( } } return; + case "cultmesh.session_accepted.v1": + stripNullProperties(candidate, ["error"]); + return; case "cultnet.schema_catalog_request.v0": stripNullProperties(candidate, ["schemaIds", "kinds"]); return; diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 945e0a3b..f0f01b29 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -83,10 +83,29 @@ test("CultNet validates the canonical CultMesh Verse catalog messages", () => { }, discoveryEndpoints: ["wss://provider.example/cultmesh"], authorityRuntimeIds: ["sample.counter-provider"], + authorityRoutes: [{ + authorityRuntimeId: "sample.counter-provider", + endpoint: "wss://provider.example/cultmesh", + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation: "provider-route-1", + }], }], }); assert.equal(response.schemaVersion, "cultmesh.verse_catalog_response.v0"); assert.equal(response.verses[0].authorityRuntimeIds[0], "sample.counter-provider"); + assert.equal(response.verses[0].authorityRoutes![0].generation, "provider-route-1"); + + const session = parseCultNetMessage({ + schemaVersion: "cultmesh.session_accepted.v1", + messageId: "session-1", + accepted: true, + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-provider", + protocolId: "cultmesh.documents.v1", + routeGeneration: "provider-route-1", + }); + assert.equal(session.schemaVersion, "cultmesh.session_accepted.v1"); assert.throws(() => parseCultNetMessage({ ...response, diff --git a/samples/eve-browser-network/Program.cs b/samples/eve-browser-network/Program.cs index 4b60a18e..db92d3c1 100644 --- a/samples/eve-browser-network/Program.cs +++ b/samples/eve-browser-network/Program.cs @@ -37,13 +37,30 @@ static async Task RunOdinAsync(Args arguments) if (string.IsNullOrWhiteSpace(arguments.ProviderEndpoint)) throw new ArgumentException("The Odin fixture requires --provider-endpoint."); using var catalog = new CultMeshVerseCatalog(); + var routes = new List + { + new( + arguments.AuthorityRuntimeId, + arguments.ProviderEndpoint, + [CultMeshProtocols.Documents.Value], + priority: 10, + generation: arguments.RouteGeneration) + }; + if (!string.IsNullOrWhiteSpace(arguments.DecoyEndpoint)) + { + routes.Add(new CultMeshAuthorityRoute( + arguments.DecoyAuthorityRuntimeId, + arguments.DecoyEndpoint, + [CultMeshProtocols.Documents.Value], + priority: 0, + generation: arguments.DecoyRouteGeneration)); + } catalog.Upsert(new CultMeshVerseDescriptor( arguments.VerseId, arguments.VerseName, CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility(arguments.TransportVersion, arguments.RulesHash), - discoveryEndpoints: [arguments.ProviderEndpoint], - authorityRuntimeIds: [arguments.AuthorityRuntimeId], + authorityRoutes: routes, description: "Local executable Odin fixture for the browser/network onboarding witness.")); await using var schemaServer = new CultNetWebSocketSchemaServer(); @@ -97,6 +114,12 @@ static async Task RunProviderAsync(Args arguments) await node.FlushAsync(); await using var schemaServer = new CultNetWebSocketSchemaServer(); + using var identity = new CultMeshSessionIdentityServer( + schemaServer, + arguments.AuthorityRuntimeId, + [arguments.VerseId], + [CultMeshProtocols.Documents.Value], + [arguments.RouteGeneration]); using var subscriptions = new CultNetDatabaseSubscriptionServer(schemaServer, node.Database); using var operationGate = new SemaphoreSlim(1, 1); using var operations = new CultNetOperationServer(schemaServer, arguments.AuthorityRuntimeId) @@ -347,12 +370,16 @@ sealed class Args public string StatePath { get; private init; } = "sample-counter.cc"; public string OdinEndpoint { get; private init; } = ""; public string ProviderEndpoint { get; private init; } = ""; + public string DecoyEndpoint { get; private init; } = ""; public string Token { get; private init; } = "sample-session"; public string VerseId { get; private init; } = "sample.counter"; public string VerseName { get; private init; } = "CultMesh browser counter"; public string AuthorityRuntimeId { get; private init; } = "sample.counter-daemon"; + public string DecoyAuthorityRuntimeId { get; private init; } = "sample.decoy-daemon"; public string TransportVersion { get; private init; } = "cultmesh.v0"; public string RulesHash { get; private init; } = "sample-counter-v1"; + public string RouteGeneration { get; private init; } = "sample-counter-route-1"; + public string DecoyRouteGeneration { get; private init; } = "sample-decoy-route-1"; public int ExpectedCount { get; private init; } = 2; public int BenchmarkOperations { get; private init; } = 10_000; @@ -369,12 +396,16 @@ sealed class Args StatePath = named.GetValueOrDefault("--state", "sample-counter.cc"), OdinEndpoint = named.GetValueOrDefault("--odin", ""), ProviderEndpoint = named.GetValueOrDefault("--provider-endpoint", ""), + DecoyEndpoint = named.GetValueOrDefault("--decoy-endpoint", ""), Token = named.GetValueOrDefault("--token", "sample-session"), VerseId = named.GetValueOrDefault("--verse-id", "sample.counter"), VerseName = named.GetValueOrDefault("--verse-name", "CultMesh browser counter"), AuthorityRuntimeId = named.GetValueOrDefault("--authority-runtime-id", "sample.counter-daemon"), + DecoyAuthorityRuntimeId = named.GetValueOrDefault("--decoy-authority-runtime-id", "sample.decoy-daemon"), TransportVersion = named.GetValueOrDefault("--transport-version", "cultmesh.v0"), RulesHash = named.GetValueOrDefault("--rules-hash", "sample-counter-v1"), + RouteGeneration = named.GetValueOrDefault("--route-generation", "sample-counter-route-1"), + DecoyRouteGeneration = named.GetValueOrDefault("--decoy-route-generation", "sample-decoy-route-1"), ExpectedCount = named.TryGetValue("--expected-count", out var expectedCount) ? int.Parse(expectedCount) : 2, diff --git a/samples/eve-browser-network/README.md b/samples/eve-browser-network/README.md index 7ad20a00..445bfb9d 100644 --- a/samples/eve-browser-network/README.md +++ b/samples/eve-browser-network/README.md @@ -12,6 +12,9 @@ This is the real-process continuation of `samples/eve-two-runtime`: without application transport or reconnect code; - one separate local Odin fixture answers the canonical CultMesh Verse catalog over binary CultNet WebSocket messages; +- one wrong-authority provider is deliberately advertised on a better-priority + route, and both clients must ignore it because the route is bound to another + runtime; - the browser and C# clients speak the same binary CultNet schema-v0 WebSocket messages through the authenticated host adapter. diff --git a/samples/eve-browser-network/browser/main.ts b/samples/eve-browser-network/browser/main.ts index 3947516b..3b0e28f9 100644 --- a/samples/eve-browser-network/browser/main.ts +++ b/samples/eve-browser-network/browser/main.ts @@ -86,7 +86,11 @@ try { window.__sampleCount = decodeCultNetPayload<{ count: number }>(counter.current!).count; window.__sampleReady = true; } catch (error) { - window.__sampleError = error instanceof Error ? error.stack || error.message : String(error); + const details = error instanceof AggregateError + ? error.errors.map(inner => inner instanceof Error ? inner.stack || inner.message : String(inner)).join("\n---\n") + : ""; + window.__sampleError = (error instanceof Error ? error.stack || error.message : String(error)) + + (details ? `\n${details}` : ""); host.textContent = window.__sampleError; throw error; } diff --git a/scripts/verify-eve-browser-network.mjs b/scripts/verify-eve-browser-network.mjs index 3f633dc9..20d819a0 100644 --- a/scripts/verify-eve-browser-network.mjs +++ b/scripts/verify-eve-browser-network.mjs @@ -17,9 +17,11 @@ const eveRoot = resolve(eveFlag >= 0 ? process.argv[eveFlag + 1] : join(repoRoot const sampleRoot = join(repoRoot, "samples", "eve-browser-network"); const workRoot = await mkdtemp(join(tmpdir(), "cultmesh-browser-network-")); const statePath = join(workRoot, "counter.cc"); +const decoyStatePath = join(workRoot, "decoy-counter.cc"); const bundlePath = join(workRoot, "bundle.js"); const token = "sample-session"; let provider; +let decoyProvider; let replacementProvider; let odin; let headless; @@ -41,6 +43,8 @@ try { alias: { "@gamecult/eve-contracts": join(eveRoot, "packages", "eve-contracts", "src", "index.ts"), "@gamecult/eve-browser-lowering": join(eveRoot, "packages", "eve-browser-lowering", "src", "index.ts"), + "cultmesh-browser": join(repoRoot, "packages", "cultmesh-browser", "src", "index.ts"), + "cultnet-ts/contracts": join(repoRoot, "packages", "cultnet-ts", "src", "contracts.ts"), }, logLevel: "warning", }); @@ -54,14 +58,22 @@ try { "--verbosity", "quiet", "-p:NoWarn=1591%3BCS8632", `-p:EveRoot=${eveRoot}`, + `-p:CultLibRoot=${repoRoot}`, ]); const providerPort = await freePort(); + const decoyProviderPort = await freePort(); const replacementProviderPort = await freePort(); const odinPort = await freePort(); const httpPort = await freePort(); provider = await startProvider(providerPort); const endpoint = await provider.waitFor("PROVIDER_READY "); - odin = await startOdin(odinPort, endpoint); + decoyProvider = await startProvider(decoyProviderPort, { + authorityRuntimeId: "sample.decoy-daemon", + routeGeneration: "sample-decoy-route-1", + statePath: decoyStatePath, + }); + const decoyEndpoint = await decoyProvider.waitFor("PROVIDER_READY "); + odin = await startOdin(odinPort, endpoint, decoyEndpoint); const odinEndpoint = await odin.waitFor("ODIN_READY "); headless = startDotnet([ "headless", "--odin", odinEndpoint, "--verse-id", "sample.counter", @@ -91,7 +103,7 @@ try { const restartedEndpoint = await replacementProvider.waitFor("PROVIDER_READY "); assert.notEqual(restartedEndpoint, endpoint); await stop(odin.process); - odin = await startOdin(odinPort, restartedEndpoint); + odin = await startOdin(odinPort, restartedEndpoint, decoyEndpoint); assert.equal(await odin.waitFor("ODIN_READY "), odinEndpoint); await stop(provider.process); provider = replacementProvider; @@ -122,6 +134,7 @@ try { console.log(JSON.stringify({ providerEndpoint: endpoint, replacementProviderEndpoint: restartedEndpoint, + rejectedWrongAuthorityEndpoint: decoyEndpoint, odinEndpoint, browserRuntime: "chromium", headlessRuntime: "csharp", @@ -136,21 +149,31 @@ try { if (browser) await browser.close().catch(() => undefined); if (headless) await stop(headless.process); if (provider) await stop(provider.process); + if (decoyProvider) await stop(decoyProvider.process); if (replacementProvider) await stop(replacementProvider.process); if (odin) await stop(odin.process); if (httpServer) await new Promise(resolve => httpServer.close(resolve)); await rm(workRoot, { recursive: true, force: true }); } -async function startProvider(port) { - return startDotnet(["provider", "--port", String(port), "--state", statePath, "--token", token]); +async function startProvider(port, options = {}) { + return startDotnet([ + "provider", "--port", String(port), + "--state", options.statePath ?? statePath, + "--authority-runtime-id", options.authorityRuntimeId ?? "sample.counter-daemon", + "--route-generation", options.routeGeneration ?? "sample-counter-route-1", + "--token", token, + ]); } -async function startOdin(port, providerEndpoint) { +async function startOdin(port, providerEndpoint, decoyEndpoint) { return startDotnet([ "odin", "--port", String(port), "--provider-endpoint", providerEndpoint, + "--decoy-endpoint", decoyEndpoint, + "--decoy-authority-runtime-id", "sample.decoy-daemon", + "--decoy-route-generation", "sample-decoy-route-1", "--token", token, ]); } diff --git a/src/GameCult.Mesh.Quic.Native/CultMeshNativeQuicRealtimeTransport.cs b/src/GameCult.Mesh.Quic.Native/CultMeshNativeQuicRealtimeTransport.cs index d687d5b6..cb356044 100644 --- a/src/GameCult.Mesh.Quic.Native/CultMeshNativeQuicRealtimeTransport.cs +++ b/src/GameCult.Mesh.Quic.Native/CultMeshNativeQuicRealtimeTransport.cs @@ -42,11 +42,11 @@ namespace GameCult.Mesh.Quic.Native public async Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { if (candidate == null) throw new ArgumentNullException(nameof(candidate)); - if (endpointId == null) throw new ArgumentNullException(nameof(endpointId)); + if (target == null) throw new ArgumentNullException(nameof(target)); if (!TryParseEndpoint(candidate.Endpoint, out var host, out var port, out var pin)) throw new NotSupportedException($"Native QUIC connector does not support '{candidate.Endpoint}'."); @@ -72,7 +72,9 @@ namespace GameCult.Mesh.Quic.Native var transport = new CultMeshNativeQuicRealtimeTransport( candidate.Endpoint, handle, - _options.PollInterval); + _options.PollInterval, + target, + candidate.Generation); try { await transport.WaitUntilConnectedAsync(_options.HandshakeTimeout, cancellationToken).ConfigureAwait(false); @@ -122,23 +124,42 @@ namespace GameCult.Mesh.Quic.Native } } - internal sealed class CultMeshNativeQuicRealtimeTransport : ICultMeshRealtimeTransport + internal sealed class CultMeshNativeQuicRealtimeTransport : ICultMeshRealtimeTransport, ICultMeshVerifiedTransport { private readonly object _gate = new object(); private readonly TimeSpan _pollInterval; private IntPtr _handle; + private readonly CultMeshSessionTarget _target; + private readonly string _routeGeneration; private bool _disposed; - public CultMeshNativeQuicRealtimeTransport(string endpoint, IntPtr handle, TimeSpan pollInterval) + public CultMeshNativeQuicRealtimeTransport( + string endpoint, + IntPtr handle, + TimeSpan pollInterval, + CultMeshSessionTarget target, + string routeGeneration) { Endpoint = endpoint; _handle = handle; _pollInterval = pollInterval; + _target = target; + _routeGeneration = routeGeneration ?? string.Empty; } public string TransportId => "msquic-native-realtime"; public string Endpoint { get; } + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + string.Equals(_target.VerseId, verseId, StringComparison.Ordinal) && + string.Equals(_target.AuthorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.RealtimeState.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + public Task SendAsync(CultMeshRealtimeFrame frame, CancellationToken cancellationToken = default) => Task.FromException(new NotSupportedException( "The Unity-compatible native QUIC connector currently owns provider-to-client state only.")); diff --git a/src/GameCult.Mesh.Quic/CultMeshQuicRealtimeTransport.cs b/src/GameCult.Mesh.Quic/CultMeshQuicRealtimeTransport.cs index a0198a32..91af6d7c 100644 --- a/src/GameCult.Mesh.Quic/CultMeshQuicRealtimeTransport.cs +++ b/src/GameCult.Mesh.Quic/CultMeshQuicRealtimeTransport.cs @@ -22,7 +22,7 @@ public sealed class CultMeshQuicRealtimeConnectorOptions /// /// Optional provider-aware certificate validator. Standard TLS validation is used when omitted. /// - public Func? + public Func? ValidateProviderCertificate { get; set; } } @@ -55,11 +55,11 @@ public sealed class CultMeshQuicRealtimeTransportConnector : ICultMeshRealtimeTr /// public async Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { if (candidate == null) throw new ArgumentNullException(nameof(candidate)); - if (endpointId == null) throw new ArgumentNullException(nameof(endpointId)); + if (target == null) throw new ArgumentNullException(nameof(target)); if (!QuicConnection.IsSupported) throw new PlatformNotSupportedException("QUIC is unavailable. Install a supported MsQuic runtime."); if (!TryParseEndpoint(candidate.Endpoint, out var host, out var port)) @@ -84,14 +84,15 @@ public sealed class CultMeshQuicRealtimeTransportConnector : ICultMeshRealtimeTr { var providerCertificate = certificate as X509Certificate2 ?? (certificate == null ? null : new X509Certificate2(certificate)); - return validator?.Invoke(endpointId, providerCertificate, chain, errors) ?? + return validator?.Invoke(CultMeshRuntimeId.Parse(target.AuthorityRuntimeId), providerCertificate, chain, errors) ?? errors == SslPolicyErrors.None || MatchesAdvertisedCertificatePin(candidate.Endpoint, providerCertificate); } } }, cancellationToken).ConfigureAwait(false); - return new CultMeshQuicRealtimeTransport(candidate.Endpoint, connection); + return new CultMeshQuicRealtimeTransport( + candidate.Endpoint, connection, target: target, routeGeneration: candidate.Generation); } private static bool TryParseEndpoint(string? endpoint, out string host, out int port) @@ -308,7 +309,7 @@ public sealed class CultMeshQuicRealtimeServer : IAsyncDisposable } } -internal sealed class CultMeshQuicRealtimeTransport : ICultMeshRealtimeTransport +internal sealed class CultMeshQuicRealtimeTransport : ICultMeshRealtimeTransport, ICultMeshVerifiedTransport { private readonly QuicConnection _connection; private readonly CancellationTokenSource _shutdown = new(); @@ -318,6 +319,8 @@ internal sealed class CultMeshQuicRealtimeTransport : ICultMeshRealtimeTransport private readonly SemaphoreSlim _reliableSendGate = new(1, 1); private readonly CultMeshRealtimeInbox _outbound = new(); private readonly Action? _observer; + private readonly CultMeshSessionTarget? _target; + private readonly string _routeGeneration; private readonly Task _acceptLoop; private readonly Task _outboundLoop; private readonly Task _completion; @@ -328,11 +331,15 @@ internal sealed class CultMeshQuicRealtimeTransport : ICultMeshRealtimeTransport public CultMeshQuicRealtimeTransport( string endpoint, QuicConnection connection, - Action? observer = null) + Action? observer = null, + CultMeshSessionTarget? target = null, + string routeGeneration = "") { Endpoint = endpoint; _connection = connection; _observer = observer; + _target = target; + _routeGeneration = routeGeneration ?? string.Empty; _acceptLoop = AcceptStreamsAsync(_shutdown.Token); _outboundLoop = SendPublishedFramesAsync(_shutdown.Token); _completion = Task.WhenAll(_acceptLoop, _outboundLoop); @@ -342,6 +349,17 @@ internal sealed class CultMeshQuicRealtimeTransport : ICultMeshRealtimeTransport public string Endpoint { get; } public Task Completion => _completion; + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + _target != null && + string.Equals(_target.VerseId, verseId, StringComparison.Ordinal) && + string.Equals(_target.AuthorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.RealtimeState.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + internal bool TryPublishLatest(CultMeshRealtimeFrame frame) { if (_disposed || frame.Delivery != CultMeshRealtimeDelivery.LatestOnly) return false; diff --git a/src/GameCult.Mesh/CultMeshClient.cs b/src/GameCult.Mesh/CultMeshClient.cs index b4667f9c..19a01c54 100644 --- a/src/GameCult.Mesh/CultMeshClient.cs +++ b/src/GameCult.Mesh/CultMeshClient.cs @@ -391,7 +391,8 @@ namespace GameCult.Mesh PayloadSchema = requestSchema.Trim(), PayloadEncoding = "messagepack-base64", Payload = Convert.ToBase64String(MessagePackSerializer.Serialize(request, CultNetSchemaMessageSerialization.Options)), - SourceRuntimeId = sourceRuntimeId.Trim() + SourceRuntimeId = sourceRuntimeId.Trim(), + TargetRuntimeId = target.AuthorityRuntimeId }; using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeout.CancelAfter(_operationResponseTimeout); @@ -441,6 +442,11 @@ namespace GameCult.Mesh if (!string.Equals(envelope.ServiceId, expectedService, StringComparison.Ordinal) || !string.Equals(envelope.Operation, expectedOperation, StringComparison.Ordinal)) throw new InvalidOperationException("CultMesh operation response did not match the requested service and operation."); + if (!string.Equals(envelope.SourceRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal)) + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authority, + $"CultMesh operation response came from runtime '{envelope.SourceRuntimeId ?? "(missing)"}', " + + $"not selected authority '{target.AuthorityRuntimeId}'.")); if (string.Equals(envelope.PayloadSchema, CultNetOperationServer.FailureSchemaId, StringComparison.Ordinal)) { if (!string.Equals(envelope.PayloadEncoding, "messagepack-base64", StringComparison.Ordinal)) @@ -795,7 +801,7 @@ namespace GameCult.Mesh _cache, new CultRecordKey(recordKey), CultMesh.Verse(target.VerseId, "cultmesh-client").Context, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, target.ProviderRuntimeId)); + routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, target.AuthorityRuntimeId)); } public CultMeshDocumentHandle Handle { get; } @@ -897,7 +903,7 @@ namespace GameCult.Mesh _subscription = new CultNetDatabaseSubscriptionClient(session.OpenSchemaClient(), _cache, networkRegistry); Handle = CultMesh.Collection( _cache, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, target.ProviderRuntimeId)); + routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, target.AuthorityRuntimeId)); } public CultMeshCollectionHandle Handle { get; } diff --git a/src/GameCult.Mesh/CultMeshContentSessions.cs b/src/GameCult.Mesh/CultMeshContentSessions.cs index 51570c54..4c562450 100644 --- a/src/GameCult.Mesh/CultMeshContentSessions.cs +++ b/src/GameCult.Mesh/CultMeshContentSessions.cs @@ -122,31 +122,56 @@ namespace GameCult.Mesh public async Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { if (!CanConnect(candidate)) throw new NotSupportedException($"Legacy RUDP content connector does not support '{candidate.Endpoint}'."); var client = await _schemaConnector.ConnectAsync(candidate, CultMeshProtocols.Content, cancellationToken) .ConfigureAwait(false); - return new LegacyRudpContentTransport(candidate.Endpoint, client, _options); + try + { + await CultMeshSessionIdentityClient.VerifyAsync( + client, + "cultmesh-content-client", + target, + CultMeshProtocols.Content, + candidate.Generation, + _options.ResponseTimeout, + cancellationToken).ConfigureAwait(false); + return new LegacyRudpContentTransport( + candidate.Endpoint, client, _options, target, candidate.Generation); + } + catch + { + client.Dispose(); + throw; + } } - private sealed class LegacyRudpContentTransport : ICultMeshContentTransport + private sealed class LegacyRudpContentTransport : ICultMeshContentTransport, ICultMeshVerifiedTransport { private readonly ICultNetSchemaClient _client; private readonly CultMeshLegacyRudpContentTransportOptions _options; + private readonly string _verseId; + private readonly string _authorityRuntimeId; + private readonly string _routeGeneration; private readonly System.Collections.Concurrent.ConcurrentDictionary> _pending = new(StringComparer.Ordinal); private bool _disposed; public LegacyRudpContentTransport( string endpoint, ICultNetSchemaClient client, - CultMeshLegacyRudpContentTransportOptions options) + CultMeshLegacyRudpContentTransportOptions options, + CultMeshSessionTarget target, + string routeGeneration) { Endpoint = endpoint; _client = client; _options = options; + _verseId = target.VerseId; + _authorityRuntimeId = target.AuthorityRuntimeId; + _routeGeneration = routeGeneration ?? string.Empty; _client.OnCultNet(OnResponse); _client.OnCultNet(OnError); } @@ -154,6 +179,16 @@ namespace GameCult.Mesh public string TransportId => "legacy-rudp-content"; public string Endpoint { get; } + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + string.Equals(_verseId, verseId, StringComparison.Ordinal) && + string.Equals(_authorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.Content.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + public async Task CopyChunkToAsync( CultMeshCdnChunkRef chunk, Stream destination, diff --git a/src/GameCult.Mesh/CultMeshContentTransports.cs b/src/GameCult.Mesh/CultMeshContentTransports.cs index 3e2c54b1..891c221a 100644 --- a/src/GameCult.Mesh/CultMeshContentTransports.cs +++ b/src/GameCult.Mesh/CultMeshContentTransports.cs @@ -36,7 +36,7 @@ namespace GameCult.Mesh bool CanConnect(CultMeshTransportCandidate candidate); Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default); } diff --git a/src/GameCult.Mesh/CultMeshDiscoveryService.cs b/src/GameCult.Mesh/CultMeshDiscoveryService.cs index 93ea6a84..3f108e03 100644 --- a/src/GameCult.Mesh/CultMeshDiscoveryService.cs +++ b/src/GameCult.Mesh/CultMeshDiscoveryService.cs @@ -267,7 +267,8 @@ namespace GameCult.Mesh var observations = results.SelectMany(result => result.Observations) .Where(observation => query.VerseIds.Count == 0 || query.VerseIds.Contains(observation.Candidate.VerseId, StringComparer.Ordinal)) .Where(observation => query.AuthorityRuntimeId == null || - observation.Candidate.AuthorityRuntimeIds.Contains(query.AuthorityRuntimeId, StringComparer.Ordinal)) + observation.Candidate.AuthorityRoutes.Any(route => + string.Equals(route.AuthorityRuntimeId, query.AuthorityRuntimeId, StringComparison.Ordinal))) .ToArray(); foreach (var rejected in observations.Where(observation => observation.Trust == CultMeshDiscoveryTrust.Rejected)) diff --git a/src/GameCult.Mesh/CultMeshRealtimeTransports.cs b/src/GameCult.Mesh/CultMeshRealtimeTransports.cs index 7b32ad6a..d37b7381 100644 --- a/src/GameCult.Mesh/CultMeshRealtimeTransports.cs +++ b/src/GameCult.Mesh/CultMeshRealtimeTransports.cs @@ -59,7 +59,7 @@ namespace GameCult.Mesh bool CanConnect(CultMeshTransportCandidate candidate); Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default); } diff --git a/src/GameCult.Mesh/CultMeshSessionIdentity.cs b/src/GameCult.Mesh/CultMeshSessionIdentity.cs new file mode 100644 index 00000000..bb17a3d8 --- /dev/null +++ b/src/GameCult.Mesh/CultMeshSessionIdentity.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GameCult.Networking; + +namespace GameCult.Mesh +{ + /// + /// Optional transport proof surfaced by connectors that already authenticate + /// the peer during their native handshake. Other schema clients must complete + /// the portable CultMesh handshake before use. + /// + public interface ICultMeshVerifiedSchemaClient + { + bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration); + } + + /// + /// Proof exposed by content and realtime transports after their native or + /// portable handshake has bound the physical channel to an Odin route. + /// + public interface ICultMeshVerifiedTransport + { + bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration); + } + + internal static class CultMeshTransportIdentity + { + public static void RequireVerified( + object transport, + CultMeshSessionTarget target, + CultMeshProtocolId protocol, + CultMeshTransportCandidate candidate) + { + if (transport is ICultMeshVerifiedTransport verified && + verified.IsVerifiedFor( + target.VerseId, + target.AuthorityRuntimeId, + protocol.Value, + candidate.Generation)) + return; + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authority, + $"Transport did not prove CultMesh authority '{target}' for protocol '{protocol}'.", + candidate.Endpoint)); + } + } + + /// + /// Owns the server side of the transport-neutral CultMesh session identity + /// handshake. Application messages are not authority proof; providers attach + /// this gate to every schema transport they advertise. + /// + public sealed class CultMeshSessionIdentityServer : IDisposable + { + private readonly ICultNetSchemaServer _server; + private readonly string _authorityRuntimeId; + private readonly HashSet _verseIds; + private readonly HashSet _protocolIds; + private readonly HashSet _routeGenerations; + private readonly Func _handler; + private bool _disposed; + + public CultMeshSessionIdentityServer( + ICultNetSchemaServer server, + string authorityRuntimeId, + IEnumerable verseIds, + IEnumerable protocolIds, + IEnumerable routeGenerations) + { + _server = server ?? throw new ArgumentNullException(nameof(server)); + _authorityRuntimeId = Require(authorityRuntimeId, nameof(authorityRuntimeId)); + _verseIds = Clean(verseIds, nameof(verseIds)); + _protocolIds = Clean(protocolIds, nameof(protocolIds)); + _routeGenerations = Clean(routeGenerations, nameof(routeGenerations)); + _handler = HandleAsync; + _server.OnCultNet(_handler); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _server.RemoveCultNetMessageListener(_handler); + } + + private Task HandleAsync(CultMeshSessionOpenMessage request, ICultNetSchemaServerPeer peer) + { + var error = Validate(request); + peer.SendCultNet(new CultMeshSessionAcceptedMessage + { + MessageId = request.MessageId ?? string.Empty, + Accepted = error == null, + VerseId = request.VerseId ?? string.Empty, + AuthorityRuntimeId = _authorityRuntimeId, + ProtocolId = request.ProtocolId ?? string.Empty, + RouteGeneration = request.RouteGeneration ?? string.Empty, + Error = error + }); + return Task.CompletedTask; + } + + private string? Validate(CultMeshSessionOpenMessage request) + { + if (string.IsNullOrWhiteSpace(request.MessageId)) return "session-message-id-required"; + if (string.IsNullOrWhiteSpace(request.SourceRuntimeId)) return "source-runtime-id-required"; + if (!string.Equals(request.AuthorityRuntimeId, _authorityRuntimeId, StringComparison.Ordinal)) + return "target-runtime-mismatch"; + if (!_verseIds.Contains(request.VerseId ?? string.Empty)) return "verse-not-served"; + if (!_protocolIds.Contains(request.ProtocolId ?? string.Empty)) return "protocol-not-served"; + if (!_routeGenerations.Contains(request.RouteGeneration ?? string.Empty)) return "route-generation-not-served"; + return null; + } + + private static HashSet Clean(IEnumerable values, string name) + { + if (values == null) throw new ArgumentNullException(name); + var result = new HashSet( + values.Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()), + StringComparer.Ordinal); + if (result.Count == 0) throw new ArgumentException("At least one identity is required.", name); + return result; + } + + private static string Require(string value, string name) => + string.IsNullOrWhiteSpace(value) + ? throw new ArgumentException("Value must be non-empty.", name) + : value.Trim(); + } + + internal static class CultMeshSessionIdentityClient + { + public static async Task VerifyAsync( + ICultNetSchemaClient client, + string sourceRuntimeId, + CultMeshSessionTarget target, + CultMeshProtocolId protocol, + string routeGeneration, + TimeSpan timeout, + CancellationToken cancellationToken = default) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (client is ICultMeshVerifiedSchemaClient verified) + { + if (!verified.IsVerifiedFor( + target.VerseId, + target.AuthorityRuntimeId, + protocol.Value, + routeGeneration ?? string.Empty)) + { + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authority, + $"Native transport identity did not match CultMesh target '{target}' for '{protocol}'.")); + } + return; + } + var messageId = Guid.NewGuid().ToString("N"); + var completion = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + client.OnCultNet(response => + { + if (string.Equals(response.MessageId, messageId, StringComparison.Ordinal)) + completion.TrySetResult(response); + }); + client.SendCultNet(new CultMeshSessionOpenMessage + { + MessageId = messageId, + SourceRuntimeId = sourceRuntimeId, + VerseId = target.VerseId, + AuthorityRuntimeId = target.AuthorityRuntimeId, + ProtocolId = protocol.Value, + RouteGeneration = routeGeneration ?? string.Empty + }); + + using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + deadline.CancelAfter(timeout); + var cancelled = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using (deadline.Token.Register(() => cancelled.TrySetResult(true))) + { + if (await Task.WhenAny(completion.Task, cancelled.Task).ConfigureAwait(false) != completion.Task) + { + cancellationToken.ThrowIfCancellationRequested(); + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Timeout, + $"Timed out verifying authority runtime '{target.AuthorityRuntimeId}'.")); + } + } + + var accepted = await completion.Task.ConfigureAwait(false); + if (!accepted.Accepted || + !string.Equals(accepted.VerseId, target.VerseId, StringComparison.Ordinal) || + !string.Equals(accepted.AuthorityRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal) || + !string.Equals(accepted.ProtocolId, protocol.Value, StringComparison.Ordinal) || + !string.Equals(accepted.RouteGeneration, routeGeneration ?? string.Empty, StringComparison.Ordinal)) + { + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authority, + $"Route did not prove CultMesh authority '{target}' for protocol '{protocol}': " + + (accepted.Error ?? "identity-mismatch"))); + } + } + } +} diff --git a/src/GameCult.Mesh/CultMeshSessions.cs b/src/GameCult.Mesh/CultMeshSessions.cs index c562de83..7db6f06b 100644 --- a/src/GameCult.Mesh/CultMeshSessions.cs +++ b/src/GameCult.Mesh/CultMeshSessions.cs @@ -9,14 +9,14 @@ using R3; namespace GameCult.Mesh { - public sealed class CultMeshEndpointId : IEquatable + public sealed class CultMeshRuntimeId : IEquatable { - private CultMeshEndpointId(string value) { Value = value; } + private CultMeshRuntimeId(string value) { Value = value; } public string Value { get; } - public static CultMeshEndpointId Parse(string value) => new CultMeshEndpointId( - string.IsNullOrWhiteSpace(value) ? throw new FormatException("CultMesh endpoint identity must be non-empty.") : value.Trim()); - public bool Equals(CultMeshEndpointId? other) => other != null && string.Equals(Value, other.Value, StringComparison.Ordinal); - public override bool Equals(object? obj) => Equals(obj as CultMeshEndpointId); + public static CultMeshRuntimeId Parse(string value) => new CultMeshRuntimeId( + string.IsNullOrWhiteSpace(value) ? throw new FormatException("CultMesh runtime identity must be non-empty.") : value.Trim()); + public bool Equals(CultMeshRuntimeId? other) => other != null && string.Equals(Value, other.Value, StringComparison.Ordinal); + public override bool Equals(object? obj) => Equals(obj as CultMeshRuntimeId); public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value); public override string ToString() => Value; } @@ -28,31 +28,32 @@ namespace GameCult.Mesh public sealed class CultMeshSessionTarget : IEquatable { /// Creates one stable provider target inside one Verse. - public CultMeshSessionTarget(string verseId, string providerRuntimeId) + public CultMeshSessionTarget(string verseId, string authorityRuntimeId) { VerseId = string.IsNullOrWhiteSpace(verseId) ? throw new ArgumentException("Verse identity is required.", nameof(verseId)) : verseId.Trim(); - ProviderEndpointId = CultMeshEndpointId.Parse(providerRuntimeId); - ProviderRuntimeId = ProviderEndpointId.Value; + AuthorityRuntimeId = CultMeshRuntimeId.Parse(authorityRuntimeId).Value; } /// Gets the stable Verse identity. public string VerseId { get; } - /// Gets the stable provider runtime identity advertised by that Verse. - public string ProviderRuntimeId { get; } - internal CultMeshEndpointId ProviderEndpointId { get; } - internal string SessionKey => VerseId + "\u001f" + ProviderRuntimeId; + /// Gets the stable authority runtime identity advertised by that Verse. + public string AuthorityRuntimeId { get; } + /// Compatibility projection. Use . + [Obsolete("Use AuthorityRuntimeId. Provider identity is not a physical endpoint.")] + public string ProviderRuntimeId => AuthorityRuntimeId; + internal string SessionKey => VerseId + "\u001f" + AuthorityRuntimeId; public bool Equals(CultMeshSessionTarget? other) => other != null && string.Equals(VerseId, other.VerseId, StringComparison.Ordinal) && - string.Equals(ProviderRuntimeId, other.ProviderRuntimeId, StringComparison.Ordinal); + string.Equals(AuthorityRuntimeId, other.AuthorityRuntimeId, StringComparison.Ordinal); public override bool Equals(object? obj) => Equals(obj as CultMeshSessionTarget); public override int GetHashCode() => HashCode.Combine( StringComparer.Ordinal.GetHashCode(VerseId), - StringComparer.Ordinal.GetHashCode(ProviderRuntimeId)); - public override string ToString() => VerseId + "/" + ProviderRuntimeId; + StringComparer.Ordinal.GetHashCode(AuthorityRuntimeId)); + public override string ToString() => VerseId + "/" + AuthorityRuntimeId; } public sealed class CultMeshProtocolId : IEquatable @@ -80,15 +81,24 @@ namespace GameCult.Mesh public sealed class CultMeshTransportCandidate { - public CultMeshTransportCandidate(string endpoint, CultMeshTransportPathKind pathKind = CultMeshTransportPathKind.Direct, int priority = 0) + public CultMeshTransportCandidate( + string endpoint, + CultMeshTransportPathKind pathKind = CultMeshTransportPathKind.Direct, + int priority = 0, + string authorityRuntimeId = "", + string generation = "") { Endpoint = string.IsNullOrWhiteSpace(endpoint) ? throw new ArgumentException("Physical endpoint is required.", nameof(endpoint)) : endpoint; PathKind = pathKind; Priority = priority; + AuthorityRuntimeId = authorityRuntimeId?.Trim() ?? string.Empty; + Generation = generation?.Trim() ?? string.Empty; } public string Endpoint { get; } public CultMeshTransportPathKind PathKind { get; } public int Priority { get; } + public string AuthorityRuntimeId { get; } + public string Generation { get; } } public enum CultMeshSessionStatus { Connecting, Online, Degraded, Reconnecting, Offline } @@ -522,6 +532,8 @@ namespace GameCult.Mesh public ICultMeshClock Clock { get; set; } = CultMeshSystemClock.Instance; public ICultMeshDiagnosticSink Diagnostics { get; set; } = CultMeshNullDiagnosticSink.Instance; public int MaxRacedCandidates { get; set; } = 2; + public string RuntimeId { get; set; } = "cultmesh-client"; + public TimeSpan IdentityHandshakeTimeout { get; set; } = TimeSpan.FromSeconds(5); } public sealed class CultMeshSessionManager : IDisposable @@ -581,6 +593,8 @@ namespace GameCult.Mesh throw new ArgumentException("Realtime transport connectors cannot contain null entries.", nameof(realtimeConnectors)); _options = options ?? new CultMeshSessionManagerOptions(); if (_options.MaxRacedCandidates <= 0) throw new ArgumentOutOfRangeException(nameof(options)); + if (string.IsNullOrWhiteSpace(_options.RuntimeId)) throw new ArgumentException("Client runtime identity is required.", nameof(options)); + if (_options.IdentityHandshakeTimeout <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(options)); } public async Task ConnectAsync( @@ -661,14 +675,10 @@ namespace GameCult.Mesh "No streaming content connectors are configured. Register the TCP content connector or the explicit legacy RUDP connector."); var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( - target.ProviderRuntimeId, + target.AuthorityRuntimeId, new[] { target.VerseId }, - target.ProviderRuntimeId)).ConfigureAwait(false); - var candidates = discovery.Candidates - .SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) - .Distinct(StringComparer.Ordinal) - .Select(endpoint => new CultMeshTransportCandidate(endpoint)) - .ToArray(); + target.AuthorityRuntimeId)).ConfigureAwait(false); + var candidates = BoundCandidates(discovery, target, CultMeshProtocols.Content); var routes = candidates .SelectMany(candidate => _contentConnectors .Where(connector => connector.CanConnect(candidate)) @@ -685,7 +695,7 @@ namespace GameCult.Mesh foreach (var tier in routes.GroupBy(route => route.Connector.Priority).OrderBy(group => group.Key)) { var attempts = tier.Take(_options.MaxRacedCandidates) - .Select(route => ConnectContentCandidateAsync(target.ProviderEndpointId, route)) + .Select(route => ConnectContentCandidateAsync(target, route)) .ToList(); while (attempts.Count > 0) { @@ -709,18 +719,33 @@ namespace GameCult.Mesh } var last = failures.LastOrDefault(); + var reason = last is CultMeshSessionException typed + ? typed.Failure.Reason + : last is TimeoutException + ? CultMeshSessionFailureReason.Timeout + : CultMeshSessionFailureReason.Transport; throw Failure( - last is TimeoutException ? CultMeshSessionFailureReason.Timeout : CultMeshSessionFailureReason.Transport, + reason, $"No streaming content path connected for '{target}'.", last); } private static async Task ConnectContentCandidateAsync( - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, ContentRoute route) { - var transport = await route.Connector.ConnectAsync(route.Candidate, endpointId).ConfigureAwait(false); - return new ConnectedContentPath(route.Candidate, transport); + var transport = await route.Connector.ConnectAsync(route.Candidate, target).ConfigureAwait(false); + try + { + CultMeshTransportIdentity.RequireVerified( + transport, target, CultMeshProtocols.Content, route.Candidate); + return new ConnectedContentPath(route.Candidate, transport); + } + catch + { + transport.Dispose(); + throw; + } } private void InvalidateContentSession(string key, CultMeshContentSession session) @@ -790,13 +815,10 @@ namespace GameCult.Mesh "No realtime state connectors are configured. Register a QUIC connector explicitly."); var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( - target.ProviderRuntimeId, + target.AuthorityRuntimeId, new[] { target.VerseId }, - target.ProviderRuntimeId)).ConfigureAwait(false); - var routes = discovery.Candidates - .SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) - .Distinct(StringComparer.Ordinal) - .Select(endpoint => new CultMeshTransportCandidate(endpoint)) + target.AuthorityRuntimeId)).ConfigureAwait(false); + var routes = BoundCandidates(discovery, target, CultMeshProtocols.RealtimeState) .SelectMany(candidate => _realtimeConnectors .Where(connector => connector.CanConnect(candidate)) .Select(connector => new RealtimeRoute(candidate, connector))) @@ -812,7 +834,7 @@ namespace GameCult.Mesh foreach (var tier in routes.GroupBy(route => route.Connector.Priority).OrderBy(group => group.Key)) { var attempts = tier.Take(_options.MaxRacedCandidates) - .Select(route => ConnectRealtimeCandidateAsync(target.ProviderEndpointId, route)) + .Select(route => ConnectRealtimeCandidateAsync(target, route)) .ToList(); while (attempts.Count > 0) { @@ -836,18 +858,33 @@ namespace GameCult.Mesh } var last = failures.LastOrDefault(); + var reason = last is CultMeshSessionException typed + ? typed.Failure.Reason + : last is TimeoutException + ? CultMeshSessionFailureReason.Timeout + : CultMeshSessionFailureReason.Transport; throw Failure( - last is TimeoutException ? CultMeshSessionFailureReason.Timeout : CultMeshSessionFailureReason.Transport, + reason, $"No realtime state path connected for '{target}'.", last); } private static async Task ConnectRealtimeCandidateAsync( - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, RealtimeRoute route) { - var transport = await route.Connector.ConnectAsync(route.Candidate, endpointId).ConfigureAwait(false); - return new ConnectedRealtimePath(route.Candidate, transport); + var transport = await route.Connector.ConnectAsync(route.Candidate, target).ConfigureAwait(false); + try + { + CultMeshTransportIdentity.RequireVerified( + transport, target, CultMeshProtocols.RealtimeState, route.Candidate); + return new ConnectedRealtimePath(route.Candidate, transport); + } + catch + { + transport.Dispose(); + throw; + } } private void InvalidateRealtimeSession(string key, CultMeshRealtimeSession session) @@ -880,13 +917,10 @@ namespace GameCult.Mesh private async Task ResolveConnectedPathAsync(CultMeshSessionTarget target, CultMeshProtocolId protocol) { var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( - target.ProviderRuntimeId, + target.AuthorityRuntimeId, new[] { target.VerseId }, - target.ProviderRuntimeId)).ConfigureAwait(false); - var candidates = discovery.Candidates.SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) - .Distinct(StringComparer.Ordinal) - .Select(endpoint => new CultMeshTransportCandidate(endpoint)) - .ToArray(); + target.AuthorityRuntimeId)).ConfigureAwait(false); + var candidates = BoundCandidates(discovery, target, protocol); var routes = candidates .SelectMany(candidate => _connectors .Where(connector => connector.CanConnect(candidate)) @@ -908,7 +942,7 @@ namespace GameCult.Mesh foreach (var tier in routes.GroupBy(route => route.Connector.Priority).OrderBy(group => group.Key)) { var attempts = tier.Take(_options.MaxRacedCandidates) - .Select(route => ConnectCandidateAsync(route, protocol)) + .Select(route => ConnectCandidateAsync(target, route, protocol)) .ToList(); while (attempts.Count > 0) { @@ -925,7 +959,12 @@ namespace GameCult.Mesh } } var last = failures.LastOrDefault(); - throw Failure(last is TimeoutException ? CultMeshSessionFailureReason.Timeout : CultMeshSessionFailureReason.Transport, + var reason = last is CultMeshSessionException typed + ? typed.Failure.Reason + : last is TimeoutException + ? CultMeshSessionFailureReason.Timeout + : CultMeshSessionFailureReason.Transport; + throw Failure(reason, $"No transport path connected for '{target}' and protocol '{protocol}'.", last); } @@ -960,9 +999,9 @@ namespace GameCult.Mesh try { _discovery.Invalidate(new CultMeshDiscoveryQuery( - session.Target.ProviderRuntimeId, + session.Target.AuthorityRuntimeId, new[] { session.Target.VerseId }, - session.Target.ProviderRuntimeId)); + session.Target.AuthorityRuntimeId)); var replacement = await ResolveConnectedPathAsync(session.Target, session.Protocol).ConfigureAwait(false); session.ReplacePhysicalChannel(replacement.Client); session.Transition(new CultMeshSessionState( @@ -987,10 +1026,28 @@ namespace GameCult.Mesh } } - private static async Task ConnectCandidateAsync(SchemaRoute route, CultMeshProtocolId protocol) + private async Task ConnectCandidateAsync( + CultMeshSessionTarget target, + SchemaRoute route, + CultMeshProtocolId protocol) { var client = await route.Connector.ConnectAsync(route.Candidate, protocol).ConfigureAwait(false); - return new ConnectedPath(route.Candidate, client); + try + { + await CultMeshSessionIdentityClient.VerifyAsync( + client, + _options.RuntimeId, + target, + protocol, + route.Candidate.Generation, + _options.IdentityHandshakeTimeout).ConfigureAwait(false); + return new ConnectedPath(route.Candidate, client); + } + catch + { + client.Dispose(); + throw; + } } private static async Task AwaitForCallerAsync(Task shared, CancellationToken cancellationToken) @@ -1008,12 +1065,36 @@ namespace GameCult.Mesh private CultMeshSessionException Failure(CultMeshSessionFailureReason reason, string message, Exception? inner = null) => new CultMeshSessionException(new CultMeshSessionFailure(reason, message), inner); + private static CultMeshTransportCandidate[] BoundCandidates( + CultMeshDiscoveryState discovery, + CultMeshSessionTarget target, + CultMeshProtocolId protocol) + { + return discovery.Candidates + .SelectMany(candidate => candidate.Descriptor.AuthorityRoutes) + .Where(route => string.Equals( + route.AuthorityRuntimeId, + target.AuthorityRuntimeId, + StringComparison.Ordinal)) + .Where(route => route.Supports(protocol)) + .GroupBy(route => route.Endpoint, StringComparer.Ordinal) + .Select(group => group.OrderBy(route => route.Priority).First()) + .Select(route => new CultMeshTransportCandidate( + route.Endpoint, + priority: route.Priority, + authorityRuntimeId: route.AuthorityRuntimeId, + generation: route.Generation)) + .OrderBy(candidate => candidate.Priority) + .ThenBy(candidate => candidate.Endpoint, StringComparer.Ordinal) + .ToArray(); + } + private void Emit(CultMeshSessionTarget target, CultMeshProtocolId protocol, string state, string path) { _options.Diagnostics.Emit(new CultMeshDiagnosticEvent( Interlocked.Increment(ref _diagnosticSequence), _options.Clock.UtcNow, CultMeshReliabilityOrgan.Session, CultMeshDiagnosticKind.PathChanged, - target.SessionKey + ":" + protocol.Value, target.ProviderRuntimeId, state, + target.SessionKey + ":" + protocol.Value, target.AuthorityRuntimeId, state, sourceId: "session-manager", endpoint: path)); } diff --git a/src/GameCult.Mesh/CultMeshSnapshots.cs b/src/GameCult.Mesh/CultMeshSnapshots.cs index cff94fa4..305fed4a 100644 --- a/src/GameCult.Mesh/CultMeshSnapshots.cs +++ b/src/GameCult.Mesh/CultMeshSnapshots.cs @@ -102,7 +102,7 @@ namespace GameCult.Mesh CultNetDocumentRegistry? registry) { _sharedSession = session ?? throw new ArgumentNullException(nameof(session)); - _endpoint = session.State.Path?.Endpoint ?? session.Target.ProviderRuntimeId; + _endpoint = session.State.Path?.Endpoint ?? session.Target.AuthorityRuntimeId; _defaults = CultMesh.CloneSnapshotRequestOptions(options); _registry = registry ?? new CultNetDocumentRegistry(); _client = session.Channel; diff --git a/src/GameCult.Mesh/CultMeshTcpContentTransport.cs b/src/GameCult.Mesh/CultMeshTcpContentTransport.cs index 0c3eb19e..f47f08bd 100644 --- a/src/GameCult.Mesh/CultMeshTcpContentTransport.cs +++ b/src/GameCult.Mesh/CultMeshTcpContentTransport.cs @@ -11,6 +11,21 @@ using GameCult.Networking; namespace GameCult.Mesh { + /// Identity served by one dedicated content listener. + public sealed class CultMeshTcpContentServerOptions + { + public string VerseId { get; set; } = string.Empty; + public string AuthorityRuntimeId { get; set; } = string.Empty; + public string RouteGeneration { get; set; } = string.Empty; + + internal void Validate() + { + if (string.IsNullOrWhiteSpace(VerseId)) throw new InvalidOperationException("Content Verse identity is required."); + if (string.IsNullOrWhiteSpace(AuthorityRuntimeId)) throw new InvalidOperationException("Content authority runtime identity is required."); + if (string.IsNullOrWhiteSpace(RouteGeneration)) throw new InvalidOperationException("Content route generation is required."); + } + } + /// Creates dedicated TCP streams for immutable CultMesh content. public sealed class CultMeshTcpContentTransportConnector : ICultMeshContentTransportConnector { @@ -33,11 +48,11 @@ namespace GameCult.Mesh public async Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { if (candidate == null) throw new ArgumentNullException(nameof(candidate)); - if (endpointId == null) throw new ArgumentNullException(nameof(endpointId)); + if (target == null) throw new ArgumentNullException(nameof(target)); if (!TryParseEndpoint(candidate.Endpoint, out var host, out var port)) throw new NotSupportedException($"TCP content connector does not support '{candidate.Endpoint}'."); @@ -48,8 +63,10 @@ namespace GameCult.Mesh { await CultMeshTcpContentWire.AwaitAsync(client.ConnectAsync(host, port), timeout.Token) .ConfigureAwait(false); + await CultMeshTcpContentWire.VerifyAuthorityAsync( + client.GetStream(), target, candidate, timeout.Token).ConfigureAwait(false); Interlocked.Increment(ref _connectCount); - return new CultMeshTcpContentTransport(candidate.Endpoint, client); + return new CultMeshTcpContentTransport(candidate.Endpoint, client, target, candidate.Generation); } catch { @@ -72,7 +89,7 @@ namespace GameCult.Mesh } } - internal sealed class CultMeshTcpContentTransport : ICultMeshContentTransport + internal sealed class CultMeshTcpContentTransport : ICultMeshContentTransport, ICultMeshVerifiedTransport { private readonly TcpClient _client; private readonly NetworkStream _stream; @@ -80,12 +97,22 @@ namespace GameCult.Mesh private readonly ConcurrentDictionary _pending = new(StringComparer.Ordinal); private readonly CancellationTokenSource _shutdown = new(); private readonly Task _receiveLoop; + private readonly string _verseId; + private readonly string _authorityRuntimeId; + private readonly string _routeGeneration; private bool _disposed; - public CultMeshTcpContentTransport(string endpoint, TcpClient client) + public CultMeshTcpContentTransport( + string endpoint, + TcpClient client, + CultMeshSessionTarget target, + string routeGeneration) { Endpoint = endpoint; _client = client ?? throw new ArgumentNullException(nameof(client)); + _verseId = target?.VerseId ?? throw new ArgumentNullException(nameof(target)); + _authorityRuntimeId = target.AuthorityRuntimeId; + _routeGeneration = routeGeneration ?? string.Empty; _stream = client.GetStream(); _receiveLoop = ReceiveLoopAsync(_shutdown.Token); } @@ -93,6 +120,16 @@ namespace GameCult.Mesh public string TransportId => "tcp-content"; public string Endpoint { get; } + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + string.Equals(_verseId, verseId, StringComparison.Ordinal) && + string.Equals(_authorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.Content.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + public async Task CopyChunkToAsync( CultMeshCdnChunkRef chunk, Stream destination, @@ -212,6 +249,7 @@ namespace GameCult.Mesh private readonly TcpListener _listener; private readonly Func _resolveChunk; private readonly Func _canServeHash; + private readonly CultMeshTcpContentServerOptions _options; private readonly CancellationTokenSource _shutdown = new(); private readonly ConcurrentDictionary _clients = new(); private readonly Task _acceptLoop; @@ -221,8 +259,9 @@ namespace GameCult.Mesh public CultMeshTcpContentServer( TcpListener listener, CultCache content, + CultMeshTcpContentServerOptions options, Func? canServeHash = null) - : this(listener, CreateCacheResolver(content), canServeHash) + : this(listener, CreateCacheResolver(content), options, canServeHash) { } @@ -234,10 +273,13 @@ namespace GameCult.Mesh public CultMeshTcpContentServer( TcpListener listener, Func resolveChunk, + CultMeshTcpContentServerOptions options, Func? canServeHash = null) { _listener = listener ?? throw new ArgumentNullException(nameof(listener)); _resolveChunk = resolveChunk ?? throw new ArgumentNullException(nameof(resolveChunk)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _options.Validate(); _canServeHash = canServeHash ?? (_ => true); _listener.Start(); _acceptLoop = AcceptLoopAsync(_shutdown.Token); @@ -278,6 +320,7 @@ namespace GameCult.Mesh try { using var stream = client.GetStream(); + if (!await AcceptAuthorityAsync(stream, cancellationToken).ConfigureAwait(false)) return; while (!cancellationToken.IsCancellationRequested) { var requestBytes = await CultMeshTcpContentWire.ReadFrameAsync(stream, cancellationToken).ConfigureAwait(false); @@ -296,6 +339,32 @@ namespace GameCult.Mesh } } + private async Task AcceptAuthorityAsync(Stream stream, CancellationToken cancellationToken) + { + var requestBytes = await CultMeshTcpContentWire.ReadFrameAsync(stream, cancellationToken).ConfigureAwait(false); + var request = CultNetSchemaMessageSerialization.Deserialize(requestBytes) as CultMeshSessionOpenMessage + ?? throw new InvalidDataException("TCP content session did not begin with an authority handshake."); + var accepted = string.Equals(request.VerseId, _options.VerseId, StringComparison.Ordinal) && + string.Equals(request.AuthorityRuntimeId, _options.AuthorityRuntimeId, StringComparison.Ordinal) && + string.Equals(request.ProtocolId, CultMeshProtocols.Content.Value, StringComparison.Ordinal) && + string.Equals(request.RouteGeneration, _options.RouteGeneration, StringComparison.Ordinal); + var response = new CultMeshSessionAcceptedMessage + { + MessageId = request.MessageId, + Accepted = accepted, + VerseId = _options.VerseId, + AuthorityRuntimeId = _options.AuthorityRuntimeId, + ProtocolId = CultMeshProtocols.Content.Value, + RouteGeneration = _options.RouteGeneration, + Error = accepted ? null : "Content session target does not match this authority route." + }; + await CultMeshTcpContentWire.WriteFrameAsync( + stream, + CultNetSchemaMessageSerialization.Serialize(response), + cancellationToken).ConfigureAwait(false); + return accepted; + } + private async Task SendChunkAsync( Stream stream, CultMeshContentChunkRequestMessage request, @@ -391,6 +460,38 @@ namespace GameCult.Mesh await stream.FlushAsync(cancellationToken).ConfigureAwait(false); } + public static async Task VerifyAuthorityAsync( + Stream stream, + CultMeshSessionTarget target, + CultMeshTransportCandidate candidate, + CancellationToken cancellationToken) + { + var request = new CultMeshSessionOpenMessage + { + MessageId = Guid.NewGuid().ToString("N"), + SourceRuntimeId = "cultmesh-content-client", + VerseId = target.VerseId, + AuthorityRuntimeId = target.AuthorityRuntimeId, + ProtocolId = CultMeshProtocols.Content.Value, + RouteGeneration = candidate.Generation + }; + await WriteFrameAsync(stream, CultNetSchemaMessageSerialization.Serialize(request), cancellationToken) + .ConfigureAwait(false); + var responseBytes = await ReadFrameAsync(stream, cancellationToken).ConfigureAwait(false); + var response = CultNetSchemaMessageSerialization.Deserialize(responseBytes) as CultMeshSessionAcceptedMessage + ?? throw new InvalidDataException("TCP content authority handshake returned an unexpected schema."); + if (!string.Equals(response.MessageId, request.MessageId, StringComparison.Ordinal) || + !response.Accepted || + !string.Equals(response.VerseId, target.VerseId, StringComparison.Ordinal) || + !string.Equals(response.AuthorityRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal) || + !string.Equals(response.ProtocolId, CultMeshProtocols.Content.Value, StringComparison.Ordinal) || + !string.Equals(response.RouteGeneration, candidate.Generation, StringComparison.Ordinal)) + throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authority, + response.Error ?? "TCP content endpoint did not prove the selected authority route.", + candidate.Endpoint)); + } + public static async Task ReadFrameAsync(Stream stream, CancellationToken cancellationToken) { var header = new byte[HeaderBytes]; diff --git a/src/GameCult.Mesh/CultMeshVerse.cs b/src/GameCult.Mesh/CultMeshVerse.cs index 547a46ec..55d6deeb 100644 --- a/src/GameCult.Mesh/CultMeshVerse.cs +++ b/src/GameCult.Mesh/CultMeshVerse.cs @@ -91,6 +91,53 @@ namespace GameCult.Mesh } } + /// + /// Binds one authoritative runtime to one physical route. This relationship is + /// the routing authority; the legacy endpoint/runtime lists are projections only. + /// + public sealed class CultMeshAuthorityRoute + { + public CultMeshAuthorityRoute( + string authorityRuntimeId, + string endpoint, + IEnumerable? protocolIds = null, + int priority = 0, + string? generation = null) + { + AuthorityRuntimeId = RequireNonEmpty(authorityRuntimeId, nameof(authorityRuntimeId)); + Endpoint = RequireNonEmpty(endpoint, nameof(endpoint)); + ProtocolIds = Clean(protocolIds); + if (priority < 0) throw new ArgumentOutOfRangeException(nameof(priority)); + Priority = priority; + Generation = string.IsNullOrWhiteSpace(generation) + ? AuthorityRuntimeId + "@" + Endpoint + : generation!.Trim(); + } + + public string AuthorityRuntimeId { get; } + public string Endpoint { get; } + public IReadOnlyList ProtocolIds { get; } + public int Priority { get; } + public string Generation { get; } + + public bool Supports(CultMeshProtocolId protocol) => + protocol != null && + (ProtocolIds.Count == 0 || ProtocolIds.Contains(protocol.Value, StringComparer.Ordinal)); + + private static string[] Clean(IEnumerable? values) => + values?.Where(value => !string.IsNullOrWhiteSpace(value)) + .Select(value => value.Trim()) + .Distinct(StringComparer.Ordinal) + .OrderBy(value => value, StringComparer.Ordinal) + .ToArray() + ?? Array.Empty(); + + private static string RequireNonEmpty(string value, string paramName) => + string.IsNullOrWhiteSpace(value) + ? throw new ArgumentException("Value must be non-empty.", paramName) + : value.Trim(); + } + /// /// Describes one CultMesh Verse: a rule-bearing consensus graph. /// @@ -107,14 +154,36 @@ namespace GameCult.Mesh IEnumerable? discoveryEndpoints = null, IEnumerable? authorityRuntimeIds = null, string? parentVerseId = null, - string? description = null) + string? description = null, + IEnumerable? authorityRoutes = null) { VerseId = RequireNonEmpty(verseId, nameof(verseId)); DisplayName = RequireNonEmpty(displayName, nameof(displayName)); AuthorityModel = authorityModel; Compatibility = compatibility ?? throw new ArgumentNullException(nameof(compatibility)); - DiscoveryEndpoints = Clean(discoveryEndpoints); - AuthorityRuntimeIds = Clean(authorityRuntimeIds); + var legacyEndpoints = Clean(discoveryEndpoints); + var legacyRuntimeIds = Clean(authorityRuntimeIds); + var suppliedRoutes = authorityRoutes?.ToArray(); + if (suppliedRoutes?.Any(route => route == null) == true) + throw new ArgumentException("Authority routes cannot contain null.", nameof(authorityRoutes)); + if (suppliedRoutes != null && (legacyEndpoints.Length > 0 || legacyRuntimeIds.Length > 0)) + throw new ArgumentException( + "Supply bound authority routes or legacy endpoint/runtime lists, never both.", + nameof(authorityRoutes)); + if (suppliedRoutes == null && legacyEndpoints.Length > 0 && legacyRuntimeIds.Length > 1) + throw new InvalidOperationException( + "Legacy Verse routing with multiple authority runtimes is ambiguous. " + + "Publish explicit CultMeshAuthorityRoute bindings."); + + AuthorityRoutes = NormalizeRoutes(suppliedRoutes ?? + (legacyRuntimeIds.Length == 1 + ? legacyEndpoints.Select(endpoint => new CultMeshAuthorityRoute(legacyRuntimeIds[0], endpoint)) + : Array.Empty())); + DiscoveryEndpoints = AuthorityRoutes.Count > 0 + ? AuthorityRoutes.Select(route => route.Endpoint).Distinct(StringComparer.Ordinal).ToArray() + : legacyEndpoints; + AuthorityRuntimeIds = AuthorityRoutes.Select(route => route.AuthorityRuntimeId) + .Distinct(StringComparer.Ordinal).OrderBy(value => value, StringComparer.Ordinal).ToArray(); ParentVerseId = parentVerseId; Description = description; } @@ -143,6 +212,8 @@ namespace GameCult.Mesh /// Gets known authoritative runtime ids, when authority is cluster-shaped. /// public IReadOnlyList AuthorityRuntimeIds { get; } + /// Gets exact authority-to-route bindings used for identity-sensitive resolution. + public IReadOnlyList AuthorityRoutes { get; } /// /// Gets the parent Verse id for subscribed overlays or branches. /// @@ -177,10 +248,29 @@ namespace GameCult.Mesh private static string[] Clean(IEnumerable? values) { - return values?.Where(value => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.Ordinal).ToArray() + return values?.Where(value => !string.IsNullOrWhiteSpace(value)) + .Select(value => value.Trim()) + .Distinct(StringComparer.Ordinal) + .ToArray() ?? Array.Empty(); } + private static CultMeshAuthorityRoute[] NormalizeRoutes(IEnumerable routes) + { + return routes + .GroupBy(route => new + { + route.AuthorityRuntimeId, + route.Endpoint, + Protocols = string.Join("\u001f", route.ProtocolIds) + }) + .Select(group => group.OrderBy(route => route.Priority).First()) + .OrderBy(route => route.Priority) + .ThenBy(route => route.AuthorityRuntimeId, StringComparer.Ordinal) + .ThenBy(route => route.Endpoint, StringComparer.Ordinal) + .ToArray(); + } + private static string RequireNonEmpty(string value, string paramName) { if (string.IsNullOrWhiteSpace(value)) diff --git a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs index 801e4716..774a05f9 100644 --- a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs +++ b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs @@ -33,6 +33,14 @@ namespace GameCult.Mesh }, DiscoveryEndpoints = verse.DiscoveryEndpoints.ToArray(), AuthorityRuntimeIds = verse.AuthorityRuntimeIds.ToArray(), + AuthorityRoutes = verse.AuthorityRoutes.Select(route => new CultMeshAuthorityRouteMessage + { + AuthorityRuntimeId = route.AuthorityRuntimeId, + Endpoint = route.Endpoint, + ProtocolIds = route.ProtocolIds.ToArray(), + Priority = route.Priority, + Generation = route.Generation + }).ToArray(), ParentVerseId = verse.ParentVerseId, Description = verse.Description }; @@ -50,6 +58,12 @@ namespace GameCult.Mesh out var parsed) ? parsed : CultMeshVerseAuthorityModel.SubscribedOverlay; + var routes = message.AuthorityRoutes?.Select(route => new CultMeshAuthorityRoute( + route.AuthorityRuntimeId, + route.Endpoint, + route.ProtocolIds, + route.Priority, + route.Generation)).ToArray(); return new CultMeshVerseDescriptor( message.VerseId, message.DisplayName, @@ -60,10 +74,11 @@ namespace GameCult.Mesh message.Compatibility.CompatibleVerseIds, message.Compatibility.RequiredPluginIds, message.Compatibility.OptionalPluginIds), - message.DiscoveryEndpoints, - message.AuthorityRuntimeIds, + routes is { Length: > 0 } ? null : message.DiscoveryEndpoints, + routes is { Length: > 0 } ? null : message.AuthorityRuntimeIds, message.ParentVerseId, - message.Description); + message.Description, + routes is { Length: > 0 } ? routes : null); } /// Creates the canonical filtered Verse catalog response for any CultNet transport adapter. diff --git a/src/GameCult.Mesh/docs/public-api.md b/src/GameCult.Mesh/docs/public-api.md index 88074704..df1735b6 100644 --- a/src/GameCult.Mesh/docs/public-api.md +++ b/src/GameCult.Mesh/docs/public-api.md @@ -50,8 +50,12 @@ not the brand surface. ### Verses `CultMeshVerseDescriptor` describes a rule-bearing consensus graph. It includes -transport compatibility, rules hash, authority model, discovery endpoints, -known authority runtimes, optional parent Verse id, and plugin requirements. +transport compatibility, rules hash, authority model, exact authority-route +bindings, optional parent Verse id, and plugin requirements. Each route binds +an authority runtime to one endpoint, protocol set, priority, and generation. +The old endpoint/runtime arrays are derived projections; a legacy descriptor +with several authority runtimes is rejected because it cannot identify which +runtime owns which route. `CultMeshVerseCatalog` is the local reactive catalog for discovered Verses. It can publish discovery updates and find compatible transfer targets. diff --git a/src/GameCult.Mesh/docs/transport-planes.md b/src/GameCult.Mesh/docs/transport-planes.md index 39be9c59..bd42a071 100644 --- a/src/GameCult.Mesh/docs/transport-planes.md +++ b/src/GameCult.Mesh/docs/transport-planes.md @@ -9,7 +9,8 @@ be equally good at files, realtime state, and same-machine memory access. registered connector for each traffic class. A connector owns only its physical connection and byte movement. -**Inputs:** stable Verse/provider identity, advertised physical candidates, +**Inputs:** stable Verse/authority-runtime identity, exact Odin-bound physical +routes with protocol and generation, connector support and priority, caller cancellation, and locality evidence for body negotiation. @@ -63,13 +64,22 @@ operation. transport diagnostics. None of these grant provider authority or become content identity. +Before a schema, content, or realtime session becomes online, the transport +must prove that the connected peer matches the selected Verse, authority +runtime, protocol, and route generation. TCP schema and content lanes use the +portable CultNet session-open handshake. QUIC uses the exact Odin-bound route +plus its TLS endpoint/certificate proof. A connector that returns bytes without +authority proof is rejected; racing every endpoint in a Verse descriptor is +not route selection. + **Forbidden writers:** applications and renderers do not choose unadvertised endpoints, implement reconnect loops, or bypass verification. Schema messages must not carry bulk bodies. RUDP/LiteNetLib cannot become an implicit default for any traffic class. QUIC does not own schemas or immutable files merely because it can transport bytes. -**Shared paths:** discovery and identity resolution remain common. Each traffic +**Shared paths:** exact route resolution and peer verification remain common. +Each traffic class has its own connector set, connection cache, priority tiers, and failure state. All content connectors feed the same transfer and promotion owner. diff --git a/src/GameCult.Mesh/docs/verses.md b/src/GameCult.Mesh/docs/verses.md index 656dcfaf..fbd9a522 100644 --- a/src/GameCult.Mesh/docs/verses.md +++ b/src/GameCult.Mesh/docs/verses.md @@ -21,8 +21,10 @@ The same transport can carry many Verses: subscribed overlay - `Compatibility`: transport version, rules hash, compatible Verse ids, and plugin requirements -- `DiscoveryEndpoints`: endpoints where nodes for the Verse can be found -- `AuthorityRuntimeIds`: known authoritative runtimes when cluster-shaped +- `AuthorityRoutes`: exact authority-runtime, endpoint, protocol, priority, and + generation bindings. This is the routing authority. +- `DiscoveryEndpoints`: read-only endpoint projection for legacy inspection +- `AuthorityRuntimeIds`: read-only authority projection for legacy inspection - `ParentVerseId`: source Verse for overlays or branches ## Compatibility @@ -57,6 +59,13 @@ static config, cloud discovery, peer gossip, or mod portals. It does not care where the descriptor came from. That keeps discovery pluggable while the Verse model stays stable. +An endpoint and an authority id are not two independent facts. A descriptor +with explicit routes binds them. The legacy parallel arrays are accepted only +when exactly one authority runtime exists; a multi-authority legacy descriptor +is ambiguous and fails closed. Session establishment then requires the selected +physical route to prove the exact Verse, authority runtime, protocol, and route +generation before the session becomes online. + Peer exchange is the torrent-shaped layer under Verse discovery. A Verse catalog says which graph exists; a peer catalog says which nodes can currently serve or observe parts of that graph. Peer cards can advertise discovery, diff --git a/src/GameCult.Networking/CultNetOperationServer.cs b/src/GameCult.Networking/CultNetOperationServer.cs index 6261328e..27b52c55 100644 --- a/src/GameCult.Networking/CultNetOperationServer.cs +++ b/src/GameCult.Networking/CultNetOperationServer.cs @@ -164,6 +164,16 @@ namespace GameCult.Networking if (_disposed) return; if (request == null) throw new ArgumentNullException(nameof(request)); if (peer == null) throw new ArgumentNullException(nameof(peer)); + if (_sourceRuntimeId != null && + !string.Equals(request.TargetRuntimeId, _sourceRuntimeId, StringComparison.Ordinal)) + { + peer.SendCultNet(Failure( + request, + "denied", + "target-runtime-mismatch", + $"Operation targeted runtime '{request.TargetRuntimeId ?? "(missing)"}', not '{_sourceRuntimeId}'.")); + return; + } Route route; try diff --git a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs index 386bc99b..e712beef 100644 --- a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs +++ b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs @@ -72,6 +72,8 @@ namespace GameCult.Networking CultNetSchemaVersions.SimulationConsensusCandidate => Deserialize(payload), CultNetSchemaVersions.VerseCatalogRequest => Deserialize(payload), CultNetSchemaVersions.VerseCatalogResponse => Deserialize(payload), + CultNetSchemaVersions.SessionOpen => Deserialize(payload), + CultNetSchemaVersions.SessionAccepted => Deserialize(payload), CultNetSchemaVersions.PeerExchangeRequest => Deserialize(payload), CultNetSchemaVersions.PeerExchangeResponse => Deserialize(payload), CultNetSchemaVersions.ContentChunkRequest => Deserialize(payload), diff --git a/src/GameCult.Networking/CultNetSchemaMessages.cs b/src/GameCult.Networking/CultNetSchemaMessages.cs index 764238ff..2117693d 100644 --- a/src/GameCult.Networking/CultNetSchemaMessages.cs +++ b/src/GameCult.Networking/CultNetSchemaMessages.cs @@ -135,6 +135,10 @@ namespace GameCult.Networking /// CultMesh Verse catalog response contract identifier. /// public const string VerseCatalogResponse = "cultmesh.verse_catalog_response.v0"; + /// CultMesh session identity request contract identifier. + public const string SessionOpen = "cultmesh.session_open.v1"; + /// CultMesh verified session identity response contract identifier. + public const string SessionAccepted = "cultmesh.session_accepted.v1"; /// /// CultMesh peer exchange request contract identifier. /// @@ -1179,6 +1183,33 @@ namespace GameCult.Networking } } + /// Requests an identity-bound CultMesh session before application traffic begins. + [MessagePackObject] + public class CultMeshSessionOpenMessage : ICultNetSchemaMessage + { + [Key("schemaVersion")] public string SchemaVersion { get; set; } = CultNetSchemaVersions.SessionOpen; + [Key("messageId")] public string MessageId { get; set; } = string.Empty; + [Key("sourceRuntimeId")] public string SourceRuntimeId { get; set; } = string.Empty; + [Key("verseId")] public string VerseId { get; set; } = string.Empty; + [Key("authorityRuntimeId")] public string AuthorityRuntimeId { get; set; } = string.Empty; + [Key("protocolId")] public string ProtocolId { get; set; } = string.Empty; + [Key("routeGeneration")] public string RouteGeneration { get; set; } = string.Empty; + } + + /// Accepts or rejects a CultMesh session with the server's actual identity. + [MessagePackObject] + public class CultMeshSessionAcceptedMessage : ICultNetSchemaMessage + { + [Key("schemaVersion")] public string SchemaVersion { get; set; } = CultNetSchemaVersions.SessionAccepted; + [Key("messageId")] public string MessageId { get; set; } = string.Empty; + [Key("accepted")] public bool Accepted { get; set; } + [Key("verseId")] public string VerseId { get; set; } = string.Empty; + [Key("authorityRuntimeId")] public string AuthorityRuntimeId { get; set; } = string.Empty; + [Key("protocolId")] public string ProtocolId { get; set; } = string.Empty; + [Key("routeGeneration")] public string RouteGeneration { get; set; } = string.Empty; + [Key("error")] public string? Error { get; set; } + } + /// /// Requests known CultMesh Verses from a peer. /// @@ -1254,6 +1285,11 @@ namespace GameCult.Networking /// [Key("authorityRuntimeIds")] public string[] AuthorityRuntimeIds { get; set; } = Array.Empty(); /// + /// Gets or sets exact authoritative-runtime route bindings. This is the + /// routing authority; the parallel legacy arrays are projections only. + /// + [Key("authorityRoutes")] public CultMeshAuthorityRouteMessage[] AuthorityRoutes { get; set; } = Array.Empty(); + /// /// Gets or sets the parent Verse id for overlays or branches. /// [Key("parentVerseId")] public string? ParentVerseId { get; set; } @@ -1263,6 +1299,17 @@ namespace GameCult.Networking [Key("description")] public string? Description { get; set; } } + /// Wire representation of one authority-bound CultMesh route. + [MessagePackObject] + public class CultMeshAuthorityRouteMessage + { + [Key("authorityRuntimeId")] public string AuthorityRuntimeId { get; set; } = string.Empty; + [Key("endpoint")] public string Endpoint { get; set; } = string.Empty; + [Key("protocolIds")] public string[] ProtocolIds { get; set; } = Array.Empty(); + [Key("priority")] public int Priority { get; set; } + [Key("generation")] public string Generation { get; set; } = string.Empty; + } + /// /// Wire representation of CultMesh Verse compatibility. /// diff --git a/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs b/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs index afa74426..7eb99edc 100644 --- a/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs +++ b/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs @@ -22,7 +22,7 @@ public sealed class CultMeshNativeQuicRealtimeTransportTests var connector = new CultMeshNativeQuicRealtimeTransportConnector(); using var client = await connector.ConnectAsync( new CultMeshTransportCandidate(endpoint!), - CultMeshEndpointId.Parse("aetheria.local")); + new CultMeshSessionTarget("aetheria", "aetheria.local")); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var frame = await client.ReceiveAsync(timeout.Token); @@ -49,7 +49,7 @@ public sealed class CultMeshNativeQuicRealtimeTransportTests using var client = await connector.ConnectAsync( new CultMeshTransportCandidate(endpoint), - CultMeshEndpointId.Parse("aetheria.local")); + new CultMeshSessionTarget("aetheria", "aetheria.local")); var expected = new CultMeshRealtimeFrame { ChannelId = "aetheria.entities", @@ -92,7 +92,7 @@ public sealed class CultMeshNativeQuicRealtimeTransportTests var connect = () => connector.ConnectAsync( new CultMeshTransportCandidate(endpoint), - CultMeshEndpointId.Parse("aetheria.local")); + new CultMeshSessionTarget("aetheria", "aetheria.local")); await connect.Should().ThrowAsync() .WithMessage("*certificate*pin*"); @@ -114,10 +114,10 @@ public sealed class CultMeshNativeQuicRealtimeTransportTests var connector = new CultMeshNativeQuicRealtimeTransportConnector(); var departedClient = await connector.ConnectAsync( new CultMeshTransportCandidate(endpoint), - CultMeshEndpointId.Parse("aetheria.local")); + new CultMeshSessionTarget("aetheria", "aetheria.local")); using var healthyClient = await connector.ConnectAsync( new CultMeshTransportCandidate(endpoint), - CultMeshEndpointId.Parse("aetheria.local")); + new CultMeshSessionTarget("aetheria", "aetheria.local")); await WaitUntilAsync(() => server.ConnectionCount == 2); await server.BroadcastAsync(Frame(sequence: 1)); using var receiveTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); diff --git a/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs b/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs index 8d90f147..68afda10 100644 --- a/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs +++ b/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs @@ -40,7 +40,7 @@ public sealed class CultMeshQuicRealtimeTransportTests }); using var client = await connector.ConnectAsync( new CultMeshTransportCandidate($"cultmesh-state+quic://127.0.0.1:{listener.LocalEndPoint.Port}"), - CultMeshEndpointId.Parse("service:aetheria.daemon")); + new CultMeshSessionTarget("aetheria", "service:aetheria.daemon")); await using var serverConnection = await accept; client.TransportId.Should().Be("msquic-realtime"); @@ -62,7 +62,7 @@ public sealed class CultMeshQuicRealtimeTransportTests using var client = await connector.ConnectAsync( new CultMeshTransportCandidate(endpoint), - CultMeshEndpointId.Parse("service:aetheria.daemon")); + new CultMeshSessionTarget("aetheria", "service:aetheria.daemon")); client.TransportId.Should().Be("msquic-realtime"); await WaitUntilAsync(() => server.ConnectionCount == 1); @@ -78,8 +78,8 @@ public sealed class CultMeshQuicRealtimeTransportTests ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ServerCertificate = certificate }); - var endpointId = CultMeshEndpointId.Parse("service:aetheria.daemon"); - var target = new CultMeshSessionTarget("aetheria", endpointId.Value); + var runtimeId = CultMeshRuntimeId.Parse("service:aetheria.daemon"); + var target = new CultMeshSessionTarget("aetheria", runtimeId.Value); using var sessions = CreateSessions(server, out var validatedIdentity); CultMeshRealtimeSession session; try @@ -105,7 +105,7 @@ public sealed class CultMeshQuicRealtimeTransportTests new[] { firstAtServer.Sequence, secondAtServer.Sequence }.Should().Equal(1, 2); new[] { firstAtClient.Sequence, secondAtClient.Sequence }.Should().Equal(3, 4); firstAtClient.Payload.ToArray().Should().Equal(3, 4, 5); - validatedIdentity.Value.Should().Be(endpointId.Value); + validatedIdentity.Value.Should().Be(runtimeId.Value); } [Test] @@ -118,8 +118,8 @@ public sealed class CultMeshQuicRealtimeTransportTests ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ServerCertificate = certificate }); - var endpointId = CultMeshEndpointId.Parse("service:aetheria.daemon"); - var target = new CultMeshSessionTarget("aetheria", endpointId.Value); + var runtimeId = CultMeshRuntimeId.Parse("service:aetheria.daemon"); + var target = new CultMeshSessionTarget("aetheria", runtimeId.Value); using var sessions = CreateSessions(server, out _); var session = await sessions.ConnectRealtimeAsync(target); await WaitUntilAsync(() => server.ConnectionCount == 1); @@ -148,8 +148,8 @@ public sealed class CultMeshQuicRealtimeTransportTests ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), ServerCertificate = certificate }); - var endpointId = CultMeshEndpointId.Parse("service:aetheria.daemon"); - var target = new CultMeshSessionTarget("aetheria", endpointId.Value); + var runtimeId = CultMeshRuntimeId.Parse("service:aetheria.daemon"); + var target = new CultMeshSessionTarget("aetheria", runtimeId.Value); using var sessions = CreateSessions(server, out _); var session = await sessions.ConnectRealtimeAsync(target); diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs index bdf9928f..81ee15cc 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs @@ -53,6 +53,13 @@ public sealed class CultMeshBodySessionTests RuntimeId = "cultmesh-body-rudp-test", Socket = socket, MaxFragmentBytes = 1024, MaxPendingReliablePackets = 8192 }); + var endpoint = $"rudp://127.0.0.1:{server.LocalEndPoint.Port}"; + using var identityServer = new CultMeshSessionIdentityServer( + server, + "aetheria.daemon", + new[] { "aetheria" }, + new[] { CultMeshProtocols.Bodies.Value }, + new[] { "aetheria.daemon@" + endpoint }); using var bodyServer = new CultMeshBodyServer(server, store); using var pumpCancellation = new CancellationTokenSource(); var pump = Task.Run(async () => @@ -64,7 +71,6 @@ public sealed class CultMeshBodySessionTests if (progress.TransportItemsConsumed == 0) await Task.Delay(1, pumpCancellation.Token); } }); - var endpoint = $"rudp://127.0.0.1:{server.LocalEndPoint.Port}"; using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource(endpoint) }); using var sessions = new CultMeshSessionManager(discovery, new ICultMeshTransportConnector[] { new CultMeshSchemaTransportConnector() }); @@ -179,7 +185,7 @@ public sealed class CultMeshBodySessionTests ((Func)_handlers[typeof(T)])(message, peer); } - private sealed class LoopbackClient : ICultNetSchemaClient + private sealed class LoopbackClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { private readonly LoopbackServer _server; private readonly Dictionary> _handlers = new(); @@ -208,6 +214,8 @@ public sealed class CultMeshBodySessionTests foreach (var value in values.ToArray()) ((Action)value)(message); } public void Dispose() { } + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria.daemon"; } private sealed class LoopbackPeer : ICultNetSchemaServerPeer diff --git a/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs b/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs index d88b5738..e603415e 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs @@ -303,7 +303,7 @@ public sealed class CultMeshClientTests result.MessageId.Should().Be("pilot-command-1"); result.Status.Should().Be("accepted"); - result.SourceRuntimeId.Should().Be("provider-daemon"); + result.SourceRuntimeId.Should().Be("aetheria-daemon"); result.Value.ReceiptId.Should().Be("receipt:pilot-command-1"); result.Value.Count.Should().Be(2); connector.Clients.Should().ContainSingle(); @@ -370,6 +370,31 @@ public sealed class CultMeshClientTests connector.Clients[0].Operations.Should().ContainSingle(); } + [Test] + public async Task InvokeOperation_RejectsReceiptFromAnotherRuntime() + { + var connector = new DocumentConnector { ResponseSourceRuntimeId = "intruder-daemon" }; + using var mesh = new CultMeshClient(new CultMeshClientOptions + { + RendezvousEndpoints = new[] { "rudp://odin:3076" }, + Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Connectors = new[] { connector } + }); + + Func invoke = async () => await mesh.InvokeAsync( + Target, + "tests.counter", + "tests.counter.increment", + "tests.counter.increment_request.v1", + "tests.counter.increment_receipt.v1", + new ClientOperationRequest { Amount = 2 }, + sourceRuntimeId: "pilot-one", + idempotencyKey: "pilot-command-wrong-source"); + + var failure = await invoke.Should().ThrowAsync(); + failure.Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + [Test] public async Task Dispose_TerminatesDocumentWaitingForInitialSnapshot() { @@ -438,13 +463,15 @@ public sealed class CultMeshClientTests } } - private sealed class ConnectedClient : ICultNetSchemaClient + private sealed class ConnectedClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { public bool Connected => true; public void Connect(string host, int port) { } public void SendCultNet(T message) where T : ICultNetSchemaMessage { } public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { } public void Dispose() { } + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; } private sealed class DocumentConnector : ICultMeshTransportConnector @@ -454,6 +481,7 @@ public sealed class CultMeshClientTests public bool SuppressFirstSubscriptionSnapshot { get; set; } public bool RejectOperations { get; set; } public bool FailFirstOperationBeforeResponse { get; set; } + public string ResponseSourceRuntimeId { get; set; } = "aetheria-daemon"; public string ConnectorId => "document-test"; public int Priority => 0; public int SubscribeCount => Clients.Sum(client => client.SubscribeCount); @@ -469,13 +497,14 @@ public sealed class CultMeshClientTests ? int.MaxValue : SuppressFirstSubscriptionSnapshot && Clients.Count == 0 ? 1 : 0, rejectOperations: RejectOperations, - failOperationBeforeResponse: FailFirstOperationBeforeResponse && Clients.Count == 0); + failOperationBeforeResponse: FailFirstOperationBeforeResponse && Clients.Count == 0, + responseSourceRuntimeId: ResponseSourceRuntimeId); Clients.Add(client); return Task.FromResult(client); } } - private sealed class DocumentClient : ICultNetSchemaClient, ICultNetSchemaClientHealth + private sealed class DocumentClient : ICultNetSchemaClient, ICultNetSchemaClientHealth, ICultMeshVerifiedSchemaClient { private readonly List> _snapshots = new(); private readonly List> _operationResponses = new(); @@ -484,17 +513,20 @@ public sealed class CultMeshClientTests private readonly TaskCompletionSource _failure = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly bool _rejectOperations; private bool _failOperationBeforeResponse; + private readonly string _responseSourceRuntimeId; private int _snapshotsToSuppress; public DocumentClient( string text, int snapshotsToSuppress = 0, bool rejectOperations = false, - bool failOperationBeforeResponse = false) + bool failOperationBeforeResponse = false, + string responseSourceRuntimeId = "aetheria-daemon") { _text = text; _snapshotsToSuppress = snapshotsToSuppress; _rejectOperations = rejectOperations; _failOperationBeforeResponse = failOperationBeforeResponse; + _responseSourceRuntimeId = responseSourceRuntimeId; var cache = CultMesh.CreateCultCacheDocumentRegistry(typeof(ClientTestDocument)); _documents = CultMesh.CreateCultNetDocumentRegistry(new[] { typeof(ClientTestDocument) }, cache); } @@ -514,6 +546,7 @@ public sealed class CultMeshClientTests } if (message is CultNetOperationRequestMessage operation) { + operation.TargetRuntimeId.Should().Be("aetheria-daemon"); Operations.Add(operation); if (_failOperationBeforeResponse) { @@ -539,7 +572,7 @@ public sealed class CultMeshClientTests failure, CultNetSchemaMessageSerialization.Options)), Diagnostics = new[] { failure.Message }, - SourceRuntimeId = "provider-daemon" + SourceRuntimeId = _responseSourceRuntimeId }; foreach (var handler in _operationResponses.ToArray()) handler(rejected); return; @@ -562,7 +595,7 @@ public sealed class CultMeshClientTests Payload = Convert.ToBase64String(MessagePackSerializer.Serialize( receipt, CultNetSchemaMessageSerialization.Options)), - SourceRuntimeId = "provider-daemon" + SourceRuntimeId = _responseSourceRuntimeId }; foreach (var handler in _operationResponses.ToArray()) handler(operationResponse); return; @@ -596,6 +629,8 @@ public sealed class CultMeshClientTests } public void Dispose() { } public void Fail(Exception error) => _failure.TrySetResult(error); + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; } private static async Task WaitUntilAsync(Func condition) diff --git a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs index d63254d1..fa11072c 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs @@ -17,6 +17,7 @@ namespace GameCult.Mesh.Tests; [TestFixture] public sealed class CultMeshContentSessionTests { + private const string RouteGeneration = "content-test-route-1"; private string _directory = null!; [SetUp] @@ -41,7 +42,13 @@ public sealed class CultMeshContentSessionTests StringComparer.Ordinal); using var contentServer = new CultMeshTcpContentServer( new TcpListener(IPAddress.Loopback, 0), - hash => providerChunks.TryGetValue(hash, out var chunk) ? chunk : null); + hash => providerChunks.TryGetValue(hash, out var chunk) ? chunk : null, + new CultMeshTcpContentServerOptions + { + VerseId = "aetheria", + AuthorityRuntimeId = "aetheria.daemon", + RouteGeneration = RouteGeneration + }); var endpoint = $"{CultMeshTcpContentTransportConnector.Scheme}://127.0.0.1:{contentServer.LocalEndPoint.Port}"; var connector = new CultMeshTcpContentTransportConnector(); using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource(endpoint) }); @@ -73,6 +80,34 @@ public sealed class CultMeshContentSessionTests "all chunk requests borrow one identity-first content session"); } + [Test] + public async Task TcpContentSessionRejectsEndpointServingAnotherAuthority() + { + using var content = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( + typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk))); + using var contentServer = new CultMeshTcpContentServer( + new TcpListener(IPAddress.Loopback, 0), + content, + new CultMeshTcpContentServerOptions + { + VerseId = "aetheria", + AuthorityRuntimeId = "intruder.daemon", + RouteGeneration = RouteGeneration + }); + var endpoint = $"{CultMeshTcpContentTransportConnector.Scheme}://127.0.0.1:{contentServer.LocalEndPoint.Port}"; + using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource(endpoint) }); + using var sessions = new CultMeshSessionManager( + discovery, + Array.Empty(), + new ICultMeshContentTransportConnector[] { new CultMeshTcpContentTransportConnector() }); + + Func connect = async () => await sessions.ConnectContentAsync( + new CultMeshSessionTarget("aetheria", "aetheria.daemon")); + + var error = await connect.Should().ThrowAsync(); + error.Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + [Test] public async Task RudpContentSessionCarriesConcurrentChunkWindowWithoutPerFragmentSchedulerDelay() { @@ -91,6 +126,12 @@ public sealed class CultMeshContentSessionTests MaxPendingReliablePackets = 8192 }); using var contentServer = new CultMeshLegacyRudpContentServer(wireServer, providerCache); + using var identityServer = new CultMeshSessionIdentityServer( + wireServer, + "aetheria.daemon", + new[] { "aetheria" }, + new[] { CultMeshProtocols.Content.Value }, + new[] { RouteGeneration }); using var pumpCancellation = new CancellationTokenSource(); var pump = Task.Run(async () => { @@ -196,8 +237,14 @@ public sealed class CultMeshContentSessionTests "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v1", "rules"), - new[] { _endpoint }, - new[] { "aetheria.daemon" }), + authorityRoutes: new[] + { + new CultMeshAuthorityRoute( + "aetheria.daemon", + _endpoint, + new[] { CultMeshProtocols.Content.Value }, + generation: RouteGeneration) + }), SourceId, now, now.AddMinutes(1), @@ -241,7 +288,7 @@ public sealed class CultMeshContentSessionTests ((Func)_handlers[typeof(TMessage)])(message, peer); } - private sealed class LoopbackSchemaClient : ICultNetSchemaClient + private sealed class LoopbackSchemaClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { private readonly LoopbackSchemaServer _server; private readonly Dictionary> _handlers = new(); @@ -270,6 +317,8 @@ public sealed class CultMeshContentSessionTests handlers.Add(callback); } public void Dispose() { } + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria.daemon"; public void Emit(T message) where T : ICultNetSchemaMessage { if (!_handlers.TryGetValue(typeof(T), out var handlers)) return; diff --git a/tests/GameCult.Mesh.Tests/CultMeshDiscoveryServiceTests.cs b/tests/GameCult.Mesh.Tests/CultMeshDiscoveryServiceTests.cs index 7d2938e4..2313a5bd 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshDiscoveryServiceTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshDiscoveryServiceTests.cs @@ -295,7 +295,7 @@ public sealed class CultMeshDiscoveryServiceTests CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), new[] { endpoint }, - new[] { "odin:aetheria", "odin:games", "aetheria" }), + new[] { "aetheria" }), sourceId, clock.UtcNow, clock.UtcNow + ttl, diff --git a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs index fe9ec370..46e0672f 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs @@ -79,6 +79,81 @@ public sealed class CultMeshSessionManagerTests fastClient.DisposeCount.Should().Be(0); } + [Test] + public async Task Connect_NeverAttemptsRouteBoundToAnotherAuthority() + { + var clock = new ManualSessionClock(); + var descriptor = new CultMeshVerseDescriptor( + "aetheria", + "Aetheria", + CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + authorityRoutes: new[] + { + new CultMeshAuthorityRoute("decoy-daemon", "rudp://decoy:3076"), + new CultMeshAuthorityRoute("aetheria-daemon", "rudp://authority:3076") + }); + using var discovery = new CultMeshDiscoveryService( + new[] { new StaticRouteSource(clock, descriptor) }, + new CultMeshDiscoveryServiceOptions { Clock = clock }); + var attempts = new List(); + var connector = new FakeConnector((candidate, _) => + { + attempts.Add(candidate.Endpoint); + return Task.FromResult(new FakeSchemaClient()); + }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + + var session = await manager.ConnectAsync(Target, CultMeshProtocols.Documents); + + session.State.Path!.Endpoint.Should().Be("rudp://authority:3076"); + attempts.Should().Equal("rudp://authority:3076"); + } + + [Test] + public void Connect_RejectsRouteWhoseNativeIdentityDoesNotMatchTarget() + { + var clock = new ManualSessionClock(); + using var discovery = Discovery(clock, () => new[] { "rudp://decoy:3076" }); + var connector = new FakeConnector((_, _) => + Task.FromResult(new NativeMismatchClient())); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + + var error = Assert.ThrowsAsync(() => + manager.ConnectAsync(Target, CultMeshProtocols.Documents)); + + error!.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + + [Test] + public void Connect_RejectsPortableHandshakeFromWrongRuntime() + { + var clock = new ManualSessionClock(); + using var discovery = Discovery(clock, () => new[] { "rudp://decoy:3076" }); + var connector = new FakeConnector((_, _) => + Task.FromResult(new HandshakeSchemaClient("decoy-daemon"))); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + + var error = Assert.ThrowsAsync(() => + manager.ConnectAsync(Target, CultMeshProtocols.Documents)); + + error!.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + + [Test] + public void Descriptor_RejectsAmbiguousLegacyMultiAuthorityRoutes() + { + var action = () => new CultMeshVerseDescriptor( + "aetheria", + "Aetheria", + CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + discoveryEndpoints: new[] { "rudp://shared:3076" }, + authorityRuntimeIds: new[] { "authority-a", "authority-b" }); + + action.Should().Throw().WithMessage("*ambiguous*"); + } + [Test] public async Task Connect_CallerCancellationDoesNotCancelSharedAttempt() { @@ -308,6 +383,30 @@ public sealed class CultMeshSessionManagerTests CultMeshDiscoveryTrust.Signed); } + private sealed class StaticRouteSource : ICultMeshLookupSource + { + private readonly ManualSessionClock _clock; + private readonly CultMeshVerseDescriptor _descriptor; + public StaticRouteSource(ManualSessionClock clock, CultMeshVerseDescriptor descriptor) + { + _clock = clock; + _descriptor = descriptor; + } + public string SourceId => "odin"; + public Task> LookupAsync( + CultMeshDiscoveryQuery query, + CancellationToken cancellationToken = default) => + Task.FromResult>(new[] + { + new CultMeshDiscoveryObservation( + _descriptor, + SourceId, + _clock.UtcNow, + _clock.UtcNow.AddMinutes(1), + CultMeshDiscoveryTrust.Signed) + }); + } + private sealed class FakeConnector : ICultMeshTransportConnector { private readonly Func> _connect; @@ -324,7 +423,7 @@ public sealed class CultMeshSessionManagerTests } } - private sealed class FakeSchemaClient : ICultNetSchemaClient, ICultNetSchemaClientHealth + private sealed class FakeSchemaClient : ICultNetSchemaClient, ICultNetSchemaClientHealth, ICultMeshVerifiedSchemaClient { private readonly TaskCompletionSource _failure = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly List> _handlers = new(); @@ -340,6 +439,8 @@ public sealed class CultMeshSessionManagerTests _handlers.Add(message => callback((T)(object)message)); } public void Dispose() => Interlocked.Increment(ref _disposeCount); + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; public void Fail(Exception error) => _failure.TrySetResult(error); public void Emit(CultNetErrorMessage message) { foreach (var handler in _handlers.ToArray()) handler(message); } } @@ -360,7 +461,46 @@ public sealed class CultMeshSessionManagerTests public void Dispose() { } } - private sealed class RespondingSchemaClient : ICultNetSchemaClient + private sealed class NativeMismatchClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient + { + public bool Connected => true; + public void Connect(string host, int port) { } + public void SendCultNet(T message) where T : ICultNetSchemaMessage { } + public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { } + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => false; + public void Dispose() { } + } + + private sealed class HandshakeSchemaClient : ICultNetSchemaClient + { + private readonly string _actualRuntimeId; + private readonly List> _handlers = new(); + public HandshakeSchemaClient(string actualRuntimeId) => _actualRuntimeId = actualRuntimeId; + public bool Connected => true; + public void Connect(string host, int port) { } + public void SendCultNet(T message) where T : ICultNetSchemaMessage + { + if (message is not CultMeshSessionOpenMessage request) return; + var response = new CultMeshSessionAcceptedMessage + { + MessageId = request.MessageId, + Accepted = true, + VerseId = request.VerseId, + AuthorityRuntimeId = _actualRuntimeId, + ProtocolId = request.ProtocolId, + RouteGeneration = request.RouteGeneration + }; + foreach (var handler in _handlers.ToArray()) handler(response); + } + public void OnCultNet(Action callback) where T : ICultNetSchemaMessage + { + if (typeof(T) == typeof(CultMeshSessionAcceptedMessage)) + _handlers.Add(message => callback((T)(object)message)); + } + public void Dispose() { } + } + + private sealed class RespondingSchemaClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { private readonly List> _handlers = new(); private int _disposeCount; @@ -385,9 +525,11 @@ public sealed class CultMeshSessionManagerTests _handlers.Add(message => callback((T)(object)message)); } public void Dispose() => Interlocked.Increment(ref _disposeCount); + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; } - private sealed class PeerExchangeSchemaClient : ICultNetSchemaClient + private sealed class PeerExchangeSchemaClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { private readonly List> _handlers = new(); public bool Connected => true; @@ -413,9 +555,11 @@ public sealed class CultMeshSessionManagerTests _handlers.Add(message => callback((T)(object)message)); } public void Dispose() { } + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; } - private sealed class LeaseSchemaClient : ICultNetSchemaClient + private sealed class LeaseSchemaClient : ICultNetSchemaClient, ICultMeshVerifiedSchemaClient { private readonly List> _handlers = new(); private int _disposeCount; @@ -430,6 +574,8 @@ public sealed class CultMeshSessionManagerTests } public void Emit(CultNetErrorMessage message) { foreach (var handler in _handlers.ToArray()) handler(message); } public void Dispose() => Interlocked.Increment(ref _disposeCount); + public bool IsVerifiedFor(string verseId, string authorityRuntimeId, string protocolId, string routeGeneration) => + verseId == "aetheria" && authorityRuntimeId == "aetheria-daemon"; } private sealed class ManualSessionClock : ICultMeshClock diff --git a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs index 5fe32fed..35b4a71c 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; @@ -15,10 +16,17 @@ namespace GameCult.Mesh.Tests; [TestFixture] public sealed class CultMeshTransportModularityTests { + private const string RouteGeneration = "transport-modularity-route-1"; [Test] public async Task SchemaSessionPrefersTcpTierWithoutTouchingLegacyDatagrams() { using var server = new TcpFramedCultNetSchemaServer(new TcpListener(IPAddress.Loopback, 0)); + using var identity = new CultMeshSessionIdentityServer( + server, + "aetheria.daemon", + new[] { "aetheria" }, + new[] { CultMeshProtocols.Documents.Value }, + new[] { RouteGeneration }); server.OnCultNet((message, peer) => { peer.SendCultNet(new CultNetErrorMessage { Error = "tcp:" + message.RuntimeId }); @@ -61,7 +69,15 @@ public sealed class CultMeshTransportModularityTests using var content = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk))); await CultMeshCdn.PublishAsync(content, artifact); - using var server = new CultMeshTcpContentServer(new TcpListener(IPAddress.Loopback, 0), content); + using var server = new CultMeshTcpContentServer( + new TcpListener(IPAddress.Loopback, 0), + content, + new CultMeshTcpContentServerOptions + { + VerseId = "aetheria", + AuthorityRuntimeId = "aetheria.daemon", + RouteGeneration = RouteGeneration + }); var tcpEndpoint = $"{CultMeshTcpContentTransportConnector.Scheme}://127.0.0.1:{server.LocalEndPoint.Port}"; var legacy = new RejectingLegacyContentConnector(); using var discovery = new CultMeshDiscoveryService(new[] @@ -156,6 +172,26 @@ public sealed class CultMeshTransportModularityTests .WithMessage("*No realtime state connectors are configured*"); } + [Test] + public async Task RealtimeSessionRejectsConnectedTransportWithoutAuthorityProof() + { + using var discovery = new CultMeshDiscoveryService(new[] + { + new RouteSource("cultmesh-state+quic://127.0.0.1:3076") + }); + using var sessions = new CultMeshSessionManager( + discovery, + Array.Empty(), + Array.Empty(), + new ICultMeshRealtimeTransportConnector[] { new UnverifiedRealtimeConnector() }); + + Func connect = async () => await sessions.ConnectRealtimeAsync( + new CultMeshSessionTarget("aetheria", "aetheria.daemon")); + + var error = await connect.Should().ThrowAsync(); + error.Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + private sealed class RouteSource : ICultMeshLookupSource { private readonly string[] _endpoints; @@ -176,8 +212,11 @@ public sealed class CultMeshTransportModularityTests "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v1", "rules"), - _endpoints, - new[] { "aetheria.daemon" }), + authorityRoutes: _endpoints.Select(endpoint => new CultMeshAuthorityRoute( + "aetheria.daemon", + endpoint, + protocolIds: null, + generation: RouteGeneration))), SourceId, now, now.AddMinutes(1), @@ -214,7 +253,7 @@ public sealed class CultMeshTransportModularityTests candidate.Endpoint.StartsWith("rudp://", StringComparison.OrdinalIgnoreCase); public Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { Interlocked.Increment(ref _connectCount); @@ -232,24 +271,70 @@ public sealed class CultMeshTransportModularityTests candidate.Endpoint.StartsWith("cultmesh-state+quic://", StringComparison.OrdinalIgnoreCase); public Task ConnectAsync( CultMeshTransportCandidate candidate, - CultMeshEndpointId endpointId, + CultMeshSessionTarget target, CancellationToken cancellationToken = default) { Interlocked.Increment(ref _connectCount); - return Task.FromResult(new LoopbackRealtimeTransport(candidate.Endpoint)); + return Task.FromResult( + new LoopbackRealtimeTransport(candidate.Endpoint, target, candidate.Generation)); } } - private sealed class LoopbackRealtimeTransport : ICultMeshRealtimeTransport + private sealed class UnverifiedRealtimeConnector : ICultMeshRealtimeTransportConnector + { + public string ConnectorId => "unverified-realtime"; + public int Priority => 0; + public bool CanConnect(CultMeshTransportCandidate candidate) => true; + public Task ConnectAsync( + CultMeshTransportCandidate candidate, + CultMeshSessionTarget target, + CancellationToken cancellationToken = default) => + Task.FromResult(new UnverifiedRealtimeTransport(candidate.Endpoint)); + } + + private sealed class UnverifiedRealtimeTransport : ICultMeshRealtimeTransport + { + public UnverifiedRealtimeTransport(string endpoint) => Endpoint = endpoint; + public string TransportId => "unverified-realtime"; + public string Endpoint { get; } + public Task SendAsync(CultMeshRealtimeFrame frame, CancellationToken cancellationToken = default) => + Task.CompletedTask; + public Task ReceiveAsync(CancellationToken cancellationToken = default) => + Task.FromException(new InvalidOperationException()); + public void Dispose() { } + } + + private sealed class LoopbackRealtimeTransport : ICultMeshRealtimeTransport, ICultMeshVerifiedTransport { private readonly Queue _frames = new(); private readonly SemaphoreSlim _available = new(0); private bool _disposed; - public LoopbackRealtimeTransport(string endpoint) => Endpoint = endpoint; + private readonly CultMeshSessionTarget _target; + private readonly string _routeGeneration; + + public LoopbackRealtimeTransport( + string endpoint, + CultMeshSessionTarget target, + string routeGeneration) + { + Endpoint = endpoint; + _target = target; + _routeGeneration = routeGeneration; + } public string TransportId => "test-quic-state"; public string Endpoint { get; } + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + string.Equals(_target.VerseId, verseId, StringComparison.Ordinal) && + string.Equals(_target.AuthorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.RealtimeState.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + public Task SendAsync(CultMeshRealtimeFrame frame, CancellationToken cancellationToken = default) { if (_disposed) throw new ObjectDisposedException(nameof(LoopbackRealtimeTransport)); diff --git a/tests/GameCult.Networking.Tests/CultNetOperationServerTests.cs b/tests/GameCult.Networking.Tests/CultNetOperationServerTests.cs index f82456fb..ca36a2f4 100644 --- a/tests/GameCult.Networking.Tests/CultNetOperationServerTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetOperationServerTests.cs @@ -77,6 +77,41 @@ namespace GameCult.Networking.Tests Is.EqualTo(4)); } + [Test] + public async Task RejectsRequestAddressedToAnotherRuntimeBeforeApplicationHandler() + { + var transport = new FakeServer(); + var peer = new FakePeer(); + var calls = 0; + using var operations = new CultNetOperationServer(transport, "sample.provider") + .Register( + "sample.counter", + "sample.counter.add", + "sample.add.v1", + "sample.add_receipt.v1", + context => + { + calls++; + return Task.FromResult(new AddReceipt { Count = context.Value.Amount }); + }); + var request = Request(new AddRequest { Amount = 3 }); + request.TargetRuntimeId = "other.provider"; + + await transport.DispatchAsync(request, peer); + + Assert.That(calls, Is.Zero); + var response = peer.Messages.OfType().Single(); + var failure = MessagePackSerializer.Deserialize( + Convert.FromBase64String(response.Payload), + CultNetSchemaMessageSerialization.Options); + Assert.Multiple(() => + { + Assert.That(response.Status, Is.EqualTo("denied")); + Assert.That(failure.Code, Is.EqualTo("target-runtime-mismatch")); + Assert.That(response.SourceRuntimeId, Is.EqualTo("sample.provider")); + }); + } + [Test] public async Task RejectsWrongSchemaBeforeApplicationHandler() { @@ -157,7 +192,8 @@ namespace GameCult.Networking.Tests Payload = Convert.ToBase64String(MessagePackSerializer.Serialize( value, CultNetSchemaMessageSerialization.Options)), - SourceRuntimeId = "sample.browser" + SourceRuntimeId = "sample.browser", + TargetRuntimeId = "sample.provider" }; [MessagePackObject] diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index dd4a63c9..03b4f76c 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -3627,8 +3627,19 @@ namespace GameCult.Networking.Tests "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", vanillaRules), - discoveryEndpoints: ["cultmesh://aetheria.example.test:3075"], - authorityRuntimeIds: ["gc-us-east", "gc-eu-west"]); + authorityRoutes: + [ + new CultMeshAuthorityRoute( + "gc-us-east", + "cultmesh://us-east.aetheria.example.test:3075", + [CultMeshProtocols.Documents.Value], + generation: "east-1"), + new CultMeshAuthorityRoute( + "gc-eu-west", + "cultmesh://eu-west.aetheria.example.test:3075", + [CultMeshProtocols.Documents.Value], + generation: "west-1") + ]); var modded = new CultMeshVerseDescriptor( "aetheria-skylands", "Aetheria: Skylands", diff --git a/tools/GameCult.TransportParity/Program.cs b/tools/GameCult.TransportParity/Program.cs index 04d7aafc..31c93e1e 100644 --- a/tools/GameCult.TransportParity/Program.cs +++ b/tools/GameCult.TransportParity/Program.cs @@ -339,6 +339,12 @@ static async Task MeasureCultMeshRudpAsync(byte[] payload) MaxPendingReliablePackets = 8192 }); using var contentServer = new CultMeshLegacyRudpContentServer(wireServer, providerCache); + using var identityServer = new CultMeshSessionIdentityServer( + wireServer, + "service:parity.provider", + ["parity"], + [CultMeshProtocols.Content.Value], + ["parity-route-1"]); using var pumpCancellation = new CancellationTokenSource(); var pump = Task.Run(async () => { @@ -359,7 +365,7 @@ static async Task MeasureCultMeshRudpAsync(byte[] payload) [new CultMeshLegacyRudpContentTransportConnector( new CultMeshLegacyRudpContentTransportOptions { ResponseTimeout = TimeSpan.FromMinutes(2) })]); var provider = new CultMeshSessionContentProvider( - "parity.provider", sessions, CultMeshEndpointId.Parse("service:parity.provider")); + "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); @@ -392,7 +398,14 @@ static async Task MeasureCultMeshTcpAsync(byte[] payload) await CultMeshCdn.PublishAsync(providerCache, artifact); using var contentServer = new CultMeshTcpContentServer( - new TcpListener(IPAddress.Loopback, 0), providerCache); + new TcpListener(IPAddress.Loopback, 0), + providerCache, + new CultMeshTcpContentServerOptions + { + VerseId = "parity", + AuthorityRuntimeId = "service:parity.provider", + RouteGeneration = "parity-route-1" + }); var endpoint = $"{CultMeshTcpContentTransportConnector.Scheme}://127.0.0.1:{contentServer.LocalEndPoint.Port}"; using var discovery = new CultMeshDiscoveryService([new RouteSource(endpoint)]); using var sessions = new CultMeshSessionManager( @@ -400,7 +413,7 @@ static async Task MeasureCultMeshTcpAsync(byte[] payload) Array.Empty(), [new CultMeshTcpContentTransportConnector()]); var provider = new CultMeshSessionContentProvider( - "parity.provider", sessions, CultMeshEndpointId.Parse("service:parity.provider")); + "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); @@ -479,8 +492,14 @@ internal sealed class RouteSource(string endpoint) : ICultMeshLookupSource new( new CultMeshVerseDescriptor( "parity", "Transport parity", CultMeshVerseAuthorityModel.OperatorCluster, - new CultMeshVerseCompatibility("cultmesh.v1", "parity"), [endpoint], - ["service:parity.provider"]), + new CultMeshVerseCompatibility("cultmesh.v1", "parity"), + authorityRoutes: + [ + new CultMeshAuthorityRoute( + "service:parity.provider", + endpoint, + generation: "parity-route-1") + ]), SourceId, now, now.AddMinutes(1), CultMeshDiscoveryTrust.Signed) ]; return Task.FromResult(observations); From 6835ea4c1063e504c79f03348ff85990af4cb4d5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 17:39:59 +0200 Subject: [PATCH 025/203] Make Eve onboarding prove packaged authority routes --- .../EveBrowserNetworkSample.csproj | 11 ++- scripts/build-unity-package.ps1 | 13 +++- scripts/pack-nuget.ps1 | 13 +++- scripts/verify-eve-browser-network.mjs | 31 +++++++- scripts/verify-eve-getting-started.ps1 | 73 ++++++++++++------- .../getting-started/01-persist-typed-state.md | 2 +- .../getting-started/02-connect-by-identity.md | 15 +++- .../03-publish-an-eve-surface.md | 12 ++- .../04-run-the-multiruntime-game.md | 8 +- .../docs/getting-started/README.md | 20 ++++- .../GameCult.Networking.WebSockets.csproj | 2 + 11 files changed, 151 insertions(+), 49 deletions(-) diff --git a/samples/eve-browser-network/EveBrowserNetworkSample.csproj b/samples/eve-browser-network/EveBrowserNetworkSample.csproj index 7de0bdbd..c83a42df 100644 --- a/samples/eve-browser-network/EveBrowserNetworkSample.csproj +++ b/samples/eve-browser-network/EveBrowserNetworkSample.csproj @@ -4,11 +4,20 @@ enable enable $(MSBuildThisFileDirectory)..\..\..\Eve + false + 1.0.33 + 0.3.2 + $(PackageFeed) - + + + + + + diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1 index bb12e0c6..08366cc5 100644 --- a/scripts/build-unity-package.ps1 +++ b/scripts/build-unity-package.ps1 @@ -1,6 +1,7 @@ param( [string] $Configuration = "Release", [string] $OutputDirectory = "artifacts\unity\org.gamecult.cultlib", + [string] $NuGetConfig = "", [switch] $UpdateTemplate ) @@ -30,11 +31,19 @@ if (Test-Path -LiteralPath $outputRoot) { Remove-Item -LiteralPath $outputRoot -Recurse -Force } -dotnet publish $projectPath -c $Configuration -o $publishRoot +$publishArguments = @("publish", $projectPath, "-c", $Configuration, "-o", $publishRoot) +if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { + $publishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" +} +& dotnet @publishArguments if ($LASTEXITCODE -ne 0) { throw "CultLib publish failed with exit code $LASTEXITCODE" } -dotnet publish $quicProjectPath -c $Configuration -o $quicPublishRoot +$quicPublishArguments = @("publish", $quicProjectPath, "-c", $Configuration, "-o", $quicPublishRoot) +if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { + $quicPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" +} +& dotnet @quicPublishArguments if ($LASTEXITCODE -ne 0) { throw "CultLib native QUIC managed publish failed with exit code $LASTEXITCODE" } diff --git a/scripts/pack-nuget.ps1 b/scripts/pack-nuget.ps1 index 14fa7340..d6f4a224 100644 --- a/scripts/pack-nuget.ps1 +++ b/scripts/pack-nuget.ps1 @@ -1,6 +1,7 @@ param( [string] $Configuration = "Release", [string] $OutputDirectory = "artifacts\nuget", + [string] $PackageVersion = "", [switch] $SkipConsumerSmoke ) @@ -18,6 +19,7 @@ $projects = @( "src\GameCult.Caching.MessagePack.Analyzers\GameCult.Caching.MessagePack.Analyzers.csproj", "src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj", "src\GameCult.Networking\GameCult.Networking.csproj", + "src\GameCult.Networking.WebSockets\GameCult.Networking.WebSockets.csproj", "src\GameCult.Mesh\GameCult.Mesh.csproj" ) @@ -27,8 +29,14 @@ if (Test-Path -LiteralPath $outputRoot) { New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null foreach ($project in $projects) { - dotnet pack (Join-Path $repoRoot $project) -c $Configuration -o $outputRoot ` - --nologo --verbosity quiet -p:NoWarn=1591%3BCS8632 + $packArguments = @( + "pack", (Join-Path $repoRoot $project), "-c", $Configuration, "-o", $outputRoot, + "--nologo", "--verbosity", "quiet", "-p:NoWarn=1591%3BCS8632" + ) + if (-not [string]::IsNullOrWhiteSpace($PackageVersion)) { + $packArguments += "-p:Version=$PackageVersion" + } + & dotnet @packArguments if ($LASTEXITCODE -ne 0) { throw "NuGet pack failed for $project with exit code $LASTEXITCODE" } @@ -48,6 +56,7 @@ $expectedPackages = @( "GameCult.Caching.MessagePack.Analyzers.$version.nupkg", "GameCult.Caching.MessagePack.$version.nupkg", "GameCult.Networking.$version.nupkg", + "GameCult.Networking.WebSockets.$version.nupkg", "GameCult.Mesh.$version.nupkg" ) foreach ($package in $expectedPackages) { diff --git a/scripts/verify-eve-browser-network.mjs b/scripts/verify-eve-browser-network.mjs index 20d819a0..8c865b1d 100644 --- a/scripts/verify-eve-browser-network.mjs +++ b/scripts/verify-eve-browser-network.mjs @@ -14,6 +14,14 @@ import { chromium } from "playwright-core"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const eveFlag = process.argv.indexOf("--eve-root"); const eveRoot = resolve(eveFlag >= 0 ? process.argv[eveFlag + 1] : join(repoRoot, "..", "Eve")); +const packageFeedFlag = process.argv.indexOf("--package-feed"); +const packageFeed = packageFeedFlag >= 0 ? resolve(process.argv[packageFeedFlag + 1]) : undefined; +const cultLibVersionFlag = process.argv.indexOf("--cultlib-package-version"); +const cultLibPackageVersion = cultLibVersionFlag >= 0 ? process.argv[cultLibVersionFlag + 1] : undefined; +const eveSurfaceVersionFlag = process.argv.indexOf("--eve-surface-package-version"); +const eveSurfacePackageVersion = eveSurfaceVersionFlag >= 0 ? process.argv[eveSurfaceVersionFlag + 1] : undefined; +const nugetConfigFlag = process.argv.indexOf("--nuget-config"); +const nugetConfig = nugetConfigFlag >= 0 ? resolve(process.argv[nugetConfigFlag + 1]) : undefined; const sampleRoot = join(repoRoot, "samples", "eve-browser-network"); const workRoot = await mkdtemp(join(tmpdir(), "cultmesh-browser-network-")); const statePath = join(workRoot, "counter.cc"); @@ -51,15 +59,30 @@ try { const bundle = await readFile(bundlePath, "utf8"); assert.doesNotMatch(bundle, /(?:from\s*["']node:|require\(["']node:)/u, "browser bundle contains a Node builtin import"); - await run("dotnet", [ + const dotnetBuildArguments = [ "build", join(sampleRoot, "EveBrowserNetworkSample.csproj"), "-m:1", "--verbosity", "quiet", "-p:NoWarn=1591%3BCS8632", - `-p:EveRoot=${eveRoot}`, - `-p:CultLibRoot=${repoRoot}`, - ]); + "-p:NuGetAudit=false", + "-p:RestoreIgnoreFailedSources=true", + ]; + if (nugetConfig) dotnetBuildArguments.push(`-p:RestoreConfigFile=${nugetConfig}`); + if (packageFeed) { + if (!cultLibPackageVersion || !eveSurfacePackageVersion) { + throw new Error("Package artifact builds require both CultLib and Eve surface package versions."); + } + dotnetBuildArguments.push( + "-p:UsePackageArtifacts=true", + `-p:PackageFeed=${packageFeed}`, + `-p:CultLibPackageVersion=${cultLibPackageVersion}`, + `-p:EveSurfacePackageVersion=${eveSurfacePackageVersion}`, + ); + } else { + dotnetBuildArguments.push(`-p:EveRoot=${eveRoot}`, `-p:CultLibRoot=${repoRoot}`); + } + await run("dotnet", dotnetBuildArguments); const providerPort = await freePort(); const decoyProviderPort = await freePort(); const replacementProviderPort = await freePort(); diff --git a/scripts/verify-eve-getting-started.ps1 b/scripts/verify-eve-getting-started.ps1 index eb653aa6..e4143645 100644 --- a/scripts/verify-eve-getting-started.ps1 +++ b/scripts/verify-eve-getting-started.ps1 @@ -25,43 +25,62 @@ if ($cultMeshClientSource -match 'ConnectAsync\s*\(\s*string\s+(endpointId|verse $cultMeshClientSource -match 'Lease(Document|Collection)Async[^\(]*\(\s*string\s+(endpointId|verseId)') { throw "CultMeshClient must not expose an ambiguous one-string session identity." } -$discoverySource = Get-Content -LiteralPath (Join-Path $cultLibRoot "src\GameCult.Mesh\CultMeshDiscoveryService.cs") -Raw -if ($discoverySource -notmatch 'AuthorityRuntimeIds\.Contains\(query\.AuthorityRuntimeId') { - throw "CultMesh discovery must prove that a selected Verse route advertises the requested authority runtime." -} - $dotnetPackageRoot = Join-Path ([IO.Path]::GetTempPath()) ` ("eve-getting-started-nuget-" + [guid]::NewGuid().ToString("N")) +$sourceRevision = (& git -C $cultLibRoot rev-parse --short=12 HEAD).Trim() +if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sourceRevision)) { + throw "Could not determine the CultLib source revision for local package identity." +} +$verificationNonce = [guid]::NewGuid().ToString("N").Substring(0, 8) +$cultLibLocalVersion = "1.0.33-local.$sourceRevision.$verificationNonce" +$surfaceLocalVersion = "0.3.2-local.$sourceRevision.$verificationNonce" try { & (Join-Path $EveRoot "scripts\pack-dotnet-surface.ps1") ` -CultLibRoot $cultLibRoot ` - -OutputDirectory $dotnetPackageRoot + -OutputDirectory $dotnetPackageRoot ` + -CultLibPackageVersion $cultLibLocalVersion ` + -SurfacePackageVersion $surfaceLocalVersion if ($LASTEXITCODE -ne 0) { throw "The clean .NET package-consumer checkpoint failed." } + + $cultLibPackage = Get-ChildItem -LiteralPath $dotnetPackageRoot -Filter "GameCult.Mesh.*.nupkg" | + Where-Object { $_.Name -notlike "*.snupkg" } | + Select-Object -First 1 + $surfacePackage = Get-ChildItem -LiteralPath $dotnetPackageRoot -Filter "GameCult.Eve.Surface.*.nupkg" | + Where-Object { $_.Name -notlike "*.snupkg" } | + Select-Object -First 1 + if (-not $cultLibPackage -or -not $surfacePackage) { + throw "The local package feed is missing CultMesh or Eve surface artifacts." + } + $cultLibVersion = [regex]::Match($cultLibPackage.Name, '^GameCult\.Mesh\.(.+)\.nupkg$').Groups[1].Value + $surfaceVersion = [regex]::Match($surfacePackage.Name, '^GameCult\.Eve\.Surface\.(.+)\.nupkg$').Groups[1].Value + + & (Join-Path $PSScriptRoot "verify-eve-two-runtime-sample.ps1") ` + -EveRoot $EveRoot ` + -SkipDependencyInstall:$SkipDependencyInstall + if ($LASTEXITCODE -ne 0) { + throw "The clean package-consumer checkpoint failed." + } + + $nodeCommand = Get-Command node -ErrorAction Stop + $playwrightCli = Join-Path $cultLibRoot "node_modules\playwright-core\cli.js" + if (-not (Test-Path -LiteralPath $playwrightCli -PathType Leaf)) { + throw "playwright-core is missing after dependency setup: $playwrightCli" + } + + & $nodeCommand.Source (Join-Path $PSScriptRoot "verify-eve-browser-network.mjs") ` + --eve-root $EveRoot ` + --package-feed $dotnetPackageRoot ` + --cultlib-package-version $cultLibVersion ` + --eve-surface-package-version $surfaceVersion + if ($LASTEXITCODE -ne 0) { + throw "The real browser/C# network checkpoint failed." + } + + Write-Host "Eve getting-started verification passed: clean .NET and TypeScript artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, authority-bound routes, receipts, persistence, and route replacement." } finally { if (Test-Path -LiteralPath $dotnetPackageRoot) { Remove-Item -LiteralPath $dotnetPackageRoot -Recurse -Force } } - -& (Join-Path $PSScriptRoot "verify-eve-two-runtime-sample.ps1") ` - -EveRoot $EveRoot ` - -SkipDependencyInstall:$SkipDependencyInstall -if ($LASTEXITCODE -ne 0) { - throw "The clean package-consumer checkpoint failed." -} - -$nodeCommand = Get-Command node -ErrorAction Stop -$playwrightCli = Join-Path $cultLibRoot "node_modules\playwright-core\cli.js" -if (-not (Test-Path -LiteralPath $playwrightCli -PathType Leaf)) { - throw "playwright-core is missing after dependency setup: $playwrightCli" -} - -& $nodeCommand.Source (Join-Path $PSScriptRoot "verify-eve-browser-network.mjs") ` - --eve-root $EveRoot -if ($LASTEXITCODE -ne 0) { - throw "The real browser/C# network checkpoint failed." -} - -Write-Host "Eve getting-started verification passed: clean .NET and TypeScript artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, receipts, persistence, and route replacement." diff --git a/src/GameCult.Mesh/docs/getting-started/01-persist-typed-state.md b/src/GameCult.Mesh/docs/getting-started/01-persist-typed-state.md index c2667e9b..7520d67f 100644 --- a/src/GameCult.Mesh/docs/getting-started/01-persist-typed-state.md +++ b/src/GameCult.Mesh/docs/getting-started/01-persist-typed-state.md @@ -34,7 +34,7 @@ using var node = await CultMesh.CreateNodeAsync("counter.cc", new CultMeshNodeOp CacheOptions = new CultCacheOpenOptions { Registry = registry }, DatabaseOptions = new CultNetDatabaseOptions { - RuntimeId = "sample.counter-provider", + RuntimeId = "sample.counter-daemon", DocumentRegistry = documents } }); diff --git a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md index 5cf90a0e..ee505acb 100644 --- a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md +++ b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md @@ -1,13 +1,17 @@ # 2. Connect A Client By Stable Identity Clients address a live session by stable Verse/provider identity. In this tutorial, -`sample.counter` is the Verse id and `sample.counter-provider` is the authority +`sample.counter` is the Verse id and `sample.counter-daemon` is the authority runtime Odin currently advertises for it. Application clients pass that typed pair, never a socket address and never one ambiguous string that might mean either identity. A product may expose only a Verse choice to its user; its selection owner then resolves the provider runtime from Odin's descriptor before opening the session. Discovery owns the current physical routes, and the session manager owns connection reuse, path rotation, and transport failure state. +Each Odin route binds that authority runtime to one endpoint, protocol set, and +generation. The client verifies the same tuple with the connected peer before +the session becomes online; runtime membership and endpoint reachability are +not independent routing evidence. ```csharp using GameCult.Mesh; @@ -15,7 +19,9 @@ using GameCult.Mesh; var odinEndpoint = Environment.GetEnvironmentVariable("ODIN_ENDPOINT") ?? throw new InvalidOperationException("Set ODIN_ENDPOINT to the configured rendezvous route."); const string verseId = "sample.counter"; -var target = new CultMeshSessionTarget(verseId, "sample.counter-provider"); +var target = new CultMeshSessionTarget(verseId, "sample.counter-daemon"); +using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); +var cancellationToken = timeout.Token; using var mesh = new CultMeshClient(new CultMeshClientOptions { RendezvousEndpoints = new[] { odinEndpoint } @@ -65,6 +71,11 @@ network proof): dotnet test tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj --filter FullyQualifiedName~CultMeshSessionManagerTests ``` +The real checkpoint in chapter 4 additionally advertises a better-priority +endpoint for the wrong authority runtime. Both the C# and browser clients must +ignore it, verify the intended peer, survive route replacement, and reject +wrong-source operation responses. + Advanced hosts may inject lookup sources, connectors, clocks, persistence, and diagnostics through the lower-level discovery and session APIs. Application features should not need them. diff --git a/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md b/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md index bbeff475..5cb5b742 100644 --- a/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md +++ b/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md @@ -97,6 +97,10 @@ GameCult dependency is `PackageReference Include="GameCult.Eve.Surface"`. This proves the renderer-neutral provider contract does not require a sibling Eve or CultLib source tree after packaging. +The real-process C# sample is then rebuilt from that same temporary NuGet feed. +The source project references in its `.csproj` are contributor convenience only +and are disabled by the checkpoint; they are not the consumer contract. + The first layer builds and packs `cultcache-ts`, `cultnet-ts`, `cultmesh-ts`, `@gamecult/eve-contracts`, and `@gamecult/eve-browser-lowering`, installs the tarballs into an empty temporary consumer, and runs @@ -108,11 +112,11 @@ both consumers converge on the same state and canonical receipt identity, a duplicate idempotency key is not applied twice, and the state plus receipt survive reopening the `.cc` store. -The second layer runs a durable C# provider, a separate local Odin fixture, a -real Chromium Eve lowerer, and an independent C# headless observer over binary +The second layer runs a durable C# provider, a wrong-authority decoy, a separate +local Odin fixture, a real Chromium Eve lowerer, and an independent C# headless observer over binary CultNet WebSocket lanes. The open browser resolves the `sample.counter` Verse -through the canonical catalog, survives its provider restarting on a different route, -and resubscribes. The retained C# client then invokes a second typed operation +through the canonical catalog, rejects the decoy route, survives its provider +restarting on a different route, and resubscribes. The retained C# client then invokes a second typed operation through `CultMeshClient.InvokeAsync`; Chromium and C# observe the same provider-authored state and receipt chronology. The same retained C# session then completes 10,000 typed no-op operations under explicit p99 and post-GC diff --git a/src/GameCult.Mesh/docs/getting-started/04-run-the-multiruntime-game.md b/src/GameCult.Mesh/docs/getting-started/04-run-the-multiruntime-game.md index ba9888c3..ce18fdb3 100644 --- a/src/GameCult.Mesh/docs/getting-started/04-run-the-multiruntime-game.md +++ b/src/GameCult.Mesh/docs/getting-started/04-run-the-multiruntime-game.md @@ -10,6 +10,8 @@ The counter is now one small multiplayer game: typed operation boundary; - both clients remain attached when Odin moves the Verse to a replacement physical provider route. +- a better-priority route bound to another runtime remains unreachable to both + clients throughout the chronology. Run the whole chronology from the CultLib checkout: @@ -34,8 +36,10 @@ packages, installs those artifacts into an empty temporary consumer, and proves DOM/headless convergence plus `.cc` reopen. This rejects a sample that only works through repository-relative source imports. -The third checkpoint boots separate Odin, provider, Chromium, and C# headless -processes. Chromium performs the first increment. The provider then restarts on +The third checkpoint boots separate Odin, intended provider, wrong-authority +decoy provider, Chromium, and C# headless processes. Its C# application is built +from the temporary NuGet feed produced by the first checkpoint, not sibling +project references. Chromium performs the first increment. The provider then restarts on a different route, Odin advertises the replacement, and the retained clients resubscribe without application reconnect code. C# performs the second increment. Both clients must observe count 2 and both canonical receipt ids. diff --git a/src/GameCult.Mesh/docs/getting-started/README.md b/src/GameCult.Mesh/docs/getting-started/README.md index b32052a7..66ded7a4 100644 --- a/src/GameCult.Mesh/docs/getting-started/README.md +++ b/src/GameCult.Mesh/docs/getting-started/README.md @@ -4,14 +4,22 @@ This series builds one typed application state and presents it through multiple Eve runtimes. It is maintained as an API acceptance test: examples use public APIs, name the owner of each decision, and include a verification checkpoint. -Run the complete getting-started checkpoint: +Application consumers install released packages and do not need the source +repositories. This checkout also contains a maintainer checkpoint that creates +the same clean consumers from local package artifacts: ```powershell pwsh -File ./scripts/verify-eve-getting-started.ps1 ``` -With clean sibling CultLib and Eve checkouts, the command installs missing build -dependencies and runs three explicit evidence layers. First it packs the +The maintainer command takes an explicit `-EveRoot` when Eve is not a sibling: + +```powershell +pwsh -File ./scripts/verify-eve-getting-started.ps1 -EveRoot D:\src\Eve +``` + +It installs missing build dependencies and runs three explicit evidence layers. +First it packs the CultLib managed dependency closure plus `GameCult.Eve.Surface`, then restores and runs an empty .NET consumer using only `PackageReference`. Next it packs the five TypeScript packages into another empty temporary consumer, validates the Eve surface @@ -20,7 +28,8 @@ headless observer converge, and reopens the durable `.cc` store. Then it boots a durable C# provider, local Odin fixture, real Chromium lowerer, and independent C# client. Chromium invokes before provider replacement; C# invokes afterward. Both retained clients prove discovery by identity, canonical receipts, provider -restart on another route, resubscription, and durable convergence. The retained +restart on another route, rejection of a better-priority wrong-authority route, +resubscription, and durable convergence. The retained C# session also completes 10,000 non-persistent typed operations under p99 and post-GC managed-memory gates. Set `CHROME_PATH` only when no installed Chrome, Chromium, Edge, or Playwright @@ -58,3 +67,6 @@ native projection. CultMesh owns discovery and connection continuity. pull request. - A setup step that depends on repository internals is an API defect, not acceptable tutorial folklore. +- The maintainer command may read owner checkouts to create packages. Every + sample process it launches must consume those package artifacts rather than + compiling through sibling project references. diff --git a/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj b/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj index 40214247..6ced9b42 100644 --- a/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj +++ b/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj @@ -4,6 +4,8 @@ enable enable CultNet schema-v0 WebSocket host adapter for browser and web runtimes + GameCult.Networking.WebSockets + game;networking;websocket;cultnet;cultmesh From f622ee1b248a8001ebb8e0960ce99679d163ce18 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 19:02:46 +0200 Subject: [PATCH 026/203] Authenticate CultMesh authority routes and replay operations --- Directory.Build.props | 1 + packages/cultmesh-browser/src/index.ts | 244 ++++++++++-- .../test/cultmesh-browser.test.ts | 273 +++++++++++++- .../cultmesh.session-accepted.schema.json | 7 +- .../cultmesh.session-open.schema.json | 7 +- ...ultmesh.verse-catalog-response.schema.json | 17 +- packages/cultnet-ts/src/contracts.ts | 25 +- packages/cultnet-ts/test/cultnet.test.ts | 7 +- samples/eve-browser-network/Program.cs | 4 + samples/eve-browser-network/browser/main.ts | 1 + scripts/pack-nuget.ps1 | 2 +- src/GameCult.Mesh/CultMeshAuthorityProof.cs | 349 ++++++++++++++++++ src/GameCult.Mesh/CultMeshContentSessions.cs | 4 +- src/GameCult.Mesh/CultMeshSessionIdentity.cs | 55 ++- src/GameCult.Mesh/CultMeshSessions.cs | 33 +- .../CultMeshTcpContentTransport.cs | 32 +- src/GameCult.Mesh/CultMeshVerse.cs | 6 +- src/GameCult.Mesh/CultMeshVerseDiscovery.cs | 23 +- .../docs/browser-verse-transport.md | 11 +- .../getting-started/02-connect-by-identity.md | 39 +- src/GameCult.Mesh/docs/transport-planes.md | 24 +- .../CultNetSchemaMessages.cs | 21 +- .../CultMeshAuthorityProofTests.cs | 241 ++++++++++++ .../CultMeshBodySessionTests.cs | 6 +- .../CultMeshClientTests.cs | 20 +- .../CultMeshContentSessionTests.cs | 8 +- .../CultMeshSessionManagerTests.cs | 70 ++-- .../GameCult.Mesh.Tests/CultMeshTestTrust.cs | 18 + .../CultMeshTransportModularityTests.cs | 12 +- tools/GameCult.TransportParity/Program.cs | 14 +- 30 files changed, 1432 insertions(+), 142 deletions(-) create mode 100644 src/GameCult.Mesh/CultMeshAuthorityProof.cs create mode 100644 tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs create mode 100644 tests/GameCult.Mesh.Tests/CultMeshTestTrust.cs diff --git a/Directory.Build.props b/Directory.Build.props index db26d4f3..434617ff 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,6 +15,7 @@ © Meta Vens and contributors true 1.0.33 + $(CultLibPackageVersion) true diff --git a/packages/cultmesh-browser/src/index.ts b/packages/cultmesh-browser/src/index.ts index bbc253f4..a0596ae2 100644 --- a/packages/cultmesh-browser/src/index.ts +++ b/packages/cultmesh-browser/src/index.ts @@ -24,7 +24,32 @@ export interface CultMeshBrowserIdentity { export interface CultMeshBrowserRoute extends CultMeshBrowserIdentity { endpoint: string; protocolId?: string; + protocolIds?: readonly string[]; + priority?: number; generation: string; + certificate?: CultMeshBrowserRouteCertificate; +} + +export interface CultMeshBrowserP256PublicKey { + keyId: string; + x: string; + y: string; +} + +export interface CultMeshBrowserRouteCertificate { + providerKey: CultMeshBrowserP256PublicKey; + odinKeyId: string; + issuedAtUnixMilliseconds: number; + expiresAtUnixMilliseconds: number; + signature: string; +} + +export type CultMeshBrowserAuthorityTrustMode = "authenticated-remote" | "local-development"; + +export interface CultMeshBrowserAuthorityTrustPolicy { + mode: CultMeshBrowserAuthorityTrustMode; + odinRoots?: readonly CultMeshBrowserP256PublicKey[]; + now?: () => number; } export interface CultMeshBrowserRendezvous { @@ -162,6 +187,8 @@ export interface CultMeshBrowserClientOptions extends CultMeshBrowserIdentity { requestTimeoutMs?: number; maxFrameBytes?: number; createId?: () => string; + /** Consumer-owned authority roots. Remote sessions fail closed when omitted. */ + trust?: CultMeshBrowserAuthorityTrustPolicy; } export interface CultMeshRawDocumentLeaseOptions { @@ -215,6 +242,7 @@ interface PendingSnapshot { } interface PendingOperation { + request: CultNetOperationRequestMessage; resolve: (response: CultNetOperationResponseMessage) => void; reject: (error: Error) => void; timer: ReturnType; @@ -297,6 +325,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { maxFrameBytes: options.maxFrameBytes ?? 4 * 1024 * 1024, createId: options.createId ?? (() => crypto.randomUUID()), socketFactory: options.socketFactory ?? (endpoint => new WebSocket(endpoint)), + trust: options.trust ?? { mode: "authenticated-remote", odinRoots: [] }, }; } @@ -369,15 +398,12 @@ export class CultMeshBrowserClient implements AsyncDisposable { if (!this.#pendingOperations.delete(messageId)) return; reject(new Error(`CultMesh operation '${messageId}' timed out.`)); }, this.#options.requestTimeoutMs); - this.#pendingOperations.set(messageId, { resolve, reject, timer }); + this.#pendingOperations.set(messageId, { request, resolve, reject, timer }); }); try { this.send(request); } catch (error) { - const pending = this.#pendingOperations.get(messageId); - if (pending) clearTimeout(pending.timer); - this.#pendingOperations.delete(messageId); - throw error; + this.scheduleReconnect(); } return response; } @@ -490,6 +516,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { route.authorityRuntimeId !== this.identity.authorityRuntimeId) { throw new Error("CultMesh rendezvous returned a route for the wrong stable identity."); } + await verifyAuthorityRoute(route, this.#options.trust); const endpoint = new URL(route.endpoint); if (endpoint.protocol !== "ws:" && endpoint.protocol !== "wss:") { throw new Error(`CultMesh browser route must use ws:// or wss://, got '${route.endpoint}'.`); @@ -501,6 +528,17 @@ export class CultMeshBrowserClient implements AsyncDisposable { let opened = false; let settled = false; const handshakeId = this.#options.createId(); + const clientNonce = randomNonce(); + const sessionRequest: CultMeshSessionOpenMessage = { + schemaVersion: "cultmesh.session_open.v2", + messageId: handshakeId, + sourceRuntimeId: this.runtimeId, + verseId: this.identity.verseId, + authorityRuntimeId: this.identity.authorityRuntimeId, + protocolId: route.protocolId ?? "cultmesh.documents.v1", + routeGeneration: route.generation, + clientNonce, + }; const finish = (error?: Error) => { if (settled) return; settled = true; @@ -524,8 +562,8 @@ export class CultMeshBrowserClient implements AsyncDisposable { runtimeId: this.runtimeId, runtimeKind: "browser", supportedMessageVersions: [ - "cultmesh.session_open.v1", - "cultmesh.session_accepted.v1", + "cultmesh.session_open.v2", + "cultmesh.session_accepted.v2", "cultnet.database_subscribe.v0", "cultnet.database_unsubscribe.v0", "cultnet.database_change_raw.v0", @@ -533,16 +571,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { "cultnet.operation_response.v0", ], }, this.#options.maxFrameBytes); - const request: CultMeshSessionOpenMessage = { - schemaVersion: "cultmesh.session_open.v1", - messageId: handshakeId, - sourceRuntimeId: this.runtimeId, - verseId: this.identity.verseId, - authorityRuntimeId: this.identity.authorityRuntimeId, - protocolId: route.protocolId ?? "cultmesh.documents.v1", - routeGeneration: route.generation, - }; - sendSocketMessage(socket, request, this.#options.maxFrameBytes); + sendSocketMessage(socket, sessionRequest, this.#options.maxFrameBytes); } catch (error) { finish(error instanceof Error ? error : new Error(String(error))); } @@ -553,8 +582,13 @@ export class CultMeshBrowserClient implements AsyncDisposable { finish(new Error(message.error)); return; } - if (message.schemaVersion !== "cultmesh.session_accepted.v1" || message.messageId !== handshakeId) return; - finish(validateSessionAcceptance(message, this.identity, route)); + if (message.schemaVersion !== "cultmesh.session_accepted.v2" || message.messageId !== handshakeId) return; + void validateSessionAcceptance( + message, + sessionRequest, + route, + this.#options.trust, + ).then(finish).catch(error => finish(error instanceof Error ? error : new Error(String(error)))); }).catch(error => finish(error instanceof Error ? error : new Error(String(error)))); }; socket.onerror = () => finish(new Error(`CultMesh WebSocket could not open '${route.endpoint}'.`)); @@ -575,6 +609,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { this.setState("connected"); try { await Promise.all([...this.#leases.values()].map(lease => this.subscribe(lease))); + for (const pending of this.#pendingOperations.values()) this.send(pending.request); } catch (error) { if (this.#socket === socket) this.#socket = undefined; socket.onclose = null; @@ -591,12 +626,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { clearTimeout(pending.timer); pending.reject(error); } - for (const pending of this.#pendingOperations.values()) { - clearTimeout(pending.timer); - pending.reject(error); - } this.#pendingSnapshots.clear(); - this.#pendingOperations.clear(); this.setState("reconnecting"); this.#reconnectTimer = setTimeout(() => { this.#reconnectTimer = undefined; @@ -713,6 +743,15 @@ export class CultMeshBrowserClient implements AsyncDisposable { for (const watcher of this.#stateWatchers) watcher(state); } + private scheduleReconnect(): void { + if (this.#disposed || this.#reconnectTimer) return; + this.setState("reconnecting"); + this.#reconnectTimer = setTimeout(() => { + this.#reconnectTimer = undefined; + void this.connectSocket().catch(() => this.scheduleReconnect()); + }, this.#options.reconnectDelayMs); + } + private throwIfDisposed(): void { if (this.#disposed) throw new Error("CultMesh browser client is disposed."); } @@ -816,6 +855,7 @@ function selectOdinRoute( protocolIds: ["cultmesh.documents.v1"], priority: 0, generation: [verse.compatibility.transportVersion, verse.compatibility.rulesHash, endpoint].join(":"), + certificate: undefined, })); const routes = boundRoutes.filter(route => route.authorityRuntimeId === identity.authorityRuntimeId && @@ -836,29 +876,171 @@ function selectOdinRoute( ...identity, endpoint: route.endpoint, protocolId: "cultmesh.documents.v1", + protocolIds: route.protocolIds, + priority: route.priority, generation: route.generation, + ...(route.certificate ? { certificate: { + providerKey: { + keyId: route.certificate.providerKeyId, + x: route.certificate.providerPublicKeyX, + y: route.certificate.providerPublicKeyY, + }, + odinKeyId: route.certificate.odinKeyId, + issuedAtUnixMilliseconds: route.certificate.issuedAtUnixMilliseconds, + expiresAtUnixMilliseconds: route.certificate.expiresAtUnixMilliseconds, + signature: route.certificate.signature, + } } : {}), }; } -function validateSessionAcceptance( +async function validateSessionAcceptance( message: CultMeshSessionAcceptedMessage, - identity: CultMeshBrowserIdentity, + request: CultMeshSessionOpenMessage, route: CultMeshBrowserRoute, -): Error | undefined { + trust: CultMeshBrowserAuthorityTrustPolicy, +): Promise { if (!message.accepted) return new Error(message.error ?? "CultMesh authority rejected the session."); const protocolId = route.protocolId ?? "cultmesh.documents.v1"; - if (message.verseId !== identity.verseId || - message.authorityRuntimeId !== identity.authorityRuntimeId || + if (message.verseId !== request.verseId || + message.authorityRuntimeId !== request.authorityRuntimeId || message.protocolId !== protocolId || - message.routeGeneration !== route.generation) { + message.routeGeneration !== route.generation || + message.clientNonce !== request.clientNonce) { return new Error( `CultMesh route proved '${message.verseId}/${message.authorityRuntimeId}/${message.protocolId}/${message.routeGeneration}', ` + - `expected '${identity.verseId}/${identity.authorityRuntimeId}/${protocolId}/${route.generation}'.`, + `expected '${request.verseId}/${request.authorityRuntimeId}/${protocolId}/${route.generation}'.`, ); } + if (trust.mode === "local-development" && isLoopbackEndpoint(route.endpoint) && !route.certificate) return undefined; + const providerKey = route.certificate?.providerKey; + if (!providerKey || message.providerKeyId !== providerKey.keyId || !message.providerSignature) { + return new Error("CultMesh authority did not prove possession of the Odin-certified provider key."); + } + if (!await verifyP256(providerKey, canonicalSession(request, route.endpoint), message.providerSignature)) { + return new Error("CultMesh provider session proof is invalid."); + } return undefined; } +async function verifyAuthorityRoute( + route: CultMeshBrowserRoute, + trust: CultMeshBrowserAuthorityTrustPolicy, +): Promise { + const certificate = route.certificate; + if (!certificate) { + if (trust.mode === "local-development" && isLoopbackEndpoint(route.endpoint)) return; + throw new Error("Remote CultMesh routes require an Odin-signed authority certificate."); + } + const endpoint = new URL(route.endpoint); + if (endpoint.protocol !== "wss:" && !(trust.mode === "local-development" && isLoopbackEndpoint(route.endpoint))) { + throw new Error("Authenticated remote CultMesh browser routes require wss:// channel protection."); + } + const now = trust.now?.() ?? Date.now(); + if (now < certificate.issuedAtUnixMilliseconds || now >= certificate.expiresAtUnixMilliseconds) { + throw new Error("The Odin route certificate is not currently valid."); + } + const root = trust.odinRoots?.find(candidate => candidate.keyId === certificate.odinKeyId); + if (!root) throw new Error(`Odin key '${certificate.odinKeyId}' is not trusted by this consumer.`); + if (!await verifyP256(root, canonicalRoute(route), certificate.signature)) { + throw new Error("The Odin route certificate signature is invalid."); + } +} + +function canonicalRoute(route: CultMeshBrowserRoute): Uint8Array { + const certificate = route.certificate!; + return canonicalFields( + "gamecult.cultmesh.route-certificate.v1", + route.verseId, + route.authorityRuntimeId, + route.endpoint, + [...(route.protocolIds ?? [route.protocolId ?? "cultmesh.documents.v1"])].sort().join("\u001f"), + String(route.priority ?? 0), + route.generation, + certificate.providerKey.keyId, + certificate.providerKey.x, + certificate.providerKey.y, + certificate.odinKeyId, + String(certificate.issuedAtUnixMilliseconds), + String(certificate.expiresAtUnixMilliseconds), + ); +} + +function canonicalSession(request: CultMeshSessionOpenMessage, endpoint: string): Uint8Array { + return canonicalFields( + "gamecult.cultmesh.session-proof.v1", + request.clientNonce, + request.messageId, + request.sourceRuntimeId, + request.verseId, + request.authorityRuntimeId, + request.protocolId, + endpoint, + request.routeGeneration, + ); +} + +function canonicalFields(...values: string[]): Uint8Array { + const encoder = new TextEncoder(); + const encoded = values.map(value => encoder.encode(value)); + const total = encoded.reduce((sum, value) => sum + 4 + value.byteLength, 0); + const result = new Uint8Array(total); + const view = new DataView(result.buffer); + let offset = 0; + for (const value of encoded) { + view.setUint32(offset, value.byteLength, false); + offset += 4; + result.set(value, offset); + offset += value.byteLength; + } + return result; +} + +async function verifyP256( + key: CultMeshBrowserP256PublicKey, + payload: Uint8Array, + signatureBase64: string, +): Promise { + try { + const x = base64ToBytes(key.x); + const y = base64ToBytes(key.y); + const signature = base64ToBytes(signatureBase64); + if (x.byteLength !== 32 || y.byteLength !== 32 || signature.byteLength !== 64) return false; + const raw = new Uint8Array(65); + raw[0] = 4; + raw.set(x, 1); + raw.set(y, 33); + const publicKey = await crypto.subtle.importKey( + "raw", + raw.slice().buffer as ArrayBuffer, + { name: "ECDSA", namedCurve: "P-256" }, + false, + ["verify"], + ); + return await crypto.subtle.verify( + { name: "ECDSA", hash: "SHA-256" }, + publicKey, + signature.slice().buffer as ArrayBuffer, + payload.slice().buffer as ArrayBuffer, + ); + } catch { + return false; + } +} + +function randomNonce(): string { + const bytes = crypto.getRandomValues(new Uint8Array(32)); + return bytesToBase64(bytes); +} + +function isLoopbackEndpoint(value: string): boolean { + try { + const host = new URL(value).hostname.replace(/^\[|\]$/g, "").toLowerCase(); + return host === "localhost" || host === "127.0.0.1" || host === "::1"; + } catch { + return false; + } +} + function requireText(value: string, field: string): void { if (!value || value.trim().length === 0) throw new Error(`CultMesh browser ${field} is required.`); } diff --git a/packages/cultmesh-browser/test/cultmesh-browser.test.ts b/packages/cultmesh-browser/test/cultmesh-browser.test.ts index a785d47c..4f90acf1 100644 --- a/packages/cultmesh-browser/test/cultmesh-browser.test.ts +++ b/packages/cultmesh-browser/test/cultmesh-browser.test.ts @@ -19,9 +19,12 @@ import { decodeCultNetOperationPayload, decodeCultNetPayload, type CultMeshBrowserRoute, + type CultMeshBrowserP256PublicKey, type CultMeshBrowserSocket, } from "../src/index.js"; +const localTrust = { mode: "local-development" as const }; + test("Odin rendezvous resolves stable authority-runtime identity and observes route rotation", async () => { const odin = new FakeOdin("ws://127.0.0.1:4050/mesh"); let id = 0; @@ -98,6 +101,7 @@ test("browser client leases provider state, invokes an operation, and follows ro verseId: route.verseId, authorityRuntimeId: route.authorityRuntimeId, runtimeId: "browser-test", + trust: localTrust, rendezvous: { resolve: async () => route }, createId: () => `id-${++id}`, socketFactory: endpoint => endpoint === first.route.endpoint ? first.open() : second.open(), @@ -144,6 +148,7 @@ test("browser client rejects wrong rendezvous identity and has no document write verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", runtimeId: "browser-test", + trust: localTrust, rendezvous: { resolve: async () => ({ ...provider.route, @@ -165,6 +170,7 @@ test("browser client rejects a route whose peer proves the wrong authority", asy verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", runtimeId: "browser-peer-proof-test", + trust: localTrust, rendezvous: { resolve: async () => ({ ...provider.route, authorityRuntimeId: "sample.counter-daemon" }) }, socketFactory: () => provider.open(), }), @@ -172,6 +178,56 @@ test("browser client rejects a route whose peer proves the wrong authority", asy ); }); +test("browser client accepts an Odin-certified route only after provider nonce proof", async () => { + const fixture = await createSignedRoute("wss://provider.example/mesh"); + const client = await CultMeshBrowserClient.connect({ + ...fixture.route, + runtimeId: "browser-authenticated-test", + rendezvous: { resolve: async () => fixture.route }, + trust: { mode: "authenticated-remote", odinRoots: [fixture.odinPublic] }, + socketFactory: () => signedHandshakeSocket(fixture.route, fixture.providerPrivate), + }); + + assert.equal(client.state, "connected"); + await client.dispose(); +}); + +test("browser client rejects a credential-free authority impersonator", async () => { + const fixture = await createSignedRoute("wss://provider.example/mesh"); + const impersonator = new FakeProvider("wss://provider.example/mesh", 1); + + await assert.rejects( + CultMeshBrowserClient.connect({ + ...fixture.route, + runtimeId: "browser-impersonation-test", + rendezvous: { resolve: async () => fixture.route }, + trust: { mode: "authenticated-remote", odinRoots: [fixture.odinPublic] }, + socketFactory: () => impersonator.open(), + }), + /did not prove possession/, + ); +}); + +test("browser client rejects a mutated or expired signed route before opening a provider socket", async () => { + const fixture = await createSignedRoute("wss://provider.example/mesh"); + let opened = false; + const connect = (route: CultMeshBrowserRoute, now?: () => number) => CultMeshBrowserClient.connect({ + ...route, + runtimeId: "browser-route-certificate-test", + rendezvous: { resolve: async () => route }, + trust: { mode: "authenticated-remote", odinRoots: [fixture.odinPublic], now }, + socketFactory: () => { + opened = true; + return signedHandshakeSocket(route, fixture.providerPrivate); + }, + }); + + await assert.rejects(connect({ ...fixture.route, endpoint: "wss://evil.example/mesh" }), /signature is invalid/); + assert.equal(opened, false); + await assert.rejects(connect(fixture.route, () => fixture.expiresAt), /not currently valid/); + assert.equal(opened, false); +}); + test("browser client bounds unanswered provider operations", async () => { const route = { verseId: "sample.counter", @@ -182,6 +238,7 @@ test("browser client bounds unanswered provider operations", async () => { const client = await CultMeshBrowserClient.connect({ ...route, runtimeId: "browser-timeout-test", + trust: localTrust, rendezvous: { resolve: async () => route }, requestTimeoutMs: 5, socketFactory: () => handshakeOnlySocket(route), @@ -204,6 +261,7 @@ test("browser client rejects a correlated framework failure without waiting for const client = await CultMeshBrowserClient.connect({ ...provider.route, runtimeId: "browser-failure-test", + trust: localTrust, rendezvous: { resolve: async () => provider.route }, requestTimeoutMs: 60_000, socketFactory: () => provider.open(), @@ -228,6 +286,82 @@ test("browser client rejects a correlated framework failure without waiting for await client.dispose(); }); +test("browser client replays one idempotent operation after route loss without duplicating its effect", async () => { + const firstRoute = localRoute("ws://127.0.0.1:4311/mesh", "generation-1"); + const secondRoute = localRoute("ws://127.0.0.1:4312/mesh", "generation-2"); + let route = firstRoute; + let effectCount = 0; + let dropFirstResponse = true; + const results = new Map(); + const open = () => { + let socket: FakeSocket; + socket = new FakeSocket(wire => { + const message = parseCultNetMessage(decode(wire)); + if (message.schemaVersion === "cultmesh.session_open.v2") { + socket.deliver({ + schemaVersion: "cultmesh.session_accepted.v2", + messageId: message.messageId, + accepted: true, + verseId: message.verseId, + authorityRuntimeId: message.authorityRuntimeId, + protocolId: message.protocolId, + routeGeneration: message.routeGeneration, + clientNonce: message.clientNonce, + providerKeyId: "", + providerSignature: "", + }); + return; + } + if (message.schemaVersion !== "cultnet.operation_request.v0") return; + let response = results.get(message.messageId); + if (!response) { + effectCount++; + response = { + schemaVersion: "cultnet.operation_response.v0", + messageId: message.messageId, + serviceId: message.serviceId, + operation: message.operation, + status: "accepted", + payloadSchema: "sample.increment_receipt.v1", + payloadEncoding: "messagepack-base64", + payload: Buffer.from(encode({ count: effectCount })).toString("base64"), + sourceRuntimeId: "sample.counter-daemon", + }; + results.set(message.messageId, response!); + } + if (dropFirstResponse) { + dropFirstResponse = false; + route = secondRoute; + socket.close(1012, "route lost after commit"); + return; + } + socket.deliver(response!); + }, () => undefined); + return socket; + }; + const client = await CultMeshBrowserClient.connect({ + ...firstRoute, + runtimeId: "browser-operation-replay-test", + rendezvous: { resolve: async () => route }, + trust: localTrust, + reconnectDelayMs: 1, + requestTimeoutMs: 2_000, + socketFactory: () => open(), + }); + + const response = await client.invoke({ + serviceId: "sample.counter", + operation: "increment", + payloadSchema: "sample.increment.v1", + payload: { amount: 1 }, + idempotencyKey: "replay-once", + }); + + assert.deepEqual(decodeCultNetOperationPayload<{ count: number }>(response), { count: 1 }); + assert.equal(effectCount, 1); + await client.dispose(); +}); + test("browser client rejects an operation response from another runtime", async () => { const provider = new FakeProvider( "ws://127.0.0.1:4371/mesh", @@ -239,6 +373,7 @@ test("browser client rejects an operation response from another runtime", async const client = await CultMeshBrowserClient.connect({ ...provider.route, runtimeId: "browser-response-authority-test", + trust: localTrust, rendezvous: { resolve: async () => provider.route }, socketFactory: () => provider.open(), }); @@ -266,6 +401,7 @@ test("browser client rejects oversized outbound schema messages", async () => { CultMeshBrowserClient.connect({ ...route, runtimeId: "browser-size-test", + trust: localTrust, rendezvous: { resolve: async () => route }, maxFrameBytes: 8, socketFactory: () => new FakeSocket(() => undefined, () => undefined), @@ -279,6 +415,7 @@ test("browser client reserves subscription identity while the initial lease open const client = await CultMeshBrowserClient.connect({ ...provider.route, runtimeId: "browser-lease-race-test", + trust: localTrust, rendezvous: { resolve: async () => provider.route }, socketFactory: () => provider.open(), }); @@ -344,7 +481,7 @@ class FakeProvider { private receive(socket: FakeSocket, wire: Uint8Array): void { const message = parseCultNetMessage(decode(wire)); switch (message.schemaVersion) { - case "cultmesh.session_open.v1": + case "cultmesh.session_open.v2": this.acceptSession(socket, message); return; case "cultnet.database_subscribe.v0": @@ -363,13 +500,16 @@ class FakeProvider { private acceptSession(socket: FakeSocket, message: CultMeshSessionOpenMessage): void { socket.deliver({ - schemaVersion: "cultmesh.session_accepted.v1", + schemaVersion: "cultmesh.session_accepted.v2", messageId: message.messageId, accepted: true, verseId: message.verseId, authorityRuntimeId: this.#runtimeId, protocolId: message.protocolId, routeGeneration: message.routeGeneration, + clientNonce: message.clientNonce, + providerKeyId: "", + providerSignature: "", }); } @@ -501,20 +641,35 @@ function handshakeOnlySocket(route: CultMeshBrowserRoute): FakeSocket { let socket: FakeSocket; socket = new FakeSocket(wire => { const message = parseCultNetMessage(decode(wire)); - if (message.schemaVersion !== "cultmesh.session_open.v1") return; + if (message.schemaVersion !== "cultmesh.session_open.v2") return; socket.deliver({ - schemaVersion: "cultmesh.session_accepted.v1", + schemaVersion: "cultmesh.session_accepted.v2", messageId: message.messageId, accepted: true, verseId: route.verseId, authorityRuntimeId: route.authorityRuntimeId, protocolId: message.protocolId, routeGeneration: message.routeGeneration, + clientNonce: message.clientNonce, + providerKeyId: "", + providerSignature: "", }); }, () => undefined); return socket; } +function localRoute(endpoint: string, generation: string): CultMeshBrowserRoute { + return { + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-daemon", + endpoint, + protocolId: "cultmesh.documents.v1", + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation, + }; +} + class FakeSocket implements CultMeshBrowserSocket { binaryType: BinaryType = "arraybuffer"; readyState = 0; @@ -555,6 +710,116 @@ class FakeSocket implements CultMeshBrowserSocket { } } +async function createSignedRoute(endpoint: string): Promise<{ + route: CultMeshBrowserRoute; + odinPublic: CultMeshBrowserP256PublicKey; + providerPrivate: CryptoKey; + expiresAt: number; +}> { + const odin = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); + const provider = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); + const odinPublic = await exportPublic("odin-root-1", odin.publicKey); + const providerPublic = await exportPublic("provider-1", provider.publicKey); + const issuedAt = Date.now() - 1_000; + const expiresAt = Date.now() + 60_000; + const unsigned: CultMeshBrowserRoute = { + verseId: "sample.counter", + authorityRuntimeId: "sample.counter-daemon", + endpoint, + protocolId: "cultmesh.documents.v1", + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation: "signed-generation-1", + certificate: { + providerKey: providerPublic, + odinKeyId: odinPublic.keyId, + issuedAtUnixMilliseconds: issuedAt, + expiresAtUnixMilliseconds: expiresAt, + signature: "", + }, + }; + const signature = await crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-256" }, + odin.privateKey, + testCanonicalRoute(unsigned).slice().buffer as ArrayBuffer, + ); + return { + route: { ...unsigned, certificate: { ...unsigned.certificate!, signature: toBase64(new Uint8Array(signature)) } }, + odinPublic, + providerPrivate: provider.privateKey, + expiresAt, + }; +} + +function signedHandshakeSocket(route: CultMeshBrowserRoute, providerPrivate: CryptoKey): FakeSocket { + let socket: FakeSocket; + socket = new FakeSocket(wire => { + const message = parseCultNetMessage(decode(wire)); + if (message.schemaVersion !== "cultmesh.session_open.v2") return; + void crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-256" }, + providerPrivate, + testCanonicalSession(message, route.endpoint).slice().buffer as ArrayBuffer, + ).then(signature => socket.deliver({ + schemaVersion: "cultmesh.session_accepted.v2", + messageId: message.messageId, + accepted: true, + verseId: message.verseId, + authorityRuntimeId: message.authorityRuntimeId, + protocolId: message.protocolId, + routeGeneration: message.routeGeneration, + clientNonce: message.clientNonce, + providerKeyId: route.certificate!.providerKey.keyId, + providerSignature: toBase64(new Uint8Array(signature)), + })); + }, () => undefined); + return socket; +} + +async function exportPublic(keyId: string, key: CryptoKey): Promise { + const jwk = await crypto.subtle.exportKey("jwk", key); + return { keyId, x: urlBase64ToBase64(jwk.x!), y: urlBase64ToBase64(jwk.y!) }; +} + +function testCanonicalRoute(route: CultMeshBrowserRoute): Uint8Array { + const certificate = route.certificate!; + return testCanonical( + "gamecult.cultmesh.route-certificate.v1", route.verseId, route.authorityRuntimeId, route.endpoint, + [...route.protocolIds!].sort().join("\u001f"), String(route.priority), route.generation, + certificate.providerKey.keyId, certificate.providerKey.x, certificate.providerKey.y, + certificate.odinKeyId, String(certificate.issuedAtUnixMilliseconds), String(certificate.expiresAtUnixMilliseconds), + ); +} + +function testCanonicalSession(message: CultMeshSessionOpenMessage, endpoint: string): Uint8Array { + return testCanonical( + "gamecult.cultmesh.session-proof.v1", message.clientNonce, message.messageId, message.sourceRuntimeId, + message.verseId, message.authorityRuntimeId, message.protocolId, endpoint, message.routeGeneration, + ); +} + +function testCanonical(...values: string[]): Uint8Array { + const chunks = values.map(value => new TextEncoder().encode(value)); + const bytes = new Uint8Array(chunks.reduce((sum, chunk) => sum + 4 + chunk.length, 0)); + const view = new DataView(bytes.buffer); + let offset = 0; + for (const chunk of chunks) { + view.setUint32(offset, chunk.length, false); + offset += 4; + bytes.set(chunk, offset); + offset += chunk.length; + } + return bytes; +} + +function toBase64(bytes: Uint8Array): string { + return Buffer.from(bytes).toString("base64"); +} + +function urlBase64ToBase64(value: string): string { + return Buffer.from(value, "base64url").toString("base64"); +} + function bytesToBase64(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); diff --git a/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json b/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json index 55a2f742..aca1ff2b 100644 --- a/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json +++ b/packages/cultnet-ts/contracts/cultmesh.session-accepted.schema.json @@ -3,16 +3,19 @@ "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.session-accepted.schema.json", "title": "CultMesh Session Accepted", "type": "object", - "required": ["schemaVersion", "messageId", "accepted", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration"], + "required": ["schemaVersion", "messageId", "accepted", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration", "clientNonce", "providerKeyId", "providerSignature"], "additionalProperties": false, "properties": { - "schemaVersion": { "const": "cultmesh.session_accepted.v1" }, + "schemaVersion": { "const": "cultmesh.session_accepted.v2" }, "messageId": { "type": "string", "minLength": 1 }, "accepted": { "type": "boolean" }, "verseId": { "type": "string", "minLength": 1 }, "authorityRuntimeId": { "type": "string", "minLength": 1 }, "protocolId": { "type": "string", "minLength": 1 }, "routeGeneration": { "type": "string" }, + "clientNonce": { "type": "string" }, + "providerKeyId": { "type": "string" }, + "providerSignature": { "type": "string" }, "error": { "type": "string", "minLength": 1 } } } diff --git a/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json b/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json index 14d945ed..937f3e28 100644 --- a/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json +++ b/packages/cultnet-ts/contracts/cultmesh.session-open.schema.json @@ -3,15 +3,16 @@ "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.session-open.schema.json", "title": "CultMesh Session Open", "type": "object", - "required": ["schemaVersion", "messageId", "sourceRuntimeId", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration"], + "required": ["schemaVersion", "messageId", "sourceRuntimeId", "verseId", "authorityRuntimeId", "protocolId", "routeGeneration", "clientNonce"], "additionalProperties": false, "properties": { - "schemaVersion": { "const": "cultmesh.session_open.v1" }, + "schemaVersion": { "const": "cultmesh.session_open.v2" }, "messageId": { "type": "string", "minLength": 1 }, "sourceRuntimeId": { "type": "string", "minLength": 1 }, "verseId": { "type": "string", "minLength": 1 }, "authorityRuntimeId": { "type": "string", "minLength": 1 }, "protocolId": { "type": "string", "minLength": 1 }, - "routeGeneration": { "type": "string" } + "routeGeneration": { "type": "string" }, + "clientNonce": { "type": "string", "minLength": 1 } } } diff --git a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json index 6e7551d5..f23ef5f7 100644 --- a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json +++ b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json @@ -27,7 +27,22 @@ "endpoint": { "type": "string", "minLength": 1 }, "protocolIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, "priority": { "type": "integer", "minimum": 0 }, - "generation": { "type": "string", "minLength": 1 } + "generation": { "type": "string", "minLength": 1 }, + "certificate": { "$ref": "#/$defs/routeCertificate" } + } + }, + "routeCertificate": { + "type": "object", + "required": ["providerKeyId", "providerPublicKeyX", "providerPublicKeyY", "odinKeyId", "issuedAtUnixMilliseconds", "expiresAtUnixMilliseconds", "signature"], + "additionalProperties": false, + "properties": { + "providerKeyId": { "type": "string", "minLength": 1 }, + "providerPublicKeyX": { "type": "string", "minLength": 1 }, + "providerPublicKeyY": { "type": "string", "minLength": 1 }, + "odinKeyId": { "type": "string", "minLength": 1 }, + "issuedAtUnixMilliseconds": { "type": "integer", "minimum": 0 }, + "expiresAtUnixMilliseconds": { "type": "integer", "minimum": 1 }, + "signature": { "type": "string", "minLength": 1 } } }, "verse": { diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index 2d86ad27..c71cdcc1 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -62,8 +62,8 @@ export type CultNetSchemaVersion = | "cultnet.operation_response.v0" | "cultmesh.verse_catalog_request.v0" | "cultmesh.verse_catalog_response.v0" - | "cultmesh.session_open.v1" - | "cultmesh.session_accepted.v1"; + | "cultmesh.session_open.v2" + | "cultmesh.session_accepted.v2"; export type CultNetSchemaKind = "wire_message" | "document_payload" | "shared_contract"; export type CultNetRawPayloadEncoding = "messagepack"; @@ -365,26 +365,41 @@ export interface CultMeshAuthorityRouteMessage { protocolIds: string[]; priority: number; generation: string; + certificate?: CultMeshRouteCertificateMessage; +} + +export interface CultMeshRouteCertificateMessage { + providerKeyId: string; + providerPublicKeyX: string; + providerPublicKeyY: string; + odinKeyId: string; + issuedAtUnixMilliseconds: number; + expiresAtUnixMilliseconds: number; + signature: string; } export interface CultMeshSessionOpenMessage { - schemaVersion: "cultmesh.session_open.v1"; + schemaVersion: "cultmesh.session_open.v2"; messageId: string; sourceRuntimeId: string; verseId: string; authorityRuntimeId: string; protocolId: string; routeGeneration: string; + clientNonce: string; } export interface CultMeshSessionAcceptedMessage { - schemaVersion: "cultmesh.session_accepted.v1"; + schemaVersion: "cultmesh.session_accepted.v2"; messageId: string; accepted: boolean; verseId: string; authorityRuntimeId: string; protocolId: string; routeGeneration: string; + clientNonce: string; + providerKeyId: string; + providerSignature: string; error?: string; } @@ -1236,7 +1251,7 @@ function normalizeCultNetOptionalNulls( } } return; - case "cultmesh.session_accepted.v1": + case "cultmesh.session_accepted.v2": stripNullProperties(candidate, ["error"]); return; case "cultnet.schema_catalog_request.v0": diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index f0f01b29..8d96c69a 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -97,15 +97,18 @@ test("CultNet validates the canonical CultMesh Verse catalog messages", () => { assert.equal(response.verses[0].authorityRoutes![0].generation, "provider-route-1"); const session = parseCultNetMessage({ - schemaVersion: "cultmesh.session_accepted.v1", + schemaVersion: "cultmesh.session_accepted.v2", messageId: "session-1", accepted: true, verseId: "sample.counter", authorityRuntimeId: "sample.counter-provider", protocolId: "cultmesh.documents.v1", routeGeneration: "provider-route-1", + clientNonce: "bm9uY2U=", + providerKeyId: "provider-key-1", + providerSignature: "c2lnbmF0dXJl", }); - assert.equal(session.schemaVersion, "cultmesh.session_accepted.v1"); + assert.equal(session.schemaVersion, "cultmesh.session_accepted.v2"); assert.throws(() => parseCultNetMessage({ ...response, diff --git a/samples/eve-browser-network/Program.cs b/samples/eve-browser-network/Program.cs index db92d3c1..bfadd7db 100644 --- a/samples/eve-browser-network/Program.cs +++ b/samples/eve-browser-network/Program.cs @@ -198,6 +198,10 @@ static async Task RunHeadlessAsync(Args arguments) CreateClient = CreateClient, TransportVersion = arguments.TransportVersion }, + Sessions = new CultMeshSessionManagerOptions + { + Trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment) + }, Connectors = [ new CultMeshUriSchemaTransportConnector( diff --git a/samples/eve-browser-network/browser/main.ts b/samples/eve-browser-network/browser/main.ts index 3b0e28f9..8251a0a3 100644 --- a/samples/eve-browser-network/browser/main.ts +++ b/samples/eve-browser-network/browser/main.ts @@ -32,6 +32,7 @@ try { verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", runtimeId: "sample.chromium", + trust: { mode: "local-development" }, rendezvous: new CultMeshBrowserOdinRendezvous({ endpoints: [odinEndpoint], runtimeId: "sample.chromium.discovery", diff --git a/scripts/pack-nuget.ps1 b/scripts/pack-nuget.ps1 index d6f4a224..fa0d12bd 100644 --- a/scripts/pack-nuget.ps1 +++ b/scripts/pack-nuget.ps1 @@ -34,7 +34,7 @@ foreach ($project in $projects) { "--nologo", "--verbosity", "quiet", "-p:NoWarn=1591%3BCS8632" ) if (-not [string]::IsNullOrWhiteSpace($PackageVersion)) { - $packArguments += "-p:Version=$PackageVersion" + $packArguments += "-p:CultLibPackageVersion=$PackageVersion" } & dotnet @packArguments if ($LASTEXITCODE -ne 0) { diff --git a/src/GameCult.Mesh/CultMeshAuthorityProof.cs b/src/GameCult.Mesh/CultMeshAuthorityProof.cs new file mode 100644 index 00000000..d4b2d0c8 --- /dev/null +++ b/src/GameCult.Mesh/CultMeshAuthorityProof.cs @@ -0,0 +1,349 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using GameCult.Networking; + +namespace GameCult.Mesh +{ + public enum CultMeshAuthorityTrustMode + { + AuthenticatedRemote, + LocalDevelopment + } + + public sealed class CultMeshEcdsaP256PublicKey + { + public CultMeshEcdsaP256PublicKey(string keyId, string x, string y) + { + KeyId = Require(keyId, nameof(keyId)); + X = RequireCoordinate(x, nameof(x)); + Y = RequireCoordinate(y, nameof(y)); + } + + public string KeyId { get; } + public string X { get; } + public string Y { get; } + + internal ECDsa CreateVerifier() + { + var verifier = ECDsa.Create(); + verifier.ImportParameters(new ECParameters + { + Curve = ECCurve.NamedCurves.nistP256, + Q = new ECPoint { X = Convert.FromBase64String(X), Y = Convert.FromBase64String(Y) } + }); + return verifier; + } + + public static CultMeshEcdsaP256PublicKey From(string keyId, ECDsa key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + var parameters = key.ExportParameters(false); + return new CultMeshEcdsaP256PublicKey( + keyId, + Convert.ToBase64String(parameters.Q.X ?? throw new InvalidOperationException("P-256 key has no X coordinate.")), + Convert.ToBase64String(parameters.Q.Y ?? throw new InvalidOperationException("P-256 key has no Y coordinate."))); + } + + private static string RequireCoordinate(string value, string name) + { + var required = Require(value, name); + byte[] bytes; + try { bytes = Convert.FromBase64String(required); } + catch (FormatException error) { throw new ArgumentException("P-256 coordinates must be base64.", name, error); } + if (bytes.Length != 32) throw new ArgumentException("P-256 coordinates must contain 32 bytes.", name); + return required; + } + + private static string Require(string value, string name) => + string.IsNullOrWhiteSpace(value) ? throw new ArgumentException("Value must be non-empty.", name) : value.Trim(); + } + + public sealed class CultMeshRouteCertificate + { + public CultMeshRouteCertificate( + CultMeshEcdsaP256PublicKey providerKey, + string odinKeyId, + long issuedAtUnixMilliseconds, + long expiresAtUnixMilliseconds, + string signature) + { + ProviderKey = providerKey ?? throw new ArgumentNullException(nameof(providerKey)); + OdinKeyId = string.IsNullOrWhiteSpace(odinKeyId) + ? throw new ArgumentException("Odin key identity is required.", nameof(odinKeyId)) + : odinKeyId.Trim(); + if (issuedAtUnixMilliseconds < 0) throw new ArgumentOutOfRangeException(nameof(issuedAtUnixMilliseconds)); + if (expiresAtUnixMilliseconds <= issuedAtUnixMilliseconds) throw new ArgumentOutOfRangeException(nameof(expiresAtUnixMilliseconds)); + IssuedAtUnixMilliseconds = issuedAtUnixMilliseconds; + ExpiresAtUnixMilliseconds = expiresAtUnixMilliseconds; + Signature = signature?.Trim() ?? string.Empty; + } + + public CultMeshEcdsaP256PublicKey ProviderKey { get; } + public string OdinKeyId { get; } + public long IssuedAtUnixMilliseconds { get; } + public long ExpiresAtUnixMilliseconds { get; } + public string Signature { get; } + } + + public sealed class CultMeshAuthorityTrustPolicy + { + private readonly Dictionary _odinRoots; + + public CultMeshAuthorityTrustPolicy( + CultMeshAuthorityTrustMode mode, + IEnumerable? odinRoots = null) + { + Mode = mode; + _odinRoots = (odinRoots ?? Array.Empty()) + .ToDictionary(key => key.KeyId, StringComparer.Ordinal); + } + + public CultMeshAuthorityTrustMode Mode { get; } + public IReadOnlyCollection OdinRoots => _odinRoots.Values; + + /// Fails closed unless the route is valid under this consumer-owned policy. + public void Validate(string verseId, CultMeshAuthorityRoute route, DateTimeOffset now) => + Verify(verseId, route, now); + + internal CultMeshVerifiedAuthorityRoute Verify( + string verseId, + CultMeshAuthorityRoute route, + DateTimeOffset now) + { + if (route == null) throw new ArgumentNullException(nameof(route)); + var certificate = route.Certificate; + if (certificate == null || string.IsNullOrWhiteSpace(certificate.Signature)) + { + if (Mode == CultMeshAuthorityTrustMode.LocalDevelopment && IsLoopback(route.Endpoint)) + return CultMeshVerifiedAuthorityRoute.Local(route); + throw Failure(route, "Remote CultMesh routes require an Odin-signed authority certificate."); + } + if (!IsProtected(route.Endpoint) && + !(Mode == CultMeshAuthorityTrustMode.LocalDevelopment && IsLoopback(route.Endpoint))) + throw Failure(route, "Authenticated remote CultMesh routes require TLS or QUIC channel protection."); + if (!_odinRoots.TryGetValue(certificate.OdinKeyId, out var root)) + throw Failure(route, $"Odin key '{certificate.OdinKeyId}' is not trusted by this consumer."); + var nowMs = now.ToUnixTimeMilliseconds(); + if (nowMs < certificate.IssuedAtUnixMilliseconds || nowMs >= certificate.ExpiresAtUnixMilliseconds) + throw Failure(route, "The Odin route certificate is not currently valid."); + byte[] signature; + try { signature = Convert.FromBase64String(certificate.Signature); } + catch (FormatException error) { throw Failure(route, "The Odin route signature is not base64.", error); } + if (signature.Length != 64) throw Failure(route, "The Odin route signature is not IEEE P1363 P-256."); + using var verifier = root.CreateVerifier(); + if (!verifier.VerifyData( + CultMeshAuthorityProof.CanonicalRoute(verseId, route), + signature, + HashAlgorithmName.SHA256)) + throw Failure(route, "The Odin route certificate signature is invalid."); + return CultMeshVerifiedAuthorityRoute.Authenticated(route, certificate.ProviderKey); + } + + private static bool IsLoopback(string endpoint) + { + if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri)) return false; + return uri.IsLoopback || + string.Equals(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) || + string.Equals(uri.Host, "127.0.0.1", StringComparison.Ordinal) || + string.Equals(uri.Host, "::1", StringComparison.Ordinal); + } + + private static bool IsProtected(string endpoint) + { + if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri)) return false; + return string.Equals(uri.Scheme, "wss", StringComparison.OrdinalIgnoreCase) || + string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase) || + uri.Scheme.IndexOf("quic", StringComparison.OrdinalIgnoreCase) >= 0; + } + + private static CultMeshSessionException Failure(CultMeshAuthorityRoute route, string message, Exception? inner = null) => + new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authentication, + message, + route.Endpoint), inner); + } + + internal sealed class CultMeshVerifiedAuthorityRoute + { + private CultMeshVerifiedAuthorityRoute( + CultMeshAuthorityRoute route, + CultMeshEcdsaP256PublicKey? providerKey, + bool localDevelopment) + { + Route = route; + ProviderKey = providerKey; + IsLocalDevelopment = localDevelopment; + } + + public CultMeshAuthorityRoute Route { get; } + public CultMeshEcdsaP256PublicKey? ProviderKey { get; } + public bool IsLocalDevelopment { get; } + public static CultMeshVerifiedAuthorityRoute Authenticated(CultMeshAuthorityRoute route, CultMeshEcdsaP256PublicKey key) => new(route, key, false); + public static CultMeshVerifiedAuthorityRoute Local(CultMeshAuthorityRoute route) => new(route, null, true); + } + + public sealed class CultMeshSessionProofSigner + { + private readonly ECDsa _key; + + public CultMeshSessionProofSigner(CultMeshAuthorityRoute route, ECDsa providerPrivateKey) + { + Route = route ?? throw new ArgumentNullException(nameof(route)); + _key = providerPrivateKey ?? throw new ArgumentNullException(nameof(providerPrivateKey)); + var certificate = route.Certificate ?? throw new ArgumentException("A session proof signer requires a certified route.", nameof(route)); + ProviderKeyId = certificate.ProviderKey.KeyId; + var exported = CultMeshEcdsaP256PublicKey.From(ProviderKeyId, _key); + if (!string.Equals(exported.X, certificate.ProviderKey.X, StringComparison.Ordinal) || + !string.Equals(exported.Y, certificate.ProviderKey.Y, StringComparison.Ordinal)) + throw new ArgumentException("Provider private key does not match the certified route key.", nameof(providerPrivateKey)); + } + + public CultMeshAuthorityRoute Route { get; } + public string ProviderKeyId { get; } + + public string Sign(CultMeshSessionOpenMessage request) + { + var bytes = CultMeshAuthorityProof.CanonicalSession(request, Route.Endpoint); + return Convert.ToBase64String(CultMeshAuthorityProof.SignP256(_key, bytes)); + } + } + + public static class CultMeshAuthorityProof + { + private const string RouteDomain = "gamecult.cultmesh.route-certificate.v1"; + private const string SessionDomain = "gamecult.cultmesh.session-proof.v1"; + + public static CultMeshAuthorityRoute CreateSignedRoute( + string verseId, + string authorityRuntimeId, + string endpoint, + IEnumerable protocolIds, + int priority, + string generation, + CultMeshEcdsaP256PublicKey providerKey, + string odinKeyId, + DateTimeOffset issuedAt, + DateTimeOffset expiresAt, + ECDsa odinPrivateKey) + { + if (odinPrivateKey == null) throw new ArgumentNullException(nameof(odinPrivateKey)); + var unsignedCertificate = new CultMeshRouteCertificate( + providerKey, + odinKeyId, + issuedAt.ToUnixTimeMilliseconds(), + expiresAt.ToUnixTimeMilliseconds(), + string.Empty); + var unsignedRoute = new CultMeshAuthorityRoute( + authorityRuntimeId, endpoint, protocolIds, priority, generation, unsignedCertificate); + var signature = Convert.ToBase64String(SignP256( + odinPrivateKey, + CanonicalRoute(verseId, unsignedRoute))); + return new CultMeshAuthorityRoute( + authorityRuntimeId, + endpoint, + protocolIds, + priority, + generation, + new CultMeshRouteCertificate( + providerKey, + odinKeyId, + issuedAt.ToUnixTimeMilliseconds(), + expiresAt.ToUnixTimeMilliseconds(), + signature)); + } + + /// Verifies both the Odin route certificate and provider nonce proof. + public static bool VerifySessionProof( + CultMeshSessionOpenMessage request, + CultMeshSessionAcceptedMessage response, + string verseId, + CultMeshAuthorityRoute route, + CultMeshAuthorityTrustPolicy trust, + DateTimeOffset now) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + if (response == null) throw new ArgumentNullException(nameof(response)); + if (trust == null) throw new ArgumentNullException(nameof(trust)); + return VerifySession(request, response, trust.Verify(verseId, route, now)); + } + + internal static byte[] CanonicalRoute(string verseId, CultMeshAuthorityRoute route) + { + var certificate = route.Certificate ?? throw new InvalidOperationException("Route certificate is required."); + return Canonical( + RouteDomain, + verseId, + route.AuthorityRuntimeId, + route.Endpoint, + string.Join("\u001f", route.ProtocolIds.OrderBy(value => value, StringComparer.Ordinal)), + route.Priority.ToString(System.Globalization.CultureInfo.InvariantCulture), + route.Generation, + certificate.ProviderKey.KeyId, + certificate.ProviderKey.X, + certificate.ProviderKey.Y, + certificate.OdinKeyId, + certificate.IssuedAtUnixMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture), + certificate.ExpiresAtUnixMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + + internal static byte[] CanonicalSession(CultMeshSessionOpenMessage request, string endpoint) => Canonical( + SessionDomain, + request.ClientNonce ?? string.Empty, + request.MessageId ?? string.Empty, + request.SourceRuntimeId ?? string.Empty, + request.VerseId ?? string.Empty, + request.AuthorityRuntimeId ?? string.Empty, + request.ProtocolId ?? string.Empty, + endpoint ?? string.Empty, + request.RouteGeneration ?? string.Empty); + + internal static bool VerifySession( + CultMeshSessionOpenMessage request, + CultMeshSessionAcceptedMessage response, + CultMeshVerifiedAuthorityRoute route) + { + if (route.IsLocalDevelopment) return true; + if (route.ProviderKey == null || + !string.Equals(response.ProviderKeyId, route.ProviderKey.KeyId, StringComparison.Ordinal) || + !string.Equals(response.ClientNonce, request.ClientNonce, StringComparison.Ordinal) || + string.IsNullOrWhiteSpace(response.ProviderSignature)) + return false; + byte[] signature; + try { signature = Convert.FromBase64String(response.ProviderSignature); } + catch (FormatException) { return false; } + if (signature.Length != 64) return false; + using var verifier = route.ProviderKey.CreateVerifier(); + return verifier.VerifyData( + CanonicalSession(request, route.Route.Endpoint), + signature, + HashAlgorithmName.SHA256); + } + + internal static byte[] SignP256(ECDsa key, byte[] payload) + { + var signature = key.SignData(payload, HashAlgorithmName.SHA256); + if (signature.Length != 64) + throw new CryptographicException("ECDSA provider did not emit IEEE P1363 P-256 signature bytes."); + return signature; + } + + private static byte[] Canonical(params string[] values) + { + using var stream = new MemoryStream(); + var length = new byte[4]; + foreach (var value in values) + { + var bytes = Encoding.UTF8.GetBytes(value ?? string.Empty); + BinaryPrimitives.WriteInt32BigEndian(length, bytes.Length); + stream.Write(length, 0, length.Length); + stream.Write(bytes, 0, bytes.Length); + } + return stream.ToArray(); + } + } +} diff --git a/src/GameCult.Mesh/CultMeshContentSessions.cs b/src/GameCult.Mesh/CultMeshContentSessions.cs index 4c562450..890dbfa8 100644 --- a/src/GameCult.Mesh/CultMeshContentSessions.cs +++ b/src/GameCult.Mesh/CultMeshContentSessions.cs @@ -136,7 +136,9 @@ namespace GameCult.Mesh "cultmesh-content-client", target, CultMeshProtocols.Content, - candidate.Generation, + candidate, + null, + _options.Clock.UtcNow, _options.ResponseTimeout, cancellationToken).ConfigureAwait(false); return new LegacyRudpContentTransport( diff --git a/src/GameCult.Mesh/CultMeshSessionIdentity.cs b/src/GameCult.Mesh/CultMeshSessionIdentity.cs index bb17a3d8..43a49162 100644 --- a/src/GameCult.Mesh/CultMeshSessionIdentity.cs +++ b/src/GameCult.Mesh/CultMeshSessionIdentity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using GameCult.Networking; @@ -68,6 +69,7 @@ namespace GameCult.Mesh private readonly HashSet _verseIds; private readonly HashSet _protocolIds; private readonly HashSet _routeGenerations; + private readonly Dictionary _proofSigners; private readonly Func _handler; private bool _disposed; @@ -76,13 +78,16 @@ namespace GameCult.Mesh string authorityRuntimeId, IEnumerable verseIds, IEnumerable protocolIds, - IEnumerable routeGenerations) + IEnumerable routeGenerations, + IEnumerable? proofSigners = null) { _server = server ?? throw new ArgumentNullException(nameof(server)); _authorityRuntimeId = Require(authorityRuntimeId, nameof(authorityRuntimeId)); _verseIds = Clean(verseIds, nameof(verseIds)); _protocolIds = Clean(protocolIds, nameof(protocolIds)); _routeGenerations = Clean(routeGenerations, nameof(routeGenerations)); + _proofSigners = (proofSigners ?? Array.Empty()) + .ToDictionary(signer => signer.Route.Generation, StringComparer.Ordinal); _handler = HandleAsync; _server.OnCultNet(_handler); } @@ -97,6 +102,7 @@ namespace GameCult.Mesh private Task HandleAsync(CultMeshSessionOpenMessage request, ICultNetSchemaServerPeer peer) { var error = Validate(request); + _proofSigners.TryGetValue(request.RouteGeneration ?? string.Empty, out var signer); peer.SendCultNet(new CultMeshSessionAcceptedMessage { MessageId = request.MessageId ?? string.Empty, @@ -105,6 +111,9 @@ namespace GameCult.Mesh AuthorityRuntimeId = _authorityRuntimeId, ProtocolId = request.ProtocolId ?? string.Empty, RouteGeneration = request.RouteGeneration ?? string.Empty, + ClientNonce = request.ClientNonce ?? string.Empty, + ProviderKeyId = error == null ? signer?.ProviderKeyId ?? string.Empty : string.Empty, + ProviderSignature = error == null && signer != null ? signer.Sign(request) : string.Empty, Error = error }); return Task.CompletedTask; @@ -114,6 +123,7 @@ namespace GameCult.Mesh { if (string.IsNullOrWhiteSpace(request.MessageId)) return "session-message-id-required"; if (string.IsNullOrWhiteSpace(request.SourceRuntimeId)) return "source-runtime-id-required"; + if (!TryNonce(request.ClientNonce)) return "client-nonce-invalid"; if (!string.Equals(request.AuthorityRuntimeId, _authorityRuntimeId, StringComparison.Ordinal)) return "target-runtime-mismatch"; if (!_verseIds.Contains(request.VerseId ?? string.Empty)) return "verse-not-served"; @@ -122,6 +132,13 @@ namespace GameCult.Mesh return null; } + private static bool TryNonce(string? value) + { + if (string.IsNullOrWhiteSpace(value)) return false; + try { return Convert.FromBase64String(value).Length == 32; } + catch (FormatException) { return false; } + } + private static HashSet Clean(IEnumerable values, string name) { if (values == null) throw new ArgumentNullException(name); @@ -145,26 +162,45 @@ namespace GameCult.Mesh string sourceRuntimeId, CultMeshSessionTarget target, CultMeshProtocolId protocol, - string routeGeneration, + CultMeshTransportCandidate candidate, + CultMeshAuthorityTrustPolicy? trust, + DateTimeOffset now, TimeSpan timeout, CancellationToken cancellationToken = default) { if (client == null) throw new ArgumentNullException(nameof(client)); + if (candidate == null) throw new ArgumentNullException(nameof(candidate)); + var route = candidate.AuthorityRoute ?? new CultMeshAuthorityRoute( + target.AuthorityRuntimeId, + candidate.Endpoint, + new[] { protocol.Value }, + candidate.Priority, + candidate.Generation); + var verifiedRoute = candidate.VerifiedAuthority ?? + (trust ?? throw new CultMeshSessionException(new CultMeshSessionFailure( + CultMeshSessionFailureReason.Authentication, + "No consumer trust policy verified this CultMesh route.", + candidate.Endpoint))).Verify(target.VerseId, route, now); if (client is ICultMeshVerifiedSchemaClient verified) { if (!verified.IsVerifiedFor( target.VerseId, target.AuthorityRuntimeId, protocol.Value, - routeGeneration ?? string.Empty)) + candidate.Generation)) { throw new CultMeshSessionException(new CultMeshSessionFailure( CultMeshSessionFailureReason.Authority, $"Native transport identity did not match CultMesh target '{target}' for '{protocol}'.")); } - return; + // Native identity is sufficient only for an explicitly selected + // loopback development route. Remote schema sessions still prove + // possession of the provider key certified by Odin. + if (verifiedRoute.IsLocalDevelopment) return; } var messageId = Guid.NewGuid().ToString("N"); + var nonce = new byte[32]; + using (var random = RandomNumberGenerator.Create()) random.GetBytes(nonce); var completion = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); client.OnCultNet(response => @@ -172,15 +208,17 @@ namespace GameCult.Mesh if (string.Equals(response.MessageId, messageId, StringComparison.Ordinal)) completion.TrySetResult(response); }); - client.SendCultNet(new CultMeshSessionOpenMessage + var request = new CultMeshSessionOpenMessage { MessageId = messageId, SourceRuntimeId = sourceRuntimeId, VerseId = target.VerseId, AuthorityRuntimeId = target.AuthorityRuntimeId, ProtocolId = protocol.Value, - RouteGeneration = routeGeneration ?? string.Empty - }); + RouteGeneration = candidate.Generation, + ClientNonce = Convert.ToBase64String(nonce) + }; + client.SendCultNet(request); using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); deadline.CancelAfter(timeout); @@ -201,7 +239,8 @@ namespace GameCult.Mesh !string.Equals(accepted.VerseId, target.VerseId, StringComparison.Ordinal) || !string.Equals(accepted.AuthorityRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal) || !string.Equals(accepted.ProtocolId, protocol.Value, StringComparison.Ordinal) || - !string.Equals(accepted.RouteGeneration, routeGeneration ?? string.Empty, StringComparison.Ordinal)) + !string.Equals(accepted.RouteGeneration, candidate.Generation, StringComparison.Ordinal) || + !CultMeshAuthorityProof.VerifySession(request, accepted, verifiedRoute)) { throw new CultMeshSessionException(new CultMeshSessionFailure( CultMeshSessionFailureReason.Authority, diff --git a/src/GameCult.Mesh/CultMeshSessions.cs b/src/GameCult.Mesh/CultMeshSessions.cs index 7db6f06b..b0dace01 100644 --- a/src/GameCult.Mesh/CultMeshSessions.cs +++ b/src/GameCult.Mesh/CultMeshSessions.cs @@ -86,19 +86,28 @@ namespace GameCult.Mesh CultMeshTransportPathKind pathKind = CultMeshTransportPathKind.Direct, int priority = 0, string authorityRuntimeId = "", - string generation = "") + string generation = "", + CultMeshAuthorityRoute? authorityRoute = null) { Endpoint = string.IsNullOrWhiteSpace(endpoint) ? throw new ArgumentException("Physical endpoint is required.", nameof(endpoint)) : endpoint; PathKind = pathKind; Priority = priority; AuthorityRuntimeId = authorityRuntimeId?.Trim() ?? string.Empty; Generation = generation?.Trim() ?? string.Empty; + AuthorityRoute = authorityRoute; } public string Endpoint { get; } public CultMeshTransportPathKind PathKind { get; } public int Priority { get; } public string AuthorityRuntimeId { get; } public string Generation { get; } + public CultMeshAuthorityRoute? AuthorityRoute { get; } + internal CultMeshVerifiedAuthorityRoute? VerifiedAuthority { get; private set; } + internal CultMeshTransportCandidate WithVerifiedAuthority(CultMeshVerifiedAuthorityRoute verified) + { + VerifiedAuthority = verified ?? throw new ArgumentNullException(nameof(verified)); + return this; + } } public enum CultMeshSessionStatus { Connecting, Online, Degraded, Reconnecting, Offline } @@ -534,6 +543,9 @@ namespace GameCult.Mesh public int MaxRacedCandidates { get; set; } = 2; public string RuntimeId { get; set; } = "cultmesh-client"; public TimeSpan IdentityHandshakeTimeout { get; set; } = TimeSpan.FromSeconds(5); + /// Gets or sets the consumer-owned trust roots and explicit local-development policy. + public CultMeshAuthorityTrustPolicy Trust { get; set; } = new( + CultMeshAuthorityTrustMode.AuthenticatedRemote); } public sealed class CultMeshSessionManager : IDisposable @@ -1039,7 +1051,9 @@ namespace GameCult.Mesh _options.RuntimeId, target, protocol, - route.Candidate.Generation, + route.Candidate, + _options.Trust, + _options.Clock.UtcNow, _options.IdentityHandshakeTimeout).ConfigureAwait(false); return new ConnectedPath(route.Candidate, client); } @@ -1065,7 +1079,7 @@ namespace GameCult.Mesh private CultMeshSessionException Failure(CultMeshSessionFailureReason reason, string message, Exception? inner = null) => new CultMeshSessionException(new CultMeshSessionFailure(reason, message), inner); - private static CultMeshTransportCandidate[] BoundCandidates( + private CultMeshTransportCandidate[] BoundCandidates( CultMeshDiscoveryState discovery, CultMeshSessionTarget target, CultMeshProtocolId protocol) @@ -1080,10 +1094,15 @@ namespace GameCult.Mesh .GroupBy(route => route.Endpoint, StringComparer.Ordinal) .Select(group => group.OrderBy(route => route.Priority).First()) .Select(route => new CultMeshTransportCandidate( - route.Endpoint, - priority: route.Priority, - authorityRuntimeId: route.AuthorityRuntimeId, - generation: route.Generation)) + route.Endpoint, + priority: route.Priority, + authorityRuntimeId: route.AuthorityRuntimeId, + generation: route.Generation, + authorityRoute: route) + .WithVerifiedAuthority(_options.Trust.Verify( + target.VerseId, + route, + _options.Clock.UtcNow))) .OrderBy(candidate => candidate.Priority) .ThenBy(candidate => candidate.Endpoint, StringComparer.Ordinal) .ToArray(); diff --git a/src/GameCult.Mesh/CultMeshTcpContentTransport.cs b/src/GameCult.Mesh/CultMeshTcpContentTransport.cs index f47f08bd..dcb07feb 100644 --- a/src/GameCult.Mesh/CultMeshTcpContentTransport.cs +++ b/src/GameCult.Mesh/CultMeshTcpContentTransport.cs @@ -17,6 +17,7 @@ namespace GameCult.Mesh public string VerseId { get; set; } = string.Empty; public string AuthorityRuntimeId { get; set; } = string.Empty; public string RouteGeneration { get; set; } = string.Empty; + public CultMeshSessionProofSigner? ProofSigner { get; set; } internal void Validate() { @@ -344,7 +345,10 @@ namespace GameCult.Mesh var requestBytes = await CultMeshTcpContentWire.ReadFrameAsync(stream, cancellationToken).ConfigureAwait(false); var request = CultNetSchemaMessageSerialization.Deserialize(requestBytes) as CultMeshSessionOpenMessage ?? throw new InvalidDataException("TCP content session did not begin with an authority handshake."); - var accepted = string.Equals(request.VerseId, _options.VerseId, StringComparison.Ordinal) && + var accepted = !string.IsNullOrWhiteSpace(request.MessageId) && + !string.IsNullOrWhiteSpace(request.SourceRuntimeId) && + IsNonce(request.ClientNonce) && + string.Equals(request.VerseId, _options.VerseId, StringComparison.Ordinal) && string.Equals(request.AuthorityRuntimeId, _options.AuthorityRuntimeId, StringComparison.Ordinal) && string.Equals(request.ProtocolId, CultMeshProtocols.Content.Value, StringComparison.Ordinal) && string.Equals(request.RouteGeneration, _options.RouteGeneration, StringComparison.Ordinal); @@ -356,6 +360,11 @@ namespace GameCult.Mesh AuthorityRuntimeId = _options.AuthorityRuntimeId, ProtocolId = CultMeshProtocols.Content.Value, RouteGeneration = _options.RouteGeneration, + ClientNonce = request.ClientNonce ?? string.Empty, + ProviderKeyId = accepted ? _options.ProofSigner?.ProviderKeyId ?? string.Empty : string.Empty, + ProviderSignature = accepted && _options.ProofSigner != null + ? _options.ProofSigner.Sign(request) + : string.Empty, Error = accepted ? null : "Content session target does not match this authority route." }; await CultMeshTcpContentWire.WriteFrameAsync( @@ -365,6 +374,13 @@ namespace GameCult.Mesh return accepted; } + private static bool IsNonce(string? value) + { + if (string.IsNullOrWhiteSpace(value)) return false; + try { return Convert.FromBase64String(value).Length == 32; } + catch (FormatException) { return false; } + } + private async Task SendChunkAsync( Stream stream, CultMeshContentChunkRequestMessage request, @@ -473,7 +489,8 @@ namespace GameCult.Mesh VerseId = target.VerseId, AuthorityRuntimeId = target.AuthorityRuntimeId, ProtocolId = CultMeshProtocols.Content.Value, - RouteGeneration = candidate.Generation + RouteGeneration = candidate.Generation, + ClientNonce = CreateNonce() }; await WriteFrameAsync(stream, CultNetSchemaMessageSerialization.Serialize(request), cancellationToken) .ConfigureAwait(false); @@ -485,13 +502,22 @@ namespace GameCult.Mesh !string.Equals(response.VerseId, target.VerseId, StringComparison.Ordinal) || !string.Equals(response.AuthorityRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal) || !string.Equals(response.ProtocolId, CultMeshProtocols.Content.Value, StringComparison.Ordinal) || - !string.Equals(response.RouteGeneration, candidate.Generation, StringComparison.Ordinal)) + !string.Equals(response.RouteGeneration, candidate.Generation, StringComparison.Ordinal) || + candidate.VerifiedAuthority == null || + !CultMeshAuthorityProof.VerifySession(request, response, candidate.VerifiedAuthority)) throw new CultMeshSessionException(new CultMeshSessionFailure( CultMeshSessionFailureReason.Authority, response.Error ?? "TCP content endpoint did not prove the selected authority route.", candidate.Endpoint)); } + private static string CreateNonce() + { + var nonce = new byte[32]; + using (var random = System.Security.Cryptography.RandomNumberGenerator.Create()) random.GetBytes(nonce); + return Convert.ToBase64String(nonce); + } + public static async Task ReadFrameAsync(Stream stream, CancellationToken cancellationToken) { var header = new byte[HeaderBytes]; diff --git a/src/GameCult.Mesh/CultMeshVerse.cs b/src/GameCult.Mesh/CultMeshVerse.cs index 55d6deeb..003dc161 100644 --- a/src/GameCult.Mesh/CultMeshVerse.cs +++ b/src/GameCult.Mesh/CultMeshVerse.cs @@ -102,7 +102,8 @@ namespace GameCult.Mesh string endpoint, IEnumerable? protocolIds = null, int priority = 0, - string? generation = null) + string? generation = null, + CultMeshRouteCertificate? certificate = null) { AuthorityRuntimeId = RequireNonEmpty(authorityRuntimeId, nameof(authorityRuntimeId)); Endpoint = RequireNonEmpty(endpoint, nameof(endpoint)); @@ -112,6 +113,7 @@ namespace GameCult.Mesh Generation = string.IsNullOrWhiteSpace(generation) ? AuthorityRuntimeId + "@" + Endpoint : generation!.Trim(); + Certificate = certificate; } public string AuthorityRuntimeId { get; } @@ -119,6 +121,8 @@ namespace GameCult.Mesh public IReadOnlyList ProtocolIds { get; } public int Priority { get; } public string Generation { get; } + /// Gets the optional Odin-signed binding from this route to its provider key. + public CultMeshRouteCertificate? Certificate { get; } public bool Supports(CultMeshProtocolId protocol) => protocol != null && diff --git a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs index 774a05f9..c4ee15c7 100644 --- a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs +++ b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs @@ -39,7 +39,17 @@ namespace GameCult.Mesh Endpoint = route.Endpoint, ProtocolIds = route.ProtocolIds.ToArray(), Priority = route.Priority, - Generation = route.Generation + Generation = route.Generation, + Certificate = route.Certificate == null ? null : new CultMeshRouteCertificateMessage + { + ProviderKeyId = route.Certificate.ProviderKey.KeyId, + ProviderPublicKeyX = route.Certificate.ProviderKey.X, + ProviderPublicKeyY = route.Certificate.ProviderKey.Y, + OdinKeyId = route.Certificate.OdinKeyId, + IssuedAtUnixMilliseconds = route.Certificate.IssuedAtUnixMilliseconds, + ExpiresAtUnixMilliseconds = route.Certificate.ExpiresAtUnixMilliseconds, + Signature = route.Certificate.Signature + } }).ToArray(), ParentVerseId = verse.ParentVerseId, Description = verse.Description @@ -63,7 +73,16 @@ namespace GameCult.Mesh route.Endpoint, route.ProtocolIds, route.Priority, - route.Generation)).ToArray(); + route.Generation, + route.Certificate == null ? null : new CultMeshRouteCertificate( + new CultMeshEcdsaP256PublicKey( + route.Certificate.ProviderKeyId, + route.Certificate.ProviderPublicKeyX, + route.Certificate.ProviderPublicKeyY), + route.Certificate.OdinKeyId, + route.Certificate.IssuedAtUnixMilliseconds, + route.Certificate.ExpiresAtUnixMilliseconds, + route.Certificate.Signature))).ToArray(); return new CultMeshVerseDescriptor( message.VerseId, message.DisplayName, diff --git a/src/GameCult.Mesh/docs/browser-verse-transport.md b/src/GameCult.Mesh/docs/browser-verse-transport.md index ecec20ef..fcfde998 100644 --- a/src/GameCult.Mesh/docs/browser-verse-transport.md +++ b/src/GameCult.Mesh/docs/browser-verse-transport.md @@ -8,7 +8,8 @@ This is the implementation map for the browser Verse boundary. `samples/eve-two-runtime` remains the clean package-consumer/jsdom checkpoint. `samples/eve-browser-network` runs a durable C# provider, a real Chromium Eve lowerer, an independent C# headless observer, and a separate local Odin fixture -over authenticated binary CultNet WebSocket routes. It proves one canonical +over binary loopback CultNet WebSocket routes under an explicit +`local-development` trust policy. It proves one canonical command effect, idempotent retry, shared observation, provider restart on a new physical route, browser rediscovery, retained lease resubscription, and a second canonical command after reconnection. @@ -29,6 +30,10 @@ not a complete public Verse proof: `cultmesh.verse_catalog_request.v0` and `cultmesh.verse_catalog_response.v0`; application callbacks no longer need to smuggle provider URLs into the browser client; +- `CultMeshBrowserClient` owns the final authority gate: remote use requires a + consumer-pinned Odin P-256 root, an Odin-signed route certificate, `wss://`, + and provider possession proof over a fresh nonce. A catalog source cannot + bypass that gate by returning a route-shaped object; - Kotlin has a channel-aware WebSocket transport adapter, TypeScript has the browser client, and `GameCult.Networking.WebSockets` provides the bounded C# schema server host adapter; @@ -58,9 +63,9 @@ Owner: Inputs: -- an Odin-discovered WebSocket transport candidate; +- an Odin-discovered route certificate and consumer-configured Odin root; - stable Verse and provider identities; -- authenticated session evidence; +- provider key possession over a fresh client nonce; - requested schema ids and record keys; - typed operation invocations and idempotency keys; - canonical CultNet schema-v0 MessagePack messages. diff --git a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md index ee505acb..28cc06ad 100644 --- a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md +++ b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md @@ -9,9 +9,10 @@ selection owner then resolves the provider runtime from Odin's descriptor before opening the session. Discovery owns the current physical routes, and the session manager owns connection reuse, path rotation, and transport failure state. Each Odin route binds that authority runtime to one endpoint, protocol set, and -generation. The client verifies the same tuple with the connected peer before -the session becomes online; runtime membership and endpoint reachability are -not independent routing evidence. +generation. For a remote Verse, Odin signs that exact tuple and the provider's +P-256 public key. The client pins an Odin root from its own configuration, then +requires the connected provider to sign a fresh client nonce. A route cannot +become usable by merely repeating the expected Verse and runtime strings. ```csharp using GameCult.Mesh; @@ -22,9 +23,22 @@ const string verseId = "sample.counter"; var target = new CultMeshSessionTarget(verseId, "sample.counter-daemon"); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); var cancellationToken = timeout.Token; +var odinRoot = new CultMeshEcdsaP256PublicKey( + Environment.GetEnvironmentVariable("ODIN_ROOT_KEY_ID") + ?? throw new InvalidOperationException("Set ODIN_ROOT_KEY_ID."), + Environment.GetEnvironmentVariable("ODIN_ROOT_P256_X") + ?? throw new InvalidOperationException("Set ODIN_ROOT_P256_X."), + Environment.GetEnvironmentVariable("ODIN_ROOT_P256_Y") + ?? throw new InvalidOperationException("Set ODIN_ROOT_P256_Y.")); using var mesh = new CultMeshClient(new CultMeshClientOptions { - RendezvousEndpoints = new[] { odinEndpoint } + RendezvousEndpoints = new[] { odinEndpoint }, + Sessions = new CultMeshSessionManagerOptions + { + Trust = new CultMeshAuthorityTrustPolicy( + CultMeshAuthorityTrustMode.AuthenticatedRemote, + new[] { odinRoot }) + } }); using var counterLease = await mesh.LeaseDocumentAsync( @@ -64,6 +78,21 @@ using var stateWatch = session.WatchState().Subscribe(state => Application code does not reconnect, sleep, rank endpoints, or replace the session. Those decisions belong to CultMesh. +For a deliberately local, moddable Verse, select the exception explicitly and +use only in-process or loopback routes: + +```csharp +Sessions = new CultMeshSessionManagerOptions +{ + Trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment) +} +``` + +`LocalDevelopment` never accepts an unsigned non-loopback route. Verse names, +provider names, and Odin wire fields cannot turn this exception on. Remote +routes require a trusted Odin signature, provider nonce proof, certificate +validity, and a protected channel. + Fast session-management check (this uses controlled transports; it is not the network proof): @@ -71,7 +100,7 @@ network proof): dotnet test tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj --filter FullyQualifiedName~CultMeshSessionManagerTests ``` -The real checkpoint in chapter 4 additionally advertises a better-priority +The local checkpoint in chapter 4 additionally advertises a better-priority endpoint for the wrong authority runtime. Both the C# and browser clients must ignore it, verify the intended peer, survive route replacement, and reject wrong-source operation responses. diff --git a/src/GameCult.Mesh/docs/transport-planes.md b/src/GameCult.Mesh/docs/transport-planes.md index bd42a071..fea2b69b 100644 --- a/src/GameCult.Mesh/docs/transport-planes.md +++ b/src/GameCult.Mesh/docs/transport-planes.md @@ -64,13 +64,20 @@ operation. transport diagnostics. None of these grant provider authority or become content identity. -Before a schema, content, or realtime session becomes online, the transport -must prove that the connected peer matches the selected Verse, authority -runtime, protocol, and route generation. TCP schema and content lanes use the -portable CultNet session-open handshake. QUIC uses the exact Odin-bound route -plus its TLS endpoint/certificate proof. A connector that returns bytes without -authority proof is rejected; racing every endpoint in a Verse descriptor is -not route selection. +Before a schema, content, or realtime session becomes online, consumer-owned +trust verifies an Odin-signed route certificate binding Verse, authority +runtime, protocol set, endpoint, generation, validity interval, and provider +P-256 key. Schema and content lanes then require a fresh client nonce signed by +that provider key. QUIC uses the signed endpoint plus its TLS +endpoint/certificate proof. A connector that returns bytes without this chain +is rejected; racing every endpoint in a Verse descriptor is not route +selection. Unsigned traffic exists only under an explicitly selected +`LocalDevelopment` policy and only for in-process or loopback routes. + +Route and session signatures use ECDSA P-256 with SHA-256 and fixed-width IEEE +P1363 `r || s` encoding, matching browser WebCrypto. Their canonical +transcripts are ordered UTF-8 fields prefixed by four-byte big-endian lengths; +serializer bytes are never signature authority. **Forbidden writers:** applications and renderers do not choose unadvertised endpoints, implement reconnect loops, or bypass verification. Schema messages @@ -94,7 +101,8 @@ new seam does not pretend that migration has already occurred. ## Security and release gate The current TCP control and content connectors are plaintext and therefore -restricted to local or otherwise trusted deployment. Adding TLS must not move +restricted to explicit loopback development. They cannot carry an authenticated +remote session. Adding TLS must not move discovery, authorization, content verification, or application semantics into the TCP adapter. diff --git a/src/GameCult.Networking/CultNetSchemaMessages.cs b/src/GameCult.Networking/CultNetSchemaMessages.cs index 2117693d..56370207 100644 --- a/src/GameCult.Networking/CultNetSchemaMessages.cs +++ b/src/GameCult.Networking/CultNetSchemaMessages.cs @@ -136,9 +136,9 @@ namespace GameCult.Networking /// public const string VerseCatalogResponse = "cultmesh.verse_catalog_response.v0"; /// CultMesh session identity request contract identifier. - public const string SessionOpen = "cultmesh.session_open.v1"; + public const string SessionOpen = "cultmesh.session_open.v2"; /// CultMesh verified session identity response contract identifier. - public const string SessionAccepted = "cultmesh.session_accepted.v1"; + public const string SessionAccepted = "cultmesh.session_accepted.v2"; /// /// CultMesh peer exchange request contract identifier. /// @@ -1194,6 +1194,7 @@ namespace GameCult.Networking [Key("authorityRuntimeId")] public string AuthorityRuntimeId { get; set; } = string.Empty; [Key("protocolId")] public string ProtocolId { get; set; } = string.Empty; [Key("routeGeneration")] public string RouteGeneration { get; set; } = string.Empty; + [Key("clientNonce")] public string ClientNonce { get; set; } = string.Empty; } /// Accepts or rejects a CultMesh session with the server's actual identity. @@ -1207,6 +1208,9 @@ namespace GameCult.Networking [Key("authorityRuntimeId")] public string AuthorityRuntimeId { get; set; } = string.Empty; [Key("protocolId")] public string ProtocolId { get; set; } = string.Empty; [Key("routeGeneration")] public string RouteGeneration { get; set; } = string.Empty; + [Key("clientNonce")] public string ClientNonce { get; set; } = string.Empty; + [Key("providerKeyId")] public string ProviderKeyId { get; set; } = string.Empty; + [Key("providerSignature")] public string ProviderSignature { get; set; } = string.Empty; [Key("error")] public string? Error { get; set; } } @@ -1308,6 +1312,19 @@ namespace GameCult.Networking [Key("protocolIds")] public string[] ProtocolIds { get; set; } = Array.Empty(); [Key("priority")] public int Priority { get; set; } [Key("generation")] public string Generation { get; set; } = string.Empty; + [Key("certificate")] public CultMeshRouteCertificateMessage? Certificate { get; set; } + } + + [MessagePackObject] + public class CultMeshRouteCertificateMessage + { + [Key("providerKeyId")] public string ProviderKeyId { get; set; } = string.Empty; + [Key("providerPublicKeyX")] public string ProviderPublicKeyX { get; set; } = string.Empty; + [Key("providerPublicKeyY")] public string ProviderPublicKeyY { get; set; } = string.Empty; + [Key("odinKeyId")] public string OdinKeyId { get; set; } = string.Empty; + [Key("issuedAtUnixMilliseconds")] public long IssuedAtUnixMilliseconds { get; set; } + [Key("expiresAtUnixMilliseconds")] public long ExpiresAtUnixMilliseconds { get; set; } + [Key("signature")] public string Signature { get; set; } = string.Empty; } /// diff --git a/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs new file mode 100644 index 00000000..88c7e803 --- /dev/null +++ b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using GameCult.Networking; +using NUnit.Framework; + +namespace GameCult.Mesh.Tests; + +public sealed class CultMeshAuthorityProofTests +{ + private static readonly DateTimeOffset Now = DateTimeOffset.FromUnixTimeMilliseconds(1_800_000_000_000); + + [Test] + public void SignedRouteBindsEveryAuthorityFieldToConsumerTrust() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var route = SignedRoute(odin, provider); + var trust = Trust(odin); + + trust.Validate("aetheria", route, Now); + var wireRoundTrip = new CultMeshVerseDescriptor( + "aetheria", + "Aetheria", + CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + authorityRoutes: new[] { route }) + .ToMessage() + .ToVerseDescriptor() + .AuthorityRoutes[0]; + trust.Validate("aetheria", wireRoundTrip, Now); + wireRoundTrip.Certificate!.ProviderKey.KeyId.Should().Be("provider-1"); + + Action mutatedEndpoint = () => trust.Validate("aetheria", new CultMeshAuthorityRoute( + route.AuthorityRuntimeId, + "wss://evil.example/mesh", + route.ProtocolIds, + route.Priority, + route.Generation, + route.Certificate), Now); + mutatedEndpoint.Should().Throw() + .Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authentication); + } + + [Test] + public void RemoteTrustRejectsUnknownExpiredAndUnsignedRoutes() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var stranger = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var route = SignedRoute(odin, provider); + + Action unknownRoot = () => Trust(stranger).Validate("aetheria", route, Now); + Action expired = () => Trust(odin).Validate("aetheria", route, Now.AddHours(2)); + Action unsigned = () => Trust(odin).Validate("aetheria", new CultMeshAuthorityRoute( + "aetheria-daemon", "wss://provider.example/mesh", + new[] { CultMeshProtocols.Documents.Value }, generation: "generation-1"), Now); + + unknownRoot.Should().Throw(); + expired.Should().Throw(); + unsigned.Should().Throw(); + } + + [Test] + public void UnsignedRouteRequiresExplicitLoopbackDevelopmentPolicy() + { + var local = new CultMeshAuthorityRoute( + "aetheria-daemon", "rudp://127.0.0.1:3076", + new[] { CultMeshProtocols.Documents.Value }, generation: "local"); + var remote = new CultMeshAuthorityRoute( + "aetheria-daemon", "rudp://192.0.2.10:3076", + new[] { CultMeshProtocols.Documents.Value }, generation: "remote"); + var policy = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment); + + policy.Validate("aetheria", local, Now); + Action remoteAttempt = () => policy.Validate("aetheria", remote, Now); + remoteAttempt.Should().Throw(); + } + + [Test] + public void ProviderProofRejectsCredentialFreeEchoAndNonceReplay() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var route = SignedRoute(odin, provider); + var trust = Trust(odin); + var signer = new CultMeshSessionProofSigner(route, provider); + var request = Request("nonce-a"); + var signed = Accepted(request, signer.Sign(request)); + + CultMeshAuthorityProof.VerifySessionProof(request, signed, "aetheria", route, trust, Now).Should().BeTrue(); + + var echo = Accepted(request, string.Empty); + CultMeshAuthorityProof.VerifySessionProof(request, echo, "aetheria", route, trust, Now).Should().BeFalse(); + + var replayedRequest = Request("nonce-b"); + CultMeshAuthorityProof.VerifySessionProof(replayedRequest, signed, "aetheria", route, trust, Now).Should().BeFalse(); + } + + [Test] + public async Task SessionManagerRejectsPeerThatOnlyEchoesCertifiedIdentityStrings() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var now = DateTimeOffset.UtcNow; + var route = CultMeshAuthorityProof.CreateSignedRoute( + "aetheria", "aetheria-daemon", "wss://provider.example/mesh", + new[] { CultMeshProtocols.Documents.Value }, 0, "generation-live", + CultMeshEcdsaP256PublicKey.From("provider-live", provider), "odin-live", + now.AddMinutes(-1), now.AddMinutes(5), odin); + var descriptor = new CultMeshVerseDescriptor( + "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + authorityRoutes: new[] { route }); + using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); + using var manager = new CultMeshSessionManager( + discovery, + new[] { new EchoConnector() }, + new CultMeshSessionManagerOptions + { + Trust = new CultMeshAuthorityTrustPolicy( + CultMeshAuthorityTrustMode.AuthenticatedRemote, + new[] { CultMeshEcdsaP256PublicKey.From("odin-live", odin) }) + }); + + Func connect = async () => await manager.ConnectAsync( + new CultMeshSessionTarget("aetheria", "aetheria-daemon"), + CultMeshProtocols.Documents); + + (await connect.Should().ThrowAsync()) + .Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); + } + + private static CultMeshAuthorityRoute SignedRoute(ECDsa odin, ECDsa provider) => + CultMeshAuthorityProof.CreateSignedRoute( + "aetheria", + "aetheria-daemon", + "wss://provider.example/mesh", + new[] { CultMeshProtocols.Documents.Value }, + 0, + "generation-1", + CultMeshEcdsaP256PublicKey.From("provider-1", provider), + "odin-root-1", + Now.AddMinutes(-1), + Now.AddHours(1), + odin); + + private static CultMeshAuthorityTrustPolicy Trust(ECDsa odin) => new( + CultMeshAuthorityTrustMode.AuthenticatedRemote, + new[] { CultMeshEcdsaP256PublicKey.From("odin-root-1", odin) }); + + private static GameCult.Networking.CultMeshSessionOpenMessage Request(string nonceLabel) + { + using var hash = SHA256.Create(); + return new GameCult.Networking.CultMeshSessionOpenMessage + { + MessageId = "message-1", + SourceRuntimeId = "browser-1", + VerseId = "aetheria", + AuthorityRuntimeId = "aetheria-daemon", + ProtocolId = CultMeshProtocols.Documents.Value, + RouteGeneration = "generation-1", + ClientNonce = Convert.ToBase64String(hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(nonceLabel))) + }; + } + + private static GameCult.Networking.CultMeshSessionAcceptedMessage Accepted( + GameCult.Networking.CultMeshSessionOpenMessage request, + string signature) => new() + { + MessageId = request.MessageId, + Accepted = true, + VerseId = request.VerseId, + AuthorityRuntimeId = request.AuthorityRuntimeId, + ProtocolId = request.ProtocolId, + RouteGeneration = request.RouteGeneration, + ClientNonce = request.ClientNonce, + ProviderKeyId = "provider-1", + ProviderSignature = signature + }; + + private sealed class SignedRouteSource : ICultMeshLookupSource + { + private readonly CultMeshVerseDescriptor _descriptor; + public SignedRouteSource(CultMeshVerseDescriptor descriptor) => _descriptor = descriptor; + public string SourceId => "signed-odin"; + public Task> LookupAsync( + CultMeshDiscoveryQuery query, + CancellationToken cancellationToken = default) => + Task.FromResult>(new[] + { + new CultMeshDiscoveryObservation( + _descriptor, SourceId, DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddMinutes(1), CultMeshDiscoveryTrust.Signed) + }); + } + + private sealed class EchoConnector : ICultMeshTransportConnector + { + public string ConnectorId => "credential-free-echo"; + public int Priority => 0; + public bool CanConnect(CultMeshTransportCandidate candidate) => true; + public Task ConnectAsync( + CultMeshTransportCandidate candidate, + CultMeshProtocolId protocol, + CancellationToken cancellationToken = default) => + Task.FromResult(new EchoClient()); + } + + private sealed class EchoClient : ICultNetSchemaClient + { + private Action? _accepted; + public bool Connected => true; + public void Connect(string host, int port) { } + public void SendCultNet(T message) where T : ICultNetSchemaMessage + { + if (message is not CultMeshSessionOpenMessage request) return; + _accepted?.Invoke(new CultMeshSessionAcceptedMessage + { + MessageId = request.MessageId, + Accepted = true, + VerseId = request.VerseId, + AuthorityRuntimeId = request.AuthorityRuntimeId, + ProtocolId = request.ProtocolId, + RouteGeneration = request.RouteGeneration, + ClientNonce = request.ClientNonce, + ProviderKeyId = "provider-live", + ProviderSignature = string.Empty + }); + } + public void OnCultNet(Action callback) where T : ICultNetSchemaMessage + { + if (typeof(T) == typeof(CultMeshSessionAcceptedMessage)) + _accepted = response => callback((T)(object)response); + } + public void Dispose() { } + } +} diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs index 81ee15cc..803a055d 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs @@ -26,7 +26,7 @@ public sealed class CultMeshBodySessionTests var client = new LoopbackClient(server); var connector = new LoopbackConnector(client); using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource() }); - using var sessions = new CultMeshSessionManager(discovery, new[] { connector }); + using var sessions = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessions); var provider = new CultMeshSessionBodyProvider( "aetheria.daemon", sessions, new CultMeshSessionTarget("aetheria", "aetheria.daemon")); @@ -73,7 +73,7 @@ public sealed class CultMeshBodySessionTests }); using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource(endpoint) }); using var sessions = new CultMeshSessionManager(discovery, - new ICultMeshTransportConnector[] { new CultMeshSchemaTransportConnector() }); + new ICultMeshTransportConnector[] { new CultMeshSchemaTransportConnector() }, CultMeshTestTrust.LocalSessions); var provider = new CultMeshSessionBodyProvider("aetheria.daemon", sessions, new CultMeshSessionTarget("aetheria", "aetheria.daemon"), new CultMeshSessionBodyProviderOptions { ResponseTimeout = TimeSpan.FromSeconds(10) }); @@ -142,7 +142,7 @@ public sealed class CultMeshBodySessionTests private sealed class RouteSource : ICultMeshLookupSource { private readonly string _endpoint; - public RouteSource(string endpoint = "rudp://body.test:3076") => _endpoint = endpoint; + public RouteSource(string endpoint = "rudp://127.0.0.13:3076") => _endpoint = endpoint; public string SourceId => "test"; public Task> LookupAsync(CultMeshDiscoveryQuery query, CancellationToken cancellationToken = default) diff --git a/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs b/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs index e603415e..dcabf6e6 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshClientTests.cs @@ -40,6 +40,7 @@ public sealed class CultMeshClientTests return new RendezvousClient(); } }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -48,7 +49,7 @@ public sealed class CultMeshClientTests first.Should().BeSameAs(second); first.Target.Should().Be(Target); - first.State.Path!.Endpoint.Should().Be("rudp://aetheria:3081"); + first.State.Path!.Endpoint.Should().Be("rudp://127.0.0.1:3081"); discoveryClients.Should().Be(1); connector.ConnectCount.Should().Be(1); } @@ -61,6 +62,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -82,6 +84,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -103,6 +106,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); using var lease = await mesh.LeaseDocumentAsync(Target, "surface:pilot"); @@ -125,6 +129,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -148,6 +153,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector }, SubscriptionResponseTimeout = TimeSpan.FromMilliseconds(20) }); @@ -168,6 +174,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -187,6 +194,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -211,6 +219,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -237,6 +246,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -262,6 +272,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); var request = new ClientTestDocument { Id = "command:1", Text = "move" }; @@ -288,6 +299,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -319,6 +331,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector }, OperationResponseTimeout = TimeSpan.FromSeconds(2) }); @@ -348,6 +361,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector }, OperationResponseTimeout = TimeSpan.FromSeconds(5) }); @@ -378,6 +392,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); @@ -403,6 +418,7 @@ public sealed class CultMeshClientTests { RendezvousEndpoints = new[] { "rudp://odin:3076" }, Discovery = new CultMeshVerseDiscoveryClientOptions { CreateClient = () => new RendezvousClient() }, + Sessions = CultMeshTestTrust.LocalSessions, Connectors = new[] { connector } }); var opening = mesh.LeaseDocumentAsync(Target, "surface:pilot"); @@ -432,7 +448,7 @@ public sealed class CultMeshClientTests "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), - new[] { "rudp://aetheria:3081" }, + new[] { "rudp://127.0.0.1:3081" }, new[] { "aetheria-daemon" }).ToMessage() } }; diff --git a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs index fa11072c..6ec0548f 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs @@ -55,7 +55,7 @@ public sealed class CultMeshContentSessionTests using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), - new ICultMeshContentTransportConnector[] { connector }); + new ICultMeshContentTransportConnector[] { connector }, CultMeshTestTrust.LocalSessions); var provider = new CultMeshSessionContentProvider( "aetheria.daemon", sessions, @@ -99,7 +99,7 @@ public sealed class CultMeshContentSessionTests using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), - new ICultMeshContentTransportConnector[] { new CultMeshTcpContentTransportConnector() }); + new ICultMeshContentTransportConnector[] { new CultMeshTcpContentTransportConnector() }, CultMeshTestTrust.LocalSessions); Func connect = async () => await sessions.ConnectContentAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon")); @@ -151,7 +151,7 @@ public sealed class CultMeshContentSessionTests using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), - new ICultMeshContentTransportConnector[] { connector }); + new ICultMeshContentTransportConnector[] { connector }, CultMeshTestTrust.LocalSessions); var provider = new CultMeshSessionContentProvider( "aetheria.daemon", sessions, @@ -220,7 +220,7 @@ public sealed class CultMeshContentSessionTests { private readonly string _endpoint; - public RouteSource(string endpoint = "rudp://content.test:3076") => _endpoint = endpoint; + public RouteSource(string endpoint = "rudp://127.0.0.14:3076") => _endpoint = endpoint; public string SourceId => "test-rendezvous"; diff --git a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs index 46e0672f..40fdfe15 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs @@ -28,22 +28,22 @@ public sealed class CultMeshSessionManagerTests connected = candidate; return Task.FromResult(new FakeSchemaClient()); }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); await manager.ConnectAsync(Target, CultMeshProtocols.Documents); connected.Should().NotBeNull(); - connected!.Endpoint.Should().Be("rudp://aetheria:3076"); + connected!.Endpoint.Should().Be("rudp://127.0.0.1:3076"); } [Test] public async Task Connect_ReusesInflightAndEstablishedSession() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.2:3076" }); var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connector = new FakeConnector(async (_, _) => { await release.Task; return new FakeSchemaClient(); }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var target = Target; var first = manager.ConnectAsync(target, CultMeshProtocols.Documents); @@ -61,19 +61,19 @@ public sealed class CultMeshSessionManagerTests public async Task Connect_RacesPathsAndDisposesSuccessfulLoser() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://slow:3076", "rudp://fast:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.3:3076", "rudp://127.0.0.4:3076" }); var slowRelease = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var slowClient = new FakeSchemaClient(); var fastClient = new FakeSchemaClient(); var connector = new FakeConnector(async (candidate, _) => { - if (candidate.Endpoint.Contains("slow")) { await slowRelease.Task; return slowClient; } + if (candidate.Endpoint.Contains("127.0.0.3")) { await slowRelease.Task; return slowClient; } return fastClient; }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var session = await manager.ConnectAsync(Target, CultMeshProtocols.Documents); - session.State.Path!.Endpoint.Should().Be("rudp://fast:3076"); + session.State.Path!.Endpoint.Should().Be("rudp://127.0.0.4:3076"); slowRelease.TrySetResult(); await WaitUntilAsync(() => slowClient.DisposeCount == 1); fastClient.DisposeCount.Should().Be(0); @@ -90,8 +90,8 @@ public sealed class CultMeshSessionManagerTests new CultMeshVerseCompatibility("cultmesh.v0", "rules"), authorityRoutes: new[] { - new CultMeshAuthorityRoute("decoy-daemon", "rudp://decoy:3076"), - new CultMeshAuthorityRoute("aetheria-daemon", "rudp://authority:3076") + new CultMeshAuthorityRoute("decoy-daemon", "rudp://127.0.0.5:3076"), + new CultMeshAuthorityRoute("aetheria-daemon", "rudp://127.0.0.6:3076") }); using var discovery = new CultMeshDiscoveryService( new[] { new StaticRouteSource(clock, descriptor) }, @@ -102,22 +102,22 @@ public sealed class CultMeshSessionManagerTests attempts.Add(candidate.Endpoint); return Task.FromResult(new FakeSchemaClient()); }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessions); var session = await manager.ConnectAsync(Target, CultMeshProtocols.Documents); - session.State.Path!.Endpoint.Should().Be("rudp://authority:3076"); - attempts.Should().Equal("rudp://authority:3076"); + session.State.Path!.Endpoint.Should().Be("rudp://127.0.0.6:3076"); + attempts.Should().Equal("rudp://127.0.0.6:3076"); } [Test] public void Connect_RejectsRouteWhoseNativeIdentityDoesNotMatchTarget() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://decoy:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.5:3076" }); var connector = new FakeConnector((_, _) => Task.FromResult(new NativeMismatchClient())); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessions); var error = Assert.ThrowsAsync(() => manager.ConnectAsync(Target, CultMeshProtocols.Documents)); @@ -129,10 +129,10 @@ public sealed class CultMeshSessionManagerTests public void Connect_RejectsPortableHandshakeFromWrongRuntime() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://decoy:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.5:3076" }); var connector = new FakeConnector((_, _) => Task.FromResult(new HandshakeSchemaClient("decoy-daemon"))); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessions); var error = Assert.ThrowsAsync(() => manager.ConnectAsync(Target, CultMeshProtocols.Documents)); @@ -148,7 +148,7 @@ public sealed class CultMeshSessionManagerTests "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), - discoveryEndpoints: new[] { "rudp://shared:3076" }, + discoveryEndpoints: new[] { "rudp://127.0.0.7:3076" }, authorityRuntimeIds: new[] { "authority-a", "authority-b" }); action.Should().Throw().WithMessage("*ambiguous*"); @@ -158,7 +158,7 @@ public sealed class CultMeshSessionManagerTests public async Task Connect_CallerCancellationDoesNotCancelSharedAttempt() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.2:3076" }); var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connector = new FakeConnector(async (_, token) => { @@ -166,7 +166,7 @@ public sealed class CultMeshSessionManagerTests await release.Task; return new FakeSchemaClient(); }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); using var cancellation = new CancellationTokenSource(); var target = Target; @@ -185,7 +185,7 @@ public sealed class CultMeshSessionManagerTests public async Task BackgroundFailureMigratesSameSessionAcrossEndpointRotation() { var clock = new ManualSessionClock(); - var route = "rudp://first:3076"; + var route = "rudp://127.0.0.8:3076"; using var discovery = Discovery(clock, () => new[] { route }, TimeSpan.FromSeconds(5)); var clients = new List(); var connector = new FakeConnector((_, _) => @@ -194,7 +194,7 @@ public sealed class CultMeshSessionManagerTests clients.Add(client); return Task.FromResult(client); }); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var target = Target; var first = await manager.ConnectAsync(target, CultMeshProtocols.Documents); var states = new List(); @@ -204,14 +204,14 @@ public sealed class CultMeshSessionManagerTests schemaLease.OnCultNet(_ => deliveries++); clients[0].Emit(new CultNetErrorMessage { Error = "before rotation" }); - route = "rudp://second:3076"; + route = "rudp://127.0.0.9:3076"; clients[0].Fail(new IOException("partition")); await WaitUntilAsync(() => first.State.Status == CultMeshSessionStatus.Online && connector.ConnectCount == 2); var second = await manager.ConnectAsync(target, CultMeshProtocols.Documents); clients[1].Emit(new CultNetErrorMessage { Error = "after rotation" }); second.Should().BeSameAs(first); - second.State.Path!.Endpoint.Should().Be("rudp://second:3076"); + second.State.Path!.Endpoint.Should().Be("rudp://127.0.0.9:3076"); states.Should().ContainInOrder(CultMeshSessionStatus.Reconnecting, CultMeshSessionStatus.Online); connector.ConnectCount.Should().Be(2); clients[0].DisposeCount.Should().Be(1); @@ -238,8 +238,8 @@ public sealed class CultMeshSessionManagerTests public void Connect_ReportsUnsupportedPathPrecisely() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "quic://unimplemented:443" }); - using var manager = new CultMeshSessionManager(discovery, Array.Empty(), new CultMeshSessionManagerOptions { Clock = clock }); + using var discovery = Discovery(clock, () => new[] { "quic://127.0.0.10:443" }); + using var manager = new CultMeshSessionManager(discovery, Array.Empty(), CultMeshTestTrust.LocalSessionsWithClock(clock)); var error = Assert.ThrowsAsync(() => manager.ConnectAsync(Target, CultMeshProtocols.Documents)); @@ -253,10 +253,10 @@ public sealed class CultMeshSessionManagerTests public async Task SnapshotSessionsBorrowOneManagedChannelAndDisposeRegistrationsIndependently() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.2:3076" }); var client = new RespondingSchemaClient(); var connector = new FakeConnector((_, _) => Task.FromResult(client)); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var target = Target; using var first = await CultMeshSnapshotSession.ConnectAsync(manager, target, new CultMeshSnapshotRequestOptions()); using var second = await CultMeshSnapshotSession.ConnectAsync(manager, target, new CultMeshSnapshotRequestOptions()); @@ -274,10 +274,10 @@ public sealed class CultMeshSessionManagerTests public async Task PeerExchangeBorrowsManagedProtocolSessionAcrossRequests() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.2:3076" }); var client = new PeerExchangeSchemaClient(); var connector = new FakeConnector((_, _) => Task.FromResult(client)); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var exchange = new CultMeshPeerExchangeClient(new CultMeshPeerExchangeClientOptions { Sessions = manager, Clock = clock }); var target = Target; @@ -294,10 +294,10 @@ public sealed class CultMeshSessionManagerTests public async Task SchemaClientLeasesDisposeHandlersWithoutClosingSession() { var clock = new ManualSessionClock(); - using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" }); + using var discovery = Discovery(clock, () => new[] { "rudp://127.0.0.2:3076" }); var client = new LeaseSchemaClient(); var connector = new FakeConnector((_, _) => Task.FromResult(client)); - using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, CultMeshTestTrust.LocalSessionsWithClock(clock)); var session = await manager.ConnectAsync(Target, CultMeshProtocols.Subscriptions); using var first = session.OpenSchemaClient(); using var second = session.OpenSchemaClient(); @@ -363,8 +363,8 @@ public sealed class CultMeshSessionManagerTests var expires = _clock.UtcNow.AddMinutes(1); return Task.FromResult>(new[] { - Observation("norn", "rudp://norn:3076", expires), - Observation("aetheria", "rudp://aetheria:3076", expires) + Observation("norn", "rudp://127.0.0.11:3076", expires), + Observation("aetheria", "rudp://127.0.0.1:3076", expires) }); } @@ -544,7 +544,7 @@ public sealed class CultMeshSessionManagerTests MessageId = request.MessageId, Peers = new[] { - new CultMeshPeerCard("peer-a", request.VerseId, new[] { "rudp://peer-a:3076" }).ToMessage() + new CultMeshPeerCard("peer-a", request.VerseId, new[] { "rudp://127.0.0.12:3076" }).ToMessage() } }; foreach (var handler in _handlers.ToArray()) handler(response); diff --git a/tests/GameCult.Mesh.Tests/CultMeshTestTrust.cs b/tests/GameCult.Mesh.Tests/CultMeshTestTrust.cs new file mode 100644 index 00000000..2356462d --- /dev/null +++ b/tests/GameCult.Mesh.Tests/CultMeshTestTrust.cs @@ -0,0 +1,18 @@ +namespace GameCult.Mesh.Tests; + +internal static class CultMeshTestTrust +{ + internal static CultMeshAuthorityTrustPolicy LocalPolicy { get; } = + new(CultMeshAuthorityTrustMode.LocalDevelopment); + + internal static CultMeshSessionManagerOptions LocalSessions => new() + { + Trust = LocalPolicy + }; + + internal static CultMeshSessionManagerOptions LocalSessionsWithClock(ICultMeshClock clock) => new() + { + Clock = clock, + Trust = LocalPolicy + }; +} diff --git a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs index 35b4a71c..979c7230 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs @@ -44,7 +44,7 @@ public sealed class CultMeshTransportModularityTests { legacy, new CultMeshTcpSchemaTransportConnector() - }); + }, CultMeshTestTrust.LocalSessions); var session = await sessions.ConnectAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon"), @@ -91,7 +91,7 @@ public sealed class CultMeshTransportModularityTests { legacy, new CultMeshTcpContentTransportConnector() - }); + }, CultMeshTestTrust.LocalSessions); var session = await sessions.ConnectContentAsync(new CultMeshSessionTarget("aetheria", "aetheria.daemon")); using var destination = new MemoryStream(); @@ -111,7 +111,7 @@ public sealed class CultMeshTransportModularityTests }); using var sessions = new CultMeshSessionManager( discovery, - Array.Empty()); + Array.Empty(), CultMeshTestTrust.LocalSessions); Func connect = async () => await sessions.ConnectContentAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon")); @@ -132,7 +132,7 @@ public sealed class CultMeshTransportModularityTests discovery, Array.Empty(), Array.Empty(), - new ICultMeshRealtimeTransportConnector[] { connector }); + new ICultMeshRealtimeTransportConnector[] { connector }, CultMeshTestTrust.LocalSessions); var session = await sessions.ConnectRealtimeAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon")); var sent = new CultMeshRealtimeFrame @@ -163,7 +163,7 @@ public sealed class CultMeshTransportModularityTests }); using var sessions = new CultMeshSessionManager( discovery, - Array.Empty()); + Array.Empty(), CultMeshTestTrust.LocalSessions); Func connect = async () => await sessions.ConnectRealtimeAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon")); @@ -183,7 +183,7 @@ public sealed class CultMeshTransportModularityTests discovery, Array.Empty(), Array.Empty(), - new ICultMeshRealtimeTransportConnector[] { new UnverifiedRealtimeConnector() }); + new ICultMeshRealtimeTransportConnector[] { new UnverifiedRealtimeConnector() }, CultMeshTestTrust.LocalSessions); Func connect = async () => await sessions.ConnectRealtimeAsync( new CultMeshSessionTarget("aetheria", "aetheria.daemon")); diff --git a/tools/GameCult.TransportParity/Program.cs b/tools/GameCult.TransportParity/Program.cs index 31c93e1e..04c71dfe 100644 --- a/tools/GameCult.TransportParity/Program.cs +++ b/tools/GameCult.TransportParity/Program.cs @@ -201,7 +201,8 @@ static async Task MeasureCultMeshQuicStateAsync(int payloadBytes, i [new CultMeshQuicRealtimeTransportConnector(new CultMeshQuicRealtimeConnectorOptions { ValidateProviderCertificate = (_, _, _, _) => true - })]); + })], + LocalDevelopmentSessions()); var session = await sessions.ConnectRealtimeAsync( new CultMeshSessionTarget("parity", "service:parity.provider")); var deadline = DateTime.UtcNow.AddSeconds(5); @@ -363,7 +364,8 @@ static async Task MeasureCultMeshRudpAsync(byte[] payload) discovery, Array.Empty(), [new CultMeshLegacyRudpContentTransportConnector( - new CultMeshLegacyRudpContentTransportOptions { ResponseTimeout = TimeSpan.FromMinutes(2) })]); + new CultMeshLegacyRudpContentTransportOptions { ResponseTimeout = TimeSpan.FromMinutes(2) })], + LocalDevelopmentSessions()); var provider = new CultMeshSessionContentProvider( "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); @@ -411,7 +413,8 @@ static async Task MeasureCultMeshTcpAsync(byte[] payload) using var sessions = new CultMeshSessionManager( discovery, Array.Empty(), - [new CultMeshTcpContentTransportConnector()]); + [new CultMeshTcpContentTransportConnector()], + LocalDevelopmentSessions()); var provider = new CultMeshSessionContentProvider( "parity.provider", sessions, new CultMeshSessionTarget("parity", "service:parity.provider")); var directory = Path.Combine(Path.GetTempPath(), "cultmesh-transport-parity", Guid.NewGuid().ToString("N")); @@ -456,6 +459,11 @@ static X509Certificate2 CreateCertificate() static string TempPath(string plane) => Path.Combine(Path.GetTempPath(), $"cultmesh-parity-{plane}-{Guid.NewGuid():N}.body"); +static CultMeshSessionManagerOptions LocalDevelopmentSessions() => new() +{ + Trust = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment) +}; + static Socket BindUdpSocket() { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); From c194bd4cc845459908afb4896b4191df5e1c9a94 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 19:41:03 +0200 Subject: [PATCH 027/203] Harden CultMesh route fallback and secure onboarding --- Directory.Build.props | 2 +- packages/cultmesh-browser/src/index.ts | 86 +++++++++----- .../test/cultmesh-browser.test.ts | 111 ++++++++++++++++-- .../EveBrowserNetworkSample.csproj | 4 +- scripts/pack-nuget.ps1 | 3 + scripts/verify-eve-getting-started.ps1 | 4 +- src/GameCult.Mesh/CultMeshSessions.cs | 31 ++++- src/GameCult.Mesh/CultMeshVerseDiscovery.cs | 9 +- .../getting-started/02-connect-by-identity.md | 36 ++++++ .../CultMeshAuthorityProofTests.cs | 88 ++++++++++++++ unity/org.gamecult.cultlib/package.json | 2 +- 11 files changed, 329 insertions(+), 47 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 434617ff..595da325 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ MIT © Meta Vens and contributors true - 1.0.33 + 1.0.34 $(CultLibPackageVersion) diff --git a/packages/cultmesh-browser/src/index.ts b/packages/cultmesh-browser/src/index.ts index a0596ae2..4dddfa7d 100644 --- a/packages/cultmesh-browser/src/index.ts +++ b/packages/cultmesh-browser/src/index.ts @@ -53,7 +53,10 @@ export interface CultMeshBrowserAuthorityTrustPolicy { } export interface CultMeshBrowserRendezvous { - resolve(identity: CultMeshBrowserIdentity): Promise; + resolve( + identity: CultMeshBrowserIdentity, + trust?: CultMeshBrowserAuthorityTrustPolicy, + ): Promise; } export interface CultMeshBrowserOdinRendezvousOptions { @@ -101,13 +104,16 @@ export class CultMeshBrowserOdinRendezvous implements CultMeshBrowserRendezvous if (this.#options.maxFrameBytes <= 0) throw new Error("Odin rendezvous maxFrameBytes must be positive."); } - async resolve(identity: CultMeshBrowserIdentity): Promise { + async resolve( + identity: CultMeshBrowserIdentity, + trust: CultMeshBrowserAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [] }, + ): Promise { requireText(identity.verseId, "verseId"); requireText(identity.authorityRuntimeId, "authorityRuntimeId"); const failures: Error[] = []; for (const endpoint of this.#options.endpoints) { try { - return await this.resolveFrom(endpoint, identity); + return await this.resolveFrom(endpoint, identity, trust); } catch (error) { failures.push(error instanceof Error ? error : new Error(String(error))); } @@ -118,7 +124,11 @@ export class CultMeshBrowserOdinRendezvous implements CultMeshBrowserRendezvous ); } - private resolveFrom(endpoint: string, identity: CultMeshBrowserIdentity): Promise { + private resolveFrom( + endpoint: string, + identity: CultMeshBrowserIdentity, + trust: CultMeshBrowserAuthorityTrustPolicy, + ): Promise { const socket = this.#options.socketFactory(endpoint); socket.binaryType = "arraybuffer"; const messageId = this.#options.createId(); @@ -169,7 +179,9 @@ export class CultMeshBrowserOdinRendezvous implements CultMeshBrowserRendezvous return; } if (message.schemaVersion !== "cultmesh.verse_catalog_response.v0" || message.messageId !== messageId) return; - finish(undefined, selectOdinRoute(message, identity)); + void selectOdinRoute(message, identity, trust) + .then(route => finish(undefined, route)) + .catch(error => finish(error instanceof Error ? error : new Error(String(error)))); }).catch(error => finish(error instanceof Error ? error : new Error(String(error)))); }; socket.onerror = () => finish(new Error(`Odin WebSocket could not open '${endpoint}'.`)); @@ -501,6 +513,11 @@ export class CultMeshBrowserClient implements AsyncDisposable { private connectSocket(): Promise { this.throwIfDisposed(); + if (this.#state === "connected" && this.#socket?.readyState === 1) return Promise.resolve(); + if (this.#reconnectTimer) { + clearTimeout(this.#reconnectTimer); + this.#reconnectTimer = undefined; + } if (this.#connectPromise) return this.#connectPromise; const generation = ++this.#socketGeneration; this.setState(this.#socket ? "reconnecting" : "connecting"); @@ -511,7 +528,7 @@ export class CultMeshBrowserClient implements AsyncDisposable { } private async openSocket(generation: number): Promise { - const route = await this.#options.rendezvous.resolve(this.identity); + const route = await this.#options.rendezvous.resolve(this.identity, this.#options.trust); if (route.verseId !== this.identity.verseId || route.authorityRuntimeId !== this.identity.authorityRuntimeId) { throw new Error("CultMesh rendezvous returned a route for the wrong stable identity."); @@ -828,10 +845,11 @@ async function decodeSocketMessage(event: MessageEvent, maxFrameBytes: number): return parseCultNetMessage(decode(payload)); } -function selectOdinRoute( +async function selectOdinRoute( response: CultMeshVerseCatalogResponseMessage, identity: CultMeshBrowserIdentity, -): CultMeshBrowserRoute { + trust: CultMeshBrowserAuthorityTrustPolicy, +): Promise { const matchingVerses = response.verses.filter(verse => verse.verseId === identity.verseId); if (matchingVerses.length !== 1) { throw new Error( @@ -871,26 +889,38 @@ function selectOdinRoute( if (routes.length === 0) { throw new Error(`Odin advertised no browser-compatible route for authority runtime '${identity.authorityRuntimeId}'.`); } - const route = routes[0]; - return { - ...identity, - endpoint: route.endpoint, - protocolId: "cultmesh.documents.v1", - protocolIds: route.protocolIds, - priority: route.priority, - generation: route.generation, - ...(route.certificate ? { certificate: { - providerKey: { - keyId: route.certificate.providerKeyId, - x: route.certificate.providerPublicKeyX, - y: route.certificate.providerPublicKeyY, - }, - odinKeyId: route.certificate.odinKeyId, - issuedAtUnixMilliseconds: route.certificate.issuedAtUnixMilliseconds, - expiresAtUnixMilliseconds: route.certificate.expiresAtUnixMilliseconds, - signature: route.certificate.signature, - } } : {}), - }; + const failures: Error[] = []; + for (const route of routes) { + const candidate: CultMeshBrowserRoute = { + ...identity, + endpoint: route.endpoint, + protocolId: "cultmesh.documents.v1", + protocolIds: route.protocolIds, + priority: route.priority, + generation: route.generation, + ...(route.certificate ? { certificate: { + providerKey: { + keyId: route.certificate.providerKeyId, + x: route.certificate.providerPublicKeyX, + y: route.certificate.providerPublicKeyY, + }, + odinKeyId: route.certificate.odinKeyId, + issuedAtUnixMilliseconds: route.certificate.issuedAtUnixMilliseconds, + expiresAtUnixMilliseconds: route.certificate.expiresAtUnixMilliseconds, + signature: route.certificate.signature, + } } : {}), + }; + try { + await verifyAuthorityRoute(candidate, trust); + return candidate; + } catch (error) { + failures.push(error instanceof Error ? error : new Error(String(error))); + } + } + throw new AggregateError( + failures, + `Odin advertised no trusted browser route for authority runtime '${identity.authorityRuntimeId}'.`, + ); } async function validateSessionAcceptance( diff --git a/packages/cultmesh-browser/test/cultmesh-browser.test.ts b/packages/cultmesh-browser/test/cultmesh-browser.test.ts index 4f90acf1..6c169d93 100644 --- a/packages/cultmesh-browser/test/cultmesh-browser.test.ts +++ b/packages/cultmesh-browser/test/cultmesh-browser.test.ts @@ -9,6 +9,7 @@ import { type CultNetMessage, type CultNetOperationRequestMessage, type CultNetRawDocumentRecord, + type CultMeshVerseCatalogResponseMessage, type CultMeshSessionOpenMessage, } from "cultnet-ts/contracts"; @@ -38,16 +39,16 @@ test("Odin rendezvous resolves stable authority-runtime identity and observes ro assert.equal((await rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", - })).endpoint, "ws://127.0.0.1:4050/mesh"); + }, localTrust)).endpoint, "ws://127.0.0.1:4050/mesh"); odin.providerEndpoint = "ws://127.0.0.1:4060/mesh"; assert.equal((await rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", - })).endpoint, "ws://127.0.0.1:4060/mesh"); + }, localTrust)).endpoint, "ws://127.0.0.1:4060/mesh"); await assert.rejects( - rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "other-runtime" }), + rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "other-runtime" }, localTrust), /could not resolve/, ); }); @@ -64,7 +65,7 @@ test("Odin rendezvous never selects another authority's faster endpoint", async const route = await rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", - }); + }, localTrust); assert.equal(route.endpoint, "ws://127.0.0.1:4050/mesh"); }); @@ -80,18 +81,45 @@ test("Odin rendezvous accepts one-authority legacy routes and rejects ambiguous const route = await rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon", - }); + }, localTrust); assert.equal(route.endpoint, "ws://127.0.0.1:4070/mesh"); odin.decoyEndpoint = "ws://127.0.0.1:4069/decoy"; await assert.rejects( - rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon" }), + rendezvous.resolve({ verseId: "sample.counter", authorityRuntimeId: "sample.counter-daemon" }, localTrust), (error: unknown) => error instanceof AggregateError && error.errors.some(inner => inner instanceof Error && /ambiguous legacy routes/.test(inner.message)), ); }); +test("Odin rendezvous rejects a poisoned route without vetoing a certified fallback", async () => { + const signed = await createSignedRoute("wss://provider.example/mesh"); + const odin = new FakeOdin(signed.route.endpoint); + odin.authorityRoutesOverride = [ + { + authorityRuntimeId: signed.route.authorityRuntimeId, + endpoint: "wss://poison.example/mesh", + protocolIds: ["cultmesh.documents.v1"], + priority: 0, + generation: "poisoned", + }, + routeForCatalog(signed.route), + ]; + const rendezvous = new CultMeshBrowserOdinRendezvous({ + endpoints: ["ws://127.0.0.1:4040/odin"], + runtimeId: "browser-certified-fallback-test", + socketFactory: () => odin.open(), + }); + + const selected = await rendezvous.resolve( + { verseId: signed.route.verseId, authorityRuntimeId: signed.route.authorityRuntimeId }, + { mode: "authenticated-remote", odinRoots: [signed.odinPublic] }, + ); + + assert.equal(selected.endpoint, signed.route.endpoint); +}); + test("browser client leases provider state, invokes an operation, and follows route replacement", async () => { const first = new FakeProvider("ws://127.0.0.1:4101/mesh", 4); const second = new FakeProvider("ws://127.0.0.1:4102/mesh", 9); @@ -362,6 +390,37 @@ test("browser client replays one idempotent operation after route loss without d await client.dispose(); }); +test("an early caller reconnect cancels the scheduled reconnect and keeps one live socket", async () => { + const provider = new FakeProvider("ws://127.0.0.1:4361/mesh", 2); + const client = await CultMeshBrowserClient.connect({ + ...provider.route, + runtimeId: "browser-single-reconnect-test", + rendezvous: { resolve: async () => provider.route }, + trust: localTrust, + reconnectDelayMs: 40, + socketFactory: () => provider.open(), + }); + const lease = await client.leaseRawDocument({ + schemaId: "sample.counter_state.v1", + recordKey: "counter:main", + }); + + provider.dropConnections(); + await client.invoke({ + serviceId: "sample.counter", + operation: "increment", + payloadSchema: "sample.increment.v1", + payload: { amount: 1 }, + }); + await new Promise(resolve => setTimeout(resolve, 80)); + + assert.equal(provider.openCount, 2); + assert.equal(provider.liveSocketCount, 1); + assert.equal(provider.activeSubscriptionCount, 1); + lease.dispose(); + await client.dispose(); +}); + test("browser client rejects an operation response from another runtime", async () => { const provider = new FakeProvider( "ws://127.0.0.1:4371/mesh", @@ -443,6 +502,7 @@ class FakeProvider { #rejectOperations: boolean; #runtimeId: string; #responseRuntimeId: string; + #openCount = 0; constructor( endpoint: string, @@ -469,7 +529,20 @@ class FakeProvider { return count; } + get openCount(): number { + return this.#openCount; + } + + get liveSocketCount(): number { + return this.#subscriptions.size; + } + + dropConnections(): void { + for (const socket of [...this.#subscriptions.keys()]) socket.close(1012, "route dropped"); + } + open(): FakeSocket { + this.#openCount++; const socket = new FakeSocket( wire => this.receive(socket, wire), () => this.#subscriptions.delete(socket), @@ -587,6 +660,7 @@ class FakeOdin { providerEndpoint: string; decoyEndpoint: string | undefined; omitAuthorityRoutes = false; + authorityRoutesOverride: NonNullable | undefined; constructor(providerEndpoint: string) { this.providerEndpoint = providerEndpoint; @@ -614,7 +688,7 @@ class FakeOdin { authorityRuntimeIds: this.decoyEndpoint ? ["sample.counter-daemon", "sample.decoy-daemon"] : ["sample.counter-daemon"], - ...(!this.omitAuthorityRoutes ? { authorityRoutes: [ + ...(!this.omitAuthorityRoutes ? { authorityRoutes: this.authorityRoutesOverride ?? [ ...(this.decoyEndpoint ? [{ authorityRuntimeId: "sample.decoy-daemon", endpoint: this.decoyEndpoint, @@ -637,6 +711,29 @@ class FakeOdin { } } +function routeForCatalog(route: CultMeshBrowserRoute): NonNullable< + CultMeshVerseCatalogResponseMessage["verses"][number]["authorityRoutes"] +>[number] { + return { + authorityRuntimeId: route.authorityRuntimeId, + endpoint: route.endpoint, + protocolIds: [...(route.protocolIds ?? [route.protocolId ?? "cultmesh.documents.v1"])], + priority: route.priority ?? 0, + generation: route.generation, + ...(route.certificate ? { + certificate: { + providerKeyId: route.certificate.providerKey.keyId, + providerPublicKeyX: route.certificate.providerKey.x, + providerPublicKeyY: route.certificate.providerKey.y, + odinKeyId: route.certificate.odinKeyId, + issuedAtUnixMilliseconds: route.certificate.issuedAtUnixMilliseconds, + expiresAtUnixMilliseconds: route.certificate.expiresAtUnixMilliseconds, + signature: route.certificate.signature, + }, + } : {}), + }; +} + function handshakeOnlySocket(route: CultMeshBrowserRoute): FakeSocket { let socket: FakeSocket; socket = new FakeSocket(wire => { diff --git a/samples/eve-browser-network/EveBrowserNetworkSample.csproj b/samples/eve-browser-network/EveBrowserNetworkSample.csproj index c83a42df..5dde9995 100644 --- a/samples/eve-browser-network/EveBrowserNetworkSample.csproj +++ b/samples/eve-browser-network/EveBrowserNetworkSample.csproj @@ -5,8 +5,8 @@ enable $(MSBuildThisFileDirectory)..\..\..\Eve false - 1.0.33 - 0.3.2 + 1.0.34 + 0.3.3 $(PackageFeed) diff --git a/scripts/pack-nuget.ps1 b/scripts/pack-nuget.ps1 index fa0d12bd..2b853aab 100644 --- a/scripts/pack-nuget.ps1 +++ b/scripts/pack-nuget.ps1 @@ -71,6 +71,7 @@ if (-not $SkipConsumerSmoke) { Remove-Item -LiteralPath $smokeRoot -Recurse -Force } New-Item -ItemType Directory -Force -Path $smokeRoot | Out-Null + $consumerPackages = Join-Path $smokeRoot "packages" $projectDocument = @" @@ -78,6 +79,8 @@ if (-not $SkipConsumerSmoke) { Exe enable $outputRoot;https://api.nuget.org/v3/index.json + $consumerPackages + true diff --git a/scripts/verify-eve-getting-started.ps1 b/scripts/verify-eve-getting-started.ps1 index e4143645..26c69f14 100644 --- a/scripts/verify-eve-getting-started.ps1 +++ b/scripts/verify-eve-getting-started.ps1 @@ -32,8 +32,8 @@ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sourceRevision)) { throw "Could not determine the CultLib source revision for local package identity." } $verificationNonce = [guid]::NewGuid().ToString("N").Substring(0, 8) -$cultLibLocalVersion = "1.0.33-local.$sourceRevision.$verificationNonce" -$surfaceLocalVersion = "0.3.2-local.$sourceRevision.$verificationNonce" +$cultLibLocalVersion = "1.0.34-local.$sourceRevision.$verificationNonce" +$surfaceLocalVersion = "0.3.3-local.$sourceRevision.$verificationNonce" try { & (Join-Path $EveRoot "scripts\pack-dotnet-surface.ps1") ` -CultLibRoot $cultLibRoot ` diff --git a/src/GameCult.Mesh/CultMeshSessions.cs b/src/GameCult.Mesh/CultMeshSessions.cs index b0dace01..9768dfd5 100644 --- a/src/GameCult.Mesh/CultMeshSessions.cs +++ b/src/GameCult.Mesh/CultMeshSessions.cs @@ -1084,16 +1084,21 @@ namespace GameCult.Mesh CultMeshSessionTarget target, CultMeshProtocolId protocol) { - return discovery.Candidates + var routes = discovery.Candidates .SelectMany(candidate => candidate.Descriptor.AuthorityRoutes) .Where(route => string.Equals( route.AuthorityRuntimeId, target.AuthorityRuntimeId, StringComparison.Ordinal)) .Where(route => route.Supports(protocol)) - .GroupBy(route => route.Endpoint, StringComparer.Ordinal) - .Select(group => group.OrderBy(route => route.Priority).First()) - .Select(route => new CultMeshTransportCandidate( + .ToArray(); + var verified = new List(); + var rejected = new List(); + foreach (var route in routes) + { + try + { + verified.Add(new CultMeshTransportCandidate( route.Endpoint, priority: route.Priority, authorityRuntimeId: route.AuthorityRuntimeId, @@ -1102,7 +1107,23 @@ namespace GameCult.Mesh .WithVerifiedAuthority(_options.Trust.Verify( target.VerseId, route, - _options.Clock.UtcNow))) + _options.Clock.UtcNow))); + } + catch (CultMeshSessionException error) + { + rejected.Add(error); + } + } + if (verified.Count == 0 && rejected.Count > 0) + { + throw Failure( + CultMeshSessionFailureReason.Authentication, + $"No trusted route remained for CultMesh target '{target}' and protocol '{protocol}'.", + new AggregateException(rejected)); + } + return verified + .GroupBy(candidate => candidate.Endpoint, StringComparer.Ordinal) + .Select(group => group.OrderBy(candidate => candidate.Priority).First()) .OrderBy(candidate => candidate.Priority) .ThenBy(candidate => candidate.Endpoint, StringComparer.Ordinal) .ToArray(); diff --git a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs index c4ee15c7..1e98fc16 100644 --- a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs +++ b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs @@ -207,6 +207,12 @@ namespace GameCult.Mesh /// public Func? CreateClient { get; set; } + /// + /// Gets or sets an endpoint-aware client factory for external transport packages. + /// When present, this owns discovery transport selection for each exact Odin route. + /// + public Func? CreateClientForEndpoint { get; set; } + /// Gets or sets the clock that owns discovery deadlines. public ICultMeshClock Clock { get; set; } = CultMeshSystemClock.Instance; @@ -341,7 +347,8 @@ namespace GameCult.Mesh private ICultNetSchemaClient CreateClient(string endpoint) { - return _options.CreateClient?.Invoke() + return _options.CreateClientForEndpoint?.Invoke(endpoint) + ?? _options.CreateClient?.Invoke() ?? CultNetSchemaClients.CreateForEndpoint(endpoint, _options.Security, _options.ConfigureClient); } diff --git a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md index 28cc06ad..47905fec 100644 --- a/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md +++ b/src/GameCult.Mesh/docs/getting-started/02-connect-by-identity.md @@ -16,6 +16,7 @@ become usable by merely repeating the expected Verse and runtime strings. ```csharp using GameCult.Mesh; +using GameCult.Networking.WebSockets; var odinEndpoint = Environment.GetEnvironmentVariable("ODIN_ENDPOINT") ?? throw new InvalidOperationException("Set ODIN_ENDPOINT to the configured rendezvous route."); @@ -33,11 +34,22 @@ var odinRoot = new CultMeshEcdsaP256PublicKey( using var mesh = new CultMeshClient(new CultMeshClientOptions { RendezvousEndpoints = new[] { odinEndpoint }, + Discovery = new CultMeshVerseDiscoveryClientOptions + { + CreateClientForEndpoint = _ => new CultNetWebSocketSchemaClient() + }, Sessions = new CultMeshSessionManagerOptions { Trust = new CultMeshAuthorityTrustPolicy( CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { odinRoot }) + }, + Connectors = new ICultMeshTransportConnector[] + { + new CultMeshUriSchemaTransportConnector( + "cultnet-websocket", + new[] { "wss" }, + _ => new CultNetWebSocketSchemaClient()) } }); @@ -93,6 +105,30 @@ provider names, and Odin wire fields cannot turn this exception on. Remote routes require a trusted Odin signature, provider nonce proof, certificate validity, and a protected channel. +The remote example requires both `GameCult.Mesh` and +`GameCult.Networking.WebSockets` at the same CultLib package version. Core +CultMesh does not silently install a network stack: the consumer chooses the +secure connector and its TLS policy. The default TCP connector exists for the +explicit loopback-development case and cannot satisfy authenticated-remote +trust. + +An authenticated provider receives an Odin-certified route and its matching +provider private key from deployment configuration. It attaches the proof gate +to the same WSS schema server before publishing the route: + +```csharp +using var identity = new CultMeshSessionIdentityServer( + schemaServer, + authorityRuntimeId: "sample.counter-daemon", + verseIds: new[] { "sample.counter" }, + protocolIds: new[] { CultMeshProtocols.Documents.Value }, + routeGenerations: new[] { certifiedRoute.Generation }, + proofSigners: new[] { new CultMeshSessionProofSigner(certifiedRoute, providerPrivateKey) }); +``` + +`certifiedRoute` is the exact signed route Odin publishes. The provider never +holds Odin's signing key, and an unsigned WSS route is still untrusted. + Fast session-management check (this uses controlled transports; it is not the network proof): diff --git a/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs index 88c7e803..246e510a 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs @@ -134,6 +134,45 @@ public sealed class CultMeshAuthorityProofTests .Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); } + [Test] + public async Task SessionManagerRejectsOnlyThePoisonedRouteAndUsesCertifiedFallback() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var now = DateTimeOffset.UtcNow; + var valid = CultMeshAuthorityProof.CreateSignedRoute( + "aetheria", "aetheria-daemon", "wss://provider.example/mesh", + new[] { CultMeshProtocols.Documents.Value }, 10, "generation-valid", + CultMeshEcdsaP256PublicKey.From("provider-valid", provider), "odin-valid", + now.AddMinutes(-1), now.AddMinutes(5), odin); + var poisoned = new CultMeshAuthorityRoute( + "aetheria-daemon", "wss://poison.example/mesh", + new[] { CultMeshProtocols.Documents.Value }, priority: 0, generation: "generation-poisoned"); + var descriptor = new CultMeshVerseDescriptor( + "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + authorityRoutes: new[] { poisoned, valid }); + using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); + var signer = new CultMeshSessionProofSigner(valid, provider); + var connector = new SigningConnector(signer); + using var manager = new CultMeshSessionManager( + discovery, + new[] { connector }, + new CultMeshSessionManagerOptions + { + Trust = new CultMeshAuthorityTrustPolicy( + CultMeshAuthorityTrustMode.AuthenticatedRemote, + new[] { CultMeshEcdsaP256PublicKey.From("odin-valid", odin) }) + }); + + var session = await manager.ConnectAsync( + new CultMeshSessionTarget("aetheria", "aetheria-daemon"), + CultMeshProtocols.Documents); + + session.State.Path!.Endpoint.Should().Be(valid.Endpoint); + connector.Attempts.Should().Equal(valid.Endpoint); + } + private static CultMeshAuthorityRoute SignedRoute(ECDsa odin, ECDsa provider) => CultMeshAuthorityProof.CreateSignedRoute( "aetheria", @@ -238,4 +277,53 @@ public sealed class CultMeshAuthorityProofTests } public void Dispose() { } } + + private sealed class SigningConnector : ICultMeshTransportConnector + { + private readonly CultMeshSessionProofSigner _signer; + public SigningConnector(CultMeshSessionProofSigner signer) => _signer = signer; + public List Attempts { get; } = new(); + public string ConnectorId => "signed-provider"; + public int Priority => 0; + public bool CanConnect(CultMeshTransportCandidate candidate) => true; + public Task ConnectAsync( + CultMeshTransportCandidate candidate, + CultMeshProtocolId protocol, + CancellationToken cancellationToken = default) + { + Attempts.Add(candidate.Endpoint); + return Task.FromResult(new SigningClient(_signer)); + } + } + + private sealed class SigningClient : ICultNetSchemaClient + { + private readonly CultMeshSessionProofSigner _signer; + private Action? _accepted; + public SigningClient(CultMeshSessionProofSigner signer) => _signer = signer; + public bool Connected => true; + public void Connect(string host, int port) { } + public void SendCultNet(T message) where T : ICultNetSchemaMessage + { + if (message is not CultMeshSessionOpenMessage request) return; + _accepted?.Invoke(new CultMeshSessionAcceptedMessage + { + MessageId = request.MessageId, + Accepted = true, + VerseId = request.VerseId, + AuthorityRuntimeId = request.AuthorityRuntimeId, + ProtocolId = request.ProtocolId, + RouteGeneration = request.RouteGeneration, + ClientNonce = request.ClientNonce, + ProviderKeyId = _signer.ProviderKeyId, + ProviderSignature = _signer.Sign(request) + }); + } + public void OnCultNet(Action callback) where T : ICultNetSchemaMessage + { + if (typeof(T) == typeof(CultMeshSessionAcceptedMessage)) + _accepted = response => callback((T)(object)response); + } + public void Dispose() { } + } } diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index 7c2cce1c..b0a00486 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.47", + "version": "1.0.48", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From 99458dbb57bcb677bb72edba1362bce9fc9cab9d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 20:09:44 +0200 Subject: [PATCH 028/203] Ship authenticated remote transports in Unity package --- Directory.Build.props | 2 +- .../EveBrowserNetworkSample.csproj | 2 +- scripts/build-unity-package.ps1 | 42 +++++- scripts/verify-eve-getting-started.ps1 | 2 +- src/GameCult.Mesh/CultMeshClient.cs | 5 +- .../CultMeshHttpsContentTransport.cs | 124 ++++++++++++++++++ .../CultNetWebSocketMessageIO.cs | 46 +++++++ .../CultNetWebSocketSchemaClient.cs | 10 +- .../CultNetWebSocketSchemaServer.cs | 38 ------ .../GameCult.Networking.WebSockets.csproj | 8 +- .../CultMeshAuthorityProofTests.cs | 78 +++++++++++ .../Runtime/GameCult.CultLib.asmdef | 1 + .../Plugins/GameCult.Caching.MessagePack.dll | Bin 43520 -> 45568 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 19940 -> 23092 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 86016 -> 90624 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 30140 -> 35804 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 7168 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9480 -> 9620 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15360 -> 16896 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12216 -> 12832 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 656384 -> 758784 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 197728 -> 251464 bytes .../GameCult.Networking.WebSockets.dll | Bin 0 -> 15360 bytes .../GameCult.Networking.WebSockets.dll.meta | 2 + .../Runtime/Plugins/GameCult.Networking.dll | Bin 496128 -> 549376 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 235008 -> 284016 bytes .../x86_64/gamecult_mesh_quic_native.dll | Bin 40448 -> 171008 bytes unity/org.gamecult.cultlib/package.json | 2 +- 28 files changed, 309 insertions(+), 53 deletions(-) create mode 100644 src/GameCult.Mesh/CultMeshHttpsContentTransport.cs create mode 100644 src/GameCult.Networking.WebSockets/CultNetWebSocketMessageIO.cs create mode 100644 unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll create mode 100644 unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll.meta diff --git a/Directory.Build.props b/Directory.Build.props index 595da325..10c8d749 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ MIT © Meta Vens and contributors true - 1.0.34 + 1.0.35 $(CultLibPackageVersion) diff --git a/samples/eve-browser-network/EveBrowserNetworkSample.csproj b/samples/eve-browser-network/EveBrowserNetworkSample.csproj index 5dde9995..50b41781 100644 --- a/samples/eve-browser-network/EveBrowserNetworkSample.csproj +++ b/samples/eve-browser-network/EveBrowserNetworkSample.csproj @@ -5,7 +5,7 @@ enable $(MSBuildThisFileDirectory)..\..\..\Eve false - 1.0.34 + 1.0.35 0.3.3 $(PackageFeed) diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1 index 08366cc5..c0e0518f 100644 --- a/scripts/build-unity-package.ps1 +++ b/scripts/build-unity-package.ps1 @@ -9,9 +9,11 @@ $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot $projectPath = Join-Path $repoRoot "src\GameCult.Mesh\GameCult.Mesh.csproj" +$webSocketProjectPath = Join-Path $repoRoot "src\GameCult.Networking.WebSockets\GameCult.Networking.WebSockets.csproj" $quicProjectPath = Join-Path $repoRoot "src\GameCult.Mesh.Quic.Native\GameCult.Mesh.Quic.Native.csproj" $templateRoot = Join-Path $repoRoot "unity\org.gamecult.cultlib" $publishRoot = Join-Path $repoRoot "artifacts\unity-publish" +$webSocketPublishRoot = Join-Path $repoRoot "artifacts\unity-websocket-publish" $quicPublishRoot = Join-Path $repoRoot "artifacts\unity-quic-publish" $quicNativeRoot = Join-Path $repoRoot "artifacts\unity-quic-native" $outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { @@ -27,11 +29,15 @@ if (Test-Path -LiteralPath $publishRoot) { if (Test-Path -LiteralPath $quicPublishRoot) { Remove-Item -LiteralPath $quicPublishRoot -Recurse -Force } +if (Test-Path -LiteralPath $webSocketPublishRoot) { + Remove-Item -LiteralPath $webSocketPublishRoot -Recurse -Force +} if (Test-Path -LiteralPath $outputRoot) { Remove-Item -LiteralPath $outputRoot -Recurse -Force } -$publishArguments = @("publish", $projectPath, "-c", $Configuration, "-o", $publishRoot) +$unityPackageVersion = (Get-Content -LiteralPath (Join-Path $templateRoot "package.json") -Raw | ConvertFrom-Json).version +$publishArguments = @("publish", $projectPath, "-c", $Configuration, "-o", $publishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { $publishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" } @@ -39,7 +45,15 @@ if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { if ($LASTEXITCODE -ne 0) { throw "CultLib publish failed with exit code $LASTEXITCODE" } -$quicPublishArguments = @("publish", $quicProjectPath, "-c", $Configuration, "-o", $quicPublishRoot) +$webSocketPublishArguments = @("publish", $webSocketProjectPath, "-c", $Configuration, "-f", "netstandard2.1", "-o", $webSocketPublishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") +if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { + $webSocketPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" +} +& dotnet @webSocketPublishArguments +if ($LASTEXITCODE -ne 0) { + throw "CultLib WebSocket publish failed with exit code $LASTEXITCODE" +} +$quicPublishArguments = @("publish", $quicProjectPath, "-c", $Configuration, "-o", $quicPublishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { $quicPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" } @@ -63,6 +77,7 @@ $expectedAssemblies = @( "GameCult.Mesh.dll", "GameCult.Mesh.Quic.Native.dll", "GameCult.Networking.dll", + "GameCult.Networking.WebSockets.dll", "Isopoh.Cryptography.Argon2.dll", "Isopoh.Cryptography.Blake2b.dll", "Isopoh.Cryptography.SecureArray.dll", @@ -88,6 +103,9 @@ foreach ($assembly in Get-ChildItem -LiteralPath $publishRoot -Filter "*.dll") { foreach ($assembly in Get-ChildItem -LiteralPath $quicPublishRoot -Filter "*.dll") { $publishedByName[$assembly.Name] = $assembly } +foreach ($assembly in Get-ChildItem -LiteralPath $webSocketPublishRoot -Filter "*.dll") { + $publishedByName[$assembly.Name] = $assembly +} foreach ($assemblyName in $expectedAssemblies) { if (-not $publishedByName.ContainsKey($assemblyName)) { throw "CultLib Unity package is missing expected assembly: $assemblyName" @@ -112,6 +130,26 @@ New-Item -ItemType Directory -Force -Path (Join-Path $outputRoot "Third Party No Copy-Item -LiteralPath (Join-Path $quicNativeRoot "MSQUIC-LICENSE.txt") ` -Destination (Join-Path $outputRoot "Third Party Notices\MSQUIC-LICENSE.txt") +$meshPackagePath = Join-Path $pluginRoot "GameCult.Mesh.dll" +$meshPackageVersion = [Reflection.AssemblyName]::GetAssemblyName($meshPackagePath).Version.ToString() +if ($meshPackageVersion -ne "$unityPackageVersion.0") { + throw "Packaged GameCult.Mesh.dll version '$meshPackageVersion' does not match package '$unityPackageVersion'." +} +$meshPackageMetadata = [Text.Encoding]::UTF8.GetString([IO.File]::ReadAllBytes($meshPackagePath)) +foreach ($requiredType in @( + "GameCult.Mesh.CultMeshAuthorityTrustPolicy", + "GameCult.Mesh.CultMeshRouteCertificate", + "GameCult.Mesh.CultMeshHttpsContentTransportConnector")) { + if (-not $meshPackageMetadata.Contains($requiredType.Split('.')[-1])) { + throw "Packaged GameCult.Mesh.dll is missing required API '$requiredType'." + } +} +$webSocketPackageMetadata = [Text.Encoding]::UTF8.GetString( + [IO.File]::ReadAllBytes((Join-Path $pluginRoot "GameCult.Networking.WebSockets.dll"))) +if (-not $webSocketPackageMetadata.Contains("CultNetWebSocketSchemaClient")) { + throw "Packaged WebSocket assembly is missing the Unity-compatible CultNet client." +} + if ($UpdateTemplate) { $templatePluginRoot = Join-Path $templateRoot "Runtime\Plugins" foreach ($assemblyName in $expectedAssemblies) { diff --git a/scripts/verify-eve-getting-started.ps1 b/scripts/verify-eve-getting-started.ps1 index 26c69f14..4a87b19d 100644 --- a/scripts/verify-eve-getting-started.ps1 +++ b/scripts/verify-eve-getting-started.ps1 @@ -32,7 +32,7 @@ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sourceRevision)) { throw "Could not determine the CultLib source revision for local package identity." } $verificationNonce = [guid]::NewGuid().ToString("N").Substring(0, 8) -$cultLibLocalVersion = "1.0.34-local.$sourceRevision.$verificationNonce" +$cultLibLocalVersion = "1.0.35-local.$sourceRevision.$verificationNonce" $surfaceLocalVersion = "0.3.3-local.$sourceRevision.$verificationNonce" try { & (Join-Path $EveRoot "scripts\pack-dotnet-surface.ps1") ` diff --git a/src/GameCult.Mesh/CultMeshClient.cs b/src/GameCult.Mesh/CultMeshClient.cs index 19a01c54..230ce55b 100644 --- a/src/GameCult.Mesh/CultMeshClient.cs +++ b/src/GameCult.Mesh/CultMeshClient.cs @@ -27,8 +27,8 @@ namespace GameCult.Mesh public IReadOnlyList? Connectors { get; set; } /// - /// Gets or sets streaming content connectors. TCP content delivery is used by default; - /// applications may replace it or explicitly add legacy RUDP. + /// Gets or sets streaming content connectors. HTTPS and loopback TCP content + /// delivery are used by default; applications may replace them or explicitly add legacy RUDP. /// public IReadOnlyList? ContentConnectors { get; set; } @@ -184,6 +184,7 @@ namespace GameCult.Mesh }, options.ContentConnectors ?? new ICultMeshContentTransportConnector[] { + new CultMeshHttpsContentTransportConnector(), new CultMeshTcpContentTransportConnector() }, options.RealtimeConnectors, diff --git a/src/GameCult.Mesh/CultMeshHttpsContentTransport.cs b/src/GameCult.Mesh/CultMeshHttpsContentTransport.cs new file mode 100644 index 00000000..d6427470 --- /dev/null +++ b/src/GameCult.Mesh/CultMeshHttpsContentTransport.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace GameCult.Mesh +{ + /// + /// Opens immutable content over an Odin-certified HTTPS route. Provider + /// identity comes from the verified route and TLS endpoint; the transfer + /// service remains responsible for checking the advertised chunk hash. + /// + public sealed class CultMeshHttpsContentTransportConnector : ICultMeshContentTransportConnector + { + private readonly Func _createHandler; + + public CultMeshHttpsContentTransportConnector(Func? createHandler = null) + { + _createHandler = createHandler ?? (() => new HttpClientHandler()); + } + + public string ConnectorId => "https-content"; + public int Priority => 0; + + public bool CanConnect(CultMeshTransportCandidate candidate) => + candidate != null && + Uri.TryCreate(candidate.Endpoint, UriKind.Absolute, out var endpoint) && + string.Equals(endpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase); + + public Task ConnectAsync( + CultMeshTransportCandidate candidate, + CultMeshSessionTarget target, + CancellationToken cancellationToken = default) + { + if (candidate == null) throw new ArgumentNullException(nameof(candidate)); + if (target == null) throw new ArgumentNullException(nameof(target)); + if (!CanConnect(candidate)) + throw new NotSupportedException($"HTTPS content connector does not support '{candidate.Endpoint}'."); + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(new CultMeshHttpsContentTransport( + candidate.Endpoint, + new HttpClient(_createHandler(), disposeHandler: true), + target, + candidate.Generation)); + } + } + + internal sealed class CultMeshHttpsContentTransport : ICultMeshContentTransport, ICultMeshVerifiedTransport + { + private readonly HttpClient _client; + private readonly CultMeshSessionTarget _target; + private readonly string _routeGeneration; + private bool _disposed; + + public CultMeshHttpsContentTransport( + string endpoint, + HttpClient client, + CultMeshSessionTarget target, + string routeGeneration) + { + Endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + _client = client ?? throw new ArgumentNullException(nameof(client)); + _target = target ?? throw new ArgumentNullException(nameof(target)); + _routeGeneration = routeGeneration ?? string.Empty; + } + + public string TransportId => "https-content"; + public string Endpoint { get; } + + public bool IsVerifiedFor( + string verseId, + string authorityRuntimeId, + string protocolId, + string routeGeneration) => + string.Equals(_target.VerseId, verseId, StringComparison.Ordinal) && + string.Equals(_target.AuthorityRuntimeId, authorityRuntimeId, StringComparison.Ordinal) && + string.Equals(CultMeshProtocols.Content.Value, protocolId, StringComparison.Ordinal) && + string.Equals(_routeGeneration, routeGeneration, StringComparison.Ordinal); + + public async Task CopyChunkToAsync( + CultMeshCdnChunkRef chunk, + Stream destination, + CancellationToken cancellationToken = default) + { + if (_disposed) throw new ObjectDisposedException(nameof(CultMeshHttpsContentTransport)); + if (chunk == null) throw new ArgumentNullException(nameof(chunk)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) throw new ArgumentException("Content destination must be writable.", nameof(destination)); + + var requestUri = BuildChunkUri(Endpoint, chunk); + using var response = await _client.GetAsync( + requestUri, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + if (response.Content.Headers.ContentLength is long contentLength && + contentLength != chunk.SizeBytes) + { + throw new InvalidDataException( + $"HTTPS content length {contentLength} did not match advertised chunk size {chunk.SizeBytes}."); + } + using var source = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + await source.CopyToAsync(destination, 81920, cancellationToken).ConfigureAwait(false); + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _client.Dispose(); + } + + private static Uri BuildChunkUri(string endpoint, CultMeshCdnChunkRef chunk) + { + var builder = new UriBuilder(endpoint); + var separator = string.IsNullOrWhiteSpace(builder.Query) ? string.Empty : builder.Query.TrimStart('?') + "&"; + builder.Query = separator + + "chunkHash=" + Uri.EscapeDataString(CultMeshCdn.NormalizeHash(chunk.ChunkHash, nameof(chunk.ChunkHash))) + + "&recordKey=" + Uri.EscapeDataString(chunk.RecordKey ?? string.Empty); + return builder.Uri; + } + } +} diff --git a/src/GameCult.Networking.WebSockets/CultNetWebSocketMessageIO.cs b/src/GameCult.Networking.WebSockets/CultNetWebSocketMessageIO.cs new file mode 100644 index 00000000..de10a66e --- /dev/null +++ b/src/GameCult.Networking.WebSockets/CultNetWebSocketMessageIO.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace GameCult.Networking.WebSockets; + +internal static class CultNetWebSocketMessageIO +{ + public static Task SendAsync(WebSocket socket, byte[] payload, CancellationToken cancellationToken) => + socket.SendAsync(new ArraySegment(payload), WebSocketMessageType.Binary, true, cancellationToken); + + public static async Task ReceiveAsync( + WebSocket socket, + int maxMessageBytes, + CancellationToken cancellationToken) + { + var segment = new byte[Math.Min(16 * 1024, maxMessageBytes)]; + using var message = new MemoryStream(); + while (true) + { + var result = await socket.ReceiveAsync(new ArraySegment(segment), cancellationToken) + .ConfigureAwait(false); + if (result.MessageType == WebSocketMessageType.Close) return null; + if (result.MessageType != WebSocketMessageType.Binary) + { + await socket.CloseAsync( + WebSocketCloseStatus.InvalidMessageType, + "CultNet requires binary MessagePack WebSocket messages.", + cancellationToken).ConfigureAwait(false); + return null; + } + if (message.Length + result.Count > maxMessageBytes) + { + await socket.CloseAsync( + WebSocketCloseStatus.MessageTooBig, + "CultNet message exceeds the configured maximum.", + cancellationToken).ConfigureAwait(false); + return null; + } + message.Write(segment, 0, result.Count); + if (result.EndOfMessage) return message.ToArray(); + } + } +} diff --git a/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaClient.cs b/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaClient.cs index 0a233ab7..394fe207 100644 --- a/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaClient.cs +++ b/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaClient.cs @@ -37,7 +37,7 @@ public sealed class CultNetWebSocketSchemaClient : ICultNetUriSchemaClient, ICul /// public void Connect(string host, int port) { - ArgumentException.ThrowIfNullOrWhiteSpace(host); + if (string.IsNullOrWhiteSpace(host)) throw new ArgumentException("Host is required.", nameof(host)); if (port <= 0 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port)); ConnectAsync(new Uri($"ws://{host}:{port}/cultmesh"), CancellationToken.None) .GetAwaiter().GetResult(); @@ -46,7 +46,7 @@ public sealed class CultNetWebSocketSchemaClient : ICultNetUriSchemaClient, ICul /// Connects to an exact WebSocket route. public async Task ConnectAsync(Uri endpoint, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(endpoint); + if (endpoint == null) throw new ArgumentNullException(nameof(endpoint)); if (endpoint.Scheme is not ("ws" or "wss")) throw new ArgumentException("CultNet WebSocket endpoint must use ws:// or wss://.", nameof(endpoint)); ThrowIfDisposed(); @@ -69,7 +69,7 @@ public sealed class CultNetWebSocketSchemaClient : ICultNetUriSchemaClient, ICul /// public void SendCultNet(T message) where T : ICultNetSchemaMessage { - ArgumentNullException.ThrowIfNull(message); + if (message == null) throw new ArgumentNullException(nameof(message)); ThrowIfDisposed(); var socket = _socket; if (socket?.State != WebSocketState.Open) @@ -92,7 +92,7 @@ public sealed class CultNetWebSocketSchemaClient : ICultNetUriSchemaClient, ICul /// public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { - ArgumentNullException.ThrowIfNull(callback); + if (callback == null) throw new ArgumentNullException(nameof(callback)); ThrowIfDisposed(); lock (_handlerGate) { @@ -161,6 +161,6 @@ public sealed class CultNetWebSocketSchemaClient : ICultNetUriSchemaClient, ICul private void ThrowIfDisposed() { - ObjectDisposedException.ThrowIf(_disposed, this); + if (_disposed) throw new ObjectDisposedException(nameof(CultNetWebSocketSchemaClient)); } } diff --git a/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaServer.cs b/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaServer.cs index 240311a1..fa2728d8 100644 --- a/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaServer.cs +++ b/src/GameCult.Networking.WebSockets/CultNetWebSocketSchemaServer.cs @@ -178,44 +178,6 @@ public sealed class CultNetWebSocketSchemaServer : } } -internal static class CultNetWebSocketMessageIO -{ - public static Task SendAsync(WebSocket socket, byte[] payload, CancellationToken cancellationToken) => - socket.SendAsync(payload, WebSocketMessageType.Binary, true, cancellationToken); - - public static async Task ReceiveAsync( - WebSocket socket, - int maxMessageBytes, - CancellationToken cancellationToken) - { - var segment = new byte[Math.Min(16 * 1024, maxMessageBytes)]; - using var message = new MemoryStream(); - while (true) - { - var result = await socket.ReceiveAsync(segment, cancellationToken).ConfigureAwait(false); - if (result.MessageType == WebSocketMessageType.Close) return null; - if (result.MessageType != WebSocketMessageType.Binary) - { - await socket.CloseAsync( - WebSocketCloseStatus.InvalidMessageType, - "CultNet requires binary MessagePack WebSocket messages.", - cancellationToken).ConfigureAwait(false); - return null; - } - if (message.Length + result.Count > maxMessageBytes) - { - await socket.CloseAsync( - WebSocketCloseStatus.MessageTooBig, - "CultNet message exceeds the configured maximum.", - cancellationToken).ConfigureAwait(false); - return null; - } - message.Write(segment, 0, result.Count); - if (result.EndOfMessage) return message.ToArray(); - } - } -} - /// ASP.NET Core endpoint mapping for the CultNet WebSocket schema server. public static class CultNetWebSocketEndpointRouteBuilderExtensions { diff --git a/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj b/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj index 6ced9b42..3144d7fe 100644 --- a/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj +++ b/src/GameCult.Networking.WebSockets/GameCult.Networking.WebSockets.csproj @@ -1,14 +1,18 @@ - net10.0 + net10.0;netstandard2.1 enable enable CultNet schema-v0 WebSocket host adapter for browser and web runtimes GameCult.Networking.WebSockets game;networking;websocket;cultnet;cultmesh - + + + + + diff --git a/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs index 246e510a..42f02297 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshAuthorityProofTests.cs @@ -1,6 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -173,6 +177,54 @@ public sealed class CultMeshAuthorityProofTests connector.Attempts.Should().Equal(valid.Endpoint); } + [Test] + public async Task AuthenticatedRemoteContentUsesCertifiedHttpsAndStreamsTheAdvertisedChunk() + { + using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); + using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); + var payload = Encoding.UTF8.GetBytes("certified-content"); + var route = CultMeshAuthorityProof.CreateSignedRoute( + "aetheria", "aetheria-daemon", "https://provider.example/cultmesh/content", + new[] { CultMeshProtocols.Content.Value }, 0, "content-generation", + CultMeshEcdsaP256PublicKey.From("provider-content", provider), "odin-content", + Now.AddMinutes(-1), Now.AddMinutes(5), odin); + var descriptor = new CultMeshVerseDescriptor( + "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + authorityRoutes: new[] { route }); + using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); + var handler = new FixedContentHandler(payload); + using var manager = new CultMeshSessionManager( + discovery, + Array.Empty(), + new ICultMeshContentTransportConnector[] + { + new CultMeshHttpsContentTransportConnector(() => handler) + }, + new CultMeshSessionManagerOptions + { + Trust = new CultMeshAuthorityTrustPolicy( + CultMeshAuthorityTrustMode.AuthenticatedRemote, + new[] { CultMeshEcdsaP256PublicKey.From("odin-content", odin) }), + Clock = new FixedClock(Now) + }); + + var session = await manager.ConnectContentAsync( + new CultMeshSessionTarget("aetheria", "aetheria-daemon")); + using var destination = new MemoryStream(); + await session.CopyChunkToAsync(new CultMeshCdnChunkRef + { + ChunkHash = Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(), + RecordKey = "cdn:chunk:certified", + SizeBytes = payload.Length + }, destination); + + destination.ToArray().Should().Equal(payload); + handler.RequestUri.Should().NotBeNull(); + handler.RequestUri!.Scheme.Should().Be(Uri.UriSchemeHttps); + handler.RequestUri.Query.Should().Contain("chunkHash=").And.Contain("recordKey="); + } + private static CultMeshAuthorityRoute SignedRoute(ECDsa odin, ECDsa provider) => CultMeshAuthorityProof.CreateSignedRoute( "aetheria", @@ -326,4 +378,30 @@ public sealed class CultMeshAuthorityProofTests } public void Dispose() { } } + + private sealed class FixedContentHandler : HttpMessageHandler + { + private readonly byte[] _payload; + public FixedContentHandler(byte[] payload) => _payload = payload; + public Uri? RequestUri { get; private set; } + + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + RequestUri = request.RequestUri; + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(_payload) + }); + } + } + + private sealed class FixedClock : ICultMeshClock + { + public FixedClock(DateTimeOffset now) => UtcNow = now; + public DateTimeOffset UtcNow { get; } + public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken = default) => + Task.Delay(delay, cancellationToken); + } } diff --git a/unity/org.gamecult.cultlib/Runtime/GameCult.CultLib.asmdef b/unity/org.gamecult.cultlib/Runtime/GameCult.CultLib.asmdef index 100db084..bd13cb38 100644 --- a/unity/org.gamecult.cultlib/Runtime/GameCult.CultLib.asmdef +++ b/unity/org.gamecult.cultlib/Runtime/GameCult.CultLib.asmdef @@ -14,6 +14,7 @@ "GameCult.Mesh.dll", "GameCult.Mesh.Quic.Native.dll", "GameCult.Networking.dll", + "GameCult.Networking.WebSockets.dll", "Isopoh.Cryptography.Argon2.dll", "Isopoh.Cryptography.Blake2b.dll", "Isopoh.Cryptography.SecureArray.dll", diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index 0e9e063f0e1eb4e6b422e481c73891e311b7c0c2..2fdc1ff90934f068ecad258a10f1b229a7f08b48 100644 GIT binary patch literal 45568 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb index 56d08c92eef104d2d1e3d7b401e9a07b48f057e6..1b4555df2c2c008dccf5ae33ec5ee0c027dca9d3 100644 GIT binary patch delta 12251 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 Xw&>s)*})L?GJbcBpZmFn;Y|NO($0JQ delta 9068 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll index 98c1020682e96095f4623614526d97f0d3d4130b..4d90585e932b6b1706e3b6a74b7fc03263176086 100644 GIT binary patch literal 90624 zcmdS?d3;nw_6CgCz1@Ag)18ELIwT=$2S~W269{{dMHUrB5L84^RNO^@i)lb4bPS@n zfQMt5VaffNBZe{+6HH zd#Z0TX0<3a$5JYysf$lQIEip4e5HycZ0mB9!1BxQVemocFQDe1%BcL8^$jEmze>oR z!2sl5-z|g$^Si=Q`3ROQx6~}3_J8q{-9?w!5#Zf((hXXApeX8G#)z6w!kZ3k^2b z*zIVbAS4Ge5&$9O6C?lv)T3ia0EDnlkN^lUC>=urAmsT334jpw2@+5$(PFFpOzrKC zeyE=0Z-UI(1evD^GB*=sKDLW0r!vgJc2T=jhO%w9UUY>#V}MdK34nlx)C38DP~a0J z079WpkN^n1e1Zf(fZb~u5&$9Y6C?mau}_e|N>gHHX@blwrlVb_yh^~Y9ZGYl=K_{wSIgFKtR$bNC1R7pCADc&`WfM zBme^JN)sdiLVuqi0T5s;I)(&5Xz&RV_$51Kc}$U65))(@Opv+TE(|x7VKyen3~U!R zoXSwNU(2I^Fe^g@{wy~1FoN&HZzS$rom zNc?bSu=tToi}-n&A>v0fL&c9}hKZk_87_W7W(0f|g1OLVHuRYfeda@-`Os%R^qCKR z=0l(P&}TmMnGb#DLtpY?9Yg9N)DMOhS83R;6LoBN7JLtV-cbPi5TL6I*p~o(OTc~v z=(z%B6QGjbTHe zv^n6UOtmB-lra)grDsY2gi$_00wA>d1POpJ+9yZ=gq?hX1V9+$6C?n_Sf3yP5XSig z3HV)*!PNU02GrKUT2X!d1oeDO(MH;TN;{PjEAnG)Qvq@EDOF%6|+Mu*cNtYE1 zH^th}+m@tZz0sAW=@rN&irivsQ&?~`KUE*`V8YP?<>b{j#~a#E!X-KlEIPW)*B~cgVo`MQc57Yo1B_#D|r=p^zF{jvaVI@(Oz6pu*m8i5J zl1AG_GjAe}atAOw47X7ftR)`wDj5u^c*s2+wMaO5amQW5`b659Antg319`BlXag#u z>-glBf_Shg0;p@L+`S<-(*71w)HjzmL?T_z%`86bFIu07WXOm|+yY;xiEye=2n zNR1D;7-mwjFpMV=iieu2RNHMZh=2zhkG7$ymbeoTO2fpXD+5xoXm@Ir*C=B{DasL+ za%{$kROzB4L|xiZG^HG|@qj{0a=D&URxDHGrIYeEnzhl16UON^dFFQ)CpwXrWN_hi-kQF$!RCG z*p_mMPG+b`dKv`PYZjI3D0Cwk%%LX%GMGb4S%7*0lzO|;Pfv%Na9Zz!nFpON=N1|i z3_a>%J(eoSqyED%5a=HR_Y$2`TYFD6s5~zYF&&s!a{JZ>%GwRG>O0AC(K}P#6yQVz zy&~f6&S0L>)r;p9M|IU=@mNz4%r)w+ld`>2gR&{a7kg!|h`?U`z904uL^gRI^y?G{ zy*(&1Kb}AO7NkK}+OVJuH7JM|G!;W+uymsl+WIuum1f#%dlB`ys=iE1xvIXr?PnqM zX`61VCGlX#pL$m8H>{W*G{|vL`BcZlJ>#6sI8&wa-1#zXOdZTQYXq33TpOHup#vM)`L%ye;i(V$qn<1K7w7k>)^2syG6h-9%Nx zj-xI*qS$PJHaq%!6i4Ohr0yxSC74Z9YR8O?badLN(qUx25Se#3$pO*=WjV#5l`Qw$ zMXJD+5*uejcjM$qwM-(8@b0N_R@$aNODc9&*ewT}1cvCQVXEz12r!Ar6@fD7U@D-L zUl7RoEr)D^QlTAkGfWIYn1Q#~Zlq~WBoQr5#LD8)4GZR+g;Bsxqq(&-U2Z0dXtb*@)XT)Ap$2fVhS>)|TNeAg-agHf19oT|+CeJ;(r!uBN4|QW$Ik zb3Dc*`BnSl!Hv4PZ8q}5x{>wF!}g5^*&8c^G8J7_U)8o33EAGL=Y)xnh>jRf-zGM1 zXSUGhopyTO{?r$C*AA@rc3STo5=vmb=unZctP)aINq$OUhG7{C)KZvZq4Z^$8S9&? z8e~Sae{n{P+U{AXXe`|EXXKZ&-Z_W4%|&kYo#eQ^k!OCmUgwg zobwpx0Fgva$74O&=aYS)u*uQmUm5#nxa0AjQ7>TBgCr_BZaHGPSo5S3LC5MgK$aXP@q_EzQ!6O!NJ!I`2jI4#=MBGzB zC@H~Ak%qZDi9Fb49_+H0>I40wGhi(BDiEjRiJo=1kaajj>OhWLiF`Vq>>1}G#+fg1 z$Z@OudPZ%p7(CmRL_E43|jh7DHu5&PxoYBLiS<8CPz1AH45)&C551*5bcz6PCoQhL_MVsM+)pM zy(8e)uJVqA7ZA{ZES~9^#YSe)AX$(jn?esmK{VESDFlbTqY=(mj*%1bP#niQ2E^K( zL~_J~VjZ2APeI1pQ6SN24!4CuYDp^87Khg}#j)VmH#-e-Ue=R^yqL6)PFm+DZ9v($ zF4<}B&p-bx)1kkRcDx0k7o@Nl)pO$R>XvW(Inh=hLQhyXbnB@t<%_i4MrcEFZ`;!+ z26BjLpV&|uYsC>S6c+YS^k{y#w978Y!$R7rZ4$LKVEXutA3aBFZC7TAR9ueqyRsZA zFo*MOmSYt8IVe!%e|$z;z%eR~uyLrc9isI1qclWVXsrG%S^XEGs5^J_RH`p3(bu#s z<7EqC$gJBQ-3cPzVk3;(h5sgu@qSU*(Y~;Vw-9lDq42dCqt$TPoLD&M9S5a%1rtu( z*=a@B&p$hD8DB($ZhmaRu+wTp6D3+4H8!9WmVg{XFg5g;phN{TR6sB2Z7&SISa?Y1m*y@>+!?8vpl_6zz@>q|E0&^)kg4{JaUE^s@Z?b+{s%YKK-Vmctlt>KVV z3nx7f9Q0Q`3F~PWL$uTJTu%X)Qote+K#rU2At2_2-StR?Mdq=HSc;~GuA0)SHtJ8c z+*M$)tBcC{)+~0l3OL`cfle{s>auXY)iJ8IUaqRIX?sy()aJGQ1%Zfn9I|rJ%F&!j zd1cxIp+HN`T`jOo+~CYpdFe4ID%Xf@@30wLfw1l9UITWQlx@S3Cc-ROAHN(GvOCmy zdOPXF-V)@>hSP_}OxKLj}#C3T6GVUAB2JWaz>> zo`v{-VOnK+uZXSXB8cmFzNZOX23ooeS=4uui^A}1%+ z9+E20OGBi$CxPCi>S?AD{irsG{v>1PQpAZ$)5g-v5Y87D&3q8V2gKEjJ>T@kE2q0b zxA~f7WNFZdX;4kbUCMjQA)O6*2O0AC`=RoQU_i&++%xVK*|_BB zxFKaCztl7GmD$MTxc#Zs6OmCO+**SUj#HbJL=o(Cu_Hbd(Tnpu9KS?a-)J0bX6$-3 zg9m6)6&53}Z>vL9wW-y&%|O^hT8~lNLijL9ax+MvElQhl>rI@qBHrO(#`DC>Dwn(V zW{k)sdD{uo5lu3;gEF-)K{`T9qSs}y$k@3z)k4a_+evXx>XGh9osOPP#|=KyUH{+H z9i`LJ%a*(_Uf8rV(!Rf4T2zq{xgKa6AC+45z}wa3Xf2zbE>o{~FDcVBotBLw>fNp` z$LMtQvSliY7fG2mZkHBiV#M4s^}xAh@{jm-di^IXO?sa4`G)**PkXxxvW+2=lVfl3 z)WUdN8(T3}d+}m1Hm?)Sl1Ri8O#zkKGvZTwSlU;pf^TW%F{n`|)s|NL%a*2)7gtry z>rn1EPJ811Nw2pWWLS#hos2+O28oEb2H`w$(+g0ALtuUg^YN(%ej+kK23m5^&6yePXpCE9aQA(l+EEx84qn( zF!XH6{|x4zSDJ{no`({r?_v?q=i)dg+n9MziV$n3@12R5B|PSeHusGi8YW?%hHbmV zlcJ`qGU;==w3^|kISXmxu`cKC?$VlsJCrtx(qgcQ=WST9^I=#AI~}i}AUV6vt0_Y7 zI?2)d$ya;ExrT9|J`AGd@PS0iZGaPVI$rA;_1dgBaFRvbB5arusi3PjUq z!zl=FXc`#q_)E{E*JYEE;|@R}%I4s>AQ4pIL_uky5XYAV@xqOXUMh}W?kz)tL{V{( zdoE;pD}XTS_VU}Z5Zb{Q6E8qpKBXdwcxj?IH6R|37jH}?R6G$6)+0vHPnYo16@ywF zFOJ8N?xnnrH+mNG`fMS|$=1&pKhIKHU@0(dtT$a4O|;{$Jw@C=5$A(Z-${<^qMmXx zUWw;wUm$I7rZtJZ3lTBedOOIDcM-xd?0hbUFXs>Wk+$p5x7|xXD99TPEt`kbe2&05ua_#0b@d*F{b)Rf)ToxD_h^%4kA`Peju}pJ&T7_5(^5cCSRZEtapA=>6kWjSOOG1?o)B) z++?@21|5HcBjd*V2y{93QxThxZ+#~@?m$>5hAax)8(5y;N&tj?e1ZhzxeTJKGPIlB zzAuyZ zsmlYb%Ux0za@-VhMTYFBIDnF0Zt4$vrhbsA@0Qf$=rfomi0JsJXT*oH5y{aJo0%61 z$ySm)9d!`8GexAhv3+n?G9r0e*!^j z`7i=q&Yv0S5z;!zaR)OFtROeeAN?k5?3vakDlyu1L2ivNm+&9q>)a#Fk1@S+@gI?+R55#+6 zq|gtJ)c7qn^DC^?2o#_}NB7>;%(s7$`IctB(~}AP zW=-~ElrG0)|HP=;=*Y<$o%b4Y##7OV_c{W2WH0r~^WI}Y{b%&=_jpFHdx$9~rd8aw zGh^-FRNs&lgZj|!e8Q|$HTCxiGRIK+2BKzgE*gX?ak3e7dS`Ip8FW^WR;=8SY%c*W zY9GbeXseweqlPlJ<;cLnCuLO93uJJHOHnuzg($Kh3Nr>o5ol4;o<~AfE!!K4+6_wZ zpvGu4l|Xzn6lOsigUU@(Z;;*rsriE~y^2*=>3%Q>|Dn1I^LAie7s12o6_3CZKwA(y z{Vp8Nn@}8W(w~2lgLvGmb&`OeWEt#$djn&-?;(+Xh|T&Q4t^c^0hksB;aFSNv~;Nh z-d3bQ&Un6%ui7eUc@sfd83=k9YFRWXVZejo|F;r0c9#K#W!|}E!)&rc*`PDkmu-P; zICWwik7h6cvTI-@W6%4DUN8d_i=s(Bp|^@Lpifu>nuNgp6prK>;ByewPwOM-jz*O3 z=lQnG)%}^@7rO1YAz3ELX0|i^=>;-%^n#hb^gfAH^ZH^muHTQBb@yYtofBw4xVS;^1=2RhitVo62OxSRRpfa0uuJ^|^z=XB$my`2 z10Yv5NS&lV_Oe_HQ6c0@3c<1oPdk=Dzu7U;=G>+RXg#>MgSU*Ox|WV_8v)vdUIPYU zJY!I=)#L|{r)z2vFzS;!Vlc)H8rm9fexy3SLiREw{0o6D=WDp>f0NcpjyulRKF8E^ zkQQ@pJpBz=m_@R;E;8SO;EwFJG7;#c&hs!(3`{Vct}h|A~J^-xvp9}N zevzy!6sfQ{q_c^Kx(i6zJ=zN0^MAF~43q_H0M=(}kNO1CA>@u)IZ_@ch@zSDVCX5r zn3XM0L8_oQXo?fm2FYil=(E~xLcFvx$uCUkmkh@r2ed81bi>G26zAJvPz{2-W?j}P zC@UgG5GlVHmyEM^E~As}l$5%UawuVAu`EN77U@|}lQt)f9@IjrG(T0COYZLcB~K z2R%E33^*>LMjRJWAC8MCEXPH(62?VF#JGr(VK^pmF$r@xmOFA7Bh}Ds`vkl;FZmj+=5j9;($!K*oDV~L4C%? zXFV}Mhw~c}D8!LD_!Z2-fgW=(mtFGo0aJEaW+rk`DehKe-b0CWDau?dCmj`%J!mBg zxGy5sHwPM01v)~B*Bj-`ovRO0LpJESx)Ntc11+V7Q5U8md1mHxd@53~GQ}*2ea$1N zo!nTd?Gu!~mesZn4I7pl4ziP!zMH{t7u5YBvWj8p zM;;b5(dIsS(#QS4t;}c>M?;Lu+&al~CY<&_-rg)2Ry$EX(8lQyRz^7Cfd<)~VHK(=E>awEaeWK78qF5GZu>1bZAhac1p@mRl1niar96e zZ2K*{M-oW%=Z_EE-#>;n8=>>!j1=&evI3$XEFW@L#E(*+b&yftNsfm#1UC;o3_jpt zNrC4rTFU+qg#r#hJvis&$nKfS!{P*HW%zTEN?&D;1!UTn`^>03E(6vhBA)Zgc@%C< z&V*!6KRJ>+KI>U()DKVBk*vOx9B%-EZ0YcJMR27Eei`Ob2{7jG(*1#>*MxwAP z4!bFYa!SUd9Jy;P2QaXcd3s99DHIYlA(6~X9T+ik)%rLK*bHucCpq3A=7Yy$@yIWh z5GCOtyVJ2WrUDoyE_axfwt3dx$GYt9>^->YXkX0zw59FZ7goh(2O6l{v|=Ju9MV%# zkk>Jy4W*nBt5j4l>9h)PXZI0r6St#(7vZ+w<+EzAeWFr&W z*-&&I6rJZQy1U;#tCsGpb)9>cO4(+=Gz3*>^3Qj^=-KX1_`_3uCpmhMa`c&y4AHzx z6;hqZYZTjWGgcV3T;nY}s|@1?-pk7N)vLUr$SUeooP|9JSfS>;U{inxiYcpnJ+z4H zTxHsx)>jEC4>iNUxT?lwD?G^!UgZvh2;MplV4c(pEj=B@3VXvr#gM-;d-9c;1qN>9 z??*3`nGKHzETqyWnZD2uu-johu=o6iw%>8DFcBuVlI#Obv^n&9oQ??vFry0w7?Z)C38DZ~zJL4>tZG#-C6BFznx?An^kK@FBy($-Ws{(QCf$ePqAxShk0G z6=!)|>y`WQZ1+VF&0a<39$D})4wed9mhHZdI9yY`EQg=Dxc- zjE%MlbB>^GagEeUJ4j=-=G5!9O^k^rdc@6#{&l!tqAYRstSuQg0RHr4y_p_5vX+ zbD>N!%b{}Fzy(~aUQ=>rieV^~k%n5#<>f0#f!cFU)#m}Pks!LBfjaYN>r+$Oh3$4H z&pq;`G1=)>6e`1}H$k?(3C@?a^~gju&bHl8mvC71rKn9opA7!p%q&q z%xdxQMm#`w+R4T~GvYVEWN8MV(GJ2Mmbi}BNe8P_s30EEG`qel`~}GAi6FZl zB9zF}Jp-d9m%_L}o3H1TsBGt(M#iIja>=LY^z?JzrX;A{#tT+=So1kG@F_cn^WA@0 zJkrS0BOc{imqUM%?p|O_Vv(^!k}l?BJhsF`CO*l8!gl@=SD2uEc4==iqC4%F)i7+8 zH}J&qKOC!fgYncSiZW!xi{wVXi^WGOQCOPjl^TktT)j3XFwcq*JmD&qIaHnyBWw0( zJpPrU;K>=X#?!NSA)aqNVMlrzj?Cjq?X4;os)?{Gi~D1kt{S8JCkxrL|C?o(U8Z}J z9y03m9h(T zRo_XDI~BUrV$@0T6(?R2(`_gP#;Phe{kKeiH(Rlj9GxCRW6JJw{*A=x-I2eGK_|ug-YIVc z1>!cb^!z=@n+m5Pr~BfX?KVVm|HYWL`!PKzpZ9bwhwjH~-S3H1QKxML<`dhUMmb!U zuSd&Ra?ixY8V!f3;j;45B=3(Vk+d_t7l>F11xf=O7I-rNRHV$CNgr<~<-7o9^VKM> z;tEvQE^+sf7%AL#vE6+othSV^utbt!2rr$uvozyvl-0t-XQQO$v-N)5#wpgkQ)QZy z{Vm2oo+-nw`;=9u_d~*HdNy2KZ}ax2n6NhoUjCL+yfM{%|1*ol0%S-15f|`Bp8bP%zNiNenoTPL@&R_|o+n_t7UmX7QkhIOvIMhYHk~{XDRY4bxNb_@*>yLkEUWcbLh=rUdqZIzid|yCf}|c9a*)? z;;dJ9c9~<1?!-sAI1b(fpTF#KR8kGhraBI(dKkiF@Nz9>AtQv0rG#K9s~I83QWh}+ z-Z5P)ekkLK?_`#UAI=;vek6nW4e{{GCY}Ui$DGPwLIVK@vmoq40;VxB43>$F!>s8u z2l~u`K69YY9OyF#`pkho1=AOKqK65hgL^@q0%>&A-RpA5R33YFTXW2 z%Z*TSDO@mhyz;-v?hOHL`%;RQeS*l&te}^x)CG)pbvE9Ki~yxVIQ&qiU3@3AO8jsp zBYq?U6Oz(UE=xth^qHPM)6-{q`bFG;)Z7Z`-kBPKp_Yj)xd^jE0C+Rr>doxU8 zKXOhu6pZ>#a`-k+ssmjIaS{zBT@I!?-2WFoseI8#KXDmf+m-$a%z8V6wJ`S(LG?27 zn4H$E$;Q2LM4AGo;gY7qlU#Sh3a{5Y_gM1S>x>`V8vZ=8Zdy<8r5@zB$~G zCogAk%&wj99St6rfTdkQtU2X`4!hN0X*|SBaX92r^1dGWHc$tae|`m+ar#&g>pMwx z_d^9-46HcWhzHy5M~UK2+n-pl;}9v9O6ZqrSO8lJ(7i{n)k`QQ&XBprS;)xYriilh zTOW|p^Jpf$2o6?3v&KQ2p3Dvy9Y`^vP{m>T*(fwvDd(pDg(VlMO{R4z=5vn3tyb z%vXQ!W*2Ffc1AS&(17eiF`l!%P6WMvJ%{J!{ta!RA}&Q%g66LV+bThFC%`V!=rPfi zc&@{uI9@wk^Qcbd2TW56D`CtJA`O#-Japx2CAwldVL%d!?c|;utb#NsH$yL!>3~P& zkxHLr)XqH#zSl{>ejHM7AkQhfeoVp_lvl$?N5DzNgzTG&JEIOb=h)0@%1MbBFmE>> zB%D40UKu~0VYxD%J{eM0I?K@iD&^oqZ|4qE-7mwPswE;_dD~4|hNEwOu~+L;%8>L} zO8%3?sLVbcGn}fn=d*c=dIRC~8VE2?DJF6LJ_Q)6ITaptK}cUPbg~whuL7nTFGTF@ z$&ZU2h1m_5?ftGG=zCJ^UD!XM{oBvR=$MlV8ay2W(Sv*WslDBpj>}hR=SukaL)vV0 zBrm?eXC6A!-iIL`c0t{F7|GBigqNuRcL)xwNebY2XCPOu9{6_OUUvFSzzkWYyXZ5o z#~_nGJH3wlbIDJyXYf3UI)Y|;7V(AIq}1U#gaH?;Wv2t%SUu2>N1)5evoQ{CK$`ka za`fUKCkm;|`AlTD)APjmx3>VMD)*qT7$Yw z)jDE<&9V=gIv2^HreZV@Ylicd^FTq`y+C2Lz4L*gy_{r@cLC#Qe!#m>111l87YWEo z>S9ZSMzx)wy9YzmZ%{pj@?Z*Jo8<^L<wc!?X(B)a>UVck6S(3Ogr%B!x|%^;d)0AtRkixGbSw}|b(MP?lJB&F4>sfy(M8!B z`}d_1YW?s0x&-9z54QflSeIe{qb^|&8*FsA+gTy+4!C?jO^VR`*ni1?!oJ!3=R=n> zekK2w|B-)w1jp}pBHQ40nLoK>=K2|7LXDfL^Uj@7p(LKgUraBQxda|(4N~coOke0+ zvKC>q!jct(0WL#wzqu842(S*ecNf+FBCx#~rew(s>fTM_eL_67CsD?S&1-uSF_t6w zX+4O|9ros9dQukJn@{gaIl$hGX*eg-=H2bhXZECwv^THoN$GEIUf+{aY;QiR2L%s) zbMN?EiUQ7LeRK8%GyM@A69=Y>Dvs!fqTcnLUgSVLcIOIqiDJt($jzu~5*0#kbzR~EG!Mt^T zFM=KW_LNhbl|zn}vjwLpv>WJx9eOZE2y0M!7K+}vkw$^@j(QXi7nB3?LUAKva<0OT zRDOITnU5K8T7pONaQJv(K0zM?a46#N>psFcilEv6G`8c_8gMK@odMYYI$l2mkd@=1 z5SW%;?p`jZ??ZVrm!XK}eE)vp%d_|a;@`0fQr_K6dAkJ!FF~)eMx^` zR#lmsz(6r>rWeZmfgUzHw}>CgV6Kp4jK$=PMxXKMGah|E-X>yx?NI_?+~yM`0K)A) zK>{G$;S(eP!ks=r0wCPw6C?n_-9AAAApFrMNC1R;e1Zf(xYs9000azZx}*{S;eMYW z0cp3O`Ud_!=!5AbdKj;1qWfTycMn2`j|IZ=U@Po)!Gc|^YjGq-3T}NAf^YT)?zi9; zaGi@`*INRQ{iscR+j|&>=PeZw^040mvsa+E`)86@=s51d5Qb;69(HSB+b-7aj)xr+ zK%~Td1W}tEKD5O+=XD4f1uISA<&dA*zdD7K=V7aZ6nK_V$I;?Z=724RCiU$mVVB{k z9(SikKnMHE{NNsflJJXFT-k>dzR*$Qct5i8O&Ca;BO2gqbF%C5@0ig3v3KLQSb3%> z1;&mMZ+*s6!}SSB6JGE=3I*C+gSjI+>)@;t+q-;BObfs)-55&+OJqZNu3aJ)29I_H zkF5xgZRZbbgScUEIv>#OnjP!Vo=#((#L+y zKbKyC`tX-tf|O`Z4=ky)cPI#KU~I!3?{EW-2ap3GzCbT*Y;+xd)mq6 zERDCw0G7k^2w0jVknAlmh|13|OD&`M#bB)NV$>1%CFo1wm!KbkUxNMwehKOc{1P+} zbeEtCh$X2uJ|~LCHv?WN{D4<3ZiTp2;-YauPm0@D-2QOW^O+pUm|RaV@}Ufbj}i*! zl7M_HA>eWL2*|e%bTYJ=ZZ=HTQkGxzIR;y@aM}+xh)psg;|$t;3|7TC<<_=?B@j$M9h2XKX~|?J=?L3ciHuWasBewtOcq z;62V>0E_LOx%UdlrxI-WIGTBg(9Jwd+qJDfF+~&KKE~95{)mcvmPgikSPfQTwMaJ0NBgRM^4!i`4 zTmjE2eyZL6GN^X@D{$TU7>J=VSzGK3A2p7rUj@g1XXG`6akuq#dV$Pe==ILLL2ngV zP%K6W+-4=v?|XDNF~{rPkg0Y3S42Q`m>^>Y*c#n2nZLd(JqP*Q?U)LJ0bAaNDa&4i zWM(x;+#qfI|4aPY+lhP>nv>5|*P~GtPLJB(f?Vk9ZFsWF&z5Z@3WDh`=)Fs0$SJZs zo5k{5&6P60gQS>^>{O@~QwjIPTR`zoHZlFg+H&7;co$6c47k}d#+)Sfs`tREcelcA zE@!TSK&d$qxNH5{o zt3E=YI8f}k7%Nlio%K>)atrKL^tyUufULB8`$-{JnkQwoB(*qN7Tt_^i}6L{M9*evut^KO-0KoMWWtFO0t^bR5TD9l{f__sDGp zp4U8M!Tx0^6bU(emf@1)q8L1Qopg_eSt79UAzB4pn0rPP+=@C9{d9=2D}@9MoPEKxcLQ$p64y0KURntvYcTZ9@L6oL47>{pDZ z&3^}W*!>r|7~lMFE|kLVG0&@o@XO%)KlY1LVdCrY_iO!PcceK``b9AcW%|VjA_1mS z0$TP}ybSacOf|q%7|^DIylE=*^sPnO0+ER%w(z`f3rx42EeOfj0<+7>wBr*PY0f*6 z55uO-E6U`^71qp5hTen4NsICMk~G)^X)xBw4rj{f;U(k*{9>!vc8`$e+Q)7uCuXmr zm(yBI20_wVTgnUAN+L-F<-{!NMOjZt=MNX%_3te1nSCbSg;x~;HuW?RwIg1jwyOeo0+ zO-Gd$d(v+)rrxf_gk)NbndW5O@%NrB_UCMik@HI}#&=h8T8uTeBNda5G!NHLzw~eIj&)g7c358D8qIMi-Oaog5)Mb_A2^ay?^ho3M4V) z{Rnv(?B@8`i(aOl;Sx=1Cy=?8BVvRNC+SLbEXfcrJp2S1W<=b9jWZE)8*|qKqtG7X z@mGfePL%b)p!8R;#!eU6WmjnauHay3gj)p+ne<((qwp4gKtAdw&%Ap<&M)i-x|e#B z@V^B>a==rz-Zl+N|G?~q^vDidearBVHMQA z+mw&z9au}*xbb4GO04`ey4k6t*<_4(AfP zw+KW4U;c+=_ZVo>3$kufnA)F&-})7jAPQ+-h+LLYZBBwJM>66+0L06bDck03w zVBJ=JOO-~q;2tz=*}-@AIlrS#f-E_HIJc0$h=!`oC|awqDc`8F4E@^GQzSbqNci>LX3L^DZix%yiZ&$;2}+A@Xf8JBEMEgS z0>*eTg`y;-R)Pc>M^n3;-y*!d4$6%Vwv6!$B?@3#Q!Y`!{-OX{7Zt$r>P{agg`GaP z&-a1d@GrN|v%0z^*{h{Rb+6`tpqZ3Prf8$P%d@3&y1YPWjpr}y^M`fYr|$FYQrSK) zf~C(Fp`h&Zdw`!|!%3en2NfN-5?<#FX4E||=FE791)3584=sw9{{tQbaaqF~#yB2F zH{?@?#=hprJ}cl%yy*pr0eN!n-++RV%dJ6|yc)po ztzrCt7jr)ce>kgCa{=lR>0n67~X{s zWy-j%)N?M=a07Ej{nbMpHLZc}+5KvV^iq!wA$?|lcYvdYH4JWa)VBU~AE~Ds7593f zJTC4HLV2klxgl|P>qlwx83esKP%F_14hsTE2j|LP%8_1CRTe0Y z`d(@}tUv401Z{L`^OtChA}oMg;5rH;M(Q`(8+q(uhb zG2Bu2RnuK1+)2q(hWy5qvv6E;;BVAK7|0=(F8Q&;nCkvU(VMj3Aprkwdm6;)G)Q_uUOm7{8)4vU1J2Q%hi(aiVbsFR~5{L=u2YpdzrK7#HEb#%WP zF0SZpWDUb-iS3==L^**J?P}d{hQF5jE|t2s^pae}b*21!NQ>+)?%*17|0y-yTcpj@ ztqcos)L$Cu?t#*>r)9VaLXSkh!&Q&ke%B0}*o}t3iikKC6sce3+?#qW0ZB%<(fx05~CTx&sUn_u^lZO#qruG*UOA=k7<_WsBiRfzVni%u36wys; zfuJ{pw@G<9G9OUugohywG2c`-33?C)Jzc%6{vc?781!`Yj=ELQmlAV}x=m0cbTeJO ztL_l=GmL#YX0p2k{RO5tU2Ro=%*pS4b+4dfu)LeD{-K@}^nIAI`#ex;sSo=+47aM1@qdz- ze-ro0lE*YkNLU?tb=Sv;u!d{<8 zXLnvb>gCeeoda862K}KDi7)PQk#LE)2T3j`i@UCbG54-`9qv+bpY8KzX`L$Q^Df+( zm4AbKRq4~QI;mZq)UHnI3m0XmQ>b;86vLWh#@qHj|-(hu-yr^#}+_T2Ja6cQ`1UEQz z1l-7oo#A#3p9!~k#C*8Bk6I45-_TCDFArG<_ugLTLC$^67sI`DI5j^saT&tjNh{pY z_d3L3EBLk6t-!qeRMDN8${w`|F*o)Z0lJp(1|-y}VI_BgK119^;`T0o2oze2ug`&K z>pHbo^qheu)Tsv1s;;|ng}Y;0?5O$A6V!aK60P}1k=KWPo`b7%c}-e}7FZ|NSf|(u zb&7UaCw5pTc37uqhjn6yzI6>ME{f0Yd{o?uL{Yp>tt+9cOZZ;J+tE7py|{mjSA#yi zwE^x`1BP_RNe*9)@NX-QhdZ+3M7Sx*{f<5_AY3{0HI#GrI@;T;QCktN8Tl#P86&=e ztJCf=?k9wGsv)A2XDgiYk?O==h2^Dq>nj2Gh)`8|oqD*m0q#ACA#fk>JqB*B&Ohw4 z8z{6xU)ulH11zvNb<%|%U8i(UVc)4UeP?j_v~o*LL#fcC%h=P0R_+aow)Wh9y<@LG z9PMJM&r3coWnJc?7PY~H;m$5N8t#b>!#_nmxD%`s;P!UtUJ_jm_w)R9aL+2-2=^uH zTDYywt#BWR-UoMB!DDbI+tf@+;1#%^Ia}cF7W+Hg(+lV>3VaE-Hca=#7~T5|=!PY) zw?jW5ygbSrpUG$V*h0ELhf@-Rh8Dt|94v#|5kCv7w0m0KEp=2ITGLTCRVG3I z`}q1k+FDr0#y;4~1qZ|Z+G>USiainTO%C0n0=lI(xt_QeT2m4JoxLyI1D(U+z8h_W z`&eLbA4^I2rB{p{w&qIXPCUZVar5F+4mU8VL8n`g-HA z`8D_RzMn@ObxQM#a1R>&He9#nJ-Cc-sfFQ>@qq687~Ow` z?=Hcv#l&9+Id>F)49fRalzB$q$DtXW>O_<3{63cIDmV@9zrvRxj-Pf4Qt6nVg&F6m z*e}N^ZMh1m{#VSK5N8i*k%LB;ma*1%cBkrD{s%>?Pe8->z|u?ApF^*~Jutcj?ydO@ z&nTRTv^$OZ0`BA>t@MfE-$*K_qEszau?pBTG+?R5F})GKD$ej}<0>i~buwH_eHN~T z`)X_e+-PA6?w<-<;MVl&4GC8yM^~_=CL#QjJq7M-4&A}Ay@h@t!f`bp?(U&uk;>0& zDZ)B0UDI7qU*>fp=nuq_&|#m#lM&ujSdUtC3|%MEHbM@|T&gYzm4QAuO7|5-d;UX% zQ{J;h9%*3fZyYvF+ImUpOspW4T3eonFDh7SZVz-=H@X(Nws)f&f!1{+@_yTmu$)yl zX%zWK(IneacNp|z5mzw}7&N115+46NX3)VkxCo)1H|VZ`lk9+c&7da-7UBJgEk19+ zBs-`+GU)LE*iWf{7<6=Mk{wdt8gx>si2JWPh2Pr9&>@@9dYF&}O&u~A{Tu_gpaCQ9 zst7A|a6!XH+z-@WBhgDlooaYbjhPmRsIv@u2@Cs(y3n9GwUg{Tb-6)D*W!N}Ro5Bh zf)`b{8Z-jDsJh3X$Ev2{Lk$lb^lDW_QA|B;&|TxE;ymjmgB~7NQIxOVH0W_?ut2?U z(3{X;f!b!!>e{I|=l_>M>uW2D3e}GWZE2Zg_fo;Wy0)LU6yfG(fksl>BGqJgCF3U9 zMQXS~RpW}VzZz!{+cvItHHd8+S9=+Bz^F-fv6^kr(W8p+-p?ThJs97+D4~uv=;`>4 z6$!OOBgwr)U2AwB!wO2&9}M~rte`~QZP1bdk5u$l4;ggwfTw_-G-%aUSV>7ynaUS52X-AC7aKHgI#GQ9r+p14FwZI^b1tV3PK^zN4sg(wCEEuKM z7{sxlRjo6KV?nDr-yn_!qt&GbaV!|Et~H2b!A_V7w3av)?4<5Ch-1MR^^idv3&yA? zHIjB8t3K*Rt1HKyS58#?UqjdfemCN`m6Oyl8mX-~&z+<$9Vk3?S@AXC z^_TOywLmvjPFA}cbWHpSCPVP|AK|3ae(Thlgu)QL3GbaFXVY>-9sg!_^xamG^4dtLg~#wII&+ z`v75)i67CNsw36!HB#T;4E$)dNu$Um&4*MSt?n`Cn&u;co-ydF@l(|?%GyPwM6NDc zSapob6SO+=aM1}s#fF#aI~D(5bcI3F`c@PzQ2hSMJgD$E280d{DI_B|3`$aufwYx^MiaxLM)G-FVKD44}iTcc-fkod}9k25D z&@o@Gv#Q$^znco}s%@!?qNVC|gN7sKGIhN{;i5?Oa`mG@7Xh89%BSjd7ZvrdUZs{A zR9VwneUjR2(6giVs9vptd+M0aj+#+@vT_Z2YSjMKr>HpwJ?hsiR-RREh^HtMcT1s`%ebpDJX@Wkoj_m8G3)N;pYgAY3lhqfhmKlt>S}k(_ zQoT{FH|R9?qw34l#WOYUW!UEx>b<=+8anPD)mNx#`)JfS?Az+ARMRYtMgU!{F2=1} z%KR(xyGDIx&>q!4RbQi4%qEXLAXIamx=5o4*F86?-xf5`~U2a9u z9qI~`jxBws`dZK_DpfV2=1w(>k4e=jDlvL;&E4u2gFYB}K+V1CIfI55;~ALxL?g5j z(d4<34yXudu11l*O-Iz+Z*qSubjg@wbI?s=j<0#3oA=lFikb(z(HHTKnupX8BIOCi zx<9PGK9KAu6zlh};=9L0tlyv2JcC%jN7OaA{>ZWX>~U*r9#so*cY^55;r}Nfu9b0C<0|NBHN_yVf}U10G?Lmrt>*MVhjybP^|Wd;=^n)# z{)}4L&D&V>j5^owqF8l3r#2dNGgc7KsjIqU76IL5c&i&G+0Uy71wEk-!m8_e^|+vq ztfJv}RzI)aHoT(Y=jFYi?D?$26DmQRHJ`7P>MgSe{PqFynGtI93vErYnK+@k(w&};E&mG7!; z27MZzUHP8+mqD2kTWYqd9}F5W;uE0YVNw=o=_{a_8b#QCe^YY|x&=A>P0jC)`BTl` z)JKNL73Vhfg+W|nZd2bF#8u@s)nyRZk=vAWxUM@_j@wkBL0l_tQ>6xRb+}E{7{vA9 zHpRRB3S;xL!}Dslsnfer0_b9cu1zLuKT|7?&@r!Zf9UnO+V4oo5A|!P{X(tMC~{Bb zu-dQGDF)FdzEbNA`m%L4p8T9|(5TTwmudtld*ywle#lApjS3tkIYd@B%*OM@B8}9g z14h?=qj*1;^X)p!+TW;E1{D>pjeMhS7sNfuKZ?IqFKJ$6$fz^p->bhG^umbAwco3) z1}%nmzgPd%NZm4U8hEcB%^WD@q4*E#LqTgJyFs5nsm~26A2vDkllr$oa|h0;?NatJ zx@09|zpd_4ydTPPzR~b)_0Q@EgYF*lc9pXD|5nQSv-+@FWj(8T$d6Bs-|I#rfHp1A zQcA`iTWebrj@9UmhS{;8RdSq0lo_(dE+ks5F2dEskQG^^(eK9|U+Y+>8+7m36KW$? zaIx@^U#2!{^%JD)S74yz#3-I!=uiuEwIK2x=^`;&Z{l7rU^Pn-IDxWZ7*xA zN0}F@vxi<=TV#D;(3+vQ0F7Nj-Z`>1ja$15;uwOH8EbDrYw%XUeYJ6Gop3wKR{rsn!gNp8Cai-sip;Kip|-?2%AmRNH-Q!#q-s9|THc-ROP~u3k0V2wb(ujN z8Op3{4H_`whuSi0Q+GN$S!UU7l6!=`vDS(jM4PC!iVb4lthFi(V&AN_`WnQ(S!*>K z#J*W;4K;{;v)0I>R8I z5cIdsF^FdZ{jEz3x}>Qp+26X-plh1?0^MlP{b=`k>vn^lNsdtU*8K)87&tA^U_EM3 z`#?)ISkD>s=9q!W0oJPqZ5uNb=pBQoGuQgiAnuY~>kETe_kq?o265Lq(E8aRYNFA? z|1!gNk8szD4{;bo?c$5<25|?Svg!HaX<8s399PS6_V1{WnqTe})GzaX95$(m`<8fQ^*j5Wug zaBO11SnEK8rdlhKaNP03xY3k{lX-&ebvb-6(`fd`UPtk(?s z#(6BcyOn=}&S7Tkx#S*JFN4l4crH2BDmLhZz)Q(JtqOw%hF?lfv-%peCiYTty47gV z;{`7z_p*i?6i3}>STha!DD*~hrnS(Z&ghEd-qx80y_)|#+6yYwm#A7Cxb;mx!7@ldwk z0pQKE)^+n{#}2kG)ks!rhgjDL(l&UAwW&L%qYkm|6|@FlRQfJ?i1lZKx~#6`eCuh0 z-m!yqhgxqKbe9vXJIwmppo)TE-QiZR6LndV_IJr6tTKa61Uk~HHRuX!TkNaTi#;p`kZv0b!SdG&w9|ryfd&a z=2_3=#9U&%o)dG4^{$CIGdwPTiS>C7?|ADQ!z&NXR>xcUE42on46ltGZ|yHg*RRc* zZ+Kqtfk>ORI)|6G)*IgH;J4Lj>j8sK4}V*|)cVw*4`Sa|FT=wg=AdORxAFw7Q9lR& z5?gLn=kQLj8Vv8&@OQ}*tl>Gl71jhnTdifdHMqjMXBA~`wZ5nw19YDtox_RNV}{3= zCt90N%Emm=dRdT;xzc*u@ECKYb;Rmy%$3$rf^^JwYl-18X1i6sCL6Qest}}OuCn?Y z9%HVuo;oEPbCvasAZ<$->vcgoUB-G>&>HoK8c~uZCKj83XM$@>+ewpyRoIqFm^xK`I_+1RN-7oSG7T2DFXHx_+ly(*&)- zXGV^yJKdUP&=%*Yx-+bK2DQc()tzY_D@aROXDt_`rL40~GBGRF(zl7e4gbQ^tQUT_8k@czb(Oiti7M zcln;lw@rK@>A698y+X}|8&m7y=Bu+uC_GP{33tbzg*z1g|LAJgIF@SZD3*$)4H`Lb zBNse1A4<0kCAXxs+O^gBDD$s}DaTT?hEcK(?~Qud>ZHD;KY^zPwmL7#9B0DSKR*|p z*QQ3sq^@ky6=F(xcBJ6JE#e#q`IgEJYdHnYq-)n3TU2DJ4ai-)w)y}i{9iUk{h%)5 zejs`%ZK0get=c8`gSsNTN8?j`x?it@Mw9E-WNVtG_*OwmKyt~Z^7Hj6eo1W9mu;Q< zv(yXF72Td(+|C$6{pfz=hizy+E5TC#tYTW0(66ZuYm4>ER#X%eJHhRVV#@p;sRFot z!&Ey`DBo{=a&;U(<}|5qU$mXh(H83p82P`}>vh|eYU`2~nIf$@WxTI*OOb;5Nseih zP}^!iSwg9Som=WZV!eMF!!W)k4p;XbUEdOkxuaxTY*=#{)_G;Elx65ML|n0lccg@- zE@FqplFDyKzx^zAd^M&0J4tuPQG#OvZjfM1aAZsfOYI_3qdXZCFiQN&n4nwyM~ohp zS}wgQ4`We4O+q-RK7f8g>RFW2QU5^eht)-(M^qtfhIfnT>VBka(N#%(vi#b9f@+$K zIi#SiF`D6ji*2Rkt3|^))qc>qt!hV-TQ{6CeOo2HANrvF9#r7$K9)i%-*o>PKkCkE3fxuB|>2%U>aN z)Z?&rV=~g_>fyhKwJm9P$C%WWrL?;v_y0~gR_1+ed%7oP%k0;V8psX%W0xMK{r=1H zt!?e*&JMcYb=4;DCSY&;Rbf>!1@F%6j=w$d zHx++-;%_EyRLxX-STk|UYbM?bn2ETv@W0Z}0&N!V7|jB07HD%2H&5lYps&Goa5E{d z1#e`*EyDkMOn0=n6UE&_+`Yw}EAFA<;!PXGJRWXubs}5`r&)O|mFfn#xIu5_wG74^ zA$ct$@O}rmU&@Z}ON(pEJ~kJp*s0vFHf?C-`thp&_C@TO5;gQCVI zpo0^to2;$&rB%bMd5TXB{(~|Uf&0f%`&vhcrGy(>jHM4z<#Y2_EL3mL&~lTWcm-c?^S!^%y+B3cGPZm z96Bjd_o}>_{p?F6&NU|O!q$!U%@V#-O7eh|#yakkn;!;cQ!tCo#x4}2yPz83dKaf2uu zwfFJ3Hv{<+-oxVAb%}(l#O*I`lel$o9aU%DR=FWa_k!SX>;Gx*-Q(k`u6yx)&dg{= zudy_e%uBLIw)_Iy_#tB(WJiN!Kwx8oWJnsj@>m+%iuH;f7!%UY#7>%r(?>{eLjyNb zF>Od`Z)iwcw+gPkzdx=) z>#Vc(+H3E<_S$Q&z0W>p*k3Ik`;nASg&)+@!*=gStG^iD?eRHEm+~GEWQ#73^h!>z z*O*uZytQFnq+hAMp4rhB8B^;0f@9)2tyI?YiO3n1_)(SkkVl>0FP<6y!&_g85WXt- zTI3;b$BrL(W2(cLB;PN1R+7(|!yE4^+2|d>&i3Pye^zqNnaWN5CELAF{XHcep~rXL z54;-qb}x-*h)+6Bwu^Ew>isXY{I`;)yr06etj5rfH~o*2XTb=_kDfDsy|X&H-TU~;%cD<$e^ulT?{}81jNTC%UbGf)(dJFjw}l>CwJn+s z9bEO6=#2l-`dxs3yv^FxZP9P3lmqC;w?$tBj`vFe$D^+be@!r~aaSQ&D_Aes2Wu}e zO~Ais-WmGZ((gx4hVE|oN%WneZ>;=&^kwhz&5L7emD()$lK)amORPnCTYR3?Zt;1t zyv65^RJ(XueC|i>7T)FaM0=Od)9qb8_u3AK{((@g@dZ$L(!I;)u3N98e|l@Ov>*8G zrDHzNzKx%6|F^Pd#s91*&x%s( z(_+5`X?K;spi*9z4ZrB~eE*AR^?LxhTlg|kzi3`nxv%;>&412xCT_($2bAy4t|ZCo#T$z4EcpXX`&*c_8%4y3bekhJF|F zXS}Ox{+7vuH*&$O(HJQQN9&xSaIJPug5=pjhJa~f#7 z$~+O`Da0p2E4SVXNN@inQgOZ=a6I}{h;zh~fU6>ptK5e|l=-r^aclRYXGD2c@FoAB zTRyVr1>xTk-0uG-q@4@By5k=feL3{Gr9TF&7yR1N+oByP-Bf)q^pj3 zqtMk`H+w${eYAcl@aI;p1pMK$wMZ@9&{A~=AZFxH$=X|in;mylomDBNp_+!fs$LB7 z4X78@`!5TBS-2Pel{Fu(^1|QT_(0Wb;tz+Zy>ljpXK3eeI{r|}IkO>2{En;X)Bbkl zSF2|HBdh+b>YQ0~-8ZT##LpSce~&t3J$kv6{js8eolK&g1oA+UiSWYg0sZ* z3yuli4>%q@4QOY~Wr;J$oAo{_%KgeahP=lU{VMl<$>|T%2F3*Y!<-ZQ#WN=S{xC;M zf0(~;HYRw^Y+3VYqFrURhrf62ZzdiJFS+6A#96fWE^`LC)_P~5f&1c5_@Ax+TH*=+ z|Jv~_@UzYjg&9-zhdGD7YM8Uqt6|OsuPODl@ac6o*S-e&Za}{G@>-bw zxmK1Fj*zn=LcjX5bXyxyYJ~p0HbQB&5qkCd2y0s(q2FskJrS{lOwFyPHo|wMY9pKr znj$YHz5<-@P}N5GKGKWeysNw^a?3UUSlbkt+O{fE>o+FWg{Hk%)|BdVy-p* z?;`qb50SGyM9ylT^lG1!YJUo;=gfPO50oAV?c8>#Zf)e1b)$975lVPgoXrvX%xA^_ zEcjQU_xoBtQukDd8qWEDv*=cHGW>JZ|Gkd!$#2)SMCjYwBlPV#>1ua`{n91+0nvL! z=@(^8l#bBvD;9d|ma7(@GarTDJ7-SUQ!~`b{SnsfwCZ+7b$c!R{pj%GGpbt*Utl@A zIA(^;V~Z2!m%Ow1Aj-7&8Ne?HepT>!!LI`*Faw+gd`0j_fH5qr3B&$lfHD8Kk(vf2 zVKRWedC;e{uL_oiC}Bm2scQgzvksIMSQmZb%2j5QXZd&zTaZZXpDV|4!KO&w- zz%#JwQSnTRJ}sW}!q1E6Ja|63>I>re8di;uExw@C83$SJMSN@K`4tz%^KC)nu@#8F(U zlv3X-IHA-@2g&n@cpg#eqk@kCPBcx4XWBt>o)_nN@q9t>f>LK3B+o_hTvY0}1z!T3 zXu=PtQ9pRMXQIh-IQc`qv;;b|B$!lcql4sGE}rE|T_M=2)D-ZErj*0U*(T05@$3>j zq|~&7t)behT#GSDX_4ROk=UFB76qI!O7Ch(0CCw1cFdSKjl=dtN*jlse-e zc`l0QqIfQ%-1usK0S`71R_h!jX9)D?S0t616ur?wrY=|N@-W-gDtgL6rnV`yO{s@O zPdmue9;Nmubrf)-DXUVl;>kHk&Qsz%CC&%J9Pbn2oDk2XgXDZfoR5feTJ-5K=@*30 zI7rGxNw_FZ6H%)jWNIiPn~$(vNzoe}Wa@IIE>~)+=qU%8+7@AdwIg++sYCFPQqvBS zr$-WcB%wF5%zS2LpI}xzIS0vkN}Q*}c^W)Vul#`Egm@+$BCM&XSPC(m;6G=cu~%2v@+ z!cz_>Pn&q!ML#5ZT6o&wkfBm&9*y=nn05kg52cO;D18jSiB&TzIQs%0bfGgr}n{J?(JPdxU2Na}JVzDta|E zIU&lVa!op%sgDSs5}bCB^z*_m2+lZ2`bFU;rdBvedPsOuu+c%%mkVzdY!f^r*dv$~ zJS8|G_=w72mq_+ugFJoUE5}p>Gb~t%@gl7eF4w8OK_=Mo3gQPzq zd`fWILDJ6)zaTiHQZ5QNcqjs2TCSQ4whA5+Ogl&kS>ZVcNtqBn=^!c7!l%oL&j_Cp zZYtE{4l*?Kdrt)iqHB)y}8qxg_0X$MK~1O4fhSy6HhlKuhFCq$Wakn}0x z(+-kyLHLY=q?k(8(?L>_!W$hVrL~e8YZWEsAnAuh$B9tbSlU6-v%+%@k}@HD(m_(D zly^#$X$MLFawT!Y74KD|K3wX;EHO-j@I)tJH!I=bWGLv9U zOpUbdASp@VjWyIjqr*vWs-gTff~}&bfPdo3L&DR-dzG54Vd+`XbE1Dh^a;@?M4xn! zlBb1F*ARb3oL^SyGosIk?$xTEwd83O-Y7gJJS99WJS{vYJSTio_@wZ&wQR+-DAS@` zsC~fvdeep4k5eb2&xmg79x&f&GIgYTbgpB8;a_>6F~_yKcpg;}gRELQ1QWkLhO8-=$nrnXa}q(n&zPYcfp z&k3ItJ}G>9vGgFyv?w#eXM}r~sjSOX)@9^u6s1v=l<<`B^kuY@v?ys&a^T18e;I2v zDSTRRM$o&Qsf~iImp@>BaYgIpY+Xu}v|vtfQgB*uM$o%Lc?DB~hpu2f)55dDbHXQt zPYRzFydcVqaPLaV5o`j4_JpSevsY4PPWXiIN#Rq%r-fe-J|o<_iqgV>6HUphn72`s zrmN^dQOznOX=Sxg`X8ZrPOIr zE(lL1rH3Ttqy*D~*(7Z*Cwx+HTJVDSXOip-)5sN2VILCuIVB_oTLn|g$(deG z`B_nNqMQZ=(nOh5>NBET5XCf6j@P7G32zkMDm*3pknn61Yn2tA6MaJXr0^-FPKh!t z%1o0qw&J|`ixu7q%5M}*38n?JD_D9?_=NCD;Zwq=S5U$Q;WMJY2ufs?x02GrD~Tsp zQbMCBtt(l>l<;1qo&^rSEy@LP&Io@I@c9*HmD;|Fr8KUh%+^)3!<6ttz!4!RH7h(T z{+v=Lgii{e7Mv0ER;#6gDZ#X0PS9JURKb*BS}?mtdJsM#d{X$7@M+-})|@x1R?Z0b zu9h6Zlwevgcl9!J{i=zp*@`K_8PUCK$e+AMx)RltY49!3n`B!3%|xUL9; z;jO}3g&z{k3Qh=~-M~6b34ccTi-O6GlrRB^RKaI9lJkP_7loTmObu^RD}=WS9umw7 zP6$p3J_Am~>cY)t<=w2j!dnFo31$T^2%2VcCIwps4+&-kCj_SiF9@0~;up*cP6?Xp zNN*K1TdAc(TS>_ZP6$p3UJx|fBuB7SaLQ7)OOD`#;FMtUdPx(U0z^x%e*j-Azi>Tg z{NxQxJtUYFoDjSqm~2trmd}9mf?(?o;uC^qC-Fl&#UnT&I3;*NF!>gx3QoL*{8NIh zt;#Ey6`T;95=^F;t5xuj;FRD6L31PXCIu%1rvxtunl_~hX4@!dLU2m(f~B;R=l4v~ zdyCiRO?gZGJ^n#|&>!{x&i`NjaOf99>%u=9ej@y}a7V;1`KywK=r^MWW5-KhDE*hx z*Gg|Fdwbc@vR^KnEc=bJaCxG9W%+^f!{tZIf35rz6~9q2UGaAn|5WjVij|c&RsK}v zVC8d_7b_o#zYza^++VbOQQxA$MdORUw`gNkU)8Cq>8e+&Zmiy2eYpBptN*V0ht)?C zBZ+qK_H=nH_|GvxNM=xRiKkBk_GZ2(A3% zTZvaB9s~S$mGZt-zYYBUjZXqz*}&9og3DE(+XO!#nV%K>(N%u{p1ZGm8t_5E_iug% z_-}3aW5Byw{uFR);|qYrZLy^nw`j|1*6`)EEb-~}e+jr{J-;_MwEktl+?M|Zc=MJp zPD$F9+E%~0_Es{NT54<6wH?bz^WPGS0Uz0dFx7l!9YO`;zeReh!W3rqEtB>(Z2|tH zjXMEwPvb)F6_{k4rz9#>&nwtElYOBe6A>rmN?*X)>d|%rB&uaBYSDne% zq-*;}fZrf!>xSBbvUlsR0M=eN0XT94t@O*X&-%@efzrF?e*jv_pxW}4t>-}bsmA{a zI9mNDfZ?To30SO|o40(MblJSk`(|w5&l}jALt8j%tbG<+Y{|NpA@fgEho4=;dVcqY zf613pJa*=ftShCx-)x}8+O{9r{$u35>iPuWA8q%RTRevI3qJhG!+<{g4&GdV-w6RP z#TjyZ*%Q#`Gh*NsfIj?CDeyR;4_{Ocyb92VKdJv!bPt}Z0eBPAJ+lJPhaYPM zz8cVnFIx`$YCs>)uU7zH3y5dm;PT9RKp#GC4e*VCKK$G@z&8W>@OA5eZvo_Q<6H}T z8zA1Z0k3DS2lU|!Hv``R=)>#c8x-a(fWB!(4$q_jeSDQ|JMcC@-?Zb&xo36(`tZ;@ zfZq)0<1F)AfOi1;=2qnM@Kl|@m2f-q`DQP2_~s7q`sPk>`S9xajV5^DJ%Ia>?!&*| z0eCl3e4KFJ5BN6R`SA5`1AIIF^WpCg0v^OKxcTM~ew)C2tE2dVhFI-qa<3{pMwH9+6I z0I7KM2GBQu4ommUU&!9SffDdG17!Q=ugoKW-!zW`{)71qz<^*#$&=KUUE+&c$Y?foHO!uuRxjrTNQtv3x==bZ;!?0o_7 zGVhN7FZaF-c!l>Bz$?A40@izf3fSO154hC(8er13Zt`9M-0FQ7aGUo%!0q1m0b9Hu0PgVq z6>z8bBf#C>j{*01{|;HSJBfS(5R@pkVH;O_&(n?{H&;T`=HVAyX1tn+sP zUgqEI-E6)XT7!}LrO-7P)z1RI#rz5I4)bTgJIo8fZ#CZleyjN_;J2B-0e+jg0DPZ$ zEwmZ%Kk%K*eI^{<3Rn`}4j2pH09Y2@0ay`!3r7C3@MgfK@D{+U!zsYE;OsK%!P#Xt zgxdf&f%C2Amhc+DTf^7D+uaC{(v30rZj7hT;~PgSylvhwZ^*mHJLA3L{n)$H&-fql zf6IT;{{w%@|6@NH+7fCH`^ze-!Sv*w4ovj{Q+AQF>G9eWf2OeX8^yO3TaEl{J_BOxc;TC(6#1U0&W+zPEh1{3GSl z<^ND#U9rF7T*dPhe^s%#a!KX(%H5S8to)_Q->v*o<8Zp0tHsgGHx1~W!eq*5I9y#h6(Z z!&aIww=}`#nlQVpz}&MI^UFF|);i2E>oMo7#~jlP>$*{Gx!!yf?=l&b&ubX@?RB5K z-f6yrxAQJXetyk0Z0>jTpK;f$yS~R=PrEC=)hKx%)YV`eV(&lgu0M9yU7n@i?5-W| zdYi8JWnOpfcGn)4e$-w2-1WG-4!G-(yS~$1N8NSYT~D~{NnH*64C`UwS9rbLjQh5| zr+l_QY<>aP4$Mj&n1MPlpLO6Hj2)O=I?Qk5?{}c{Z$r-&-hQNa!+#E#Kk!cA`fjts z{}6ajAa5sV1Lk?$uMU04d=P)13!T8kmOU zkxo+;*=u%2_M^P-nP-9X?-xq;BmZ*mbD?Xzua{io{dqLwRm3{YYJ48+y4a9+UF;U` zW3iv}UWxsJcYW#4d7mi#1@FtHH-YvZ|6{Soywzo&@%G{`UG_EapUVz_{+C{|{0Of7 z`0F$uFaH5}PMDvRhx`xWYaSnqjp6S;{MGnXmCOCE$`feIeta*!1AqI~##4(v?En6v zPV?181LpTZJBxN-#Fe@_QPpAIU3J1dQWf%lzpB&xUDcw{O!d#BE=xo0iF4+s6Z=CS zi{b0Cv5^1%ns=M8;O{T+cd_P(H&gu?Z-4EdhW@;ECRADXQfN8;uCM#o(0l8C5;|+5 z;Xka~YhJ`(c=1nz_odLg7nfi>G~tit-QG9aGjva8WM^-8_r?vTW#@KYyMg6X4hNK- zJ>A`)4)>=|w)Lk+M=c+7G%Ls5>9L+;JHc*myN24*W9j~(qq_#jMot#!%pm@oGh?YS z{7B#7@v%&A`%usLKxS~P>*R1|l+0q;J(TWk&m8F+>>KMF8Z6LN&5f+)zVXpxss4WE z*eHUMn9`X(l0gx9fi`tc8#ZksEAwm?7PNX#X5esULqk{b zdq(<($A(5&ADbZAV(A#|I662qlIdmQW|8JfZMIZM-DDR!b^nbFkf$-y2r z!Lm@?rY-prJ2M06!Lh!co4}tL8OBdAj!75dAaAoU3NDsM$o4Glkd5bU(8*A|P zo=V-S6e@9G7^A7ZZ)EIb=U93yL*3iNxnlEDH*RO;sH~1rWn>;pr09Gq3N6^T)Tku# zAitDzPi7wmP2VWSvu#^@W-Q&;Z!I27@({MGSqLcT*J3kp)H^drU}*9s;BiLi(ry|V z8nC0&5xPcBYVh5h8O)5J$#X`b>UNZM8YxuYWrZpiuny!1ypiKWEobv}3_{#sx}R=y z>&Cp>1Qs|D=7fWEY4C`^iX9|w7j%#=gZSCguje2gFZ>>`*^ba9!Ak)P>NMM_k!6lI zwCwCr_%5U`?9`lWxS30^C_!eqg)H;Nt`mUq3ha#y$MR9$X62x%!Wd`wNHDIQY%vy% zYe&Yo2Bv1c;kXX6Y-5&7k66dHWjomxHYl4mO7Gdba|`Ukv}EpuMZ!h8+XaRJm}Z+g z2ptPe2cQ?5D{kk`nw;Cw*`xL{ZZGKi<_(8PpJN2gm?#&bZ^m$6h>q5YjyBk-0mW_E z7;tXc7GRsV2iO*3H;oVW;L-+1+LneR4BF6kERC7NrF0IZyV7vIxV;@ShC@4c4I)+; z3GVie^k$&h*~DG%97ptFv$hQl9_c$e4xX;`=<(EvbYGCN_wZHRk6J7jvkEsn$L1V)Cm&hf+VK)JwfNsk`u%n)N+_M`{X zM+?Cgs2xG2cEccXzdL>3NnBcX4s>^84o~;@pWJBn!{2S*1R{NXH;^~-OpX_x3D|7U zuOjSp z|J|ANaXaKTnY+^c_C6VVdgLVT zAhlJd`4%OQ~jDa~;@F$zio%q=n_9tu#*le3}Nw6tk3)_@qflUEh z*rpr{VsKy!Xw4?D4w(8bAw>zQ=5=IwQ-zkY9AFgZ8_Ybhy{o|6*Kfo65AIF0J0k&}e zAOW=;hFzRwEtvzg#N~At6RhEuxfKF|orDSjTew0<=*_P@S>6^kbK`ZU^On@6Ew~&@ z^RiI(g{fN&BbWVMH*I6-re)7i?|6S^CvNtd&XWjG2iAA&#g5WF9CqE^m}Rium={tT z%!#IZ6o3vrA8*@P3Uf%h7xNxu_NX+3^d|??1ATap(Uwq{dq&qQVn$LhbE8Bo?t3yC zcBVbkpBZ!bP5oSEbKz(9jbu10-83|EXQm&^^SvXT$HvEchfWN-^w(M0nM)cHoW;OE zpB?a~Xz;5MyVIj%o#Q<{nbFZBHb-Kvnk}c@noLi<_w~UcaHZV0x6=7YWat%kh{%#N;g4w9UDQ2 zfuxq5{oUQ#{z&)diQP<8u6^l~>;p9>z$vXWGh(+5iiueM%q92ECk;E1sPoi|yeJE1@b|8}2oV`SuTvVoPZ6ApIpxJE&~o z9=^q{UQwR`Y>V}p5qG^kbK>UlKJ>?>%x&lK?ryh|y@?Yu_Mcq$&QF+=HVWy&vUYMaPJpyxn zy>bF$xI}(zs4kphw*wYV>lj^_ZbL$doUb4&j@zyC)AqUOV&PQV#D&v>sAb{Q8&57w zmxJ0nh?#wOXmo-4xH)8u3s1YXGx|E*YV`a@**li#TG{11S?>5iX z(N->%W=Gw8S^4cc=dbeKY2=N0i~Q6A+$lv0*n?lNo|jIA)L{31Ud~j1UwU-DKM_gt` z5y~Y5x}e7veiIxa7HPH&7gmTfz!4GO$=ymspNISVkV|`)T+48YwCmm;cMo&d9;fNy zgoAkb9ys^H)+cv4F>PwwCFqc%NJT}WUd5!MEl}>daGK*Trqf2j^_ae*pvJThp`K9j zIG)fAWLT!%=`@heJ8n2T3ZAM+72F6lXa+Bx7_GQaE$E)RhWcSkK#!saI0Fc=hNfFC zpNekDmbalI`U0!0ytFydku))vM!9o?Br(n-j?D?BRN><2$&4yySMg{)8=9{Q^^*jM5-45v4a-PwKTB@-_`xJZ`+XXkC%yZ)D9M(5OBbI^+&L#}9 z57708`v%cJT8--&;2szqNu7M!g`pGNV*nLy=0pa1D2cV+aXdoDW3|2>w%A!$F=hYY zfZbA7dQX2EcGa8b)k-LTmyaFo4u|L*`F0@hJt9KzU_>`98+KwB8b;}O?GC$csBf!N z-O`APZmo**jK$%n33j@a3MJ;b3Sp?RmJ)F6>dyp66QqJ*&QN;G&duH3y+x-NoK@w; zqVQqvS&1-4HPWuM$TfE#!`fz+BhUoL9hu_CqDJ0h7R8Q5N}5GhU^|PV1+xkEmkO?8 zy$g<2-P!T7Y;g`r_N`r@u?3^%LLNM_TqvwI*tOteSUUmT4AX-*#I`e+?`9<3H=3~v zN0aXD?cO`+ZtVKIaBrQz_NI<9V|(WC_)#1*xN&6Yggl@u9Fs`T+$Z7KO4-=mEt_{+ zTjuT&Of4ogI*I|=e=-QX^N(E3E}Q}3o=1VwF`75{0+HSp{V5sRQ#&8eV4M)S&Rr zvCJ?wq=v?i9xG(EBj~0P6m?=~JbN^D@=k2; zjuz;8lHz;`d!s1Hnvm**UDfox!iv%Z1)#9r@t(q|fV}ax4Go_h={tI?D2cTWJXN6* zfjMb)dhZ-|8~6v$k1)q`TaFJp3;BbZu*QgA3kfc$5FJ}6eSBJ3NKNO3c5;u;bZIh> z?IOayI zYcV=e%k6dz z^mB(?67YDQ&yhK&+0|QMl))hy)RLR6mq^?-+Jo~^*w@$%XFUQr?$MHVe6eNB(sc=X z(RJGTUZiQJ9m9fQz#uw60Ij3`f;mCn*LY|1@$N6EC~aW6KAu;h}6JB#M*{4mHI zafcf06XL=nC0RltUM!s4)Hun{kAVT$fbEUAiqd=Y59Dast`dx^c;W@it;{gJzqx$~ zkBf%6633~KJe9qQl$(ae2aBliB=d=>k)t|{N@rORfMpxbVD#=qG$;RDNP&+k+yt2m z6zYFYe9fIV^}<4Qdx{_!O12}EAwRr|XckqxF5f%SN72^x6cH}{*ba6ze6ni@V+D^b zIEiAY@Fa>*nGO`E8;uJ+Ms#dEBP(=YnsYwJr|#g5;P5a{4cYtrGR5+`cvv?_d8i;T ztlfS1316(CM`YvHI@~~ENr-}H?Or+tx?sh}SVlWiewpd+*$U3M?sl+%7LHh%0WHfe zWvTYLD(wsv>0p@)n6qUHA_5G41X=Tr_PD-LCqq`t&av+9!#q7q!JL9{WrZEem5oDb zjt=eaw~Rw$SV7F{ZQMftbI&`0dNceak3Ip#<|5#YePiInz~>E11P4);=ul8e!BRn| z#2uGAXE|@<7Rj%ZM4_{ji??y-<(-u{jPoTbJlMU}4ZAc?V5jD97VIAz-MDQ{lMawz zD+SgX7-GSrkx#VEF124B%f}VOb9c%Q8{Y5?Uv)HM!Q|u;wgfv?Vcz&jRmLvO*fuV@ks-7 zxUCA_b?b;EuDx!(WA_C|iy~f5yc~j{vE{SRv#S_#uy8l*?JeRCi)eSJ_$>`pI#?*= zPwZRfpc?jW*TAsdP_a!b-tZW;aV`%N6u-4#x)0B~rESk%eM$p5fS|f-tubmB7^4hc zWhrQmjaCbH+SE81+~cr@UQ@`QSLNbk6uuT)9fL>h4F^#0xJH(fzd7L^;$T*2?(QBt z);DVQ^!1DkjSd|dThBe|6e8)9IHM5QW_~%dp7RU_!Q2zV#vL9hyh6eiKyVJkJ%X{( z`>4?=Q+B7_#fLTNNb9I6oRiIO+~W~XW{?jdi&Kk;bF^EOQV89Oh?trYo);0ikEOBM za)et>cvLiwy-JnkhD(u$HnJnm%ms(5zAQM7{0l@{5bRZy2-8*^kRGP7!%+7KQ}+pF zFEJRgCT2qiJ9Gm)m==Tk?g51mDqQxE4~yIg)blojH3lBL$;iYN%vz)9D|(>d!J9Z> z#|6g++s5Fx3?{$0w!1D?V9@4K)9dDJg)I5OWVs-8_G(L2o=eIPeWl91_w@wPt*gI% zp$XmQ9{0(d=RE{8ST6DKdl4VCi~gGbBv29-PU&5cpOSPcN6t9E47ZQsFigpHH*;{w+t|w8O>=Je>~2Ows2-42PQpVSY-G?#LBgi3;U#ZH(ItMWa9UG!yyYriQ3MPSh zw9$Z_HRXW>>uHFPQhZcFDYFQHSFrh*2OIt)WA5;+1XvduxRpXx-OdobV^KIb$MfQ0 zyJdibwSyni8(vP-ChhH+d&kV)L0z0b>LQ8N#=+j(hfcUB^mhW&V;Ul;9~g*n%YxSt z{Ow8Kn}1ZQ`4o@2?YqP_WW8|+>BlmIH=Z&o!wt1Dn7$`1-bylXxi@XD^12*}{Tdbi{dh zdYE=kplnNnE>Gdn2#;#@pERjKn~f(!TxEsAHRJ4q&^~*yGlVDuWTv9f%)+Y{?)XH0 zOQxs;-JUep3CuI=w&1R9urn)>D+pUJw_OMg=vZ(57O@Jp((|{J7Iadc7(5?53Ew?B zK9YgI#OqXTX&n844$aVTckmJv@s2?psKzo4DR^xG=NW(mM@F>(v zv_YhH;cGy{z(&60Y0Um4=#<9q7g5dtB#hweMj3n!DT%Kw^nrQ|Set9(+?uA4 zK8759_>$5%Ft*p+upjb{OXdl{LhiP_hLR}N>WU?@7Ay~6ri!eB1aHMYNW#v7YDcL| zot*=`ovW0yxcpd(9%oSbrAgQ zMb&ptp$?=TMu|tEi6Q*4UbKi2-0^ErNtMD9*)R7%f^A{Yqi;v+*CNGaKYS2XN#f%p z0SnD(Atlf_24F^P1iPRQqS<99k$XT@uu5QasgOQ=*otX19jfDAj!6_%!u5FRU%|$KTG>h;#(iZO?Y$NY(=xHt@uKfkD)QN$fFv|^Sgb1=d43@ zi)+9J^r6b^O4>fhOfUX;#i%L22iJa-kik#cv)m)N(ynn|N>dm_1I^NRnPs!&-i{PX z!}o~sXYdu}wR1S`LTX`@I6iz1l6=f*$1^#Y_I6{cZ5bTTW1^WF@&?A^B-B|6ftPjE zMoVRv-wD}AR5k}r4>0nW@*`>|{fjB3-&2dLZEb8T#kYnO>IS=?;6vaw3l(%`A_wt3D|6c#^osT4%s#12>?n=^a-A z-w1paKHC^{`)qN=jR~f{`9plJw+}oQCw&__a0;X81Tu=dZg!*SivtG$chC_ur%-P? z6PCc?V2vRNa0=}yDD7QllUn#@Bw2SuHxUd~wv4WU<#Eo#i+?c6H?9-4J|e?l*9J4G zdHWmZEFS7}_4>N)t876Xt)tv&%$_$cDKNTqGMFKRS>u>n7izJ0_Zv$qxEsz~fy1HM z1~#@ppR8br!&+10G-kRhIzzl?T4cx{UU-zgIfD-3(Yf(l!36vBfeg*5V6=7?oLxnj zFMb_ni}vm%{Ru-Y$JFScJA5hiZ|K1jj5u+ z|E5YUFSrX+P0M7A#POy6-NDfo#Ag5L7S$C;cKJTs9_Z^oEw7rk!3i)gEAn=H3)qBO z@CUaWIe_m26Cwr$gna@I3Svesr9Cs+HI+6R;$#bY`3X?%635id9T%GUmopBqqc<4m zv;7(DwV@LlOM#E0|FA|0r*CQz@mEb!9)|G=B}i%wrLrcBw+BFnlobm}Dds8UEchTw z^(wjI3~FaW7{BZ0)|n9)BNaORLLKcw$h2L`uXc#vP7=k|uz;t%W4`w2FT2G`BT3(j1A?$i@Q!F* zBKujtzMgjtN=Y0n0iMYI2uZo^UiC0!_`pwsb~>EIM?*oV2VtCqSkf@5#zc}Pk=s?W zGA0TsOX4L}_{#SZ6DsrK;W96g1QH=qPtb(B7?BMnQ9n_ip!l?(XpOPL z^_O2><@>12NpA@X8y8IbOUx24QbwkjWlkh76CW77*=!|bOC&3*(8fSSIO@maCCphL zM}uq7gs|~^k_NoQEg^j8cu_QxxCI5HJLRDzqlLD3QI8XhAmqr{?*%iQ%uVI-5f?aCt8-u1|b6wIvEOvmIr5I071| zNXrXN#LAPt?rKadR)*x@#)MF#>fD26P)YVh^^t}`%35Xiv>!nU+0)CTWr+iIi9X=Z z;eV?*Ykm!hRw`NQgod)u*(}dF3D3nVr8#DzwXUfOg_V7YwhlA3!MmQ+!O zIz0z{F&h*r;fdBL3=EZMWzz)?!5ZdP;!KRACVM94@||({aCP;1e^9UY+X7VD`(0`A zXgHDmC0T1Lng@mA)?Gq&%(U7u(`wUVmC+bjVu@CmiiROn)e3`#!om1%+*}D2W}ngj z&q4ve40k~j^Kr42OeV5lV&iy~*1u%wt&YwsMob)r!x2LPC;%mtvO{5eHAco3gD>)9 za6Se`urFFCUNlTObb$(RL-=7I)&5c*k->vt|7TP1&kkVHpwVYh1oG0+Py}SKTfP*# zQqBwV(5-XaoOFp!stJO|`C*!85xbbDtF);1S3D2iFDa~aG8@_I0>dP{v6RGI#x7^Qw9 z_bC2%t$WTI({r(EjD64w-UxlG0`X%KYF!H6#}3VuPv%($uECmG7Ojv1Qi)U?nq&1J z_Uo+}j!{$}x`n1vxIj@Ur~~?tnLQlGJXl{J<6wQPAtH~QeN2A&F_;1%PO=G&VN^Y;`Y}4-Kx5srCPZrNmZrTkTjHalnC|yiUhMJwgh05V=s3fsFR8l=` zl?6J-{R{>et(;TK44jVGvp>YO63t6PRQ4fg%xAZOTw|cdfQYVk445V^*Mye+I&y;3 zUNnE$20jG3ozk7e8dFaH;qK%`g;OV2*x?2t)J;Lw60f|>7EUg-L+z_ih7k$L0w^zy z`4VkW#u^ikS`kh*^Ss7Js&VzUS+AiUFg?HC5fSjz+t$}Bn;Pw~dNtb8mzc6L7&kS9 zMoaA#LGvAzx+bku)i?``&No+uR>1n(6YXU9GO${xFOhu$*=%z`v_VWeDuwaF3v)y; zndh1?D5`U%(GBY_U4~Z481$mrv{Bjh#73q>$|MF^x(f z%!&L6gD52g*QiS8>QNEalGpOO>RdIZ*D>Iw1_E(cV=CY$92o&dN>y-9qAijG5Ii;JvZPv-XrIG& z#R5gytXC}2_X+(0C=!$#M zj&Rbz&%lwl`_;oGy84x{DcNxL2^c!#eW$fthvI=Jpaf2#ymAVCB7qRU1p}KMT|Ep7 zpM7?0SiFP}ZUO`u|I+Rj@n!-}6havfU8dl%CAzB_7 zP8@V+MVL%Z+P(+Izz+g`;5$5+Y3zNV`9ym>>2n2SJzI@u1J0mds;IPLDIm-)qM|qz z9Rpd;Qi`Xy)~KBnF;V90k!VNF*Jm#utoJ#o9E9?s22BjLWH zP*0)>w`g`lgtoD*9+NVR`U+%hh}2`HQv=ss<1zGYUK_1VlO&|KxWL&8MvNn98P;+jB`0$!6_-Y3FJ3f`AsDT#xxTzjYhDB$gS|Xz{8RxS>j<` z@=|r~mFnF0V%51HM3W(eHi&KE!+b=HU}9*43o>|dE?{yi61g=EkxI;Vg}iWIDBwrv z)`kf3#xTXo>1rHe-ik)xfa{@E1J!DnytI}QFIp4V5&|A0hVbr}80v)> zG%LCYmsDpz#+wP)xf~rMxH36IP3DR|zOE5xqdPmFl2RTG*TC6`O_7W+^f{)}8Ol7% zY!KB4Q?OTC1{cRLI*co(fZ^S^z_Hb18v^6CI{QgDG72DH2tSK&=-v;kl2IIAI)V-8cMdPj> zhL&8(i_s6&IgDPGsbXWu!i6F>Wk|H8GLozDO7pB35V2S+jzFs@vv#3k)eLoJNrZu6 zDZ#}K_J>>~kT__ar!(k-T#v$@50*1_!(cd=$n{lVMAaqUvt4D^KzsQcDV~8#B#}Fi z=!b1%(M4y~uOI@30KAue@R+ZCv}3?gywB3L^x?jioD~CUngAnDTosA%%K3ef`lI0f zNsobUqp?FS2d@}5xl#=mrE?*X3(_lG4m&|nMlC3-qcAZWm7x?)U8)%8ONMITs>daf zi!cMxLYSpNWAhZEKn&6izRwu6YKd2+P(ng&W<*Er)L$GMKlaoh_oi81=J>}J7yVH7m-)j!;U2m-3}MXcKkaoiQHOnVU41Dcs&kUCTP+I zrzUOXpx?H6k*Qp}Ycv7`b;iW7#Tbi5BN#w_eQ~IgVjtfRX8~*B1yRHOibGmUqG58o zQTU);R6iK(3p@ygGN^gbr9W)b9}dzVcIgkx55H%ZWbRvzwyTb=d)SnGPqC-bR)!fi zinhHKaIll;lGv{b(1V*=*X~(@V$Z&-<|kiW_t$sC{&x3@^4f2#TouN?wF!F!J^+3w ztE2%x)f>Sj&NwOQm-xKa6EtC6xfUrg-fTd>x8SeYEwbV?QO)`m!Ca3DB{D(WW1TYg zmP+c04qzQ$QnK`&*g1rdTjcsNxpy&mw~=_2#R@}Y(QHDz&fcTzi$feI0OOW3i(A99 zL@wgPC5i0g1YZT4f=n!pn8powwyIfo3}wKTYv5fUI`sVka;BWz&e4mP{$1kX;8h|# zJbatd=B%(@JcPJIAL9Mc1Vx1p_DC=Nw^MVuA z61N4w<486(Xl)tT!^~rDo1J@0tcx^*YH$ZK;5idxnIvL+F(AG_pYi>(GA2HY$nqLkW|>x znROqp)$JUEfO&O664#tq0aArrGtjHnxdQgFNV_IgIu|0M(B+_Rxecy8?TMgH^>JGj z__1~9Fg`Cq*$hiv74y*)^Q&i3Z$w;d>S@wa%s|J2`rS`zmbIbxI8B7D#0X5}ZV7aW zD8SM&`7>d6aQk3h-4A1AI@_~u$Vq(I?#ZA~I{`BLTO%CxdPW5ojm>sMmWI!t5FsY% z&vDUBy}iNnDWo*nnyNxLqzGfSOtg~&bjKqYr)zPx%uov zAqbY9h>H^055yUba8Ur!QC$#B>p6~V7V>R_(X?>CF#D|CQZMKJE1k`A%8P_YNz#Z1 zj`>Q@5#cte*js$`2mBb7jY}B=IIf_x&tt>Q<^ql>5<=zO0{wJFskU*1P4)^4hoD<3 zu*(Ea?j#~UhB|QbbyB$NRc#Xs_rqnD!$pu7PY9?GiOX*HAspq6qEhThU9~}biNPii zHqls9PE1SixS1QNFQJ>Tth6l{u3~8g7R9LbaIzq?a-|T55Es!1Fg2xsVS@lP?C*#- z9wTcUoXCrv4fw_l9v>EgQZ5d=kAcRpMAio;0NGRqTO-nEiuA<E5QRA*8bWSG*^WIOY>*xuAk9gz8$v{Ze~ z>00q!_DfVk4CLq90}TG|bhn1&no%FE-d8#q-KXgT`1^ zco&2jk8(i2HjJg$I+^3 zPI-_4$7u7_)2U!muv4t0vv1(`PRm3zS%QqFJ&6m~wz!~f58BJa;(apD`($XfiHBEn zmIJ^6h}BpEtTsML5s~Tzn?S(425Lmei3o8lf>DQ-V&#cjJFJwT;X;nU<{b_zD-TfEd=I!e@ z7}M;TYwg`NJsY<-AMS1L>Dku2rFqNt?HjhGj~s5^w*9)p%^SCG>^-t2v$1D;dV4cm zm1nBve%i&D>pWA*Hz7y)@spGLNBWl^8yg!Qz5d#3kD_AZhu7o1=WBy(*RoN&`wru< znTH=sHm#nyAxK-##^NIoczM<}{F*oXWgXMznVoMWp!h=JEEi8V466Xv%~BK89`2E!|gT+j*sX?ST10RSUSD3YBN{)&A-rV=q0 zA}rEob)xJoQe7s)Oe^uD@W=VH-d%nrz6vpPVh?Py4?m9rLTSe!J}!k1M)i_yckP>E zW6j=38pc_ewVPcAs9Dr*Hq}GPq*7#|KHj|AULoUA=GpD=0u>>x&~o7Dm2&DR_;*+L z2fkpuJ^R>zGx$4pk7tg(6^!m5u^RlX!r!Y%d$lNyEL8+W|L*K;?@V0gz2nr0f4k-3 zAB}X}c-_+p%DCzJ1Nh7iKj$!dAcYUD@vZIy_y!Wc^SG`D+VAf>ybfPQ}>ld!Xne?eNO;xYiH%9u9u7%2nqZ4X!$9@~(H@`s&Hw{+my{w(0Jj zzuI=9(AlSS<*LiZN&8PnZ1G}7MRzlUEg9Kd(NMluF@Ihh>1>C6LxWng zuN5)ht)?3J0^PKOi*F$CeK5Y0P|)ek$VC~!n-uGTSuNN)Qr0FM@F>Wd^Ogb9H{z`K z2K+Uf?ZTMnR#S&O>|MU?Hi9cp<`-)IM&+)D-uSu~|NXRSfUMn+b5v5;GsDmWTf=up z$H4O#^vCnu)I}0D#{QyCsWHB{!c*t03-j1_!r6c9eFGb3UH3Y9Y&Yu~=#F>PkTL(6 zI^QV$?}JQh0enlo(E2x)#d@8FRy{LY#$4?e>;4+FawGbH_TCG~w;#6Qx4GXty#_i6mm6I`#pPeD`mOV literal 86016 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

    jOW%vHD+)J_&|bFz2*R>;_w8BIfr>>J6zN$}j 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 J|KGL1{{xQCHaP$Q diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb index 6213e7e82bce39a425eb1da20b69ab21dd060864..345abea559693d74ce8f34c8d68bc32309d48dc7 100644 GIT binary patch literal 35804 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%0AL8`s?wQ>JZk0~h*=}juyODcZshb<8r@`=hR*^ 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 G=KlcXgGdel diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll index d1fe789ed5f0f8226967157f6bb7f31035834e15..d37534a3b4d14a0e8ebbe58082ec2fe1695c6a4f 100644 GIT binary patch delta 1593 zcmZXUU2GIp6vzMf&b0f{y6(1RTWlh2SLm{Bx7~DFcdCNY)`~@GXd6np3E7?QwuMT` zbQ=Y-?qUg4tQNT>#6S~ig^0$W+0Yj?V0^&`1L%VqKQMvtqD_n@h6mDkW;)$0IJ@`U z^E)4N&fGh5GfF0WV!Hb3-Gg^vUB9EIs)#iKC1kL#7?7^^M#APDq{YF;mP zv0A6>rhqCMCE_@%V`XBI)w0LLZ>(nRt;Uty7ii)rz}0HtPBFm5XPzh1HQZ0BP6T3> z!pq+-BjSWFLrQxS) zDJ2=$jAvKm3{mo{gFzlO5QmYH{%IScDO`sRmn1N3}ev%nTh0hdy; z(dS9nN6I3jgab1c zbyEc=&WP=Wo$5z<@os#Q7w^HRdGRI4T4Z!oipA`=g*KLM!+S8+nq8a5RpF$1Ec65^L#sQMO zI7V_1Uy#&HxfkEDc1*yL=I!((4&G^&(F_yUOpAs?aPhclpEB)JrhSU+EquYWESQ!B zvPAgTsKFFTqutx4e%sU~X4rj}G`y{lGv;@K5X238g|q{e*(LkWvU^}Ua=l`CplkNZ zg}vua&Hp8Se56&DY>W$+E6r_8%(@~DCqqF;JT^Hj{&huFMGew1q%bswf=xk1t+oz>^*rizW+U?U{{&{ZZ`p9KQanH9!di3!(hrc`S*jr;^VJ-lXY=4rkKPB}=;bHL254 zYg+0@>2z!yP=?kY7Ka0Iu&6)WR74PU3Qqh}okJPukAe)pP*CwbH`kbo_i~?ep65O9 z$9?a)XRc>1dTi1AGkg6@7}qyw^VG92ppvkmBAz^cY9AXGjQPzIAz)Z|d8gRV{B^H3 z&;k!Vs#szj%q8Are%2~3v9_xgyDGlDG;o;lv=_K&2bgfW;-yVIK=JE>cU4B-@{F5I z?aj&yvkWi=w;Q_hU&_~86-oCN$Y)9kI4NSlCBq)N))8o=Nr9wVLx8eK>!7`2rkei8 zT+m1kG||#dwXkdbk|0&yEQ3&ma^%XaN!G9=`V?<(Fvbhd>kjm$7Rg>-$4;+fXa8lE zVC6QVi+7aGJO7}-zQ=$GGhmnVq3Obb`|l~rzE0bK4{7v`4B@)E%E$8EG;;GE()x)X z6JApjgNU1UmLD+%SZNZQG0LY1FYqF~ILQxTz(cF?Wb3eE0mB_tRw6c4WsegpRhhIM ztujVzS!ds|HLemnZLlN6I6gI4UrAA+V9lV*WHiA>-Tx=O8O|zGX}5TEHV-$NU_nx6 zl@uFryTN1%8^#URM;YpHpTT5?dKsRs56o*^yl4p}W@^Hyn5_xpM5!i>3-dK$8nI9| zTdk}V!wJY%eFSFOfm2vjcH;tmQhMQ_D)*rsE*yp*K|IAnlzj;yG(r$=#2G>#&JuP& zVIgQ}B-{ZHVG>EgK0Hh~geM3`@fKl5$9wS^OQHyCi6`j=t^7tkO-4#QqbnKAz|M2J zzM$(1y1qcV##eM@MORixxs$(-Hp~;saxd%i%lcelQui4st)@BpE2)LD9UJMP>u&l3 zxh_`tZPHL~w|!+&e>>Xo=Z}TYZz!+6oBDnKnNNi3h>2SqCC(wvJI3nm4A#NiWG+?A ziEu+Ip=lvHXQB*U-Jx(u(>l`IqLF+w9g9Y@*?1z7>rREk*;Fo{jV0oVXe5zM#dZMDU`m5!dRVpa9|S7+Qme1HDRo;Uj0iSE}X zzLxK@cl+2t;ems>%ye-q`OxHa|ADmF?Yz2L*|@VaV)zaJV)#+6YX&cUd*z@f*5c^O zZhF)>+++)$m2=N=Z!=pIUpTjjznl}o;hxiXx8A$*bME(;qCqQ^0||^Euyh&+C1? zJta{GqW5{}*CT`$$di@e&~30G47?kmZwW$7T8q_XrlxPz()yJ*xwu&L1H=YekBnOr z4nCZLklYhkmZnhx7szqFGVqY!m>?0sNJ@y2kLT&>^=dvw*eARgdbfoDy{ z^bYAwAtXRn$e$^Id@tPDD1dE(`j7DixF-47QK$~QKUJlV;Zq^zsumsd4i z^r*;q5MxFpVi_gR)NE6$bXqD*txHj7rYhXDs!WAWl_FGSrRmaAmBLI-mJSXUu+A99 zc#lIRV5Y6plHf?AhT&(;`IiiZ##)B&G`1M~7zGyZXz8Zt<=RV7iP4BwxJzB8paS?AWkuy9> z#)G3fKgqWo4Ri=wzPj#ok9|{!p;c;s?v9k%NlINOuSwA*>#FW}IkPJgpS2UGri|R+ z5%-R4(~hrRop`8<)aG?Y?qmgj;IPfFO4G=$6Q(LgMEjowwyr+Y@cqP*LjBO-!=Bz| z_e;MU{>USTrxUUwq^nwsn_E-XqRb#T*+dl6_uzbPRWyD?&Hiv7~o*nq>LI z7utT`yl%_WfkWeG$EqzSO2?A)S2pIYbE;d^l<0U}x!Ls8v8!%Vg=YPEZ|6Ak@nX?= z-`W?usmp_-Idhu3V@mlgeFE{}(Kh9P_wh$O`wz2E$-d+D?^!l~bH0m|6}ro|G2-}V z4|CHuRVJ!?n zP8y|b_l0jxU_!$G9YG~q;-?Cbh?U7=l`2FWBo2~D{6iF5l;RLcuu>c#2}s@&qz+I? z6cVxDBF}q-AN1xzv|d;j3hEk&P~#Xg7Ttj5mbsI2a}uMIiw} zJmiSvEQE>Al;i}`Vp{TkEyd81zy)Ryb_XexP(1pd>Wy-EAYekcj?mGc$ALiuBM!|Y z6dmPqVso`R^>)#kwJcnnhO6(<;|EjD&?A9Rp2q859)UJ=N+6^M6mu$Gln)a0Zj6!0ssI2 delta 1429 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 Yud7&KkNd6t-`sMQy8kBqe{X^R3-rqFnE(I) delta 7166 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index f2a26092cdb624cf42373336a744e30c45257fad..a73f4ef6cf4dec36f835d3c95792602d11f3df4f 100644 GIT binary patch delta 3487 zcmaJ@4Oo;_7C!gRH_SH-KLaR`{EehA{K-!P!77R%A|i?+3hD*}jDa8sf{JaEa4_+U;}da`INo&bz-rO|yfx2Wg#&82E`G7f=Ey3=~D=Ng6N~ zkP~PI$aOJ1(7RWzJEW%VH0 z06uR(R%2Y)o(}TFe9gf8RLEZUWh+>sA)tE`1n5KWPaxrt_aIph*=LYE4zlkcSw!-I zL9!Y0ut72s1M#}&kd)JI^|j@KwMVOmdzDv}T9#3XxvI3h)LduDvNVvrP+f26m{C~Yv*GphgI$KF zYc9<=TK2)Rmh+=@sn6)X&Ts14^Md^ob<{|Cyrr(pQd@5JTU=6Wt}7{vDygrmt1K^$ zs;#cC!#x1G&aQPzH_7vuzqk@6Oxn8rrvzK@9opAyK26C!Q~%&wPXBk z(J$?;4pmhbFHldg6xTm$U+FYX@9tQrY>P`!b}BC_N9?DaelY!BX*+7T@vK{LTgYcq zf>JO1SbDL`82@C&vasaaOKK1?jvH=t4^MMz z-m~MG&6$&KJ?m!YKV|5Aw{P3%ldT`@tiQYHir=-9SC?DA`YQ99 zwt1^|)7`8hbxWHi^L2GbNAqjvs!AsPxjtvEX4Sxy(^ZyTJF5yQ1#Mt)8Bt+t*t%we4W>guXn7A zY<)88@E=m&xtcV~*p*g0t)jP}vv=!Xrk+pv_=(Ot>6_vs7aDUduYG^$*T|}%!~W9h z(=~@izjJYY!Psv1&WOG%`DNyaBlYL%N~Mhxe>#(Y(~B{s!``b67FMsA z6kQUVWGXH-m6Rl!;!W{MNim7$`NgKhq=aHqY*K9L{CG=jNs>9q6dfM?_~VUv_NC4p z&ZQzpqZU=xFSdv1o>5OoPtD7CrAL>`d}E{oU(hi|*WOszV&9|7?s(7hrzch{h_%PK zM)~$kNq8Mah-N7^X!b5XB{f%X*6DxchO@l?Wk% zH#mesAuwK}<`!=A5%t4&M2P6vNP`n6R%>+iWy*I32vgIx&TPW5j}7^3J?uAnH~~5z=SBazRIQ$(M!taVAL>`vQc^7 z6%133DLY*Kn6gGvcBzzI%qBD27H0dLHJ)WFEvzY3fVnC~$A(DCQI&F(*#d>e+GtOA zEdLtEbm6imHi8>&0%<_25lDOR5)JtYC!h(SL>KbrnL6PS@O3=Nz-lGEO3#zIvgQFi zwwUrg>>0TX#8fnm%VK&B=X#TvuUx~RpHL=R#Q_~Dl*?rg{s>38XU+`{4=}(R;0=tk zAuO3GyF{FHI6Ztr!Hp{2Bl;Z= z=E4L=N$0TPL1A)(kTG$gLzMbsSj1&NwMlA77ACMSb3pA!6r3 z$Z0$fqH(4g;OYuz=^$2q0t!wMl6axq@A0Jhz2f)stC$H0>EnUk+n)_(+?~(o$|2-p z)41T~e_}{ED_%1Fs3;wPg^+U+ayC}DG*o1gi+IC0-1HQ>F!8OrbFvNqX7HxT+#HO+ zi-@Z5CJOYXAYS6*IJAHT@$#TVj*mx#ON!)jG(|eoaDg#m=Lj(JL5yKG5(w$c0SyF4 zj|RiO12}qeJZ~}C`Y?rfP$r*?Sj`j@WpWuOUT4FIO@PpkAWlGWpplc{4^0|C5bSS1 z@sdr)X8+yah5k8EFviS-dOrt{wlpiZ{fjqF%pzX~>`kw&wfLGF~v~?07*sKBc+H%p#n<;ojNt1wJ}(cK_ebJ_Vka lgNz3FMEBmWg1`4G1yca>^2hB}z;Tb+dV?D`;wX5I{|Dn6(jx!> delta 2932 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

    a&ndIJaaL0OdJCgrC025 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

    +_tMHkN*-8xGz&)Um>FXG4KbyWFN@swo8 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

    @s5RZ|pGPdGts%89tvPNBK3-A+VkE2) z0jDqPSTXBZN%WT)LuiM}uv-L3B2)-#V5#+VA}eKElJ-5CMNV$iGO|VCAtS~{;TBt7 zX6S0nweJ%tktUMA%7A}BJ!SdsGc#m8T`{jIHzu>RsmAfVdCcaQ1b4(a-A-vaV zwOP$=NWe*NP(*DfAeE4Ag54~446GB48V!7DVx!Ir)}U1Z`P8P$^S1Oizs4G}W?J60 zQ3j6yG2>Kq>r{s-z|b&;P|TQ3U)NzWqUq9g1T4E%U=xe^X|qN?LbdFZ;FSd;>$|(0Lsin}>H|ArOSo-SySq=yXVn-+P>3DdNwQx^4>h2Q#fSv2#nYGk}nZOFhy z5D>I6tEcpk95`x(#I@)`k~+|0^(h$ywIqR<#$ft6`mY)6*Q}nmE&Mjae#~ZftYfS* zb3`x8@(auI3#~I~`iNSY*31VooVJA1a-%2d5$RhGNYz^E8B0AAiE_eWp0GxyC$vw3 z(XQv?Vp(-)lAet8Oi!v2q>ieI?UzUbRtmw@HU(_1lAcroDnl_Rsd7P63@5JTUuZaI zoe$i@+c}Gn6&MHZ!Z?Qd{2v;V>C=FMy0ZV$6S2Az=CvZ)oh`yajuiu7YH`7VMddgD zoL{my00XZyf{*!4FnF6wdTg!YwJN`Ne^BVq#o6UzQW736zL@z zjfrgL6{55Vk}Pb*V)qG7`WrPL;%FMNuqkQ(NK!~JZ_xw}A8`9eT2xVq+meONAyJdQ zhE#a9F(QjpYdy^R82!s+gEZwF!`~xyoGfg0ywqN|wSKHNuvufW{bQR3^Rg6co$;Pe ze$o%LD7kV}j*F!Ix>SC3Vx?Rl2vjvtYfZAw+drFZ|8%(~4=c3*^s}V;+%Nx zhJ27BC6hll?buQw`G++n=)aQlM^m^TC+%OG#h?|Tr2US$Ha%z?6%K~Y`C z@H;GdyM?!#j-713uVE$MZRoZ4={5D4#j2St38nULSL7aVNnIJRMEW9xSwsu>8pwO` zvbFz*OknN)mg{iWWPOFaqjI%2B*bN7)^JZvcHbqn`#zrk39TluVbf-ON&6#pta^4G zL(*o!7LxdFyL!;eI&xi3VC-Gwb$cB?T21pmlG2iYOl#%sk8p=C?1z33CKx5uIud6ZqUCvv#ehqvdWD4A!Xc`Ido-Kb{nw1Oazi z@UJp$whYODA3b`}+Usb6ENcr`V?XjGlJ-aT^|AF~FG#ia$3!leMY`~mg`ctvU03hk z?_Hu=?@w0b1btsq*q^DLuPEl{mi1{X_OykMS@@WRpSAF_=04dv*_K4uhP$r4Re!7N z%M{bp(A=yZp0t;E2*-A_CKrr~Do);Cbvye8BwRW_*BT_FaHn_jb}DJ#YtOy%%1XZl zCsKQR+!u1M7$Iz}Eo{rUSLm~cFxzQ0rQ-;66`L_)qUmAFW}7uEWa++3ad((~v!|DN zc(!wwk0v{xLPFBsKE^Qj4ZiM=_pnKRT?6rZf`M+Sp zoE@9PY|Y!D3`4Nr8+PqVXcQYAp?TY$$lB(i&^G_@ws|gdDy6Dm4LnPH)d^D zyC~bj0rIr6T09hL@ei-X!?6~J;Y9|#2Frtfw^#;%H`To79~l*bXLE!8951jr}Dz{Ms>Q*Lz=jt zH>awaqPM#?DGn*)Oh6sB?VXK5%F0q#-_<8-npR70jYXiLQx&0AVm4kGj~aDqOQ)AI zr4cU}V7iGp)GKg!Qu-sU0ONi+Of0F^e3I+3 z9jrf$)^B^k1>Tm0ypAqW-qg23Cf=S#$mPCOxklCxFd3Y-=`pczT6&X}Vt2urDaN$9CPUSHLlQGhoKsF(Y&x zdo!`Brf0(0GT9Jz#%y|eIwAxYl4aS_m`NhjG8w0er>Elvr)gZi`qEax>FEh#7k;5? zbaqJFTSm1nW{V6eW)%vJYCe=~(R66Vbkcc2b9N<7b`+tlbV}BGmKG-Tr)d3$bp%3% zZ3fJ2hzQ823jE!M*!A@>*z=iKZ zlTLb*PSMbinoUbESMbo$AxvFtZ=S|ylv)HRl;E%i$kfrCSr0Trxam!@)`PV;Wz5H==Nxz(Zogb z^$$dupUN2Xjn3zgX77pAOs-n5Be3km1<^05VlhJdH-?PKGNLr0muu9ERav`E=GYp^ z^0ov@=N_lWhgL|%7d&KkutRd?=+Qp!N+;zNm|UtQj){gl!4`feoDd7VMa!%=3t#Fw zDHSC#Hf6=Ci%Q>?4x%3_KDYNn&{Ay(>CBq59PeNc5j4}u5@m&2^8w^y@(*5Qf%n)f zHkD8iZsP0woELSI!S+Vw(O8*|Y}1RPliW>is5(;Ty@odLfD}PTicmy~8eAw4Vim1v zTUL5g?@FXNR&ToqLnc{YmGcq_dm74gzJ0prgsOCfw|BufZxRk0-QA_o#2= zo1M_7Jd{FO!@T=Gc0=oX3Q&1(0z~vAJRbZDor4xBc3YBk4#})kKu?7~qoqSF;PbzF zP_DWLn#6X2%6`+Hmzh!9cR-qsk4O_%;s&Q>OJ}PSvaD>XX70d7o;a2V>)O~^v%_xz zh*n^&s>#m&dcCf-RCyO@JO~}s?g1^zP_sfz^gN`75Yl3VJe876+F$1}E7gPMoiyx% z^HjT4u|MUkoT{+5L|z|B#q^OG2xJKf)BeNQ-5uQjod)QQ~so$Quwt#87 zb&cJ3Y@bi&ChfVp$$sVbHuHwb6^32cdD%Ly^>kpw*3;&L)+_j)()aUar1X6o(5h+cjJfa*h)m&8eDAYoF9x!yMu8ZTl(sJ z5NMLNqVd*w!WKY!=A_aSMtwe@oOTV1=PmMtg-=*GE`Ig4TBI$0^M}W=+imo@6t+%DxkiXAfZMMiaOf>b9&S7oeloeqJF7p-o_$4l3F(N&{fSfyi6os;XX``CP z<_TpCOwxVU29lt96`j)|kS_D>Q-YyIi;%nHOFepFV?qKubI>_0RJ5tC_?eYxu+KtZ zX?ExI!*LTsfnhq@otAM==R9CIK#)bH-O|TEdgn+XUA)0|6U}&X0G;zhm0)(x7g@G* zKJ>EZEEP_>1BE@Rl&f=OAXn!I`+1X8yGKl`pi;D;pzXFRj7{HOA)&kiyWwXnY8YF* zO-lL#O4S;XZB#agj9_W=qqg4Ll9#$@*)X)@cQeIjXVq5r(6w2(A{C33BnDP(>Jz7_uh#9 zCYdy-Rg*~YiLHuvZ^`huKU8(UTB3zrK%o~z*fEb^F<^D zB6MoMF;dZT$)BQWma){haniUdVE00@IAK;uitdu=hv`;X4jn9&2{-3bN7?k}a6>@L z>x*2WAjUT>yJ&-Gag75`I!A4Vw!@NIfYCXsSepuTwKc~Ejf8CHC}0ImC8*sweVv6j z8YUYpe4RU6O>8e-XHjNUEN;*#z+{mFwoG}FG#KfGEEg=4EZ$<_?H0C`q{VF(-e&Qp z>le3L_%@5b&BCTV7dL-O7Wm@*GdjjBy%G8|(22l_TQDXIKug%wtVg`%ddEv zOh+eE3>>*QJbi3qt;XF=6XCuWFXXwU)s&l~hbo6Zd{kVir&@Vhpp^`$pkQ@Mp*q58 zs4gxhTqqW>0g(RKk0Ab7o?Tp_TgdjCn2mrUbi&su_<`Qq{eo{B&#tK^F`C+9em^tA z$SYQTO|OEF&JGD@o;Cv*GG*>Hpi(%x*GideI2AyHCVrsL;GA{)kJQpfr~toDA%~`{ z3~+LXjY0n5_kUPCz;QkC#+#$}+(UZj_%G@!fX~n^#F;lxJ4CWfK*C$31uv}OY`+K_ zmFD&;fpvBCECH!bdoxya-;a{mc4|?E?IO!0 zWa4d^HfNcL@=R}sOuQXHh?yfdR(juMI)yuHdNg}`28uyVeY~x{9V8i2M0tH@LMGmx z7Gmbe?V{>kiMscJy;D&LYyY}eUw>I&Ci$k}EYk`JB;(mcl-|kQ{TAiP7VAOel|q_d zV(25#Lcoe^d$|>J2vcSdgw+Ebj=oaS=#Uu0d0^IP?tPIwT!PKbAA_d6Gl}PzjNoDelQO%`#yG>{lewp@K@o#2wqDUPjQ;y`MwJ?!v3TIP zM0bO!dK+qVQ6LJD5tfmGh>Wy+tQSDdtuy>=ts9^ObaPqgZ@~+bk=tw}DUqp0_Ho4o zwu2}i?~=0*Sz#Wj>^RRIDxr@JKF+Wh=9aV|w2pnIKGyIQ)obPSPUemWWax5v&*doM zPfL<93jyTkvP{ZZv4#l^GKLC{|2?ga9je_9peCkE=AI7_)(=!4B1*!x$+iJCI*6nw z&~>&l0E`H&&k>@M{7bERUW=MKi`UE|k<8EMzVYV)Vy6rRKYd*@llwj|^x16W$jUKj z)yXS2&ovAl+rH0Z+xHnq1(a~%8I9!Gi*nn_fxDb!?fXI_2pCikh^p=TvJyH@H($9? zDp`>Zu0X^2B>}G-1N8`kyS-;dPJ;}J=smwA`vT^Y<+A3YVEj8Vft`S}8YC3u|4TO* zQt>01UsX)pSWMigN+B4I*$o8bGawM)n4WC?R?bDyHb%~vfvXO+2-d#xWSzLOzK(e* zDT^S0<4ZO{_>$-@f6uRq6>luns4w^@l(V!9rhR2FH}=8YsJ34ch1L`?8K6)f)D6k} zn!YGiwC1M3Z;`pUAH$ljlWfxl-)@c4@E&Dsy0YdP0#V3bA6J_gv(ShlwnGVFF$yHt z$sDF6T?f3QRL}_)b^&RriN&nNvs~&Iw`|A?%RJZ9ToG$Y5`lB~X&b#(%~&@ZW%hFdO$H0 zV$wX558!Vmg^HhC5(@FQLY73EV$%FpGON&bn-^jW%x{bH#u)N`N!qN7n#4PRDPgRV z%=>g;Icm@treG1w?-uYNeR7Eua)~iPmMn%EaEiLoQ{=m7r#kJ)z9Kl;gr$~gqp%cM zhhYLFJ+PrZuFYjgV2Q}0B;FguGw6II9%Eo+!VZzko^C|0uV{AOQ=>H`?A{FJ_nK>Q z)YO!JSDjThJ5dRH^Pi8yQq4kdztMAm4k4oLLs!&w7{ZGHmz>ZqURd1Uo^u)by7iHF zS0sqWN?<-iXDU*MHL3uC!rp_XR}L`!Xw#3e#qGJQm=V=0hA2{~bUkzn0o2cdSb6(E zR#0t;^?|Wh@?C;Fw6dbdJL+}AvLPv#+l`?7#!v`bBf#||tybtz`9`in(c1dV2Rl%82s>(G&kA!R z4`ddNeyTX@*BDN8#yGe6?PFYNQpBEFR1y~yJ~A-u*((4ISe4;IY^&3ZcI|#$pU*pD z&H{YVvs=W5kJST!iQUjQTOSUrr<|c%2o8cG_CMJWFf5}P*~L=Oh|`<%(nhkZ-9>@~ z;AGMGYW*?{+3uP!WIdoEt2Iw2mqM8U&A|_}5s24|zfCShTcaRUh1b){66z<&T={2u zPifOZ(q3d+8Hd{BJDl5t6j;ovHNK^Y$sy$oXLU`zJVO((e$B1_WP?0UHrB+YD2=z* zNsMnM$SiAnEMF{|XOKL=6+a=arWJ?|a9{ zGMPV~4ZA?~*873#9uU=A?`LYpN`y(z{BfIA1(?B*mPMS*KOd%5qH6$Zb~3!+0Rc@n zWI(OR_^j4V*R$hEk|Eacyv^j-D`sqV)Xrve83YZqH9|c_QM0WDWtuNBC_*uc_YN3EMYix`blM3JGTBU+~L}OY70zW#Z_^wv9Lu(S+pvx>m9Im*F|L9e2iaX)DqCt zabyh)hgZ&3)4d=UY0sx~82wF61hS7&nBUKRSIu1`j?q3-y3IRBw{rYKeXwlhtnW!O zrWAj=UY4MSA?&3RkvtS)1gb|2l&^|?#3e1s{M(VI%$igav`9D0{ZYTR^et#iChNcT zp=1Fn34xCZL^e@S0AnA#ts9Lb`nMNUW2WSGIYYYrut3@bSX}xtDgkbxc9tteKW+DzQ z?+k7L)+P2?V^!`eb@tB#$r_bI94D6;)_iG}Jmc-dSEq(LR`>@q7Arv>k9+H$w-&32 zl69R${PK&^#3S?XX}gy5i+qF(f84Z)q51bPpyds)5R{4`yoB@bGATeJ@t^HkVk%TFrV2l z(I!D3CLR!pgkUyAHNK6|jPr$61N8IgFd!FJt<&&NQhAE47bN5Gcbzi;@grH#(iB77 z(_pL2d37$ZTp=xUB`J-LkL5C!3wZ_hk}987<%4BswM`kprRyzmN{W+obEWQ-kaI_| z?Cfq|sq~f7hA90pxpw*QcALm(1UXh+Vhu}e^0P+koh)oGARgjlej$VD^}`H6=;QV~ zw$9LIb01vb0rj*67$IHBY?G26(1_?ZGN!g#i?mdl<&O0hX{pR%n8j3~9Qvo*`0Hq| z$3$-oL#hxH1VfBT7Ir{p8xeZsS+zMm3Xv@DAiygkKBb}yJN{bh5RlzUB<SnA1LVo%Ktv2qyc9cx2>P3lSO{i zo@utDxcyv&qJZ@WF#(Gl%5y?do;loWO3%&B1=}|Fx~lvbV=w2Q_QwI*$H_uhw3M!c*#AYu zGq9rY!n4^|TSR#Ff{>1VBBWy_NXM?OYfyEr;kw?=^+{)a(zy|S6qwGS~FLTCS87Aj+9ja#c0;0_u4$KZrd_gCIw6MZ~(5p2L zZ8=tpp6&?1(y+7FQ=$*Ef$$O5UaN6b7xGEP;L$M?=E*hDV;MsDgzU_IILDV~2(h0r zQIMZ*FXC}hSP6@wA>&8q&8yA&x0WYoXamTX+wk&`vc!e@UQ}?P%&SAn5Vz6~^&e9Z z2hj1)%101wu_C^&R0kC#j6ERxnj9=?Jp7*X( z@9U(OTpgYFo$)2_4v5QuD#+n~HfiW#_%0 z1TQwKKgo{vCue1_Yv-JL--71Lmvc(io~mbKmM)#uHq9re^(Pp*j@G_pS;Pd-t3|my zN_o{moySF!$$ay-?ebH+S?Fx{lkwe1da|v|?&xjJqISk8cI$htZ82(ddD3d9S+YrM z=X^Oe?PB%48>h5>9&3Lz^;=ZV%!WX@VCP7&U^rcMBoypCk@R*_&;mHadR_0(r1OC- zXlgFg`5@_SvrGg;-yTB$0yAWteTq`Kpxc9#hiz1xEX%DfPQO%i9ck$F7%@pOes!5DN9< z7>dL)Xu)A&Cl@tU4MO0w0zpP72H%EWP`|e=+d6IHa9cwrOD^>^ZZ*-#zWKC;3C|nq z$`KbTf*3K^Z<|n*A6PLkI#G@DmRVkJ^HtGxV-X=o2peLX!;CrP4>RV%mpQx)Rx}DC zY!Ow3na9WMkwh@N(zq@#OH<&L>}!?j!#qkxQ#f9_%sX*w;DNqgeACB;Bzb`AW4-W^ zbz+bLg~8k3iEs9IqUi}8_Uvtsov5oJp!Ig|#O>uy++OU&?TvCLZZCJ@_SlIT;?{}T zSKeiQ9C>T0TlB>sip}IJ>z1v3-6A3NW~p1s(n)EBZn0{zZrSRz-!Yu_Z$;Yg40yPx zD12lX1|d=y`~9?ktDp9+YDN2C_B);SJIl1+S)~2WKHBdr(|%{9eTKNvKKsgP z??*dGo0F~+U~E_3W*q%S0pHOSl5|cr?qpgQ zGV`{~A7q(BlFqD7RsLM$j@Fo1cyKbNBeXW1_O|gcupn<)J{iy-ih%eS1Vrrh(tH7v zK!WZ%W4%Q0L`QxzLK7sbq;hv1dQ?>OfV6RUUAA_=F6)g9g66^9b$^Y~*wwFrnfX%K z7`x~`2g738;Ce7lO$uzJUR%tTa6A{sn0=qFoo8loqPs~qoXa_WL^HF%0gK~4XV)Z2 zRG_2v!N26v}sahz(4h1?0R^M4aC+pOJ~R^=)?u2+d5RoFCHwescZSM^IiG20}5-_vXIvg;JSc=xf!y;*Tq8DT}8hcB|As;R$u4< z?BY6`M`tcmJN-(B5eS*GQW?)%!7J4WkxSUd#&2-iWq5A5Q65RhlczSFC5sykg{<9P zR10SU6PHKHRc!yp*o2MO;aZ8c-Qti7Qxv5LOw4^thOTC`i?`^UIkh;cl0-v2(l2tfhBHT|XAvW{n|0$QFT<#iwFaq)rAsM3kD4S0DzTHwk^K{`T` zMA7eg*|1$w+gxFO`8sU17pKR2wXhSi7B?csV(bodDxy-0DnxM6(Rk>fQJ}8niYK^& z+VPAaik>z^2@FB>Lr1iT1W^}70!1E>7^s)DYoeo8tHst5v9wV}?_}}3aL>5YB7(Q5 zeN_216m`QHvUubxFBhWF_kJt{P1mg!A6)ZS$f3O!8!u zXh9%7quS!g*FL$BEsp$W@@7ypjN&I7zHS6m+#>l9e$w_ED6UXrffiB)PmY&Bj_Vf# z*wci_dU6%GxR!l#)$moFu=1je(YD3G9hYf*r@nr$J2)}@;yoR<$L0Boh=a|Ze_<`Y zs#v`H3eGAxCbtBe7s#xiNedweM2uOJ78*XF%=Q#+#Q|k;7s!sO*`J-bi?Y{~n^Ytx z;^E}UO}hU)Im}NbC5QQ`GOitvEBCODwB8bPqyqXh1T&&U9fbR zjd1ouWYS6v5kfm|bg8~_F4a$;Doss4YJr$r{y_auGr9E(QtP1L@>2&3CKMj;2-Il5jJ}O@1!Ut`Gis`46e#+7<{%ObTRA^~>D&~7l`ChYpG3Jb7&Xi)<#&tTv zO47F-7W=uZm8WCLv&whY@_7#Kb+8;~{QGSDJ{wD%Q;Bm{f;LJNl7Bp=zOB@^E%j6S zRnx=%3kgYQjpXhcM~D2=e_j7HUDUZ(@mf!}yX^FHw9DN9(ZzPi^ZccR@Lkijj8cE3 z!4G4V>T)_$pIRQfdhYw#1$>H<`1Lz?Nw+gqyf)QWS=BpLwVe1?R$LR{4}H}d*IQ$f z&TT4m+l2}>uC6x?^fu+%mgkDF8|&&R*?^-srn5T(WNDt^ z2K_KG^xL5f=8H5)X1yttz4-|vFjz7`<3DTEQ2a* z++6>ZrEFG;xqbVQ&Y%Qf+Q2++B_2|VhcZ}d))n;$CD?&dEh}jytfNa+HDG0sGWdBO zwlO(y;N1f0_D!e|h6OcQsyp{x6^V(uXhnAC3Qe`}NCLo%ypH zHh-(Kt8$>Sr}A*+@yh}`Bht)rEDDmRJ-d(_fHYU?-k?_sr*cyH&{O92L19@GE(1oROBJScGA z(f{8JK>ltJNRG)jlySGw_@FTQhA16zaB$%9`3h8kSi@SE!+Kbd9#L=Y%G$F_?Kz~F z1B!dZS_rE2H@*1x7enmAz#bNJJT7qG4q(RM42W>3GWHFn>{BClSDFV^=fldhtMV^FjT4)KGs+)TEmpH1vyIB*iJv7lXN57(FIbzN1D11BQkO zMJPkKCa0?-6UPGGyGQ8kQg1z|&Mvmn={mMY1V5-64p$lx$^`ZAx9;6-Sd0UMM$tp# zupL=x?f7HL3gw_^6Iwi>z~ff!1U*R`$k#1B+ zXuvJmD55Rd%%jEQX&!Irkqi>+(+n9{YIor6ea24Ev6KW=4q5|g%o;v-S($RepD|Dl z2*dqKX4Lth`HrXw4?P+>>9&jYsE@FJQemeh{aGA?pXtwVli=Oc5c7tcF8~mw(Pu{fPRTszY#9cxa2dHktIuRzkBD+ zORm@Vx+DDmW;@XL9N?=x@fIZX}xe$wY6~f z;l~c`IdIe6dmeuL(S3&w-?U@TqX+gq_SnAfJn~o;na6E8bm-B2-+27co(;2)>^bz< zp8Y z`1pcAZXa1XxmO?Ub^6$^fA{L&{rdNi{_R#mr_1A*f#cdwrB5DFRI6o9ETUR1eRrSL zBmFlW5pNyiah%8VWPFurn0^*RpGoVr==#zGnxCK2=_vttkxOq{Z;@s8B;?n83jrAIo> zMF1T2YH#tYzZKbCLdvRQ0EDX zy_&U9zoSbY?JKHfm4~0NRmRTm$SqRO*mlcwZ&t4T1B1x6Iyxq$D?0Cq*6Gtc=mcFN z8LK~`P&mm#<`=C2bV~X%SK~qJ!p@KMWLAxS9W|tn0*res^+TpK|CCC%j|#JKz`Sj7 zr|o&ho@Z-Imy_j3BwI7+jr0%1y*{#3zcVR#{76cZ7+n*gb=x0Tty9n2hkim*(5DU9 zdFAOIC*wx3;}iOKh>u@^lQu`-vI09@xK8x1$vtk3e^(BS-xBxlS%2%N2t!NSbYbhq zk?!%g2FZSlh}hn2Ak=;Bov|Vh(WUL4v2FMn8ld|xzVxy}Ckq*jqTwfD0DU^k<7ZRo&to$F$*4&0SKK-|45-q~*n>imxNJH+VpNO+2eI3Vd9Crf9SvOJqLz4df# zYrNZ@oNrNvf!4CpXZSoz!f77otTZS1I|s60>wTd6mGjEf6S(-7@DoFr zt8;!xWZE4Psm)ePR{;2ZJ)Gau*ICP^uW@|~(e5T5^j7ya9-Ec5yOqZ_9@_%}l_d*N zqFn(bH16J?QK!2j4;EtgD_6@%dQ_oKN~Un#FW-w9QCtK(5z@5^DQ4ECs}%3*(S16g zAQ72YWMkB?hmC#H33sWg^x`oduktv-;}nnMJQ(GR8o`r`FY_gfockTuJv;gJ<1WeE zht2+ovHpk}O<@4&(kUmUtH3p2bY744eUT_0g4h=FmC5@e(&+{*`YZqZc*HziHx|*< z+4rQO>(m-xpCT%)XVQ&)ZZZ^}5&bvtxglI%q%3y*RQc0NJb@@z=fO_aiKAfA4u>UkZhifXM!ZR_C>Auv8u%P1N;yGX;CY=Qh-;V%u zB5U;CS_pE~J^VZhmeP^^Sl&Al zt!4JuUX^X@XAhJ47a^8D!YlVgA*7OkhVe@qN7)vf}`d`;b6zr>1{n{53 zdZZDcIxn~p!T54|A+YAp@`&ufH>@v)M$y{w^uyPhB*nuzpBhMN@_ebV`q_rN;YS z+rmWF0_}7@m3?UYtu^Njm04JmQRhr1b@eOlS?uX3pU3TRSSCsIGgYz4=cxN!Y*{$X z3hZ?}7;p=>k>x`|+ahEg5S?-e*3}}PhISWbv7Hlbglu~iv|&?fF;(sLRI*r!Jk^*= zKdgH zX}%9ea@$Wl6Z3>M4YB%h7=8@~HHRXV+`i2|bTpEWkF&Zg1Ov4{Rzip2yKsbu8~Wl4 zOISFa)!##NEM%HQza_}`c|CedVkZ3-7Bv93b5JR6-5rdzT3l~c%&lK4inyo9^B$U@ zW1Nn~WN~fGb|a5xnT_yGN_Ob`&H>886upgn{=w#0m<>98LIr;OgrpDjNLz%S(4%*6 z#6&+66odMmpcvGa-l)TpctXYcINOl9HOsoQKdbhe(sNl>x6Ra%+U?y3bz5K7%~9;6 zC!A@6y`{qZ#7tla{hUl$xcD#7kQzmao2ufT;^e4v-Vlp<+;Z*9lXfEt`6lX8sm_sL zew82}$x3DWX0w=W!b@D$+YI6DaX%0<0o6Gld)8C8#ZQy$y@yJf({4=dZ3W(TJ{d!% zN_)GB+Zy8vAtpr5o$z|^UHLNSpETGcQk(hJ zI>}Hye1|^$7J^h;*b*;Dfi_AgZ2sNMr%RBGa(fZbI-^tuzC8!Ny$l>GIPk4y;P#mV zH!?n9z<2fm-}JzaWcf8pR_7I}H(`vtK4*w`0Gnto(r4Gj%yy!68w0C_0Ocnm4KxHrmZexvHS_|bVzkmHB_B8BpHo}mC{ zQP?2JCw%DY_k=!88yk_@xXl)kTjO)jH!A=4rXz2}O)< z4%sNk_-upFlaO}tAfb(;wrV(5Wzna?V|kU+v$!@ZmGLSL%J32MD4w4P9bINW*T7yB z<>zdvTz>Zap*>%+=Z}2%t2_ZM{T_s#KWWt`0-`dMD==gU0@ zAbvhFtGg<-WA_$K7xgm6NDYLV|^Z8w{`?n>7o-;%VqC!3Ou zt7o-y#%R$?wq~(bZ@PMVV^yj4%SPPr(Y7NTH;TJ-rh03C;ttLj#KceMC1tOd%9WE! zrIu>@j^;AZDzzU3OuxWJpY5YiXZyG=(=k-HHWNDp+C8q-Z#kKk==lV}?!o01-L9}n zzh9W7*GmfJ*b;X5!WbUtWqPV(l)LPnfX@IMlNDXZ{xyCN2YVX$XVXjrY&cL)EvT$F*j{bxv z3MCE|OT+|9loDEn5=V+9Vge;RK^I|*;MV(&(-G~v?XIm{!_Pqui8sFESnc}HPE^(G z!IF87JXEU1D<7<-5TBPU)iN|ko_CeEg`fWNwtT2iDG~sMNPa-fbMlj5A*V-@lgOJU zY;;gatRL+VPLx?#=e_~4HI9l+e`)ph0hR}I)rH?*4yc3=%mF>xkjVQ_cT zVDJ!!Hf;=(#%=?)Sq&LnbQN?b)m5G9YD_2gZv9GxC1>PFSYlU1N+f4OX34Hc@<{%} zmXZ}Cd8LiyNQ}g2ZOM^hDUrO|{bMxBNMvU}-*ex6uYLoW+5KmyZC>4<=bn4+x#ygF z?tS;k%5_uJYJ}0&N_&TO@uoG`wT-x!4XsTySM1Rm_{oPpYa`bHp`WQ#3piP`0k*EK zRP)$Yjg$OApJCepxI%qbfudOnf&=W!^8&zl*O<+ z)U$rLkLK4?-AxHAIjEM`H`LldR39^`ZLnJ0W{|9LU&i_&SDgc+zB=s;S@lf@X-J4x z50lCj8w98>Fe$3Le1n=Ys1h-?ht{cQ4_Q}Hy!zPhT1#<@`Jtglhx-tkd{bAGIUST%|F=%9G#)?jvrz^$|J=TpO&YrdYC(!5ZqpCmiUq zsd^{%0Aqa80O3o++)($sK^D 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 literal 656384 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 WU!@sBHh9`WUhUqHO7_(jA=6~BbIRs1sI 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%0D_lqH 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%0D5~@jW)1Z<`MVBLqj-`U{F9lk^hdWOZs(w*%BH zD!`V<>wq|tumotil=>^$=pJCN)5zPBZ*{j6-E{}u-D$g}L=Pp4j2^=MS4Q%qiI(Hh zM5q3EqWyS0(b0b)N&kf;{gcVaesY2T{@Jz3b)&z2(@Vn9Hxrf4x4pke^zG!k7rbO0 zy^y5)UQ+(|lJb9;-2X7Se<`sAyfj}Lt~z?OYxk8#)-?~$XV2EEd^YYX$uGroB;O2Y zaBX2@hopHyDzV22+nh3o<%VSh)}A{^sNFR|b7j!7J!qRBG(QqFKVH*zW^nAgx(SQ> zv(5ImGLtq>u`LgVZ7Zw!BKkDD1t7%)*{{@OpUrQc9W>7_$&+F5BQ{)MGf{PZ$Uc*7 z$g+bqn9-9qy;%sF&Y{1mi8MND^hX;%Rn26%zb5)KEY{niKLgLZYuvpx*~|MPNx)`B ze+u0P^0SvwA;#hzP$(>ptA#-Mm=*5*AO}QCM`oFsmxeoEtT%(rIuQgX>Li7Hix0)x zMm5_E!zb%3WIo2JCpwCQb1c@;z}|o;ZEpyJK6ZQbr&z2AO(dUgEgA^Oh+u2EwQvCZ zNB5X>!}4MY!&xkD;>D~~;j~9iJLQDwtC$t-oGy{mVL2VKHhHEmP}LQq@6RPUAiu7b z(;MV;xtuPQ(`9n%PN%o}mUM-ZeW5ujrz_<|L51d8Ib9-y+7$!jlL z(%pVE{5d19_eFoteXWKaXrR7!c9*=KiXxthCQjmdV_|u zE6w0}KC7CBb#W7^xx-rJ4uKm;l!^{&0l|k|ZwnYo#txoiI+L8TfNOeV{-O#EY2TbP+0 zn*qI_xmscDXi}Oms$*-J+|OvKpD5ST_L8OkI&BSCTi$+9-_LLV#A10qzoTZcuu%OM zp2fw6yGF^=y6n?+>3EB=`#bNeOa<9*$;0ZF?>5M_wB>tBUe+G_R&_ew%BC&Hp0Ab{ zXB>NepQ+w=B`so?R{;Z##d%Pl-;&`Q|7s9?o%qj60eET2G-Tf{0J^pPS`cu(D^|%01Th6Ex0gx(~82Fz0#s5-7;*CZWV9mD z*KG9Gs_3|USJpg2%dKeHp>s?BmUH!5(Q-v{e`RuiD!IQ=uTxrHs1op(w7gg)qFVO9 zYz0MnvTE7JFxoLX08C}XSZ(_uVs?Tk!a?6Lsy8yoRx2Gpt zWt&DO$v$4?-YvP#Zo5};_Kddw)a!z_j|`FL^V>c;BvsS=HHG9UAo;FB@}ff0_6IO? zugOavt5i1m!edo~dBOeMx@qw~KIe{EKD+w#LAu`Vs6*})A~v1<*THguCpQ%L@MSZ{ z39%Cz_O0d@?~J*PpZ)w0%)nM>qR-%C9q;E21wpprGi4(BgiWIhJ7n0Q4T7G_nV$>? zj>|btun>vAzi|{wei@-Zk&6#-AuHUYm?yG>D4qo&N=~MkDYB?-jp}~X6J}X>MkT&& z@IBDka+8rw^_V$KWtfju1|35YwwdKbuonM>0m(MIO$GVIvRr6dxj7sCmeL-+nI?+z zT=kFf;6FG{to$UT=Q|uHRyG2dxrtf&UtMz)>SB{=zJexdJ3o5QFa@ZS}!5% zX`%DrJ5Ot&g{0icabmT$(98VvEkr`IBfgL`84WkVv3HOWOt!X;N-X`8%^_43U7?$d zhCZxReb|(Qm0=`tg4XE79iZ@nne{YlZYi<_}^| z5c^JI;Q|5E=0BT3#I_Hh%`3_6xPaySc%|K)IV_&Y&_9`!=o!%&?V{( z8dX7BNKQLBPHa&OxCiLuPp*A+{DPhKLL2=Ea&VXz9@jfZb(bn+!7w)=OewPU&UU*Z z`x3G%%4H|R=7`S<zqIboY zaM2zM+C|AXC>b}pG2B4As++_Nv}+Xol-?}2YX%GTfapjxc*MrD!lU9?B7HB@P$B*i z(R++JY|g4+PQ@Ldgrp*&r58yMeYq3ds1WULp~(yJY*pqCT?GDz*3h?bL98528u~J_ zzJ=q&`L#f79!vUOt`Bm5FRyafngmaeuvLe3E))NUd|_9RZZ9 z1Yo^UfGZPvq3VeWyGgNG=tVdsY{L9=*c26aK@Es!n?btOeJ_4fXvEn9>%ONr|5AS` zavuU#`IBo;n61mtI0(6oKbwia$E+F4Or|gg**gLF3}M)jb_6lRJFuBMbXbDTK110b&!Ko2UQFY$8& zFW$vbAp^fH6a5(Y+;1}RoyM*K!Yg1IvDgr3Uy@gUf!g1ozY(UyB5FYwDXH1p++6#@% za-29cfi&>da_+wRyWZzd^YQ0+q|Kj+ZUR@LmvH&dupftw@69w*BBRtyTLahM*QlG# zy0wet(TI&aD#m2T@9=C@myX(8CU*Rf1^cB6%j9yZ0#=Ph!3*`Bf=%=dXD0U;Y zyb{-_deLe(TJxz$Ru!#vyS1*xtvS)xs8b-wG(eX1v1fza z4pP`6zX4lC)S-1rM>lI7hDUcym-j=Xw`iHuqnl<77LLnJ?#aXB;Vx>yN6v?f|Fxu_ z;ihem-VtBLPi`3oaCUAC$jFLbCA+$!cadGM{YV-gHen+WvICU<}L-L%qZDS zo9Gy;+4Xmyd>%*lnXRl0m*f?GUJKw#M{hKf!=^!b4lP3T6B%vi7n5dOuYC^(Vn0bu5J$Pvp7E1(`+8q3_KHYr# z$I1RVjuW5SNv09UiBD<8Hc%D$>v=8Hi8gvg2*#Ni+1^WftJqz>A{~9(zQ4}q9 z&#(6{{;Drhv)q4I-Vh%pEsqf{ob0dIix181Zl>gUaJVV4o1sF6JOUKGo(GlNC+yJS zAcY1hO?NkWCeB{b0Cfav5*DPQhfB9F6#6)@NEi|5Vs~x70SJHQIPst2L#TPCzH<3jBBJbVld~rSSV@&{APf!Ob}7wtrR~%lk$ENuK_&Y^#DGAqK;G8 zg;%mid%zw{E8b4E0A1qc^alNl0Pr)86JM3N{zC%#SGzzL=9oR|| z7X`Lc!bi#OQ4bC#Ai5YvOagXPq;Ocr4;OPq zm*h#QL~CG<>ycldTz;&U+7hGc3gQ>Z5)6ZaBuG_kH;LvQ0CLdj4IH6Hq(aS{Yzci1 zF<1E%eK%Oo1nPmtLDp7;7pL%FzT3JhymNj0t6qTPHmpcoX3mz6NA&nO-$OVpzCnQd zlYG_{Aw@l^kPc*dB&QywLR!_Lk{{HCb5e88jw4hS**^3ZT#_#Jl<;CE9nR7t{`RQ_^mg0qA zMB(8oXU8!BXQ%kqOd4>U;Pj$I>0ZU~D5Atit|`E8T4tJDFBAJNRS|5q(Hrq=!EoI< zN9L{UdE}L|aC8TSQ410vmy0-8EM5jfq&d2+`qozPIp2>Tg+G~J6+bI!kxQjT1U=3W zM0wp7*=cc;m?+Ki8ZBa_MToy30Vv$a0PDN9$jOCC6WsB?X#!v4$NzUV_SL<$M|Eq# z=AGH+go%K>2!1CxL+dHx2+RYs-yIna3u^Wj6o_5w1}oP3J@TeLdRKj6+=fOlivBS# zRTKA@PL9jvUB>kp=HGR=Q43Nv?h!?RhxI!*ZJ6o}-CHneqc?;QrG&BxjY4nOUf|!p zgSW8P!{se;PLsDC%D*(acdhcN{(=hg0_E9L2zj|wu}*uV+&`2H91`EDYe!b#6231! z0erwwt4vHR`yC^MtY&iCYj)YG8ta02h8oc}GR|>Hm?5MVlwhTV3?y0Z;wQsm zaUZVkrGw*J;R1-lz|o#wNk#`a+S9^MgW3E_OOiYIS?TF%X0A-*%QEgvC~AR;-Wa!& z08kkPs3_d80hM`6ezX2kX054G#LQ3N2ZV0yADvMWfjFmU$@z&O!f)(vlBB@yf~KDqQ=l?;V>_!#&9LNH-^$el^jP| z+;c#Rb+VpflpUvY+y=QB*LA~Ey76{|zB${RD`BjMGdCxiv@>jzzS&`RO0qh=IaiX6 z46z8(J7uhI-~@rIlAwCg+YNgMgNfRZkcA=o6GOOYhPwMif*!ywT<(>bdn824Si+`v zcSuo~pX-n!f}To0_{=s$fkPZe9XyV?z zB~N^}r7sy}iWdv&E)YMy66n-2>QBo`pUw_Y9i}ff6Vq6*aUL}wc4)4YKu#AZUWBRZ zZF-xzC@pcUgcR_ZO1dTt&{L#qHJ|j+z@BTKuV=~%^ z>qsQ{$(=FZq$OVFXz%-?NUAhed*7Qme?)&pH%gl4z^MP}6>Fk-nPLC0u0H2ipI@PI zp5-x-^*~=^KF^?|Vax`l026E!TUTV=tv1fs_&%F0uvzq#$394BcV3w}d5N(npU#{* z!`NdN*&FY&H;=KmTyEbu*}m~nV{cz)-~1kX$CvF}UNH8~OQW0OPI9I|>N$_Wl<6^; zl-%iHs!tnw^zeY zKwB_1lU+g^xG}g(#3y;xRjuexuURc<1QTA0aFURP!*<-cy{h-$;@8uwdbp~-e)Z1j zT|L~wdsPq0*j>GWR1KnP+AA13!l!ehI$!1ZR#L61uFj@rE|Z1kq53oD#HW14;EUqp z)C#BdGlalnpKg?OBa9MqU`&f7Fi~{X5b%CtgzZ*IR*5%yxo|q#{#|-W1qRKn5y7Or zT3G%S9PQN%CT|zWUA-!JD7P86nuUO|3>KPOpt`IFRPc=dFGDq`3#vgqfq*bBi*NEk zKndc+z2|i%1?h0`avWs%F!M_&RFn?tR#)FiP7iam*MO16Tth3Ac6CU`K~KaSb~9%x z4W7rSnz1{jXVVpA2&;NphUZ8w9PPE5p-5R?|6R*NQyq(9O|r@xA5SlxWz0^IBmG#| z#;Yqi+J}2Rx6rC{<6AlN0fzZ_Ne|@io&a~)t@8!B;d@G9J_*Es0H5w+laVbZ8gP70xh`o;wM*SaFJT2Eg??7G(d!DLz{@eQ*h(G(`ro+kRw=&q8| zJc20E?J#&^7VgOrqtQ_h$`a2UEEawWpj4WrB~TcCG*J}ZpYzfFWLY_?wZO4p;_0Ae zPSE{rfOSlCO#V5_Z|5~GCj*!WX(%<49i&hc{ z{MhZ6i5?Y#bs5fp@mqQ2p&|BxAf}ESwXU-8Qg37*Qb_1o#TISaEVDgd)F+2Tnp3g@ z9}=R?!xAt-*2f=F5C6?-PU}`(eUV~*%F#aien5I!z#=Foe`h6VC|3rLEwwu{vImKc z4`i}4aXvVNwbQjCWv=K72DMQN1Hlx!Z4!Z{%B})8Q}Bf-5QZfRggkrHI;ot=b2pFk z*IV=&x?6QAb)9>oD!0?__5t#~&b?W$74EH?ZnV2oCtgCXfCJ3bMIyUsY%`cE`2{kG zjCbaA><^&IM@fcgL0%Hps8(P(pP2{={5=xlZAyrNf$s{$n`^p!Twqz~+fLXG(FG_0b>HeX%V1 zql$}p?t6<0aC^D14T-`od7CGT)%XE#*LbLkmJO9$0)|dsbeGwhcR^u)2%Owf`Ox4*KN}0IK zW|1JuM72Cv96zfCGgZG{puw;yhL8NY%4`c8YZW9JQ83^Vhk2s3vaIj%9GG1*c97XN zV-sYBLDr4U{m5n2F68r`+resWR;{Tf777)a`_#eEW$sGG1r5 zMAZr3qq;3p@ZZ?8W?!J7pK!FF-^8n{INHzGaSAu9e{%I?q%`9gn;G{rgBtUlyaYhz zg6K`K08p(NLL&x>TC=O|bQ;=9$~SPddpoH>;OFxi9!59Ecl(@}DkJg29F(XV4hp^y z0awTTY%{M9fgBbCS%~d~iG8?`9&YBE6@75loLMY;B9db$a+Mmf8)a_XDVV`DR4qQ0 zRicX;Nn;X|goMjwQT%qhX}ru>jFgIUYLU7jsgv_(g?)$T#!_Ew?TkAO$immyA5_cg2Sh=NH4Ep8 zd+SK(MiHGUUYlS5qp^xmSLXa=u$U2kU3hrDFRg7WIm&uA*gM%2iru~S>A*b{g?>r0;V~Fqvg=3nKkwtw` zNBmyiQ5-ZB_r!m~)D?$kjRx;&8amZ9@AID62yd^t|n;O32ALjGn*)Tu58 zdC^T5)N+5swTV_=T0vzPT$++-EJR6Q&SX=_;?yXC8&rfrvCD{DU;h|B*KTu9akT zfTR7cV(?-VNBdnlCH`7E-!*02c9i%7a5KEu11NIx7NDD)NM-%Ze%a_^;6MQtcYp%K zrjdwEG9%*Ja-Ll|Nd|KN#8%0I`5GALtq{c=bEA{^JiyhMwf7{PnzQ`L;9@|B4sUOYw)e001=< z0R9dDSeK}*hIu_CW!I*}^)o`Mg}Ve{yp!er@aR}Ew8IJ<7KO`JcBq4cD3Fz)O=i+m z>PO$5?;Ronn7c(Z6!Wh=I+p4s)Tel8@g@YP=*A)9Dv}RU4p1H_3^xm_P8I`;u!KecH3JQjVN3Sx2N@4$SWCTmtcgf6vvNvIuU0EGOFS2^slwdz#eg(mW)d$`2 z?d13ZNBg~FLDJQNsP`U$e%#2>es2`<%KVbX_`iAex%WO_lR~q|BX!fwtfrpP(|{cR ziC^tIZO6y$sR5Z&Q?#QXK2Kblc8g5GF!wcy@s_vMy)bor{9(T5P9~sF(1|Kx-C)g1 znQ!2SsXr99m$OMTU%Zo}{iA>3jCjf~A*^RnH10Bzn<;Sxx{joeD_H3YnT46kH8>munG6+{7AuxU8Y#`da_1|8w%-NEZw}l?g7`m!?2|$B6*exh*;Tf6sCDnNZJ(2_ zofZ!mEZ6?gXO~8Ak00PCw-D9nQ=aq}i))GEsQM5tmL2g_D>=zbfu_>V(7QaCv-3P< z_1j1fSF@aa(&L)5?YKK^ygL)`H|RV@up%%P zR-1}5(?;UNtIR3bF+`R_)C$$^0z!x40g|_Hw0}=oz(4TQs}zp%i>@-&71J<pchAvvh`0HRFVE)sPf(-*}zJsR?#&f|mqmDkWw9 z<6mMqrN@}F^TlW2u=YvXY*sq5HP@8BL349+2Dnt|0#v5`rAk_EDo6WE4|4vN{;HKE z9n1r-cO42l-OpZbqsOcpnsL8lYKWghgc`?jO~M5?jiJJ3vMYz(?Po5gDNN}4tCp|a zRqU1fKGB<-{Pkfa%!i{FYUO@j^a;IAi$1B>Qn#p1o{w|$Yt!+$?1}Cc=!*)sKzZIe z_e+g1TIYVJLB;yG-><}5fU)`?j8*(t`b*m@LMF~Es@Nv(Lw1>Dyv%*D%=_AYwMY6) z4`18n6F%M3Wl?AR2|nRgrjD0rMfiB_PMs~Pl9l8V_7T+(QGXb-x;(#cI8k@9ftfRt zFlx~Je5RU6KYa2hA*g*px^ZA%1QRT=ItBDYk1@8#xF{L4>GT=sKgjEI9392AfM)KY z7ju7t+^xL&BG-$#s#_|u&~6rtWZWw}jgG~iqe>@ad1S8phWek){0e%>#(d5>nnUwK4W^R*dz_h!^9G{u^j%fmjSF> z+u06~vhJm$9~^^uJ4Z*qTHrHF6NG%NtNN`_tLP>xZi<=auX1X=kjlL|e%MzIQYDr9 z43)!mPV1wJY8G<6PUY6xx;m*T#uBz8XqH{1ncUOzK7v%Guxv*5o z{GQ$ek)4yfYgwfW0iXWk>nYIdJ*62@< zqj4Tpo{zytrz&WMOkNC*WI&)i9c-56AI->O2c32XULx+%9>=;@0KJvGKhDunsbo(` zaS3D~x-H}OG5&YJ?L5kB%7pEU>nU@Su+8Y?Ap0y+|DpUTKi;|tb&)Vo?83*N$J{*4<)NkGo0l^D_ zc>qRC8XH+xX+Vzpln3Kws1$}KFU7<-M=(q!8G>PrMAgRxjMm#qw70?fP9{q#l96|X z{#_})i)PR&akeLt3W5#ID<_Om>>rfa)K~EeTrnMW#Y3_?DziH>**8h* z8n@|fZc~s)yE+_djV;dpama-Dvq?@AFCif*mW;0Qs!=qZ#lpDJ0*Hz_o^*1a%{SYm zh>_Z5nWW3)>L+PX0Ze_OJL_+ewM_X!!G=lxM2G9Crh=y~lak2ZPoxb!h3wU2bfJ{T z{__0o?<(g8?k2>a<4lEMEn1cm)#!+Z*uIKYZji;-tHJ`lIgg`boK|66QU$sCD(vLt z{P^>n$$B_h%L;*BH0=Vrm3m$aR<;RsW@w)+j?^?3z-wdih{oo42S>+*zjLmkfQ||8 z;ry8X(h7;cOfqF;t~MrfC=)Tz=EO`>QaS^zWzy-TFVoVa=GQ4&NB9xGKP5#^7Flny z6gXK6d>=n^OQ0{Ozz6{WT@h>6#Bh%9coLnLYEZrj+l;afVVj*A-5-CEM?8jyZM_Px z7&!u>C{Pe4<|oaN^e&c3HQ8VTkl`RMqrQW2Zx;I&_B{&WX$c0BWsG4m7Ud&ruo%lc z&yzvR1*L4p7=^X_Y>l-)#{ssdsgBDpQ1}eaATBMp^AU~ zWE`P^(|83vT0qbp7^okkoRIjUeNtsC4*{Ku2yLs8?x4V)<&PGNAY7%^wMUMXxUUPD z%2{k^9P*lZS~JYxSrbRcoQ)K78Ar#Q2}7vx{ro)r?|Kpz_jq`Mdoh-xUb9Tj(ZiW| zC9jG6oyq>yT;Y~-Gl5q7Fs(-OKvK*kNJ>v*@M^6o0<##z*(h^@wn0K)>{S?O3;JSK zSZw%3L$Jywu_Ci}79A1ol}md~1FU~~BN_DmThHzFy&vhFXsplXSDpZW^O^Bi_zlJh z=@1ixlBi2xi$s+n8`BI`;sUc!XW4&7r-&BMpR96};ZxdHSufY}w(mC#h+&$vNE;se zQ4RHrj!EwbDqvOMK%y`m>!95eDzg(ZS?X)j@slQh`>~&>YI*7Lm-Sk4{FU(x#;17i zYjEQ2$zYW6Lc7ygQ)TT6;$=_#K}hPJDg}S-wEUxMi3sE=@aY(ZnnW>5ksB+sm;^mv z&j0{D1w*?>rW!8&fX)qbO)h8-Zsi7}R#wRGbyC@FrEn*@fGBr`=gB}wZ1ht$3eq;5 zf8wXAKc9W#XL_CAp7rFS*N}>wJC#ctdlMsYFr)~Q7@Aqc}m z6Hd{H#dJUs_Y#eR7^Z4*7Uj%pnM;_fnRbT?$0n0UUjka0Z53Vf3w*#pmIA;86oy4e z7G_8V)6^E(!dWdJVT8#J(mhu_%vRmg z;XFctn&`bc9vD9<><9~K$s8zf{6*5#It6`@-ufy>$F=u`s8l#Qu3bTUnU6`U|1Ym! z0d~fVsiPbF*_}4Kz-Di?txR{eV?(B6voV>IbMT$MLb`=MeNer4ilc`bcEr!{gr_uf z^i?n~=e&lq`D#>6Yzlz#LoEKjd1RtmBRn({6)uWBU2k>lBY9G9t#cAtO5gMxe^m- zA`wWRi9Dp-2iC4ybf2Mji+>%Hw4DKw^L{*wuEIzHi&}xZ4^cscs4Mv4a zQcI9FqdyV(S$AM_kB&vHD$4^YW`U?x1L7?}r3q;K3P>TwIUX?%`ujD6jifY-W82Wc z%ofBU@aCYLFQ4~Iw6Yt;moczgLwpo3R~N}xwu9`IcBtK@UGHD_J^l4WyGvb<7fLuA z+~Rw@?`{y4()*fOaIo3|1xykvB-pXoo)Q#XDy(l)^D%K5Fwuu3YB&{UWt7}|!KeAS z`WJ-FCnibd_cou(`4_+v>82LiZ&LZN`PL-u8)r${^P2DYCu!fFqfO7Hg!NKi_e)R_XFYwhx_Y8 z+7<)i$7acWVf@%0$!Q|@)ldqqcL)nH29{jA)vdtXIR%ea+<^}4HUf#UNn<|_4YA)r? zd-Z*J^FDoE-n_pdP5hSN()jR9DYh{_f|JH~OmJ@8u|OV-h&u=S>$?Z{iT|4?C3bE6 zvC*kv@e}fVO#E3jf)0y6u3S??{J`jR`~n$d2PmW2kR7NQG-M}|!Hb%~y6hs!;5x}* zY<9iRAiG#I7@1u%x=;LFO}9GxKGL~PNjECLt>AcjS z=7p-vAJDwCZeRR8zT|1GGBnL&nRdE-ME-BB`0V)YO6IWx938(!cGVfP?Pv8M`m9Z_XSC}CD!d3P#*v~co+UIK zd8D7YvJi7-pP@qGij4Od6mnlHz8lJ&C~H!@OT-%H_*(H5PsG0~08{n$Db2C=vd5A|7OQ_7IgRDuciMsCH?nP4=w$d71dzx{<~TF zZ|?uB|2m5%3lr^|R=O=KQFjo2Yo*&Rl5X4huez-yIvM{w*Nj2-M`W;^J$OCm6y2Cd zMBYa(8Jj{uH$5D}K2TL{+5Hh^od|4SU5rDV$zL|Ee7buf=_<;ea07vBoqe3+}q z4-z1+R1CrVxq_GxB<+Lp1d^J4XJL@K$foxRtVB5;mk)1v*bF9Uh|otgw8PN|p_w#c zUi2=h7-Dteaj?dW-m3LR7n}!uJm5{Hc8EB<3?oCK?BvWREFlQdX;g=hXx!Ve?urnB8An*~!D%z-fUw|T z(IEC!vODT2h@r=a1*X1XM_G6Qi^EyCt^OUWD89?zO9aP1aCG)}wC7zMo&AR+V=w0D z>`&}3{{Bw{ZTY_E-;4CfF^=DGVA?PnC{a8na(^##U)SS4_xx7iS;5U9WR+U3LzJM) zFB^ZuW-rCA`U8xN(01E;lgp2P$s6CrLf-EIgE4>6L@8oBVkkNPsGc@RDPWqSCS^zhZrs&i&n4`-*-!}UoIv&f{o zhh_iG=!EI&-8ad2{{MCFE_uz~9oRs-E|WeT_&Lrm>96QU>667tul`T3@@?*~CF#{S zCB6D5u04D8PRe_&UM1Fuj@w60@73YtY_m_%tJl)2%pSINl)zbW?Tn@IulYgWLhGUr z(Xoh(%m*oE5WH6g+KtNas!jsx+($MhD;F=tNoukeuBMAcy_1X{;^-Wf=4_D7Ju7HS z?f#5A!Xqhj#Nttt;@|LtGNGX|5%=pQS<5f?y;K~t*@EyUOp3UwzE<56-)7i_;)@pX%=x9!Cp}+e#LVo z_D6$cv_gCvd-Gzq@y-2`(HP~$_K=bM^}PDU=aH}?(F5`Cxd1R*!$cBl4Al&A0@cE^ zDVgeI_L2qeO~RcMw5v*{_&Ra1WY=r-Dq^!PA)Zyv09P#r0+h}&If_~;nVH1tMq)CT zih*gr%(X;z)y^JGLI|K1jfN+ue96sd6-ic9h5ww1BqP;gHr9o8o#VxBJeQ+$yhQok z!qGW?zis4r7^H(+Gp*ne!HKRF!+(4YO<|_QL)2z6E3T>I%}9NtVr64I zHF-0wF4<#t2Wpj^PEGfi9d9@3HpzqsFR6?XSRgy(}CoeN7j zcddECNa^i3jF0G3yjT_gg)^np0`eoLm$E6`F_Hpga05C4UK86I$`jPo1S1NMR&*Dv z7;u#AZs+J+(uDy_K1!+2aC9zFV77q)@+a3sK+PPMzJChgbZf?4p#YhF?(4c_rJwt~ zDCvT2?zaumt?~a;+_?wGQQUcacC>ofNU~&0wrtBrTCXiXgl%LSWE*VB2ESimWb+b! z^6(``kW85huH%l~)uu-j{q&Cd) zH)s~7-X^LyGxVImC|rSwbW~tY@LkfV;o*BSC}Tx8#+-ZO;+wTm`ub~SVDHpO(=F2S zZP547=+a&T{!!|A@mk5`>Z2Ur(@>M#c*)Q1KxeO2GR(e29EM~b_C)FnYf9>`M5`-V z=loiXV4}k^u1Ob%W#(upkDH{MfWlmE$6}WAmJJ>jGpVYDCGPxQ{o3QsCu({c<^0zq z=RIB6add=jloDm7`^VS{2P1zE7V*vj9wA1-4=Gi_-}+Ez6prPOr6i zH~`X51^*zYxa(`(bkiVnwNnF`0QPL6YN>GIF=>N&k;oq^x&H+ApV0^;PZsH8(mn}V zy$LdBeIy^@V!G=a9PiZ7FupNEWRJ|sh%QDj_uN1CK*Hce=^KV;sC zS0T*qrHMVKTb6Sq_20Y##L1AlL`&v`jVd^ct%5TiJrfx>7IO~E;7uYP^8x;oEsJ?N63%^mBsJ7dy`OO3vFzE5D?VF!(ln~)ZI^kru`~50Pji+0>A)&L zY7*hX5<1qzGUsUAa{jqGUTv9~CFhS9HF~2}qr)l!Dr?$T@e&cl>CNTjtc5+jnb#og z2f*wK?CDFUfW&h4^d&e5eJ>%{5fGE@fb>>xxh|c zVY?fk+T9?v_o~F+?awBVZ>4#v{I6YI$hh zF4cqQs1_JNn#COq2_-s_>ff3zq1!F&=_^Fp16x8eVHl=cLaNL-1a(%G1d(min=m(# zgZb9iUEu>#ES|x#=m>QZ+*Shp>317vZ<8ri5coOz~!5y_1dh zHYCwTsUJ|ArAxH_ZzUpRB(XKp8d2GCnD4zQiQRc2NxWVXyR(V^T5SYpYAb6Kw}P|G z@lGpvy`=s5g!uyGU$<6EceA9OvXPSh32(|1-&Bn@F<+V{tn?Kol8!_jkHp;4*H9EC zaWIIkf1*NS2QS>ip1xX@BvB_2?K&6`uY<%c`F^>5lC{F6I*?BzM^i)jG=`N*Yn?KC zm(X++J;f4L*1G`EaZ-&^7FwjtW?OR=l#6)V4f5Sx(mGo%pzP8c;_oTH=vjR*6P={R z`7B|cvzEFf#HXS8U`T9;RQ_)4lsZ;`#o+=u0+&$G{)K{8?mUTz|2Gm=(pDogN5B_| z_KJ+B$uD`8Xg=xd&f(KnOG>WOl$5=!3FCu zNR}`o2`Df(N2Lr$8zXHsn5B!37%`8kIBV|D*N^kApRuQJm_s^5ZBE}H2GE~qNckFv zZE^S(=4dAw=DtLV*^r3SyjEwaX?A*&f=;E=tG#%o&K~U>ta2XFiGQk{=e2TdaE>gN z=SrQU+MikH{E-e{6hPndO z-V!rh=kZx+R-NSB?IL`IqMWcIn#B+mO;J_+exqt)&WrK&m&vr>6nq&e$#D2B_UgIi zNQ#|)4Ua^bB?3+@zBSftte8JRAJrMk-wjv)7Ph0FLQNxWACw&TB5xL-G&1(cCd59#fM)nI`dU znLTyJIBD!hhS*d@huE+j1HzI-ie}DvrdqHGb_=vgukQIrBA;; zbN9?sXryhHh+cpCnLMyX;=ESb*t@m9cWa_|D}Ni@T8`2=s(ogu% z!TO$qi5`f^hp6Jcw9ee~`+OgIgAcu5-}`=|_kHhaYiR12I!m{@CrzB@!jJL}%ta)4am0JXLr~=H`qO2$ZK#+xgFq0p8ONwZap60 z-UBazjksXsvN-(l|gGOWSbUeWf|7wt7Pr6lcjGC)@UkQqt!xlRB=5@Tv6T9A69U*p5|lTh$I+?av;J` z;XKdzGTD(;;5KvXdiM0km5r$p^{EkwlyLkgS6V|FXN#GdM3vqgmNyX$=)zX|KPwwe zUp?!k7-4zmahGB&k*F!1SIIzN;aXDI!nM|3J61&UXwPci>JQGRN1V!+tWZ^mQLD_Z zxZc|C&u)qV97!Ix8N_#TX}s*(6;>O?L#(8v71LHc*Knn65_ua!HOrcDFV|jVPk-_s z+>-T()1OcjaXKcLd;Wk6qAvtz_zZ=J zyI7eIv8Vr3jvQD?Xp}pSBky50OZT%xhgyjO$-bFe%4mOqY1Y-7bqUkOrMWKEM$G(_ zT8I%LVs`@q_=dNXU^k%^IzpE49Vb4w3B`c|f{bDj^LRr^1HC|Y7qgP^xOJj=dx`H8 z{7%;EFdF;oqr(Ic)q8_@5%Y%AT#QUH^S;CoC)vr?7z%(sQf@QAb%@nQJ5w6LTgs5X zr=Vk$@%LiUyH^j3CB^-<+&M7BIS_LWjPsnBKMFq2^rY06{`WSC<}=B>QhS2c0eRgf z{t8&uy41+Fj;s>4nOg$Z{!&Wi_4TRi6S7JSahf@5oHJ50+NNg3S-~wS?)xi<(^8*m zNu*l5r>$1!-xm?9>|*o!yxDHTyGp7MH0EVr(Adr|bas!ctm$Rg0tXN>Y2y8Ze1Mr& z_{kWrYupKdH@rkTs%&5?@#x1Nbz?5II?HaO5VA^0>L0?t&SUz} zvvS0?U1iaaEwR#iyjhN{ARUigZkIcsyGg!1jc+%|=Ynrn;@f~f!;L!Rs@$3+Eu=GS zXvT;RXpf>PlNk=R?YO9uRtNK>u`-NRCMFqYf1EF9aJsBzq9M8=`h^C%>pbm8rc3X| zOmL1Ln~N`GE;`>1gSTaZm@3NUlhPtU6)c(1UIy#zP;w93xuahD< z9h<{brzT~>ZO_|Jlh2PO)?EFA)dP3odgawsfx`?dU zmnHEmfLwr(@oRNHYZz)3q#zWn_#RekEJ~B67=qJ(K=#@V$kah(aYtj(NUgi$?#MJ zrUQAieqJ=|tp&2)mNZ?qY*R3%7OX?EvaY zSm`A&{&)_m)Nq+vsNAl8ZT%O+O5ZZdW;k;f#YEp z6`?pdFkTmc<5&pC{tO(>?O|}h;_{X8=u8@naBS_?#=>~`vd0s%p6q!-k+>y-3f-hs zaS^lxn@astOMW{qNS+zD6Vp(UpZcC`%#oijr5*JOSka zyFXIgJn#?el}87H`eSznP#2|ZFV7D(y7qpb?*KKT3`NxL08}1eLd2k56kN?#jGhaj zeku$Wg?e6%dtqT{v$O$Kt3CC{Yh6kaz1787UYufGponHE)=sGKM0hHf_uXtnD|$7frzR-&Uf0j43jQzZM{mz&W%$tEsdC|q463s-7&K} z*85$rF?%Fwer3z{Bnk=h-B-(sE>>rao>mQK`d@m;>sHC>vXL?Yr!}KtshrLqvrbNP z8kfmw=J=(~LFFZBW96|gG#Qc z8dS9-p+S|Fkq5|nJl18G8{@V?!iD)Oktt%cfLyvrYK~U6QOp~fWU*c>wCEfZds)0( z<{vAyR>b2NsJJs8M^XHZ8sfXK`NJeu8%7W8r6fGQvd(+BK@3`vo0R$R=0z1dv0h3{ z2WLv6a`)DF-$Flbw@$>Qw_gZuAB^{P`nR`-x9P6;Vjqh4&GK)r(0O!&Wb)>H@m>Bc zf-pasVV>x+OcIU-FS#S$H`l*?L0oL4nuKSA+qcB~&h&4Wl($CgCr(eK{_Koh9dquF zIY0GSj-_VTB+bLNc{rI$G6}t%de?Tpw0l{nV)s(Z-t#Rhx%Xw8ZQmH?#=y?Ph&NcQ z(__t5H6Bk6_js~Sd9r=v^6kofqX*|cb%hPMZ%~$_Ec2|^CvcRhac99yGm2rR<^5`g zY-YmGHk+9)jK>2OcuoOU?&(|XQ;I2YgWDd5aJyIzv6-Pb6T3f$Ff(^Z+zfxE1O7sY zEsPR(RfM)PvT55CGfR`^UYjj7jxK2ijrQ!rME^2$#T%WK3ZUfX>a*muzGjWr4j++^ zXc(S+kx?O{6hf$Fa5UXLiSarwafq?LM2nLPt(APq&@V`~zI5*xm zvC0!ZC3&HmkPX#D*78f5@7Zjrgt$twypJNayxNlpH7XJ2)`^RcwXR`}oX#4v<~l`h zTs~G~x#0|}4P5y|u14UE1S4!235wL%;GP2NrxqhMmJWjY^la4oS3zfqX!peQg8@rY zQ?Lk`)>xFh?Az1^k=xtDYN)@MkdKMeN2DT2hZTt`xcZG1WjnK;+7Kq`F-;QRq?D}n zOUZ^XNdw(3siprvW0?O|q!2C0=8W+%b7s=qW1D-DsYYA2H|?I2?cVFGD1SDCg?L#ahXGw;@&Jc6Y_jAa?(1 zqCb1&szbai(0I_5@~cBsI4x14Ult+yso6xI7Bgoj%_FvXB$=9G%l4k#v&`_DZHK4@`paMq zU+r8!2vJ*diCR$7mU^qwc_QHD2#x25l~zDRuhN1n`@>3$m4pL5!&5_)YQ7o*WtKt-d!g5fLHDE#&L;Ez`Sh2U2Mif95q zm!&IXEyc*${s!1=?TiU5vbD2x&rHF5DIQt6av9bAh?5o!Mp<30dA|S9B zm<8^9(3@2Z4-Teb&CN#d|G2Inq)E_EohxN*WJaCa77CHqgDU^dc;B2rF>Zi*ryybxCC@Id~)r@5)w0B|I8o=Ilyr!>ccZBm$JL_6ynZ8;hrmtZ!(;hRMV`gv6 zJQ3S{jc1DZD49CJW@8pb+}o_!Ft}i}n0#Q2T6Y0g>bSDZwug9E7W!9KXRdVkS5{`O oboy6TWv(pBc@NdAHP3unFFs8ed~|#Pa!>y1(1LdJM@;?y12r#B%m4rY literal 197728 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 I9nuK?3x3}OHUIzs diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll new file mode 100644 index 0000000000000000000000000000000000000000..233edd88b4107ef0127227111d6d5e602a2e79e2 GIT binary patch literal 15360 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 PzR0Qf|6l*h}6;9i1~%~=D8N(qVx zD5yXXPyrFd0*ZiQLq#lL0iv(n&)#0ktIPNMKQnXh*<=Iwz28FaJoBHKXP)WL)N|F3m%*n-QV@~jX%|K$k#VM`n1!V3(sw&uWy`u z_QKbke9k%P`3p}ubzx)qISWreXW>4FAGPr8^tGoh>Fup<=2;)HZxkKUQHZ|vnR^}$ zvHd98WMOy5R#9|pXB6?j{#Kj?u@GXF0W*w_o!oMl{Py<L?>MuMKkRGw&|_T zOEe7M!$rgQ-7O~GQO3=kQ4hwfGdrWBbMT9j3tOU_*G}vQ5h0m*2C>M(xo}L9Ywm6 zVDYU)m&2DLtPM0s#ksRr&m=j+gHH5nUo?E)aU|{ zzK-GZj+a0E1b?Evzge**s;4g=K93BWd!>GYKhe%#vJ9?^H;I#7Fr&q%cp@HJeV0j9 zcyC5+FD~9KI26{im)3Eo<`ow&a^)CBi&bVFQ8#I(|ENXdyAn~g9+ej_Y4%c0S5@S_ z<%h`dLtQJHAMj$-Pc{)rEsD=yL(WylcY{TDb9a7w`U;)N9uR$njgmbvqe@EMttJGi z_Z7{reN%xt;<=zdtd~7`zO3S!?++662TJex3bNQsX?3Z&PCFb&gx$K{FAuZ*}PwYZUW=JJra94ME*LUNGI&Xnp{?JxEflS4$5>QSwvd@DvPNGu`AH)Tw`S99HXcCFf4mJ2PfkGVy!qbFX}4iGODAT9)Yj)qj;h}8p2npR@#uS`<$;? z_$t@R8}fC(^EDe^m0Bg|%QBM~U#1v8;e5@(S7)s==gTq{Uq{N4<$PHt zmt(HD4R`_ta-Zktd7UgeCjy0In-0Dd&Ab`O4it1jaGXoMqj`$ zwOoGh-*BCtJE=~?TeV~CqC!-xq_2=jlWY6f#pW_aZ6CX2gD!7g&jqs58{U4R}(zqs~wX zH{eNG5Osz+*!E-@L3DJh)l9h&&F$}+R3W;k5Y$Lj2&$YaL^lJUgMMN;PV@`LUiu@#^(Iy{5MZ_(mtuYcR4bKkH&5C z)_96va$zu+BLle{Vfc1AGRtygc(Cyc7hos^XrAIT;~&mp)?|mdArBR9uu&>H*^Esq z9~HEnj}2M9+pNZXLQIvH5`1>Z<5ro>Y3zvqIgMj{Jibg^9!Pa$IHtZ7B5H~Cdhr+o zb+KCRt`rhf$LjeEOmlnUl|nl!2~ECUEmyYvA;C5n?$nnU4n3h3k4>8#AI(7V2_?Sj zRPn7F&f?opIF!zf;I(+axS;mZd>z7NhTyV5E}Ni$8Ue5{07h&Rk5R70^x_gKjM7_* zXhG7aYmJl~ZQhE*aN`Qd^ajXiI3Am;0Ppn{T6%GCtM0yNV&>_jt6cAn$AmRsj}+ixQOy2 zuczZwEsyk!4hgzwSQlaYnU-NO7O4O%@izQ%(w=h>ubI2!3 zb3^&$QaUiv(OntoZrn&OZwyJ_CDKoQXbCpmYrpp z?k&q?iR-1I{&KxAC`C~LO|3jU z`*kZn^5|>It(;qY231JaH*G2><1y7=M~Aw$JE^Ar*P7?!qgpOx%jL6Olr#@9sFWwl z7vR!-13%Dj6n!B-=?Z=pEnZBGPpGqXH~%R`;!Dxinq#<&?@G+cd?k$dX1D~t&XPs;SoE~bN%2emmlXeGk1qa86_?_F3&ceEt^E4<;O8Om7#7>GFx`=^VMhC8nQ_cS5PD6@7AQ#VONs zmZfI{j_1g+v;oJnm0D!|Pg0w8TWW#9t^Ujv-Ji=Rl)L4vLobX|ZB7IeL!cL(SYLGKZCLx2tybfcj63R?W% zQ9R^wXM<_a-b$Zw(wZE?M{~p{xrM~B@|dhq^6OD$aWt2DZ~^YL6P1(du61wi zh9rpgL~l%isNSPKQB^2dXH9Q~Srn};E#gO@nhbtu&YK2n`T^&MC3 zgVq^ETShZ>k4jXKjyO6hn(-Rs;ZOB+6qEWu-~^84!bFFws&h&1W&p&=5bWeeUARqg zE#BEFR0XF@Ek4Z2<^^+(|GkkL`4s64ja9j*ot{ ztp$kRn2(oQ=emj!$XyD>u%v;s*40HjM z5R|18*JW-kB6{u+I^WLZ4&>;K6KTL&@EX}Z1iOb|o#|^yLGvSU9O&!y+Hh}j5B8h_ z7u9KaE+J15sLP~YPZM@ScoI;pI)0jI$YlaJ-jw; z>|9173=P|Uj2mLi&Zx)5`h2{!N8;Yv^s)2s=Eh<?S-87=jFU)DmjZ|_etb0N#u_~x%T46*jVeC z4D^fQVzP-*bI~Esfayk-e&0`TaO|4pI_8K0*amQ$I1f7QAzI7IfS2s^NLF6AFnr{$ zOghNHh!u`mDGKyEcIy9HiT$^!We}y^%M_UG#8~Q}F(9%67|AwpIB`>0lg(shcA-#9{ zDa`J*-Q^KF5fWaFo~4V9l!llU=W5NiSLhQ9(N=^dXKT&3&?+>~IH$<6Wt=`hsF8t~ z>BU=dXg-KFp~lo1U#-IrVG*R(e47u;ddB3imu)cY4#Z1VP=^byS7X#J4)xYIjgwXo+R@#31iry(zc8)fb4%G>86Jpi zYC8ycJmu~y!^pOA@=C15mxAGVP(t(a|VW=R>m{j}2f`5}j!mcv)K zb674foWt80t8%uznH7zr<%~u}Je|(6qglF~WvN-J*-n@$2}vqb0?L$xG9{r*v6m_K zGR0Y@n93ACnZ~bwtE$Q{+Fa#Wh=@fDr%;PcDcN0J{ai zh{NK>AP82Vbg_@C|SoIRnoRm{F2qm(@OgFLWAkYOX0K2nXaBD33~<5zJ=u?2$9}x*&F{;JPB0V_(c7B%AJt!< ziQ|gwWw;hcE6o+u?HO@g)Ut9pJ4#`S=>^ckqrHm?=@0Nc(AXW(s3wVcdXKYz4iaXy%J{%9g~>7@0H z=7!+(mYP*bPUgZN!`6I`7}B-;M9Mh1$dp_%#Zab#n{Gv3MkB8MQKbSiOH#U!h?o!; zlCR*RLrP`W7^NGiPBHA%nm-~cRHE#^Fi~%QteE4(c$HoJZD!d|aCMP0>i-l=^Jn}d z8xv8kov()X67jx^23;)|v$xZl15-D?vW(z|SB(z7s~A(k7y3HVpTm@K-V}*yB+a(Y z`HY>ln0vSSh+imDG^=_KW8E*Y*tv-rQ76`4$qmh^-ovyD?MET)WQW>PF``do25kM6 zr7@?A+Gw8_M-{+Lu=^+^*$Tx{dL=4UPn`{|VkxCmhnoQ3nqaCxL!Bhg=%WmaXRuj2iue}j{$tvU*42X0vZ z>+6hbowZVe@>)*+jt>)Q9>mTkKmYvmYkN6W)QOOD<@9Xx5z;p7wj4(b#d?485AgnK zU$rrtOiTWWEoF0_?a|Kke`I;UEQRzW@&B~9oPwqKFF8MCNbKB`8(R3o!u_EOHy?Uq zbo)jfz2unHWrmDsTSRSs*V=aSZ2ND~;&Tvu$gY>r&ImYBvgYPB6blJ(-@nS74;c2sIS{A&G%WF*FhokIjU>p^_~b51|QtUtN31_3q|0WEd`_9T=`kBc?3%KC4{4 zeT7`z>c$}&uB5#(eREcx&m_<3?^=0Ix2Pr<)LN@EjC#=yc|SI+6;k@LdO}A~_Y)J% z;b-A@$MyWCR5|BM`p|6rEM3n}LFZ5dE2~#?ruJ7%Da_zeI?227w-fxD-Q<(T!X8X2 zaxX?G=JJ?qWc4(R=5$9>F{;fPKe2O8NM(Jz55P*1bCztq4an0>VXNelkh=Q#knGx? zbAD#Y&usG}mxR>Sn{3m&#bc#e?QUnuI1g0n1xB_JKZv_PMj=}$qnK?hV`jFAjFs|1 zyCZe9J6RGGFxT!JPsXNNlQTsvo{OO2xw(u&#<@@N+(O38Y_W`$@&Qkzfu}5Oo}JN0 zkdyY@t`An$Z55)Wk7)h&GxPxkjZ1H%;dUjDQ*V5C?J7r4ryVn33^rBZ(BRmt6Kjg- zEJ9XhaVk}e2E6Cw0b>*3N?&PWChFkaTB&oOIVy3HkB%r=0?lT6RCDJ}V%t+@PEc!p z)Aemz;vxGSt%m$~OhzH&z=b-sM`f&(6B$7eGD4PQYeMMkKs9=reAq3Q4+XjjOGN&p zKp(rT*V5r8=$?$#Qt}GvH2~@ED5Kpmqlr|vo=!K?&~Lm)zH|9hPTAb6_I8Q8PMD%C z`D7jEl(%xa9Ud0NowGQ$YuVJ*+>tgs+{oy!(rv6+7t$SM`kbqz+Y==C?;Pk#zN8!t zj3?*U?#i$4xcsuQ;2X-CIQyoILiVJLnb{LER?3O|B3sBWS^OZNe&m(p*B-h2ipM~T z<2xXUwUZqoCdy0sZSEv&m0S{HuaEC6yV{cTvrK+=F+Xxi;OZND*rVBVy?9&)Bsu8v zwJuw0a-NPhQOI_~q!S=aoz_pexW;VFIH&W{jYWGK-}NedV>LPL>ZmQ}Yo<}Pt|Axy z2*U59c=xb)<7`h%qTdUHB|90`Nw-JE_i4OAG0}k?FWuhwvaZIYGZj>6x&Yq$iuXRa zt8wp(3Ge-6Fp0z<3fb0);*M4n<*Bf_sStIK&K3UTg8+q90nZauC@L*+4dN6UTWzF|nKgE%* z!bm}@Y4wg-SN0HsLz!JyQuG~bhG%tJ6I(MpyH;E?JcoaC3u_W~fNX-IZcJygXZUoX zV;-uMS)B{}7W*r0Pf}VPCQ0LA@;EsRZ$YNwn)Gn26r&|E?6DNqq(=aES3*BCJ!&VH zDbPL@%b3RZ;g`M)pFN$@t1F!UI})2FP|KAFblCE3B6{oj{OaiP)2QPYOf-K$b03bg zKhlDS3)vqqw7*%fdpZYL*^JC#eyy(RG;(vax0sFx3mPzGUy261>y)VL^NJ~rtlU#z zUM+uCLh0F*W%t_hB7ClmDvPCyYJXwVL!vV6q+>Tf78G6owpe!c7zLN7%#FLElL>L4 zyEK>>UV_o$5UO?s!3{09Vha1{n%6PBM2EPrik+HBQPVE( z04MIF!O~ZdobLJd0Z-RhU3BxzOH{?wwL69QCaJl=s>d zmyZ6m=RAj!uI3YNoI9T2*=G=duJmCUh3p|2#q6^(W@ew3u~I%5?ifuN?qo?`ML=qO z(B}>l&#&fqenjEo>|Pm#>>e4#?4vSfW_Qb2DIf5pfx=UkHqUB=ny))`^o{BEjOQHM z;LtYJN_p#cdPb76sSy1fh*VwgFZY#_S0h!eJkbGp0_2IVidmIOqC(?mv=Yaon2y5s z%5SN2`XHsyIJt)*Xf=FR^l_>`r^&QB-?CF@PJ}NFNpPj5u@tP|o=!nI+!U42R-Jq< z#$vi9@%qlSGSFr1vY1%gi#wgRQgK~xA!sXPGx=PJ=;|WIR(~d}JZlMU{R$+ea#~lk zla&9+sYI?3rAwp>F)?~8-cq_XEO}+Ac_W!RY*QpIjpe3D?*v!4%M|Wi-f}q>?u^;O zL3*7B)$G%Ed&{L*+?@OdAx;(RU3$Yy5UY@)IJ1e_xR8RVml~I0c$KbsJItn%@+OOx z9#N9Hyl`s}w|IF>xLduYE!=HF((RNT?LE7)-Ib#FY8%c%HwG}bRDRaWt}uPRx&+Kx zGB{OANKj5kEvDNN_sF>}Va}GmAE8=fjNA?J6IdoGxzcgz_Tg?OT>4THOh50^JnY)k zP8X|KtOhtBy*e*~nw=q2ir^}U4`*t=t0f*$`*}%KL_(Ru-qIf{(dUqh`2)HGvu&rv zEEq^nqTP+ZnQkK2iQcSE^kf`{SII@kzUtMYQ=6Wo`*4a!P&d2^)YI8gG@PzuT1{^` z=S^CP8_(x>UCFIcmHJBUVvw?go`e!3ZAE>|`RF=vsshQ)#R}2!q~|E5XNW0VY&ARK z5=(V-UTn49S^8tEBF<8+_prse(NMKMXI8a7*tnL~5I3IVH+d}~n@`bYtq?oEq-y%) zZN$UCRBJwL{cxP!LY~n1u8~p9ZpEO3I);u5?f(&F-OpfSE8dLftmB=*+}}Kp$*-+_ zbXVrD{xBKI&6S@JyWKG*EvSRD8hbyl0w(?bb@)LepdQykJq~pY6<6ALwcOIhgZ^C#`u8;Q zc=ogs`8uskrcSFk?OR%Lf#T9wBPFrYR=g$Gp2YbI<>%)uKMUC%E+aqaGE#aAh5wi` zvWNgW+{s>k?;@wt45qIK^i7dE<@llB)tX=M{66IPeRvYTFTO;6CA-@=S9!wrhDPX- z0f^HeVjILKI^5zi%GP#d?Hy!oY-7H2w(;5raAWt!CXd~*+Zw8B;ous=K=yz#NAf>+13R>29c7GrUp04quDMd>t-mJ{qB))tX=Od32{` zPI*jbQ5B=y&4t-gdb%aJL;-wR8FmI%9a_>so1RMNiD4|#{Nq#bq~AdQ8JSgdCv;{M z($~VuEKaXs0$myES$Y+It+iH@{nY_Ff92tcidO5}=Xubyi$gv2Q&_1nMrRQ}%bz=- zp`I3zD?JPq{|en9#DO2&97LV#o1lTvqE|fQIa^HvRm-YrB=CFTTN4(NrqB=DQ@2B zVdO|3;io{F<)xiwQ~i|c;|k+ruWZ-5meALa`dk=J>1S zO4)La?3p%tg3yxP;$vc8&Q6q_Eza*FS+gOoCwb&qAx$B42j=HFA?FJr+YI@EkmW+i zJ42oka)A&s(2%u4-XMewG~|au-YA4zWO2wO9aFsNfz&y&!6Aw_Jt&VT-jwNU+mGT+ z56L5nH)T54_M>=HCSz?x@#+==_hQ&=SDv`BH`d*th}cyYf(ts188$mKuai3tZFdIi zwtFeMlX4HbZ6Y;!m%EJq_%4 zYGAFw$*l*YT#alTHPnqe{>G>ATkJW7$UEIj6wC(8Zh+Z%T~wn@*QC+LmYesCx*A(f z*I{Hzr|dk~7&BG)sRtj+#xC#wLm{D~y*VW8c2@c-ZB5d<-$=auo%LgVcDxg-C$Cn{ zxcvK%*$$*EGK+6i#jPUmZtUZ>lwa@5_~uLFFnVEXhqSS-GVB zOc<}dR{aGtBVXFT1#h2cvn9wa;SDCW+R!a^Xxz&dPeT zqwY4Q+T&%x_J>>ko|Zqf@pb%Zy>c1+NG%f$G5oGPqLPw=wHsAA*1sE_(Xc!9o-Tuq z^lpV*U5rlSck9v}QD1+w(XQxhVQr5*t2O_}`hvvP3m4PeS=$?xQON2tiWv*xTHD(~ z#!C5MZ4V8PwLMwdYkO|5mRzqj|DNNyk;28<#xl5HFQb?(lrb}#FJq;Az!Pl;p0c!g zmLeAZG~cQoqnHh0s0)=f{X)KHZXxw<@BDe3vq6O$)30@DOt`k7 zKM@<3myJzl(6Dn^X=aI(pF47s zpb^yFi5piXT@49y6Y1{Mz=))19rAndcZ*gmipiprUQc5(Z=m^BN9Vd!7hP^7$_SR> zSCLeU&%Y`^ zFM@wUvvDW+G#7t@`hi9&neQUHYPr38DYkQ`@RO+nMxcZ;`1Q^B1_TIXpf&Q+g|E&twxOYm~hDR%lhHPQswjTPj z{&KnT=_x(3dd+e&TaRuj*`7!`ENcFWf^_Sy{oQ@tZG9zOi$laR;*ZR9OPfD7njML+ zk-zEB&DNRzQYH`M9Qjh2p#n@^19p64giC&`jQgqQ7}ea}kV^C=vVd-Xs`bJdR1jM) zoDMNj{8L7lqx%C9vV`ntfyL1j7-Q>#_ z6m-i)D+$&inTLO<(v2y&^g$ig=!^z?mr_1a#o=zcu27?#bv3VcQ^pvLZXTe|s;-!? zff74|rBq9r2H37N4@6KOEXudEqHJH+sjRoeVcRez(ojFHGTw`Pe!l-RDok zGk50uu5$cFmBY>Vn&Yvz(7D(>z=CaM`Sr)x?h&48b;$L_g?Or^d^h&1kK7x+XUV@C;$pib z=f=3<=H65?RutY&My7i~Yk7f%r*iTQA$tf(ch#@cUV`qxtiIOl=(GXht;m_}Lh=}% zcb8Gfc9l`gc9StPTP9^xAz*)5zh&$!-?_SLE++TCmDroM;XOz zsf?M~_A*w=2Ru(Up0c!g79s`&`-_{GOTtt-lskKSh3x0b-rR~3%FZ*VB|;lU3Z<>M z)B4X*8h$~7Yzzw$xikD^hAl|2O!4csUy_5GYau4ee^Eufk_a>-kxJ_EzY1S194(Kj zyz0RgNR6<(({ny7a&IuPBA_>sgmNYV^=wT~Y_673$lfWVm|Y{Il)X#F%Xj%SGnCUxAGL5J=sCTvtW)9`@+@+%zY$Oxhlr4Pn644*O*d@hTg!=uy0oy-)}O5?luFtOv&KO#$Z}8 z{hA}n=@X8qNRQfnT4j9y$xM6NOyx2q(w8mP`2i4L0@Nj8<;6hL!6?oz`k)dRlsUndb|8j%yH!v zo&jv#{pR>zv=9GQP{-8=&$PK$N&0F;H=}hLRd43Tas8F9;xgA4zYA zyuec3YJe`)etP`OI@4og@?nDcTuoU_6m3n`CIpN99p**R7KgXz&Fb^$*J{lgIjoN5 zP)d)EZ z&W>>`j+QYqJ5t6FJ`P)!?OG3p_F}G#+dn@D1QP=^OO9fzwsF!#-=xt)_Ckw zIOWEs!`#>;l(7l%)*qYJ4QFH1O9*$N#rMGE_!#{<7z>3GAL(2_zIDS{d@m4AZAA5O z1@TdQ%G2s#H}hIoeDTt?HA<$5i#wKUocPcX_JO!o}^l zH=+MrseI&m3Il#@{{2C*A+DqtrYv-ro9~dK($Zfebs=THp=|S5=Z>JS@A8;*G+wDpnwZII+g#ofa#_{Gc15Mjmwh~@$~UYBz{=@EkWqa_A*~SM zas|3gKh?6m>CM)D&>-UaF|KO$R|c8x>jKs!3G~)+ymsrjJh_m1S-Qq|D{8efi<&D6 zpHCgYDiS8>bKAVlKmh?!m5-!NuH*%q(RWV`@i9 zzGTXqO!<>3e=^OV&0C%zu0gfYWTCpXhY=@x8@{~#5!o*@`&M*E$mP`P+&a|1Us#Cfkh2^-!*;~!Oekl`1 zUe<05nke^(=jY*>Ue1!D@-y^znbJ0P1z~di-Qljk6H5Pc&Q$%+y5X$g)c38=4_ei_5<0mZziQ>7?X+xJ^LVwoB@fE@mpO~eO zizaVfTR%HG^g-9wwKno@(n)QrHJA8tlt;6QElIyhNFM6|8Xg^7q$R;ERolJ>JlvZJ z?@AbP>j^__ong0rV5RehevQLQs67Gnb^MzC&85bC5^*fM)MLLxWE8XWF|;}?>*V$57NYfNv)FocQ{l$+Yjb&K zGmBY)5u5ebqtVm+4nco$>2?U>Ji6dbUXIdQv~n4ZBYi??wd*!MRWrbF%w^r7D zLpeH8-bVD3(CIh%LH#urUdV3&p?{ggl_6Ky-pSUoH7RO%L3r(2Mf^?#^W@c#xzvWW z?pE?!Yu$!STI)8Xz1Hn^J5yA|I{$02+w$lUX~DS|{Qc7WVz&$8ZU(t4zN%}CnGrqN zOIhot=cXm?LpfO)6rQ9HNJW3T{7kwMdVHGtWwg=dJJAW*p*y!> zF|Cr`L3SnIA#`uOIOrDyH3sYh55|*Pb6aB4++;bj(B?0YQOw>Tqm=RZpgJ3C0(Yz4 zPn5Tl_!q%2zh_a5o~L~A_LBB28e8_G#%}|U+@3|z%H9?vHm#MfI(=~Kc@FZc^zGGR z(}xD#O6~yL=a&-oOWalH+0dp!Z|AHfW!>gOqtu4QPE<2--5N<#>ht7%6yB_>b-m~& zizPGZr}+ArsaVCVb=9iL0EjdveYK%e33!aC@>>o)eLd^#UzA>S|Dsm)8yJ=P1u+WU zOht?aVi9+70?sZ5g~<8r!$^t9>j%Bmn3=v!I`8iGI^gRzIeZ78`bjO+oP)YRhEh9= zMJ^rXI#Rc-WgA9eWo{H<^|ls1BinCR9o61lVtlyCO^uj{sn+D2Sn}G}ZiQ02QlJD2 zTU;F3w2aj4@5bn|RO^`mjx%?*k8t;Poxd+_KfRf*CcLzvzL!4B{&dYq{g?f) zA)|X$iMkSd1JCVi%VSQ3zLF6D6?(OdQg)RLROq*xS<2pFW;tW{v$t)K*50W#qR+5i{1WV(>N?{2>$?N-9{x|zQYOi%iK{Mpwipno9xMEYu7u?}~;6d%KKgz8&sF7^HN znCqEW%0TiA6U9NO+m)pua7ytg^{@;MEFQOHa=xq?psp!Id-^Z1E8ZzuJD%7GsvF3pPY)D&x*WBWbVCupC#G*z_kZQ^X0_zR%lhv;k~=gdmd{c zxRchIY)k4Fg<~qen6wTT){ORx=D#NB*9Ch!72QsA3yDy$O}Rq)Ll^={ltMZJ(?a?q zgYxH9=8z61Ag!=m8T>CiX(6|xeOIBNw`GGrV~D>WukWuI?TJ6;PV7n9MU_J1Y)TJdMkHcElXH7 ztar2hdH^mx?#fPH40jo)d6o2&irc?@12iWy+=l0c-tt+>KOOUypODrxGYdPnKgGuC ztlK$%lcAmIMP@ecAT8-7Mpx1$h)}Y31k#_0!=;8#v&H)gBC-lnPT#r?xS7Dq)&Um@ zykZ?N5x82QyHM168>49J(sVo!w zY~`0-ZiqdnlU)W)JC`RW`RML6OJ_}gPVV;9w`4{37b=^m(B*FO(w1x+9RH>)F$ZLE zC`t|@=eUj;C%?qQ=$E;R8BYv;hpE5Q_n(cHQZ)P(a&PyPm;MQao6~o)f7GckJDS6Sax>#23STh;og6-cetn@E%#TRq5Yey8DfBoR$M6VpFEu2pK& zTe(Dv8AYo*$L?t$_V+=kBdlRZ{%xRKD|e@o%3~_5+-pGnH6};3#nGPIG2W6`MiYB` z=X;pb`KLeO|KcBuf4YF+E+1eId(#Y~wdUbA1|(Y|c6KdsptiqTMj^XSMlriy#?0(p zGFHk53Aunu?kxV93>(Mpg*QVoZxe{8X=;od-HoqO0Dj;|_E$yW#-b^X%+1TZWjNZ! zk-goZ`J#vQIu+|)ZB)rt6Jup`|MHj!yC3Sw&yZu|7Xz9;wF+XQyjvJ(hF8gj;bz}S z!%cT3C82cOZ>F83zaq`;^9)XZL6xXA{|^rA-~L*8@*950li$iHWWSS9%ziIpX7&de zE9HYcVP}IpktO*PKQ?c5JW)$(&0}0X{~4$3gOV-I?vzo;J|v@<$?#y_P!hWjEYmEgndTtPNJ%7>&y3{pr!r})`xgz+fteAsZ4g4rUoYp6E%jy z>RQ{g*4k1B|IOGT(r2vfJgW3;&)bTkV3%O3YZv8s_rW`1^1T7Jwq|8~S$+^->D`Qp z5n5QSxu4}jA-jr1(c8!fD>}>_iuF_DWG`4J7m{?|mEprTD#8C&wypr`=?q;@sT1Ej zvTR~3_9aaAc9@mN^y?M`1{D6lN#X6V8BIr*03;V}AVL|eH4lGy?& zJ9rZFDTFf?CfqxXrG9h59Wp6=PB_W-VPr!u4Z1}7H!@*#hSNaOzhiN;R&<|fel-6F zV0ZluPH##7iOu`|AIM~k{9~E;U=rB;w}6!Z<9szK--LFTL=(7jFJH2QiPerE;K}K!RJ}5c9eN2mu-GlF)$(smXEnhV}u_fr_T0-gn6BLg&;O9; z&fvMzdA6~!_56LAA+NZ0dbGB(h$EE6wbt(Zn||)zT1T`S-xb(Z-2*4_F8pj%5v{U4 z4Y3zlo&0}#*ZEEDQel=Hj(iE-z>%*oRxyjJH4Eeb{c;f_EAd0sD$6Kj6&b~>Q^w4! zOU6q1pbx!BC25wV8^pK!%Si`gE^RBln6bdr%%lf*TGPC!F*Klym*(()6$sqSsq#JbX2l8 zsd0T9&4v3Aod{VnJ$0n!%%Yu z)J;E1V^Wl-n1giM9K@&jXLM+O4pK55J1jlr<{^_Ea&wW%4!QZrWQW|GMc4Kc%Vf{ROaek}x zcHza0m0>M`d{74SnGcy+%IL6c$wR&*th~vTKbi6;)BNpBZ}=)IK)OFC99!$teH1Qr zi1w-@C}?og)mHVX}|8X>~uT z;g~(7DjH{>l~KqZmN7H?jEt3XqC>gZIuu!wenPlAWeecNgqih8-Y7nnsAhB&hBWJt zwws)S>E?dw&xY~)eoKq3qr43H2AW&)TP^TaxK7@{8<3b`xxn<4Ss@HB)xK8T_-;)~ zr_ARne7i5|yFt-S;%i?WTv+Ilf~bcFT5-Gk>^Ga7V-J!7db=$ee1hrRxG?rE5HGT)@WFBZX zpC1@+B%?IaTWT(VN*3}{FWIVQwlTDr$frzsm1+16jeo%^9%ybNbce>YL1mn?UEm%~ z=M+i0DX#UN{LAU?8-%A)KKhHBo8cq-L_0GmCwCU9eR=nOxq4NR$Fz(mPlbM5FiPAw zrP>@8n@>Ws7W_o9*N?OYzBlQlv#2!}<5JZ@)w;Zt z$7C>1C(7>m|MIfh%(n0wv~-Mpidd<)aebH$NtQ{ezf`Gr zVy8Y7Sl7#Y+OYyr)N3(2cpk0 zx_K65IBX9ou)xNN(KA)1PWR+HTC4?hR1%hUc@~zWr{`LrA^!*j4{D?hLtM$F=bOb9 zn(n5n|CpS|-3`ESHT{Ka_+HfE>;aWVF+-2G{#;fyo?!p9?CLO#z=1iu1B?K8C;&zP zFi<-mMgTk<03!e%34jp*j|RX9fX@ZM2mlJsF*SnzHMaMLNj`6GEFr}vV7&0?Lyp-y zlO*wi*JctRUNHNNOcBS8Q{+ui#tTuHqUP%voHNB3`nMC>Jy7Y4wr?3cx?xitlxeWe|r4LsMwkbx(CE2cfC zNHg0gnl)=dt+@^UtL5=+F$SC4sdg}9sgmGw_C;fuA_p&CT`HN5ht>k_tIXsXEP*F@ z%N4Sm@mlXe){qiV0j?En;-zxdg z$9?6Jj$`Aa#A6aR-4XZmccQkpoG+Gh@cSlN-28qE^6qx!-Pl(Nm^+trB>F$+B874; z=`!+rz3()y8}3w!`1}_PSJGQP(H-h%mLj5NCOavY%VRP^x3@xV>P(n%L|=v3UVWzn z+!>GI3#VMKob-iLcxw)}GbXB2t+@;Ciepzy;*c>#95CHYj97B6CYSq)B|eA42i1#r zN^udNJu%@U13tEP*OnDFd*NVgI6tGR=e$3A|C~7Sc(IHCFIk*eD9^6BTxN}@U0-~^{tq>mQ=yxq^Oqs|Twr6bZ%v#$}fh-%wQY@(fhtLAF9C3vz@ zl8sW@Re|gkPy=-5iq254f;u~E%-EGw3%-YWC;wC?G%I(;vU!k7<6Cg_N58)O9;>tUXNhk9`*>T0(c@Nkw(GqMCFchu5{vZEVJm6Qek| zb+x$g`RAiZrxk{5UDSYf*F6qDM0ixl-lwZuwbi(ZAq8pWBL+v9w#k6P^8*9_5 zcTnEjXq!&+h)N;tl%=^XznMzIm5F_O&OX#rv=7jwJIj~!=SMZnyd3G8ui%H8daNWJ zZmcDJ>2ad7r;Uw%)8hqDj;`OhN9E`~Lb~DAR%>;3 zwMiZQsPP~;^{%_o?q%((LF?Rx&fKnOSz;c^3&VJ zGoP5OF-~)U6?Z$zGZ^L3M+pY@ct0=sOivJE*{FPE)uQ*js3;w)<$r;$XL zvxh2YqqVwi6_z#Qp^5%Xb-hg6=ji;=mic4KQS_4DN^&w1h8NGQ+aHQ5n-Xgj?UC+b z-x6ib@O`9a@k5CxPx$8lt5ai9rzDjbr8Lxn}|S z$n`=6-LhKdRN~7JN9(70TW80pGIVTr*GNC_-cTKLUm%qa9bN2;yX@1R$;oV6B%h=D zAi_t>>5)oQMdOxEF>>_tHJ0tMZ{~7fsdncx+3btf9=}}jx}IXjqTomSnSN^~TD#+N zC2u`1tl+l14D{<9g8CQEXEDhjgVt)o*OBm#|K9&lRJw`e8vUcFgxYJi_x~&!pl>C= zN;*>zeay;SpQ)Ov!nqSwol9zM?C6CrQ`!h1~5Mh65%xoTRPK=k3|oi!8IZ z8+K`V@9%CJ!lDRAraINpAFxGSni%{GTA1JabY+ZNwC%30Zt82xi^tk;_x2(){W3ZQ zA0(py+=`0&eLnoscGFC<`CxFL(>#a*rR|n%U*h8y-WF7;gDjO?M-6T~p_U43y8IzV z)zguJ6w?fbqQ;MKiyP%RVHKDe8doIKGcg*wfX+?OBc#~yKGS~KZPw6OAxaYiW(>(= z2*YO8@jld=Rw%1kyz~DB=V2%B;lZ{CRjMA%**S499)n(<$MND;rA;38vzS*DI{duG z2!LV$i~uMFzzBeH0E_^r1i%OY7GK?D$q0b102pz5Y!5$ViZXUMAUO$%!lDLKm z5kadVs1X3&0WbofCjdqO^aj8PfN23R0$_Roi~vBrbP^i@Pz!(&0DS>40)S-?7sLnv z&NMo}h}(InRLPOpKD`pylM*$T$eAIi5%3xefDr(knRCpIfccGLF7KYBJjTg2G_y=z za?EFiphm!Jb^we3m=gda0Okh32!MG3FaltH0E__GC;&zPEC_%R01E?PM5qK3$|phM z#L2sq1bN9NVdD_g2zaqt?y|!OfK3Bn1i)qiFalsW07d|?e(rb}5#&(}ULNuGq`2nf z**pX_0$wb+J6=WrY!Lt>02T+ph;PM>r&NFn@1syiPb!Qj;G*yXPp6o|Kmy^KC;=BjCje5Eq*f0NV$^2$=6J=JM`2%A+UcCC7Y+5Yz~G?HB+f z0G0;82-c^Ui@Cgej`En}xKr?N1Ppc#fDwoJP_pDgEeoEEAk;1aFoI~WSG02V420(x z>>B(V0fXHFU<3@lEe3M+420(xa2&+tsu3{QBLGGK>=^(f0QL%i5deD!zzBeS0$>Ed zz5y@-V7~wu0kD4njIjCHF^tptiyIyDNrMFRX^?21lu(|OIG&UsaYL)2nMClYBF`oL z0Ib%gj6jA117HNeK>;uV;NSol0dPnFi~u+^07d{D762mv4iA7407nGC2!NLbz=+md z)#b%9<%J~niIS|Il$4&7e4dnKo|H^JTO^G~C5Io5B|$ESj>Kv?WCW5O6#ydujt+nk z0LKKt2!NLdzzBd>1i%P@V*_9Wz;OXE0^s-n7yw705$~K3Y4C3Z z49*RJ5dh}}zzBdw0E_@=2EYh_^8;W6nKFMPgis7#2=Qj?Sk*;bbMh35ub}2Cn^aN$|phMcv6C})FcuN3`0C;NvjG&o4BLNkqmr9X&Qqg!)5#(fj8&=B*BVc=30E_^*JOD-jToC{x z0Im#x5df1i%P@cLl%*fNKL_1kHH%0&-sAy*LV)i{iTA-v}679{?i& z-W>oVsBEtkM|t-gEdZ2>R>;PwC* zL2LNFB$fEdCj(#vz^4LW1i%9UFaqGg02l%A=>Ql3 z@R;6#F2X0Y#^vuxb~lbAv)pcD^Hsb*JnYY@v>wBo{Nba$^h7b+z`R3SPWSPa zO=Q{6TQ-&D0B_k$mV>-ySe8S)Wsxk0dCTUq9N{gAEJu3F7P1`eEsJG&xwq71Io4aW zYnUD{OY4kDe$W1FRR|TauMjF+PpViwsp9ga3d)lzqFjx62&**#Bgo~?2EYh_hXY^) zz#{=L0^rdA7y2!O8# zzzBdR0$>EdHv(Y9I`gPME1#4EpGpbuNlEER$>&K)=1IxqNlD{L$>B*!kjt_sv09cH zfn?tdfDr)S3V;y+-wuEg44k7IBd@~yC=@am#dm^#BVh1U0E_^5Isit{HJ&Gq^6ojx zW2~>l*LABj&-}X~s1fk`UI2^$_KgVjK83Eg01i%P@Uk1Pk zfL{f`2!Q_+03!f?9RMQ$eiHyA!aQ10dZ`qdCl!q+6@e!eFemnJv6|RM5X0{RUm{_N2Jx%>O3@H3DA$3V;y+{|FUn*G@rI$*Pc~V^c`j3KpQ~`5h zbA!Z*Z3HoN2EYgaZk0G6MgUX;U<5#S0E__W34jp*T-tCvi~!&QhXafN;9R={i~yJs z03*~tw&sD2uPV_J*r!RNdQw7qQsQ}1f_YNxa_Q&ryNlNd;_VB75da)^cRq{&7zlt7 z03057K8yh16uAS80N@Fh?UDc( z0kCBNi~ykhxFALVY#jh2P|EgI1rkFarU+$Q)p0tOtZbsUWV*e(D@(2ZXvj`Hp~ z%43||pl)1V0&|_4+=lx6Njsxp&m4pULb>xr>zwxH@QyzwPdjkoITOzri4gP~MD9RxIym3k^^V!b=F7Y6 zF}Une0va2^%eEAo&pCguxbU0>%RU-nkwnW-F{7ooI_I7KcK*R8*2w(7Z(;mCaL@A1 zmYp2p6};?1@oTsM3okf)uVqwm7oCDHyAs>dqnz`s2R1&|*ff6k`lFX!XQ!HwR`9ZS z$Y0(0`_=BBJ#pEs!TEu2o^|RnW_XUNvT)h;5|V>-O6&9A+~Uo9ab8^XJeH9)6#@%hwu@qV)CK93cll~_Wa)&-G^azJ)f7_v77rL`UwK46` z$AoU?Q0*H#^wUD8IaHg$4!uw4-^mGN(DtfBKP2?W4%J4ZLvI%PHHT`0%AsmOOF!XI zZ5BE7Dxo(!R9iL7{& zzvWPkbq>8-=w}?N@yemM2)&)3IC(!UgZDJ=Hw(uLWx2yFH#^G>X1T>#R-0vwv#c`9 ztta4kyI z-f=?wqIQat&pK+$IC%ObZR9QTIG^6se0g#AkEno)?_$4DXD_ zGwXV2;$ftxkjnJ;?(N7W=RUiK*I~9?%+BP87Y5G8;EszD?u00mE6q#MU{X3V4&Syu zZ*Dr*wxofQa%nJ~Nl$Wx8$QN_>o4aMQg`|*JWP5Otirv!f}IDad~4130qY)4UzwAm zWqVPo+kM#BJ?At7ylk&Nb-QN@9b5x;!luiKgo`@whf(ta=;Tpq2Ea`M-XP#}0Lkb1 zaSwLrZi42M)T-Q`!P(tG{^kit!hXQv80jnd;k!2+orWl+N7?T&{3Z-><@DA3M7eiB zwt_Epv=)soB_qds5z~K;=nQ-|--tNFtAr+xAy4x)fOe#J$@q@=PcOusI$4O+`X3RW z#_1Fq-{2uJNlF!{KKVMeuvbB*EAU;``(k!9zHtXW%egl2lxj?O#ZIj^8Kd;Mh76;Z z4`WWuyp&VXJP9=ppsJ)t065Cb$L{|Pn`D)2)xK(S5rhujH+Z)L?pyGidl`aHu9g~y zg7H*2!+|SoXoc#pG&7Pn%u7$_Dwp_p(AA_OZFtMIvb@n-u9sznS$Ovfox=QIqg2l& zqtf#Y=}Iq<+26f(Qx33qA~lY@A;qEW@UC?3gN(Iq&R26jJm$-F_sw>1oyXB((f1a0 zbW=>=dquzB+R^F<&I6EnwnZ&R)nn{Ni_MPg-IS8|qw z;%qn{srHZkK5Lm?(L)K<^ez1Km(~vEMB~(ac>k(aT1>o7&Lpbs6wZMUMSC6_=vs87 zf2&V*qe$JLO7lJSD0wTqyDQc5(2{993@~fiYIrSuh9B-z;-rVMlFJ~j{1tJGPq*sG zEefuY@O0FB$=>GWxDKz9i+ct13(9U3cHTncqRMIro)yC3peKcSGmQ-tKW20}F~d6$S{eSjDCuNA3N^MM=Kbzq}E;jnyhVvsZY z9lnqYDViEAyQfEm1dRleC+z2`98Ze2zB=U9!_me+a_8rD@8klqf|g&4G!j*-RxGjI z-ry0glrf1$an>m1ocb$kyYmIB2UuPIUx`jWE%g7L+!~A`T7PX|qI?=Pr}K_ZI(q}GG{<>qtmX$+_ilidCPpuf)kcBU z&o;nHv#FQHYC%`vwe1#!nWFw_4)zjxRRgc)26$=a=6P+PQZDEYJRaTv56$ylghx-{ z(Ytu+G-+1&B0PEnk7IH?a;;-S@=pulZp($sS3b=>UnK77fyeJRz(X_D7vV7@@Ytz7 zbslJ5`yxDQfye3%@X##yMR@cD9?xumhvv*L!lOU%7#*29Et+A!2#|fDr)80$>CHYj-Y)5df@m zIKT*i-2z|)!0rJsA}qZtO20g<$o$f~qVcFA2rJdx+C%qRjP8Yo#9R?gAGM}+|K-n5g)T9KFDCyH8d9{n0Yxg2YD?q!aCoEMV5 zL`>56>e(JjVh|%-M3rFvW6bTn zp+h)Ft(d@Cb)mb?R}EDYdO9c71N(T8|MuV7NpFtF1DOh@RTnmn7VYoSe59)Y_Q(RC z96k`P{vD@DZ}Uh5yET5nRy4D-Dex8U4T(LGc~JGpeXMm~{I!~q&_|i?zznQP&;$vVwvMoPKW1q3p?H0P_@qFTb3jA zao3UWd?)S0s$>!U6rZrJ75!(^T6Och=#sy-CpmC?38zJ zeYBk11thpPq(mo9X{}_acN_CGd))ysLr8~+<;h(g`!>vuZQ(IvNvxdKVD4s?|9k0=SX!*vJb?j zTKnBB1lFr$mLe>>0w9R>cSm8;cZBs3_r!%;AhFev?#lR_$pwF>3Z88u`X#v}Ps2R9 z2RmQC%&mU#^~;t ze^Bsm1i--oFaqF^02l#qXaI}=I4l500I=uggfQa1cvG^Czd!7re3X=DiYi_h(T3fd zXo@L_ zm|R8G$9;3Vr+e0r&TQEe0uwHonLt>>p2)t4>?Ujh1eEncHxZ@h#-P5g0YO|NxCFsn zQ9%JkToJcda7QJIzV7=j`Z|8U|Ea3mw`2&6GwZK#CP50WIW05P6%$I@7yUmPNo2R7zbu6|3kU0za_+ z!~FXEvA|qu+|74-4?j^*s@~VGq>wQuLsvzoelFP0+&x?L>9xj(p{=!p#g?LDK&7cK z>#S`=o-$q+3d#t4cPLwOIHl5!@fx$v>#pY!7n5Jyv_kDP?=g8O)Z;nUEJZZm{siJy!Zi{z;6b)Mp%_XGFAb z^*^V;K(rT@oz|d)>C1C9y%(Ob1jQ9d9|7uxdu9fYKztO$_>ZxjPMaL+ot9rT;;JLP zPt4}@tLpk=g1sKNI9jmM*-S%ukgHnbqpm!s>z*(~Mdev0r<9G$8O+$KL3!jwdA?=k z(TBe@MdR4i?bziO4*25O695jYP?@%9) z2nPKqVe^g@#?L{gaXqX8x>pH+;HQ^1i8@^K3i-y>l1eJdx&vSCj039;al{74P9 z%;~NkrnY0Vp_+*%#zbMx{@Cfjth5P@TgO!kEcsiWcn}w#Oi=B z0K}SrFaX5q0bu}$GXlbZPlkbMV5J+FL})5bw8G$YVN0fMc=;l=ot-I~lv!;~Wk=X* zjW3(bB%h*y*$LDQZH~LG)aJNTN^R~WIUU)_at5=f$(c|18^X`T2MXIIPN=}b9;*;R5TW&@09}Z_#8#@ z?`gcC62R*b+Anq2%ZMO3_v1=lg~He24sKt4L=dKXiIzu_GGj z8|8w0%a($zzp_1B-)Ut_KHJKcx~vix!$FDJL>O!7Eo+S*xO%eo#3h-krx(g8WzUyW z&R!sAFxwz!LLsOwOcGR=KBl%O`oCJ^M}@k7iQ>iC%jJ|ZuJ9Dim&qB-UMy!qA)xtl zqp1%s0ygLQKeVR{B$aAU1|a$u280KkiB~=KGlP!-Af6Qv27tIIAPfL;aX=VQpW0>= zZ+mU#aVjypMDbmlQQyY!QEU97P#2rTn>f2gPAPkX9Nu4)Gnl0nJ|;O?|Xzx;8{lN*l5<8QFY-yXMB` zxQpMCh-0qWO8o`ewZ&sAHIkm+LO12sHy5yyev#ngofW-)%=Mmj@qgpLf&JrFL5Ast z8N0wU(KZ==2$(WHET@!xMNT>Us+_^>YjP$Of-?Tf%BYWG84G=mYAF2+b+wH#%lv+a zA9sc**q;EWj9H~JghEihUt9U~Q7qq~tI+mxnCd5Je-n zR6?vKW&nuG0>S_gmj{FaAku&^0K^pmVE~9L1Hu3h^?)z{L?a*!0C8177yx2jKo~%o zWrKSiE&~nn>*j$y_1EOuaD6jiy#D>L%X<7f046JchVlNDCHdGZOUc&}nQq%|;`d%v z+-;$EGm!LluKz$giNA=%dxW3Xc@gf~r-&`-R&MolbvDwDM(PE*wW|$g}>u zEku0wpg8P7an6I{nFqzK&@KJ;kW8%cK4MDfoM6_o`LtRz0L0Y+VE~Bd1cU(}V!EoJ z1>y=AKEHoRreGlf*Th?_J4gCV6yTz$=JVE)m1ph2z_qPX})(R+}R>0&hgwA!ap6EX608zVMi{+y57`=bfob-}$D$Ca*qOkNJ(mD6cMLx{R!0 zgX@y?{}$hbrPERCGJ2w_EMTMay4aUN1;b=y`U2*}L`XJQHR45cKE|#CSXHY}4mkY}Pw2BNg{zw6{ zjaGOI-4vXbs2h8)OJOO#jjbo)G7Q!l-Cj;UD>wMGwNQ%nuQRV-M z6cZLiMw1SH^6?^$)b3p$)4YzTu5M;iH~QjIHT$*fRV;hbIZq)D!)^@+v+^M%(>>HP zITkv zk$xiDb!oJKdCN4mBfOeCj1O?(8Lqo;y(9TE<)QBMe#4ER-TMv7n`|Oz1GGDfCv!-B zXH|n$p(Db3oYt_;7M?|e?rR!}U^kNQUCuSxz;JwpC0AI1EMw8_r* z^USeny%Uyl&gBL?+w9iv0{m(rD=E{aO8UR#!FH|xLz!te7>6~^gQDe^hdmW8UuZgo zc3s@J^9mAf-z?C)9#rc=oGI7O&*v(=Hvh&X85!wI|Ec^u*_#I^rLDPwY;RsZB?ri7 zJh~k5)Q2y2R8oz3@!;K)N@7*ueW%H{^>z!ITCLG*ZOP58KSx7i?)e2drR?)^2D1m{ zOvsB#XBW(OAAZuQbqU$J{_N^;Dsx=fOZFb8=gW9C>4wT^r7MlePj}n)3N6fO1OGy8 z;NPU>fvLCgw{6^i#Lua^qh%{vd|E|*^cTDLRK$J13Z(3LQ1X*Mt)g}lH%eqDlOVUI zHrDSjdA2|Qmep%5a9M$0*NXRgf#Io^O)Vl}IXKxX@BdyS-===k4%GpgbDKJgyAR=f*n+F_5~6U9S?)`RBl>7C5{ns=v!CA}VMZ>x8w zrpB}Mwik~lv8?nQf^9vCC0?dgFzU#}K-ZDM1C2FfxF-AghRtG|RF$+EAdiSsTD^VZ z-i}1WS0B?Jh|_J)Bh*+ICs#U2l^&E1a@JPCV)t5;#vrZlPT(>P5WP2$sI|e%X@mOk z)#%%xm9R_oOIRyhHy>#aKRu(6|IpypJat5>S$(c|HNcI^?eK?Y6if@EDW`HmN#G3-b01$5u2m?U8B_IrN zGN3XtBjWACb7>?TAzh7=uHlEXY02sOIKzLOsgJY#$JzQ=s}Iv}7Ki3dGMhs(27tIV zAPnGUjU76<2SdL8x)tFI1l}6L8vudZ0>S_gw+Dm)Al?=b27q{bKo|hx9RXng7X|H4 z=aDIexc*obP_*MkKP^R3CaUqG-PZw%Uc3k?h%!+MxwQ6kC|D+v@U8(P3KuGN7{&Br z<5elW)Lh;@H@73b!dzZ$tP2SGVI{g8dQ3EF2f@}>&pWu`QZgN(?T*vSh{QDO<2)qu zy;X5~37?mSY1hii@S01L9SC@OGK1fXk{Z7~%$ZVxmF>&u^j15yb6VRq;LSTJnaPp? zAl?-a27q{XKo|hxJpo|=i1!AB2mC^T&Xw*%xq2y=XP))mJ1sFD8NZFw#^agMS{&Roh#P`%mNRQ1U|Pa+Z;FUSjFI$i9b? zm&mm_fqUBt6_I*KUJQDC*EsnvTp7U6X4}WuP^pD@7Kq8RP0WYY-Hk?7>m#Yhh*`gA zynwiDl8%$-1Eg2-)5kUjjH=yZv#^G>VKH-c+teAwZA}ypyL`p|l;vDDH&})n8=!)~ ziFA53KYB;&x!!G>+lrEAh8RwtCl^KF!nor3V2L|2W~Y~_ zUq@ZfnAYIJ308FDlzZzKFo&<(m&hEl>}2}X+gVCC63W?n{KsgAaXRJFyGqnqe>coX zxr+|ClwMEZluP35;|6_jHyj^R&VH3NH6!?I2r)9goo;{~BI*XZtQ$x>EXwe8D_rgd z`5dt^B<+^ZNd1$_N3VmwCV0BR9J5t6X84a+>0_ooHm6lNotsmxpP}HsClvo^&;JVl zZiUv9^Y8V9%K4`c_$L7h{4+T4k6Ct$f4Atv@ozilqXHk_LdbuMkF)**KE6rWF+S=5 zt*e0lQ9+)p0tykg&vR~>{8xDX0W$IAJbx>pa-Qo0o~xw7-;^eCa%ElIhGh z;`DBC$$$l4*c*`d7LYRn@}mXhKtO)1fSfhU(|l0KK}T~=Kz_b}oEMN^EFgWBD9qg< zLyIXNnO(Tk(9M;ug*zu478y-u@Do+LItyQ2#XDLm(J3*n9Ok^o7J~imi#hM(>n~ck zGY8VNZ4&l=BWLdrXZ?6izSeo8u@TqatJJYS1$@drp&p`ICr1r=$kD>t@xty}_xPvd zIXCidV}DPrXKZ#=GfO~ z?JM3Y6X`w9BS={*d+A5Kj~BIq&wbv<%Ui+ce(&Rj&iQaHP=LJ~4sQw$o4IrT0>&u~PNv0M9xnP9F## zbNG;$5ACjC3(on9%>~b%WBeX@=wYrkNfwaY1~pwN(~rv4-Vp;pYzYVhKzuJC3<$lE zg8SY`0Yh&@`0t1C27q`hAPfNUgMcsq#18|)01*EZ5C(wwQ9u}Qdz{p0mzh%VnNUm* zDo7zeKMqk1fYwg}!T=CI4G053{45|0kl{!{-K72EOrZ+&e;xuGKRfz3;^+X zKo|hxZvkNdh`$Gf0U-W2APo3{-{hila4uE3hmFcWV58FZQx2sXIBN6j?<4c9-qeUP zDZj=-WH!U{8|=bm0~ZO4ahAvrGBs)JsBg@b;d`;ZF;<4}o%D?vF(lSHSAP16Fj$t- z-_U+~XU1+mJ=FTvOqwmKZpqwjyHm9K#fs|=UY63I5jn%2$YJmF7lQwx{Fl;S3C6T2 zSP5kR1g{3DuyW+$I=o>4pgE;DhCwY2*mxoBO*0siLdHv`XZgB1? z*ScywL$z)q!61FLyI_K3pq5oQxPO2YoXTzx8l1*eZF%c`9r7Ba~izit5Dkll~9ml*NIUMct z$X!8Vfpfw;%g(dygn(^JSQ|ejB6l`^T{^lofw}%xi9_0BSiq5Zp2>EpUHcvya!oD$ z9>gN3y*Q^fXIYOUJ5PIghV9Jyr9LH4CM zsi{r6K#c>A)#*~|0SjY+?^Q`KCzN~??!PQ0qZG8a6BD>;%PqgdT_B{kGes&YHXb~Rc^ zKA3HhkGW2g@?J6sQCJ_S+FlA5HdIqQ|A`{W_I(2M6SIB$K5}eEJfZetrfg@o#z@!^ zVY=S6J6*<4 z-{_&(zIOxa=OjNjg3PCq*2KvWyfibq&1@I2E>TJs;6<0keTD$Z9^$s^jda-_$#;+6 z$(hd5c8H&RlIZTv8;eeuaA!Z`XhyXD z`Y_IR+a4ZQ2lR2%%hNliS5#5zPpzW#_SXC03o}vbv?+a}o3&`U!{2o3MM2dL?!9EY?Had}n%R>ZUi-yHYosX^q82 zfz-Z(r;&HGb>)3Kx{GVRjfJpsq~xk)o@#2O>}o9K%4UX)Hk~Ex3F(gZJJj(qoa;r& zVNZyJQYw~>%ehc;%JrErkzGsF!l%p3= zUUaM{EU&B^>0TtaWT?(_gZBGVTt~XM@=+M<#%9oJZRk@!HCfVmh|1V> z`t`;>AToK`{*>P|K_U%V)aYx?*`&XYC5aUOrdab9$zh;j`R_ zyMloE#!{{K-NkrK%c5T0?3SKfhL#JW#$FPd#&_TsW0)*l={NY%fYv_1e9L1sil^V! zN4lHISjlG6>2m)e|KZi?p8D*st~iKT>jyNEw zX7xJ(UDh0-jV#(vOWv-f6rO^Cm#FgTew0`#XzV&DYK*H?zC25c*-ZS0=J@GSeXz;O zh*Z=4p}{mE-4(RU(w_a*bQk-QwZH~cX$UeIQH)RLd90?R7RnXIF|NF1fP=Kd^kn(? zkshEFswoF@Nx%g*ezJ#Bco9ht0uBr@3sYA#03+F{6+#?Li1mMO??(3{YU2<*MmESN z;l6FI=yGO8Hhm8D_l++1@oRnQDgsJr9AYbIZF;aCIhaN0sMa^U`eBXTc}8}q?J$17 z%X#_~V%sbT`;+5cdMKzRMOmt*BPxuOrLNRT57(#6CSPsh;~4D4hX8X3(2^gFIJ%w` zowFl)2C5PRgZc$cQA_F9sT20M96{PtJ=2WEQAa6#1Z;97IHaQ$&fZ0&cAbA^A2#2p z@gf6+9NRf=YL@Fs#_7>Q_Hu0N7)P5*ckOPjTQ}YY;nCYX>B$bWANNIOV(IT#fkiPS zi@8zF?m~)CNUv(?h4wK&Y8*?d{cQ1Th2gT{<3KVHaQl=>*w`8NBH=sYsGr|SZu(S( z@#&84TP%NB??QG*K|L#NM&}d%60S(#?qZmpaPFQn`f!|^ezm9QTJnwfqDvf4RJdu` zp{CgPC-N1l65L|p zERX1$yOAm8g||c9u8pz$97}!>>yRJMLp@i?42#g}H5XMoH}yqvm!nVtQ2El6m6q%4 zF<9^M?ZUknrRY59B9^&awTTo1-cfC$vM})B38ZAGC?!GiM(;+D#n(Ma4=GoAvdu^r zkxPqnwi^3#0^P-w-H@o?qnyUK_y-f;?Gdx(0H^g@-^3`hOH|+eK=qeeDcH!Sfoaa) zKf^ zj4f?!mea3ZPVHV4MJuDpGU^Ch4}~>E`gF-A6#l8)%}|t5It`)CSdtOkCzw4$#cF@Of_0bpr%-F0Hva>Ji8|(H%IJj;pRnZz+$%R^% z;@oBgFoq0__EeI$Q9`>nI(?hR_i7`{MKHKlR)F^t!0r=D-(dmRfG;ZV@*m27`X2vb zd3vA!sLAL3`bh5LHyb8{vgDa7yImaV90Bt}wH4K2(Qd~BZD7)2h)lJ{QLq`zLe<~x z==3(adsTdT4(TWfR&bov-T=u=Z1y_(5v3(@&;z$JSJJ3vM%?dUxHaHTWV+Q;vbp1l zZg-LL+7X>WUg<*>U)9LU3sF%MHcKMhil7yuI31(ys}#vwt zK8|i7{Ct%)c?+py>@8^n6|VqykI(Qs-~6Rhqg6c4z#}=6pYHNj;kX0jP0+~0G3ljH zCy?HfI5|n>h?Bi>Y*ej8!wgY7Rg<$I#xz=~R}x;Yu{&1AAMio_D5iR;nC*9gtCy{0 z$g#h~1!_+xiMXkDmYQYrWTo6TV{#%ilCDOEdOL?NUhVc|=CDFakbdB8Qzyzg|o(Cr^U+!cLV-}qoBPR3R^YKc~<=9H^XvZ){p8Gmki<>4n zjMy_o8%d5Np>^yzpL?5ra#@Tc_JT)?+GNX#PREmrPI-LC_5TQp@&5u$Rt`k3Xsks* zMmET2Mm)NgFO3OQH`BiK9K5BfaOvq-6+;K62X^wA>}^ZZbUE!u26Fp^+7Eqcw8+X; zT0hyIfrDBZkWV=|nu7bY_=(um8n;Rj{ZF)`$!e*b97905kWR-)uDx)UcGb-+!Z|(? z?%1t4sdFcsr3rR}?a6(s$Jr4Yp~oq#Y|w?|ed#XCoqYe>v={w5v`f)mr1^w>zUwX1 z#gLua^M`(a{w^-B>em?xqQ%Bjc8i`!q$O^ZMSb?OOHs6w`8A~tq~{_D^!aA<9a67v z*z>@iIE?)t{gfrI6Kxs6=2qS7PhglCYV5^t4;{aT^`W8k8AfKB?M3L~!rjhC>E*q* zW~=Ap-y1!;iJYxl4~N|ur$n>Y^aA`A?YT=7Et?U(j^oSOrkQy%u1wZ+i*M(n_b|CG z#NWr34=nx>iobxx0{c4LQS)ENpEOx13YHi|3JSE5dwW0o*wc5;?Zu!oSpe)*G3>`@4^6Le;uQrpLQ)_;a zPC0oAY9S5R6Y3x8AE@=u9LU!JH57Y7=>q7eLrAxfPmYiE57q|9rpF%PQt+9awJc;8 zo4f1-L$#r?*=}edh-Ua^_(>OvCZkGUZGepR=NF-+-xn{2Yr|uM6jF)?$@~CZ`YiE^ z8Cm*23NfoTiwj4ThadClYD>jJ1G7NiRg{Z}(m#9j#Mkj!pa0 zXYj!ekvZu%73TtdrI#qNQ0u3I>YrPit8wlOc}EbO$8bA-JNchY{%3~=Pkbw}wzIpG z#$vOn8T01V=8bj5TzY0o4S8mo$#f=~wvzwo@M!NlP@^e?!IRO@y})sF$FwEi2=C!M zKJD3Q+y@%`!|~a>l?;~6Tk}G%cNLl+9cMhckE`kJXhowtPRA@I4^vyr z*2N1XUBS{TAVIB21s+My#Gf7I?%YWIWyCCDYfw1X2G!@wW_UHO%`@vOW^vQkNpx}b zvw`x#-sSeR@IQ$13<8K!yya+u_z!60I_307yx2%Ko|gGr+_fv$9`{#vd!KSWj0=Tex@vi z%_Yi>-&~@6gv}){^T!HDS~yxlwnU%hY-b$Ju7ayj`j)r5H?%dMOX{?}w)tFgC76G4 zKBu{%>?pgKY;2F#Y!SQ?*`KN=8Te1N zKG2iUgXni_>w~b1WRA$K4`)Fk8&{N4x>oQq2cIi=HwQmcFdeSKTqJn8gD(TlHOXub zAziv{W(JiZm>He{{C_tyTp%jJ%%IHWW(MUh4Q7T*^ieP~u*PCiX)b3pRT1e`)I-9I zcC>XSH9@xGCQB*BGpVOgr<@TL7M+D@<<@6Jr^d50qT7pyVM2o83=`01-_NDU^%vCV zO7nuF@$>bBN<~^IfeC)3tJG zUQ0KxM1MkM<{cA_b!s5#I;u#zxmJiwH#aOAyma&K6`y@F-CROn(9N}oBi+3F(d$e% zmv=!ozn;<*bnB$0TYKHyYtt>=yroTxC=ybPzmN1~r;U5v+<$r9+<&!nbJXWt)9-$i zt&y-nk}{IE>El!N>RhpowxD&umq$6N$CTEYikoWTZeNI0YN_YlE3PL%HMtJvqwnTw zcYm!`+fTbm@Vg#AXMBu}f;T?$raR=k@sao3_2G<5F>R~t=YrkU#Q%DUMm z0G+(`Xz30Op>+?<9QHE+(1iru6oAQoJ_V5eJ%|?Ta_8`g<;C+2{l9-U%JH0>(p2rg zQ`sIydmakff7R*sb82(O_SXh4ZZkq7Nv~EBLs`=2Qj#UEGqN{GRDx^7L6!PTswLvJ z)c?7o7uM!F^?#mkK8*XFOmitvb^4a{qs4a_Uf2AT?&HkC+_Jew2Dugx#a3Ff*v z!Htw^E~T2=niI6zte+FKnrn@%M5X=(wFPW42;A&+jHEnF4eajSX~%7IZqTi{K@^Qo z>)mf!_hAHyqn}LMb558Wtc~jr2Y9bi<85J{P&R#b)b%HYuE6FA=Xp-Ih5;=m9bt<} zT~1VVUqkCLkhrVMM8zc2O(R@q+x}WdYsu-(FsIl*+WBnvZin>wr&5wIr`Vq^)+CEn z$xC6oV@P3UQQrQ{B6u;gxX0=5?u@PJ?>sa9Gz!9pjkU`r9T;-~1l`roZcKXDP8D%m(!1&RRI<-DAfFh*J`$`^J>*)6GCpD!1I zv*i{ceHq~~4ZB@1wsWn=<#UPKw>-cPo^|g&Q^;h#yL0?9ty*0g zzkaebSmJTFlBrG4CJn1AZ0@#fbmDOLmaZi-whwi0u`3IGdz}{bO^>?jjQh&lO5*9y zH)fl}GCuN=~?+xmJ~R zEgO`>tMGElS-+f)Y(UOnHbc&Y0y4K@QHL97^bxKxHrj_kdtcFBF4|4S>MvfG_~Wnt(6>#OVQH0EjaJ!T=Cw2802( z`;?V}2SwY1qFTt$Ss|(c&^kLH3;?kK24GGpokQbXGzc1 zvH{Ry$<84R0C9dm7y#mefG_~Wg#lpzh-U_b0UzH0eS3zyP^$e`h0e! z;anz;_r<6eaN)h$XA9?|Fw2KD%SNMFvxP-t1G_s~6NV)plAaG7_T16zc?P<4X!Gjl zvB5|C*%-8*4v+rIy;oC7-k^=5^?J@gl>uv+uDF%7iGAnoSwv~)&_iJH|q5l*#dk|0t*X; z?yZ#KYUe=u5*50WbM)6=wA$^0?i}hG0+GNJ_F4~hIZyZ5rMIaHuL2g{rrIw$d84z5 z{vYyv1?B$#E#G;i&l2}toofzt(39{VJ+90?pJ9%DCUc0|Z4yyQUu3g%?MD62XwBUg ztvr+R)ml*{(yF$XktI}jtD^UKGLd=C_C%&dV~vv@t`iI5lhC@llb7oU3;t;DR$kWy zuf9sDtFtX`sD5m7gE%uzfyZo-kuOlhrTE~^*o$c97 zUJtT&%Z^K)gn)XPRj3d8!yFuORw_D&{~dv`4cZ)V1lZXh6GqVPg$JdZAii*P;duH6 z0lO_+Ye37cSBg$1&Qq-Z7mhC+b>Y@7+|7lnq%T4G=A6~r*;AdhjIAow*~?~(&a2eF zh3wh;y4`GXtZ!+3F{0i~Qtq^Z=Ccc+PagC4YPq>b0^K&qNM~{T)vDBZ=qF-3e|o6p zt#J!Pxha0zkzL$Qy#mxjayFmTJo+%jT`yf>WV~m|dPc%>`bH7bt>>> zN_-R8#+&)spg_r61W9LTt%2T78Mrdm*gK41>?D^3f#J0O?3OQ^vz7BloM~r|G;jAp0BbO9l}E+owTcE?N5H`VXk3 z#;ur#M(UG*=_Ww#r(3)V_-2FMqfE~v(e#Z%m>G_`k$wILkUc_aH7-`B=m3~C8b~`2 zAWao;ISFSpSU|Gcozdt3-zt#C26&r58biR+1OgU;1}6d zA1zvUcv|llt(d4EQ1B1(ld$gGmi93Uf2R+Bm%_J|No-Ks9})pyrVk4)lqpa9Skb!M z)4E5rl=i&}{t)~HC^Sm3f@AS*KgTL{3KiqB`s&IunU+8_0_?-T7xQx3BUOszfj z)@{-x`7t~!Noqxc>w;F(Ly=@QcKVDgqb|zH(@+P^J?@|rTGlAIhC@N z^e&_+C#2c3cyk}NB_rSFi#XAKF}XNjJ<3tZbT?{y=Cc-(^Y$0e zKAv(yTbwFE@-Vo>c6WLV4wk!CQAl!{UAt{t%#VwzE>UeKg(-F-PRp{A*g}-5yMj9! zowJo6J3j5MZ*+{NWUUY@b_wa`h~v1#(@Li;5=uyiVU^I7C~XO~`_;NaLQAzA!mtsR zL$LlM6a$%1uI{HX4nNKDgps2Rdsz=T;3;&swjL5EM_A94e1dAt)KxcL4h`LJrp!LTo`!6^wNN071I;)%bN?*kf>pYW{Gllw;MCQ}{80svcUaL^A z3!%;y>h(fx3aGV0VKV4WF$K7Y=Lq#`px5l<^Z-iick9gfPe0VyX7Nt9C`g>rW2P4Va?$y7&ER;iBWMV1$hc8R91yoqQ- z-^^(=PmVyQJQwL3W zDJwR-0~MO(+!m4B+KG%F($&NYQSNlxXLKE5A@Jv>1fCg}tI5e^26|n|sRC@?!F^8A z$*z1CLu~8vbu?wm+X~MyJEl}zqf<3kYWtKj|E4_$D>kdl+|fbOYzmm}iqD-16n7NW z$(Zpt%%~JNpz|*yIR`q&tM%bSqbs+12a6J#VB8#J)aze-dqMvmMurq zhm0#TE6KwEY@(75QgZ6Y%sBmueASwhsMAq4hICe}?4XonhEmB~CqwHjN68Mz0vAFx zBHWR#OkLDSm8ayamy%_*wHfvb{JidD@~}T;eY13wazUoQN)~#!j*f{@w=4R`>nrWx zsm{`&eOZf0PJy!1hYNOMzem&sOc+Wk<@A{@V4FR8Tj8)uNWqc4<3aMZI0DC*)_4~C4J6XZLX~E=^ehaX#%U-`} z?WT&j_@Xm?1g|By-^LwU&J8)=0jS#HP6S?ZJ^lz+GJF@T4^^`Jkov1^KI>rfSvlWQ z=g(wb8Xb4H+iS&R7$+H5uJ@z)?|5ILWP6Xw8>tj`pGf<7egHgi<727$D2ZBV+v1d* z04>H#+l8uHH*0*lg+NPGmQwmXWzuxyN^}f#?9V6t0{TXmbU}$|6U)ZM&>tyfYQZBV zwS!DHkz!;~wXV3C|sHcw7Dn=hv$!%|qT z@J{MNxq}(@Wpo2YKsR7PoNd^YF}AaSXNvAq3v`z&aGdQar<5_mi2@x76yya3mcd7t zexo&wwl)9OJS1aQ9(Dpe0@wU`{oBB5CxCpH*5l+E)PhyI2j5R$YA5{^$O7*XCl@Ht zn^`nks#CK1Dy6Jc(Xd+IZt>_~v}b15`MQt}5U7Uz0+{-iuw(>%rlFRZJLOt^Gp{rq zFchm~ssLg*qyDol?nfN$rX+Wg$7{;wLp9B)|BB+(4>>IOU{MfZ9;y{xe|Fr~c#;NL z&zK5(EE|C}-Wry9vDf)uN*>OVT+}aurkx4uuD?EjUQ5ALD`lmxvumwsDNI2$^%?6* zQqthu?2+|Hl%uaJubo}zSoh1t6|0XRuixRPlUD`^nfwqbGJ~&~dR_QRb*OQfBg1Z4 z#Kd?*J9T!i`jK#<{uo4tEtog@Y5x{$deI^`KU6XO{`)_l)imGi|Ts?=_fj&u9xF))Opp z>!0qvgtgF($97%xnOo*vD^+74W{WYLtem^(rfJc%)4NRINQp3@P@9*e#4!ZMP>wD`w(_Eh)d z$RO+0=1{OLgY5(@A&@*|6S~d$BG#3$p1M1`|*D`|0nW)7XKIVzmETF`M-|;*YkfH|L^Dj zKK?()|JV6{jQ?Np|2Y30CCbnLeExUke{cR*@&8o**YJM<|5x&V4gYA?(MJ9;G(^m{ zBl?W!4*u`qpBZz61{g6YM~q1k+>TcKoSz008!qr$@l(P42xg>=8b5O|N>J1wpzutZ zqsA{Bj2$X!{L;ZFdr{+84#xZ%HGb{jR|)=&gI^-}w+@~V{5uC9sHo+2ORuS!C!Xp9fH5^;I|6?o`YW}_*V|zDEO}q-XQp&4!%lo z2j&Fw`7FVG4n9-xYzLnpcqa#AIf@#)Ie3NOy&XIz_+SSw5`2_{2Lzws;7-A(Irx7O z*2eh`{;l9k9Q;3muXOOY1V7usUlja&2Y+1fD;#{M;5Rz>?SkLt;F|@%&%rkczSF@k z6nu|^pCkAa4!%_IXB|wZ5H-HyU?xCO;}HjA#D%vGK2Y#49Zdg>oH&?mTT$a54xS~r z)5$}x;2j(s2_A6pAL$GlBM$zl;5{7tUBN3H{E*;P4*s;@V;sz~Ow>5p!S5El*1>NU ze1U_pJHcBA(`iPH%MBiVUgJs!JiBp~11@V^?SS(e*EryeM&^K%8XFvNbmIjMSlM`y z1NLpa)B(#H8yzsxc!dMzHD2j}{>G~v&^>_BvqL|6DNK4`vtoT+ZQ;`!f2MJDR~|~~ zrLF%SO8){TsyuW>z;GniVXh3ASpicInAri-2$(qmb5+314VZNSGcRDC9We6)=IVf1 z5HN_1%i+SVfTElBsP2HeCP(!IR5M5Q2Gm53>IS1v8SW z)KNKV=YTpoM~wuOY?`jpmITzEIck@HIy^@u0d-K0+BKl|&QVJPYQG#c8c+x3sIh=r zoTJ7AYE_O}7En*gQM(1yp*d>zfTH{G1zH|Z%sxG8kAONPN9`Fx>2l1Hrys1tM4QvymRZYL&(1{4N!k2)-%R^+I|0}5N44|PO9 z?USR93@8i%KGabGwK+!}9ZXd-GBS)PYP>hg1;%OeWL3%&y^M0@6 zvc_K-UN$_=PYcOpVOisE!vB3LK99(Z_kZQd@T$28tvotU^9l^B!Y2rO_d0}aZa3Do z;U)E5kCYJwx$3qJ8E$T96QLr>e~9E%dP4pu^!DL8pvx(3LO)n(l}_kXXgaA%!F(4tdx$ID8_DG7fH@EKUpneI} zkvxMCV_ft<34>*RFXVYyRBCcUT6c9G z?ka(;Twg;KlnLEr97A9_0J)Qt-;|-WP$wj)QpL?zQ0U3HMc{ZIS_j3;^8A((#&;&g zlAHO+A;n_BcLEFQr^L?8IPf=@UlE!|6Doo^fi=N6G95%1D9!*fOqEYUN`PFm4Vh zHwSGiH!F*|+5KOL`UH8BT)qV+d%4_f0&uIij@DNY0EK&NE8J}s?skQ1m*3+F_crr< zyF4##BO=dtnCCm?d0Ct1y*wZ+EOwTP?eZR0lY%M0( z!MY=v5H7Gb@*YBB&5B1mR=k$^nO=OqLy`AH#y#6 zN(w@x^HgSC0K(14^DJqKqd;wSXTqe)FI1xkcO%~}xAT^DIsi%7Di6GPY(!NjjThvU zl6wL36fNH@G17({6Ri4+#Zg9zq>|4n{+6TryW@qEuCfMP!?f z<8u0WKIo$WGkIw{VOuxCiwX=``Lc^t&g397-f1SgiVCsCD$$m!e15R9+U;{%d|*L( z>m1#0)p2n-GXev(fIr5WzR-R4wD?i_Oji^gby>Rk* z%ezm-^uXvMHBFDr+8kU`=veDQVd1mj6WvNO8-lLNN@11h(XQupwb@F{byaawm>znpPRY0NY(iPgyDF27 z0Vfgznbqn*V4*R^Dy7z$>}*Z*u>TX8YBC}!tuq1YvoxjnPrgmzLnFL_W)>P@?wKxu^EAUX(cJ5p zoKG7Ws`pQ~^*o+J4irq|=1Ro+W=L=uV$qA$58FIa?>rN;D_r{zKOF!%Yxty)f8q^_AP!XmmHhk z@+>u=+gzfJZIx&RhT*M4up_Cp8}N{*&M_x=(q7Y!79l-b@z4b7pt4%DTYc*ia&0TJVJ2!l0?h zkh4J(mgHG;ytf9>#KOB(1-UY%F){~N28@5i7c+-f8<*XOEO%m5Tu(mpna`UJDK}(je;D0yZ zt;gWbXhwxKUF#pdP0oD(a3K-m#(UL2d=D(I_}cFlezO}2i0=w_4>*jAs@ zd#vws^4RKb_AxBoO`bI)q+aGxhLE;i#-+>pD7!TnO*Lm48}+%#vGl17CoO7uN1@$X z>MPd5lia(073mkgy`q)-TR-F5h~>L2omOKhb|1b{Eco4I){&pAyidKtuE;{d8L!dx z8(?Cg&$wB>OYt4qAfJS@R-^X(ZE~bKj#q+1m{pmD?CR8AB{__s+4u2L4-IRLB)fz zJ|!O}BU^`j zygT|XF@#l~r*OUbD)3wL;Qgd#RWZ`vnJEaGfGQ;&gD=_th?X-L%%Ou_@qp`_g-*v6 z;71BTVsZ=W>yP;at2*-Ps|7I4^(O^Cr%o!gQ#yYtzhX3_piqt5kslN&E7ga9(>L)W zmC7iiU)V>^O%vbr)~(QL1oi@I%9arB&nsxL-E_*cD3tZ(vjO~^UA<0R>&kv1mtJw= zI{-7gX7(hUJY(9!DlGLa-~O(6LT*8ewaAPOrp5kT6ym0w>}P@pGDCS`7H7N?C`LG~ zHf|a-o95|M!Mu<>vAT$oaL)0ry`@?;m7JSjm+6 z<}#(PAW|QLZ`SL`Xp=o=w5Bvh-*?C_d82C%f_t`bOQ*&OW6FT*xCgAgI;Pfo{b;h& z6NL)H$g1PuJMFW<4~I&k9)^V3xcU?RIehL`n4BSy676AGYj~UWe_Cx+G5zW1LR94lgT@R!4hR{DVkC z-H#qC*&8%606eb_Ju~R0f1p1||EMm@GPCd>7HGw9iAOl$(m%;pc@#!&`fgz?yG2sp z2Fo+-3h-wIu!6V%E}rvDn9x=-mCu#Z%$IJYWK{c&@J)-_R2VaWG7>RmK1k_Xs^;%! zt47GO)wl`9X1@}FVJii9Pn{b35>cE1m*oMMu_A|k6}aMB{~6JLx|$00#QjyBaq=CK zh`!6Jg40dPxcXyl!TvklXH@shb{tegF-}@T(aMc7xpYorH)Q9U@Tql*Ly6D!8|FBw z@r9FnG9E~_Ov?7`LH9MG>mKbNYJ8il=p2peGSd(6Zv~5mEcL|6V}xi~*5~ft89f_; zXD=P}cPDADBWd#;J=1b9LG9TC*std}W5pNxbG65Xbkd_LPdO+$n(avpsz*e)X4HQ7 z;@24(_O@^LD?eX#3cxF~gRG{{?aIvzBHym zbST{)&o0 zD?w+g_@TsdvZn~15I8x==1dNgI7YZ4EAz(9&{Zqjj!T{sIlsB%GXx$8Q-4P)$8VR2 zOUyAwp$8g=@|$qxRYy#e#hxuIF(%WWSV0C{r43{jaY9ih2epV7Pbjc2i7^O`=P*#w zmIMBi*r-d2Ort64^M8QTf8&28(zh*r{y=2)a+B2~i0x%n{TYQh5=@ZQqky+Bs|q<) zR*wcp&B=?a&g}OXex0lyYu{V>ea6No7s*Sf;yCF%E|25AlqW7t<9FNADWQ~f%7ALZ z=?teTXxx=ORgMh%#{v9UBX;GnOqWLrt;KnnY>;%CR0)uE8 zOPqB}h4&(89FWPL>)^l|N7EC5JiC!;1#t}XaU8E)Va0=XiBrmwI5V|c{suDR_CRJ4 zCln<}oB{`lQ?W6~79{T9jU6PeC~x<`J#Ck4$=kBV&*>pI?9fqMI{#Of!t+(ud>wWs zbW*PA-fA5-57Dm>{n$UIF2P{}9>9<1JM4GJeuw!@zE6#~bGjOH*b&HH3V)pQiK5sB z_dJWNKbP%YJoXRjEXXVj8?ORU4MxvO|S_~~-&_0aLok3tR zNOT2dXv=aM94l@+14CpHPUB$el9_{0DZ6O8(*tZSfGGv8Y$22)>Oj-*vW^u@#iXBz zEE*$7ZxJbm~={@e#K{6=4LU`|JUkv|1<#6!H7HT6P|i-u$=P-Kft!?6 zQ=T#g$(K7A(ozIx<3l}$)CA|t>NnL@Y=m=+;sfw{>4lH4? zFp9=&uC@YHS}S`;sk9;3Qi% ze{-@Ws>s%!a^U$=T$3%%#>4Z6Nq`|6B(f!^FnLoj~ z$R~8v6Q_pnDiCYQvKC3@b_}8&uw<- zjD~t1)rNW=5+F=pSNdA|rSUl&U9~vZt4*K7&q#kc<=}(NOgrT8--K~_Xx;qfS>W;e}T;0h_btkt_cZ#`n-5EV2 z(q4Bh{J&Or+@!j@`b$pXWvfVuos>_J(~%vDqxrV5a)p*__+^c*PSn4{gv(W>!e+-O z1@lPgSEYz7M4Y0Keswe?o2jk190SO4y4z@Pm}Yp)q)M7xpA0%(qXA>-KJmnHd?lxw zlon~aJNY?>QEObu5v8za@L4({8)p}}1GAhG=fJFRY!*D3dT<|8HVcwB*%#P59dU}I z*0`!b?+QonN=J{gozN2wdZ!pYeN3Up1=}a82xpM#7V{XVtI>nBE6#<`c1|LMZ#yeM z!kAMO=SK*#v-j|jjA4EfFm!NmspC;rDm03nO7Q08#>YHd$|jOZb{anMLr+6}Na&Zl zhp(Gq>$KdbtQ53DzBy&mga_LnS->qMZCkuHVriMSIYGhg&Yo_x4iB{Ir!f#M97xWj zXxpTVLclc0^2YAGaw`cD+MF^tc3*V@)Uwqg+|=)Ow#%$CrB-ZWyUZFnGCG_LVB2L* zwN|yBE1kixS4K{uQ7K-&S7tY-s~N2U$DRRKvH_P5BbyJ0yQ+_TlE7h%#2}_W>1eDK zX%4cTHMs8Rc*a8x08#g6!98F1FIH?Wh6ZERjwx?O{c@OGze!dlbV0Vja8ES;R@tp{ ztExyA(%n<>%zlCAF7YA7`t;kxHT#oINV9Vw;VE&ev_34=#(99*Gx+hU;RM-HaxPFf zmg@pTiU*b8^uaCpe7E&D#cGQ&e5po&^`tu0nFJHikv)K+(+_h$8m6gobXF zktj_;P#EERwyap|rd{^xy z8RCpC|7+mk@A;?2>&+SXzArO%I* z#+H`Hb}6L`=%UN^o9qP0y~gQ6L+RH|-LXh7z21dPb_ToXI#NH~mLFZb~8IuU3Ji9zZSlO1zgS-cG%g8qsRj@Wb)t>B`TD70k^6gYQ=6iJt)m*oc`E#L!gG1Cee;SKSUVN-GCTc2h*ENf z5AhuQO6fKHn$2!X8s+F((wJ}eA2gfFYuY3z^Dw0Dh}LFKAf)Dp^O#}tK`f+CT?zopwV!twrJybo%}>(s-N0! zIYN(%GU|xeZplO3yq@5$6Bu7pGD}z+?(kB|#JD}&@>0qcRc=N!j~gH`GCs6Kchnt4 zX&d)~#_A({B^k!Lgc`JTf4JF7YU%MIsaNF5w+ex$OY_E-XCC2|EnLnK8S-udzfD6P z<<)tBQ|U|BQ*Q1ZB%~k9S=^x8X}(r*-;CDgw#967zv}AjAUBl1P3kW)|*6%}G;_2hyxzIw2InnB*E4!8;#$c*;`j-LaL zTLYODimM`t=2FEdzccxZwj83t{Xe0Q@BD0E3;H}_^)Ua3Q5H9L=c0-%mZHytf1b(W z^GM%hF&;e?LEOM++`QI)pU-d7+NdEe%FHKopPBibwk-l^+b@tKZTp1)rfoClU=Dj> z+>{sEww!{I&*DK!R=(^-{KQTDTE8u8gqcb$kdZ{welgzB7PCgQ@YUocLZcXjJ>@rT z+SZ=(4!h%IgO&MhRDf@PUxX2w8NCx!s=ZRzU(|Rh1haR8X!FS4qiP$<-ix#1k1m#> zn!;?#-sdAIVs-~VoFKB_ckmmw(O5EV+h`OmwQV#{#A+wF$~Ub3fE=;GCKzeYseON^GcsOgMciZ}3LtWB;NY_YpT$aP}`g?)EP_ zHmKk|TXu96XXySj?oCW-;jeqswq&^yb+O0h`Bv_a z!gVjp45t!+kAVsELI#ri==JN5^W!Im&HM2m?};1mwvy?CLnHjw`;KRLnDUx^c5m+Q z?yLO>-#!h_jqss0txEDbvOSc(o?q*9Hvx#vZ@~2vIpvd1_j7X8>D~umoi6*1>2z5M zf~f1R$feV7QS%}lh=&ROf7o)Xxt$5 zXGGyUTT>9_xG7(sItHOi<6%X4KvC9CErsJTlU>p*Es+u^66u>LRVZTIv_hFjpe+mf+3pu>~vTyd`i<|yJP)%Tcs-Bo-I#zn;VS(CL@!B)42!)mV3@y!UM)H zkj9GvD?4Ax0*<9`wauP~H!Is)PErr>{}Rz^@)pt5ys&7GUX3`d<)2k~-)~6wEJaUI z6XCBgk=;!ExLkidJV@wDD%q_p(b9o`O%Mdx8>DO$>P;M;$RZ+B2= z`1XDr9rzR9J}9T8vwq^+U2-Pm1>Y_(zUd?RFh4Dt(yw(#7?-D`Uw;)|_&)Jt3?4}P zH^DUT#=>RR&@n=C0s>svg??F2z8$Oi4TrZ~=&idEJq@-ym&bN=k<+u$7YcaC(!$>J zb)+I{E{uy*|0jH@_dPATB0mdr5Fe({Dn~|C7p2y(HuS9e%gtrZXoXM)+V`d>C~}pQ z(}#4Ubl)nk&mUjp�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

    S$B9yIBAighj;p= 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 zξ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 TgizP?;g3dD;EQrTPZan+O+&cr diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index 090557587b154ac8f72ee73c912f5a8f5a99a443..bbacfe84115aaa0c47dd65eed630f1c7b07ac37b 100644 GIT binary patch literal 284016 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 I_GIzD0OazLU;qFB delta 126013 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/x86_64/gamecult_mesh_quic_native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/x86_64/gamecult_mesh_quic_native.dll index 91133b5a4a241023578fb9dce5cf395e49ce5be5..6805f1757c4eda510063731332cb7de58bcc9059 100644 GIT binary patch literal 171008 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�m9J*0 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≪jM65ZH9kHCdCE(ncK6ZGOX>}A=FVssA@ zJP3^?dSh}#ejA%x+G$s!{n$=>JtB0Ry}k1xj69bXSva;Kvlp2gQexZX4=@X{`xZxK zlV}r;Lh*}mZ`*8bqDqqw;Fjk_gs8sJ8Jw$Ufie5&;6UDkHnD>@;+`*A?BpYd`Q&@8 zO=?*#rFMg2f&%o)pqGV*u5pF#fKu*7lV}Tcxk|ukFyih8#seSv$8)I^ccQ-$J$^;r zJRQx1^;gg}XVW%I933uUdp~=zF|hR9(ey4g?fJryY*|1X>2TT^J7NE-Tm*Ep+xv-K zQXERsJS4RtEp9gsYah0@QHP?|0c1+PZqCE#GZ7%C9`Q6`%g#{)s5x-BJ z(Sff-^;XE!Bl4Sf^P7TGzlQ;Vgupo>+CqV?*tLrI6pDjGC~p$sA$ST$6|mmuS|m38 z_SX4?J0`g}9LSIg_evesD;%un#?Q2H?QWj= zJ59O%Jit5Ep%2VZ6F-@)0Gp)7|48u|uz!fy3Bc&b99C}RC_EIJL%0&RcI-Xp=}FLEsCTi5~^azUKH(gy21}zX^g=su+Z?$0;k~ebuFED z(|RM!h1TqCVmc<@G9Gm^dofXOZ$yYDYO~dY9J+DEW*5XtxfI8yIq&1XWR}t=9CKeU z2p*5S3^P$UdNoNKP#sER0~&97Hsy1$d{t9 z^}$yj$fQWxNVFlH3G>MIv!8Fn(Cs));Bn7Bi_~Hdg|KpT!^_AyzOU~yB@@E+8S6uU z8A8F$9jbK$s!M`sLwW3G+%6T}QU$+Pns)m;0{My3Fit4Osk3$>- zltj<2Z6y8f8B5|c;8s^t|B@kMbOO(P#ebkhirAGO42$ua|GBEQIc^=vz1&~hYgLLH}?#X878U1dE zmZf64ACYrm4KTD6#%os2so4#dO*S!tScB8)-b;Fg#~;p@l5uj7-;LcRwFj{k>CA1g zc(Kl5u&LScxaU>wIjCiT7Hx~pM*9o>4?`by2`x8#GbF4Sfg|!Wsmda`L%T>SrM}rq4F&JO;ZBx- z44DpvJ!K+T3;G;n3?!+YK)2>w^NGFf&(oR!gHFumGWmwzMj`?iQ8`f<2;n~8M8~qQ zI;2BcD5+>7R2@5$4ZfnYpX${6-NCPkp zN_yDZPE_4~h{lx^ry<%>Hma$-Kb3;f2TBv>%K>M&NNc=9H_v1$6+4k`fFTTSo^dJ) zJIYD`03|esXR5K(j_Wj#l@?(Csx;B}9QXieI?c|#KTw_|+{$oM0j~ZIQd7AHya_PN zGWt@*j^Q9q`%QN@kmeBoR{vWZ$8h4ANZKAq8-Nwr2^?Ef0q7GDDar4k1!x47FvCKb zYC1%>$v^lt%?BKE%>4&t!`=*MI52ohMc%xj!8W}bj@Je!B-Dw=ra z$2=ZJyGLQ3C*OP}bzrs875`VH@Z66Yroa?W&Oy# zKUVCS3T9HLJXXLcz^3RG9!^cbkq<2UpbmD)pH3p1s^lARVUcrOK8YCNM7t9knVpo; zm3Il5jXZ)t4p%D9ClBC8R0q#xI$XN zJ_L&k9K`)0+bsuSE2C(Fw3kzC@+^d-`iT2$w6_8x5*Ay~nx|3AxMzu(d7h_1L7eZJ zfjRUE!4;~hQ)fKwKhW%XiSr~*jG_&zKIoAFjpaJ5sq!zeT_2XM*aw7DegTx_hcBG} zXH0T=%lY!b$k7{M@ZkVWAZ*2y<3h}tfrbSnaWN+P8NxahJf|KRCi3tE3^*>mkI0Wq zBwHae@PS#+(EC{BSji>5M9KwPv(Q$}@P@L86cIIIm*wajHD)GRNcLyKF2p6>4s3wf zyBLAO+|C9N_bgp4(y`@iv04D&6#{`9Sl=$1gD*B}W#T23IKC%uSODj`uH`HS&&A4`a^%z}1!YNYCV24U2pQY=k=b`7?H60CPh^!*h0FmA0OoPaTildC05eI8FY!4GM!5fSz8w^b zEbt|OF|Y3*MTW#+fR9YU?+6AsLPFavfBdQ#RHra=f~c0?nZVdGvXz$JaE3WrU0z)n zPQF4kc^#TOwb-La#Bam_EH0h<$Kg<;8hSbQ^@!qpyD$?>|Eep#9S|aE?;;&laHxgW z{Lmh$Ac`zO5s@om6A)`n&uK@B zDgOk97PHo-sCB9?iMWcG)~1Ewl{2kIOzR zRF$Mt1)ey+emAAGla+B14Ms`xVN=T$V2oy*ca4S5rI#aV+cQBx6n9CoU!U_`!@dIql2B zb1kmij~l8B(b8A}2SwiZmx!Jkh@T6Whpjh=oOH^$#3dD07t%*ckU8i}*EOR3IPg6R zEB$*8X-W|n)F3Lg4=MOepg)m!CDJGoLzU#TlQlAg2hL4+*J4alkcGY)fGiVyqa13* z=4Z?vJfi^S-{QE+ajIk35IIPjOggn_aEM3(V~A==)4^E^jniSYMB~&U2vrSj<`rjr zMkMX{)fg^RRfa1|xIh`MMPj%XVYra>Eo8Z*Wf-nAPU$eu?B=hL$UIX5_H&n#DWn?>%oppVNFx>y)+&| zj!`0{+9jqvSf_?hE(m}hPyvu3N70?xtxej5v zsiOJXBbwQwBU!rVZ%Sbi`b|^n?5kq zqL6g3JB5v|Fyz2$@l^-Dw^OXDCeVHsUBo$HQ!5A)JqL8H&ZBd{BXZquA`x+y!NXE7 z3hcR-vyq9{-7!~7H0)CZzv8Q z5T0A-e8Pv{tZ057;^LZ9wgv1I3E6HJB3Iy zAhUhaJ;p$*#zd9B3vCwmg1l3IEt9PPC&qyCaLA)MD-iLRNVOCCr3ZzXTGv;pU91k< z?fXGj!*PoKw!w3}@(;bou|FMP<^On*R!n zu>IxXM~M14?fal^;9O>LLF?kh5Ij7N@<;DzS3DvUV!ett0FUFtl@RtnWC$K=NH>jM zY=N_d8tko$^ARe8^PozTDBDnkLM2^0+7P3O8G4o|LT0p4%UUw*4f!ncNZ?3#g*<=_ zT^^7H5D?G#kdLcTz&YsZD?RJd-X{N z7PjUmT3~Soo!jEQALpw3i0<|}5$in}I!WQv_r z5w8C;9FZQXuo<>&uga&PefQmjnjj=9mtkiFG8LFz;ce!<^hLSp>Vn-aSWH!guw%th zCI$;uDP0BKwgBmcs7d8nSAnq=dvyeR#9kPIUYdIu2n+zQg@zMh3!JTro3^&|6g|Ve zeF$gJVT0!?1z9W$iCkKay+o1S5jj4H=W@dHr+@*N9&w}!p#B+l097yM8tMM^A=O~9 z6>ywn6lO1qb~lN-YXq47jmUI`mqTG*Zp5~-5^0#fXtFWdq$v9~q)fj9WpM=Rk}#v3 z(xs?^?_AnMXP%{_B-$ztsr7xTHPRWni}lZYj+9X8BU+2FGrzAF=*xRPAenYwJB5Fs zgoRA|yeB5pUX|%i)FmnWN4u(LtaO@1U%?S)a^#c=|6%J+_%XGy&`y^rhrj@I>5Clt zGK`MK#iQtJ%2vvPFTfmh3JHPurVtaCqPP$WqY!{#G_@KKP=uo&7f3V}=q{4VZGSE* z98Jw1q9Z)`++KJrFfJ#ukNWHg&6o1*urq2i<)7`N%X2OTcM4B>iafehly)QJlJwn9 z$!>IJGCmJAw{1#%;WPOCwT=*OlHT{1=APMdixam=(#wS?M|ZrC3nbw_&aDE{2}3_3z%Wr8+wlNUI6N>{}LV%C6$=r1)4=T;pDQP0#K~fjjN)3 z)71hA@ghZ9Lr{y`21Ql}i3~zk<6a;ttFir7wo)K#AVHiKA0%VygOMq5lOfWkoV=3W zf0;z@6XN}nc>hwoez=NBevf#vTJ_4D!#3{?KkwSbRr-v_i>&_3I_4 zbWBb`|C9w@$;LTMfrPpA8%OYuXI#=@$0>$u?PLB+92l-bSn{7f;lk${72U4M$D9=h zmq@7?%RDm-6yX_9+X`z$w5iq_6e#J@O9p~xgYCw_-^wz3VCj_nqmmPMTeltJ5JHKVA z-apzUy&AO{ep!B7Y;A?E6kDaSE9XQjd<3n|o~Sn{??Ee=pr*pA8T+!roVQ$%j_Iz< z9(;WKn%gB!m;$1+!ra$TYHu{I(0FFk zbwdWd#s)t|(ZO+)4?tV#H^}$HGwo4^o(>K$#(lJc7Gmx{h%;J5({*SrwinfY%PxKB zl0L{eEN4R{!~UST8x>{JVD34A({;Q3SzT}*sVmIX@*>_{h89Esjmdg6+uZJ7E+-}- zHXl`FH(rp=Sr+nACc=~b;62>lTrEF}JQTMe|8t!7AfLYfk(VLA%U}agn$-5}$UR5o z1S3Mtk-!4kg%IiH!C8zZdAGbupy`J|@7zxn=%7@>2bd{$02u5nBL-)w%Ijh}4C>Pw z_Sgp?z#p*NX#NmgXg6%L-*Z+nj6i+d)}>%Bp|Tz1!wFZUJ7gGGhf0rN7zJf4!FgLN zfpp+{`RCO2Wyp|ckD$&}Ae|ocWG-6DIc~q_RryAQOnXkqdr(+9*#EeKLlaWlfP^w% zEUu6}Jye7#!;R|snH^_m^g+P~xMnfUPOAkD+vq&z&ww9Z7{S*b4R&0X&V`hMum~8! zCAxlNB(3%e(lF358(l#C1cgB`Nqk$r3!)lX`N+a>->=QO5v!1rg`Iccdcey@H5^$( zi<(0#6x6B*JLDsRO%2_Ui6o##Max9e{&6{I7x5c{k@&#eSc^HD7X7d?Fnmp^sbGuq zN(Hk>8riWZSobI7yhYe6CmW3z1iLt28{}bee1S_ju_wmF2pHtQf-RvG&^`a*h@gYBrOtM;-#Y2Wk7;?o_B-jptYWOJ%fQCtw95UQ_t5VXA@HXUh8CD~On(Zm*FNgv==PT_-*Yp`&Lj*7!}ei8cdf`&XyoOV~is2S-BM&h>ogcyOo+NPZJ)m(ayrY)$EUjpFIWk2@2>EEK$ zq}+pn23R2AcCUa0ItcwkYMm}{LInenv za8AJh&*|X%zz=u;X)+=?gROB>eg_8N`8CuC6c2v72hqP%6ygKg9MH@lCN=Zmy!#-j zSODL7PDD$C(j2^*l2V2mkttsVa}ckld-0$b$;JD6A(@4u<_yX^i}B zC6XwA9fAJ$H1*|eNRjWrOHHKu7-KN}&;tSbiJSqN^L_GL?qjSYUH3qU)7oZ`~U zPy{1sb%F_Txr!Y?ktzvdY650sY2Ul3EBd-Y0Ko@j;goKgQM6e88ENu|3Uq_~jwm|V z&tI$YzM~?5H5xxh(`nd_+B&%rQd0goGSoh&%HLNaBS4|=AlUzItb>Z~*EEl#CX5#Y z8-izb4PTLejRgYK&%n{86S1v?Nbd|82L-Bbc?#}{MbLe{6O#&>~9l>_WphE#L zAIS%bs&e8IL^}=dKtkYmp3qAiy(q^NpQCXx?pRVg_7>iZQ-axse>?%jrIA;pWb6_W z@Xv_jbD!387*rF%z0XjIL1qz8(j+nb(Uk%}w3bbR1zde(u{`{X89IQbr0)fWvdDE|NJ|0Mxvp_~Hz*FHxJ4`$k zHcUdrgI$IMc=TYt5TC8);U8J;^m8KH-)l!5=}lEauf*vAn$=53UcP!CMi+Mem~CV@ zR5;+++e54V2D~VW-?>6zE`3)VRmIxP-=-B%ZH7u41VTdiiOpCT!11o+RS}RI} zZUw)XR)$F0WWbb8K-4NmLeThl7{U&Pc_eKO(T>b(PtY-c#=NqGIOd-;&qLmL z!|t=6LIJZhSouC37Fr@GH$C3rOU;)IKgUFJ1iN+lA;VJ$_9r6k5d?6P=a4=qkd75d zZw5b0++ufx9f^da^kKeq#I*m9BYY!KFAnuQC=UH@OUhn|MHc2Cn;y%Tj_hj7$_segQShxU!;cXpY*B^2H^gOtWOxt()Be9)>PzOP{R0?(Ll7wr)BZnP z;#)EZZ-GNP;($uo(Py3#!9iK@_X#0`fmECAhpz_Z_~~POatcRP9!jC_}vkl28iX}3( zbBs*+a5PwK%AdshZ{odMyp{9^MEINHy;Hm&6YnR)`}^X}#}I_$BK*2|j}j?Nyk8P2 zZ;SUU;(bQEeZ^P~ zF$9dp|Ma{Xr(Y!_6CjH?)>>UQ_zMM&-MamdbdXUyu$gV+dl427NsH(K8Q^gw_8ro~ zTSW$J(6I%`?@~P;S_?fO0ChT0!lhqzQxg4Lpqn(!;xly7#NV)W(l&wq+5xby`ByP|dPE$z<)Z!YJ>W3b=O2vk zrg-IG+>H6%S3DSRqj>&c+y=4HH*+vbQ~V?@u2p>6sQo_; z#yyn(`e1y3@(&Hhizxq@!FUhlKQS1$QT_viaT=yR|1V?hS%$@1DPBC7KTE_1>)R+k zb1>gdalJ}kdz3e&0!y$r4hvXX&~5n)%d(ws?;y~TwBBTJlgg9o_}~Z@A5~2G963aV z!DlEuBjA|z_z7MGM)zr5s_@o5gHi3`a6ss(_FpBv(6DJ1a z9(026?)&B`P*0O=k(-f1+dGIKH*!0&jaxVlt)^P zdx63-`Fa#5NVE$ABvB*h@b+0)4%$r*bVwaY2O0lAd7i(2C?Y+NDe=Fk{vXu*Gpe6V zr^$GpdQ|ZbsQw$O|Do!e)%sS|pQZZ6s$Zx2&8q)x)qh6yA5h^atuXeZ2bGYl`qz9@ ziF;LlkLsUM{ki|4)T5DW2*nQ z>W?9t9XwlAf3NDFQvH<=EBOzo{t4B;>bpvMmFhpE`k$(P{`ZvpX4QX5^^+e_(z8^2 zN2&hb)bV*m^?#!J_o{xa>aSD%m8x%3eUAz!UG1;Z3S%#+?Y*x0-&6hHs=loHAF6)R zPNin88oycfn^ivw_nBewTD4xI>OZXd(eiuL_!FvsRg~}4_}5hbZq@Hm{UX&LHH@#; ztLaRAo>2L`P4%Pcd)Kli9U@WRQm(}9!~AIZ2g;QEX#8LGD+0Ctjc#A{bf3T6@1DMC zMikH;3jCrg6~Bio{_48h>gpTnEbhh%x5r;oU#FHYx%2$x8!rH7eR-v&-0LmhY^ifM zSZdvM8~oJ-%IEji)NQc%>+3DG^>rKAG!=dx@GJzLmKCd=c@~s%;}_s>tEsEB;1}U* z>Nb_v)>LMp?9g&m_?>vS3Vmq7Sh-qs+vlHNUst<%2!17TW z^u+3G@p0ExqDDiFzuHo6@#9zND*(Jwsi{_6#W-`YY<3;HUchNm>1I~x{FNewj+LrE zuu!FIzT&S_)A6mX;Fl6LzEbsF(R{Uh+ajghA~oHv`g2sjXp53xJ4x{gDO!c;cirjP z$b9T}7GO23f@QHvR*Qf11nRtQUw!Q+cO_%)P3}7XJdw;Q++Kf8RSk7quvEaOW+>Ui z=!T#2-&9lS_F6R6ES2?cpM`$ee`C47qT1rGc3a9THxXPPh`EY8re|Mwy~R^gMP-ymX_k$?+XFQf(OyJ<6hLim zSbr+&>+0MU0&qjQ&*G~N_|Y9qm-U-PKm6YEI-jTB>mO|J{CIx}oG9J0*kUj^@nRND zj;s~HG;0|;xyh}<-2`;%y@T_q(tUftJ-2xNU`n(nLw&mLqK7sN^hDz~C{eaR^`qXI ztqT8?tsLYd$`kXv7C)6QD9@PX=SqIN>eFF>_OX)?{I7U+55+6mM21gEpQZZI^qBu& zg-@)3p@G_M=f~5g;%8TVuKGo)U!wX1E&4Yma1qF!h=Zw36WmuQ=fD5alCN<${x-xN zz=7Na9x89w9@gM<9&I`HQ|Q$wgEi-Z z54GQ}$v;8iE$1l?SW(#8ei3}_Q-*TUM_VrAd@xavVA{2L+OV2W>l4YP-GJSQe5_#? zeCUv@O_Se_uuOIC0xhFo*KL}--3Xt6vt6LyTKL+242S;^@HpT2vkSl_oYOUV7s8<( zaOp9^Y1=Q+)D;EP7vbR1awa;FE zh1Ka0ZJ!vDcC%=^SDRL>l#_>~Ddh%`b^_)aS&hoyQ|b%;!C+brrBx$MGlt+w$gcYt`wEm#wXU*h+6{S!yy)M4BAuW$ z8YePAX44_7$hUeyRm6Xe7twaTPsE9cmPEwWzYE2BKzkGsfB(s9n}HxJLt2wAmL~9~ z^e=jJxXDV$jW#H?GKty2 zVc&s!U9C4XjzJk#M$l_zlwKnU2L)%)#WoQ)UvL@CN`JL+U1B^-TpwQ)XG0#*2l#vD z;r1D>gvw7avI!>>*rk6;VPg&@GlPGm$on<&D+N5@f;=ien5GwLY&a~2vgf3M{+jki zrzfyqe{kIcs0W* zZI zjxCHEotnf_XO3a1Uh@D(yeFw$!J7IhqguuXctw=@sA(7VC&eW{&;Ah?_e&T>#bJ=H ziet*WFp7Dhj9Vfz&tdE_IP+K4u`Esy@t+`V0;K^z%GbuVbw-aL!Nyl!%f`>lV&iog zOmHOVNw_tmenuFt$Ki~l%pgRT9j}|sfZB9@8G^}vHb1UfzcQiHI3*rU4dRITpzwMU zczpyLlRau6#_NAFGMvq@hw~usR2;Q&or!2oV+=ogq(`?bnJxR%NalP!iJ506Gjn$G zK$M2MMI+gwlX~WOJ(-P?Y9@_Z02}x{1rHM_L znZhQlOdlBAo7yv`-7I*p8Rchv9j{$*d*G6iiYyqPIqmee&D!cI1E1*i2|q?&+~_7+ zyZ0p~x8OcL?qtH+IDHXuKG7tO#;j=EHQ=@4G)wx-R7<+fY#Hgn-w_0@=@(@t8RM9- z@)C;&hXi~U)*gVQu^SqvxiqxQINL}zapp~IqHY-*%3aEwa6!0#1=TWEBk(@lG{K|2 z*~Fi`*D2S&H5F3wlLuuV|u~}12e=YJL1^fQPa#P$4oHq z(z7d4Gsb45XN_;4;Ju>PQhQ~uwd$(pGm5WyG@c!r*2qedAjOk(DG4maJ7OTQ*U(N< zZqg_{8|61a&W&Q8F^v5XZdOR8uO>e3N&`#PWiexKLJ!7HjE!A~a$^&)Hq2xx{%K5K zIh_?PVeI>G9)e-kGxN!7nDJ1iNPBiE=8g(mTTb9KGl`{SkLXSGBorz0cLZyLuGkHC zRIM{KPUAhaj50Tj%!Kug=7zH7B(NqY`URJIkJ7K9alpY`(x$0AX&X1$$R;13IB zqd~~b6a!0HKf+@yODsyTDKg~*3Sa#PyxxWD(HoNlv^cUtV&jtaEO{klBT5qvdPyxi z3W42m-xFm>A6TfsvLd5x*t35wf~;dZO_wefM9zOj3ur={R!hO@xwjii>vr6UMYLtBHfagZ6~RN2ys ze$PZ+&3wGe}$}GlG4EKpz$AWMxP<4AQRbf2kZXWLzN!lq=|EU@HrPX`fb;FW8_2)nnSAD>w@qWTbd47hl*{AHm zqG@kXz6a&nqAA$xKY(&bGk<+B&GiWMNTjuWV=(QSo#1OPbVE(rT%T8@$-fW_c@bDk{#&7KI*-TkQ51)>j5<-HULS zx(s3sXN9{4Q+at^Wi6$-64v0dH?O+fTU=jA*Xj#5``v75!OCR?E)^L2THJ!t62%;# zR7g0x&tGY`-{dGMDp;6r$4b{?C@u6imf}kBHrU0AaG*}a0;pSD?X7Pp&GUGM)#j)= z(XfE7fQOqRP2deD(s~1=+K=@MJK-c(v_I#yh%G+7saM%Gogt{{>vhHfN?r+gE zPrzSczsUM4P@ju&{k;OA?q7jWx`enx5P%iNFL19fuej}E;W#nMMFoz08plrXfyeLt zH$cqSFYpKsx=4Cxu~-Zk{~i`apvAx05q2ABu|BZTbMeG*I7$T9?-yLZ;a>tkBVx1B z+_?#T`8UbIUJ#i4Yh4kT0FmeaT^OP_OCYO?>T2pPdcx3TRoV0|?R(?W+S-e2NJxoft*rf0{Gw8Jini955ko9~M0`8OJVwGV@7fHz7^|;p zIpm*eE6OGL;Lqj0s>T>u{;Ar6O)z{$-GT+zM@R0TuIWG*Z8Pje*E=W#f+}n93_C@$ zXV~pGlN?C?cVL$;1&C@|#RI+f<8=|8Bl7$j)Typ-*jQe-`J%_>HVU1&Kx@!jO391Jcvex~y)*HN9NXyLSg{fN{JA1TkmpfoWBn#K zn@=I=zI5A++NC~^yMpXMORI(UkDdFJD88FL?$VmNs`^qDi_+Q}AL`whw{rEeVrOAN ztcTi^(iuvx=P=aWRDYYhw4vPZBlBa4p)c4mh?X&@YGF`Z&Bn2f2-a6H*x8~+ji1QG zZWs)gx^e$p<#prCYym6vxvNXD3ZPIaoxY?qkgl$(W<$X1#;FYc3{*pDxp%`Re8{Ij zsDZul9kDET-KH9EeceVFj^YG!NQwG7cVmse)L*{77A;{U+>JQ(@w-chWQ;|7R1W8g zo|@?!ebXCi>ZVtC{nKGX?62AAo?bg+`i$wyzz-{d&ck3@TVObhYInJ3Nd5@JN`Gx$ zxyO&*<<;X$&PG2QO=+(B`r87YMc5>f32Q-}--|I$r<@yWy#7FW?dm!phEvc_46Eyu zfvt@34K{xuAND|oRc`+UvmLZA%nNwEpjDB#zQT>`_!EZWC=W-I*xq=jPmR=j7r9}a z>vb2wyyVnOc zY;b#{SYGI)>VH;x^cN)l)n326)^fb+bvl7nY(14wBtqw~baqR#UVTKA(&-s!7-~nG zwZL&>OfD+h-V2t4%`#>?zD+2FC@EDABcfH#2UnCQVG}JRASGppYv^~ra{lN_XSZvI?0;6+jO*VqaIgi?-dofC z^|hDaejj(n!AzTTu% zJpv zXp8)QxO{}kKLb~cFjQ7%oCght;J{xGM|`EV+tS*9ksa8f`QQ)qo&1pt7<&j|@~6Q4 z2x0Ow;SM28{tUPi2;1S8!RY~?{KIe-r1!vIXjj5T@XO(n2?zMw)o>g9N7e9d_^-h| zfimQeao~OoVe+qsJA|;-rjBgwu33ojLOS_&xKF7J{F~vDfdlzvYC8Epg(Et&!~Y{3 z@rKrRiENri=fj2%G$h{zCnH=0{~b8upI-Po7J`r$*(f~;XGc2uC*X<^roRoj ziz{_^!+#l0vtj|5G>y_++pABRInI8Tefk#+)L1r$58d zILh$JjxHbO3gmaexe)G!pS2SAnq1pYWFGl2i5Q)_@Aj^b`$EuQ5o``YS;zeuZGDFso{I!zX!Jk@X4QWvr?CQn;Itn zD{8nF{%JVE`3(Fv*ew*JF8R;E37W%yM-88ZzhEu$QN|8`3mnNV^6|~A2>%lPiZaj% zWh~{`ZNcrO^6(eIwIfXSZ9jzT22YZ;PxgBF7;7^5c@S;@VKNe&4_8bw4Sq-slL;mn ztx!7oWJ6-b7?Dr5A(T!&*@aM;e6sDJF!^MkF%3A7k1aFvBm6(>(P3vT?7o|Z_M2=X zz$Xv^!_)D@d-!4&?v3m5)h__l`Q}XxcL@wef3rT>G)K1PPuPm)}|R#@jYr?O%+Z~)O%BtpWlf?4HepzUji-X zN>L9reK-#A*7!H8ainNQYPmKBXYREZZe{MZLvWgVZLD*sKKI%v-4HopW6A Date: Tue, 18 Aug 2026 23:08:59 +0200 Subject: [PATCH 029/203] Bind CultMesh application ingress to session identity --- src/GameCult.Mesh/CultMeshClient.cs | 2 +- src/GameCult.Mesh/CultMeshSessionIdentity.cs | 31 +++++++++++++++++++ .../CultMeshTransportModularityTests.cs | 6 ++-- .../GameCult.Mesh.Tests.csproj | 2 +- unity/org.gamecult.cultlib/package.json | 2 +- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/GameCult.Mesh/CultMeshClient.cs b/src/GameCult.Mesh/CultMeshClient.cs index 230ce55b..5db03ca5 100644 --- a/src/GameCult.Mesh/CultMeshClient.cs +++ b/src/GameCult.Mesh/CultMeshClient.cs @@ -587,7 +587,7 @@ namespace GameCult.Mesh using var snapshot = await CultMeshSnapshotSession.ConnectAsync( _sessions, target, - CultMeshProtocols.Content, + CultMeshProtocols.Documents, new CultMeshSnapshotRequestOptions { ConnectTimeout = TimeSpan.FromSeconds(5), diff --git a/src/GameCult.Mesh/CultMeshSessionIdentity.cs b/src/GameCult.Mesh/CultMeshSessionIdentity.cs index 43a49162..3fad99f9 100644 --- a/src/GameCult.Mesh/CultMeshSessionIdentity.cs +++ b/src/GameCult.Mesh/CultMeshSessionIdentity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Concurrent; using System.Linq; using System.Security.Cryptography; using System.Threading; @@ -70,7 +71,9 @@ namespace GameCult.Mesh private readonly HashSet _protocolIds; private readonly HashSet _routeGenerations; private readonly Dictionary _proofSigners; + private readonly ConcurrentDictionary _acceptedPeers = new(); private readonly Func _handler; + private readonly ICultNetSchemaServerPeerLifecycle? _peerLifecycle; private bool _disposed; public CultMeshSessionIdentityServer( @@ -90,18 +93,33 @@ namespace GameCult.Mesh .ToDictionary(signer => signer.Route.Generation, StringComparer.Ordinal); _handler = HandleAsync; _server.OnCultNet(_handler); + _peerLifecycle = server as ICultNetSchemaServerPeerLifecycle; + if (_peerLifecycle != null) + _peerLifecycle.PeerDisconnected += ForgetPeer; } + ///

    + /// Resolves the runtime identity established by this peer's accepted session-open handshake. + /// Application ingress must use this transport-bound value instead of trusting payload identity fields. + /// + public bool TryGetSourceRuntimeId(ICultNetSchemaServerPeer peer, out string sourceRuntimeId) => + _acceptedPeers.TryGetValue(peer, out sourceRuntimeId!); + public void Dispose() { if (_disposed) return; _disposed = true; _server.RemoveCultNetMessageListener(_handler); + if (_peerLifecycle != null) + _peerLifecycle.PeerDisconnected -= ForgetPeer; + _acceptedPeers.Clear(); } private Task HandleAsync(CultMeshSessionOpenMessage request, ICultNetSchemaServerPeer peer) { var error = Validate(request); + if (error == null) + error = BindPeer(peer, request.SourceRuntimeId); _proofSigners.TryGetValue(request.RouteGeneration ?? string.Empty, out var signer); peer.SendCultNet(new CultMeshSessionAcceptedMessage { @@ -119,6 +137,19 @@ namespace GameCult.Mesh return Task.CompletedTask; } + private void ForgetPeer(ICultNetSchemaServerPeer peer) => _acceptedPeers.TryRemove(peer, out _); + + private string? BindPeer(ICultNetSchemaServerPeer peer, string sourceRuntimeId) + { + if (_acceptedPeers.TryGetValue(peer, out var established)) + return string.Equals(established, sourceRuntimeId, StringComparison.Ordinal) + ? null + : "session-source-runtime-rebind-forbidden"; + return _acceptedPeers.TryAdd(peer, sourceRuntimeId) + ? null + : BindPeer(peer, sourceRuntimeId); + } + private string? Validate(CultMeshSessionOpenMessage request) { if (string.IsNullOrWhiteSpace(request.MessageId)) return "session-message-id-required"; diff --git a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs index 979c7230..e5d3867e 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs @@ -29,7 +29,9 @@ public sealed class CultMeshTransportModularityTests new[] { RouteGeneration }); server.OnCultNet((message, peer) => { - peer.SendCultNet(new CultNetErrorMessage { Error = "tcp:" + message.RuntimeId }); + identity.TryGetSourceRuntimeId(peer, out var sourceRuntimeId).Should().BeTrue( + "application ingress must only trust a runtime identity bound by the session-open handshake"); + peer.SendCultNet(new CultNetErrorMessage { Error = $"tcp:{message.RuntimeId}:{sourceRuntimeId}" }); return Task.CompletedTask; }); var tcpEndpoint = $"cultnet+tcp://127.0.0.1:{server.LocalEndPoint.Port}"; @@ -55,7 +57,7 @@ public sealed class CultMeshTransportModularityTests var received = await response.Task.WaitAsync(TimeSpan.FromSeconds(5)); session.State.Path!.Endpoint.Should().Be(tcpEndpoint); - received.Error.Should().Be("tcp:eve-unity"); + received.Error.Should().Be("tcp:eve-unity:cultmesh-client"); legacy.ConnectCount.Should().Be(0, "a lower transport tier is fallback, not a race against the preferred TCP tier"); } diff --git a/tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj b/tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj index 14e99d63..292e42df 100644 --- a/tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj +++ b/tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj @@ -15,7 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index b957b18c..cae02b50 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.49", + "version": "1.0.50", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From cc89aae4a0298f820c0cf99e58d3fda507ccec90 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 23:42:15 +0200 Subject: [PATCH 030/203] Make CultCache directory commits generation-atomic --- .../DirectoryMessagePackBackingStore.cs | 110 +++++++++++++----- .../BackingStoreTests.cs | 88 +++++++++++--- unity/org.gamecult.cultlib/package.json | 2 +- 3 files changed, 153 insertions(+), 47 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 191febef..9d9584a5 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -17,7 +17,12 @@ namespace GameCult.Caching.MessagePack; /// public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { - private const string IndexedFormatVersion = "cultcache.store.v2.directory-indexed"; + /// Fault-probe stage emitted before each immutable record page write. + public const string BeforeRecordPageWriteStage = "before-record-page-write"; + /// Fault-probe stage emitted after all pages exist and before manifest finality. + public const string BeforeManifestCommitStage = "before-manifest-commit"; + private const string LegacyIndexedFormatVersion = "cultcache.store.v2.directory-indexed"; + private const string IndexedFormatVersion = "cultcache.store.v3.directory-immutable-pages"; private readonly FileInfo _manifestFile; private readonly DirectoryInfo _recordDirectory; private readonly ConcurrentDictionary _dirtyKeys = new(StringComparer.Ordinal); @@ -27,6 +32,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private Dictionary _legacyInlineRecords = new(StringComparer.Ordinal); private CultSchemaCatalogEntry[] _durableCatalog = Array.Empty(); private bool _needsIndexUpgrade; + private bool _manifestUsesImmutablePages; /// /// Gets or sets the predicate that selects indexed record payloads for hydration. @@ -34,6 +40,9 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// public Func? HydrationFilter { get; set; } + /// Optional deterministic fault/progress hook for durability tests and host diagnostics. + public Action? FlushStageProbe { get; set; } + /// /// Creates a paged MessagePack backing store. /// @@ -81,10 +90,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore _durableCatalog = manifest.SchemaCatalog; var reports = new List(); var loaded = new Dictionary(StringComparer.Ordinal); - var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); if (indexed) { - _needsIndexUpgrade = false; + _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _needsIndexUpgrade = !_manifestUsesImmutablePages; _durableIndex = manifest.Records .Where(record => !string.IsNullOrWhiteSpace(record.Key)) .GroupBy(record => record.Key, StringComparer.Ordinal) @@ -148,12 +159,15 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { if (selector == null) throw new ArgumentNullException(nameof(selector)); var manifest = ReadManifest(); - if (!string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal)) + if (!string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) && + !string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal)) { PullAll(); return; } + _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _needsIndexUpgrade = !_manifestUsesImmutablePages; _durableCatalog = manifest.SchemaCatalog; _durableIndex = manifest.Records .Where(record => !string.IsNullOrWhiteSpace(record.Key)) @@ -207,13 +221,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore Directory.CreateDirectory(_recordDirectory.FullName); var currentManifest = ReadManifest(); - var currentIndex = string.Equals(currentManifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) + var currentManifestIsIndexed = string.Equals(currentManifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(currentManifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); + var currentManifestUsesImmutablePages = string.Equals(currentManifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + var currentIndex = currentManifestIsIndexed ? currentManifest.Records.ToDictionary(record => record.Key, record => record, StringComparer.Ordinal) : new Dictionary(_durableIndex, StringComparer.Ordinal); - var precommitIndex = currentIndex.Values - .Select(ToIndexRecord) - .OrderBy(record => record.Key, StringComparer.Ordinal) - .ToArray(); foreach (var key in _deletedKeys.Keys) currentIndex.Remove(key); foreach (var key in _dirtyKeys.Keys) @@ -233,18 +246,10 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore .Where(entry => usedSchemaIds.Contains(entry.SchemaId)) .OrderBy(entry => entry.SchemaName, StringComparer.Ordinal) .ToArray(); - var precommitCatalog = currentManifest.SchemaCatalog - .Concat(targetCatalog) - .GroupBy(entry => entry.SchemaId, StringComparer.Ordinal) - .Select(group => group.Last()) - .OrderBy(entry => entry.SchemaName, StringComparer.Ordinal) - .ToArray(); - - WriteManifest(precommitCatalog, precommitIndex); - var keysToWrite = (_needsIndexUpgrade ? Entries.Keys : _dirtyKeys.Keys) .OrderBy(value => value, StringComparer.Ordinal) .ToArray(); + var pageWriteIndex = 0; foreach (var key in keysToWrite) { if (!Entries.TryGetValue(key, out var stored)) @@ -257,7 +262,23 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore document, stored.Descriptor.DocumentType, Registry)); - WriteFileAtomically(RecordPath(key), CultDocumentMessagePackSerialization.SerializePersistedRecord(record)); + FlushStageProbe?.Invoke(BeforeRecordPageWriteStage, pageWriteIndex++); + WriteFileAtomically( + ImmutableRecordPath(ToIndexRecord(record)), + CultDocumentMessagePackSerialization.SerializePersistedRecord(record)); + } + + if (!currentManifestUsesImmutablePages) + { + foreach (var metadata in currentManifest.Records + .Where(record => !_deletedKeys.ContainsKey(record.Key) && !_dirtyKeys.ContainsKey(record.Key)) + .OrderBy(record => record.Key, StringComparer.Ordinal)) + { + var sourcePath = LegacyRecordPath(metadata.Key); + var targetPath = ImmutableRecordPath(ToIndexRecord(metadata)); + if (File.Exists(sourcePath) && !File.Exists(targetPath)) + WriteFileAtomically(targetPath, ReadAllBytesShared(sourcePath)); + } } foreach (var pair in _legacyInlineRecords.OrderBy(pair => pair.Key, StringComparer.Ordinal)) @@ -265,23 +286,18 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore if (_deletedKeys.ContainsKey(pair.Key) || _dirtyKeys.ContainsKey(pair.Key)) continue; WriteFileAtomically( - RecordPath(pair.Key), + ImmutableRecordPath(ToIndexRecord(pair.Value)), CultDocumentMessagePackSerialization.SerializePersistedRecord(pair.Value)); } - foreach (var key in _deletedKeys.Keys.OrderBy(value => value, StringComparer.Ordinal)) - { - var path = RecordPath(key); - if (File.Exists(path)) - { - File.Delete(path); - } - } - + FlushStageProbe?.Invoke(BeforeManifestCommitStage, pageWriteIndex); WriteManifest(targetCatalog, currentIndex.Values .OrderBy(record => record.Key, StringComparer.Ordinal) .ToArray()); + _manifestUsesImmutablePages = true; + DeleteUnreferencedRecordPages(currentIndex.Values); + _durableCatalog = targetCatalog; _durableIndex = currentIndex; _legacyInlineRecords.Clear(); @@ -367,7 +383,9 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { var started = tracePages ? Stopwatch.GetTimestamp() : 0L; var metadata = records[index]; - var path = RecordPath(metadata.Key); + var path = _manifestUsesImmutablePages + ? ImmutableRecordPath(metadata) + : LegacyRecordPath(metadata.Key); if (!File.Exists(path)) return; var pagePayload = ReadAllBytesShared(path); @@ -607,7 +625,35 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore : ""; } - private string RecordPath(string key) => Path.Combine(_recordDirectory.FullName, $"{HashKey(key)}.msgpack"); + private string LegacyRecordPath(string key) => Path.Combine(_recordDirectory.FullName, $"{HashKey(key)}.msgpack"); + + private string ImmutableRecordPath(CultPersistedRecord metadata) => Path.Combine( + _recordDirectory.FullName, + $"{HashKey(metadata.Key + "\0" + metadata.SchemaId + "\0" + metadata.StoredAt)}.msgpack"); + + private void DeleteUnreferencedRecordPages(IEnumerable durableRecords) + { + var referenced = durableRecords + .Select(ImmutableRecordPath) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + foreach (var file in _recordDirectory.EnumerateFiles("*.msgpack")) + { + if (referenced.Contains(file.FullName)) + continue; + try + { + File.Delete(file.FullName); + } + catch (IOException) + { + // An orphan is harmless: no committed manifest references it. + } + catch (UnauthorizedAccessException) + { + // Cleanup is best effort and never changes the committed generation. + } + } + } private static string HashKey(string key) { @@ -681,6 +727,6 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } - File.Copy(sourcePath, destinationPath, overwrite: true); + File.Replace(sourcePath, destinationPath, null); } } diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index 3910946a..66078f26 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -434,7 +434,7 @@ namespace GameCult.Caching.Tests } var manifest = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); - Assert.That(manifest.FormatVersion, Is.EqualTo("cultcache.store.v2.directory-indexed")); + Assert.That(manifest.FormatVersion, Is.EqualTo("cultcache.store.v3.directory-immutable-pages")); Assert.That(manifest.Records, Has.Length.EqualTo(2)); Assert.That(manifest.Records.All(record => record.Payload.Length == 0), Is.True); @@ -599,7 +599,7 @@ namespace GameCult.Caching.Tests } [Test] - public async Task DirectoryMessagePackBackingStore_Precommits_Catalog_And_Remains_Dirty_When_Record_Write_Fails() + public async Task DirectoryMessagePackBackingStore_Does_Not_Expose_New_Pages_Before_Manifest_Commit() { var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); @@ -624,18 +624,21 @@ namespace GameCult.Caching.Tests alternateDescriptor, new AlternateNamedTestEntry { Name = "new" })); - using (new FileStream(recordPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + store.FlushStageProbe = (stage, _) => { - Assert.That(() => store.PushAll(), Throws.Exception); - } + if (string.Equals(stage, DirectoryMessagePackBackingStore.BeforeManifestCommitStage, StringComparison.Ordinal)) + throw new IOException("injected crash before manifest commit"); + }; + Assert.That(() => store.PushAll(), Throws.TypeOf()); var precommit = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); var alternateSchema = alternateDescriptor.SchemaId; Assert.That(precommit.SchemaCatalog.Select(entry => entry.SchemaId), Does.Contain(durableSchema)); - Assert.That(precommit.SchemaCatalog.Select(entry => entry.SchemaId), Does.Contain(alternateSchema)); + Assert.That(precommit.SchemaCatalog.Select(entry => entry.SchemaId), Does.Not.Contain(alternateSchema)); Assert.That(store.IsDirty, Is.True); Assert.That(CultDocumentMessagePackSerialization.DeserializePersistedRecord(File.ReadAllBytes(recordPath)).SchemaId, Is.EqualTo(durableSchema)); + store.FlushStageProbe = null; store.PushAll(); var compacted = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); @@ -649,6 +652,67 @@ namespace GameCult.Caching.Tests } } + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public async Task DirectoryMessagePackBackingStore_Fault_Before_Manifest_Leaves_Entire_Previous_Generation(int writtenPageCount) + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + var store = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var cache = new CultCache(); + cache.AddBackingStore(store); + var handles = new[] + { + new CultRecordHandle(new CultRecordKey("record:atomic:a")), + new CultRecordHandle(new CultRecordKey("record:atomic:b")), + new CultRecordHandle(new CultRecordKey("record:atomic:c")) + }; + foreach (var handle in handles) + await cache.UpsertAsync(new NamedTestEntry { Name = handle.Key.Value, Value = "old" }, handle); + store.PushAll(); + + foreach (var handle in handles) + await cache.UpsertAsync(new NamedTestEntry { Name = handle.Key.Value, Value = "new" }, handle); + store.FlushStageProbe = (stage, index) => + { + if ((writtenPageCount < handles.Length && + string.Equals(stage, DirectoryMessagePackBackingStore.BeforeRecordPageWriteStage, StringComparison.Ordinal) && + index == writtenPageCount) || + (writtenPageCount == handles.Length && + string.Equals(stage, DirectoryMessagePackBackingStore.BeforeManifestCommitStage, StringComparison.Ordinal))) + throw new IOException($"injected crash after {writtenPageCount} pages"); + }; + Assert.That(() => store.PushAll(), Throws.TypeOf()); + + using (var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true })) + { + foreach (var handle in handles) + Assert.That(reopened.Get(handle.Key)?.Value, Is.EqualTo("old")); + } + + store.FlushStageProbe = null; + store.PushAll(); + using (var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true })) + { + foreach (var handle in handles) + Assert.That(reopened.Get(handle.Key)?.Value, Is.EqualTo("new")); + } + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + [Test] public async Task DirectoryMessagePackBackingStore_PullsOnlyExternalRecordDeltas() { @@ -743,7 +807,7 @@ namespace GameCult.Caching.Tests } [Test] - public async Task DirectoryMessagePackBackingStore_Remains_Dirty_When_Deletion_Fails() + public async Task DirectoryMessagePackBackingStore_Deletion_Commits_While_Locked_Old_Page_Remains_An_Orphan() { var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); @@ -760,18 +824,14 @@ namespace GameCult.Caching.Tests using (new FileStream(recordPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { - Assert.That(() => store.PushAll(), Throws.Exception); + Assert.That(() => store.PushAll(), Throws.Nothing); + Assert.That(File.Exists(recordPath), Is.True); } - Assert.That(File.Exists(recordPath), Is.True); - Assert.That(store.IsDirty, Is.True); - - store.PushAll(); - - Assert.That(File.Exists(recordPath), Is.False); Assert.That(store.IsDirty, Is.False); var compacted = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); Assert.That(compacted.SchemaCatalog, Is.Empty); + Assert.That(compacted.Records, Is.Empty); } finally { diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index cae02b50..b695c7b6 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.50", + "version": "1.0.51", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From 8a203c165fe8abea5a96af4a87b0882392cbef28 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 23:44:51 +0200 Subject: [PATCH 031/203] Publish atomic CultCache Unity package --- scripts/build-unity-package.ps1 | 4 ++++ .../Plugins/GameCult.Caching.MessagePack.dll | Bin 45568 -> 44544 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 23092 -> 20236 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 90624 -> 86016 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 35804 -> 30204 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 7168 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9620 -> 9544 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 16896 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12832 -> 12312 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 758784 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 251464 -> 205548 bytes .../GameCult.Networking.WebSockets.dll | Bin 15360 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 549376 -> 510976 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 284016 -> 246920 bytes .../x86_64/gamecult_mesh_quic_native.dll | Bin 171008 -> 40448 bytes 15 files changed, 4 insertions(+) diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1 index c0e0518f..804f78df 100644 --- a/scripts/build-unity-package.ps1 +++ b/scripts/build-unity-package.ps1 @@ -2,6 +2,7 @@ param( [string] $Configuration = "Release", [string] $OutputDirectory = "artifacts\unity\org.gamecult.cultlib", [string] $NuGetConfig = "", + [switch] $NoRestore, [switch] $UpdateTemplate ) @@ -38,6 +39,7 @@ if (Test-Path -LiteralPath $outputRoot) { $unityPackageVersion = (Get-Content -LiteralPath (Join-Path $templateRoot "package.json") -Raw | ConvertFrom-Json).version $publishArguments = @("publish", $projectPath, "-c", $Configuration, "-o", $publishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") +if ($NoRestore) { $publishArguments += "--no-restore" } if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { $publishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" } @@ -46,6 +48,7 @@ if ($LASTEXITCODE -ne 0) { throw "CultLib publish failed with exit code $LASTEXITCODE" } $webSocketPublishArguments = @("publish", $webSocketProjectPath, "-c", $Configuration, "-f", "netstandard2.1", "-o", $webSocketPublishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") +if ($NoRestore) { $webSocketPublishArguments += "--no-restore" } if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { $webSocketPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" } @@ -54,6 +57,7 @@ if ($LASTEXITCODE -ne 0) { throw "CultLib WebSocket publish failed with exit code $LASTEXITCODE" } $quicPublishArguments = @("publish", $quicProjectPath, "-c", $Configuration, "-o", $quicPublishRoot, "-p:CultLibPackageVersion=$unityPackageVersion") +if ($NoRestore) { $quicPublishArguments += "--no-restore" } if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) { $quicPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))" } diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index 2fdc1ff90934f068ecad258a10f1b229a7f08b48..dd41e76d91054b0af3171d5e1cbd967af00caa14 100644 GIT binary patch literal 44544 zcmeIbd0#OfoZJLI?>HLc*90!Xk@+gs{beED4)Z!;lOxn&gI=2^wQC zacfny#igxQE8x;%ZL6);+Ez=dtzt{-_O;)NBDl1*wX5y7)mnW&&vWjbB?R?--`{_~ zH<0H(+d1bs&w0*%XR`8~&kIcmVZ-l-9}4k3Jn3g4!;c1o5c?*4-zOe)9V!2wvf@a2 z>yCIb+>giqf8azE^wTZ`{T#)4CD7WeuXGtT$OecgY4&@up$Kb^rML_SCT6I&pb_% zSorBezN-j1F2oDzOo(CqD8dVL#gBz(wOF6z8BUwd+D_2Rv(nY4Vi%=AKXoSnWL#I% zZczLa7Gg_%BAI9h#kL&>qT}}-{AT?uM03?AVx0yQ$yOmuAEZ6XOtDaiMH0)v{ir|v zkZr24iUm7Womy&2zND}Mf6;+M#kc)Y6HYpZ3D~|A~;nOjmR99`n6tE zyu_{Q^)vL^DUo7QelF~N^Pl1HY3tVAeNI+# zqZA4oO?D==QAX@)vcVy?a=nGWFab+?#$FeINtGd4ygMK&ciLgv9s+f?s=^x22O+|Z z3CQ9=Ws$6;C!kdBM7xyiAz+9^ z@TX0{9~dcw-)=NO9#Mp-(el;EbP#Z7WCosgV$ zW)+Ao0uR`#Ct|o@1Ps(vG8h5FXE7K7Bi~{$0tSYAnS>EA{1$@|Fwo1T1|whuEe0cC z6j}^Mz<>{A5=Q2#MFV7z>8Z1|I!6)>pcg2rRuq{A48a$is){q=D~tqw_+>7tI)K2m z1%x^P&*G^KAQ7Hp2I}~Ei4;dvxHKS`ip(PqaesOSd7+2<)yRDCY6}uCBb*Z=uYCDak zr0ryFJ>C4lsk%Ve(vXsrwn!QDH&kEMnI@gAi7kQFI+5 z0He2Vd z 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
    WaRTL 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 htyu|8gzx{{RagN38$= literal 90624 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb index 345abea559693d74ce8f34c8d68bc32309d48dc7..978cd4cbe7f222da256c5032c96cc7734ade4931 100644 GIT binary patch delta 15933 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 M-Vy!+wB)P*14?DmWdHyG literal 35804 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 delta 1520 zcmZXUU2GIp6vzMf&h%rqb=_^tw%A14uBGd?ZoBEU?x2F9ZTP4{L)%c&CZs!U!9b8K z7zI+cSOOKR1uh9uXd*3=pfQ+i=!+UKCZ;|ZT76LC2PP0+w29Hg@IV^>Go5Z0oZWlw z`JInBXV2{15o08NdaUy1y~B6mUcaNNqChkNMKt6u85hr7-7Y>>Le%73jsjh(Qr4** zqQh8pgUci2Qp$*-eTDfab$iwKUutQIjb!oXA(T)s_l9Cx3BfXg++jllYf;8pOJ@JS1ldik; zHc8?Km)du0erR~$;v}6q>iOW7%jY1TAwLJP$)RH?!3qv|pF>L}@-gg^UBZKLm)cpu ziwkONZkzd0R=gjdWW@*YX;yp@(k|H@6=Np-ZEl`Ot-*V+XYvh%z7ALMlh%rP{G+`D zAKU3gHD9q|{;?u>OGZ(EcM)asdCGFkP=;`WvKDzFibj-DK8GsG7PL{e;}B&hPEz*b zb4tsRd+?oT!2x(uvW36IBio!ZmTi)jW3g}qJ~`;vM;-g9V;`lxNlrVKX~!~6OP~A- zRT!nT+r8uHcN|?4wmsxZ+uIC%#CcB;gPO}P)3%{Jy^#O2?(Z4vyH&Q>vti=Kl|7fv zP5q^Qe5_g5@`O}AUrOc)HR0>?cm;A=2NSRDQvdq;O~Z`x7%~LvW6_4FVb)qP(?|>$ zmK8Uej3(1;i1!aAjJVmHFk)tGV5n&@W|_=Wryu{N6ezgZ^*6-J9sCnxoa;W1uKVhk zzclAK&!*#Q(jW2nb&DX}v+ TCGt2w9xX`s2R{&MB-ryG05%cn diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb index fa1b1cb445516cd68826f515bb6b522c4d3329cf..5aaeb6b14f96eadbd27ea9be13628aac9360ab68 100644 GIT binary patch delta 1278 zcmbQ@eZp&kN__wW1A}rvN)nJ_I0M8l7=ipc28Jz63=AwFx!{tb%)Insko*pqSZFZA z9w55_B<~*T=>nv6m>C#klz@Dvoctsp|AY4Avm7PQJoXDm-j6g46A@Hy+YJ(oOnTlk!|8nX9Mw zGBI2O;zvOI5{N$l@prHsNFD~58U86UGq5T#Gw=YhFwj8^3~WG;FoG>)U=RfI*^&6N zKt2Z=pA*Kf2TFnza09i#4B!Lud64)Z8+eiUyg)u58lNAHFM!4uW#EFF4+MIGq-+dz_?!H|6#P%f8o8<0$7VrFDv z&`7G3U&Lpk zyIx_F+81WO#(mM85wrH6eOqF3rSXh8D+9yYDYvuk7znhzXRd5dTpxAohTpBFuT?}Y z>2kKRc$wXen-bUabfe9J=84C6>z3?uo4@A7ZM}PP3CydlJ&o_%|1l1lH(#biR{p7F zOp^XTm&uD8oW9=vSZ}bleVVdHys%ADOD;nQvqHyrHSdMDKcsg11+#oq(A=EpoKVQn zTC0+E8@5vMN%N7bqFnaTRP{>gg?=+b58rZRd3;^>Z24ju%YJ3roCYI_8KGR#YpcyJ zz_Eh82%3~Gam-*3a`ka_40hd|&*>wbE(%PWdIG%c4C+h_0t})I0jvy+Oqw83ZE>Ke z21GOnB&sd|6t$M)t=AAhH^kO$Hl20vq9Bhs$6FhN4qsfX;na(Q*L~ag( z7{owGEdmZqNz(%7t8u;7_noar&6Cx~ZtwbfQTg`2pp2qi3F1J6#~^2fN@_eyy1ZAi zfufSfj+`5aE~Vp%tTVA8>UdF(AY?Fc^AUtGjEh!-`L#dR=RgA+-&q_ zOtWl)6115~Mo;Dz#(W{yn9Y1_m_KamMtyjs@5acOd3NU3({+2FSKhr{YC7K7y*|4= z?AS|*Q-9%2KX{|dr!2eg%6LUnjGRw2^|3<(?e`>qx;ymRa*CLt{+75?amPj**1ClS zJFoh?WX?Ii@vP;*-Z_T!(nEd4?5*_R9hGNXGAk-al{>XQ|MZ-wDG2;!*kbsh`Q7Em ztMWHKI>KK<)C{+yGyY+{A0M<-`8sS^yQ%KdpnY>dL8r?8YM+YPONyPJ|ENOOxRnF3 zYG!{3zGA0H-ZgcTPh5JkOzB^~Jbzyol9AgRa)9OkzQb;x%CshSog`T^B|Z7nw{z3w z#_wj&9G;xGKRDFta_zTJ_p1|zqPU(|`=4>q^yJv*-40Pr;}NARnKt0 zxpd8rK1=FAuPN~0!$FfNCxgu$UckTQk>iWcV_Ttuo@M2;6uvk$s-K8D(IQM{W|3TKpz;F4Zg{xg- z4mzn=Yw~QHg3o<=KPNT1EG}WFb+*3t$>Yd@7c;HDuktYSW=?0V8|JEP9AoE;Q%RYr zyr9KNXPP*KFr(l?o=BgjGwRt{Qn^y0)hl$m0EM5zPpOmzXcD!G0HwcHAy>-viGB%k zol>Jz_;`u(^AATFIn)KNo?b|j^pwmDV;l8_P0+@`$dINe&X(l{I4uyi<6t-lC<=)Q z;v)f4vk)epuPjHfR??QoYb%_##7?l(u?tvh2*pRRq&-kJ9}G+gPXHZdd=BheI5E&t zLeW_cCn6_ff5IN==B+GTor0@J=-rH$v7yIKLWzvr_uPCJ(Iv5j-tWv;xtHv1E|I>? JWv_D>{xAIXb;tk! diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index a7c40f955087f0026f13a9fdb40fc33d16844230..2e4576da8dffebd0e8db19c1c84be958510744d0 100644 GIT binary patch delta 7178 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

    3=q8ocktdq+5#j3?=(jVY?N zqF?O*f^>Luh+*rJ@=FxSgLMeOU>!n;R_m|`ws`Vow)Oxiji0-W0BP-JJuSsn#dwT? z)MhMZ>==#cnJqM6!#UMiWe^M|fyUJF=7Oa3!fOV|!akVFL6yK-E|Ek#6Ig=y!2@dA zr-tF=+zx`YY91g?-26#TtK~NXs#^d)$x?9*P?=X5#pZ^UJIxKN+CCir@Xi}W#BouJ zb0PrTT@sKqzvVm+vfMp;fv5KDW0*B(B5~^>!L)_+%Pa=UaA7<) z^HKxn4+KUq^{CHdWv$Sv8w|wCu_9oofgz=wR*eLS!#J*p^B3#Q&GYqgWvw8m0@S-Xygs_(U?XTeB~YtD`L@a|P*?s;&C!O0C+cbZ}6vQ);YMORaVT6s_9&daD5yTYK$JHMmw-Ep^JJ zdaGVuuh#49#dfQ@T3anad01Mlt#nHD2DE~;()#LJWd&)~R*SV(t+CRq)|$n&wNeAs zW8FWl_<7ymXt6xOm*4$eaut%q?Z~r=DNYQ%+=$VM6U50z$AC}*&)tF;3Vo_+*=Z4q zbuspGkPT;Zh+jW4NKKR zZMD5#T(7O7{p-a}tx+vk*ISh}c82D9rCu$q*D9;6m0G#l1WB!0FBi+rX0g7~EEbDx zB#=5+!65;l6+ScpN@763+RkdLHFl0^BvQSABUo3AulJS02vqz$z zS#7OWo7Ku%rCO-36jzHS7*5tkS-1E3K>+JFVv0%1XIdFGIWDS!s1Tbqvi; zx!G7>gQC09fY!Tuhv^ERXwyG@`TgHW!2H6OJDDnqr+J`}bSgs-OZQH9TEK5X$H_CQ z=<)3CmO-3){u&bFa|d&bwjPjB7z1m)A73EmQocEXl6e-^ZU@mIK9PpT1B%0+VT$&xs(wJaZi|cFE z)pc12u5ERYi%qHUSs%!41#lEbi%t>K24jMgpP(eckSM>|F{!!~*Mb!D8X1!+NWr8h z*lvKd!?CX+U~Qn=sjyv0bq7!+B~Ay>{$ct0)&Uk3F-Uh2fZaR^uZ@B2fG4Q=sm#@* zPqG9YW)rfSNsgdaXtXQU;!3qtX|B{^Ki#TUOBn67$~sJ~+vRd?rB=b(SYIW)j+wRD zT3>B;inTJ9(H8JbIB76W7>Y+lC{8n{G@`Xar2=MQt=4K{602a%ZFNe`W~WkbH84+= zS6Y?zDz@anSD*-u)zu~@s`X}VWvx^#uQV&`wL45b$oQ=MNr#Z4zKxyrP6At(yDVSvbwf{{mV*a1uI1xIbvh8SSgnqg;EX4m7D8e zgc_x02Zy9l#pPxR%ojMjMs=mOS}Cp7tCc2JQ?Pie#cHimtrT0>7%k$$f&-?Pt6a4KmJsFW)UY$##=dX6R781e<<&K;mTlapfUaIx1B+U! zphK^8s@Tq5!GzUD^{sZmIaILjwTiWNtFhML2w1BkHI%+nLm3+w0O+IzEUj&9@^)Ye zSL4^#YOVDWIMQaj)dZW|Dppa(a=TcnHp=yK9b*dLR%|z#>m2|at-=~O=sNbXi)-yF zSej<5RYfmYt#yj6Y8}M2PPw(VUPFzynk$`Jt6E;i09@m+Y2z-4Rsp^?N)1bwNtHQH@jBs6guTjbp=#ljn|8n zVg=)D1>5H9s8liz#ddkE+9;KI4MnkCU#YIIp>*|nyN+)v)mA%&I=FnW=**AJi`45Y z-WoMiu7G*yl)z)7Kdqr+*DIy<4mh=HtKE^MpuXAM*uc2GT-ZFCLS1oY z<8tA3g)PCoL0l;RMHo{P9q2dTP{4LxHwR1^{biL+cV+gT# zGXKP}?ASNl?-fFWB^>6$K_`VjHJt4ZCcRs|y)ccd(I|>Rp-GWZUG3d+VyTt;q8tIU z1EHE-d_dIi5@iqkS%Mo=$gFbk*s0kJVH#`;1)l-SMpqavRR^;e0OM<6NxWUxkx_R0 z-qvf{@W6q)F2aK}0ap?ao@$*nQPN4T&~CP-y}~t|96m%(9PrFqfwHQ2R6`|Rb~6}M z^#&wh`}@GYgOUWg7T}Sy>b)<~0&mz?s8faa1CuYs3?vrgo2eZ;#`(i-sc{?>+Z^s6 z>MnW;k3Gi1#oqN{-wmW#3qL(`4(*T3rT>B-9K%x3Lakx%`e+P3yYJi7?fm7SY~L>j z6Yl@zp#64{r1$u8NGx}=Ovxh%OwQ(^gVTyRB$rSzxXc36K=k@ybSsAHPUeHx0E%GuS60&z;^j?fziJs3`{f-7wdqO{&5Z7b@UAUaw7) zuZz_hh13R;t0&hInwqQP_)NMald%0R+(9UCxP+wOfNmO2B1Lf+OwviL6$o?G)IQiC zuG~p6&wbS#Xr8*G5b(>Wzwo{ntuw8e_U)*T8;UgGx7|=Xv6`DzC5Ep`WF1-qp$<|z zwj+u~4A%z~6W_drbY*DRmj{Y8hjEqR#dwE6l!Q7>x^H~T7pm)yCXoCh4BSmIg3@BW zpjN#QQsi0D7i8^^=WtCI)95G-vyNup?R0SI?v#7>mZKALnhjnbHLbXl3b3QU_}HWz zSx9M`F}ZpDUZH*~bDaTx%H^fr7jLJ^4vCEJ&O*VCNFsdhjwtbw1tJ#XiwD!?})wl-0+Nj4%ms)QM z%oC;2L6b^EYlN-7-R5OcgHtC8nRj|bdz-5drA<_yg4q3%L|VEeOd@7=8-CW&(H0lc zHAj$b5|kM$>7z1eC>Y-5oS`XXH(Yc{(Y&r5!0^&4KiEpNrNa_cQeBwNgHzHlYSPLp zcmE|f*L3bmssG}okPMdB3dZ2@`m%!Uh)&Uj(hWm(kc`I!fZdYIj}|FU@8=RsY_a0C z^nL-|ZxiPfx?*F;sp#5F8N~#Ed~O0j7WHa0!wnYvcE%w|5;95P8AF$mL4D>TBmkt) ziVi3e08+SX9|D>bD56vc9gI<&voEAhlcJJ=CATDqpOB(*k+o98xJd}3CLv7UR>KcSeFLmm&UhT{hD{AkSF7F!Z@siBTjDQin*r0 zDJ6lTaitP-9LAS&#PBeY@EBW6k++x;Taugv>k^SJ9OE1~Q9z9rqSDzWDVSUtS-JXE z>2YHq@{kX(UVVVI?&LZTbn=}Cpyp-M&I5LEZx1ccJm4mz2p>THPWH6SISqI;3y=he zd}^f1?jk@kA2<*u*hrds7|ImUTe}lX3qU=2cCXuoof~%pJ&+$rlTYAx^vBhXDJNW- zs;WSU`6h9qC^3xpQ+Z->O;#d>8jL!)n!CH0K}gKt0e5BLLI+uFV}zX_biP#l7%FX- zp~dW;7h9|bUFJUEJggW>97bSXLUHZ|!cu@s07!As8ULGJKyE5ULOO@JRV}9KdTU4N zidSKO4wF91ISw&tm6BqZe6f@{hN+q(Lz$v-7QS&jrq7G*VhB@DLZkvr6&KSH3eILq zZY%yj&QRt#p;9B{Yimb;j=JMoo#&ejE) ze^BX6-1(WJ^E*19gF_@rIG`_x1MEe(e+hZIv_*EaGB`g|a(-r|-OWLWO+pT& ze>!~kCdENZ(sU*W;iN7v$a8Q#WQY)xvm=LwgGOdHhJwwILD2P}0CW%&v#2TzF{VgG zmSPi!MsBpAeHxAc=_f+2B2-Y>)I%gb9~TYO~MXuN(3 zVK;||u(7*c)urhz(gDD?n|ZonV-XDi)SzAchJch@buSRNj9l)N41uJ|05PbjvWcUa zYDFxGky3XWSQcB#XrRGiKblz1K@eqj7+-TXy5qQ_caP(`AmTbN+nQ^yBT}zGEd#^4 z{47WcH*XloO&S3O^-^*zF_Iq#{(~SJJzxrg1Mg$vrVU`hz#8#X5WSWy>v7Dm)v5T2 z)9ZDmmCQ?S5E=~&k;ch>h|YEjf<2Q8EhDrKyM@sOArF`2rbxx5Wq!i3NZ=B#7#D_# zcifLf4TARR0c2nbanPQ#Ij=n$NQD8!)Pf*CDhR^b*b%z8q5?Qzp&2y}F=iYhPA+%j zm2FnaFx=%SDoca9R(Q!Q8H9Y0MKZ7BwJf-;1k43{LKf>X&b?`fWr;&$4<|i_f%M;) zG6}ii(-W;W(uQ<7VIIM~dD@qHa;qD){C1I_ijkDZ2$WxPVZPpNcST}5Kg;U;EgU!) zL@v}Gg&~o@cr=s8@*Ihqi~Vfn5d2aMG4^!_Vn!(tse-8{q|5>Hd$LZA0(=UU9%DH?&lo~o*)UlJW7VCD0C4H0{`WagZ3@CE=;X?ppQL7Zmz&0EXOhn zC>yR)+?aa6Iox+R^C3)2N(WsmY)mK$(2QUpdQF}~L6Qy_!f+runsEIDeq(wy;*F{{ zz4b;79NuMWfS?aM<>s>=hKc zoQ^?*;>*SK*fqnMvjN*C4PqlwH~TBB$@ZW5awLYia0cX5cG%4n$Dfke6*akdA9A8exxA@)5kX`=R&(FmaR z&kTHHikWB@Cj&80w^jJj<`bPNTBWxWp(8r|h2*+1jlI(x^?alzfT(N?8pleXNTRcT)L)ar9n`pXSwY;CJ|Ub!$(i_F^!mpO;3J% z8qm3gH~`6(V-UtF7KGk#_C~jB+H@=48?zvru~qYsMLWeu79*R~S+FX#q-C*JoyDX2 zbk8}9iZ@TQIB5)1NoKJLU1JsBGU4>>5Ofyknq5+RF8C=&9}34KA9AfXb-qCGF2`>BDnpN$%d z{#k6mQi~j)rAP|(Sv(dAliPD~CYR^nL}bo^C&ctD-gQcq%Cq=DM9(7a_NTmBjOFue zG-IA9m1mJEiD%)l9G=B_**l9e5j%?rTzoJT=F)KS$)@a?;w;LoyIG7p8jzU^Z57K$ zwmFy}N9AMX!kv5+<;HvrUR@jR=c9c4=c7d^n_h_~n{p#T_5Z~TcBbBWSJiy%h2D0Q z6Up}-u5CGZr$NeQrxWznc$^ap@0^O&`_?Q68EAHL@fg!XoC_cCXY&+}W(U_2XHl0Y z*YU&*U_<@kT$1FAlbJMl^aU3XX5gF)auK>x&P98!Z7#)y-VtWX`RL*H z1oxB8#XD8KUJINpI27*wjCAI!Vc&!hWj|sw~vNzo0F|} z#8^tsCSkg@+}5}5$l#9G(U z@OB=7r0~L2Fz)4DF)v7n`orEj6zaXByxb@szW~W)x!Ba6Ufu%3d@1L%+$ZPZ7zc)R z`v-Z*STUcIs#7KBprp&qhS=Y`1Ci;7bE1=(#`U3PoiOCNAE5 z8n=$-CSc6M;L6_Nb=ooIOay%1Oj+Z|9(#nuDr>9JFYOX>!bt zJjAPhdW&HWWwMjrk<^s;If-anNW6DKvHl*y67YP$LPTIr5;C+9W z`6q_}RiHc2IXD#YD$d)5ugYgu$A;Pwi1kKJ5CTF)k#mVJb`+BI8JjWFt1O+idl&=TuXB zM4Jar$DA5zB@J;XhfdPYh0A*oc^FE5d2o3-GzVdPb`HifeK|Os-JEdEQr<6zAdnSu zu(ICg9$(HOjv2%}Bu#DH9p9PW;?AnRAb~O zdGA2vb%=MM@#;b^Gv?&B=?rS_M1-0|6WXQKGF=##o0lL)D{dCfd~xcadkzI9&&dEg zNjW$tAArfoCM}y>FwM!sd4?k!O;w+IQFj)5Wq3QfIc}pb$)g zoWst~*c~Ox7Rhc{XbmTL!AZ}9!A#KUFYgpjE?vnccm>RL7D}Vte2kyDvXQb=V3x+U zNfu+1To%DoxmhSnn2*`w1uxkM8`$mTy%H27rn|ix5yfL(ub$}=7?yJ5-&_LEM!g_q zXz%fY#I~r<%~pmHo+il@7^gvDHkO66hYA~;McJ0<25+(48Q#tk$mW2kFS0n%T8icQ z7+0wrWeI4wkcHBeF;jNG4rNg|aqqgz9jH*PqD{z69Riv=3y@78Jpw!fc%{C)>5?g$ zSViO}D;xcrhp>^ui!b@{vS3sult+0HJb#gmBwVbhGVVa64~bP_gH@N8Zl+KFpkgw3Xa+xo_n z+{iGrILybiajAN?FnEP&4<{A)#-6*Z$%!K2AlD-X1@tUTGrXT5!Vl-F0*#i%RGDfW zF}N0wJKz!P;{=RdQ#rOzRtzZ#Ol+isu%7pb@h&OT<@h+x3Pb={#J$;z6h{v`2#yJ0 zlgxcr3WF(QHx}Gd$edRzB}rHgPQ}E=-#_I#geW9@8HE#&(1bhB9`1~7e00D5cnrFT z*y9wiCn)iJJ;>oipOm-Gk;;!ltX7|@P_tr_5SJt;wj_ql+BlME8+;+!{4RoUT?* z$eE4{*lz3}r7Z(fMVst4kAfbkTW+>6E2FY0-+c(#i( z6MV$=^5m)AAq*if2}T9En#}igxPuxb}Ofo?=Tw-8@sch=bs0S?RB^>A=&puz@N_1^AJqoTDIE zOlMmr~WXbd06CbXy6SoOc(uJp9aOAwR`SJLh4rrISB@%1j({c5ZDuc!2!gI_a3V^9IpIR*u38pJ(-HfAByyTZ1$;-6pHik-=wKBi9JB=ZXs%v&vWc^D zvm*hryxf=ab7Wg&KF%+_?F2#s`y?GXdf{PcdPZJN&OL^)sYoKueMcTn%w;gD9j!~p zg)zrV8r>`bZ8YUX>RX|!9y({MHodWXNZJdI~OX94R3 z)HIEVA7(5!kH(fNQ&+0|9HxWj^(%Bbjq|e^_rgWy4&%VTD-1(m8kxrX-Xw%+tW_Fv z9y!$3*!irw#ULSs!N02#mz+dCKvSjBajjympK?ZN;n(U*Ot6k(->RxU7+mF9ZN0dR zFWhR#j_7I#%NQ!*CN_tYB8hPix!w;3NIa`y^QPdGW^2E5`c)@ z2`HD>ERboi#9=XQ^N-1KI%$n@$4Ugz!8|CW`I57k)SBG!aRI8> z{!t&1x(AqZ;^4WaZtP2KnAeWs$qCm08<@}IRx5BAhI1dK3!4a4$%MF=2I+*B_63-5 zV^J|IzJs*1cub|uAs=unhE?07ZEROYYIh~1ZvRuEUi|IL^;0*u4{*B zum|z?^<$m~hli!5DTZ`r?@?w*gQBMsFSr-^oshmel^c5{0iH{c1jHj1fKj#I^Ke}q zCAcx7P!ynJFC%iBaesU7=yivfRG_{YNF=it_sMr4f=NKO7?sb&C(2Q6fn&nx?Te2r zSAFj~6-v7b5ge`@oCx0TR2TobXr?eIGj5AdgjRiM8Fl9_4Ke`8&Bu~+OxhU@sf#c? z2ypHTU5?llfMAAKs0A=$RWdml5R#DkRE$%9Tdp8czZ*sv;Bs_8p=L&-0hM6Xze8n( zWFT#Tc0@Jfb_W12AM~&w*(fexlg4@3=`W8gmK_^`J)IOzdy1SgVdvV$qv)DCvbl(< zl;I7zKTtFV4zvkP8K++tmbuskF7_rju%pKZ)(iRsl=d?06c@lw!*wj1(=>x?KOm2; zmypCF26P5^niyQX{IFWFINBGC1rdgfw@~T9^C)r!1LjZJWXGh=ZMdiqIS&)%>?5@y z9%=~WUU>;!8$n_KpD~aSsma9pfICwo(S3kq_(r_C%#?3>-^SD_11|3f>RbeiGQ5hn zcZ3PSCwd7osjb~)Vg>GohXk8 z`&dIR879L}2a-!8a&j&vSVyoafSSssir;$3MVmrJUVg|WFy=QG7rqgZAmAOtYCnYKvfloN(k^~lp-Gp&v_h<@!e9xr^i^MqK%5L4&(GVPpI1b*$M1wn)z|c-+4z?2%-XF@- z-r|nvxjSECwCN>d0IS;d^B79rKL_g5Wh~w&1GXHG2%^xeMQmxwud0CXL{h1=g^84? z7*uwkYtyKp%J~NLZc@3pJBccY4Lsg4fnZ@!Cj~h<$A`NEd%-~>%go?sHd(C6v*&_& zUM5ERe&Ph&oH(*?lLObYH}kMeFu<|aG(s{Z<#9=zgf|X%s5zF`_L3OCxGK22N8Kgn z5&eb2T|HY)lr<%`4=T@_3Z|4DU>!VxNl8zOx?47>EwYLh}gK3%GbU;&hS ztqS}IZ)5CwR&2;gwqNWdlQft^cl|ibqWnA)qxwEa$8xzb$q&=(d`sCN+! z6y1up53j>isDUfRL|l&JquJx*w0M01x4tRT)woSx3Ups+3tf`Z*NuXoJX`9j0s&o< z%z#K%;$dHtLBk5MB~rsK0}XZc$qfx~Xra~(a|^;sRNx>%e^s3buiPTc#3Rf+^43v( z2K()8h%1FQUWDWYeKBNX>Uqx?XaeBGPML-D*Qm5X#ZrAh6>}WP`>s)A=0o^&Rz2xs zm{V)SkW2%`DEB#A!&#UHn}^a+NQgshQuQVz#HbDMtUWA=`B*GwkXG^hiLWR%K9mWT zt9Fdo7{z{9=JP|;X#j8EhUE$Q=9tud1NjQh2)Rf|y*P9!8=Sqp43#r$zy|C-H{SA+ z$B^C`;sBSVkwzSp2T&i=U zhA6fer87&^AQu|-ZHph#6vaVrN2GIa#G{4LzW4if!4mt(y`TN49+!I%N z7;xY-THJ9x#Pm1P-z`kC2r~EB7vFTC8ps#}?T`kbi8CLgp0n!@IE8yZ5K24H0i=N~ z*GDX!n?C=#JgLNrb{O#?R)l z8et=R;5<4K^lF5=!6yn48;B8>ZP^VM0yvj<3f%XW9t%Gcw)-0j2bkkAJWWAPpIa_r z+h==4F)Z9sW8a-Ig0wJhIhDB#gKiwt`_@%(Yrh9JvC!G;UH922x2^h5WrU&JaQF2G zc5zn;Hik_%6$#kFqS-j&dGAoI|w0AW-Ch83^P*^p33>XYS;m`#?I&2?LcOB zeZbMk0!Hn+vYZa=qdNXGO|Y#7HP#$Qdw7#hbbb5`k!ke0UJpA+h0kHeWZq{ePco9; zC^3k5!^gF=uRnB8D9!X>G`L&h)WR8Dfu4@A4>g_szp8gEGgePZ;n0Khjp%>g*9AJ6cw_)Animr-g zlMGb26n(RUIF+GU1Uaxym%gitl;UwV|K(#@U#vdJe{!e4lXryq&{$>d=w+Z|E60VUR|R1$9XHY(h07ri=K8Or?e?u$d|SD{Tfq9n zA7;uyJrE-uZH71~MG7xy(XO6=IFlh=z_I(ydtP|Szg)bhA*SOSML|8cc4#n?SjxAH zG@x&6Va8GeCukSIqak6bb{~?+KE!(P0m_CFqkBt#p9lr z(69tVv6Tr7KgeK9YNRDID*=jI8drj-`jPY_30)$$xrcdPLaZ5?xFy6Uk3)T4)f-c} z3u~mHVs7bi{>B=?#UU$244B}y?5RW}mo&lh=hGknGL3|#96{L92}v-tJ^3!{)6ZfQ zW&MJIshdI6i0yko6={_(7V!nS&lpCQGi?!Cp~^>yhqZ$S7m-o`H^TtcRQhnCn{_Ni zyTb!2v>evv1=;_UbYv@8eE25LR}m~alb2sB2#f{5kATz?2%JOUUgli5_f zveIIuv-w}F?7$-JS@Bvh$I%|3K2|skk=0UM#s))R66BeM%M*qU@i*RnOWXx*jXe{{ z9R(f(!OW=-PRZv(PY9K)0Y<7R_F?R*QS$I%XjtW`D*xVzB*7zK;{z&cQC%n$%#-PJ zR<9cD>)ZhOPr{<+q0A}POmdtY;{z&K!ZgT_mdlrHtTWYMlJDSj-$3!6Z_%Jz5p2zY zV)%w1ipohO=_DmTGb^s!muON3PF&FqHKpRXj;-pU{*RNU=#@fAbWUJ?M^GBi^V1q~ zOIBn1%EV5@XJMbSF{qVt>7| z3g#51T?hXn#>Hr~-k#JVil6~(gHCq=#Iz+`yp$!l+Nu!6*IWtC^v~eRXh_YNV8Fr5 zQ${}{7h4hNj8)rj#TI*Ee9%27Y{g^?wg{Ie9(nEzAZ?6BNE|!K+@!Vn!hqH`ZYRXi;(B|Hv<3DVaYbJ14K5A$^xa+YKnkhv?G?_! zw3$DSF=ziiGXR8j0B%N17X0XO%L6yyJQ7@vQZb6|xAW34T@++9TY}he#bOIM$%6aC zgf0vdZ2kuW3r8C1#`hg_!%h2a>x zF}=Kb1@<<$*X_1Cn7^N^h9J!Za z0*KNjZrGE)$@6)71W#kCT$hG+XOb*3qU4MY1E-{mtT2xf)@Hzo%@XZjNmb73u-T@SLOtQPxAVFoflMnFWFKAWNOk> zqBC$1X{!Xp764s59GM;TP6G9yRT4H$q!ox2qIPh}u^f532&@Fm{C!LY(k&c4OR0K% z2Vn{_DN$%~K$2yE-I?30cVvv4xHVDgC<%e_8)`HWBXNutM%{4|)MWO$#VVB!Jw>2>{+`D}bRmQyl(7*LF5uJj#kQ&jBEcagZov zG<);XDObBdx9JgS_(A+)W4KqzK|@m1o1Flnno#$x0@&6#2n=Id_B7Gy`5tYvK-kG; zjhv*&Tze>6;dYv3o)GyT8@0Woa86B3r-k}xh>4x#rUng0n=}`6g^)Fsq&o-#EVAv7 zF*=7-5R3)2K>&4ymFX;i3u}Vz8-S!nIQ<4N<{?vZ0E1YbB!V?U1Pua>cmgse=Q!E` z>?M)_<_gL6GXP(Q@u#+@)QfLj`SLh~P9{2!7(zXG9CDu;LxNtXyXrBDJt_`yO~4V> zNel_1!a^;EqmR(&Edk@)KbZ7xg;cP}i-Tj=Um=q!tFIZ_M~8h#R@Vep7A3H>)=|L0 zP349$=~~*BFlKNa5%A-Dj~=pK2ysp0?Gi%dSY9x<0t99T(Em^X zll-{l>Hmd3da za&D<%gz=N`<902g9ed1G7i$rTVj)s9O9RpQ@O+%N=%x&J4v+H105BykRco5~Xq;a# zG1tmI1Pa5?M90vUf-xRnhGQW){Lyv+L@2$s29Kr1esJutiInSfCBcoU47*`pmjmqF z54nwpWTgl(p>8Sy&WXVXct*>V7l@;30i2P>NgC+Cm?@#M}-3~Wx0aF_}Y z>mBS4`!U!L799-HAV$S!B!C;?MsOaqz`k=#p;A3{=QtY~z;QC(U$zxf3(IC_=3^)r zaCeYC2G%Fn#m4J0rhpxA;n#U;$iB6H{W{o1OsxUP$f;S1p^!2z@8}{D16}X5)jUR6 zrTO}ZF)oUl?&J{68!~YcI4`6s3%r=8XEkG-GIwzpJ$MtRj^gjNkBcF8M@!>NKzIc#1TEh8ciE}6->ThAL><95_C6`UgDldcuqChe&M`69k+ z8kU3-OQlH;J{XxY888)?3w94@13c4NxG|#3X>Mx_CHBD)ewdM5OpP~paXmF;CtO}a zCZwRFU%VU);$f4(G*$e*McsCkC)FuZf@<$0Ue7j4$R9vaoL=mDBmovlF2yeNcr|w6 zTcgPsk}ke+|6K3DWL|7zK-#;78N!%!PLwdrfQo%{7J=%<@BsP;F@9hd1b4cBAYw~| z?nKpg0k&`)rs8LwYNG`W*n!uC2Oy~VK_pn%TO)%b@B;g1uzH^1jK@dJF|F{3fu0aA z(Bj#X;oSRMVH1%1e&k!a-7Qp(Js;^qqAjKZ>TD7e2jhEAJ%!WXO=Bv|EV7{xL&W|f z4)We2mjKm3lY(!Y%?Le-c5dKECel~5L49}BzDD(xA-aCCo#tX}DCkclF=xT0fE3L83gA$K|1;lF?j&o&Y= zfy(3K?FZcs^awXC*|<%|OQAVA`KSjj^ZJy75W0FDWO7#!%BbVlg|PqQye?5cpG&T{ zXKDQF_&jwA_vG1Gx4X^Th>!ylBEu)_mFVG6Ew`{q4jjh-w_<_;<*&}OX$LEUQYZ_; zF*lEdY`4(T(TihJ=hnfoLG0G_tXKgA?FV@}%wTTLg1~ohe6sup@)1Z9y&~AxQOocG zEdb36-7NN4EKom>YCu)E@U|F|%Vr-okA?NYj=k?U*8Hl%kHP4{xF}jx%?`&63TXMH zal`^3NJiIwYTv)(iB=5)JLW4?=j$cJkJ~art zYP7b*ICT#w6sVO)x;P{aS01^ZKs75OsJ~JnG($ZHEg#Jv_*TOCXb-ZSj3=ER5eS!N z1pBf^!=s#ReFxFl)x_FX_x$8kHK0h@dHFpuXeXlk;PFyOO*bQh1(TpbVgbMas+6&S zC|!|*@H00fGtt$#0ZYCV36t!|DU}R{{RTvmx`ob_els|?i;9t+OwBIHnE<=wxa-2m zm9N@{QBeRS6^}n*DM`Sg77xipA7trtWnDx5emt;udpW5-z}XsLT`R{!Ese1)B*spo zXzbv|bBKo_yd>TjLVlBr2auD~5EMEQCRBVJwz6@puHQ*a#huICWB zg%hI)wn=gfGnvHj%K;IGf&>Y9pB3VyGKN zX(Nj)MVPv-JU(R1Osu9%VU4$$hi7`W#$h!OE=7*MdRSsXf?=jdxJsAW3lVo>1$?unD%GcI zq6pRn586ik`vA-9IXe%f-XaTiCCp-|XOR;!vQ#Uxoy&py@{)OmR1f9zb$hYNco<_# zEDaXKvnmE-k)0%EAeIaZYd$n?sz8N2Ct{%5aTH!SZ~*H8@tO1#LbpIgoY3)Oc$OQD zJHc&w6h>5S8-!i;%DkW?4g`J~I(C2HI*Bxnas%hMc$2iHj%_c=CH?itSheTy1oIqX ziQXGfmebx=L1&HO2fZQKpQ+>BF@{93qj%slx zV?(R&@<@LpD(MPaIE{uMWgoHQ%z&#EWwN~FV%Tn9shnIy8Hv}^sCtpnRayx_{v4r3 zib(@WCYZ8pV<#vI+bo`@W4ycY2x<+d#feq$(=(?0KI6#1kUp8Tq;lVSHPUSkH(NgG zkL8>_<7hZXLWSZLBxjL6ZfWBACF)i33J_$RB32U*T=anrS(|~7#2FrPgDXGmD8|_U z*qw@?9|K&ml*#w3a+UzfhZm&5Xq!vpS#ng8Y>DUO)SJ6USObzgz`Y9oVxp3_RFOjCp zKuMePo?u~|ePdAjy`2ueG%XBbFH`Yqcc7@tnd$?Bg{?ZQhNd`Gu9Js`um=d3>!=}@ zIdyYnY=*609ftYMVK}$}Scx?+JklrC6Ngw(nWK{r6$(`zyk|S5c=leVR!PzOn3{}p zpAiSW1e}K?-0KORHk8cLFq(LWG`!28f8?x0@EuGj=KA~-h>}5Bf$ecVN8ddR7X)av48fGiv5 zG#86QyAwsIpmVek#J1ZM_bK2(0rPiv6b7pwDnG2j?NV2&il#70&?{awls9;&d`V&h z$xIQ{sOhEhQ!%09yuzUyJ{XD9l(}HWP0;Zp=e;^9tvDBnD+msGF+}>UN@TAY zDQV8fMu|x~jE6ecy%(DvI6Q4`P_L`abBBWAhv`z|CRi%uEV5qV4utbKBM}{yXZ%(v zxif3R#j7`pid1#I}%mx#WXKBw?|L?J%&lXB1|nhtNMw00Z5d1tF+J^px*q`r81cCYFzTQ z(!zcQf*a|1dP3wlb}_DS_N}P-btBae= z+1kr9{nSIO(w}p{a^xjQOov^Z8ak>y_gd)cZAkoTZkA|H ziRWAk-EE$Ii}aQtDR~oH6XShbv(-}+YhOFGgzYD3>I@4vu*Fa&?BUgYcnPR{;qa?iNu?P9EjdXlUg0^RYNWb$hsfxUZL!>bTGdhEPp}B(X8# z<(_eJ-h4vxk3;ONSW?7{G=}nHvry^SJkXaU6x)p$WIJmqsN|D+v9^G%Gu{^#$DN`L zw9K>xQ6Fm)y``qd6m9hog3j>@Sy!pRqmJSiab061fh;Q}h+OJignhGR zk6Hdm^Te5BX|Wix?aKnm7NQ&ZA@ki-0K`?8lH-LY@Lu4W&^u{c0^Jh*EE`2)0-*f-(3suTY4`U9iI&L8kHu6V^%%Z0qJ4h89oi=pfO}T+KTFHD5`34(iFX ztKBZ1#tD}vx%vtRC2{*9a&jkf=~XoZ4rkub{NxjDW?{#h7W(>QYm~o}TIaHzM2o@uAsAA2rNRPf0Qsr1oq)6<-rb=c9Or9=@dxs~n zK0s?TFrDrw2HVc1XcW%2`OFo6`xoc@_yHBk&TRv8eUuNnA-^Gvg>LW>lruEQ>dMr-CVoAkJXfW198a7GM7=wC+Cy(ni0uIs(wi&dCl9$Gn zM*D|))GdM6YM+l+Wxb`YYH;*QBxoS3dkEupi?Kyk7Y-|961Spwa(<$0f;kG>csjpac<2I*>XdB`#@K0?s_-+=E4$%gfs#4y9mZkkKbl_u7NCnE;~0|?twa*?HL zU>3$??4LN7y5L6f1_8XHZRc;a<*NjV8|r6(JakD(alHYQbAaOu0a`B0{q zkKuG1Z{Fq8z|41o)2DiyRysqlU^r?4qp3M~7TMWN&7*O6XQ3PBfwqq8!|zpu6lm1l z`MVmPl2_NejG(lFwXCVRb1Bj|+iP)n85Qj55x;W;nmtHY#K0KvZIiVX;!O`86~3nXS2+&!YSXg12?T*cV$P^m0SXu*ZEU zx$Tg=l=Cq&XQ>Fik7}9J>X3ZcfQR;6u-&}kF4>p2XO|{#DQTS>cmXyz!rm9>Ddh;p6%m@f$P(9qf%(Q zHMTkqf=m}{+USlqn#{&A$N31hP=ao(F^kxw0`S-M5yu<>;Ya+dz|K zFs)fKDp#q>h8wUaws*K*5zIR@eOf8r+DJNi9C9gmZX!nQfb-bp{u15;G9?Doh{B9; z_HiI=D}p!g;AX&Cyx@kGks@VZg?z*f$e2XIa$G(K;kjuVJu6;R9P#=>B|A2X+-;QLN5;E%NVr>|5$m{x}^(pE(jceX?6J6!y zt?0P}<&NRQk+qRk0FqPIU*|X=`(h5QiMuhv2?wT%cqU2L8O!!iK6JxlJI|$@& zyE#6@y(4-QMbq;AF$R-72Rv|AHZf)7lj0bLwQUm@#8@FOqYGTcZDFW#D3#5Nxd#9{ z>8AyNbu@Yq2BcqKr?}lO3**Q6BZ2{~zltq@C1}~h1DvN%!A(bkNd#sx6V5x7=xwdl zqJm@|+WPi9bgYM&M?sCuTpTAUD1xo^dCR4GIbIL77nqmF)v#fya4Z*`=`3U`v4x!d zf(0+%FpnGq(CBW;GXB{JP)NMb)5jg1nz#P2##7b=T12S)aqLa~IOC)q-NTWf_Dyc% zNHE^n4QM7}ckRiACB!CGK_n!4m^+4zZVD2S3S%63oZI5E#QfG(=`T7U)kAy;tw+Mb0E z0>5hxIVh+Lkx@B}oY%|6BxiH1E5T(I7$byX4027{ z6$}fcuw~YhXzPsXnDKr{Rj!Fo&z7`r6WYf65%^(!=#gPzKDp%qwoUgw3Y$>VA`Im` z660!bN9O!q1$CItZUGAcB8;G~6cG*wT%1ok&%u$J>-i7VdgK93L-b?smM(ZwJoCCw zwNErCPff}hPdT@U(=__aEM-}qLi3}`V_}CpVGt&bE`-v<%u5+P7D-Tf{<0si@00j* z1FeU*p#!IOh8hy9Rxu@!wAIoC)q`0E%b*3JM38+jkHrSKy?<*U*M`)I8;I&cdVZyJVK$z+o+{=E<`)C!X6!N zKnh6s1@WDa$U0~%LQl2#l!iHbYz1Ly3BB+$ps=;bAiEkBFGY6tBvIUlgru!w)^ThC zFR3x8XN%-45pU@Ni;u|^DBScI@z~PkNeeeA&GgC4jj0BB^`G4Yps3?Iuu3} zlj4P0U_T_tDl;!@)4YC+n7m4+%TTmoNKtWp4z6-Bte)$&0CF{-9)Y*OL<<3P32eGf zxb8vSGMd4D;==e=2slSZQL0GU!}tsnR1>n75i*b7ZrjPbJ&Oe;swt}t;8sjgDu+p@ z*io~%rF5FqtoT_d2gSPe9mx`BC8uG1*V&9l;(<-W%csHuOaIX}HGmx+w2*79Ac*2S z)ncd_FbXQZG-XXSR{Xee5J2LSi4K{F>v-w}Fi&6PYbqD%c~+N>Q>8%Kfp!|HgU)D{GZjt9XqoW zWAd_g#_i+??m+hccL`-i`sJEU(aH5;;SD#xmw-zkzj zX>82ert~etR7@<+Ub%e+=2_>6OkgMDOX6$j}J#04-EL5U16vD~d*<%R$ zs8Z{}N)zH_rJ93t$?KfwmH?luM;rU#5#1>KWbE_kQ8#cq(c}OI1KL!1a9pb7X|%6E ze<96WS6z>An|dFMdP!B6O^0|av#Mz9m!@#@MHbJ?NR1qKKsztf#-1EiftnO6D*@0l zim;;;PSF`>O`h0ddmp1+9p6$g9!ra<`7HSuwWG@@cX-mdCJ zto;NxB>>wCA!9AK+36IdH{03gDclVXzo8HP39r9%+!eSTsBm%P{|s06PgtViJ{Dj2 zt{tF`g^rwsWN-z$4B{aNAp!P8A7Bf3jarsxj!`7!nwZq@<*4^0GnMB9MIgo<1Q0H# z*d;~}jT82VmcZ2{4>pmv%AvCWPV!jQ9V8Ttko_TSz$lhg z4JoU@XQwD`vEAXaJuc&R7Z}KzNAKUif+qzgz^ro=DuUpi;J;7T6c!E}% zNd`6iuCYY~bU=I|`4<;-G``}~h}Q+THWe@y{1r~Bp=6#iS6}VjGO_(qbPIwP55#R_ zZ<}@qaDa5k_sfMQR1 z$FGLWP*nrW?f>KLeL(cA?!52op2=kHOp;0Nd(5hJM#pN_$Qq4XZLQU3lO|aWjT%kT zRkmW&#A=>ubv4;0Z&UXEHrN$ASe57A%i~1s5u`FBV$pLKnKAV8QY#Sm?uR z!Sb$PA$LnZ%pw(OS2 z^hLJMpNzlFW;n2)>am-1)>YwEupBz6Ylr*|#H}AYy+fLc9DG{)9GNeTC)3Z}`TK!K z{0ZP}Di{IlYfwiIpS01cc|qC?%hntp%;lL$l+cd^hI~Hxz3S7p!cN+}5R^G}#xE%9 z5IEU@qb&^sfoCMp-G`4nerVrcU;k-8nMILSC%kK3dZ;+o`ZUI_b8hb|*#;(LJ<$bx?=S`#_2wGn%BJzBV4+{&=eA>cZDt9dJVB4}aw$O+NhkaGa%yGUYS7aQNVF4?TQT zIVJ!JAMMbc1wJmVJnik^vKDe;(AGW+$WMQWPu-UkyV|>iu*`VqF{6AS=xW&{v{Sgrzv~-2vE@h$V+YnfFOp4ee0a7kn~g6~CvBll89RT8&7w`4 z7qAd{U*0C}#anlFs>_sLxz@0JZe|_k3{Rk5yGi9pIm8b-77vtg3#*z1EJ05{UaGs} zaX*-$UxV~MAGhc?r$=;GD~zD5{Iiy9UX|WmaHMh(cA$%t2aZBaUZ0G7=n?%e$T9T} z;RKJj+w@P%*~Tt3o~j^6*uss4(oDw9hFzf~6X@OrM%y)n`aN zNVr55fV0!JOO)IG*1n^UihxphHXl1+FoZ=b*^=T*bc&iPsDdSG0U8?swceFq%anjG zAPHMQn_P~*{6(vW49VXQ$6DGK+s%auTChjK=+$zYQ>nBfcEy zUF0uL!};r@u~HC8QJ7l~`3piMe_`zWCD>%_Fz0!X->={(0|zda7BaHT4UpUq&P$lP zU5buvZhz$K{=VmNF74>I!?-eyCXzOg|DrT)*uEq!nZtwsee;{yH?!I7*6dLBc=n)D zH|xJ|X4|rFWmhS+Q+dZTeI&V7R@=rEvEJ7O&oPzg)T-*;uXcy@_LzD(l0BL=)-Vfw6(Y;l{-a9u-6j3auDLbj z&Z_=#E1%82qLNv`{)qnDYCS!wb|xa9aZj%#vVUzwX+S|)HeN>t%-BYHaw;g9~Ev62?v_prH^Y) zUs3O@9eBTAFdS7G>&NW8#p6mpl!2A3MV<8im};bz1Z&xPdUsj`m*22AFrKYFY~zCa zYy)e{>O)_ucj?ZsnmhA4vjz|KSkY|ruo_2H%KAcU^p>q<-5KX0rCCi{vyQ`Bf5pc7 zn&zsnn+wZ@`qzHZ`Z%t(E5yE5o_TAY>%k9eVK~13pn3<-kLv$U8(tI$ zXEUo7tj|NXCiL#B0;Fk#+3bp}F?I#iH^p2d#+>uB)>mwPtShs8Tx)p5AU&j>kvm1~ z+^#n`vRb3E7N3f+FI~Snm#p=;#y_MPF<1CIt$FOFR-?tL!ktl8WEUQ`^>hl`X_yHs zzXpCpUS(HC=!&%Dvg9GdPsC%UHiwLMvh_C_bkNa_S$6%f9%5bAZfI|7p}M1nQ)CEl z&`-AE#;^j=x_Tegf1e#T0xRSa*C}M^D4&TfBi8C`HMHH%v$s3z3~xDmA1j)}ny(5k z%rRU04~C=1Bt5gKN3_z&kVNU(dZ*~4Rs!W#_k9c#{=J z25M!d4hJ4&-JwgZ%+!raRVXz`J?Ic#R?FHdRNFYm=Rs>s`xd%i=>j2;k7$e+Wq6|zphx=f(-8#ZtmeF&ICLOsYiB#?Fs z41kHkqAvPAY3!mywyIwo^pj?=z=BNZlUX+QNfg+@yRw7XE~%DT0a#|`V&5oM1TEr| zXH-}>X4mNdMTFc>uYb3)ULnQ9#)L2xDt6XH4`Mzk+$faQ@95v_zk9Q6raZnIWH-t% z!$iq0a;3>4p`20uE|w))uc-_A&l^7FF&kk+K5<0Vzi_F(vco$JO2`x;4a5+MV~0H0 z9@Rk3SHc)zkhLz3GArmXp&%ca?Wm=WfX54CySr3YQtB5~SL+V7a$_WGRhcGxd}ZrV zUFB*&akWvBXQhEVB8?~Yc~HjZ9<@xyQk?=Yw+;ylWusNNxM-*5w^yrlo48=V4DS2% zPJj1FNZqHD({XmqeNx=jnz8ggwY}Zw{SLLfUA<*9OT#I(y&J~%^}*0*jnX=>Mzey& zRdRjQ2w&r~2|g&`5u0NjgVivcWT0fg;%moU>rL5qTb!iSMI1_*?mAI2vvH+z=G>AU@?zrgOM0}Ors%VP&e+( zWEaLYaIp^0VoSmK<9E}*wGZe1Q$afD$aN*%pJ z4i|KRgUzT;6IW|?(WSatyne0Z!_~^cpvo?b_1!GNn5r5@*;G-N-YEGZeQL(l1HvGE z+iav^6AeZ#4G!r{vFyGeZyr}`v|}~~RN00kM~s3uxKu(S+57q^AMEWCMn(@K4el~d zf!mpdmG5{!^XDzj?`nI48m?AJQ4$dZxKB8XQVeLeW!azEgRp#bZ{8gN42(FRZ4i}VU@;Nf_FEHaUoOha2{4jHdCMFZb5+Lkh`n!g3DF{ zFh?aLF`kbJFD}`#^LhTG)h0i;l@tzeXI&<%1t#1T0GjNggu@#JtFMqt;A}nC+}D+7 z4#cK1Y_1EkMs~}&t{S}_wvimdK;H@|#MR8!eog6|rAylads0I*HEzr?cyT;LdaF zqbk8ZrMY8=#xSIMJpWOnG+Z%o`&brq&dqn|-Q{J(lh;81K(SCYD?Zd_m6PbLy*J~~ zYZC__F^VPRa#UP$oBlqe|5NUI0xc-L^{G0QpS8_e=p1gB``1~#OOJ<@!RxSCPO4m= zh&vy%SwsXeNoO24J<25Q2qBh4T=A$FmT zWMnjQSRWS88uuSiDX33lB$U%)7`h@|#c`47kLmx8IoG{OR%!-!EI&$Iwj}SWBb2!; zJN)PNw?`fZ|DTmleXYX8pVoh*BmWCChn62>Da*-*e*Vk75Rpw8t- zjJ;{(nJCwW&VFs;knHBW1fk2J>WrdTh*St=SA724>UuHSHPLRn+Hl|_Y^~rdj^Ns{ zF?xw?+m-ZKUb(G7Vz6?dr*H0-WZ5NY!#YA0dhm4j7$o6#a`9iu2tpHP8ovcoC^ZsKHEHCpA-tyyCe+7K%? zMvsuL*^cGdmy1* za@ZKdw-eE+a1^Q@OiQBQv;5fg`LDR=W$}_M-(<-a?I!aS4=)FsTZu_;yBtO;xn)Ac zXshVz>%tcr-}OHH@l|2K`AqX~d9;&At5aTfS=mGa_b#~;Rb`hj4zvchxrTSFGM+Th z-|tX=td!gL#RzNS=;iuYM#^^nTOBRSbp>LIV!=&xgvVF)e7hHST4vaCQ=qeXi0fIv~YqZXHeG>H;CynbM#OXVoFGYpO1*ED+ z1bxwc6D)#tc1t+>+-)Ymh-m;zab$c==-QVEBC1#eioJN9eI{|_%@;iHU4pDgOxg9H zSM4LgKLb%K^4Qi#h|E{}?0(HT#utHX#ro<1e%+(QXZsxw)ZI7;3a|I;^tblve6_M=WqfLdt^^Ng zEMM+7mfW?Wyp<47A{MdBxtf=8F5U98UPZqgOufRaZ&5nM@BAQ+gf zYhhp{PDR{YQoJ))Ce-By9>#@92{9lhb{)7CApIX!4nU*tLUOrXb{PVMGe-3DH5t}JOwOZsXlY#WMj}c$b*+PP{)TEf zsZ|N7I1h|TSK-CQr=2Z*9RDu=BsB|9n!>l3jXXunL#q-!If> z6=hwr_{#0$&P2_Q>iqBfB^Eio2F|w1i1omYQ^9h``u~ns8Yk=H682sS%HiDS%ICPy zUf1I>w8X3l5hEInV$SzjVFY`aGz9qbb6nB=DsppFPkYr18$b%wt;faus-+cHUJF&b zi=MSy0VG#HyR$yqDq*GN1!50!NA7T`RCFw}$Iqq5b0zv=>n?Hs59c!Gus*7C7Cge; zOFFfk#d8^Q`R?G|>Yg8i$GFN_M6Gs*I53BXjNY{qj@b*^p>o?l#u10?_H6#5M)QMP z%h*k8qga&Ze*Rt&DWb6`4i)G8TwOyRHj4_<0hum(%lnIg{4cFyG>#ET+;>cf>@qUD zwDHK(U3zDp?1+Zg*@dyJXnPq(cGhDzDGC&c{XS{&VaE`O-2Amz3?gy@5ghr^z{sEx z;$4MW2Lv5z4|W#@ckdOv>;NJ_@RsAq=VTxLRfPP;y!}>-y}+PFJN8D!VCr>s^h-My z!{(ViQeBNAgu~lZnW$`y7TZ@XV6pH6A9p-wM_kKK3}#qK#3=6j7*Cqtq?RG=gPY_a$Y09Y;WIOTHDC37u0NIJmgQM_) zv+4jlpvO(^dTfMRma})F7{G= zr)ZeR-oPB{gtr)=%`Qd#ozOe{^l)&nPZn@Ve3ycqp!DvJJ1fTiq+2ENZ1r$m&`sTce1B_YM)t5p z(PE9=C{Q`@Qr#-88y!mq9yj@NChyD4Q474K4PNoXddNh-W^D_SZ9wGX6NV7JZd_0s zrpT`Ka1&e!USh3lRM(Fq`rR+&0^Z@nSwjbq;{Cepl4KAb7D;_w8`r6nTLJ0qR<*_p z&RTBt5woK1}7LgDhq)X2Nf-!%*!HBh^`&tq`1u z9GdkBLgDS^9TiBNn3mS~nDF4%Zn5c3SMgdvwtr`isr`bO?Uv66(E;gJt%wH#j(xkP zsEud|)rg9;p|(p00G%;eEw+5X8IZaNBZP@)iWERkrwGU> z;OGM`Hxyxf?@7SVOU6j~Xd zR_PEPJ%MZ1=`KBJwGk1k$PRH|f@z!$h+?ZU*_e$aGa&YwlWOO`e_XKO-LxO(+r2Bp z(b8ZFTL;eto@fCfu;JXIo$QAVphoqoL%B;)c!I`7zpExpQ4k(E+?6&;*igBm{v!3b zLRl6*xqoMeFU<%@!F3Aw`^IkM%4osX2`fgngppSZU;0z>deoKWY^vy z<-#aQF`uQy!%OeZSFJU-rhxf=DFVXCFbt6h@^GG9F+_n51yn4++AW=(rdC{p7cVO# z5wrWtBEBp+WAAPf{27*BZ4{IYA`g=Xdze$#7&~mcFzSERe{Skv6GwI|!{hdUq+?N} zgg{yOB+=x(B8>cY1<^Xj;#k7jz4s`@9EVXXab`aIo8`uI@V(KxF;%cB zuv;~zhWGMCwZiCc&FcRgHET zng^HW(>g?Tye$Elyv-ucqxYBG1=?YDaR#}pG~*@OuS*JW_S@wEX21JqiRKThMmqhw zJQsgOqqr|zUm?zIEcH1DKZ>F;w-$~2;@q~XEpm}FNp9LGK0SI$n^kEIvdy$Okk^X= zp}$o-i^f{&hQH;yRn zvD{hQt2q%sLoQ`gI8uJIYO8Sm*TO`j^PK}KB7#{ZW%s+hI>BPt8S#XF7|oNY;C<=I zkNeAIr4l#q{J0cC7l6D@iUVSLK@K9mP0^1mP2kG7r;UM;u4BV=k7{9gQbPvAkV7_U z@-E0gT=}Sg!xP7FMvo!0I4_8#xNn|agitGf6^D}qEQ^qXJv5`^XKRIsM8FXpMy#;*khn)RR_3Sr3Z-LH z+2^XG-e&9R$G_@0qIQHLeWl_y!_tFUz5d^+wsR7+9nJjZ#-uObY0zgO)q?XGh&(wL zQVVb$f{wXEM;PV0Dt9pQb)pfSrw}<60)V)VZR6`g41&ONw}Tnz$11;c_A& zSWg1i;0n28LGj~fA3yd1e#){cjzkTx=D0^DEx(V`xT3l`X;tr1KM|wZR2093b*Qzy zMdPww731KdbXBKj03WG&mqP`#_EEa(X^?D|vuCXoS66&7o3@Xm;9W*-2 zgr5{nn4Dk7w2invVjJHvvnBTNWf^Yp{Sdq!tGj{uLN*RncFCPefg#urf_cp18Xol1 z`I)xwVxXb&?wK%qK5#^;iX5VR!4K7c#a3v@{5I1L{J3|$MDO|} z6Ke?u%s8sq<#$+wiIoS2BZ|zytqHSyR4yv5Y%NrY!E;FTmGaq@L&W4hOl=fIrBk@m zMQxO+Bb9MQv%`c;wVK;=0OTw^j7W<}awe|#67N{lmPa8Qx882k6tIMRgd&RmvtjUE zC){b5>rNCPWXMGqM02(3#{+11PfH?Z$Q`ZWJ-F;Yw*?yBQ-p;N)758f^5nuKZ*~@> zxsa!?wG{bjSPv#Q6nBX+Ot`{(^;k@d`T2raiPU~kR(f7}#+E0qzF1H2w2;=wb~q30 zWV`o;Sw$^atx=DLIPYU7<&iZV=Ea%ulIX%{yiLde3}*khWSCit=r!~cKhKzb3eqk* zHU1qEk51etXz`ShdIf)c6ATzPj`60^P+9A0^;A3qo^8O$M@st)b+f%i&NFTE*V1hl~KZD4-01^ zxTB7T6h`FB3y>fXgI5ocqz~fN$=hXaJfcCs6t5Yj==OW~oW}lh9|=Xq9C>;G->C=E ze+V;0U&D-$`&HNMV09GYCd?z+->Bnq8>n*mak6C(1wU$(75jWlfi_^qGxD%u2)1?p z+itT1TwVBts!e@KQ`5R7LNp%hHS&M)%RANHh67@sMZK?%rZUjO6lfw{x0GhXjqmEx zi_B!RL$(Pi^_iU&=~K)TLUez@nBnGgn}3;XVg`ukg9*xN+|@_J>kNr4PaFMLUGq=$}Esg)us(&n^b*ItWHi z)5Qe*a|D5jz%P6pIEL9KvJwt{oW9E9qb}+j|5ROqI$nmsQ|))9gjv(rmpy94kc2p& zLGfrB#KwwbGsEkkZS}3;YwM-M8R}}0BteesGb?X_0zm`;Np@wu9k4=#PX74_LuM5Z z+8@!3_$+WX<;+lk>zi@6nmshO<<(_lvtGccQ9Nu}*L}oGOI@}~%8P5?Ss+$Prs@i8e z%R&;dS>@7NOhaCcJ_D#NM6B1Znz|;Sm03{GKXwSj*#;CvQmxq+KK{&Hg>Z)x2sI}i z>c%_soNR;d7kRKvBim;xqX%WMC~WAva`v`orI6HBOB|QboZz^uEdPh9yZzuldW@`e z@n%oaK#9JQdl4~=jkj3)?TTyMP{QM^_b|kByJflh?%m!clWV8;`}qH7)hiIYYys1# zW|>2`-K7zkLoCI7_OLh7fVmyj+IzUrO}=H+@fYg7m8tC<`nsjw7Y9Mk`4bq2>-`YU zC9FyG&OIk|Vl;-+r;MOZ!oG1Geciwd(gBPSB$an~O*L*&9TOuedAdXp;`V!Xv|6{k z+g`^QW-rj#z}sVXNv_Bdh)8*RBS3cN z^8LD=a!QGZ%skGpB&0i+?-8vX4{|Kohdy-1`FuVI>%XkSCPlV^S)`9JaG0_h&BiGA zO`y6Q`j{JYoy7x2?jR7xle(iLqesg%f?KGsG9Q z`0W5Q#W&ac8FCN36-mJN?nK7nd#b75J%SyNIHBtnTNVg8nPsYR2ZANBK1v=0`*|)u z>6ms3eOo?8MfqMZ*y8<&{?D>EK2zE(MFb*z z7^i3qG_EPeeCzW<2o=A2*SIv{*!%S>LhgLQT8%5JYq>9NcDr{N9Y!M< znq+p+AJ|x-${X_Spz`kTu-=lRR8}o1hAyS@nJ)^4_>KH?fN1oZ{DN4b*I`gB=~e$# zjQJ&3q`E~$#NW*M{3pIrT{k2V-B5O@mw(Yb)pK7k_ddP@JxJ=KS6k7np60w@Ca2ke_ zpYe{jN&Fn+!;&bepF>&0cB(N;fl$tf_LL4wdsJ#^7Oud&)?cY6wQy#@d0`*xl2|j+ zA4hA_Zhk{M&Q&_&5DXHbh$CSne90uz_s(H;HdiTRXsz1$e@q_?(xRRm219id#_Oc* zt!2(%AFmr1|38enrx>Ni3BwRcF2^}Yw?pyR7kUtig8I=HnDER5&P3bz$)SiDmqi%M zbqq(&3I?4ny7Ji~XoHo{{(k8ZtE=oVxY@d*u{!_#c9D-)b#KF`l2n>|tk|~>4ik;W z&dxymw+T!?i%?X;Zu;gEM4ePiUYtWi7WzTd`xx4ZGKX}m$2Xb~Jw;O~{cd2K-+qH6{fb?A?CjEV)L@rJgXv*- zh)t|(zi7q@KS(CKwk{H?Kblby5<4E3NOuE>b%^-CUAeuIkeHa;&?)s zIYnA9aV<=`FApo+WmwkCB>sv%yk@Q+yGr%`R=qrC{YbFtKo9JP7n$h-8d1y#lt$$G zuT{bXh%aYZOkqCn5A&7!(UFG4eg>Nkrup~l9@BY6SIm}gh8@QSP}Q)|ks;SB1oOR1 ztDY1I7{<_5^9q_Tk0*cdin=#SA)n^P-p?+4`%69bkcWvkU!*BR}Z^9UKz}g`Y#Ss2M#_Ge+>37wr@?|I|TI zYx4IJ#U{9mtA;FS+`qrDj!AB~AWeN&y+a^#k&Drpq7qJb%rKd=n&B6~rhC*5t8b~n z|H&l;S8;PEemJ8n<_%j9uM}c<0wdB#h#SSkJ&;W+`9U{9ev3mM)qinb=VXA1hQL7T zr|z2mS&A}XIQ3uDDI95quc5IhmzOc!bY}rrX@R;uAZ)Q}9x2GqkEWT|iX%4UJi2j4 zUG!7+G;=i98u>3eM!GzpaUM0PlQ2~0&a8!!hJxd4vJ2scbgXHoM)jCSc4=KwM2d91 zBDUTc8+30qK1Ke12`FJvEFiC3P^o$ghK0lj%p}u*}`o?GrbdS-R zPS!+h#xqONs1prdQm2)>3O!|=|FK`w=8$*%wzrFKlvcfI6$~cp$K|87URPw-s7~32 zi%ao%_%%unzd&GwPZ3&nPpR{N?Nw&#Hm(3X8F3L!x45U`lEx#{zMZ_(B#HomPj^L- zjf7D62oGpLX7IBe#rrHwkfXCq!mm)(Wo3Q+63&?h`gx^lj8 zA_l@3a>!ax!7&{gh<06endj_NE$k9M+D)oZXGqj^_|9jYD$aq&?T|a+A6a7d$s?x= zx#;>q5^l`dRfh+nbCN8gvIK+y(R)#^!e`hE>CW76N5y3u65nX~+PxYPhIV%LT^Ou^ zVWAVVBko8C(u+%15Mz=BIVirk0-$7ln$6O#6e^2zO-Oh1O?JizEK&6jU+cDT@HyT%-)|r8;(T# z5n;F2B8HuA2+(7fF>hIe8(2kbQS{nu*3geA)n~g@k7u9h&@pvEBEnSk6QN-JNbuNI zdS;j0tH05wbt*t@`HmR1?!}IN07>YjP(b1l_kz=T6X%K)FlQ`wJD~sKN8bnw0~0bN z-gwkF4}V_xah|C6T%=oIDehx~lzn4*OCyPJIX%WrQszO~e>>exhr`5{Eg zfE~(a_5I@n{*#$V1Ng_kpT*ik%fq12T<;l10r+xQf$)q|6`HgvFA^OM!xBF=QvQNh zcIjbBnBw8{ss-85PYon{VyoZ=a~yN`6C|72Z;}zr9Fgw~cHm#% zni+O)7#kKC9X~vaEufh4y~QYZp`;={ik5;fS__Rck8um@u7sWhpx8LY49EbfZeU?8*Qj{Uf^r{~oFSji9yJvraHjoxYTa#W_j7de<5|!Om z>c_Bp+YV6+AHKymi$1CM&zTL6T@i!gYF_*-%#!O?vpdmI^;-{;^!78k$P0L%jXqYy zqaoiAX0Rj928DVVLtiw`k1_^IkDj|Iz4N`O5~8Cw9GvP>)OU*sg1Nv+p@uS9IFpvi zHmQs{5(M6muui?6tBNv-;kaQ27gc4IM1-8FEvm&*5f(*EUYTV&w1q;82nSw7T8e59 z^VihZki%|KSh~`>ddNao&F!!&)|p$`vP28XEqO;o`8)h@1y*6YU+l|JKU)F?qcfwv z(3FOJ&UqO6t58(?#u`D-K_U+YOM=BQIW}awcGwt@Qh8?8QOZ(9&=2`S!OD96vv4-- zQsh#@mT4A)4zW-01ePZa+N!E~tI&v(#IH?)7V09d;Xik#8$&Kdt_+4MQsdG^Zb*j> zG2UFI`z4=Y9~55Mms`!MZVcSRB!vPp4shIOY1isuu%Gs8t-OoraGXkQ@>~JXCBo zDoct^8DeO=^Xv!t)mr)l}nYjK|F@ZsaQnVi0sRyCtp zi{ecZjjIp3miw*;p8TUGZZ1U=1St}KCHr&2P8IqXJ}B1HXm?geTWTi0Dl33rU#&ha z)#O`X8T$9^(*f9c?z1eihx>27YeeKQeuo$98g1iuCSf|f1Uwr1^6=4>kzQJ^Is+8V zvZzqV<0rwHEMz8xZm!O^N~Xp-JFz7X3p4nDMXNMg7#`flyDBP*G)qN+?z>&>Bl|`{ zP-hENS`3&Y)EKSWZ^TZqe=Xe?Rh@7^`Wj#gGcfh^J8V$~M(xJbs)61V`hbI-o)T?4 zy#Q>{852TJ9tix7aa26_61lD%vcNd<;|6JSGD?hU$Ro`r!4Xe!YGmE}_pi5Bml-JI zRpgZs%F-h{zm?~pvH#|F;h`9wNwA`G++IlGPv>?I2$?ac8|hde9&`Y2*$i`Z%=nd< zVz7p9swGWTwWYJmJlMjjdN76W%avEEF{+Q{jNVmPue$A!Hh#;WWy1D~y%nW&w3!I2 zyBp1!LJy#!0e~ldJPAr!endPOaKd&5+SbDYD_g%RwHJROQX_Z%$MQoDi#J7#Gs#BK{tAbbwbvJ6qQx_LtF7@u>db5cf?3osq?@<<~7EvN<2^>2UOsuKSJ z^_@0ulqLrRkNe4yE%d39paRS~8jL5okR60$G zeUHXvRU~fJx?&t9Vbln}{SevPwS?j_S7Jrh6}Fgr!HB zp~mEj3h6f=6jIAQx(yj|;)V+1t}K#m+)oW#vgL%8VAV43G)J`x7kpO6!HVXtb1voM z>e-b=p?XB$8%*=x8Po>j$Y{EvER4%F6P~iai$*G|R#XQOOSh>%4}(+{iW|bwaCTV& zAsZKlUlUFA!!M5_ZM;UdyRM(sS%&gKdzAPHa0bSeaF`$7 zyj`{F=j_MeP3>LgiW$|OBB*e6RKHUEQ`PvjRNtNF!oe$2es}P-qj>nw#r3R?#kqp5 z$_XGWdP!B&L>Q6xv2@$=E)1~-$1w_nXbjxIQN$EeEgK)||7$DnziO~0gUOWze=*;q z1zVn-#`@M$V^vjjbtGs83CyhGc5oWAZhN{X$^jly>{Dp;Z|oGM731*LQOnkGeYK18 zO=kTt!!#)^jeGA>vo7N&M$e$&b6CIqOZA?@6e)J*rbL-Xp%L~v;<_ydaim}7hiP4m zFL@|8!p>S11E~>)<)~m7ChIFX%&a~Ke091@A*^msSIMyGp+Eqxf|B1FnXgWeC4$ho zx8W?k9C~5o7_mpOrEp0MjI!V!g%^_Tlsy|AzOcm*oKEkIgD`yzx^gMe%_je%gj>&< z{l;Pa$9uB-ivb<*gzu1om8gSctwSgu>pbMp6M~MVsOY1q=b@LgK~-O4{kGoO8m)9H zOGQzzDB$(7v`(>^?C3D3ZK>YQ2JNyV9UP{e2)o4H&S(WEo4G2bhUloj-&^XTYP+Vs zA_I9J*u(T*jEggV+E|i?9=}3Yl%&1CISfbjqk@rzJW4_oX^5?(5bl=aiQ31Ycw$W&g=}&7D z$;q+Vk;dfY`0Qw-y`$*`d#w0g`}^?truNB3yYoVLp7Ce*s_@(vo?F86NO=AvJbxUX zZ-wU@;rVuWz7w9m_GkZs@Vq-b4}|AU;dyI#?h4N^UVmSB-y5D^3eWA~`Q`B3XwO#X z+3NW;U}*Q`y*T*6y#N`A<|^&+ZRLCMRR%?vC*C zTeY;8)tB@SwmX0DaeKE4p8oxfcK`L5_<2nHQhhX6O=VM!sgbGC(ba-t^;ARujp)D8 z$?-<9Sl#u%qw&{B3VUjNc4S12fryeuc#eeUsBqGp$|lD15mZ}~pB$NLj7+KN=$dSF zvfcmQT%E6)ZRGiFs`F8zBI=}u7(@D+PF9y7rsvlKwpvX`Q!Wk z_<=uuxK;}kmgh%f<65L27;=9w0kS(k6_#QZOQ-CYLrbz>rcL*H>oPq0KUg)cWmuIp zM))&1IhtjYW0|rhS8I~?XtX-7trv{jS@PAb&hJK6PmZo`-=O7h-=Hp)vuXkaYnH9S z8%i~`plcfK-U2u$39h|p&FWajwCPe^?OL_E-P^0gb^34H$m-_!wnn3|@#1XFMC{V2 zs`IAm+axyG-wXNM`H`h(?C+(TT)Vq@L;zi!t=`zkW-iV~CmLhIS!?bg0RddQ#wK`M zx6yF=rfOcCDXZPvw??#jWJK?c4H6!LN$+2_)m%Mc*;F`Dsc_6HP|~TCoU)SfiIG-s zpmkP_Z)>niC6TPMa;I3ne^WI9ol?~Nchy{<1Mz{Ou-!XT%Ka$hexTedX2%+n>O$Pq zHavxN_p_?h?(H%bZTGXSq29lZw0jSxs^PU-8%RD<@7K<*Ub=Vn5~ffX=f==tnQ;Wt z&7*?CW?yIufzMqvVa1awH%UHyfssHDOibB`TFl!-sCpntKCy9R#O8Rh5FlxEv169C z+&D2(uB)}`Z?Uzy*0G`bLX8awvI1U20HEz=@vG&5oCfQBs?;_w2ki`=XIKM+M!l=E zsd0fgx6NQvCy^>FXQS9bXv_!CiW#;nBFV5`YN&c`8_LsGaB*f;tn#obD!4dv7_O_q zK#rH)P%9&6YJ8Poze%Zf_s$ugkEp|P#J*kQ$j-m4-qxVPdhwQ6&*tvT)z8+=M$#1U z8%ndi*b5Eva-qRIZcHtaY>j|m^Isr7=C%XQbsA4(vJ0Sqh|IA}g5h6G(#6NWN-A}p zMwp@VO#1iUV`8vlC=r7Ddmu1%u4Rn4HMk_!HPm&j@v-*c_4eTB?ZGeGgI~1=zeW!3 zFkyCaHZi+OBm8c1Qj!rW-sK>-Z;~*S9I&T}ngx?c&qhi4qKTwuO@z)pBdAAYd<8$rs7!RWr#MB8nQrW30~-&LDP21pd_cn7Y|V zH`|JsC8t%U({|@KG`$nZ+j9pDa(}K5%dHw((v79{R-QMwJ2c=a)qwbFcivskwB}BX z!n_wtEhDur9$8z+i|80p!mX2-XslC?u^`sLdK5_B(+2DJ)GF`y;?dCa=z7&jxv}rj z*yYG1T^`KMfeqGaYOry9RI2=aO(F4xy-u2TeiO3)si>-D^V6S@b8%)BL|vN0j|&xI zj_Zt{sN*1~qI3@(hW=u8l!;3+aYGA!9iSN|3sCXyCI4P2;eAXNz|5p^`rsqICn6G= zSk8G+#sDn@(-k@<%L?bRTA^j3o_o8JVUi(8kC`AP^<_(8MFmz==G-dFY{vx^85#1- zVuN=ZwnFbo>P{jXVN+pI)r4u0fPkchvM+8pXQ6%r%XNOE)t@6Hnj}Way$7AUC|GCW zdY#SZ9vFq>>RGLF_a2Fs5j_MYLc4p<$axzZg4Pxw=d=-6y?akH8O3!vuN4n}TiyFx-J>ltYk~%}2ePH}yn{*`*)lw?o+T^L;&iE}5%p8bP}e4~o{HFe z#{||>n$e^%BOHv5uA0!FwHn=9n&CAn&qBtc1a7;#l}D@pM`bnE@MmI;{@8jqy~efn z=>I`;RsEBm+yBr9m>iKIH@Qku^;nSP-Q&pE-WL9LAKo-OIx;gB$tONHW+Uy+FWQ}- z2}qoqU$nDT`a9VyWLf)@BGA^Lu}<1RkJezUHCWTkgmW##$?>eAML#)-$(S#)E$&GH zB%+Yu6r%~y?zfbX9Mv*UPBzD-<6mucyY$`ds#t7}mR{W;UwT5SCd;cW(tb`Atoq{E zxH_3`TGy&8+)V4?guK8gqFxH<=agWwtm%uhV06J?c^lCJ@^x%tL=`5aEq4g&>B(uq-|p|g z)tn}coOf=lp|6!ctjW^X2#T=c(QY*iAiz2D%mP-v%goZ6Jy3+c3djZ=Ppns z^(vRQ-FbFUrQ4?v8xI)A@X-11vjaq8N-j zOzwVf+*V;`Y^@~F+;qG9op$%TF6F)(C-PmFWFh-|qHC|VZd`c&j$r&wt9x9_2$y`% z*6X;|c~XYEvc^KecUKF&gG(o2Cc*oiwd1So+3t>wjFCr)@<}vXohO@0H~F88o8G@P z#lE>k2ctPf#rYK1cCm1r-5wH-{yMJJxa!3WmV+q zNwK%A){RyoO2L9NA&H3p-pG_#1Kub&7mkU`{O%7R1~G|S0I*^ZBTA?OihJCo>&)1B<7;N7CxZ%7$Y@&9V^VYM zEK0XYt2;UH+C3_2o|$Iq7F{`2}w4!Mk6x(Ol6HIL^#=O zu92RH2qq@hNZ^Z-^>3}#tv~Q=z>}O7e@wRT_AF`p(MSq#v*7~W^r$F#V&jAc_?E=Q z-D{Dty&V<@aH-k8d-75f2ZP;0jleBX6LMf6tRg&B4^F)sU8vC;bbhxWod}>p|L&}y zWxD^?>b{Av>HaIrJ-AI`a&X(|YSEj9?!W0pe_=V1tfEo++wT6RDa9wZ zr8_3FXm>h+^E;i%Pa3q}8aa#DQcX>eNq0KujgPlG10^Qt`d`(1yYp=sGZO~eJrky> zXQW~8fmFNq!X#FIjYN+@&}Bb>~B-ltx>0JuXg9h9wxm~qdTgV z+jMn0lrY?8qX(fw%~ky(i+BuQn5GEGD#dktVFXlnUb$pkmd^`XW`QEzp=9{?V-+eJ zor2VO!N}kRXjY*^y}|=cN~K{t$ zj|oNX?tcg^>*Xy^wgx9#gKxD4-Czw0sh~>_>1dd-^V%wHvsqMj277X3a%A)tr!;Xf}fZ(0O6Tm6L!rVv;&&ih}eI z1MpbqkM(-2%f@5}{i4Z#O&GHCJCH2fv^SHYyuF!E;8c>`Ia7VE75Ibi`afBul86eG z^8X3sptrH0Kw~5j7`@FA1DgvN*gS~=_TKc! zjB2(!zYo3iHd`;f&4Cqqo9Rg8ZVBQ}Mx*lm`ZwnTN+IxhqiKffMW^EcMiasNEN6}UrVOqFDedepiBU5^?db?$@qcvFnl zc|mJD_W{eQAu~Y=;fX&idqiBck+gK`Zf`aB0c@m@s#>AGD1fWi-_HWaa79pRqLwo+|rcfk$9!DJ-Wvs8LJW#TZ9Bi z0A(fZX@H&=0PW%mwdT!Goxi}+7bw=MKGh4(#uoik-x?usS9i;Vxh-2J=pX+J)6?r( z*JjqvPp==BmHJ`3cfW+7bg*o~-u;u8kB`r=Stnl^V?=G^TkN7ezcFwKbM%^UNP07= z=LH(r6OiZ#e?}veSB*@LkD{&OB;+>$!DJJiqFA2+Gz!Z5C$uu%?OG4@Ff&P{8RjSR zRx-6&b>V~z;vjd)->ZBJMzPbmL8@l{OEV%~lbNr1G1CwW5=xkV z&|LW(wCx1a^6b>DP}MND4h?MCj|7V9pD`*r;-j4kqw)07EM=qN&&$o9=jmmZva%4= zn9qY6p0ctK44Kb^A(pZoSyo$UN1P9SC@5Lp`Po3Ip70vh(FqOg-jy7Q);zCTe7vn` zp+8#*j3<;KPLH@JE6Sz$SuvJC3u4M%O*AbQ1koZIr}yNf z^PNNx)95{EXpUP5-BX6{Cxf;J27U?`}aMx8{FtHGZzhs5}^D$*S>! zUs=JgEVO!R+9&*L%lmcg=|%79MVGZNYG)Bv)!%8s-_eC`~{X-&;)Ti>FsEU=7SeK2e3)D+%@UrY}%#POJH5>R1`}>ytefuI4 z!$t$`?pyj7l5e-WZ^s$G<%{$d#)WB^w@4t!--=b<_9}1ZRo;$O-Ub_H9JNm8TJ9a2 zuW7l{7g(CV?e06Vzc;+UH$rrK9j4s6$@PW`=T*6*(OMDJjjs~*MuFdyRnQvzRPnA6 zJrv=`$C%(e(u5pE{WT||;Z6h@Ba^Im_uutzWCG6~uabzvSo`#}tadpi8%fV%sNrzR z7Fnw;53km2ZX`5j-bcuSn*Q_xJ2tp5D^#{ZPWAeflc-Km_A@Kb*u*IK9ma zqz9X$GUpWYR!n4-SXZVjCa_d9k-pQ{X;@7~ZZ-zXj|3r>F)3U&9#&$o8=<{#NPSc6q;=1@-P=7A5Ysb{7UM|D4B>=bqJjTafdn}y zKWg{>&Ei#xlDB(rZqVMgpkcet!}Nl2&^&J&0dt884e~Q_v4k`yHxM84oO^G!d%uAK zPj7FlLWuNPRkao9DzM_4EPa#lRZregcfDizw_sVwwJgzeM~U9MvfX%i`0Uvuy zWbe0oe{AYM3l8`G$U>d|Qr^yl-|4%fY5k{m?>}m$H96MrpX6hBHgjozko(9w6g+r5}$ z`=FQ_!k%(zeZ;)CNeD8e+r7OyFcACo5T&1TG9KNFvu%q|$CJeqaHP2UYR2 z8q0R>MeKdEjdvxmRKb8LgB7-CtEXyC6ae*f1k?w1K+|a}KV8K}j?Thp-&Ql~6uR(q z?7}O17ap*(y}79mWsrK{q-8qrlhb?oZXbRH_2v3!kE3-0_w$S&M<(P^Q<~NUy@Ww`fl}} zNf2qFMo@O)I&<9 zDiU0=cro_C9G+0JjwSW}?37YWvV6~4FXJ0W^E0It^C7yoXR{;2IC3rWB8(jM$kfnaLfji<`Ew-E5d&WC> zDKzA14IzOfs9>rIIu6U?uOM$QHwoW8cCzP{$VCv+M z_#NHf-Mn79!ie48z}X?&&EBkpgY;?uiKl}^>LLz@DXcU-BWPbyhIFDmr?nyPC2Lm4 zGG;^_6INod>3Be3nh2WSbWCM>dLy}~iFxSYF8lY|WfpYWY#x*C+ukA5cm1?{0CCnx ztN&SPIsMgXd@0KQ&HR%91QWgoW)DnRxA<&)9R$GXw)7>0PkRf1sv$g~5bOe(vP%}k zZGu#Z5v4*MVa-C!6oPP-8afv)#P3jFb|hC~B)v#9sHTkoI=zPB!K9)?A$QkDu!OpO zNl?5bC~y}IikCc2&3mXBVIj+5!TC}XgGC+boV*>ZQdW3Lu)O53P(#EYMS!X~{rMBY z^ePHkl%hZ3Y>)UOW=R5uCQ{OsN--zZ0rlRoo4&C_9brkPupMb}f%&T-+ghhRm2{XtTD1rNp|dz_XNmp# zxB2Yfu|}v1PnnJ7J-M#%Y<8}+d~6FfH?sMxiJb->ScxaDNL-r28;UwBADb<`TRA{} z;C<-?xoHdKkdh%}^P$wo{JqyZ&2wT_hKK}+&SGPZq+{81_>n5u5rLGA(c_|i(wj2C zgH%4F%4S$tDvnAQ&O)SYT|VG%-_pHX<)sT13dsN#{+8q%u+ZhF1Cv6=3MloCK5?8O?Kk%31 z>m=OU{hQkT?d|@qcK_CPe{Z|LPetwi-R=ID+WjxL`$w$u-IL7DqbVCv2^oEh_y+GH z`#XzD2$a@>ITreh#~C9)eLHY+`V2vV{;?Po=pU0pV6%$21u+G@OgYw4v)X&LjWn`# zszVlJ7I(+jte$Obl}I; z7luuKS=4)k=V#Vo zN@)>sP@cg(?%!+fasS?N9T(71Gj5Btuf@QgTW^0`6XUA|pQQP%w!r;Y5DIIgT6CgI zbvsXm1@Am%VIn<(+uMCNg>Ad<3Ix2z-kpVQ%{v8w981D_D5lO+blCl!KrzCzMYL$# z!aCwHlTGdNH#tzJ0F*~Z7h)8v^RC*q7S3~E)R&XMwB=4)PnTQzat($T-+#{r>(ry1 z{WJBv?x3F6$7gQ$-w*Tec2Kp(9w&_S7tAfzUuZEN7VVmUyzrD|K5Gy?Yw1^mSH5ta zWm-^q;TcOm!&&V@6m}r7lwz_2WcS>K;Fl2XzHi&kQ+jiKp|AwHOM75{ayhW2u z$Nn2Gq~ExYO*%A2VF`6^BCV%h%EjG5p_rF)asO1IxPOYfHi|+PJ0zCexIM%v=Pj;1n*^Al!r^+e$o zeU|JMWJI-ov(`7h_2T>4o!^9_?oW-@emWs*+^$(Q6$tkP@oIGhsr@q0{=h=$3%|6? zUl|C$vNVzY*1{W3i@(+&>STc@n(G25NM0e1*8oV`MTEr8xK2;XTUGvV8w%;-{ON$6 zI&Irg%GSaMaqKNN#s@ye`(X^8jIqtf;MFU<7b@`dGD{h={63WN^fFR*yJO)krQ{`W z<3YgLs~5aoF5oq6m<(p+Q@|LO0Q7}-fOT*K)BP<8d%bQ=hWC$z|5U*x-erjQ2vOivB==@P4A;Ddeo$0?tMkD?|lv>4&I z-e8|$7~q=LBEoo`EP>odW2J0U+vZ>+e<=*{rS>2uA8HRCihw<2)7U=4Z4|MK`q#hM zM|?8Yl@T#kN$#VJW7;m9vGqNp3EC$5qFn&(e+vyD5vm%CtEjQKO1oC=e%FR+FB(Zn z#&|+oMTc z4T4!q%b|fGk$pS?!xFZB(&%gQZO|E?#HptE`T< zHD$B7O;>f=i+g8u5oe(I95OXZV9~@gcE-R(utEi~L(`uRy%hYnPxf9C~}UCN9E`iu!~K@};emf`iN_r))1w;~SktiZAXNH9R}WY~W=8EnG-lfD&Vqd#d?2@gA^-S>3=`D1hgImb6Xo_|?^GfuDU#}>- z5M}Wp+W=*AeC|ON@XYkoK{}wzcS!9X#M}{;h)w63=HU<)(ak=*>=?`L{&eE)o>mFQ^-5%U zrPo6xo?hlT?Kx^=pjIwGv&eh%=06Y3dDH zz7Hr1A?t5<>+O2Q34{>cjFD+*a>bt?Vk+9Zmnz8)kyW%K?0ALPvgC)vpGfH7VC+U0 zZKlG7`q)y>-yqgQ*mRn!L9t)bY$7m*eNP~NQ%82yXqWfgZCR^!wGx-Gfq z|IB3UB|z3~kq~6vRt{OWrB72_s(pI_GpXo5lF}>Llx;rh85!A{+at|GmAOartCSNc zn7P~X{yM)=>5T3|8SEl78?e-u=xOd4E9A0U1IN7@la8%~v{3OXX@e;nxbq(hq`AP? z15My>Wad4xw zm5symPu{U7@>YT*Z>7aGu>dGHj<0J@>5LNiok}ANnS*yt70n3m#IK9{jHb{`%*k$| z15>!KJ~i5oGdY#t9RG{rT!v%=Y|0LkjN8ig)Fc_nCOMLKC*TV0q!hI=$ZRzTBF-Q{<(Yz8s9J?#q7AmvN9TNRGw9 zJ)<@E)Oe;7%=+KOLNN2TZtTIdOL%@p8L_=hmy8^Fylhq{?cR&F)de^q@u4O1zQRtx zO~wc^%COAym-9EGsD<754Gw#e$>W)-PTbXdB;|t;YKiIA+_O=(JbQ^&Q?s@*e@Y5< z$8t~|Xy63egqq_`~c_za1`TVG(ZfL14*I<;e zfJN`%rzJ+(-3t`oSDdGJ(9Qsg`ub3k!0(@ttKb9l4tiGapk@@n>Jy@eTKi}6G1X)q zpOXkp?_ih+3G2`kYSBBGx9A;Ii)&CB-3ydI1cyT)K=Squ&B%w&`%&b4epUWPudCL| zmWqOIJ9OFICim64le|{M$zG%+(~GpEzicyztio^1 zoH1)si)tBMa7x8GVrT0e>(VlT!CMZ;RT9LdcU^CY18*yhs5fXR^l4>zpZ1pbxvkRY zHuYJ_x~esQK_yGIF4&@V(hbEx72I+)$hj;aE`-#@mWmPUL?y6rSy4Im_)}vNgOG}o zEnTPx#)Zyksu$OUTq^O7Bo9JLRkdf|n8ZPDFa8P(C+rT(!pS3=(|l~oiX^b+4zgF& zU9X!^@^?G`((Ya_Wi)@eXu$%l3Zsmyo?!3u3?EF8$)*ngsgiQKeg^i&*1dvq+*;3zeB=+p$UF@^ zdT|iEs6CI(PXF5}u{r&_<+K)gS|cm6IeAfgeixP1Xm9RC&EINCGDAvuCXU);^UD(` za-)v+{N3tct0^co;Kk@8*HZT8vLnh}t>65YRk5hAEJMn_)%%vN!EoTaTR9uC^cuMXSB8ORLwg zhQe}06o?-JaoO-w5g$x8^gv@@A(Qa7CEPn|xjxBWH*bbE49%h<(9$w>>j6$zA`Y&( z#_^a&Xox@1<71ZwQBz#5CQt(U0zB=76r32Hpt-4J*qFAVuy~+^hg8pK6G{x0AXNoA zyx_JX8Ttwt4!0JLBb8GPUo(ZR0Nfsb(s2r3JAFISnbE-55a8FqHk=FA26$ZjK)IfiUdDz{jGfX+vMv1Mg+yVkU z$J8RHe_L;I?6e3}|8srt3ix6L1)|HvKIw=O!9mcHHB6kZ`I8+tk%Eu@U z9tdhMv>S5&i=#bsBgLZKB6T2Yx2MQIyr+TknT5_5)Qy<6^DlvM1{Vaz@q~318E0@o zzHs!b>tn;_NA4$`@LVNECGNyw)?OGK>Jg%3Ac_99t=sO+af*fO0oq2IJA@RO%f~cg zp^34oYKA*VCc!zOZ-Tgprg9hVIy4nW>NiOa|4VR}nt>cF#ocf;zprPh1j zQ9-I^x)FO+x{=Fegrv`GHYxF?cK4KYysv;gHQCO)Ir#?^cXZ&vWRoX^VtvXjN+eaE zk1oahvyR7yqAeNCNIk2anvsaW^MpF>UX^#M>hsU7o7LvnW=@KTHG^9*|D5@!o0T&$ zJ7yQlRDmfzVY@|7tkThYj=tDt=sNkjTbkM+ooMRYFZ!w^pK>Kj{Z1Lfosfw|q(JMd ze=WXk*^1vSXtw6^VP9Ft?x-z>>P;K9#I}Y$>^zxsDva{7_uUBdt5J{1x_j09+3tz9 z-2`XW#~ioaV`>`^-em{DaAOIpm2h)R*ZFkDNRv3B*1BfUJ?{Tvp9z-2z(3IePF0(@ z1-GnwLXaNO4P8e^aR!bL(I(sVvAB%7X>~AG==yc&yL%)~_jPUdbJEQADZB^&qy^D= zXgQn;I5TBE?o)&z$z8 z#Wev1MQ8aq#G9hX-?ew3rS01cP8G^?I6~r2)QVK0WNZGHaRq*Po)PFk>^&XAc@UR- z=JUUzKRKb@fd>E<3@p7GSbFAQ`FP490TmA7c+{Hzb?oui_#S*q>A6%Nx1#%&_-Da)@wfbdNB5=5fgvty&i`IQmBX1fs?Mb3<}U>XYfzrytzZrc z5vV$>?~?gTMlQL{J|pKclasFsDF25a2O?eyxKeh`v@b<;S;A^1biLG?|LAH&&ivbg z+8h+?s_+stozSCB^)EQBp>}YD3vWRMQ@^OQ!SnnXUs?lv{=FeU`~DCrDP4$-TqOe3 z4EZjczO(7xrrveQ^}mj95);trjOL+u%yath#V~~zRRmf}=+V1K*aVan(M|~>R3JiX z%BK5V>?0?=(!i@WsvP=yUQYox|F((?Z~lE0E6@oU$l^^RWQYXAFX5s2=k>N2cC z9!(+?y-iJR!Ht`gotS9u9bc=+>3Ld{p9Vk&`tX;{TGVFJNrnVZW)!4V;U!LQfC$H= z`hMO`SeM?LeyaaX7EG(!vxKPA642a0(wQlTgpC`Kti0Cgn~De;G>ZuGw}>D?5&1R1 ztHz&4HdLl)^A!Y++~VXN`NbSWhLL*-NxGWK+LkKTdpkdZQ>@oIz5Q>-IVwA?`=%4^ z-XE-;KZON**U7DR@99v$lY*yn8q^n<4J1`oAFa9L*l{r@V6*oJ62fNh4>>k_e`uY) zl^w(0AM9`jpYUwYeJekB*?T(nqC^-p_VRS<%D> z_c=b>w`1Ggm(#sSZK<~HUSvy#EcYQ*xizPci%H+B56%=?Uj#$@TXk+kSFZZsn~@Rc z`*@01*i>@EKy4A)@t3Ncq7 z!ifL~j;91?FiDDV6b%u-e`zsWyB|1}v1y#Qt+v*gzO-*;P(QCR!l=(Gsg^ISU{vaC z@Oz=OB1rPw6{0^i_xC#BIU$w6e^IU7kYH>{&a?}5nlcRHLZ{_58Q$efYUXkpAY!cL zG}**{WEo6PZ*n{{4e!Jdlh=J!`4{-ej*Vy+gTB1hk5x-Ql+hg1eYE}#`@35oaL^_D z9qs;Z+ePi~&@QTOAkb6L<%YjU zweSM$#*-O1I-uQuwB5%=mYnTBYWy`x z5xBs<92%kfyBl$jIV=AXn>D|a_|~V_q_-Rb6OYmkxtRQ&L)86UfC#rLcgj?)wXjZI zV?T6*_ZAWYv{>RToGUljy(e3nRgIR5XW9lSa+MA`9o4$JhUN zHI9c%fXF?*1?>}Y{Ko5iG`!B`8o=%|KCw<8710Vlp|yEpqs(XR7iqaHY0+#&d}OS3 z-VGm@)A76~)^Y862PgGN@?|+oYU=%e&_vhg=WlgCPMF5(=U|r(qc+kpgOF-3++da7S3($EAaWZ$ReLd_GVbR%L6yW< ze7ovV_EdG#qvEMr&R&d#!740;OAq*nbyJr$441- zVVmf|;U|0uw}?wfhTvM>OnD36j&%9$2z2ezMg?fyqOkA;T$6_>3iG6V2+(#xsS>qV zc*-Qp!gUIc1dN}`Ayzmvmn_{=L9%#)p_4(?J=I!x#yZmMHTuGJ-j_W!b-Gtzz|gR8 z9SOBxxGqP-!gZ|$50=`Jfk2|gR?GL-$x2;pi(PC>U2M~ZrHH`(sRA~5sUQ%s7p7}Z zfmmrW6oKJdSi}VPrCbsxO!S3`=N+ZG?HDDsL~I~}{;`D8evE5+LMZ7U%jYApyzoLq zso-7M9`L~vlx{EJV|&EM_PqVV_SV9S*JFCKBs#b8j!-xRrS1Oji?}+Yy5?V8nR*gt zU+O$L{uhz9iFYjApo*>j+cY;EC?XQ)sJKUvWj8qQFYtU*FmKDBv?;upS7tSaKq(0t zqhUq`8aW3}!!EoMVi0!6OWtabGjAvn#E^0bSLi|TW8sGU5Uxqw8=5kCa*&%64{~$z zcV2D#!0U#w(v$>>dW>_?r@Lv&_rR^2__bP_+eI-NW3@w#kT$^L$&7%l;4~NB64IhQ zkq^A^N^s?4^%$#K(E6#Yh(xr&%Q^nfGYy)uWHNRk{$dBr>zgk^(Q(n4Fw{TNj_M3d!Z}Mfosl4nr6_;I3udSE< zxE3xi`%Q7#^C{V~=Wlt)&F*+2+HA^k-lc_9SrHADg_Ipe-Cg-EJ~-SnbSpz+LZ!z`N%O!w(z<>s3Dmgwv0_-ekdJ+2+!rC0v`*% zEWQ2eN}@b!WnPoa**h)eC$nnjNp+2}U|w++(Q>w#UvPn{2b*Sa9!v0=teoFq2&1`6 z2N$D_^F|JfnuaTrPn@SQWZT&j7gqNUWiM(kkYgtZ z2HS!i873PSmeU0B1i`|4dZ}&mTQ2sdZJ`t+g7j#)m*JocwiWPb(;jTGQQCtoS_gV~ zU;PJbeXxZ!)*^XANE&R(cjvd{`z>@~&33S_IoL*DhRqUIZQyCcb)F2-&FLYQrqKOC z3{7}_ZkGm^h-;>7O$@FxuV^5{?^MBalGi04OTQrzs@omSC#LnQ7y_?jUg2U2w=asQ z#`yFS--ZZH7FYe18OI&%xf{s&8NeJ=(T_YKYm7bynTmxkHpjIE*?(Aj*gp7T^K$EH zAdudb*SJJ)#V*c^P0zFK=E0!dKWWYU3DcYu$azH7?m!|7N!WUMLg??G%n7)EQnw>7 zG!zUpEB|b*g7?A3k7G`6H6si={ZAGd;P_W>aTwSL&A3}SqU)B$In2iXX3Z#Eyxa%! zX(UHnJ;aJza!BQPFK+pS*xrKn+LEvbkPL3ltTZ>o4KCaSlwjx6yKoN*7Tei zt8;eb;c^M{))SYD*DRcU{8|%I`dZQALo*7prtP@0rHcFgqW#McD=dE5E|o5RIic{S z)}mr7wi}`iD5ur#i=P_bAP#t`-8&8w^mUp}QGEg=Iz$n6%avv5)>E@);^IC9MtbZ5 zQ<)2rrg@bUYmyMP^^WHNsN=Sx9CQB{_=k%hi^B93z63puP7FWD?8mGFT| z1QfEy9GEy(X&Pw=kyi447%;s7@~O>Q&~Sgt`ZaZDQaNZhlf%-2@kDffoC`h2iz(yu z684SKaV{^)C3txCi(;EBN;tP2nn@QFL~+8G&e#$?^q6?4Vju?S46rCM$y+4usPAGi zpMBP&RWB>LT!lfOE#sc|E$4EceE3{@eY<*ITt;&TX_d9J6wCcb^rbmEg7^oskk zruU?3RAwq7*3__C5VTEChu}=xc!H@tndb9kYw@GV%OBYu)%-K6FocC|@l+H$(+GJt zsDd;3n_yW~h)`DK{PPYK(70H7V%4R#i4gX4?5HbLQ6%1Ilad$rdhEozj=YB`sqv{o zsS1fo>KSvV7m?8uFDR3J1BF-|H_S3impQcbKu&vbq@6EQGE5uzf$n(-K_ zL!)P_j`?Urw)ka@WrtGEPGnD9zDnD4$NA9`?yD<4d|cmwKiQb%#(+LOJSQ>ISwu5+ zp0&Tv@neCfQ4G*jOvR+ZKIi7zr~Od?i0BmJx2lu!#l8W?}kjjv(rS536Y zF~@qaV`lWA8}90ny+pm~3a`M^mnm9G!NZ(9JuVsgf_dertv7AzH)pmnC#yTr|DB$`o6L~>woQM z_^872==(5h!L(krn{~JQXAfn^va9Xw@$5mR4k_=LQjcVhWk;1dncb&4_bdN#{k=N- z$L#-Y@BCw=x~@F`s%h+Y)3lp1PVfXg<-|_V8k6d#vB85q5ZVM!+I9zULU!1NqN{M% zxVo#SyBfoY-K}5P&C1r|6)O>mNRC8i@GRMJB(LO1j^yk*k|T{Jj}}W7YO0Uw=YQpBy$s zgMLN!vx~&hYdN>e8IZ#rH$nfPoK(C1-J1iQ483)j&Ted_qw8P}g3nXK z$zZ@k8BzTO>)xzkF(epjlzM1LY^SVh?a(4wMLAJ)ShP5SXUVD^QBNvLvY7!~(sith zb}U1GEYSf9B##+HL4L>>=^;7-f$L90wEmrP%5u)ic?T!iPORUN$cUw~A#dl6okYi` zBvEDF8mPvsQ!ry?niIY#L74@^0?Cp(Cp1Nd!_Q{CsPM z&A`pUhhhXrz4e{5%#rPdAl;tWO*$1{3Ld`s0>W78RA_8$FD5*tk@Chsn{frdLxWx* zi_~=1diaIjnxVCx!%g%M)A^oo9Q^pLQNML~=qcjuxWvUBUak|w-42_5w}wsUu9B#V zln&&2J`=KZU8T}O{^(LEw<$H3D=n5X zbJ@&7c2nvbxdjz@Y;5!B=Je+Drqu4GLTPCs_t;!+skD$OY)VZn9WCUizm_|Bp!j@l z?y>3VM~;qeId*J1JC@C6w~pSOIM6q;|M~npkn+d!)0qPQ_hsEXdaxTbuP5Itbf|g*CT%JxG z58V279$uf-6CQj`#5Fq?`;_}VrP+;vr#~QET`lKsrCbbO`@pB?k&_iQp?powji{~8 z$5glcF$vZCJdV`|bli`d(#ZX`k39V=<>AfcWMxR-i|eZ<|F$HN^?s$S7Bl)?(p$AGS+^r z+|YW1omQ;OMCGdAHrOn|#8HMDnAX`@@!?tcQK>6=59P>poc*H-^c>!&+kZw{zH0 zX?>17fxK>_iMID+ntsoE7`K zamiZziqB&sT}2{l?BvwJs{$jfXmKWZmEQ==#i*4teW8~j;M?IVXta@aSNE~8>XQWc z?~ubjWO3IekXPMVIT4OD1NSx;fEZ0*p8N8y(=!m?n_6ZS7>XD!3s_4F!n6L{5LV#@6s9I^AiX_NUNx>Jz-7iMmUAZJ|!c79D zczvI&yyoebm+_^6s9rYC-;M%r zhe*6bnU_YQHeHQ0V1*A$ee zWbrP?C)O>%LgeaFRn@iXD)$+qsMGklqk>3k^+V_rw7pBlf=C=Dd#|hstL$TM?Br^aYyBtF+`=eeiQOM&wU!|lBntq3-IY(RLyVX zGYuvouKtaH6y(ecl2mx!E%~lD7(mn z!~9@NksZxN#+!>+b7oqKOf(nS*<8fXep_+00>|s4<#&CPc^kr|a==fGiX@PS4Xk~3 zB9D~1$XAyLnC*PVW`RI8o3!ppQInlr)pJQUu%8hEXQZ9ZL?Xkb_#wx`p5c)y&zf8U zzs~WGQL*V-djXk>GM<+@I&;wLFMS{Ost?#D@;*@4ZSmx-y`lo@v+!t^Z+CS`3DSF} zEUHV&VtPvnFK)uAE_o8hqtcmQD)n4cEHZkk@;Lyi^4|tHXO76ndxKiQ8cb+cA&|2w zp|2aYb`}If39W#I0iTEXeh!(fyu&V2*WNz)_2nOHO6Gpo-0zwDx;s6{T(kTG4&x-t z|5=YLv*r}OwLHPbvDooP?z`Udk4Yu@$8oWraOa+K2*giTq_{GVgOA&ofvnW;@LIVf zqeQARu8rLE%u7&;WctphZo^y=R9&V_buu}gOy8Yi)7yx-oScYat=`I-_2~qu_ADS) z4<&Sdm+3@}PhBTk6Q>#zetf#u<9~C81yM8Y3Vzo z8zhqzlF|cEva(foX32A%|KrkxDAF83nd`{_(oWgqZH`XW1D_50L-IDq^Fem9S{SyU z`Qdf;*aS}!>v1c{1hruLF?j@Z{!i>sU$W$z7|YH5g%OE%TgF-M$N&kKXm zuXXmu==&>tO_2i75P2iY`AMA9MJlHvH;$n~uyw6p)njfn3i;=J zf-N?)*yfuG6p2lZLn-~kP(E23BF)I#5EKoE0=_!KxHqA(G9!dn1q=rx?lj2Skv^*D zB9l-gEaxiKytbj#I)ZwNptjCR@wIufwq99W@%3?Wa2vMb>kZPdDy{A%mirIzP+VYA z6nE(cHDyqxVruuVQOoYPrZQk_X-BV+)$VsQ$>HHgNBU5jJf0|Fj!0ru7C$((EHM{n z=t3`xQ^zM<(NO$UG)hK8+dW`WgylCRoHl;M_4mtSM&s~?F`$jX8p^>ZobWMGy_s@e z!W$rbZG^MpzYT;Au$o(ep#4^@_|$S8YHn`DEkNi1>v!Y6V5DdtIXk2`4TI~QrchtH z@(~w_X&Q8E`F#gGIo=_5Dml*0PF@e&mj@d1+v3RgnT86td63C;do+2I=@Emv6YR)} zkZePysYVf!$ZdBcK-Eh!Ic(_>ZZt!qHP&El&?#XvKQ77qXfD#%E!B$l-D9jP$p}<7 z#=B#RE5m5xDs*LFc^5B@FrFN@2FagJrp@2fRosl>WyV&SnZ&Emwfh}uFj`!gEHjVVdM(R&*UPO7MpD4&v4pOT3_T9UJ zhKaS`b~4f^AUu{MV)&HCB)+ZHXc{ForZoZF(3sK$kdH)M>&ymj0!UJ0+PVe|?b}0d zIEQ^=SWGk(p{58wQW~M%Iikyo-vN7PoVoD3gq?Zk!tW7wmZKi6O;-Psu=R5D!A}IOtN|{ zY;yIgfx1r2Cif6q+rSq^ghM0G8Qq(3T_t_OmlX|?FoQBfs_SbJY#V`@VUdnIh%t*U zKpf@O9W~$LrLF<+m-79Vrd4}~c|g$^(~9s7K*m_W_T7KJrjom-H91+~=(bX*)C#gG zLn({C#wRs*XqGZNd`EIpy2gBalaqs?e^^tUFq&n*$$Nr3T%Kw=c|u$fZaya`ISC)7 zlm-LNVpBvRLS@O7& zZT6(wv9`&msyxIhE-)%5hjqu4Mk4!V+iS>9#M2MD3|LPZllyM*fZm7FIAdWSp%c`w z&0C~}S%%%G^gik|DXYk&uG6>+1PZtjq9X0>BAk(J5gNMsc)$D?AVa_320q-wLjUMC z7PjRSac{7m$1wCt3>eZc4?qe^+{>fow5t459)6LhvK0>}!tpo_v5Prn93Z1T$-LE- zaB29%yd10vec=z7t>hLP)x&P{PoWfiYNL4hr>g^(l&p_1)dgFn)t9s!{o|$ zNH)^{2R$&Itb9iY)y?;9`3AU@iHYlXNTfT{Je0V82-d8x!?zaHsTBJ0{HR8DsE=NC z#6&rfx6kRTFp4xk@{nXqvlcMXyH6VN>T5p}gcsa32q@89B4w~~^mPw2e6rd+6X+Hx zG8j@0psR`)nx}-dl9&gyz`-@)28unR-whnbP8*n3P+o~X0Y2vB>xfVfgsdV?BsXn0 z+ORnA^)ID{xNc267(Ia#-XH8Fk&h*mqE!nnWn|@q@5Yu!Qm}4yV=Sy;Rm#a~2K%SD zY@_mmY~S!8_cEib$vtRmADpUSAtYHy9^@2Jit|=-4&tntxl=h0C66StoFU=_5!EH< zlLhr(iTGNbCyo@MqPpiz@JLoDemv!y?np2JqIfq2G zr)&X=cUmbz1tR!48mXHC%P;!?j8pq13A)f(nedWbFB{?$>z7}4Q6@F=-$6x+s=QA# zDE6}r!79QLvAJUqyr_u($;5qi$kWf*K~R;+@-or%5*$y^Jfsf8z+o;bq?KCWdEcaf zJFcyh%A%TmYD3e?7hK?iy08423%DO{*o3#xBl@Bv0M+uvWciYN_Hg({s*h-Q5&vlWJmqZx~QTal!=Rb5@pZI}0~rigDOJs|*< zbptNzhLuK8KR7llZ{Dzs)q6zU@(p09P2tezkO*^ZSev?C651@I;qBu_gO5GsIl{(e z=VT7Mn$v86N|t{aF2Fuuy!mmm`T&cK8al8LEGuuJmM`88=JxqjutM5)a1h#=+7-yb z!Ox*~!PIL7p(^Uxebj@2o`$(0+Bn?b_KZQ?CG*pt@(-)?=$KC&;|=;CpK+N^c?@!o zkYmIsNvD6}`?C6&VIOuX)qP?DR(*=2c&Y*QXeaSR&|2NlsMXa|$PddY?{Y40$W(@L z;|4D7c7rDpq)yJmo=^nE$@5L&FR>8CGU#Dyos1g3!+Kjj(1AS3$|mO`XvFV2QCo!!pYjl zA$uVgwU{R=^4GSB=b{NoxhwMmApUV8p4ti<-;does?vbnm2#Cblti7|RHgUz1L-hx34n zYIG)ML%L6&_G-N3eG^ugNaA0Z8sm0@1P8z&5!JsotvM9{!kF?8$@aMzSERvG$RoL^ z>Zjjk<^X$+Uf{7BiG_^ix7SAPp)*ZevoH@502~7>HTte#;lv zN`I+3tzcAUl9l;n`MrDmRte(CmV#tr31freQHow9Xi9;xtziI9lacE}qo4?gNYm2q zB=~MKOAd~FdAyXGOsR+Mb4=xd`TJmdf%01+*6)U__==YP9PsQ7af_RT{9SW@ZoF%$ z;ovQ4UnQkw4Fm{}IB>$1IyKaC;ML3!o|R@yteo?54`>8OM@r6voNrmOa#^^BU1B$U zjmSu81b3elG>2(JdS0RuugOCUtfsS(>{Tww8!CHigpJu4JPK6YAXK?1wGqN(G=9U1 zW88xhTp+}pte%f}Uw6_Ac}K?cW{?xbwPZ`)0_F5dvH~<^TLoP>0xBsoQ2K^d&tg~J zzTJj`%G*LmZ*ilO+z<3}mEJ97TzL!N=Ao9i#D7D^f4DT`1DBH@!MqZ}yyAja!{AjH zycPzpdF6TiHe&_-P|x*MGF2SAUNN&p{vqd+E|&4PYSk*X3|FnO;gYdkA75&8`r$}w zMIV0b90;Moh06LcG=A+yc|2U+u_Tj*s(x*_|8gj$VF4)O8a2^u6nkB5z_fDpb%uW3 z8iFlEa&=!aw%%~n6Eu(LGyryxb8yuidjG_lVr}5$>s^vs-a6z~n+Plxii@M>C{m4Oz#mCg z`;CnT`sqsalQuSu)a-AHz0ww7o0Gr^Gv!anTQV;$S>88mN)vOXOjz7s8Kj{*HJ%|9 zIH!@yfzO7IwwW}uf$?KSCaWKd=fLMuoYZTYQCUTTB%>i&{cC1J=0QLIb)*%l z8zTv?ZgfNme||$nPCaQB^;uKY=qd$RGVzK8&WJN5wP2E@|64K*(R~)Bbkm&WA817* z1-RdEe9DhKI^gawgDPdHVm7Y%jqv@h3*Aq3Tcj?z;T$N@@B~a$clB@3Hur|(3%!9Q zq?w{8k;GK&t?8|q6gJWIZ#IJ$&5~XAo8fiQ{qa#8@BuC%;S|12fT}xJ1~AQpwF#z~ zp!<}@9|IEA5*7CRftdIQEc;}#8hIv!QdLt~pv|}rnY=Tt6%!EBmg6K#Y05L@OW~Qg zJ1wmp?snsGYl~s5+qfpyEo+9cZgZMgxA=+eBM92f9Q%fi#y?XOFpjY9+sGZyY3gFUCv;rPMC(9almH) z#VkYn`>?Z`U!XcoV5$X=kb1FN5Ydf>1xLe!7CoN)!fzK2>tJ3QO$IpU7}7et*M?%hGNb=7cR?Pf!7F8lxugulk_xIS8pV%7a|?H z-a&kgXn5Yr=o${k|1y_2VeU=n>N0w%Zx{`?Y|UIA9sW(ia;#}(VMdjl|V(So8Bf6VtR?!y#( zTGAM01iTW?E3Kth;|dw3Y5G-WzM{Q!&sB<>WL$TW;YaY*j;^{71_lC8Y&;mt#olnS zj>gJzkC@X~5>%RY*d@cYei%^liu8%lXonU0EN5oK7_Ww**Ke?7HVQj@0v%_lo(fU0E8xTV2bLD=xQQ8<8b(K_!9htim|P&6piO3|Ff8~2I| zjeBkwR_jV(L*}g^^BYz=z-~rj%&~koOP7OSz_(1^jHPvyCc@ma!YNrhXW1{rq&*i6 z;~b4}!+3RXIE=5RiJH(c_t$tnH zOWN?A0mC~2*SHc;_Dw`$(AZqPKwMUq*-=2APGN-4zoTvv+wuf3VVk!7XS-iy%Dwcd(wQ&kpMmebHw^WdZfY;bBM zzAM08)3x_Bd#?Rby-5Z%I^WD)VFKm=%5HT&zLJEZs}X!HPgOsJo#4U`?+Dlg!T9=6 zj0Igc_^~p!l0U@APgcIO!4|mTthL%CQMZ7a1@7@X4)t|Ox2R^(FvfabTQMswNNC28*(nKQe92SjCV|6D*g(Ni#}I9yLa5fIz@6ZAjKruJ`KeHD=W}0TccU+q5x> z#i&&w=k~R&3^gaOVgR<1s=%)jv3@kchr70>WOWl9?(^02jVRS}u4F=}sHf#;`>LK= zBYZd^7T5n-+fItmaY^juc*>;0Y1`mxO<@uPj8z)*_+34aa{E1e`pB1#R+6t9>Fd48 z`XpaL+VNBpjZ!w#s_!-bq*tG0CxWc&Y@+Q!guml`eFJgM@e#LnO&P)F14}FF0PI|s zTCI1^NgPCx4G*vK(^PddGfd^Jvc9P~E4$l=+~o1ls?-3-Xa|^|u3F`e)G|lL>{?Sl zqRDCf5S~Fz|K0Q$EfmiF2M0$;4fAHLRWl-4{ZO)U!y}!nPdPr*4}oDf55eTY?=)L| zTvkQ>AXGcx@*c9hhcFP>UMzr|h4CPR^_?_O3dXczeH(Bnr`pSId@7$cBQI2%tRl$L z8R23yR4reEBGlj@;XcipU9x@xN^6zSv}5hG3$a2RrwDfKj30NjGRkBiqnSl#GRzb~bkGatSt>uxnIEEG-^!?CWe(VZM|mz{&& z^vHQyg2bG2;{YF@cuT#0-n<%h>Tanm{Hbc+M+GCpmh<}c^Yj}FUZKZM@QWzbC6+Fd zwO4G2WZKN(016XZd&Lv`m1OmAS6QtZ8_Pe#?De!|a_iL~=lLm<3`dCp069vqQfcHS zE8S{)xS#Vw33rdWS@2SnW{RDixd4M@ml5#&yL^2Z$u2*;y&*mgQPP$q+jO43@a8Ea zfj;SlB-$2Y6j`xCel&}yv@5xbAB>!DyPxl9v_SvvFxy_}Dj);p^c&{B$r28?KNyH| z8Cn4}S$@k(Yo`i@{V{t{F8yPRu0BAm!zHl6-L!COoqR zx{_6RiK46J6~Ah%J0@MfaHpj$Lm&u)3?+kuYpUOM)iQelv2O#3VaI(z3W5kYEeXT4 z+LVUYn23+^{f_lb)5oF>Mf#*?D8d%0t#)f-rUmTq8fObR6tv~5Hz_SYk7bHDn&Xk% zA|cKN2kQbe7y=}}3ca~Aly0k>^PVN=p-MbroTUF)7egeRyohfvyG9rfUv?;5aw0~m zEkRo(8{4YwZQz*)sBb$ouDTzGaOH})VrVep17{moKNQnfwI_msv(X^3uqI+_g-tU< z_qLWxS$01TovJF}0Jq}|`=OJaLazLRG#Ne47RHSJnfFy}a#A_v!(3V*W&5!YSIP== zg@;+u7|o@~$J!=2PZ$!Fvi(>{?;XL^8m`>43pN@UPc4rdlFJi~=@SjFKDWbq?VYxlue<)Wh~rhv#J)B-M4yN=VHY=4mgX4};4p16 z^&+eJWAZX+IU(f%N0GpWc*ctx z1I??*s$I)Xko+if{osbh&I@V#B1|5-p$2?7)J7LGOfQ?p{ zW@bm+Ei=bSY^S@V8?8wU<1VSPsV#~9J1bR5wmh`qydb=&@EuZvO zThdjZbhRxBd2<+EX-m51ldiQTUH3`X+mb%;NguQ()p)+a>Z+{^lLi89%&`R$(Ybce z=Q-F`{D4n7(3Z5rC+%oU@@o#YXw3mWAQ!s|6TZ|$TPfcKwYDy(P5Dw&ZKWoC(qvoG zA)j=pEorAu+S!(L#3vnTOJZxy5p$#^i3^@w5}vlC3qI*WTM~o60Ae<@CRNUcNtLs0 zN#}ghxiG1JBljN5NyzDwvts4OK%X$m{qBm5L7(})hi}+?uMcjNU0|OwDxx}p%j(R4 zMKkk~M;6MUW&d%U{U^Y+#L zooxbqmrG#C;y-ZlSKQB4_j8RtTxH#%-Bj~vbJj4j4Oc6A!{yNasa1*cP(0h4sv}Ju z$rEx)7Jgq|aT9k>S+=1FXEp%UR%e!5T0bt^B`XKWJrUo@m)k#jHGv=@3G8dMg z$*w0m)7r>;-ugFOd9^a{CoM7lAd-bJ5riq3+&5iQnD0bDc%?opvfjGhnal!)v5Sj| zD}UMiC38>6 zUnWJ?I}^d+Y_61_I`)5SS= z6=QK>F$j(%fkGwXLG4pl2X}BDRUH{24XQkyHv1? z76ok)$V-cFM0`b_yjE!D6kqCajTM2>p zwJz>iD9$!u6T#TFH3*(a1UqjANLLt-92xD6{3NI^mZC~W(+?lna*HJ%IRb$mPXyb$ zVY)B($4gW)mCG&cpUccI&J-Ie-14wcd^8bk?N)sM($U3ft|QG&=8xyTQp}!=u^p%8 zKS=~j`5RcyxpXmI+`mK=blTIirrZ;WHm;CnPS$n zm*c2ZX(m%j6;O4lCxVlIzxsQzy04kxr!(LGej<3Z zGyV3Xt|7OibZVg}gcly%VZ0O(a9c0F?O0x25*6RxOFQk(&ONxplqpIe(%-jSf*$V9 zz$Oc}?K5AK-MVDDKx#&JyY+?}pXrFRz#g%-9R9q+9V ze0g-~Y`)Z9{Z;D)hJ86AA_T`xGTxjO?xEkxbAB`tz|JrPWHZueyFxyY%4G^YgiElL-M2K9dOk;I{@A))yg%cKb>qc0d}K7_Zdh#pb(K3213$q1;}JElbu?~9nTk+V%u$$-swJ# zX~*Q5ql?IFZhvuUVY;D@d&?Cw#@(6enOqExi6&Ano{Nafyr%n5^~78Wn;-0tWsWYk z+Ymj6xlbuez1>xUct7GzH#+Z{%kD1DGE7I9kM&fZO(0y!uP+rBmS%UefRJY?AQ$(> z7IZ>)2HUGyd87PFr*{zVZH0=SZZEn$XFzNvatA#ZZrkgEu6@;dTHVT(L_QEBF zoK`(T(Thm5g@$$4^WJEQ%kS8=xE~>$&Fm`VGmDQOJ$!h}w!`Viv$7_-_f}YI*25Df z`*Oz?G>`Xo-zX5eo7TSMH<|M17F7P(d9i&|9P#PSY@b?M<7oF8V|#HdR_2@yibvRY zZ@6hQZ$>;z#?VX1+B68rAfSNHPApyx!6<;;kbTNo2=4boS0n1!sXENJcI zsod;Q-jLC<+7`I8JEYjE?ZsbXoVfwuL>I%QiQzr*Ym6 zYTDT?E%?y+JtW>O=CwAd17%RC?VUA$jkv&_BI+?_LRaKcd<@ud=Cbp}{2X?^ zwxvAuNV3NuNOvR9XKx15j?9>CINS=HrdGw+SfhIITE|9td}1)RzV@|5u&1+bV2}zG z=1q;8=$Y;)b%51nO4U;sJAw)AiOFJdK5E-2s<^wFios|RLgKTFjobNBGYA-PcOrQF z(}?3i*sn8BVl-$?_x|pNMzpCB!$G%nH=f(!?8iK_b*l)129)mmi!p?6vQ-B;v?dyt z?PYhj7Pa|@9R!HV_cTFT%ZG-Y-YcenhTaEs^9J+bS)$#WCXI^x$|EFy?=+1#HTXN{OK z=lP|1x6;eTsD+}oqP4nrZV0MPI7Yp2bl7EuudQ>xnF#iEX2h;gJv$oIn6eI65BHA~ zL7_9;o7pexo~Mhkz@V^{2+}&0AJ^$i z;2(5n9uu6N<1ip!#|1eHYx%-{PQ`#satwRx8H?afCyCv$jy zNl#|mQN;UtCvQH^5(KMJspf3c*}22oqSaxP!_(R=|H7wb*nFHh3qw<{jI)`!{4vh$ zeX$2HpU2=anB3oz*PRbI%-i5|i>QRdJSASred%U08AOb}4b7Re-cWR)S}4(y+NG6l zIrZ?oZT20W=e!lWCoQe)n8)CKIT8G`uZAaQo6nT)EAnDGQ?lYhmZNC1nG(wrz4k3Q zy`gi!#W@|TaSYH_`VQW@60e&9+(L`^VDDjdeG4sGQVsV|u=)ZCv=>|pc-7Qrjp|s%k9j0g=CN0~l+^EIq%$n(Ht)IJd+zX_LGR(Ydj73U1b-IDU(t|}3XL?=Wo)dMK#^p*lb>v*P+XRcM8eYV6| zZpCoElu6{`u9C&*By@vts~E~`ARBJHxzM8Rm?>>I5!|t0+=KRX!D*JZ*q&4B2*eMA zU*n*%YtMYFRydzM4x(IkD}5*rh<&|`DTAB#xojG}fcQk{rY4d#cXt00puCc8T2O%nJ zJt}JMzArvDn!o+T7ao1)fBf=y--w&KUH()wdV##O#Qi`c6C`$6FU96Yq8@w@WJ^f4*f^P)-gTuH_1pDwk89Wm_P54RNJ^1zSNB#fn|2S!rw6NM@lD8$G zv+SQhN=6(1O%TAc4l=<4aPmPR$N_UMI2IHw+x<2o93VAA>>}xzphUhRA@}c%{y$~( z;Dj3S3#7~i&vk%QwLEFs?fx4Nj*14s2f-oA?grj0|K%uMq87%i(xHCNiUI>C4Pb5jOET7jFh=5r82@dLv0z#=7>={RB91uvsO#oqEs+#EmKal zc8R}~(PDw$uToYxdfZaMJard?KO%0LT7>t*46eRC^H$eMddQU%y;NTKOdCZ-Nd_JM zO$6K}647mfT#J?`PaTc)ZpC49M4=!Uiha6VUWk}~RX zQFDP`Q8LE3dwrXsVuAmH;9mwCfH6t==d84Pb3Ql;v^+60Kt@MEKlP>Bo`T;TedFG6 zbax!&`nG7;3&+VoI6f1?6Nd>$&#elHsw`B8KHonvZDL$^7@enpRRsSf>$kYIy1a5u2i+&Z4&cfFPDD5cP*xQYGsMr7L-#-Ec7~o|IPyQ3R{}G7&|6djV E7fy)n}VQh0)niH2~})PcMb!B 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

    a&ndIJaaL0OdJCgrC025 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

    +_tMHkN*-8xGz&)Um>FXG4KbyWFN@swo8 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

    @s5RZ|pGPdGts%89tvPNBK3-A+VkE2) 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%0Aqa80O3o++)($sK^D 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb index e9bcee25a9d8998d79a4cc9485cb5246f16f03fa..5971d0e3dbcd70ee2dcf381803a9558c4a2f92ac 100644 GIT binary patch literal 205548 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Μ_WIy;Vr6}_F^l! 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 literal 251464 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%0D_lqH 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%0D5~@jW)1Z<`MVBLqj-`U{F9lk^hdWOZs(w*%BH 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index 233edd88b4107ef0127227111d6d5e602a2e79e2..e5c84a7fd44384e8ecdad34ebec833ab37d6723a 100644 GIT binary patch delta 6920 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 N8S_E@LF`Fd{|)IWHI@JX delta 7540 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index eab06cce16caed0e27d9c033e7fd219a3aeb8c36..ac97186460e3b8907647c411d48de34a23d4150d 100644 GIT binary patch literal 510976 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 z%0AQ~fG<86EhmQEyG<~tdVIv0Ju+LHDXfLXlJ9w}7&!wgg{8?B!1J$CQwV^2KNK;h zrV4iMr65mD=d-Xsg}uc>!&Lq3g{>}5Y>jcM)GSSfg*W;2-2h48%sTA3QgLEaUw*0q z&)keC4zaLCSOQt!5rNbe^b8o^-2oK+X!E_1=b)y8@WZMgp0ms|S0TNgXW-m}NH`9J z7kGO-L({wy@pE*8wSe#RsJOX?;||d6C%SPXP>|c}aYNg@SI*pBK!s6fW

    E~#Dd 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

    H#}pzvE<5{d}X_z1tWL`Y`0m{6vlT? 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 literal 549376 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�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

    S@%mRB z&yxS*ZqI)_rdQ()_F;p$OTM;)gor0bJV_j{>obUBz>LYA;}gOQoJ^tP`d8fU$zg*n z2e{PXbd|6+v#u?Aa;eI8KI{0qMVuJ47+PsoG{ zi@#`j5%W_%hZi+J?H%tGuM@`x7o+>@|0ruHZVli26|l=oh@*p&;&_o+N*sR^9R7o? z-`TRf=B3Sl@%@#xwldc*pg>f;c*?oZKg#<5^He z922Z+jp6#ptBK=TQr+rrbh81{tl+Zm z5C=(%h_@7!`@Bf+O6)yHbYs;_71lx%JWOdD~7L{kAvH=ySZr>mc6FyrVcybe+UeA9R+6kFBALd8+R))duKlp3OVn z$#fI1YTjKOXSN>VIB0u{&lAsW%j0nUM?rQQppP|FwtFx09DYC?<-Nr_nfEcz>GQA5 z`W~ zd)gKDj`|zL>)7~in5_^N;5ZmA1-9sJk{k!th)6zCay%_>7RQNeRK!P%2Pn8j8t@q2 z8W~_r#BWQ^7N0QQtd13*V?NHjnBQW2%r;&e6P%Fl0sq|o#PkOKtoH5VI5SQ%FD|?A z&g7)Hm$!Vfc?qB6=R2mDm-LPg8}3fd9QQ=458`h79}P3&4YBVB+$Ub!^83w8`3`Mo zxCg|M&q~heO8XoavpEreNIbjc^Fznuk5l2Iu)+Hy5nn8h6U!2tMK~ZPv^3(&BECH0 zkNN&EK3{S@E(4&$l@Wg;;;SOQI^s`?HhkUf|sQLXRfW{_~!C9*@ex`cSw#87+w>{OSoO)6U_IB z-(&tdJn#n%w%|=EfbSE>1P?^~ka#uA4~wJzh&cY5!h7Ns?9~3Dc@Q2%)ve$YDZnq9 z9~Z~>>`sW|+x4f#@3g1U_u@^=e@f01KS*&5oD;`G`b)C?|G#ho*oD7J0sJ>{?2+Ha zE1CZ(j_+vvWnRYbQS5=g#qo#guZVZH30_UM|Nj@RFxv_eQ?B(4Df#0$CXmrC5MezI z!b}lQj(BESA6uLy;#q@8L$-+L5U*h!HS!_wkYaQ!c14Hf+swYP$D=9RtU zC0Ipq?7~VBuN?6z5w9wagRr`IZe0IsN&_C_TH-j!>WJeZsFzaI{v5mQJG^0bJz5XT;AC61R>Z6e-Ie4OR&#RFW8I!FVa zHk~5gIpSR--d((p9Yeju4_kkI#h5wLiRXrtQgLr-`A~73 zdT$iR77w$7FnkEG$A(LeH#Rp#@)2>j{f`w!$_iD?Zx+WZqEY76{9|0fe6%>8CAV6K zVU7vjCizs`pz$eX<2lBg5IP=zJa2EehU$I*Tzu{j$3rw({DAGksp3n_?-9rQf*C0l z;{D-id7n)%>>m@D=^f+q$M5b>sT?oBG4Mc2wYcA8{$QkoSrMNd@i`Hn8}Wx?&hhyW z%!?E}9P#-wq3JfE1(AHAxGlOx;@ILx#E~zi`|CdnmP87ciX&epZi{ZYIPw+Z$RCvn z<2Tv4i`7zq7m-h;*zW=RW5Qn4x~F9T47gSt9d1ae6R$`9Y{WN4{5kU= z8~_78F9m!FC61%~MRDY>q|}cOh-b+*@x10c#Bof$Cce=Q(%mWc`+wmCT3EyDQh*=B zeoMTZQ>vfOo|4-tS%U^5#dy@%rNZlpuWz3fS}XBPqZ?6+I@7r`_k`IQ4!J z@h`xy1tLb0j zc*ui)q@lMpTo%VJz7p|&BmQ5+uSVRZrcVI^H>B~&hPOelBiwWroRlJ~GYuXzpsG{yw- zi?7D{r;s$@FI*R~4#O2NKv8i#�lUQ8T$@zhrF_3^AIAvx+xiu3dTQqq8e(&Ff_ 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 literal 171008 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�m9J*0 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 fI#zIo5EKk Date: Wed, 19 Aug 2026 00:11:20 +0200 Subject: [PATCH 032/203] Make CultCache page commits content-addressed --- .../DirectoryMessagePackBackingStore.cs | 147 +++++++++++++++--- .../BackingStoreTests.cs | 132 +++++++++++++++- .../Plugins/GameCult.Caching.MessagePack.dll | Bin 44544 -> 47104 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 20236 -> 20852 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 86016 -> 86016 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 30204 -> 30208 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9544 -> 9544 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15872 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12312 -> 12316 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 695296 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 205548 -> 205548 bytes .../GameCult.Networking.WebSockets.dll | Bin 14848 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 510976 -> 510976 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 246920 -> 246920 bytes unity/org.gamecult.cultlib/package.json | 2 +- 16 files changed, 254 insertions(+), 27 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 9d9584a5..0c980c61 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -22,9 +22,11 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore ///

    Fault-probe stage emitted after all pages exist and before manifest finality. public const string BeforeManifestCommitStage = "before-manifest-commit"; private const string LegacyIndexedFormatVersion = "cultcache.store.v2.directory-indexed"; - private const string IndexedFormatVersion = "cultcache.store.v3.directory-immutable-pages"; + private const string LegacyMetadataPageFormatVersion = "cultcache.store.v3.directory-immutable-pages"; + private const string IndexedFormatVersion = "cultcache.store.v4.directory-content-addressed-pages"; private readonly FileInfo _manifestFile; private readonly DirectoryInfo _recordDirectory; + private readonly object _mutationGate = new(); private readonly ConcurrentDictionary _dirtyKeys = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _deletedKeys = new(StringComparer.Ordinal); private readonly HashSet _hydratedKeys = new(StringComparer.Ordinal); @@ -33,6 +35,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private CultSchemaCatalogEntry[] _durableCatalog = Array.Empty(); private bool _needsIndexUpgrade; private bool _manifestUsesImmutablePages; + private bool _manifestUsesMetadataPages; /// /// Gets or sets the predicate that selects indexed record payloads for hydration. @@ -64,6 +67,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// public override void PullAll() + { + lock (_mutationGate) + PullAllCore(); + } + + private void PullAllCore() { var traceStartup = string.Equals( Environment.GetEnvironmentVariable("CULTCACHE_TRACE_STARTUP_PHASES"), @@ -91,10 +100,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore var reports = new List(); var loaded = new Dictionary(StringComparer.Ordinal); var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) || string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); if (indexed) { _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); _needsIndexUpgrade = !_manifestUsesImmutablePages; _durableIndex = manifest.Records .Where(record => !string.IsNullOrWhiteSpace(record.Key)) @@ -156,17 +167,25 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// public override void PullSelected(Func selector) + { + lock (_mutationGate) + PullSelectedCore(selector); + } + + private void PullSelectedCore(Func selector) { if (selector == null) throw new ArgumentNullException(nameof(selector)); var manifest = ReadManifest(); if (!string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) && + !string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) && !string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal)) { - PullAll(); + PullAllCore(); return; } _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); _needsIndexUpgrade = !_manifestUsesImmutablePages; _durableCatalog = manifest.SchemaCatalog; _durableIndex = manifest.Records @@ -188,29 +207,42 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// public override bool ContainsDurableRecord(CultRecordKey key) { - return _durableIndex.ContainsKey(key.Value) || base.ContainsDurableRecord(key); + lock (_mutationGate) + return _durableIndex.ContainsKey(key.Value) || base.ContainsDurableRecord(key); } /// public override void Push(CultStoredDocument entry) { - Entries[entry.Key.Value] = entry; - _dirtyKeys[entry.Key.Value] = true; - _deletedKeys.TryRemove(entry.Key.Value, out _); - IsDirty = true; + lock (_mutationGate) + { + Entries[entry.Key.Value] = entry; + _dirtyKeys[entry.Key.Value] = true; + _deletedKeys.TryRemove(entry.Key.Value, out _); + IsDirty = true; + } } /// public override void Delete(CultStoredDocument entry) { - Entries.TryRemove(entry.Key.Value, out _); - _dirtyKeys.TryRemove(entry.Key.Value, out _); - _deletedKeys[entry.Key.Value] = true; - IsDirty = true; + lock (_mutationGate) + { + Entries.TryRemove(entry.Key.Value, out _); + _dirtyKeys.TryRemove(entry.Key.Value, out _); + _deletedKeys[entry.Key.Value] = true; + IsDirty = true; + } } /// public override void PushAll(bool soft = false) + { + lock (_mutationGate) + PushAllCore(soft); + } + + private void PushAllCore(bool soft) { if (!IsDirty && !_needsIndexUpgrade) { @@ -219,11 +251,14 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore Directory.CreateDirectory(_manifestFile.DirectoryName!); Directory.CreateDirectory(_recordDirectory.FullName); + using var commitLease = AcquireCommitLease(); var currentManifest = ReadManifest(); var currentManifestIsIndexed = string.Equals(currentManifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(currentManifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) || string.Equals(currentManifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); var currentManifestUsesImmutablePages = string.Equals(currentManifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + var currentManifestUsesMetadataPages = string.Equals(currentManifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); var currentIndex = currentManifestIsIndexed ? currentManifest.Records.ToDictionary(record => record.Key, record => record, StringComparer.Ordinal) : new Dictionary(_durableIndex, StringComparer.Ordinal); @@ -262,10 +297,13 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore document, stored.Descriptor.DocumentType, Registry)); + var pagePayload = CultDocumentMessagePackSerialization.SerializePersistedRecord(record); + var indexRecord = ToContentAddressedIndexRecord(record, pagePayload); + currentIndex[key] = indexRecord; FlushStageProbe?.Invoke(BeforeRecordPageWriteStage, pageWriteIndex++); WriteFileAtomically( - ImmutableRecordPath(ToIndexRecord(record)), - CultDocumentMessagePackSerialization.SerializePersistedRecord(record)); + ContentAddressedRecordPath(indexRecord), + pagePayload); } if (!currentManifestUsesImmutablePages) @@ -274,10 +312,17 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore .Where(record => !_deletedKeys.ContainsKey(record.Key) && !_dirtyKeys.ContainsKey(record.Key)) .OrderBy(record => record.Key, StringComparer.Ordinal)) { - var sourcePath = LegacyRecordPath(metadata.Key); - var targetPath = ImmutableRecordPath(ToIndexRecord(metadata)); - if (File.Exists(sourcePath) && !File.Exists(targetPath)) - WriteFileAtomically(targetPath, ReadAllBytesShared(sourcePath)); + var sourcePath = currentManifestUsesMetadataPages + ? MetadataRecordPath(metadata) + : LegacyRecordPath(metadata.Key); + if (!File.Exists(sourcePath)) + continue; + var pagePayload = ReadAllBytesShared(sourcePath); + var indexRecord = ToContentAddressedIndexRecord(metadata, pagePayload); + currentIndex[metadata.Key] = indexRecord; + var targetPath = ContentAddressedRecordPath(indexRecord); + if (!File.Exists(targetPath)) + WriteFileAtomically(targetPath, pagePayload); } } @@ -285,9 +330,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { if (_deletedKeys.ContainsKey(pair.Key) || _dirtyKeys.ContainsKey(pair.Key)) continue; + var pagePayload = CultDocumentMessagePackSerialization.SerializePersistedRecord(pair.Value); + var indexRecord = ToContentAddressedIndexRecord(pair.Value, pagePayload); + currentIndex[pair.Key] = indexRecord; WriteFileAtomically( - ImmutableRecordPath(ToIndexRecord(pair.Value)), - CultDocumentMessagePackSerialization.SerializePersistedRecord(pair.Value)); + ContentAddressedRecordPath(indexRecord), + pagePayload); } FlushStageProbe?.Invoke(BeforeManifestCommitStage, pageWriteIndex); @@ -296,6 +344,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore .ToArray()); _manifestUsesImmutablePages = true; + _manifestUsesMetadataPages = false; DeleteUnreferencedRecordPages(currentIndex.Values); _durableCatalog = targetCatalog; @@ -384,11 +433,16 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore var started = tracePages ? Stopwatch.GetTimestamp() : 0L; var metadata = records[index]; var path = _manifestUsesImmutablePages - ? ImmutableRecordPath(metadata) - : LegacyRecordPath(metadata.Key); + ? ContentAddressedRecordPath(metadata) + : _manifestUsesMetadataPages + ? MetadataRecordPath(metadata) + : LegacyRecordPath(metadata.Key); if (!File.Exists(path)) return; var pagePayload = ReadAllBytesShared(path); + if (_manifestUsesImmutablePages && + !HashPayload(pagePayload).SequenceEqual(metadata.Payload)) + throw new InvalidDataException($"Record page '{path}' does not match its committed content hash."); var record = CultDocumentMessagePackSerialization.DeserializePersistedRecord(pagePayload); if (!string.Equals(record.Key, metadata.Key, StringComparison.Ordinal)) throw new InvalidDataException($"Record page '{path}' contains key '{record.Key}', expected '{metadata.Key}'."); @@ -527,6 +581,16 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore Payload = Array.Empty() }; + private static CultPersistedRecord ToContentAddressedIndexRecord( + CultPersistedRecord record, + byte[] pagePayload) => new() + { + Key = record.Key, + SchemaId = record.SchemaId, + StoredAt = record.StoredAt, + Payload = HashPayload(pagePayload) + }; + private CultPersistedStoreSnapshot ReadManifest() { if (File.Exists(_manifestFile.FullName)) @@ -627,14 +691,28 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private string LegacyRecordPath(string key) => Path.Combine(_recordDirectory.FullName, $"{HashKey(key)}.msgpack"); - private string ImmutableRecordPath(CultPersistedRecord metadata) => Path.Combine( + private string MetadataRecordPath(CultPersistedRecord metadata) => Path.Combine( _recordDirectory.FullName, $"{HashKey(metadata.Key + "\0" + metadata.SchemaId + "\0" + metadata.StoredAt)}.msgpack"); + private string ContentAddressedRecordPath(CultPersistedRecord metadata) + { + if (metadata.Payload == null || metadata.Payload.Length != 32) + throw new InvalidDataException($"Record '{metadata.Key}' has no committed SHA-256 page identity."); + var hash = BitConverter.ToString(metadata.Payload).Replace("-", string.Empty).ToLowerInvariant(); + return Path.Combine(_recordDirectory.FullName, $"{hash}.msgpack"); + } + + private static byte[] HashPayload(byte[] payload) + { + using var sha256 = SHA256.Create(); + return sha256.ComputeHash(payload); + } + private void DeleteUnreferencedRecordPages(IEnumerable durableRecords) { var referenced = durableRecords - .Select(ImmutableRecordPath) + .Select(ContentAddressedRecordPath) .ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var file in _recordDirectory.EnumerateFiles("*.msgpack")) { @@ -708,6 +786,29 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore return buffer.ToArray(); } + private FileStream AcquireCommitLease() + { + var lockPath = Path.Combine(_recordDirectory.FullName, ".commit.lock"); + var started = Stopwatch.StartNew(); + while (true) + { + try + { + return new FileStream( + lockPath, + FileMode.OpenOrCreate, + FileAccess.ReadWrite, + FileShare.None, + bufferSize: 1, + FileOptions.WriteThrough); + } + catch (IOException) when (started.Elapsed < TimeSpan.FromSeconds(30)) + { + Thread.Sleep(10); + } + } + } + private static void ReplaceExistingFile(string sourcePath, string destinationPath) { for (var attempt = 0; attempt < 3; attempt++) diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index 66078f26..e43fa7b6 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using GameCult.Caching; using GameCult.Caching.MessagePack; @@ -434,9 +435,10 @@ namespace GameCult.Caching.Tests } var manifest = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); - Assert.That(manifest.FormatVersion, Is.EqualTo("cultcache.store.v3.directory-immutable-pages")); + Assert.That(manifest.FormatVersion, Is.EqualTo("cultcache.store.v4.directory-content-addressed-pages")); Assert.That(manifest.Records, Has.Length.EqualTo(2)); - Assert.That(manifest.Records.All(record => record.Payload.Length == 0), Is.True); + Assert.That(manifest.Records.All(record => record.Payload.Length == 32), Is.True, + "the hot index carries only the SHA-256 page identity, never the document body"); var coldPath = Directory.GetFiles(recordsPath, "*.msgpack") .Single(path => string.Equals( @@ -469,7 +471,7 @@ namespace GameCult.Caching.Tests var finalManifest = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); Assert.That(finalManifest.Records, Has.Length.EqualTo(3)); Assert.That(finalManifest.Records.Any(record => record.Key == cold.Key.Value), Is.True); - Assert.That(finalManifest.Records.All(record => record.Payload.Length == 0), Is.True); + Assert.That(finalManifest.Records.All(record => record.Payload.Length == 32), Is.True); } finally { @@ -713,6 +715,130 @@ namespace GameCult.Caching.Tests } } + [Test] + public async Task DirectoryMessagePackBackingStore_SameTimestampPayload_CannotOverwriteCommittedPageBeforeManifest() + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + const string storedAt = "2026-08-19T00:00:00.0000000+00:00"; + var store = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var cache = new CultCache(); + cache.AddBackingStore(store); + var key = new CultRecordKey("record:atomic:same-timestamp"); + var descriptor = CultDocumentRegistry.Shared.GetRequired(); + store.Push(new CultStoredDocument( + key, + storedAt, + descriptor, + new NamedTestEntry { Name = "same", Value = "old" })); + store.PushAll(); + + store.Push(new CultStoredDocument( + key, + storedAt, + descriptor, + new NamedTestEntry { Name = "same", Value = "new" })); + store.FlushStageProbe = (stage, _) => + { + if (string.Equals(stage, DirectoryMessagePackBackingStore.BeforeManifestCommitStage, StringComparison.Ordinal)) + throw new IOException("injected crash before same-timestamp manifest commit"); + }; + Assert.That(() => store.PushAll(), Throws.TypeOf()); + + using var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true }); + Assert.That(reopened.Get(key)?.Value, Is.EqualTo("old")); + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + + [Test] + public async Task DirectoryMessagePackBackingStore_MutationDuringFlush_RemainsDirtyAndCommitsNextGeneration() + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + var store = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var cache = new CultCache(); + cache.AddBackingStore(store); + var first = await cache.UpsertAsync(new NamedTestEntry { Name = "first", Value = "one" }); + using var flushEntered = new ManualResetEventSlim(); + using var writerAttempting = new ManualResetEventSlim(); + store.FlushStageProbe = (stage, _) => + { + if (!string.Equals(stage, DirectoryMessagePackBackingStore.BeforeManifestCommitStage, StringComparison.Ordinal)) + return; + flushEntered.Set(); + Assert.That(writerAttempting.Wait(TimeSpan.FromSeconds(5)), Is.True); + }; + + var flush = Task.Run(() => store.PushAll()); + var writer = Task.Run(async () => + { + Assert.That(flushEntered.Wait(TimeSpan.FromSeconds(5)), Is.True); + writerAttempting.Set(); + return await cache.UpsertAsync(new NamedTestEntry { Name = "second", Value = "two" }); + }); + await flush; + var second = await writer; + + Assert.That(store.IsDirty, Is.True, "the mutation blocked behind flush must remain pending"); + store.FlushStageProbe = null; + store.PushAll(); + using var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true }); + Assert.That(reopened.Get(first.Key)?.Value, Is.EqualTo("one")); + Assert.That(reopened.Get(second.Key)?.Value, Is.EqualTo("two")); + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + + [Test] + public async Task DirectoryMessagePackBackingStore_ConcurrentInstances_MergeUnderOneCommitLease() + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + var firstStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var firstCache = new CultCache(); + firstCache.AddBackingStore(firstStore); + var secondStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var secondCache = new CultCache(); + secondCache.AddBackingStore(secondStore); + var first = await firstCache.UpsertAsync(new NamedTestEntry { Name = "first-process", Value = "one" }); + var second = await secondCache.UpsertAsync(new NamedTestEntry { Name = "second-process", Value = "two" }); + + await Task.WhenAll( + Task.Run(() => firstStore.PushAll()), + Task.Run(() => secondStore.PushAll())); + + using var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true }); + Assert.That(reopened.Get(first.Key)?.Value, Is.EqualTo("one")); + Assert.That(reopened.Get(second.Key)?.Value, Is.EqualTo("two")); + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + [Test] public async Task DirectoryMessagePackBackingStore_PullsOnlyExternalRecordDeltas() { diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index dd41e76d91054b0af3171d5e1cbd967af00caa14..b279cd097384209b114e381122167efe477e350e 100644 GIT binary patch literal 47104 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 z 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 stage emitted after all pages exist and before manifest finality. public const string BeforeManifestCommitStage = "before-manifest-commit"; + /// Read-probe stage emitted while the selected manifest generation is leased. + public const string AfterManifestReadStage = "after-manifest-read"; private const string LegacyIndexedFormatVersion = "cultcache.store.v2.directory-indexed"; private const string LegacyMetadataPageFormatVersion = "cultcache.store.v3.directory-immutable-pages"; private const string IndexedFormatVersion = "cultcache.store.v4.directory-content-addressed-pages"; @@ -46,6 +48,9 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// Optional deterministic fault/progress hook for durability tests and host diagnostics. public Action? FlushStageProbe { get; set; } + /// Optional deterministic read hook for generation-consistency tests and host diagnostics. + public Action? ReadStageProbe { get; set; } + /// /// Creates a paged MessagePack backing store. /// @@ -86,46 +91,50 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore startupPhase.Restart(); } - _recordDirectory.Refresh(); - if (!File.Exists(_manifestFile.FullName) && !_recordDirectory.Exists) - { - SetLastSchemaMigrationReports(Array.Empty()); - IsDirty = false; - return; - } - - var manifest = ReadManifest(); - Trace($"manifest records={manifest.Records.Length}"); - _durableCatalog = manifest.SchemaCatalog; var reports = new List(); var loaded = new Dictionary(StringComparer.Ordinal); - var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || - string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) || - string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); - if (indexed) + using (AcquireCommitLease()) { - _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); - _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); - _needsIndexUpgrade = !_manifestUsesImmutablePages; - _durableIndex = manifest.Records - .Where(record => !string.IsNullOrWhiteSpace(record.Key)) - .GroupBy(record => record.Key, StringComparer.Ordinal) - .ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal); + _recordDirectory.Refresh(); + if (!File.Exists(_manifestFile.FullName) && !_recordDirectory.Exists) + { + SetLastSchemaMigrationReports(Array.Empty()); + IsDirty = false; + return; + } - LoadIndexedRecords(manifest, loaded, reports); - Trace($"indexed-pages loaded={loaded.Count}"); - } - else - { - // V1 manifests have no page index. Hydrate once to discover the durable keys; - // the next flush replaces this compatibility path with the indexed manifest. - _needsIndexUpgrade = true; - var legacyRecords = LoadLegacyRecords(manifest, loaded, reports); - _durableIndex = legacyRecords - .Where(record => !string.IsNullOrWhiteSpace(record.Key)) - .GroupBy(record => record.Key, StringComparer.Ordinal) - .ToDictionary(group => group.Key, group => ToIndexRecord(group.Last()), StringComparer.Ordinal); - Trace($"legacy-pages loaded={loaded.Count}"); + var manifest = ReadManifest(); + ReadStageProbe?.Invoke(AfterManifestReadStage); + Trace($"manifest records={manifest.Records.Length}"); + _durableCatalog = manifest.SchemaCatalog; + var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); + if (indexed) + { + _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); + _needsIndexUpgrade = !_manifestUsesImmutablePages; + _durableIndex = manifest.Records + .Where(record => !string.IsNullOrWhiteSpace(record.Key)) + .GroupBy(record => record.Key, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal); + + LoadIndexedRecords(manifest, loaded, reports); + Trace($"indexed-pages loaded={loaded.Count}"); + } + else + { + // V1 manifests have no page index. Hydrate once to discover the durable keys; + // the next flush replaces this compatibility path with the indexed manifest. + _needsIndexUpgrade = true; + var legacyRecords = LoadLegacyRecords(manifest, loaded, reports); + _durableIndex = legacyRecords + .Where(record => !string.IsNullOrWhiteSpace(record.Key)) + .GroupBy(record => record.Key, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => ToIndexRecord(group.Last()), StringComparer.Ordinal); + Trace($"legacy-pages loaded={loaded.Count}"); + } } foreach (var pair in loaded) @@ -175,30 +184,39 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private void PullSelectedCore(Func selector) { if (selector == null) throw new ArgumentNullException(nameof(selector)); - var manifest = ReadManifest(); - if (!string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) && - !string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) && - !string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal)) + var fallbackToFullPull = false; + CultPersistedRecord[] selected = Array.Empty(); + var loaded = new Dictionary(StringComparer.Ordinal); + var reports = new List(); + using (AcquireCommitLease()) + { + var manifest = ReadManifest(); + ReadStageProbe?.Invoke(AfterManifestReadStage); + fallbackToFullPull = !string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) && + !string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) && + !string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); + if (!fallbackToFullPull) + { + _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); + _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); + _needsIndexUpgrade = !_manifestUsesImmutablePages; + _durableCatalog = manifest.SchemaCatalog; + _durableIndex = manifest.Records + .Where(record => !string.IsNullOrWhiteSpace(record.Key)) + .GroupBy(record => record.Key, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal); + selected = manifest.Records + .Where(record => selector(ToMetadata(record))) + .OrderBy(record => record.Key, StringComparer.Ordinal) + .ToArray(); + LoadRecordPages(selected, manifest.SchemaCatalog, loaded, reports); + } + } + if (fallbackToFullPull) { PullAllCore(); return; } - - _manifestUsesImmutablePages = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal); - _manifestUsesMetadataPages = string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal); - _needsIndexUpgrade = !_manifestUsesImmutablePages; - _durableCatalog = manifest.SchemaCatalog; - _durableIndex = manifest.Records - .Where(record => !string.IsNullOrWhiteSpace(record.Key)) - .GroupBy(record => record.Key, StringComparer.Ordinal) - .ToDictionary(group => group.Key, group => group.Last(), StringComparer.Ordinal); - var selected = manifest.Records - .Where(record => selector(ToMetadata(record))) - .OrderBy(record => record.Key, StringComparer.Ordinal) - .ToArray(); - var loaded = new Dictionary(StringComparer.Ordinal); - var reports = new List(); - LoadRecordPages(selected, manifest.SchemaCatalog, loaded, reports); PublishSelected(loaded, selected.Select(record => record.Key).ToHashSet(StringComparer.Ordinal)); SetLastSchemaMigrationReports(reports); IsDirty = !_dirtyKeys.IsEmpty || !_deletedKeys.IsEmpty; @@ -438,7 +456,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore ? MetadataRecordPath(metadata) : LegacyRecordPath(metadata.Key); if (!File.Exists(path)) - return; + throw new InvalidDataException($"Committed record page '{path}' is missing from the selected manifest generation."); var pagePayload = ReadAllBytesShared(path); if (_manifestUsesImmutablePages && !HashPayload(pagePayload).SequenceEqual(metadata.Payload)) @@ -788,6 +806,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore private FileStream AcquireCommitLease() { + Directory.CreateDirectory(_recordDirectory.FullName); var lockPath = Path.Combine(_recordDirectory.FullName, ".commit.lock"); var started = Stopwatch.StartNew(); while (true) diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index e43fa7b6..ee833f17 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -839,6 +839,103 @@ namespace GameCult.Caching.Tests } } + [Test] + public async Task DirectoryMessagePackBackingStore_ReaderLeasesManifestUntilEveryReferencedPageIsHydrated() + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + var key = new CultRecordKey("record:reader-generation"); + var writerStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var writerCache = new CultCache(); + writerCache.AddBackingStore(writerStore); + await writerCache.UpsertAsync( + new NamedTestEntry { Name = "generation", Value = "old" }, + new CultRecordHandle(key)); + writerStore.PushAll(); + + var readerStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var readerCache = new CultCache(); + readerCache.AddBackingStore(readerStore); + using var manifestRead = new ManualResetEventSlim(); + using var releaseReader = new ManualResetEventSlim(); + readerStore.ReadStageProbe = stage => + { + if (!string.Equals(stage, DirectoryMessagePackBackingStore.AfterManifestReadStage, StringComparison.Ordinal)) + return; + manifestRead.Set(); + Assert.That(releaseReader.Wait(TimeSpan.FromSeconds(5)), Is.True); + }; + + var read = Task.Run(() => readerStore.PullAll()); + Assert.That(manifestRead.Wait(TimeSpan.FromSeconds(5)), Is.True); + await writerCache.UpsertAsync( + new NamedTestEntry { Name = "generation", Value = "new" }, + new CultRecordHandle(key)); + var write = Task.Run(() => writerStore.PushAll()); + Assert.That(write.Wait(TimeSpan.FromMilliseconds(100)), Is.False, + "writer must wait while a reader hydrates its selected manifest generation"); + + releaseReader.Set(); + await Task.WhenAll(read, write); + Assert.That(readerCache.Get(key)?.Value, Is.EqualTo("old")); + + using var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true }); + Assert.That(reopened.Get(key)?.Value, Is.EqualTo("new")); + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + + [Test] + public async Task DirectoryMessagePackBackingStore_ReleasesGenerationLeaseBeforePublishingObservers() + { + var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc"); + var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath); + try + { + var writerStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var writerCache = new CultCache(); + writerCache.AddBackingStore(writerStore); + await writerCache.UpsertAsync(new NamedTestEntry { Name = "source", Value = "one" }); + writerStore.PushAll(); + + var readerStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var readerCache = new CultCache(); + readerCache.AddBackingStore(readerStore); + var observerRan = false; + using var subscription = readerStore.EntryAdded.Subscribe(_ => + { + if (observerRan) + return; + observerRan = true; + readerCache.UpsertAsync(new NamedTestEntry { Name = "observer", Value = "two" }) + .GetAwaiter() + .GetResult(); + readerStore.PushAll(); + }); + + Assert.That(() => readerStore.PullAll(), Throws.Nothing); + Assert.That(observerRan, Is.True); + using var reopened = await CultCacheMessagePack.OpenAsync( + filePath, + new CultCacheOpenOptions { UseDirectoryStore = true }); + Assert.That(reopened.GetAll().Select(value => value.Name), + Is.EquivalentTo(new[] { "source", "observer" })); + } + finally + { + if (File.Exists(filePath)) File.Delete(filePath); + if (Directory.Exists(recordsPath)) Directory.Delete(recordsPath, recursive: true); + } + } + [Test] public async Task DirectoryMessagePackBackingStore_PullsOnlyExternalRecordDeltas() { diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index b279cd097384209b114e381122167efe477e350e..1deb800477b19261d5e7ac968d3cda450b042687 100644 GIT binary patch delta 22197 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 delta 637 zcmV-@0)qX3>;Zu60g!wYsFi+q{&)3Cpxke0Ex z5=E#D;Q{e5UXSe&>m6%%Oyp1EH~2ENE<_0z$w=7;OWrwi&e7S?r0LOK&*4Fu!X zPevi98o#o5Kv%$;t%aPxN>Jc(!=^PoXmbEh8}UIBF4Mnw-gh*I2VG!S!F?Z;nv31v z;9Mp@iNgnq&hsL5?V{}Yg0{j4pdY0>pps-E*s>CvPie?{X_*-RdP9?jxPuDaMI zZj0-VEMcJkA!>WiRjpej-@m~vDZ+|e7~aQ-dnk$zk#rBQ(wq^q+R$!be-S>(a5gZ4X&fP2E diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll index be0edb36bdd2b942c5c65bce2fe6491e7effc4e5..bc67fc77ad5d50d767e73d7960b8ba01bd556024 100644 GIT binary patch delta 290 zcmZoLX)u}4!SZ}ha_7b#Ck{r_&F49OvIw|eh+2L5SCHe%S5`$cS_Kq0-{5Uvv@q5) zU|=v|WY9O%Gte_N)=o`Lv@kNUurN2bOffODG&Hs}PfRs1w@6J*GfhcMO)*I}OG&aw zPMJJ`{|?I{iwA9!3j_iL4n8z9(J5i`=<{WKemG7`bn*p(Mg^!KhaXfBsG4oNy5W}z zfo6v+%O`x^Tqr2Z$Z5*Jz`zK^rkke=9cE-S-W)Ab%@}OVpv{m9goz9m3`PtlK+=N2 z90)BLQh+=&AZ^HC3?$8gDpDB?fV2fzeHw!)Pz6W~q%Ik#GKC=tD3{ETGFem16li?B H*g|Fi-6c{a delta 290 zcmZoLX)u}4!IF06$gGV$P8^ITo6mFnWD%IcyqqOM=C%8@9EFzary_T6zQNnTXknyh zz`$U_$e?eiXP{?lq-~LCWMG_ZXl9z0YLS?fnrND6X_lB~l4xO`WME-oWNc}aoRpeo zWHEUH{~Z=L*=3t27YGCh82TH(__6V?`|fwIcW!<8vvBeSfkp+WAiFhG5UAStH+PBR zuB7a}OH(eM*jy+m%gAZMz`(!=#3q}k3ms-;G};_3Qq34_#GuV!!H~#c#9+W+%#h4r z$Y91`%8MelK2&l^vNG1bCQi1YD3>K3$ N#Y}<5$BQjw1^@=dS!n?ZB}_ z4tpPYT|KtwyB5WJ7k?vL9ABV;&Nl@B0IihIY8x>S#_xf=!{Ad(lU=8AVmk?i)Fq*i zK!{WKP6W~4kn-s?I%$aA>DR{XIc zM*)TOyY*pOPZfVhP+k)}V}xx06dfRl_clwR+wet&&QJvLASttCOHgA63foA)PWcc> z2^3V}H}(P0X?SL9p&+n63UIllNktoNju2=ozLtcm{4X`{+nU3JHnKCY?4nS8xx39C zskEeoy}L}IUob|h3)@}%8IWE7yj%FXLDyZ21TI>kSEGNL1XVy`HG2JZFvXQ@+LYy8 zkM&5FFw*~knl7-ZqzC!_0{0{cD{^t&ALZTV(uX)4{ehwi-27JIlIzA2G`2Mc3!9p5 zO!AbSP$*Fmls>?swQ|SBaJ3t@w4xAq)lRZ;c6OSLhjTu<(uaEOE82en-w@&K?PNBa zPUm`ZK0kjsollX*(-~2DrjhhSo$F$z^l;sW;B~9|ZMemnk`3A2#-ZV?Cl*V4A+7e{ zXqqLBf;(f{_{icz_bJ%X5sWI4CSK&ZVX4|8Q@+lB6`l7-Vg2%N`@c~7Bkq^)TeccL z2Nqg*O*a8eO-?{rj4?}jKL7y(fT(*SWN%}2ZDo^b2XiQ8WnnlnG&ndnH#uZ9HaRgf fIX7WtFgG}5Wo9*GVP#}AV>V=BIAdh91PDYO{TUXg delta 593 zcmV-X0$$b*`Ef=arn9k--2{JOx?7TpiFA*=)GNN{ z6z%fKJE$<4AfsYjsh*g8uIYz?&Nl@B0IihIYTPgo#_xf=!?358Huh%IO`Sg!(k%&v z1WGp99!d`*c`Ogel99#@ex$xepR8!NUA%1rF05{nW`5uB%}9BEwAXcbkmqoTo%ka| zjsgmm@79NDJ=A|3LHm;62_tL+K=pti-rFpNZo?NfIzth}gQU!gEkT1lXzU^ZJMBXt zEug5zZ|no0E8&T)1w~+e6yR!0lbSZ#93jwFd@Tu=`9Esjw>5_cU1Voq*+-$~Vt1R} zQ)NjDdv}pSzhI2iDBGX^9gto9x?A|BMb}@61Xi8Ui%EY&f;ynEp1k}zn&K)pUCQ!q zz`7?(80mjNZ6DawGJyQ}4tFF7D{^t&AMM@d(uX)6{ehwm-2PGFlAG2Nw6-$_l}$}I zCV9$EC{(BkDj#6cS-E93UhSGKttiA@wc~7>&1czkJm;gUd}!9bqJ0kds=(RwL?55d zD=JVC&hdZj6stE_yj9bpn4O%>w4!QOjMx1XeA($?8*Z?nWJ7klacDg2fyL5ZNUJ?K znr5j)!JRQ}ePr>We++i=8YZ0W@R{GVr5}9 fVL3KoW;9_qH)1e2I5IOiGGk(8W->Ul1PDYOxuhN5 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index 477716731b9682725ad61241d001cf31b06c6be3..96f9481b7cfcfe45c8b580726362f6404eedbaa0 100644 GIT binary patch delta 316 zcmZpuX{edd!Lr6*?ZU<$14%~H$$KSL8BI4omi#Fsuv9qvA?Nke4(*8t{&{YFQ?U7s z&LS2&V?6@~1`|dGeM3D1JyT=t)YL=^BNGb?b92iS6EjOgV@vbIR0DI1)YLT7l*H5& zlVr1$B#Yz}1_n7s2Fc0uOe8lyHGIyB$$20|ch|i#_(4Y~$HD=VDUEeXW+s z1}2jgpn@|GLIpvpHH=pHp6Z-%eG%KVJDVSytY+jiWnf@n1Y*<8)6Jxr7>ze?wph&= zY|Nm|kP3u}3>FMV3?@L*g25aJEg4dPJTo9|$Y2a4&4DUX84Q551z3F=gDFr2NDQPd j8K^RaAqgm#%#bp9qm?PEF@qk1!Dc}lF~-e|Hvbs`H#1>B delta 316 zcmZpuX{edd!SXrO=Ih2D14%}c$$KSL8BI1nmi#Fs;CREjiu3my?~V3vmxv_v-rD>| zXAz5?k)8nqg9#&pzM-Cho~e!g?W;Jg@uu^ zrBQNHYMPM+1A`nRgXH9SCX$<<8a`)aIl?r5)#MAt0Rlgj9wg-4zUHuAH~EyCXT|Kv z1}2jgpn?-%fwACVr8VQBwVNNCtY+jiVPIfj1Y(oT)6Jxr7>zb>wph&= zY{a0=V8M{cV8md+V9b!rV8~#`V9JojkO~$}0@8^X7UN8lJV}9^(PC)dDum36*UPbxatD74DG0}L#`bT++vAzN zXJ8C-kk8vj=Qk0(jz9x;OR(yJ_Id>X0IgKpYTG~%eGm8_20bODSvidp+es*-E(wLY zw7Iwsr4OUskufs0n?Fl&EEPzMy&+0nP1SzO6go2tCq z;JPPE80mkAmR;bgmJO00-(gOIupk%v{%G%3mp;VZsozo5ftTA353`Rb{7f*9pIg>2 zYpu*F)({A-aFZJD9BJ_ZN+00Kv2?u&FEs^lBvH^A3DU`zX*?saPoRInT*F%GrE|bo=wL{!|{ZuJU2*Y zq%TY{(PnquPr;j3Z}#mD8%lYq6sSt^s#Vy#lRmOo+Arl|4;iO%G$^<==}RA3eCVEn z&0axPi!9@1o>we&TPW?D{I~l&Jw9;J3--(7ZgU)wr8P5uc-yiy@FlR&%p1B5ST*DX zw8a=zs{09(8VY|XWo2PFGBh|iH#a$CG&VUgGdVY5WiU55Wo2eHWMO4wG-EboVmM=D zv%w0e0vNS%*qH#p0RT}jq8)Rxqe_$m#*!jT(RGu74J{ZKYfOm%fB*m##Pf4!=D0zz zY`V=K*2SumfejrVWNd5z00BbBs;B@U0RYhY5Olu2Q$vAEm5eCs*Z`9O4i^~bykm&~ zfB*nT&|0h0`cgru>N;|mg%BZ=Fb)(L#ym3H000XB!ReIawq8?1v{UlqXBcbOlUxoO l7)vsk`~W}*00kPrmpQ^EtatD7v0OVB!B1VuI4Sk?$ zJV_>Je?4ZC|BbyjCH2PrtC3KF_Id>X0IgKpYTG~%eK+_Y20bODSvgLfDi;c=OG2P7 zZ7%LZ>BDF>k_J}0W_OgJPwH><%gV~Fv1tn`cpn7Po;l~}%rI$syw@`xr72utEAF@= zOCE)axBbU>9&3LNpj|=mf&n%F$U8s~=ZuQb4SZgsttfyvkXTu=A!x7zjcp)ctDX0x z1#+tKnVko;1-!5^kP{de1i0L=Nlj~Q4&Z4c-b=z&`WMgpj%GY+1KSE7x**hC?!Kyf zDh+92XD=i4D~3Q#Zn}#<0qXkK-P|=RwB5Cc=e!jV^ z8C>wW^08VY|XIAJm{Gh;C}HD+ZvVPa)rHDNh6VP-U8I5%Q2I5;vhIWl8nWo9xs zv%w0e0vN8@hM)kz0RV{C#vIKwOGTR(lW)#k;scX`4J{Zs^kts_fB*o{+n5nVm8?mJ zVqX@?YgFQsfejrVWNd5z00GN61=|210RWD}XKW?++(nqfZj;KkHe-_k4i^|^)Wn(q zfB*oL1E&O79P3AxY#@Z!%2~UUFb)(L+B06m000XBXXy7{J*{;?xrBxe1(J2vlUxoO l7ToB%)w0LW1F4t6gULX-21co3Izhm!#h6tkiZdLj4AYR~`x diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll index 6d06333d04a1f77851a053138468757cde03b199..65f566e9d3683aff583d28438c5dc911cf8f16f2 100644 GIT binary patch delta 384 zcmZqJq1CWMYeEOhF&m@XjXkYBjIBLPtv$@GJuIy~tgSt4TYK1l)G?Y)Z>?wdmo;TT z0Hgf~HY1Q}I{jlkyELO|J8uI!5OZwjZQ$I_EO7AUl*ilRf}OVne|nrQk$rvpm1fR; z_biO{3>X+p7#Z{p^$he(jkQx#6D^EPEG*2;EmKU)EDen<%@b1%%q>z=(@awmQ&UWm z%~Fyql2f*yO5)PfW6AonX5sWd6Sx8dUaK9Dxu2}%ut47beD(WXYo^CcMHie6BF ziUQRuzRZ2I>>@w=>)qnaI@`Zb?wdmo;HP z0Hgf~HY1Q}GW}ycyELOoJ8uI!5OZwjZQ$I_ERg=qfrW*Uv87RR zQfiu!#r9K4TzYyecWi_AP5(22D?osG|C2m5jYRKlvx;vW=`@%#J!T?TwE|Q$y9_D{ zRBzY$rK!5xe@Srb>s!*>zfa_v&cq4w57_P7r%&M$W@0qjzIqy0GGnk2gEoT&Ln4C_ zg8_pvLo$OQgBgPn6Y)-(xC3pdU}qd+)leYW%FBfsnP30qtvlhe|5W7)19u zjQfp{-G)i7yF- z)FmNMLrGHiq4Z(2JCe4ncFoSHL7&v$=$DmMTw~J~RO)>YX!p!HXU`6kWv2&S!$Fq8 zH8$c;OL7!YNWbYe<9e!p41@BT;29aV0LWWF5btdop7mXPURh}LZ|WhFZ#Kfc3~1Yt#vyZ2Gvy&U?${i)v(t6$g^~mUYw_+{@1;Cg%5S-6Ybv!Pl#~(dOV#@CNn+0oSmJ|CP<^n zl&HASNP4U;bv{+PzwU?Nb)$CQZy< zyn>_>S@S&0N+VU1r^?saFUMK@yf=9#*bR%j-N6;d%JT6RZDXv4Pr(Q+yr$cLWlc^% zS!7OQ-A}iVZUHogC}m|~I5IRiI5#&rWHdH8F*7+gVP!BkIAvvKHDqCBWHe(oWMVjD zWVh?|0kr}cwQ<;)0KfqNQ81z%bF!mKlmo_+B23YBm;Ltv9hcSj0T37$YfOm%fB*m# z#Pf4!=D0zzY`V=K*2Suq{r3SQe?rHqr~n`V0MPmnbiTe*LxD?`j412a07qeMWkYpr zbS_M9XJ=_{XD(!HYybcO=e%Qy0Du4hN6=cU)A~|Dsp>j%n1v7_M`3McLv?I)E>2~1 zcW-iQX>MmOWNd5z00B!fnEU`h2ml2d)Gj7&u1lBqiY)j{Ww}XnZ*XsDhoATXx1aa| FO)5Qbg0=tv delta 827 zcmV-B1H}C7#SHAl43K;j1Ha^MCMaP`wwiO=#ECGR+&xpifsnP30qtvl@aUaTq&t_P8BB< zQkR564JAq4hth}9Y9wvhi`f}9=#%;z{j#!(Yi!zrO1%#P?VdU3?Ac+m?DU{(Jjyb- z##a1kO^yNzm9P8FxE>pS&7gcq@PrIo02Cb{i1#Lq&@Ft?psR^NJV>mpv>~wAfx?yv zv{OC=QUV1v_)Yr&=t_9f#y~+}JPYV*%O(vyYiou;Tk&2JuCqUQK6EtWQOnxZ@X)c4 zz1sgx@2N7RM093A$*7juK0Mf-0bBl^lN=%;G9t zx3S8*9j<#agiQZJwC;kg8`&ZG@g0^V2rF{jzmM|n`OpU*PW_fx1-;&Pc$6$!BTM3D z4yESJb_s#dh_(clxPcM{3uNlmS=-k17mY@k9JdUW4{(Q;7OhF(WIT%Pk_fDW|4K?Y zIUa2DRvTJT;Qb(f&(cXco2HZD*L~+IA8hXv&EE)5a-2@i)!D_Yq8tmt8D3ss^%C<} z#U#(C=a*AeP&Lhm>wXBnY}M}jZE}$`#yp(pQH!Dfr&%2!%h-Og2lhI9?OBTt-N#^( z7mzd}Yo2FWt)*#;RC$~Ia-7A_dtLN`{jj*(9b9p&EFaz$wAz&LDQKaEFX=X5ZOI8J zgUo5H`w6#?ZUHogC^%sV@F|aWkYpr zbS_M9XJ=_{XD(!HYybcOX4J%*0Du4hlmn*(SRCs|mTVw|*UDMDM`3McLv?I)E>2~1 zcW-iQX>MmOWNd5z00B7-X`BE+2mr`X^$vC~6+)BqjCc^2a)(KCZ*XsDhoATXx1aa| FO)3U$g^d6J diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index 3b4141647f2625001e3f7284147352ff46905243..7cfbbf1329f9fc6a106ab8aff8cb25109f1391ed 100644 GIT binary patch delta 306 zcmZoDX(*Y{!4l0nZNtW%90^9#$v-7j8BI6KODggUY;bMg`A5~tb3%+oQ0D)oYc~gI z2(wxk>lrXGm@qQv8|oS8nHp=SrY2e#nOInun_H%sm{}ScTbd`P8kk$8rly&uB&MdA zB%7rqStO@yo~qo2MIbGcg)(UTkunG1!XH5Lz;%0C{FW+K|B*NSXsxq%s%)X$!FWGzL?k3Xm8`T{2K*3PTc5E}0=^ Z@=-HWR$~S|27}Fl7EFwr87=-Z0s!UmT$}&^ delta 306 zcmXYoF-yZx6ou~#tsn(Wafy_of`U*?UT$8}n@u+nECnasUP3bIR#(kfp@TG_gE;9b zRNgKs6cH@>0d*D~`~kY?Q_AI>%l*!|Stsk9c1y1}GyU7%zQsiTWvwuw7Uq{`&Go6; z_1SZ_*&Du(5AOz_s=Yc_CbT(#VBokmw|#7oMDE3I;HL@6D3QJl1DOg*&4?4B7h)VG zDU!Nas~V-{m~606wIU{L@Hpsc#9P=2CN^ouCPk?vozW bU!nF`lSJnTln(>Hj9>#-RWg`j^uvAu5T;q3 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index a6163a0a9f8604ad0c972867f36d0aab151753bc..40d4ec74585ecf0077d40ae478ebdbeed3720d96 100644 GIT binary patch delta 361 zcmZpeA>S}VenJO}x6zD0jXkYBjIBLPtv$?JdsvpeU^Ja>{F22LNL9XM(Fam1!BqRr zmn_?FzGSVh6=0RRqNkR(&U^azE}b7fsXMo4q_dgU*%|8@Fff=fGUyxX8R(fBYp13r zS{RvFSeTnzrkI#n8X8-gC#D*hTcoC@nWiMBrkEs~r6gG-r!X)`Ffs^FH{8uCy#3u% zwyp~-AAapWJ-ucddw_s2?~XMAY^U5dEh|f&7%c8K{mwS_b_JNI5mXeY-n*#nPc&1; zzapR1=X16#GG~Ql)jJ=&P*qA|^Ar%M{87vr#7)*eq z1%o*dS~8>nd1gS`kii&8ngdm&G8h193$Xe$22-F4kQhi^GEij-LlRIfnIUC*=m~aH XR$~S|27~Q_r`U@aw=Z3}fQVQZyU0e$JcuCq92M5JbD2TJXghX9C_<^mhNQp=WK_ne? z6Tv|c(Z0b=L`o+oq2QzlX3@dAs?So6yC3(v|5fXmYCUtXCs~QrFgaDp&m(d0-KSf2%LA}wGAqYCgVDULQc84-IJWB%3NVTw6>7J!6#sY|*ngCMCNA&7 zQUe!~pC{63rPG-|y0{vAc~axya|8S1JzGxq?0>$fEKRyojrG#(*frnfcqkD!004gg3;bkOy1jsVE}8EHNEn aTyZBr!^I{xV+3SS`A7>F5l1cji~azLj&X+o diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index 5e79480f43ace503c5386c5c7996034e19403c3b..e1aa9f03792f573dfed8ec058fba2760cdd7a35a 100644 GIT binary patch delta 744 zcmVD%+i#5QXmpd56JQEsb}b#)*Ft z3aLv%p$R2U(~HuJS?$?fQLVH{voZTfeT_a@(WW(af4~9jLZH!^?+ha`X*%5NIy^{I zy5Lsau!MzYLH?!>#&utR=|IZmpcMyN3n=S|Nt`oDl&dbWWzXi$Vue*h-8@Ao0h}T&wcrmDdA*?)$R)d#ccc$1v zmu<{)c7t^X0}gl&sOh{eE7^ej_)a$vrU%&2zmIbE>C*YIJNg}h^1At>!bNN37BZ(L zRO@j>l`gAc17A2_vsIuPA#fSnTsTi9*9d8Vf5dalue8AfeAqTWPDaV;Niy1<^WOG# z#Tc9a0bc<;eLbFkPA8LDK0ccrpUftV(PRn~r#T}ZtFt_ts(g3fkKX02+I*F3t`VD; z+(K5)f77D2Qo~EUis?8GR>hyQr zY1}^OtT*h(!S&{7ii6{3`L@+2r_Wvs7B0t?=dy+sQiekWNMhbkhsHDkx5hLAor@@C zWnnlnG&ndnH#uZ9HaRgfIX7WtFgG}5Wo9*GVP#}AV>V=BIAdhDi@*Y^0vNS%*qH#p z0RT}jq8)Rxqe_$m#*!jT(RG*pxB?xQYQq8$7#C|yi2#5A02IXYb7$tbL9uMQ%^%jq zs+az_0v(sH!vY2vLdUA803ZPX(E1Q`zP?jKflHN)DC^h&m(9Zh7Z~TfV~GHO002kO aTC3CgQbDQeI&zqW5Fv;B!vdH5!vl<#>SSgB delta 744 zcmVQr$; zA$3UzG@-<)dni4ORwHTXYFF%xV)RITjXqgfr!{u}fCJvc!p?s4`-a(N(sa1jb$F1b zaDkorVMUGtDiv>6!MGlOY8EiQAXuWnS^#+u2^_8XwsQKv%$$t%aPxdQsqVP1Bm5yjdX7+I(dR7wJE0-gj#b4?1C2!Mqox z=6w5@+*4`ED0_DvrC(5ln%wr&zXi$Vue+IVT6F!TYT&$6dNF8!NYMmTHiMU6cc!?K zt1f1Fx52t6ODOalP}>KtYTbbR_zt(E2n%wudLQH6)1?oxJNi9|3Ecis;jFWIM>>T$ znJ&-~)LhlWmO|-6gU%2%B!X3JbLj(A*pQ?`;gOzWe#Mp+6lB}{I2k3AaWdMSbK_QZ z#TeTE0bgY}9-WwfUGn=n+jSa=- zC3mDtAHK1xcE>%iTG|(Ev4_6K4_2V!*57Ttu=>z{3U+V=gIcwfX_{87b(bf`H|g)X z)3|-$d}X*A2iKdUDGrX?`MZv70iOda&3r*u0jq|bg0UzBlEl29hsHDkx5hLAor@?q zVKOi?V=*>0W@R{GVr5}9VL3KoW;9_qH)1e2I5IOiGGk(8W->Upi@*Y^0vN8@hM)kz z0RV{C#vIKwOGTR(lW)#k;scldxB?xQYQq8$7&-K1p8$XW0MOf*5k!@&Nrz%z7RhT= z;+Ouo0v(sH!vY2v%Q*$x03ZPXj>BhcCHLG#n8R+9%CqnMsAcWV-S-Xe)!vdH5!vl;v`C#|} diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index ad99d1ba..b441d31b 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.52", + "version": "1.0.53", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From e80383c33f54e83e44bf9e4d2c5a612f802e819a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 01:36:23 +0200 Subject: [PATCH 034/203] Make CultCache transactions atomic to observers --- .../DirectoryMessagePackBackingStore.cs | 46 ++ .../Contracts/cultcache-persistence-format.md | 15 + src/GameCult.Caching/CultCache.cs | 424 ++++++++++++++---- src/GameCult.Networking/CultNetDatabase.cs | 239 +++++++--- .../BackingStoreTests.cs | 140 ++++++ .../NetworkingTests.cs | 54 +++ .../Plugins/GameCult.Caching.MessagePack.dll | Bin 47616 -> 49152 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 21032 -> 21564 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 86016 -> 95232 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 30208 -> 32688 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9544 -> 9548 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15872 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12316 -> 12320 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 695296 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 205548 -> 205552 bytes .../GameCult.Networking.WebSockets.dll | Bin 14848 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 510976 -> 517120 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 246920 -> 247876 bytes unity/org.gamecult.cultlib/package.json | 2 +- 20 files changed, 774 insertions(+), 146 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 4f1a2521..454a8ed8 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -253,6 +253,52 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } + /// + public override void CommitBatch( + IReadOnlyCollection upserts, + IReadOnlyCollection deletes, + bool soft) + { + lock (_mutationGate) + { + var previousEntries = Entries.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal); + var previousDirtyKeys = _dirtyKeys.Keys.ToArray(); + var previousDeletedKeys = _deletedKeys.Keys.ToArray(); + var wasDirty = IsDirty; + try + { + foreach (var entry in deletes) + { + Entries.TryRemove(entry.Key.Value, out _); + _dirtyKeys.TryRemove(entry.Key.Value, out _); + _deletedKeys[entry.Key.Value] = true; + } + foreach (var entry in upserts) + { + Entries[entry.Key.Value] = entry; + _dirtyKeys[entry.Key.Value] = true; + _deletedKeys.TryRemove(entry.Key.Value, out _); + } + IsDirty = true; + PushAllCore(soft); + } + catch + { + Entries.Clear(); + foreach (var pair in previousEntries) + Entries[pair.Key] = pair.Value; + _dirtyKeys.Clear(); + foreach (var key in previousDirtyKeys) + _dirtyKeys[key] = true; + _deletedKeys.Clear(); + foreach (var key in previousDeletedKeys) + _deletedKeys[key] = true; + IsDirty = wasDirty; + throw; + } + } + } + /// public override void PushAll(bool soft = false) { diff --git a/src/GameCult.Caching/Contracts/cultcache-persistence-format.md b/src/GameCult.Caching/Contracts/cultcache-persistence-format.md index 66d0e135..e7ffd54f 100644 --- a/src/GameCult.Caching/Contracts/cultcache-persistence-format.md +++ b/src/GameCult.Caching/Contracts/cultcache-persistence-format.md @@ -39,6 +39,21 @@ These are **not** domain fields and should not be exposed on app-facing types: Those belong to the CultCache persistence layer. +## Transaction Visibility + +An authoritative multi-record change uses `CultCache.ExecuteTransactionAsync`. +The executing async flow sees its buffered overlay while staging; other readers +and all observers continue to see the prior committed generation. The backing +store commits the complete batch before the live cache swaps to it. Observer +notifications run only after the transaction context and commit lock have been +released, so an observer-triggered write begins a separate transaction. + +An exception before durable finality discards the overlay. It cannot become +visible through a later flush. A transaction currently permits at most one +durable backing store because independent stores cannot provide one atomic +commit boundary. Applications that require authoritative writes may configure +`CultNetDatabase` to reject record-at-a-time writes outside this primitive. + ## Canonical Store Shape A persisted CultCache file should contain: diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs index 21b437af..1d1dab81 100644 --- a/src/GameCult.Caching/CultCache.cs +++ b/src/GameCult.Caching/CultCache.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; using GameCult.Logging; using R3; @@ -1278,6 +1279,9 @@ namespace GameCult.Caching private readonly ConditionalWeakTable _documentHandles = new(); private readonly Subject _changes = new(); private readonly CultCacheSoaStore _soa = new(); + private readonly SemaphoreSlim _transactionGate = new(1, 1); + private readonly AsyncLocal _ambientTransaction = new(); + private readonly object _stateGate = new(); private ILogger _logger = new NullLogger(); private bool _hasUnflushedMutations; @@ -1348,14 +1352,29 @@ namespace GameCult.Caching /// /// Gets all document instances currently held by the cache. /// - public IEnumerable AllEntries => _entries.Values.Select(entry => entry.Document); + public IEnumerable AllEntries + { + get + { + lock (_stateGate) + return VisibleStoredDocuments().Select(entry => entry.Document).ToArray(); + } + } /// /// Gets all stored document records currently held by the cache. /// - public IEnumerable AllStoredDocuments => - _entries.Values.OrderBy(entry => entry.Descriptor.SchemaName, StringComparer.Ordinal) - .ThenBy(entry => entry.Key.Value, StringComparer.Ordinal); + public IEnumerable AllStoredDocuments + { + get + { + lock (_stateGate) + return VisibleStoredDocuments() + .OrderBy(entry => entry.Descriptor.SchemaName, StringComparer.Ordinal) + .ThenBy(entry => entry.Key.Value, StringComparer.Ordinal) + .ToArray(); + } + } /// /// Gets the document registry used by this cache. @@ -1457,15 +1476,22 @@ namespace GameCult.Caching /// public void FlushAllBackingStores(bool soft = false) { - foreach (var store in _backingStores) - { - FlushBackingStore(store, soft); - } + if (_ambientTransaction.Value != null) + throw new InvalidOperationException("A CultCache transaction owns durable commit; do not flush inside its stage callback."); - RecomputeDirtyState(); - if (!IsDirty) + _transactionGate.Wait(); + try { - LastSuccessfulFlushAtUtc = DateTimeOffset.UtcNow; + foreach (var store in _backingStores) + FlushBackingStoreCore(store, soft); + + RecomputeDirtyState(); + if (!IsDirty) + LastSuccessfulFlushAtUtc = DateTimeOffset.UtcNow; + } + finally + { + _transactionGate.Release(); } } @@ -1489,6 +1515,22 @@ namespace GameCult.Caching throw new InvalidOperationException("Backing store is not attached to this cache."); } + if (_ambientTransaction.Value != null) + throw new InvalidOperationException("A CultCache transaction owns durable commit; do not flush inside its stage callback."); + + _transactionGate.Wait(); + try + { + FlushBackingStoreCore(store, soft); + } + finally + { + _transactionGate.Release(); + } + } + + private void FlushBackingStoreCore(CacheBackingStore store, bool soft) + { store.PushAll(soft); RecomputeDirtyState(); if (!store.IsDirty) @@ -1514,17 +1556,77 @@ namespace GameCult.Caching return Task.CompletedTask; } + /// + /// Runs a buffered cache mutation. Staged records are visible only to the executing + /// async flow. The durable backing store is committed before the live cache and its + /// observers advance; an exception discards the entire staged batch. + /// + public async Task ExecuteTransactionAsync(Func stageAsync, bool soft = false) + { + if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); + if (_ambientTransaction.Value != null) + { + await stageAsync().ConfigureAwait(false); + return; + } + + await _transactionGate.WaitAsync().ConfigureAwait(false); + var transaction = new CultCacheTransaction(); + IReadOnlyList<(CultStoredDocument Stored, object? Previous, bool Removed)> changes; + _ambientTransaction.Value = transaction; + try + { + await stageAsync().ConfigureAwait(false); + changes = CommitTransaction(transaction, soft); + } + finally + { + _ambientTransaction.Value = null; + _transactionGate.Release(); + } + + foreach (var change in changes) + PublishChange(change.Stored, change.Previous, change.Removed); + } + + /// Runs a value-producing buffered cache mutation. + public async Task ExecuteTransactionAsync(Func> stageAsync, bool soft = false) + { + if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); + T result = default!; + await ExecuteTransactionAsync(async () => + { + result = await stageAsync().ConfigureAwait(false); + }, soft).ConfigureAwait(false); + return result; + } + /// /// Adds or replaces a typed document and returns its record handle. /// public async Task> AddAsync(T document, CultRecordHandle? handle = null) { if (document == null) throw new ArgumentNullException(nameof(document)); - var stored = await AddStoredDocumentInternal( - CreateStoredDocument(typeof(T), document, handle?.Key), - source: null, - raiseUpdate: false); - return new CultRecordHandle(stored.Key); + if (_ambientTransaction.Value is { } transaction) + { + var staged = CreateStoredDocument(typeof(T), document, handle?.Key); + transaction.Stage(staged); + return new CultRecordHandle(staged.Key); + } + + await _transactionGate.WaitAsync().ConfigureAwait(false); + try + { + var stored = await AddStoredDocumentInternal( + CreateStoredDocument(typeof(T), document, handle?.Key), + source: null, + raiseUpdate: false).ConfigureAwait(false); + return new CultRecordHandle(stored.Key); + } + finally + { + _transactionGate.Release(); + } } /// @@ -1549,11 +1651,26 @@ namespace GameCult.Caching nameof(document)); } - var stored = await AddStoredDocumentInternal( - CreateStoredDocument(documentType, document, key), - source: null, - raiseUpdate: false).ConfigureAwait(false); - return stored.Key; + if (_ambientTransaction.Value is { } transaction) + { + var staged = CreateStoredDocument(documentType, document, key); + transaction.Stage(staged); + return staged.Key; + } + + await _transactionGate.WaitAsync().ConfigureAwait(false); + try + { + var stored = await AddStoredDocumentInternal( + CreateStoredDocument(documentType, document, key), + source: null, + raiseUpdate: false).ConfigureAwait(false); + return stored.Key; + } + finally + { + _transactionGate.Release(); + } } /// @@ -1572,9 +1689,15 @@ namespace GameCult.Caching /// public object? Get(CultRecordKey key) { - return _entries.TryGetValue(key.Value, out var stored) - ? stored.Document - : null; + lock (_stateGate) + { + if (_ambientTransaction.Value is { } transaction && + transaction.TryGet(key, out var staged)) + return staged?.Document; + return _entries.TryGetValue(key.Value, out var stored) + ? stored.Document + : null; + } } /// @@ -1607,17 +1730,13 @@ namespace GameCult.Caching /// public IEnumerable GetStoredDocuments() where T : class { - var type = typeof(T); - if (type.IsSealed) + lock (_stateGate) { - return _typeMaps.TryGetValue(type, out var exact) - ? exact.Values - : Enumerable.Empty(); + var type = typeof(T); + return VisibleStoredDocuments() + .Where(entry => type.IsAssignableFrom(entry.Descriptor.DocumentType)) + .ToArray(); } - - return _typeMaps - .Where(pair => type.IsAssignableFrom(pair.Key)) - .SelectMany(pair => pair.Value.Values); } /// @@ -1690,9 +1809,21 @@ namespace GameCult.Caching /// public void Remove(CultRecordHandle handle) { - if (_entries.TryGetValue(handle.Key.Value, out var stored)) + if (_ambientTransaction.Value is { } transaction) { - RemoveStoredDocumentInternal(stored, source: null, raiseUpdate: false); + transaction.Delete(handle.Key); + return; + } + + _transactionGate.Wait(); + try + { + if (_entries.TryGetValue(handle.Key.Value, out var stored)) + RemoveStoredDocumentInternal(stored, source: null, raiseUpdate: false); + } + finally + { + _transactionGate.Release(); } } @@ -1701,12 +1832,27 @@ namespace GameCult.Caching /// public bool Remove(CultRecordKey key) { - if (!_entries.TryGetValue(key.Value, out var stored)) + if (Get(key) == null) { return false; } - RemoveStoredDocumentInternal(stored, source: null, raiseUpdate: false); + if (_ambientTransaction.Value is { } transaction) + { + transaction.Delete(key); + return true; + } + + _transactionGate.Wait(); + try + { + if (_entries.TryGetValue(key.Value, out var stored)) + RemoveStoredDocumentInternal(stored, source: null, raiseUpdate: false); + } + finally + { + _transactionGate.Release(); + } return true; } @@ -1734,9 +1880,116 @@ namespace GameCult.Caching store.Dispose(); } + _transactionGate.Dispose(); _changes.Dispose(); } + private IEnumerable VisibleStoredDocuments() + { + if (_ambientTransaction.Value == null) + return _entries.Values.ToArray(); + + var visible = _entries.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal); + foreach (var pair in _ambientTransaction.Value.Mutations) + { + if (pair.Value == null) + visible.Remove(pair.Key); + else + visible[pair.Key] = pair.Value; + } + return visible.Values.ToArray(); + } + + private IReadOnlyList<(CultStoredDocument Stored, object? Previous, bool Removed)> CommitTransaction( + CultCacheTransaction transaction, + bool soft) + { + var mutations = transaction.Mutations + .OrderBy(pair => pair.Key, StringComparer.Ordinal) + .ToArray(); + if (mutations.Length == 0) + return Array.Empty<(CultStoredDocument, object?, bool)>(); + + if (_backingStores.Count > 1) + { + throw new InvalidOperationException( + "A CultCache transaction requires zero or one durable backing store; multiple stores cannot share one atomic commit boundary."); + } + + CultStoredDocument[] previous; + lock (_stateGate) + { + previous = mutations + .Select(pair => _entries.TryGetValue(pair.Key, out var stored) ? stored : null) + .Where(stored => stored != null) + .Cast() + .ToArray(); + } + + var upserts = mutations.Where(pair => pair.Value != null).Select(pair => pair.Value!).ToArray(); + var deleted = mutations + .Where(pair => pair.Value == null) + .Select(pair => previous.FirstOrDefault(stored => string.Equals(stored.Key.Value, pair.Key, StringComparison.Ordinal))) + .Where(stored => stored != null) + .Cast() + .ToArray(); + + foreach (var store in _backingStores) + store.CommitBatch(upserts, deleted, soft); + + var changes = new List<(CultStoredDocument Stored, object? Previous, bool Removed)>(); + lock (_stateGate) + { + foreach (var pair in mutations) + { + _entries.TryGetValue(pair.Key, out var existing); + if (existing != null) + { + RemoveIndexes(existing); + _documentHandles.Remove(existing.Document); + _soa.Remove(existing); + } + + if (pair.Value == null) + { + if (existing != null) + { + _entries.TryRemove(pair.Key, out _); + changes.Add((existing, existing.Document, true)); + } + continue; + } + + var stored = pair.Value; + _entries[pair.Key] = stored; + _documentHandles.Remove(stored.Document); + _documentHandles.Add(stored.Document, new DocumentHandleBox(stored.Key)); + _soa.Upsert(stored); + AddIndexes(stored); + changes.Add((stored, existing?.Document, false)); + } + _hasUnflushedMutations = _backingStores.Any(store => store.IsDirty); + if (!IsDirty) + LastSuccessfulFlushAtUtc = DateTimeOffset.UtcNow; + } + + return changes; + } + + private sealed class CultCacheTransaction + { + private readonly Dictionary _mutations = new(StringComparer.Ordinal); + + public IReadOnlyDictionary Mutations => _mutations; + + public void Stage(CultStoredDocument stored) => _mutations[stored.Key.Value] = stored; + + public void Delete(CultRecordKey key) => _mutations[key.Value] = null; + + public bool TryGet(CultRecordKey key, out CultStoredDocument? stored) => + _mutations.TryGetValue(key.Value, out stored); + } + internal CultStoredDocument CreateStoredDocument(Type documentType, object document, CultRecordKey? key = null, string? storedAt = null) { var descriptor = _registry.GetRequired(documentType); @@ -1754,33 +2007,28 @@ namespace GameCult.Caching bool raiseUpdate) { CultStoredDocument? existing = null; - _entries.TryGetValue(stored.Key.Value, out existing); - if (existing != null) + lock (_stateGate) { - RemoveIndexes(existing); - } + _entries.TryGetValue(stored.Key.Value, out existing); + if (existing != null) + RemoveIndexes(existing); - _entries[stored.Key.Value] = stored; - _documentHandles.Remove(stored.Document); - _documentHandles.Add(stored.Document, new DocumentHandleBox(stored.Key)); - _soa.Upsert(stored); - AddIndexes(stored); + _entries[stored.Key.Value] = stored; + _documentHandles.Remove(stored.Document); + _documentHandles.Add(stored.Document, new DocumentHandleBox(stored.Key)); + _soa.Upsert(stored); + AddIndexes(stored); - foreach (var store in _backingStores) - { - if (store != source) + foreach (var store in _backingStores) { - store.Push(stored); + if (store != source) + store.Push(stored); } - } - if (source == null) - { - _hasUnflushedMutations = true; - } - else - { - RecomputeDirtyState(); + if (source == null) + _hasUnflushedMutations = true; + else + RecomputeDirtyState(); } PublishChange(stored, existing?.Document); @@ -1799,30 +2047,26 @@ namespace GameCult.Caching CacheBackingStore? source, bool raiseUpdate) { - if (!_entries.TryRemove(stored.Key.Value, out var existing)) + CultStoredDocument? existing; + lock (_stateGate) { - return; - } + if (!_entries.TryRemove(stored.Key.Value, out existing)) + return; - RemoveIndexes(existing); - _documentHandles.Remove(existing.Document); - _soa.Remove(existing); + RemoveIndexes(existing); + _documentHandles.Remove(existing.Document); + _soa.Remove(existing); - foreach (var store in _backingStores) - { - if (store != source) + foreach (var store in _backingStores) { - store.Delete(existing); + if (store != source) + store.Delete(existing); } - } - if (source == null) - { - _hasUnflushedMutations = true; - } - else - { - RecomputeDirtyState(); + if (source == null) + _hasUnflushedMutations = true; + else + RecomputeDirtyState(); } PublishChange(existing, existing.Document, removed: true); @@ -2104,6 +2348,34 @@ namespace GameCult.Caching /// public abstract void Delete(CultStoredDocument entry); /// + /// Atomically stages and durably commits a buffered record batch for this store. + /// Implementations must restore their prior staged view when finality fails. + /// + public virtual void CommitBatch( + IReadOnlyCollection upserts, + IReadOnlyCollection deletes, + bool soft) + { + var previousEntries = Entries.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal); + var wasDirty = IsDirty; + try + { + foreach (var entry in deletes) + Delete(entry); + foreach (var entry in upserts) + Push(entry); + PushAll(soft); + } + catch + { + Entries.Clear(); + foreach (var pair in previousEntries) + Entries[pair.Key] = pair.Value; + IsDirty = wasDirty; + throw; + } + } + /// /// Persists all current backing store entries. /// public abstract void PushAll(bool soft = false); diff --git a/src/GameCult.Networking/CultNetDatabase.cs b/src/GameCult.Networking/CultNetDatabase.cs index 8000e45b..9c1d1480 100644 --- a/src/GameCult.Networking/CultNetDatabase.cs +++ b/src/GameCult.Networking/CultNetDatabase.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using GameCult.Caching; using R3; @@ -297,6 +298,11 @@ namespace GameCult.Networking /// Gets or sets the durable store for accepted shard mutation logs. /// public ICultNetShardMutationLogStore? MutationLogStore { get; set; } + /// + /// Gets or sets whether authoritative writes must occur inside ExecuteTransactionAsync. + /// Use this for state owners that forbid record-at-a-time mutation bypasses. + /// + public bool RequireTransactionsForAuthoritativeWrites { get; set; } } /// @@ -464,6 +470,8 @@ namespace GameCult.Networking private readonly Dictionary _nextLogSequences = new(StringComparer.Ordinal); private readonly Dictionary _appliedShardSequences = new(StringComparer.Ordinal); private readonly Subject _changes = new(); + private readonly AsyncLocal _ambientTransaction = new(); + private readonly bool _requireTransactionsForAuthoritativeWrites; private bool _disposed; /// @@ -476,6 +484,7 @@ namespace GameCult.Networking _runtimeId = options.RuntimeId; _documents = options.DocumentRegistry ?? new CultNetDocumentRegistry(cache.Registry); _mutationLogStore = options.MutationLogStore; + _requireTransactionsForAuthoritativeWrites = options.RequireTransactionsForAuthoritativeWrites; _shards = (options.Shards == null || options.Shards.Count == 0 ? new[] { CultNetShardDescriptor.PrimaryAll(options.RuntimeId) } : options.Shards) @@ -503,6 +512,49 @@ namespace GameCult.Networking /// public IReadOnlyList ClientAuthorityScopes => _clientAuthorityScopes; + /// + /// Runs one buffered authoritative database mutation. Cache state, durable state, + /// mutation logs, and database observers advance only after the complete stage succeeds. + /// + public async Task ExecuteTransactionAsync(Func stageAsync, bool soft = false) + { + ThrowIfDisposed(); + if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); + if (_ambientTransaction.Value != null) + { + await stageAsync().ConfigureAwait(false); + return; + } + + var transaction = new CultNetDatabaseTransaction(); + IReadOnlyList afterCommit; + _ambientTransaction.Value = transaction; + try + { + await _cache.ExecuteTransactionAsync(stageAsync, soft).ConfigureAwait(false); + afterCommit = transaction.AfterCommit.ToArray(); + } + finally + { + _ambientTransaction.Value = null; + } + + foreach (var publish in afterCommit) + publish(); + } + + /// Runs one value-producing buffered authoritative database mutation. + public async Task ExecuteTransactionAsync(Func> stageAsync, bool soft = false) + { + if (stageAsync == null) throw new ArgumentNullException(nameof(stageAsync)); + T result = default!; + await ExecuteTransactionAsync(async () => + { + result = await stageAsync().ConfigureAwait(false); + }, soft).ConfigureAwait(false); + return result; + } + /// Gets whether this database instance can authoritatively replace the document at a key. public bool CanWriteAuthoritatively(CultRecordKey key) where T : class { @@ -817,6 +869,7 @@ namespace GameCult.Networking public async Task> PutAsync(CultRecordKey key, T document) where T : class { ThrowIfDisposed(); + EnsureAuthoritativeTransaction(); if (document == null) throw new ArgumentNullException(nameof(document)); var descriptor = _cache.Registry.GetRequired(); @@ -825,20 +878,27 @@ namespace GameCult.Networking var previous = _cache.Get(key); var handle = await _cache.UpsertAsync(document, new CultRecordHandle(key)).ConfigureAwait(false); - AppendMutationLogEntry( - shard, - previous == null ? CultNetDatabaseChangeKind.Added : CultNetDatabaseChangeKind.Updated, - key, - descriptor.SchemaId, - document, - previous); - Publish(new CultNetDatabaseChange( - previous == null ? CultNetDatabaseChangeKind.Added : CultNetDatabaseChangeKind.Updated, - key, - descriptor.SchemaId, - shard, - document, - previous)); + void PublishCommittedPut() + { + AppendMutationLogEntry( + shard, + previous == null ? CultNetDatabaseChangeKind.Added : CultNetDatabaseChangeKind.Updated, + key, + descriptor.SchemaId, + document, + previous); + Publish(new CultNetDatabaseChange( + previous == null ? CultNetDatabaseChangeKind.Added : CultNetDatabaseChangeKind.Updated, + key, + descriptor.SchemaId, + shard, + document, + previous)); + } + if (_ambientTransaction.Value is { } transaction) + transaction.AfterCommit.Add(PublishCommittedPut); + else + PublishCommittedPut(); return handle; } @@ -873,6 +933,7 @@ namespace GameCult.Networking public Task DeleteAsync(CultRecordKey key) where T : class { ThrowIfDisposed(); + EnsureAuthoritativeTransaction(); var descriptor = _cache.Registry.GetRequired(); var shard = ResolveShardInternal(descriptor, key); EnsurePrimary(shard, descriptor.SchemaId, key); @@ -881,20 +942,27 @@ namespace GameCult.Networking if (previous != null) { _cache.Remove(new CultRecordHandle(key)); - AppendMutationLogEntry( - shard, - CultNetDatabaseChangeKind.Removed, - key, - descriptor.SchemaId, - document: null, - previousDocument: previous); - Publish(new CultNetDatabaseChange( - CultNetDatabaseChangeKind.Removed, - key, - descriptor.SchemaId, - shard, - document: null, - previousDocument: previous)); + void PublishCommittedDelete() + { + AppendMutationLogEntry( + shard, + CultNetDatabaseChangeKind.Removed, + key, + descriptor.SchemaId, + document: null, + previousDocument: previous); + Publish(new CultNetDatabaseChange( + CultNetDatabaseChangeKind.Removed, + key, + descriptor.SchemaId, + shard, + document: null, + previousDocument: previous)); + } + if (_ambientTransaction.Value is { } transaction) + transaction.AfterCommit.Add(PublishCommittedDelete); + else + PublishCommittedDelete(); } return Task.CompletedTask; @@ -906,6 +974,7 @@ namespace GameCult.Networking public async Task ApplyPutAsync(CultNetDocumentPutRawMessage message) { ThrowIfDisposed(); + EnsureAuthoritativeTransaction(); if (message == null) throw new ArgumentNullException(nameof(message)); if (message.Document == null) { @@ -920,31 +989,41 @@ namespace GameCult.Networking var document = await _documents.ApplyRawDocumentPutMessageAsync(_cache, message).ConfigureAwait(false); var kind = previous == null ? CultNetDatabaseChangeKind.Added : CultNetDatabaseChangeKind.Updated; var predictionKey = PredictionKey(descriptor.SchemaId, key); - if (_predictedDocuments.Remove(predictionKey)) + var reconcilesPrediction = _predictedDocuments.ContainsKey(predictionKey); + if (reconcilesPrediction) { kind = CultNetDatabaseChangeKind.Reconciled; } - AppendMutationLogEntry( - shard, - kind == CultNetDatabaseChangeKind.Reconciled ? CultNetDatabaseChangeKind.Updated : kind, - key, - descriptor.SchemaId, - document, - previous, - new CultNetShardLogEntryMessage - { - ChangeKind = previous == null ? "added" : "updated", - Put = message - }); - PublishUntyped( - descriptor.DocumentType, - kind, - key, - descriptor.SchemaId, - shard, - document, - previous); + void PublishCommittedPut() + { + if (reconcilesPrediction) + _predictedDocuments.Remove(predictionKey); + AppendMutationLogEntry( + shard, + kind == CultNetDatabaseChangeKind.Reconciled ? CultNetDatabaseChangeKind.Updated : kind, + key, + descriptor.SchemaId, + document, + previous, + new CultNetShardLogEntryMessage + { + ChangeKind = previous == null ? "added" : "updated", + Put = message + }); + PublishUntyped( + descriptor.DocumentType, + kind, + key, + descriptor.SchemaId, + shard, + document, + previous); + } + if (_ambientTransaction.Value is { } transaction) + transaction.AfterCommit.Add(PublishCommittedPut); + else + PublishCommittedPut(); return document; } @@ -954,6 +1033,7 @@ namespace GameCult.Networking public Task ApplyDeleteAsync(CultNetDocumentDeleteMessage message) { ThrowIfDisposed(); + EnsureAuthoritativeTransaction(); if (message == null) throw new ArgumentNullException(nameof(message)); var key = new CultRecordKey(message.RecordKey); @@ -967,26 +1047,33 @@ namespace GameCult.Networking } _cache.Remove(key); - AppendMutationLogEntry( - shard, - CultNetDatabaseChangeKind.Removed, - key, - descriptor.SchemaId, - document: null, - previousDocument: previous, - wireEntry: new CultNetShardLogEntryMessage - { - ChangeKind = "removed", - Delete = message - }); - PublishUntyped( - descriptor.DocumentType, - CultNetDatabaseChangeKind.Removed, - key, - descriptor.SchemaId, - shard, - document: null, - previousDocument: previous); + void PublishCommittedDelete() + { + AppendMutationLogEntry( + shard, + CultNetDatabaseChangeKind.Removed, + key, + descriptor.SchemaId, + document: null, + previousDocument: previous, + wireEntry: new CultNetShardLogEntryMessage + { + ChangeKind = "removed", + Delete = message + }); + PublishUntyped( + descriptor.DocumentType, + CultNetDatabaseChangeKind.Removed, + key, + descriptor.SchemaId, + shard, + document: null, + previousDocument: previous); + } + if (_ambientTransaction.Value is { } transaction) + transaction.AfterCommit.Add(PublishCommittedDelete); + else + PublishCommittedDelete(); return Task.CompletedTask; } @@ -1699,6 +1786,20 @@ namespace GameCult.Networking }; } + private sealed class CultNetDatabaseTransaction + { + public List AfterCommit { get; } = new(); + } + + private void EnsureAuthoritativeTransaction() + { + if (_requireTransactionsForAuthoritativeWrites && _ambientTransaction.Value == null) + { + throw new InvalidOperationException( + "This CultNet database requires authoritative writes to use ExecuteTransactionAsync."); + } + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index ee833f17..39fe52ca 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -1335,6 +1335,146 @@ namespace GameCult.Caching.Tests Throws.TypeOf().With.Message.Contains("changed target schema")); } + [Test] + public async Task CultCache_Transaction_Hides_Staged_Records_Until_Whole_Batch_Is_Durable() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-transaction-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using var cache = new CultCache(); + cache.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var firstKey = new CultRecordKey("transaction:first"); + var secondKey = new CultRecordKey("transaction:second"); + var staged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var observed = 0; + using var subscription = cache.Watch().Subscribe(_ => observed++); + + var transaction = cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "first", Value = "one" }, + new CultRecordHandle(firstKey)); + staged.SetResult(true); + await release.Task; + await cache.UpsertAsync( + new NamedTestEntry { Name = "second", Value = "two" }, + new CultRecordHandle(secondKey)); + }); + + await staged.Task; + Assert.That(cache.Get(firstKey), Is.Null); + Assert.That(observed, Is.Zero); + + release.SetResult(true); + await transaction; + Assert.That(cache.Get(firstKey)?.Value, Is.EqualTo("one")); + Assert.That(cache.Get(secondKey)?.Value, Is.EqualTo("two")); + Assert.That(observed, Is.EqualTo(2)); + + using var reopened = new CultCache(); + reopened.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + await reopened.PullAllBackingStoresAsync(); + Assert.That(reopened.Get(firstKey)?.Value, Is.EqualTo("one")); + Assert.That(reopened.Get(secondKey)?.Value, Is.EqualTo("two")); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + + [Test] + public async Task CultCache_Transaction_Abort_Cannot_Leak_Into_Later_Commit() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-abort-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using var cache = new CultCache(); + cache.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var abortedKey = new CultRecordKey("transaction:aborted"); + var committedKey = new CultRecordKey("transaction:committed"); + var observed = 0; + using var subscription = cache.Watch().Subscribe(_ => observed++); + + Assert.ThrowsAsync(async () => + await cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "aborted", Value = "must-not-leak" }, + new CultRecordHandle(abortedKey)); + throw new InvalidOperationException("abort probe"); + })); + + Assert.That(cache.Get(abortedKey), Is.Null); + Assert.That(observed, Is.Zero); + await cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "committed", Value = "survives" }, + new CultRecordHandle(committedKey)); + }); + Assert.That(observed, Is.EqualTo(1)); + + using var reopened = new CultCache(); + reopened.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + await reopened.PullAllBackingStoresAsync(); + Assert.That(reopened.Get(abortedKey), Is.Null); + Assert.That(reopened.Get(committedKey)?.Value, Is.EqualTo("survives")); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + + [Test] + public async Task CultCache_Transaction_Observer_Writes_In_A_New_Commit() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-observer-transaction-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using var cache = new CultCache(); + cache.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var firstKey = new CultRecordKey("transaction:observer-source"); + var secondKey = new CultRecordKey("transaction:observer-write"); + using var subscription = cache.Watch().Subscribe(change => + { + if (!change.Key.Equals(firstKey)) return; + cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "observer", Value = "separate-commit" }, + new CultRecordHandle(secondKey)); + }).GetAwaiter().GetResult(); + }); + + await cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "source", Value = "committed-first" }, + new CultRecordHandle(firstKey)); + }); + + Assert.That(cache.Get(secondKey)?.Value, Is.EqualTo("separate-commit")); + using var reopened = new CultCache(); + reopened.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + await reopened.PullAllBackingStoresAsync(); + Assert.That(reopened.Get(firstKey)?.Value, Is.EqualTo("committed-first")); + Assert.That(reopened.Get(secondKey)?.Value, Is.EqualTo("separate-commit")); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + private const string NamedFixtureCanonicalSchemaJson = "{\"schemaName\":\"tests.named_entry\",\"schemaVersion\":\"tests.named_entry.v1\",\"members\":[{\"slot\":0,\"name\":\"Name\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":true},{\"slot\":1,\"name\":\"Value\",\"type\":\"System.String\",\"isReference\":false,\"many\":false,\"targetSchemaName\":null,\"indexAlias\":null,\"isName\":false}]}"; private const string NamedFixtureSchemaId = "sha256:e7b97801b94190f3159012ede45b0069bb09ebf7920f7432c971bc86a0e08de8"; diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index 03b4f76c..28245a3d 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -2701,6 +2701,60 @@ namespace GameCult.Networking.Tests Assert.That(changes[0].Document!.Username, Is.EqualTo("Raw")); } + [Test] + public async Task CultNetDatabase_RequiredTransaction_DefersRawPutPublicationUntilCommit() + { + var cache = new CultCache(); + var registry = new CultNetDocumentRegistry(cache.Registry) + .Register(CultNetDocumentBinding.ForDocument( + cache.Registry, + payloadSerializer: SerializePlayerDataPayload, + payloadDeserializer: DeserializePlayerDataPayload)); + var database = new CultNetDatabase(cache, new CultNetDatabaseOptions + { + DocumentRegistry = registry, + RequireTransactionsForAuthoritativeWrites = true + }); + var key = new CultRecordKey("player:transactional-raw"); + var message = registry.CreateRawDocumentPutMessage( + "put-transactional-raw", + new CultRecordHandle(key), + new PlayerData + { + PlayerId = Guid.NewGuid(), + Email = "transactional@example.test", + PasswordHash = "hash", + Username = "Transactional" + }); + var changes = new List>(); + using var subscription = database.WatchRecord(key) + .Subscribe(change => changes.Add(change)); + + Assert.That( + async () => await database.ApplyPutAsync(message), + Throws.TypeOf()); + + var staged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var transaction = database.ExecuteTransactionAsync(async () => + { + await database.ApplyPutAsync(message); + staged.SetResult(true); + await release.Task; + }); + await staged.Task; + + Assert.That(cache.Get(key), Is.Null); + Assert.That(changes, Is.Empty); + + release.SetResult(true); + await transaction; + + Assert.That(cache.Get(key)?.Username, Is.EqualTo("Transactional")); + Assert.That(changes, Has.Count.EqualTo(1)); + Assert.That(changes[0].Kind, Is.EqualTo(CultNetDatabaseChangeKind.Added)); + } + [Test] public async Task CultNetDatabase_ApplyRawPut_ResolvesForeignSchemaIdFromPayload() { diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index 1deb800477b19261d5e7ac968d3cda450b042687..7284604e40cbebb6969c979999eed559cf05ec16 100644 GIT binary patch delta 18501 zcmaic349b)w*INA?&>w2^p@`I9gkB^6XrA6Y#Vn47>mGsTFNZk$*wQNas2*6v7pR9F zYS7Vg26(4{H<))sN|YPT?GZz{$vhA#RBkqZj3gBa80LTnz3%S1JM}tpI)HH7&y8wI z?o^2qFVpoC`eryaeaIjoH_r^nvUqwCd(fwP z*r^(`&HDJuW>JoFbxx78+)U&c$_jH>PN8zEIU}cB*<#+AQ=qIgx8_tSQ_T-^YRsYn zpBc`LxUNJ1G{!87RcyaBccbER5C($sOeK_^{u5GZIk#&wZ|5BUN4k|QEg0g?y>JJc z|0!m+$e0;nqAI`IoP;9v1#CtJ6d7uBSr-L*)%X(Du+x$Himz$Csv)SSzGm5_8k|kg zmHInVPsn4`K?u212XHB~g8#zmO?_@zxu*ZL#!~-)5fyxcOZD9c{XxCE3slBOLkekB zq*qPuNWo{P;Om+~(3g520qhL^!)VoEwG@tBbGNhW$l#u?8K|+e@jDLeG$x@_|MCuI zW0{pg>RagDsW*UOe^s}T-*}V749gnIFx~>i1x37)96a_zu73YuAml%hU5){^nrRQp zzhLx)1KHuss=Qzzn0a3~3zjr@T9(~SnXz%<%wT3PaQn>Of3b&-iv=BB$_4GV27k3! z&?!wik?ZQN>ju^j?}5reOL&bSGVz&1ek# z6{|gJGvEHzN~PM|4MOTWlr{bhz?3Ut#q*~W2H%aCC9}=d`Sa7?!%#KSotlAb%P_ch zS}X44yJRm6$yTFip|Hw%ogh=ir}8Xt$ymJN&Ntiu)8W8bf^e*c9hdIRHKAOli=! zj5MngIq8vX46<NYTpRZFKqhlTQSuGj%rBNy713!&}QyLYqIs+%oV@jMR1wS;wh zOtAo+T#>5mn>*#?`Y+;m=~K^&dpMvj8MDa>h$&Mg3t4v974~O`Gy2|z2^RDRGwurq znw~%rzF+jEGf~1`IdEq!K_@#>Z=*ror>McqW~RZ+6Z(@}g^OF{=6Z|)6h(lnV3sk9 zBLy+EfnYH8lex1fKg$CbH5fGJ!vv2-=3k4tm0=}ECs+@XtsL$5s3aKxLV9xe@EXtfoU+imso6uY!*UBBVFtZf)uTE=xXuV?csXlAl?oS9de zm+qRm%uQd_1eeTAzv|Xfn9g2f4vJ-9-}5MlJB5kq^`yc)1tQ#sZV$R(F-OL;!)xGS z;0-$*L5KDHb%va>cL0yU;<6UX2M&7iP;!~il$NFAP`8ZlnrJ(R%Yh-;$`)&I(dShy zmvwdAz>c}%$QA>gStS#|tLncP88KLGaySia>~wUyj^^k_!knBe%}?-yX&dys@tci! zcs`-=4&AZzDziPAo1Vo)Z)Vf&GjAJ&XWi1dT~tF@g`1eY*tyze6eFdojFW|~We@b> z|KWkv#*y7os40(IXym7HDKrXzsX_qO=BK#*MUagIpvjN6Q32r^)yr z`&+9uTUsvfYTkIt%oeNP@hGnHNh)ba%N1PDRZomNMj}i`IStjCo1RZi5rin z=XHG1n_1a0F`K)nWMVFX3X{t6>X=lhsOy~iFYdcjb1K_c)r<3M&Om7w@NMMYOO3*sTtEDr|Qgs74iCd6s*MBnb*@n?_-8yUIm+W z+0AL;Y?~QVecSdEufjQP)byVujY)c`e*TAi) z`&`BuklExi=9)@%3~$gm)rCXFjaSvwazJvyo0v?g6)YO)uBsHWfLO)iN{9xYY1Zb- z>P%HO@}=g|>SCq8xxTst_)_&bSK#gmstf*zV7aLQ=mf8x-78Ozk5f};fc3~yA*arS zXq*M8)}`J`HCs;&1h2fQ)0SH^7NP+Q-a{;FA`ddB)m&71HW&U+3zy~Rq(-yRdu8VS zn(A?bp}bDBx?%|A1sCZwujMo}CUp*IYA66hf{wMe_+6@ktrBxarF_K0s0EF4*U@B&s=}2T4#AH@f#gPf`k-oD1gE6+?y_Jk zTB{nPxY%gG%A0uIkX2z2s8^50vlE0}O9BmSZ5jie&!uH+x6e$Ch3tx@E;bMK%+1AZ zs~?Q(s&NUl2QzhRXNXt33(QEb?uD2jK{Y2<&~zyWZ5qd6bXRIThe%y!PV9Ad5Hkm_ zj3>L<__w*OS9GK#AqPM{(sOgNW2f@)AFN-5yIDzAJIb=F3d&rm2?&P({CDO+<8my-UIqSw(tmoLA53 z{g61B>w5R?g?(D^Z6}rp<6NHUSi-Q@b@%BDv8T#wos3eI-UvFFmyqv~LXGLH9T98b z1X5Fg86nM>imN}Q8ja?R+9IXeyrs6(KOH)?xew2;E6p9XWy%cm>rg^-!zb z%9^IMSzOoMKbuLRX4K#&GkewL2Sw4_QW!Il5NpSDc7wZY-cMe;Wh!zgP|3nOXvj&6 zSF7x;rQYS~ca>F93X7Cf*u(s^u0+W+UG+T*|1_X7{4H$=vKT;-W%%}9Pk*&Yixr*(jr?g=f6xELGSyfJB5j>D378r+ipHFS(Jz6s+x1+ko zCYVT$>aayzonEa_9Xq?^^@@+lEH>Z#u}{HyvP`+U~}?mlhV96Hc3P)R1mKjcnmmXH_b;2{Jh6ta5b{C z#txYu0@y}`QS?EznRf`bL(maK2_zwpI~A+lb|fSlZhqXcA*jpN8|Ap8x~I`ngmY+~ z+h?s;n|V8_Nx$ji*H@0zkj%-7jsA$m!zoUwYf<~%*|Scl)--a{LzqUrFa%m^F-)2P zxe9q<;YT*vWt`2koJ}*VN$*nD>qfe_<%RdAlU~nt@?yPiu+toCIT?7zz{^^eBE*~t z2!3^^Vmmq&W4&(F{%WzNG)h;VZne8uvl_y@Q@c7*^NM~k&JrskR#r?T%qZDSsm;8b zl5xV^!WzV*z!n)MT}H{(F-W}cvCX0iovw>3ak?_2TAZ~8@C zyiL(kt!Ci#ICcc(r`Ml34r_S{)O&Fqfng{avv9%8xmPw}ASmk+obFLfX~S>?>oIF-3p353GKlL3o5l@!xg|4yEj6k$-y!{#rUZBjK>Ei(fe`D zzz5xozkBg|#eX`zm_4SOPD5gh7e~hyXHpaPa5}vj)#5r;#m>*yX+SpPzteJ=Ab2dw zs%()D3f?W6?;@;!AoOvrm#)peJXfb~Q4alj@?*%aM8+2D)QoC$IxILY#`+&h8PAAv zsa}cxz-f-(mj-B^_I1olPZ#~Z)JyqAX*SF$?VlT8pLNfxg_Cm%f7zi&NE$I(A zL;7P-1()5C#nyKcu%_u+jEznYJ6QF!RPef>AzkDw<&IipaG)ba`!Qz!5{y)Gsn4aF zh*bP_;$^gNS{cW=P%4`z{d6pgtNdKzU!TRzyA9Qc=vK!$I{NZvja6<5@rJZ@N5Vjr zN2Q}KM@Q*&Oj>zvDUWw;4p)%QWx2YHahtS83v!BcLY!8Gq(#zC>!s&@Tg0XQmdibQ zG}av$^t0x}bS7u_MJeNNLyY}Jeq9Pb=jApHm&#(8&wP8iyo?Ipgi01Yf_rw@Nj4Do z`!LeTCAO^S+>-J54iR@gJZECkG7Aa|;pQBV!}oMp=)<^U$CFp?^el>CNQRLQhn)%v zj&OV(8-~OGxUfr0qpr;v*7>de_$3KaxW zz-<`|f#)%%X@-zp{%fE*;BNyaMRQxmC`F-vW~_kbxPOg=_)XTR)_2Ng0K1hlelD^X zavy3deK6~i)*muQDVMZHvlu^xJgW8Yd5q3{#xEfk)0bHfXBAUr#THC&@C$h3>VOmv#*%WkU0I$^Jz(Q9A@KqP{~Jbc%$Ht>OjbT_Xywzp38s=9#tHf zvBon4$kDL=|8L=}5QV-Q;Ch|n1?Feo5xOXW8-i|fKj3`~_uDv|onsX03Qe|H{6L}t z>{?+j)Bci&LmK_TW`E2q&>VE!W=1-`23v_)#e_dAsFN1h?A5YD-AUKkthD47pOdb) zS)zCi*b19C(l~5Dh+ZbH-baUQX2jELu+F_AJIk9;5M4%p zI?1+#{PfvL)};CAyOWG<&QeJm<@lEv5n*k1lzd@-Q(iB65}qZ(R>eO_{1;eH+qORT zVj6U!O*hBh3T4ub!mMOMbeqN8FI7}KLbS$aZ&w@$h3GdnyCm-;B}@<7Y+7DB*b_FZ zj(ns<=sBAWjI@L8w%Hii(ouTVrn6y;(mtE@$oe#tP5W(jcGf>bG5W-2{|NmMiqqe1 zb|T~m=g>Dc`$PC6C6|7**+=1aFh^4Q$$e|i8ay}rHhVB9i}KRMX8=6*R}^K2^QpjQ z>7u-F0hQV8wy+T{q#B!T4)+ch(P`8WYxI1mK**ITYxCcFbk}VJSB0gH?D6N6J!gVw`ZPT@Zb>Vut!Df@)J48}3VQ+bmuFVz?hYz^`Dmie?914xdgR*zD54+u;GU67L3Vn^N&% z_zW7*-D1Nlj)c#m$<;U+W#iDoe})Irdw3gWR#W(W_-rz9v;ih_b}+prjHhK54W@U5 z?L{(bWC(p|vvj#Pat?iBvroLC$WZ#uX5V<@k#ot3V+J1VdJOh3%IJY#b1)r-(LSHD zZR4=aL}WM>+U$zV%E$;Rw^>PMedGe_VY5D&XGTU+UyC8ND1IRI4`FxHpL1SxjH2&_ zy-asvvo)If_T=i@=wmEqqp7_f`(IuZUi0OJN7E5dHa-{13y-0NeVM&X9|jz*vGlgh zjGXf#7gJ+D%XTz!UgQ$`-ew2m7e+3nZ}~6<(}j;NCRCv)M5{G+Zbu28N)30KgAm(GpPK0i|vf;jm)8e z!Z3v1HzHTjcf`G5^F~@>*5$47Uront);sv3;~E-#p;c~rP9l0O&9GVDoMdz{y=k-IVAs)* zi>xTRuY0tamI!N0)8pN0qf4lClx5s#yy#d;tv36@I4jyhb-a~BE3WnpjkeNV!rI6c z84+zGV~k}xkd>X$MmvPH(Gqt&vW#A}Y%+mwq;G_o^m@?`%8m5Bu)|6^AA9ke$bT`1 zIjrP`27=`Zvu6IyR3^+?kZ-0MVaupR-5a@?&a_#cGa0>whT3etK03ObM%v79O^U9d zOKkRzYj5OMnr^cz)9z$+CC#;Ij%RfAHoDqoOFfgKx6?A4{T>zGL8i?np~5@qew#gm z3Rlrao6SLmt7(hPHlPNRw%hC|C8KxI^ESIsNzaY0p}jW!o7x<`o8Ge78F0Oa-nZE> zbw%`E`oLz#9cFYb{ncip^>xwv=znbHaczv=Pye#nc2_dGj()P)5AJo*2gr$|38G~* z(UV9=e?xAYzT-$n*UPyD8++(aMcXOIw!P#@MmJEg&2B+z4^oBAw$k3nM(Ss?2h?PA z6AiN2>CVy7hiI71?$jqmAEr?@JJYA{7Z)6+Ag;`;qqJ(Wb=>9(X6xG@+ig~e}W_0p=n&#Oy4}~I6)4iQ+&rrK< z`xqPYXDB^uyj5X=7LPnj{QWw2ex>{8=nlHeX7wIV_D(wIGRyXrr#Sn+XspfhsW|(2 znrbscEzaIW3xru|zd%cD+g?=o0)1~Y74^MH{`3S6YPs&Fh_GdNef%c6o06Sud#KvB z-J^UT-Ged;ZT0ez)TMYzDzqZTarm!8KLU?kjrdDA2bE$k=%k*&uAVhTSpQWitl>?? z3cyZl@d~Pn#wzejw0ygU<0LwJ{_j55=VWC`{4@%jBqDcItkL7L+2kM{K|{U{S)~U< zU1g2F#-2bCy-JoR`&e>^H9wPmEbE`+c>Z({6W!Gz*A#Iv%awaVAxrauRM1&7NvxLU z`W*JO;#`cOQD_bJPr#E&{7QeMyNsOGfv-vDd?n#t6Fi#WKo&kH<87s4VG;IsNEf}O z(I?3)ykdR=O$Tx-Rk}h-VHXFv5WT3;81YH}QZ*be2P&NaYU1j!UH><|)lU|3XqA>q z=ruA}r>rXdS$c&vD!q)f(BEls+L6b^YS}1pvd|i*C{pRLI&-D0Ep!3jlRKw9My*0#^Kg#m_ z?_9XE+&Q5WSscQ`&K0nK$LM}2{b7|V$C-6UpII@jQkHB@Or8^%|D8Lna|lcBjMgOT z?B6jN-82n{X*xdh@g_J8zixd9GQSrFfjZ>?y;K3r!Z8`%rM!;MC7X_B z@TxUb*{s%7j)I;$Z3%F!vJ!83Gk{ym7XZ)AO~Yq#rm4K9azEvuC+<~plx29Gy^9*N zwkrG7Nl920e*0Y~nj9q<-lc4UoZhM|rq{FH1)dlB2zXuo$G{&ejsShh&w+hIUn_^z zm6<;%N7ePQ40RWMmn>6{s+(iAsz;ga8=@XV#aZN0mixx2i}qIk zPb^d4v^AS!_o$0$b&&gbSMrgxs%TvJJ?WOCz*p3xRGp(kQ%=4rrc<(0Y2beL+Lim4V*QB*ch&JUm{0y#5dDP4JL^2COYf4sYM`(WR zBjpI?l|7~fwI*YmmZQ~F?nSBBy-nI$3A$E^pnYqVYmwqwh49wDRw*iATvWUtKKGX% z1ahKl(^A1&r6iaI^9tm{J_z*^;~lzk=&;OaHCyRMwRjNQw&WRMw+{oyxU> z_h){jaO&;KPQ_L65%6r$zYt`(PceI1z4HGY1&H%dIY-!v8h0w`8*x#TFpf}mDqAu+ zM5u!CQZc_8WceH%Jby)J23(F}ZFQ#KQK3DBU*mp7u0)QbmzM8Muw0pR>_IhIxPz92 zKE`z8Bb$EOMY*+(&oQin9N!8a7ko_;+NT^yqriSC#?xV{W0gDPD{kit;&e5}y;tUnq@SBKSE? zPu}KAzoyh7&<&`j-L+Dx*{pGPBb1Utd~HOG`;>Rl$@|p)6?twRzi#fkB*Z!?wMj}H zm!ywNWs9|4$;;d$l;I^!?#IOQX~A8B$7y)UUiV&+N2Jlp+gxu7*^AHtbnMfC&)n}} z5EKMA9e%S*a zzYNEIC8K1ACnEZMH7#Z=K{}1n&MH64yH8t~H`x1{_AS26EfuHz+S7@P@N+cwo&HiG?xA7o!ZF2o4mSEZ8d8F1TB8kCXlPh>lIz%==amac!SP!qE+X) z(kAi-JWS2EjdohXoa%Ze)b>aUroP@H;CK@`HlDn zk@pB57F04MGGjW_kikxEqS_$x9>K%FKgX4TxC#ymtfs^929etYHwf+#JS?bWa zuJAN^{_ff0d&O7npW;vXSNS*ipY}ulq=E`T(e#G zxE^)&agT71aW8eRc5igQ?f%gHy_-B9{2T8co-;iccvgG%dfxX8^Um~c@IL4LqgU~b z^Ihq?+SlrP*7u{&>(BAO?tj;RRmQ@M9U1Ru^bL#(I9mBkhVX-+j=!j!X0gutyFV~I zK&M02;OMfIn*#me_Mb(+mIGhz!j8Yk1Lv4OP3=ErZvh8%Py}a_{Oc`?>*Op{g@0GT zWpw&;fkUpJ;XsW4Ju-g2g};P-t+uZ-?dw(cHHE8rU*q}drLI-V+3uf|D;#er%iXss z>)dZC&$yohzZsu5z~4|l!Ka(&4dsuXx0J)4pOhNsG3xJKr8IcYQP+5HRJ^S-l*D<*dHzL^-%vMa>{n}?`yqd!?$-XLZuK6+=OD)^^2(<4f~dGYk=i9Q{++I}nzPF;1?%!Ow+ESx%ni`Mp&=>5!H z)BlL-d;MDV?N@U3&t7S0b(4CT!Rc|t|KZB+F$9GFF9L2aobkRg2(fzqN-)-rp4QMf zZ0^EZ*d^y)lYY(9teeFZ_T~!B`({Qi?z7NlRs8Z?Jv)k&=1&Z$8+-!Uhi zsGoGISpBaR`-c>(xBIWhRV&N`SDkEBAI@X--2866CY|;xg;~kXudSVQnmKS*#2h`h z2F-7q8v))u*U;)G_2meqxU%QT20;2n1GfJ-*OTe%G;G_omRhf~aoU_|3%A$aazb$~ zn%6XK!SK zXsGp=8VI!o3jzfJu(qTEV*M|;RD}=?wq=M1gsAQ64g@k$a+P{g7YKIcD46NhPMN_y z2}1mR_*IM>*ZLS&Wa*N&PU<$_`b}Z)q!I{NS-AtD%L|;L);(5kd_}$B{0WF9z$UX` zeVMY~9J0QfU+3^jfG;P)Tk#Hq2L0?!E`70#7_x9+&K-l2Z+o%Ffuh9WGyOjG&ahsZc?P z-S@p>P|hkt03{T@Egnk`Ru%X;J6*$=+)(5EAQcfpO5PCMgXGM{{MZEst- zE^R9jd)}6T3qCJhn|ZBg(fHxhP`=|Y&2nN@ake`DJ=5IQygjj_yJ7~OYW46VjBD+) z{hp^jRg}8z%}>9ijH$zGW&_pZzg{#QkGXpMY_%VqMs<)I@m|>v+FJa0wHKt`(Dj4I z6#Um0SN_yNQ;P_-@a+rVI;t~QJzMXrqZ;bDJ-DNf;(6Qpzu~y64)hCURZrLd2Xpeh A&;S4c delta 17507 zcma*O349b));@f0b$9ieq_?EAr?YfYAqfN$ARsVoDkv(O3Mxw=D4R4SpkfG}2#O0X zP@=-<7z7t^88ztW$oRsDI_}Hpyefz=GCD3Z4sIjL;Qu+NDi|E!@An1!sq>t3?z!hK zb?escw!Pia{dJ-+mw2e+Q@}?pL)$4@2Rxluy65Zu>du^Iju5ixmfm-g2%h zj`|e^K*zjFG}$cHs?DjY!nfzQP{r|i*t}L%sYlJnRfT%O+^=K*%-LRs|o;3yb(R!V+<6A?M1W;5`DaGH;BQ zs?}y!)KFKN`=W*FD)WbELP@{~2i))TRO_DPi^ypo!gZaR=}@_dhbYT zamo>2Tcg>Obty{k%W6UE#>X~i+zYc$ceKqy5xsTR`s`{idQgwJG-JNmKl?eg#ypx` zs;)NUIfhx3<1)|4$yL{yb8{-xedbL$1?oEUp`0o;%X}}VxA|a>-;Cr&-4`MPT4NT) zD!b0j-J;BI3k%hBb9by_Vnip(BMP-_lZv&rCRD5$|3HE@Uagh;rAGDAT1y}#{|T+} z1)yAOEoH@1KPe1O)6ZGnP_(4vUocdi=}FGTb>*pW?a_(X!?9(L**8Dulp9z)44*_h zCXG`@--r-Zp}no;T*H?zCcgqiJXMj9C;2st-jFNgO@0Hh9I9{4_IPfnr(-X6H1CfW zq+NsxlK+OI_`MyEr=p0i8JMZQXBWLtI42gEO7@2T4xXjAaPISD99e)r< zwpgiWvE%6{I<8~Kqv8ll%4x(nWHvw#xsw0k9LAkkVm+7GBPG~kgt)-{;6@ly+id7C zk+!qfG1lflyYh?^W0*gs#}=!2F437*)e+qu+cd2;0lV~b6^2hb=0{HI%o79N%mGPp z$FRi;m}ibCDM&p4QzRplp|_#CF?l#fC_R+10`tz9d>yQMmKMrrWg5yjra#D`6havq z$1`x(>XBe5Xv||FguzV@g=Ew;V?0!e8b^6msQCgXEe^R8u_+OE(t(0nC}b>#*@==% z&4VQ+sX3^i`#wW|=`DXm-HAw~jSZoQ;pL7xOPr3c5gXXg>TI@H?aF{-(uq1E;i_aP zoOH1m4LNHvv`{peL{wuD%2jzl4NyIj8Ok*J?Bu2nhqR1US)phs%W6{$ZL-RRVphXK zF=Mg$NNL2g6t1Y@Tl3}8+#p)p^G>SGY zGr=0i6LdqteDJV3IL~IGJS(UCkjwTC<=Z*sOMk*alFCVb%aP6N%SuyLe;=Q)6Hd(Y zR_Xhei+`X<&WVvBp-zq-C|$@Wr0_Ml+j2dRs3`*=F#ywGx>0BXhV{u_oJ^ zqybi0>w<_znt5tsgUUDGPt>2nIU`J@)rQ7km!=n`mdt%5%(h<&o6$qCom~Nk2-qac}$|Es;6uuR7l?Gc=%i97)`QK4Sr* zr{R&Y9mJEwP2%$=(|N9Ba1VOC=#tAgfgq?60z{n7kTV(k6rK@R*k#m1fFcZ&8*}rX zioU4`H|&@xj%+c|rByOHe474W<0A&EB~F)t z?iM>4tgNkH8kB zZQ%*3(X6`IQn7Q5+lV8jsMXd5P%-ipC;uAB^&3 zJpaT3o4G(fs!p`C#lYjdN^e~vPFb=qd4xR**(0ru^`qqTQiDx^@G33jj_A#=^V5MBC__=ow-zI(Kq}$6mWR^l1*v(Y3r?HlpkLqsOsDWf4ai7r}6804kYp&?Iq07Q1WT@WC^Cslkgl&ko+DOMXjpr5=a(D60 zq)6@~U)X2eM_##)LLNN1JRuLto8znEDa^M}%j0N2PvT1u0h9PrzBt>EFi$&Hc2D2s zC#YolUJezep5@g&^^p0AGw*lY!#~ZuEPqvBPI-APO1rI;^;Qn$-V|_olAL*3*dd8~ ztouJU#qWlTSyUa<@gUWceas=%+5Kx#RWB^Gd9_Y@8xtARFT^`{Y0nByx0Ny37k>VR zm*DA6_A{@kezD)TsAF)iAAkIDzt3H*yRFSK+%Y2EGf4kEr~Mur6A`_w&AhVa(i(|Y zhiKM*&|4KlSAPwc2p$TUG0u=L`B%_#sJ}HUdmoBo9_vX&MV^sb2VI98^3CEt#cG*3 zs?SNlRG&d7<5mc@JZ0y-5@C3AwUetqhLQ%JNLAIEvzOk=wj>@{J_F;e``yM+$ZT>O z7n}d-6PqIryx1nM0wg@iHGFX=ujY$gUd-39qWNV^#^kju8kIcXksd^JX;pG9M579l z>=UhC)(CT&O3mf9#cF{0>)KM_|J0s54R>EiYkn2Ma+3qmJ3c+DZ=Rk#MN2k-^$BW_ zlY<}{g8?pk>W(A(nL`3^5z~jxQU^g@110vi!7l&;q2D zFyHQ5Gi4Z*mpiOJ`W58n@j5MDIS`FW4hKz+0AQ@Juvo7SCsYMnFH9zl@(~Z~K*$)$ z?z)y7WuDis*Bp#+$bn}CdeC}WTl?bCaFTto9k_WX7J?){tdb`K{8g3i*i^uUEL{W@KteQu0RbCp|YOD;CEmf5V0w=Ykni09nTz$3o>;!KZ@kf5Ov?RFKTP{+a1KO|yJe=aS}#8t0kA`j@Jy z=B)mu5%DBFHpQps^t;=#-qydNZ#4?M?!x+FoXU3tmME+&)qZ{Hc<9>CNBmyCC>qb} z&v!_p##95w4ZMJpNKOOtDmES0Kv*+o;94_8%S!m#`eE{Eo;FNMr?#RF7}hkS+FU)L zEYJuqt#ts;&ROP;0p)78xpzRmYBIkYP~e#ZW3S#VE6sv|)qx9{6gmv-Wvk4Zf%)m8 z=w~U6xn^o$(TJWt@RhfQ|GN4dz4|$lf982P*NU0MDk8Can}-LMDzBN@(5LX{vF5Sw zDGuxXdzm?_p(HimF22CN@-wbip~ee{z5ES9C-s(@Fda>-Hk#LB)E!zYw&zY6PhXgf zg(xPEMr)$-lhF3Km;;W~T4%!Dkz53pbxa+v;k47V)>&*ZtVaYpch?TnY$?P|ll!mN zn_6oVRI-=1y2g5q!K+P^dDfsr)`hTI{gRXvE@JN{^V&g$v-?OJWKZVInqTEI7Q+Ks z&f_{Z`L)*h?2GB+tm$nNe&?u(Sj64q{SDQzvs>Qbssi0?vFRUNFh*80kG#v_E%|uR z>N>B%4;Etys&!k79g7C?s;bFqzQIk4Jr4ldIbN}G5@Pkjn2A%e1wn7gntx2| zh3ONi_0Zk8)?x@8$)zwk49Hc;3lBzQlU2^yba6J9a5la+*6T*9ujPfO$Z@Y7J-k@2 z8|<{uT2+SMIQ+bomm$Q$^AUV$k7AGYD8_o-z%Bg?#ab>$>0X1ab{9LWhA{8Zt{&99 zcu0)1#6rnCD#s?wD6G8Lm9_GAOvVXwi}ztjh%GWqx{Q*$dyse|Wt(-k*4hf%-6>CS z-ZDgiHr9Kr0kV4Eif1~8M%^noM6%T^9Gb0GngfUSAGQl!Uy6ID8teGic+8YeTG~8s z(M9MpJUj6AIK~6r*^_AQG@Q3WS9hgjwsc&P z524ZI#3zamHgPz*B|=n!N%R3+)9^tz`#L5$qJr6p^2Zvg?fShFl*F&I;%1bSTQ97biAAz9#A` z)@dlJ(P@idZjAN!l`;Mp!LPI!$-%i1}!K(c&^6y;+!ILvGpOoOC+L zV8hTtmRD8oK&Go58*xV#{3JTuz>eOHGN!R#toz8W}eLB7CVlsD|&3)7R1GU9@NDYa9J3N1& zl6*WJ73)(v{Ypao+QA{#xE2-Y^ty!0_FQJ@bdu=D=5xiHq)nG2s7{^P+~GTPuJT|m zpJ)EP{Gg9_rgUenXWb4&&SX0zH&gQvJR?QcucEL}ji@Hj= zqw)+6w5ezf#_WE<-&b;}yQG@GNX7TXuSO5~%Q=p#fU#Wq>8T)B`H;jP9pvVnh3dm} zhSRO(}VOPZkMs-r%X~PnC{#W8;kFIvW2wlA}0C`98KaP@C9pKu?UN>9kcCPE^2V(Y?YZifuM+6ZR`a8A}(^1HwKK+gy5x zaDEx2K2q=!dR&ZnIe_a5dQ#X6;<}Qa!V36~e>|`zF!{J#AaYvZF=#&XKMDMg{||Ua zah86NQBJF39|HfJ_ff_Gnw;@ThN2JB{sC-B`x5A5Oi?c(D+Av^^_#$tz>foLJ}Yg4 zQuJ_|2P^T@fk==;7&w6Xl*NPRwC~KApw4OkU%|V96WTlT z7@rcn2Utvx2FrrQ^tw?AOkEi5jmywPUtq0ZW@0d^{H9_g@V#Qj%d*b^wqUf{KE))#qL|AvS4{k^Q;DE@<8tiMV8Pj$0?g?N5LtgjP{ zYrluQ!}&bWr@sZ&{roP1G8mayx*AI|ey=e84vqxXIfDyff}W zuM`vyPT>+Ry%?&0s&>fd`jjBs4ybn}DwJ z%di+S2WXN+3Vw#V3!Gdj@Fb?*g?>lP96aKDB zNl+IxThz0tyij-1MEASs3Y+~U9s#@7W`B)mgRQq1s{bm@MZ1Jq)%$1)HXTII z5m)c012*I8{q($SCa=m)t&ihzORw2@?I`i;#3d3#la?y*^3UMJXZZT3ZEuZq&|Y^Fs!!FJg!GrCu0(w}Tr6HRr3zG2hD z8AHQa^sddE!7x$2b&5oq@O$^ayH;u z5wO{moFG`H%_2qfQh34^*tCDqvTy;F+w82!72!haZLQkj%ch44CPqe*iDLj5 zWzLSG7liS&4ALliQ`jDgR9p}lP4C$(QPC7Rnf_|Ct-ghkQ|McpJ?3kUoJuad{_^Va zS>e3!SW2_mkA=&^r%_hQrv8k}Bd1fL&5ANsN5)Zw&Hk2tW8@6#W3%tlZ;PBs4HiRc znfPhSXTolwwK-2aC(w7oo}*K+<(fzhcn!ta)8;U<&i>4vqwD<3!V~Eb`d^I8!^^^x zXla9Gygl9No=mUXY=8D$k+Z33kY&3+dROEe`p#y96h9U@pW1EqMe!?<>9k@Las3@MspRjGMp}4^<+`Q#?~yrFb*jZ$qu%H| z8Z8VX>I+8a)68z0KPS3?W{zdgb@Y0Ab@XC-=s4>iT||d$_GtRBXp-_zvts_HVl0lp zcYvY(4w{gC7UPZ!c1x}`d(c@M}-|!f5e7+ z6$N=qNC(yXq2XYK!cx{;UroJ)T1)V1>L+X!&CtBj)ily(?>py5ucXs#R;S+@y^1E< z>{ESfbPb(nvzy)C=+!jeW|f}#(QByLW_vxiMz5vIEtaBRdACNdqcyg13o2Yox7uv9 zYku^4y3=NxP~i=9ugxa7w?=QI-`eadRB;pi&SpQ-{OCIRADayYTTjp1te4gmHR(m0 zrJi=~jNVNDYtyLyY;*&CV6&I>m!r4P$2ME$o*%uHzOdO&&$H3n=-)Prd*?(q()TvI z8U3`0v`O6EtLU(6Z}ivXx7n56`O(`cVzcHHa=U}_Z2Ay+qraglo2}I5NAILMn;muD z8ttT$Y&H?yy_rVY%1Yqnr^q?Qs$!PP&wPq9g;`N{Qh{x|OciJDR{ zVjmwa{RafT*U*eJMps%ohbJBZp!tVz^0-or32=9!Vly%^hgG=)t-l9a$ z8q5FZDC@uF+OuMod%8-rpH*r3G~@(>npBCmq7!6Jh%0u``LcujsTM_Vp}`VA1*fKP zcV(9Bpxf}ISEAQ!SN5^wZtMPZ_OSxpmi?1FNP5xITfJg=a!)9-G_xf>Yf{~+Ofg$d z4LR&;1;UXS*IBYIrqv zZQaOi(#XM1^d#D(;RT92^N5V)e`^%xhekVq4)Ju_p8uEL>Xnpr;m{hDO7-XZxk^jV zsL^fG2bO$wkFH*n$DYy-iinSeR_DKjmyvGkQ)qqnP_nG3Ml?yQTYef{Mf+s@{=vsI zJ(5s#egSJN{3*F^&HsOW&$6>CG5?i41U*AogJR8atH2x?xt@i4$~}|Y737!}b`NfM zW4Z_QCLCo+>M2}4i7>Ic(F$r6vt(=PS@WQ0KOGN&vX> zZ051a9=ro89EvAi2tNqyD&_+T0<8%0w;`IUjGb zS3<+Mj`%%xn>H_TI}MS8sUgINQA3CiqlU;~)DSt08Z8kXlw##oxRlWtFx zYoBNfVs)BV)%ZtiM^JH)yy|rSByG9Mh@Y?RLFA;C;sTdIH9EdZds$Rm>{e|#O%8D< z&q~~^P6QEjU&(sMA*ze*RflMI;sHmhdTog1A22|N@l}W0S?rjc5pY&I zmg1MWhv^SRInKV0{oXj_2P+fK-KZsqJ7`$=1I)I0m8lx%Acvk?=lmSwI>Pz2;8DRB zB%wE?z`KGx8)iGlI?gRx1bjHL%sIhPgLkBD+M3d5oY!dkD_(XwrBnbo!rAEf3uZ~9 zgJ;q_2T$-uhf3^4#e2&>bH1QHs>J+{DxUv*Hy?B6qgc9YxuYcfAld`=ULBd!-Q2eZC{Eg;Mr&dbJX_t0QFGr!RM0ns^bo zGVwS4sGS_YX&0w7UQ#|MPU3U2DtHgY-cTFRX>Vu;jVs+e9BbW6C6&){obvv@CEfPzD@J0Sry9bRQr4n zsn4bC3o2Hg=M6}IzpI`p8Q}d|txuuKqm~(qj7}3Ae=Glk_YFs{yxrax9NY1=suL?$ zy6auX{P?ThfXLjL|Mo^j&KE2bthTKiV>kMi;|r7D`RX<9ry&{-&1j8}aK~vp8%Aq< z);d~yvm~YadFuJGMDh{qXpN6nSySLY?06zn=AW$bGFgcfhxsp%a7_|!p%zGN_AhsA zGadyVNqgC^RC)Mgl)W@|7`Q8^z_CsHRoM~0a^9CX2F+J7N5Jd+V`w(+mstsKAmqfY zn4%o#r?E(2n^u>R1N2>x7_I1AQUm6IZKQ0eUbv_zh3;DzBPBqUlF?O@)?+$dp zithlYI+WEyV0^iPkBhcs^Rj@ZO&rz3gAh1^BZ#eMO@_Ezd zIj>J-;7gk43$xOus$KrQc*mrDJ18KI5ZMa47IuF=lJxkW(neY{+qg4OC8?aYEz zY5VYGTa&iLS&@5l+U*)2!*8{Nep&uZ+5`3scs6$*A`~0{N?Y#uxME-0Xz0I6Tc~j~ z4_!d@=^mO%gMg_f8ivbU8kL?-i)jpSDJ=(HM)v?$&?~@p%F0N`6F->|rYq?k;Prwd z)H@j(O;hh@6ysh0tyl%!MQgKr# zYR2Vs+76sVPXN!yj|6(tY{3PT5$jDYbV_W5R4@wP1g?mkOzY{Np_%yQ=89OO=x2+* zT{In{StXiPbOn4?)3wwQ>lEu|v2GFT7Fbg&Vq3_hSoYm;`aZNvoOX-TGvf4&gm?x% z!?F*G{zK7!DEbefpOYO?*V3!8QEHPQ$C(YeBQ_iQ??X+%j##_mbUPF`;2O1_-jA7F zDi!M#!)7sTkz!k<7*;6yI<`$*yVy0hOFVar=QHB@jCdXp{Xx-xDEbdYe@G%IjVn_c zSL_wJ*hbc*Dus*?^C-cwqG=R)w%|O`;D$k<4$-UiPI|X+M?sl>NZjld)d{AVi zvtH>ENB=dgi8bmxC)#zMD;@f^DUi*g+AXNuY%UfYA=oI`A-Gv^x8OlRP9cz*l2&(G-hjxX2?!p2>15)+nSyaI@fU!GnUzFZl|N5Ns6e5Zo+yP*4Td z)8Avo0rnpO`RiDt$Q^>41$PS`6f90-pVWvnF3>2XLvS&cg0oGzgS z8wEQAHw*5D&)2boB2$L+K?ZAz1xE-r0)5%-8L|>U{(Ee5DuaXW7Cex#2{Hv4=fK3ctDWC;vY`&Ww?-;g6)Exg1ZC{2vS4} z2o4vVDcCO9DY#4UfFMQ1UvRkKOu=1(lqvOQZmL7|Glj&nL?t*=uwAgz(#P1^E@-~q zG$8f1XRqh4p1Iyi|H=OQ{RjO2@Mi>y0`-CO0v8811%4m+IB+CzURqPyy=f|4!!dya z|9tofOa|c~3x|%AGYN-|Q-PNJWjV`Vl}rb|7oQFMC_Wc>W97xbqP!O1fGA^M#{T39 zUJ9}&deXX&t^X%)z_BDyCIc)A}sR!JL)imcT%I&#M<$7LGb)MgWufgXf z@R!s~d^+(N=Y2)Z@E%sZT}S9i?>hB?_arUiTcfId*Qt@dSJXwmleO)>Q?(a;^R#Ks zBb2~d#dH1zkauepX|HL$U9Um@o3_jGnO5!l0-vw&A=5u^a8~V%S{gEbS^I8@!Y+OESVPi`RDrs9_IO=BfHIFXNM#S99# zF+}S33!%(^T^vQyzi7%`#PRxZyfj=i7w zt)EdZk>)S%jW+iGrDAVLvF{h>`s&z$v+Zy0N@kh$OBVR+W(??VwE60iZ1c_~l{G)B zx!a+3#=u_+pWS>WYHs)?(~4%pQ9q-B6D-4(D>~jnNI$EmzP9UNvp1vb+m3#|uG+Of zsIIyj%3Zi^Mwxe-oi>XlP;EYHkn;p z%GDd@ds|B4-Z(+?wu+FBYk!q)DT-}HvFX`btll8LPrGkzG z#8*&#s4YFNg*#kaL&6+(cb-a^=iPmlD(>2T_Y#G2`S;ESw%=QkEg4I7nW2tMG^QhD z<@G=Jc4cwsPw*c%tv}1s#;&XHD^!Sh@3svp(KU4YZ#6Uc;7btqJy@!l(T7iiZN|fm z0V^BnfrR<;!xOg*encBq*SnUeUQwUEy=!~-udA9fr?H`5eM7^*frFar2Mp?4H)vqv zoZ5j6bLL#wzo~IfQ~m4#O|u$iHxU&p{y)W56?ARrx=!&DyL!yizbAFZcwY7H!RPi@ z@p%}6{l}{$4!8-&`ZV%2d>%0;K2{Pe{4sB*=cx}*+xUFu(_by=yw6rlS??|zo!>x#3qJ6QKzVl&4%3Nt{i-YU^cQMft^5B0e7a27 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb index edd92539ec507f134c17712ee3aff78e8944e84e..c881cae560937bfae3234629bd6097c665032677 100644 GIT binary patch delta 5300 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 RSh-gpbFq9_QqAol{|DAw#%urp delta 4764 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 MLp&_w47}g}06|=U`2YX_ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll index 931e42dca46214e55bdd8529ea9bfc8aaebf47d1..7158570c906b0b5aeb861d4ee6048f000790f71d 100644 GIT binary patch literal 95232 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

    ?r7GU|3heHfu7LnAx2!xWV3b9Q}K`5XvRkxz6 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 ZbHEh%r-uSu3m*I{nckoO{LE3{{{cWh9$)|f diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index e1aa9f03792f573dfed8ec058fba2760cdd7a35a..e13f706dc082da6cd574d68b817e9b2f62651061 100644 GIT binary patch delta 79649 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 delta 78539 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 Date: Wed, 19 Aug 2026 02:17:38 +0200 Subject: [PATCH 035/203] Seal cache transactions and expose coherent generations --- .../DirectoryMessagePackBackingStore.cs | 83 +++++++++++++++--- src/GameCult.Caching/CultCache.cs | 69 +++++++++++++-- .../BackingStoreTests.cs | 52 +++++++++++ .../Plugins/GameCult.Caching.MessagePack.dll | Bin 49152 -> 50176 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 21564 -> 21832 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 95232 -> 96256 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 32688 -> 32928 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9548 -> 9544 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15872 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12320 -> 12316 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 695296 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 205552 -> 205548 bytes .../GameCult.Networking.WebSockets.dll | Bin 14848 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 517120 -> 517120 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 247876 -> 247872 bytes unity/org.gamecult.cultlib/package.json | 2 +- 17 files changed, 185 insertions(+), 21 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 454a8ed8..648ba09e 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -70,6 +70,37 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore /// public static string DefaultRecordDirectoryPath(string manifestPath) => manifestPath + ".records"; + ///

    + /// Reads one complete persisted generation without deserializing documents through a + /// runtime registry. Indexed manifests are resolved only through the pages they name; + /// orphaned pages from older generations are never included. + /// + public CultPersistedStoreSnapshot ReadPersistedGeneration() + { + lock (_mutationGate) + using (AcquireCommitLease()) + { + var manifest = ReadManifest(); + var indexed = string.Equals(manifest.FormatVersion, IndexedFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal) || + string.Equals(manifest.FormatVersion, LegacyIndexedFormatVersion, StringComparison.Ordinal); + var records = indexed + ? manifest.Records + .OrderBy(record => record.Key, StringComparer.Ordinal) + .Select(record => ReadPersistedRecordPage(record, manifest.FormatVersion, out _)) + .ToArray() + : manifest.Records + .OrderBy(record => record.Key, StringComparer.Ordinal) + .ToArray(); + return new CultPersistedStoreSnapshot + { + FormatVersion = manifest.FormatVersion, + SchemaCatalog = manifest.SchemaCatalog.ToArray(), + Records = records + }; + } + } + /// public override void PullAll() { @@ -496,20 +527,11 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore { var started = tracePages ? Stopwatch.GetTimestamp() : 0L; var metadata = records[index]; - var path = _manifestUsesImmutablePages - ? ContentAddressedRecordPath(metadata) - : _manifestUsesMetadataPages - ? MetadataRecordPath(metadata) - : LegacyRecordPath(metadata.Key); - if (!File.Exists(path)) - throw new InvalidDataException($"Committed record page '{path}' is missing from the selected manifest generation."); - var pagePayload = ReadAllBytesShared(path); - if (_manifestUsesImmutablePages && - !HashPayload(pagePayload).SequenceEqual(metadata.Payload)) - throw new InvalidDataException($"Record page '{path}' does not match its committed content hash."); - var record = CultDocumentMessagePackSerialization.DeserializePersistedRecord(pagePayload); - if (!string.Equals(record.Key, metadata.Key, StringComparison.Ordinal)) - throw new InvalidDataException($"Record page '{path}' contains key '{record.Key}', expected '{metadata.Key}'."); + var record = ReadPersistedRecordPage( + metadata, + _manifestUsesImmutablePages, + _manifestUsesMetadataPages, + out var pagePayload); var catalog = ResolveLegacyUncataloguedRecordCatalog(record, catalogEntries); recordReports[index] = Registry.ResolvePersistedSchemaReport(record.SchemaId, catalog); storedRecords[index] = ToStoredDocument( @@ -797,6 +819,39 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } + private CultPersistedRecord ReadPersistedRecordPage( + CultPersistedRecord metadata, + string formatVersion, + out byte[] pagePayload) => + ReadPersistedRecordPage( + metadata, + string.Equals(formatVersion, IndexedFormatVersion, StringComparison.Ordinal), + string.Equals(formatVersion, LegacyMetadataPageFormatVersion, StringComparison.Ordinal), + out pagePayload); + + private CultPersistedRecord ReadPersistedRecordPage( + CultPersistedRecord metadata, + bool usesContentAddressedPages, + bool usesMetadataPages, + out byte[] pagePayload) + { + var path = usesContentAddressedPages + ? ContentAddressedRecordPath(metadata) + : usesMetadataPages + ? MetadataRecordPath(metadata) + : LegacyRecordPath(metadata.Key); + if (!File.Exists(path)) + throw new InvalidDataException($"Committed record page '{path}' is missing from the selected manifest generation."); + pagePayload = ReadAllBytesShared(path); + if (usesContentAddressedPages && + !HashPayload(pagePayload).SequenceEqual(metadata.Payload)) + throw new InvalidDataException($"Record page '{path}' does not match its committed content hash."); + var record = CultDocumentMessagePackSerialization.DeserializePersistedRecord(pagePayload); + if (!string.Equals(record.Key, metadata.Key, StringComparison.Ordinal)) + throw new InvalidDataException($"Record page '{path}' contains key '{record.Key}', expected '{metadata.Key}'."); + return record; + } + private static string HashKey(string key) { using var sha256 = SHA256.Create(); diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs index 1d1dab81..396b8a3a 100644 --- a/src/GameCult.Caching/CultCache.cs +++ b/src/GameCult.Caching/CultCache.cs @@ -1577,10 +1577,12 @@ namespace GameCult.Caching try { await stageAsync().ConfigureAwait(false); + transaction.Seal(); changes = CommitTransaction(transaction, soft); } finally { + transaction.Seal(); _ambientTransaction.Value = null; _transactionGate.Release(); } @@ -1904,7 +1906,7 @@ namespace GameCult.Caching CultCacheTransaction transaction, bool soft) { - var mutations = transaction.Mutations + var mutations = transaction.SnapshotForCommit() .OrderBy(pair => pair.Key, StringComparer.Ordinal) .ToArray(); if (mutations.Length == 0) @@ -1978,16 +1980,71 @@ namespace GameCult.Caching private sealed class CultCacheTransaction { + private readonly object _gate = new(); private readonly Dictionary _mutations = new(StringComparer.Ordinal); + private bool _sealed; - public IReadOnlyDictionary Mutations => _mutations; + public IReadOnlyDictionary Mutations + { + get + { + lock (_gate) + { + ThrowIfSealedForAmbientAccess(); + return new Dictionary(_mutations, StringComparer.Ordinal); + } + } + } - public void Stage(CultStoredDocument stored) => _mutations[stored.Key.Value] = stored; + public void Stage(CultStoredDocument stored) + { + lock (_gate) + { + ThrowIfSealedForAmbientAccess(); + _mutations[stored.Key.Value] = stored; + } + } - public void Delete(CultRecordKey key) => _mutations[key.Value] = null; + public void Delete(CultRecordKey key) + { + lock (_gate) + { + ThrowIfSealedForAmbientAccess(); + _mutations[key.Value] = null; + } + } - public bool TryGet(CultRecordKey key, out CultStoredDocument? stored) => - _mutations.TryGetValue(key.Value, out stored); + public bool TryGet(CultRecordKey key, out CultStoredDocument? stored) + { + lock (_gate) + { + ThrowIfSealedForAmbientAccess(); + return _mutations.TryGetValue(key.Value, out stored); + } + } + + public void Seal() + { + lock (_gate) + _sealed = true; + } + + public IReadOnlyDictionary SnapshotForCommit() + { + lock (_gate) + { + if (!_sealed) + throw new InvalidOperationException("A CultCache transaction must be sealed before commit."); + return new Dictionary(_mutations, StringComparer.Ordinal); + } + } + + private void ThrowIfSealedForAmbientAccess() + { + if (_sealed) + throw new InvalidOperationException( + "This CultCache transaction has already completed; escaped async work cannot read or mutate it."); + } } internal CultStoredDocument CreateStoredDocument(Type documentType, object document, CultRecordKey? key = null, string? storedAt = null) diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index 39fe52ca..20f05a3f 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -1055,6 +1055,9 @@ namespace GameCult.Caching.Tests var compacted = CultDocumentMessagePackSerialization.DeserializeSnapshot(File.ReadAllBytes(filePath)); Assert.That(compacted.SchemaCatalog, Is.Empty); Assert.That(compacted.Records, Is.Empty); + var selectedGeneration = store.ReadPersistedGeneration(); + Assert.That(selectedGeneration.Records, Is.Empty, + "a raw generation reader must not resurrect an orphaned page left by another process"); } finally { @@ -1432,6 +1435,55 @@ namespace GameCult.Caching.Tests } } + [Test] + public async Task CultCache_Transaction_Rejects_Escaped_Async_Writes_After_Commit() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-escaped-transaction-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using var cache = new CultCache(); + cache.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var committedKey = new CultRecordKey("transaction:committed-parent"); + var escapedKey = new CultRecordKey("transaction:escaped-child"); + var releaseChild = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Task? escapedWrite = null; + + await cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "parent", Value = "committed" }, + new CultRecordHandle(committedKey)); + escapedWrite = Task.Run(async () => + { + await releaseChild.Task; + await cache.UpsertAsync( + new NamedTestEntry { Name = "child", Value = "must-fail" }, + new CultRecordHandle(escapedKey)); + }); + }); + + releaseChild.SetResult(true); + Assert.That(escapedWrite, Is.Not.Null); + Assert.That( + async () => await escapedWrite!, + Throws.TypeOf().With.Message.Contains("already completed")); + Assert.That(cache.Get(committedKey)?.Value, Is.EqualTo("committed")); + Assert.That(cache.Get(escapedKey), Is.Null); + + using var reopened = new CultCache(); + reopened.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + await reopened.PullAllBackingStoresAsync(); + Assert.That(reopened.Get(committedKey)?.Value, Is.EqualTo("committed")); + Assert.That(reopened.Get(escapedKey), Is.Null); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + [Test] public async Task CultCache_Transaction_Observer_Writes_In_A_New_Commit() { diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index 7284604e40cbebb6969c979999eed559cf05ec16..60e4a9a79526bec2695d2a4bfae6d016ddea4ef5 100644 GIT binary patch delta 22200 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 delta 20978 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 z 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index a5468f11001f30778f1fcb3a72e1b93cddc978bb..902adced8e8d4e603450f234f6ad1047089f3834 100644 GIT binary patch delta 316 zcmXYou}i~17{%Wwb+9h2f)E68ajK-3++A~(q6iL(i_qeprpcs>t{pl!R4BA1h?7!u z5i*r?aqwS|g0oW*7ZK4(MDz^h<2@cfeqXEEYWCWh+C!&3>@bVG_}*F}Pmisyxm>n- zEAIvyJH3<5v;Fxz)32*nnRRJ1KoE+K&1@+Pp<-O|fb&R-Q1MWRdQ=WY!wsZga@~WI@}JRlHH5rt%KR0ZY^daUY@}3rwRPGi`4%Z zoF08Xj^1zdSR^##0U#4kpO+S=NNTMTs!9|PLV?r+qQ`<3)9}m~$w1f?&^OXhCRQaVp#eQ0y delta 316 zcmXv`Jxc>Y6nv{^VIzrxcmzaH3yECrvTrxL;jmLIL=-z4xw~+`B1I~(5d2s{M6eJ= z)2Y@A@v2=FXjE(std?(L(m} zFmkh34K+aW4M@nRz(G-~sgZOA h!~{k9OQeL;2Tof>TLd(DT?`?3;M-z2&MXVR*$)IoTJQh> diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index 2d70b0ebda4f9d99be3f2cbf41be1d292957ddd8..08d6e79051262458ae9d50cae0a907bdfe5cf22a 100644 GIT binary patch delta 1083 zcmZ3GFehO`fr5w@1B0?tPJR*t14BcuM>2!OYVWzlE;-zP`$OJwE$*K<;aHf28Y6=| z5UT;PE)bgl@d+S)3&dZ5__rERGXo<7lR6_q3=sDK@pd5I55&iT_<}k}9t0Q}ZUXuI z8jK8z8j~Fvg++iO?ivu8S5Uft^JT`%OpKc*FK1D-6=qRqU}M+|B-t5+*~1u^8DtsD zfTR?YG9wFvAd3PE2g7L%1r|<*n;Ho~cBAG3Ao&SMaxyS%W@Y`$RG;bWxG(XfQfk5a zWjqgdT(8&K!W*aOKG(rFepbQZQn}TQU#nOd7}kc|&bsX&P;34}p3PQuN#xI{Ioz#R zR2K$JHxvK9@Jr$rDNp9rv74>EE~tMs|CO5h^xBOrtfG7PnKIAMDK1HyVPb3lN|NuA z$hwV!<#Cf#`<;HyewS?1FUnqjK~KHnAcHkSimpOOv774)zI|&hOcQaC@mQmCL*UAd zrekUX8?K(KzQiOM`s2(w=36pxHzZDQ^l&r9yt%Pt(ONV14_k!at30&&+xU+2Ai#e3_U+mYgH9CKAnIqS3f z4_R6My*B%(Sd7`mQ#WnbRU5T5{%g;;T&1UdQ~Bpd*)SE3>`e<__m`DbzpQ!T|F^H{ z+aAl3ib*vt57V1Mf85#7mYSU6x{%2~qviFDf^5(3&jA+v{?nGe^|4raqFI%*$;^Fz zbAHYmj|Vvx9giomOr7&>Tlwj>Yfla*HYA(Y2Yuct_4WVR@7oIYy_0Hu7T=P-MZ#A+ z*mkbiXGz^|Roe{o12FFX_YA>op_-Uf^Nc9oO&G%XRj*>c)MH*%F)@U5#a^S@`-)I3-t z{ASwmyZ@A=yR*~2e%O{c`LOtTC-3Q(*B;-broDi-Z9&pq|H-mk^%|)b2F4b~$;QTM zrY5Nt#;GPINokg;CMibArio^TMrjrXMyVEtmWi8>aIIjHILGeZ&+veeLH*3y-Uos$ zK8rt0zdcVyE^+cnURe#tyu_T!s??%ly_B3BU~2C=Fl|3W0|UdCor*90mU*~MH12i^ zZm69PRIFT|F`IWHg90PN=0yi;qi#9+%sbFu_bcG#F89RTROiy15%^z_WUbf{U^ z-u_q$GRw1x<;%nK&mCGAk2YKQmgmEj`=%CWK-KnTDJ*4R;AY@VKGyfBZmw5j-k*g| zMJKD^YW-46CTH{UNi26_ILqL`!LU8{YHCB{1OFw{Bp*L0Yw_JYkx^)fD7viXXR4>JIUFt6AE delta 1049 zcmbP}upnVVfr5k<1B0?tPJR*t14E#d^mFFrJ{}V-T?M12%>Md1>CB#q6OM(+s4+4q z1F;qm8v?Nf5T60!k3jqbi2thrH8U_Wu&FaLBmnUQAl?nchk^Js5MNOT$%6nR!(AX> zSc8#4Rb#Rvqp%22#9IR*^A1W+-h7$yG85yr$;(+3ZN*vC8Q2&O0!elTarQ6}L7>+Bhdi6D>XOK#olkFh zYAn&?Y~8hG=MMg*{EM%8NJYGzbvLldZsq=q`r6vtk1f;P@t{_~qx#vL#m0x1ynN|+ zzF@+#g5I?|E_dlvifG;YBLBE@O}%A98S6oAwi^rz?-?BD7;8D1f2i!;wus@8LsII= zwgm_IOtv*PtS!4YS?t8hW9emb7sNgsI_BV)5F_xQ=&+jU+O3ui%X=&IIj_Hrw~@D+ z^>6`ij&{Y1rhwwDzgN9mX|kks?%u8o>m`^>cfQp7&$426e0lNpH5bavmF9g8d!dn8 z?-5Wfa_!{D?Rz70PKHXao^$unE_G*#7xf)sbDyu8raMXgexhU31{LXF|1#3APe0fG zcjf)S3-iKv^Yu^a*ZAYJfn$F3HG}+K9gQmwIC~3(rSo(%3+23?KRmgqWG|<>Y2^kL z#|vUFxIe7z73G}6!(JFOX~Cq&d*9yOv{3cu2I&X&9&=abtU34X|Jm=`O7^{!ZhRKs zqCO*aanYo?=i~a-Tjxy`zkKmcjnU~(HM4v-e|*EF|7D9z|M%;EpTu5#^>*LgL}x>P zQRYy?xUGEE^_SemoZFYi$SFLw=ws!2=kx0ntJw41>iLtxCtOilT`+T*sQ2AxUc1%) zzP0P^=b!Vv>F+1`9k=6B3eW1u-P@MK_2W@w#yhTEUn5LYShpM$2wC%+bFw5?y+)#` zsbQL#sgb3*iCIdrWty>tp{0?TrD;-zTq~F))SFN3WOx8f z%S|j1r*^YCHcb0=q(k1tV{!wpu7rx+{CI$2~DO)wwjMM9(+1I71I&&cuV0KQSE&aIB+oBdsJLnExgTbf%IPSOG356llVn8FXWSv6Lw)-=FPY)lyO-JKM(Zx7M@!%bGJF zfYE*gn-Rz~pZ>9)U7FFnowtD3&p^-AR6Etez}Uh#+1NPE)FjoyIMu`?Da|s~B*iG%G||k^D9ysa zDAmHyGI9H; z1*+$aofvTYf^_|!;IuOD?cXPIO=sc+`3LOw?bD}l2{SR8ZeKl(E15CaltG&zmBE6+ zfWa6@8Z#sVu`xp$gDHavSlk#$ngC%EP|Om{PGK+tsxoCrWH4he1hUh>x{bi94S{MB crzg(fGG#So&|@&zE;x%Tjd45UEUy2I0F=XSPXGV_ delta 384 zcmZqJq1CWMYeEOh#DkMRHTJajFt+wEwe~Q#_OP_}u(tNFZS7(IQO9UDy|td*U)GEP z0gUz|*o;7?+4PU~?9z;8?Ys@_K+Lh7w}Epzv%u66L6;sDe!u0Kes)YQzprn<(#*N< zo`s2?0Rw{xBZI!7o`IgJiFTr?sbQL#sgb3*iCIdrWty>tp{0?TrD;-0 z6r^5$)&iCb!V%AsYBJYs|2~mxIuj?zKVY|SpFV|4n2FJ3`|4?2$&A4!4B8Bd45mP6 z$dJZh2BeJ`EE&uhOn~eZhGZZ+jlr0~0w`+^?EnA( diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb index 09bb1b130d18b8f50eb2f03e63594c0fb0494ca6..ab7c2c7de2bc496aa83be66397dac3c76ee16a55 100644 GIT binary patch delta 27098 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 P@z15t-Q-(ccLx3+?3Y+Z delta 27125 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 UD=GJnZ!UfA25xcO)$iW_4=;0X^Z)<= diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index a01a7704523e60047b20e2c8498770d89d3b91e8..a3a774034539002ffecc6d67395e8e526c77e24d 100644 GIT binary patch delta 306 zcmZoDX(*Y{!Sd~${*H}3ITDQKlYdI6GMaCemsI2z5c>7)UfJ)t?t9KNH}mIj%-kHH zAVRWXKJdQYGGh(VVrDioMvj0YGIseVv>|*nQD?^lx&)4W@waV zVPKSMVQ87SdA6<~6H9`qe$V6$`T+t7Q^F%mwmo<4YI5m>b$P)*|G Yqh_Y8rVM%v2Ac&fm>4%RTKs1O08n692><{9 delta 306 zcmZoDX(*Y{!4joc-LSDIM}pC8@=pm>MzhWGl8XESF|QWy6srI2JynW*bDgt}>E-|p zVO9$hJp%>?6GjGoLp=jMQxok(Q&Yn9Vw3ZX!6ppa42cY;KxoL2 z#$X1djTkH$%o$98>=cG%AUlo0n85-lYYCJw1Cpi;NkC`;q(RsOC}IpW!5FC40;tx6 b!DRALGgDR*20aFY&4Lz8jGGxP{xbpqks?=P diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index a68c628de93e648b986589b29f91377245cfbbab..9328d1646ad3496958ec1ed38474d025b9ab04af 100644 GIT binary patch delta 350 zcmZoTA>VL9enJP!awmqfjXkYBjIBLPtv$?Jdsue-WHg`d`isRCNOk{W(Fany!BqR} zUo6{S|6+aHD6l>DYHCB{1OFw{Bp*L0Yw_LwqK55my@jcs0Rw{xBZI!7o`IgJsdlP` zfw6^gvaxZRsY$AZajJ<)QkrF|Ns3XjX`-2-QJRH;QL2TZW#aa~f7ljZWU&m;?4Q2m z9D9I3)e-$31Et$8b6W$YK7I3DGM(i-d%FTu)I=643RKVU<@dsT&XoV16;Z*0+gG1w zf6T*a&cMLH2*l>wr{7_J%*bfEz4{(|J7cgZgEm7dg9U>DgE5daW=IBNV}>*aQw9^T zxG|740m3Apm?fB$6&Bs@DY0v L<95bJ?Ee`7i12W< delta 350 zcmZoTA>VL9enJO}+PZH~8hcuM7+ZUoT6>tc_OR^u$!Ip+^%sjPkm~-$q7S5YgQ@n{ zzgV`v{>A#XQQ+RA;?ip2ZEg#s_ljQtp{0?TrD;-STdM1m;l)+ z49P%t8iO%|1yI%!C}RdBO&OAa&;m$Ysk#QvWV0D<#xWB>pF diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index e13f706dc082da6cd574d68b817e9b2f62651061..ffef7b76db36fd7f7f74c7fd1f2a86f789cd7b3a 100644 GIT binary patch delta 23178 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 delta 23183 zcmaLf1$Y!!+yC)pYLwMzR&w!|8rga%E%~&P$>UW^zOHWa z4qG>tMAkK(YN{ny8=CyLYRPxaOg^Go;%sYj7S)o}T};lcTGFYf$)BnE1@tp{hH6Rk zK_>T6E%|qt$r(O3`RZts|5mj=-e$)H(@C0Y@&(nB?z2oDrCL&CzR9twCH0n=+*!3` z`3jR=swM5$nmkz5uWXaaJ5)>NZZ-K^)smV!O>V1NGJTK9tM-VkozO$36RBE~;e^Tg zRZCX=Z1Qf^k^z@Yo}gN?@0!WyRZF68nH;BDviYvbM^#>z5`E zP%T;aM#OeVl5Q*+=Wytbg{mc!15947T5>au$uCt)e#~g{b=8uvtR_dRdXa^hTt>Ad zF^|dLsFrLjWbz@^k{KmTUaeYEzLd$0RLhp)6-=k5YDtz@lZ&c);#wxZR4r)~Z*q6l zlJ|{G&eX)@buCOzQY|^(#^gU$OO|yodAn*!Oc#^us+QdAX7XFrl3Be>w%6*$lFI!| zrH9Q7x%G+T`}CB^MG*eyCdFnqcxt)sm-^ zO!l8_^7R=ezg8`g#KwMA5~KgP3EpYUruLTHSP(~J>RqNDj4kj09>t!7I0S^@9~_LUa070`J$M*T z;(5G=A&1Nv!!Z9Leg2(NlujwEgf%f9n`3(%iZ)Kb-|-wV2js)LI0O@L1}?^R=u*9#Q;w!ooRa!z@?NKWqS+~*qHVNg5}kRt z0Z-y1EOP9_!SyveWhjotnfN{4!e^>?b4tM<%~4%&DB8H(os-!7iqG&3h8{PE<-tg- zgB{R?H}Ng{o-pG>RUavcJ2455;wkj|$&5*ZEp0a2@Bp4xy_=6bpTbTWg~~6d-LIp* z@|u2zGv=6r*bFD2oHg})7>O}h5o@S^QeX9Me)5!h-Ji`~8&BdZ^!vrsLyflNp)&-( z#MO8Z&!F!)b3hu*ggGz*i(oWXQ2nJk*2Tuy3OizV?1%OcHY0JY>TdornTp?eGhG@C z#%!1yOJH4`j?3{I+>AT%3|>-`=kIlate?|TN^`X2FW=*7e2-BVO}z&8$0Mq{`O7UT zX)l>EA^0)2R0E_VcE^4=1V>`WbNV_OAYgibG(JJUU(Lug7>wC4H-3!OFb*4GOYDH% zurDU6cA#vfb3%2uKzT^zIr?95NGeH(jj$zlz;4(V2jd7#z)3h0=i^dbjT?-%Y^Ads z58#h@1~1}o_zr`wngwOU+!%>{a4?R80x&&>VmKDS zVptk0V=ZieEwBT2$Nu;kPQqEZ0Kdj{HkRcB8yDe9qb=Xk*@^q{M?8y{@h(2X7x)hS?wIAK#mtx! z^I;^$U`4Ef^|2Y+?bvk1zBmLI;Y$1#x8e_Y2!FzJcol!ghxiQNpwC^i(9{@=*)gxp zrZ7ffdF+jYa0HIUDL5M!;Y$1#x8e_Y2!FzJcol!ghxqI+*Z&(jKKEE724i;2i-oZk zHoz9x0lQ;={0v9qmpB9G<1$=}`|ff5AEk2|FXDB)hmY|kzDNK2W})dY7k-Szu?$wh z+Sm|VVn^(O15~~KhtU~>({L^>!PU4Cx8q(sf~W8T{)Tt)5y}Iz&;U%2p%{(@jJ6b` zQyME{Eo^`-umg6-{`eV=#xHROuE9;{!hLuYPvb>=fc8^1uhH>`S#Ka_z$};xKgQx% z2CHCgY=|wfKYoUz@k^Y6^Klujwb^`!J1_~4;TgPyH}F0_!B;4M@*rb+ERJQc3f9Jk z*b+Npk3YHo2hbUX<8d0!#U;2JH)0YV!!vjZZ{U4=g0D~>vP4Xep&0&<>%Rn@vRD=C zU?XgWovp2f>}Q`PJL0iCD#8XbRe9bg8`g848KW3VFD z!1~w>yJLU+3`gUaI0NS!ZCOTVEq;ePFbR+08N7ry@IF4lS16BokTE@mVmKDSVptk0 zqg{(lD;$mqI2jXhA+EsnxCM9PK|F!K;1#@$f8sy*FXnq}78;2$Hk*oA1M6cmY=>R3 zFAl*`I3B0rTwH>yaU*WWy?6vq;RW=2!u6l_iCIx*%!&Ch5@WC;*1*Qt8arbz9Eig) z0q5XiT!kBO8}50+^?#VoNj#6&@DBck&+#q#J~hitgPAY~MqnH^#@5&wd*MJFjtQz> z|C8w?;zC@3>+t{{$Di?6yoG<@-}oQ2{x*wCg+Z7Vb7Mg)f%T2HG^5iFyJBA)f}?Oe zPQ$sl1b5*9JdQu(uXqdpz`yZ7X#K+y(GFsh6$@h&md9#X7n@)k?2AKi6pqJfI2V`T zYTSt1aW9_7Yc`uZ_!mCMx9IzfXBsnM4vfGe7>yOMI(Eb!H~@#?7@UYR@vCQC|I6uo zgPU_XIs#pgbVXNm{|DEXc#82^a`~oN8EL?zJ z<2w8vci{m%jz8lolow`M0hk^`Fg?5AhkkL7$gq)u}NUvtwQ?hp`xkjj=U$#$GrOhhqXx#`$P3W3v{& z!yTA}$J8|PD?Y?`c=?q>(#lQj^4jDTxDAiv6}*KHu*84N$X<8_bJ+iyQelk3@>mV) zViRnGU9dL}!Vx$Yr{HW{q^6TCs&`8#wceQew|M7G8q>-7))?{D{LHk1ok3!>pFuvy z9VqWiB{vSnL|lj~a6g_?y<3p{t(72ojq=`Pe@u&+FgxZk+EP$A-YrPlv%4$y!9h42 zzrcw&1LxrqT!r7_7Tkx|F_-!0JX(;{B_}rVOK%6sm)Z%E1-KcnVLpecSHhYYkIk_? zcEf%+6m6V<({YiSeBzBM*;BE_#h@fShG*~+-oX3#1Ye<8W^nR>}`f$gy?_Q63o9KXPcI0NV55?qDfI&Cxc7CO6dKOV!= zcmebIn2~YV2wP$Y?1p`DFpkF`@eE$V8+ad|_~^f{2g@ru;%iP2fax(5!?6IC#fCT- zN8&h~igR!~?!^;$3A6i|vqWON+CErX=q8iQ_4SL1#)v%JLI{w9Bn192&yQQa-G zJg1UBz>KUKVDWFYG1{zag1`Ne8%!frVN_DponMh>@ z&NJGwgw875j5}~2nkO|xj*w5{IlO|m@Bu!-m-r5|q%y~bV=TsDV{DCfXEwcXAP&a_ zoUA6VJ(0>P+<@C~4<5#ocpk6eb9{$IKQap{g_W?T%_bh3V|(m|{ctF{M@}G5$9cFE z*We~};XXWyr|}|Q$9wqrBmEu=k(YGdqkn2sNrxdAhWW85mO}Hd+aXekTodE5Ikv~4 zI33sECUoJx)cQRZB1h?*#*26z@8M&7iSN-r4NJrj48#0b6iZ;_%Wy4zhdVF{kKq}-hIjBUe2#C?H=UU|4Q4Xhl7mhJ7Qtw&fbrNI z+haHEheOfE2{;`W;Y$1#x8e_Y2!BHRoNkh@@T-)5$A|a~-=I%=v-Z>&jM*_S7RD$n zkJYd)Ho-R71$*Nlo6QIui&JnmF2XICCWASBCd`2mSOjZedz^`1VW}W<)JgmU|3ODa zQ_r1|>%TpnZfN5oJcd@V8JY!a;Z=;uWa_=~E8LIQ@G-u`)|t)7&Nv(saB^m@|3o^Q z@GSm@f8sxwKg3K?9UEbL9D)fr0~g~uJc_3=Ak@r|9&=!Xs@H!7I!&<;4#yQ)%mIh- z5vIy&D#fujHpG_L5qsbO9EM|XBF@CGaJkWzZ|H2sowy%=#ItxAZ{h=dim%a;%`7Vr z!?8S8!@Af6d*e8qiuN2fi*Xfhz-_n(^Jh0FD5`oleI=(}3A^DS9E0<5HD1qQW(dh? ztc-1Nkj-WUPRDt;6qE26p22(g4nGPr)8)W2*c8X(d|Zb|@EYF1ztA_ll$oMjxUmuT z!14Gs?#1i)FFJFX&kh;wQ28jA{tBi9VHOO>{1}PRSRP}s4mQLV*dDuL9~^|kaU1@C z2l2Skma}v&;dQ)=f8jIy7yWaag{H+!m>tb8pM^>uazQMCWw8d<$7a|L%_oRZvCT(_ zQ0YpkFAl*`I3B0rTwIIa;SNl~V|WHH;SIcxPw*9{%45zLgjsDixv?OYz_M5s>tG{n zg`Kdcnngxng6eKrWD=F-xB>U#Vf+by!C&*(W{RiU$tv$KbzYN$)ohX-Bh~CuR?Q&| zu(_I3+Tlc;fy;3%?!@DG7BfVcqZ(oh91x+eojGL^omqGst$b!^Dh$FxSQ5)&EZ)Z_ zm?^(GstDG=`l@#elU7;@lUw*ewZr5IotO9?{R)^tEwKZ3!;v@(m*O@&f>-e`{15tn zY~~5ZTv!7eV+W%xW9ZDsRrmz0g2`(SlV+O3q@7pArt%}^C}j5L!$^$5ir5QB;!?bh zFVRk0*c=>zU9cAp#HqLxe^9+!n4G457a!pZe20ET%*+{8cMFqn&EZl2i(*W9Ha&4Q zZosX$8xP=RHIF>QRFNiMOe|h9#Qv>l@(J9hamd}2ibEc0cE}5_icc|9Nv*n@Q?h#H zTwb|AN=_bG^nX=iY%MvZBG$le*bj%Id&~s#bk(~#WghjVxCS?&3-{qsJgs^+r(7(i z?*aNNy8mm(Jq~zW%>5$ol$R+Zobui)`xiIIr^8Uy-JBBcl?$Zg!hEAvCO?1R$O1u#roTg)p*M6;r*v<7#*hku{?&c%iymDW!JcN;>yegBu z@(T8@_o`Ga{bA1AUb#S-fhjI+?g`QSN2o+CGmPp z9{URUw%6W*Ws^_lC)+jq$w@qq*YFPhh0ig2xerH`Ny+Z@_(>J5_(^SSh%K?Bt()XQ zJ-l*%b`SNcB&1}2S*Y1x)_PSoQ8|!OanI>5C#e5|SMWCeshXxgGxL{!JROt&^~%oj z<}_(Alj?5%l7mWwSEUG*Xs^mLul$o&E>IzPo&af5LEjU-TcA8j*&Qgs6)pYE#(Wrw zm9Z8!z?Rqvd#c_oP(J;yivA*BB};#uucGR1MN>)LlujyX$?lF`m2F-*e`WVe%tz9; zvZX)a{{(yEKsBv=j+@Yh`|v29#*26z@8M&7iSNhDgVM{)4;>j3%cAN9i7Pf`i0t#qN4@fC%|UV#A9&R#RQqu3 zm1@k`D@Y1dH=~QG$qOz`r4Baosz33{C2J&)&nTn4@*%GrQ1io>E;Zc`Yp|@*YOrif z+2=lMuxz8g2M^;(JdfA#4*rGD@h$q+GIOTEOqfGWo;e~VXR@V;b}~y;N-49fqw*7; z!>jl^TD8siRI0mWmZ2#*M1t#>{{_pAd9g4?VR@{Ebi3vvCow z#O=5jkKif1fWP5g)w_krBkC_IvU&HvcKgMduOlsHR^3g1$mW$##PJk{b9lB)iP@gbVSB+RQ)#4DHe%2mB`9nE3w zq>)#te0=hx?xi^8c1lG*05wS7>nr8F@|KhwC|MdNj|r6J4c%X2J~^hp4^T%=?ui|a|1D_ggc}3?v`ZX~Jq`_cZ zs=8Z2rYp#F1!ZwlOA5+LT#w)5PE5k1cnZ(sRUFVPdCo#&n>7|<(n2z#xycDQ31{Lx z)!hoSh{B97%pwZQv=){WmN~cxSKvAfZkc>+5xJsxuK$41H+@|QRrpQ`Rwl%tArR8f{*RF<}}Eh#ED+9VGu zChgl=QcSvHA2g3gF&RW2fn#wpzEIt*I43C135v^!c9s;EpYbx@zw~Wc#F?oiP%utdU zO3E9pmlS6wV=C1yDa~}_Zcz-4VrZ1y)q0ftg^`_;$3)8p&C!gB7V`*2%T_Apiiwup zFxqmBj`<2=U(i~fw%eJ)Gd%IauS`On+_zV7uxA2qh$rF`fqB2ZWhKb5Zy&jg7 zk!h;Cm1Rs>#+2ncD9iU$SxM8=lClzv*>I$-8+R)w(Y-7wC*?5~>tI7{f#zGQoU|wZ zq`F&qPFbE)mghMxFSC2=pDD{i{2E`Y?pA^E738Zvdi__BWqpim@V@G96&X}fy7jfB zB4a8tX+`Nv#k}fOl)>bYI1bI_Tv4WyXJe6m$#Yg>&Po!~-;zpFp}$`Lm82S-IGm@t zTV;k;W@u%}Kfsd85{dm(cdH_gG*{szrHVW!7y2}LZ&hZf%A- zEQFI({!dAy28Y$)p{^m7hFMxMPf87`L9T}@Rd=h&_?lAnbG;{k)J9u%w_5D2C6BaH zi@mkvdCK0}?5)jXSzEf;!}ZV4`ChCoy~v;9G1cAba6lans3ZMGSW-uZpi6bPI3ChC zP8r7|8YeL$^_C9I87JnwF-|H_sfKZQaHL-U?pBx6)a5jFc_*wZ3rFc4BIIj)q`F%@ zM%H6wJsGbv)RU=rDrL6%Qg5^+^`!}#N3=e#0rjOdl}^}Wv|j)9r9YjaI10yM(3s>? z#!Ju_mc&aI49EN!iP1P&b+-mAu>nhLz+8vm|!&TkPW1`X1jsR(v7<{%^`YsA;xNY;$gTOPTZo?>Uc{U+cMj1 zWFeka-K_~rX~I&PuoShcB~7IA1Y=FCk4@2h+G!$f$epn#4#3aQ#*$wqpQWkT=H0BR zjGAaP4{TEzM>fw>Q%=)Vrcg=51-Km7V$`JM>6)?VW-Pjyn9qsLq~v7dAl2QPGp0FX zn#%}VADre~Xw7+hXwKV1bNNRPXf92sBp=p-!&-1y3&}Lqk`}xex8N1Ng=D9a2gj-I z)>0}=v!o>-Tw6*tviZ!|QsT&srs);iQd-gZL3OuQ(sQ~ctz-axhBlhlgH~Jzt@z;D zO2$*4inB3vM)EwZS#)dOL0XG>OKUBUb#H4aHq$f4mNpD(!;4QFUgz8JI^RY%B@dFe z5;;q6%A&V3NuIi$WKXoDopey$t-ajW++M1Woo#nLV&hk8^(nYGzv!sjkQ{C+oIjQ**`8%c3Rcg$)q^k^8-L0Ek)7(vh zze=ujm$90=%XcZ29&O~K1(x)X)~dVpWa^&sI;GM}>MgXSmvq60YFBJ(VOsSbI6s; z4*j_el$ri+Cy}^%pve zl8bpNt>4-X{cLPnNlz(8f9Elp9OJ3nv&=_9)AU|H+VsX6KIYSwY30!&@BGY%8`COz zDh&h8ClJ%BdMc}OnRjc`^wvPy95OSHRYTt?O{<0aNA7ve8<}Zwo=O3&)FapTR0e6K z0a@=Qq)qQ`Gw)!i<7vq%^YjioTJV!UJ;`5U7wCiM5d9f;C9NfpDrh>W7l)6On|CjZ zBZqxfpXs?iqCZ{Vlv!juX}>;G7f8S9Q}cpUAh$?nUo&n@gh&i&bRm&gO+Hdar)@@U zmOiGPQ))w~J=aGsA*D8#+8TYdw)^VOWu0=lx}J{WCw297q>PR9RDNdaQaW{JO-{L| z_e{!@Qj4KhwyiE9rPhpEY!{Isq_6Z2N(m`@=Th6;PftvmruR{@FAz)UZIoV+GLF%& z)8Cw~#0331NiX&ONzF)0XX(_WF7x$qBaPKNCv7KP()%V|AkEdgCcPkC)_W#p*6%W> zoYXre#gInn{gRrIj@Wy2LSp%EcYl4j|hk+zd=>TQuOkbZFJzo2Q-dx;zf&<8k`nJ_f1Lt;o0 ztj{N@SyqP(A+-u~$XwEKy{fj8?iA9yOOcM2&rose|1g-%FH-Bu?gUF@I}>SgMbK3#M|QqJx=A?e3nIw5IjKb?@2 z^r_BA$}~jhBefZ(^O25?(D~As`9_b{7lAw)eI!~hvlxnL#_FB1QfkepJ({35o=T|= zp;lnBep98?=2FW#(;+LUmDKrelZtC{>T9Tvm=yaV=_{&_Ek(`lAhWxOIV2^;>I63x^{HQiuf>KJzllx&(Rzcb^KJSk94YZ^^0e<^nglo(CR^c5*U0!q76 zpybkYgW5Nq6etHYh3n-PAX&=jr`-<3c4f?qk||~@tEVGP^Q1sotSKp#**m(tI|a&g zP4bbc?a@@36i~sP0wqM#Xlm6pC6O+8QlQ*1Nq@ONK#9{(pJ`okXn6BH!_>O>8rX^poD1(4>7ebG<6{5h;^qx>8@!RwY8eUvzT#BtC4Jd z(EDoaKykMx19rY` z-5gP^raJ{nV@-Btit{y^B>nNYNrCc}N#Ul}y|zwB`d3qj2vduXbEiOQ zr)e3r?>)(VRotK!URP(yXO4)gr?ZffY>hWQHbpDmo%Dj!6kgEO4ti3c{H$p-HEEy+ zlL~8+LS|f|Ck4vankrM1hI#~PmZl`qPEEAf#(B$EEZx3+Z`X@(M^Z-Hx9iM%JfLNzqVkeXopR*8^l=X7oLgnj?!RP>xmj1=mNv{j-7{P9^hX|#`QhM=#&Nf= zm43Qwd&X4X#8q7$7*YRP?1PTY%B|0_ZcgSNFpC4p;*n^YIcnN?DgP&lZv!-6+dyg$b|}74pctvh+f>S?T?v%KRR?r zM3ojZ)4ct+K)0oli_`tP;(EgdOBPlt*k)^uN44&Lm-qbVWyee!9Nqiayi*suXR1;! z*RpASA{YBljUE>@!(MrBe)}!UGrj(6 zTW;Z=objom3!PrG;_~G#bML&4TG;jW~7h9{-Z}IcC$2cbW z=9*GrN?%v?QjW`kCeOL$%2UOW#>eEZ-n*i!J3`4zs<;}}c2swo`tq5sMRgrb$SW_o zp2s`><7?{cF1VgGb)@k(dGkG&Ut32z^3IyB9$g$87_;}4D_c)TAwPc|lO)xnT=n`p z#(8>i+gz82IM(>O_ciR~nlQp~(#PFvR@T*ejH9k+WSeQOjrtcf{_cI97rI>29Pyrg zJs!By%yDG!%+gO>5epm_JhKeE?y{CT7JK#$8}C}Z(($Y3xY1o*6V^LIJ-zYcT&p)b z_Ii3#Yq@&ua4h%CGV{4B;~vLe&n)v?u1g0TSv(^b|LS^m%u&y?Z$&9r-BS*i=eRY` zU8#O?l=rN9Q)ySdUmbsW_HB)DRk`k1=jrXZ?E3q*ql%}uC#$RMea9D`GaUTdrGIAp zx2JdfYu69Y9aBAX{#@GC;H~4Z=j@k!T-lvgO;7Jy9aql)tDk4in@3zXQd@&Py}PAd zZ8BJMJR|@7#q}z)bdgM7n`JJw--BQk1b-n9QA8Ws7 zU#}Vu?s_}STJ4#o@od++QPxUNuSHo`(*!H8r`M*dYxD%`kkh@E4kcZ2 zldb8VUgzVko71dyo?f@%u359Jm7ZR&2v>!<)*4T*?={z-Us+W>y-)MGq83~AJ-xw$ zToadBl|8-B_PNfius-qhMufS_udz0JdbWPAt+U2^dI{ZKV>Vj9dU_KYyE=VumGtx` zCAt=EvmW>c7Ef}_Xrcctb4}ZA9ri2GF*>?<*O=%MWlBZGbm?5CYsu2Z%an*I6Wys( z>FCnYQDsVYEZMnq*QhAh@B`L5D>Q$?@ttzRA=kd}X>okFZ=v_%4=1VV8vn+6>YktR=hmA6EInn^IsJ;vePQyB`=!XSejO(7SvxxQa|FP~5qmq`Gq3*}gRUyEc)#+!( z>+Eemat;jTU?zT@%?wO==si!3)?P;_=hnMk=W(91ycX&HQeD^bIYZrx{I6-4LC=$} zfOEg&zn%xLlchYxI`rAc&Z$nW49^4Uno`7>-oX{c?36z&E%}Pt9qGL0b>02<1LgVZ zUB#VIR!RNpt-x~nC4HEX)%@QNnU33C-jy@TnJd)2E^{5~O6$~h-gLOHVfUpyFQ&e; zr{mqc<@KGp9OdU7tnZBDvEs7utk{(^-WlSo`ahqq4_Aa)ftA0bp1|um)9)TD|7e!M QnN6zc8rsPD<6wvUKX^@ Date: Wed, 19 Aug 2026 02:36:49 +0200 Subject: [PATCH 036/203] Gate cache hydration and preserve legacy generations --- .../DirectoryMessagePackBackingStore.cs | 26 +++++- src/GameCult.Caching/CultCache.cs | 42 ++++++--- .../BackingStoreTests.cs | 83 ++++++++++++++++++ .../Plugins/GameCult.Caching.MessagePack.dll | Bin 50176 -> 51200 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 21832 -> 22044 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 96256 -> 96256 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 32928 -> 32988 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9544 -> 9544 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15872 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12316 -> 12316 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 695296 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 205548 -> 205548 bytes .../GameCult.Networking.WebSockets.dll | Bin 14848 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 517120 -> 517120 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 247872 -> 247872 bytes unity/org.gamecult.cultlib/package.json | 2 +- 17 files changed, 137 insertions(+), 16 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 648ba09e..6af00795 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -89,9 +89,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore .OrderBy(record => record.Key, StringComparer.Ordinal) .Select(record => ReadPersistedRecordPage(record, manifest.FormatVersion, out _)) .ToArray() - : manifest.Records - .OrderBy(record => record.Key, StringComparer.Ordinal) - .ToArray(); + : ReadLegacyPersistedGenerationRecords(manifest); return new CultPersistedStoreSnapshot { FormatVersion = manifest.FormatVersion, @@ -101,6 +99,28 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore } } + private CultPersistedRecord[] ReadLegacyPersistedGenerationRecords(CultPersistedStoreSnapshot manifest) + { + var records = manifest.Records + .Where(record => !string.IsNullOrWhiteSpace(record.Key)) + .ToList(); + _recordDirectory.Refresh(); + if (_recordDirectory.Exists) + { + records.AddRange(_recordDirectory.EnumerateFiles("*.msgpack") + .OrderBy(file => file.Name, StringComparer.Ordinal) + .Select(file => CultDocumentMessagePackSerialization.DeserializePersistedRecord( + ReadAllBytesShared(file.FullName)))); + } + + return records + .Where(record => !string.IsNullOrWhiteSpace(record.Key)) + .GroupBy(record => record.Key, StringComparer.Ordinal) + .Select(group => group.Last()) + .OrderBy(record => record.Key, StringComparer.Ordinal) + .ToArray(); + } + /// public override void PullAll() { diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs index 396b8a3a..cf24c47a 100644 --- a/src/GameCult.Caching/CultCache.cs +++ b/src/GameCult.Caching/CultCache.cs @@ -1446,13 +1446,22 @@ namespace GameCult.Caching /// public async Task PullAllBackingStoresAsync() { - foreach (var store in _backingStores) - { - store.PullAll(); - } + if (_ambientTransaction.Value != null) + throw new InvalidOperationException( + "CultCache hydration cannot run inside a mutation transaction; hydrate before opening the commit scope."); - _hasUnflushedMutations = _backingStores.Any(store => store.IsDirty); - await Task.CompletedTask; + await _transactionGate.WaitAsync().ConfigureAwait(false); + try + { + foreach (var store in _backingStores) + store.PullAll(); + + _hasUnflushedMutations = _backingStores.Any(store => store.IsDirty); + } + finally + { + _transactionGate.Release(); + } } /// @@ -1462,13 +1471,22 @@ namespace GameCult.Caching public async Task PullBackingStoreRecordsAsync(Func selector) { if (selector == null) throw new ArgumentNullException(nameof(selector)); - foreach (var store in _backingStores) - { - store.PullSelected(selector); - } + if (_ambientTransaction.Value != null) + throw new InvalidOperationException( + "CultCache hydration cannot run inside a mutation transaction; hydrate before opening the commit scope."); - _hasUnflushedMutations = _backingStores.Any(store => store.IsDirty); - await Task.CompletedTask; + await _transactionGate.WaitAsync().ConfigureAwait(false); + try + { + foreach (var store in _backingStores) + store.PullSelected(selector); + + _hasUnflushedMutations = _backingStores.Any(store => store.IsDirty); + } + finally + { + _transactionGate.Release(); + } } /// diff --git a/tests/GameCult.Caching.Tests/BackingStoreTests.cs b/tests/GameCult.Caching.Tests/BackingStoreTests.cs index 20f05a3f..31b37b40 100644 --- a/tests/GameCult.Caching.Tests/BackingStoreTests.cs +++ b/tests/GameCult.Caching.Tests/BackingStoreTests.cs @@ -574,6 +574,10 @@ namespace GameCult.Caching.Tests File.WriteAllBytes(recordPath, CultDocumentMessagePackSerialization.SerializePersistedRecord(record)); var readStore = new DirectoryMessagePackBackingStore(filePath, recordsPath); + var rawGeneration = readStore.ReadPersistedGeneration(); + Assert.That(rawGeneration.Records.Select(value => value.Key), + Is.EqualTo(new[] { "record:stale" }), + "v1 split record pages remain part of the selected legacy generation"); var readCache = new CultCache(); readCache.AddBackingStore(readStore); await readCache.PullAllBackingStoresAsync(); @@ -1484,6 +1488,85 @@ namespace GameCult.Caching.Tests } } + [Test] + public async Task CultCache_Transaction_Rejects_Hydration_Inside_Its_Mutation_Scope() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-transaction-hydration-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using (var writer = new CultCache()) + { + writer.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + await writer.UpsertAsync( + new NamedTestEntry { Name = "external", Value = "durable" }, + new CultRecordHandle(new CultRecordKey("transaction:external"))); + writer.FlushAllBackingStores(); + } + + using var reader = new CultCache(); + reader.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var observed = 0; + using var subscription = reader.Watch().Subscribe(_ => observed++); + + Assert.That( + async () => await reader.ExecuteTransactionAsync( + () => reader.PullAllBackingStoresAsync()), + Throws.TypeOf().With.Message.Contains("hydrate before")); + Assert.That(reader.Get(new CultRecordKey("transaction:external")), Is.Null); + Assert.That(observed, Is.Zero); + + await reader.PullAllBackingStoresAsync(); + Assert.That(reader.Get(new CultRecordKey("transaction:external"))?.Value, + Is.EqualTo("durable")); + Assert.That(observed, Is.EqualTo(1)); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + + [Test] + public async Task CultCache_Hydration_Waits_For_The_Active_Mutation_Transaction() + { + var root = Path.Combine(Path.GetTempPath(), $"cultcache-hydration-gate-{Guid.NewGuid():N}"); + var manifest = Path.Combine(root, "state.cc"); + Directory.CreateDirectory(root); + try + { + using var cache = new CultCache(); + cache.AddBackingStore(new DirectoryMessagePackBackingStore(manifest)); + var staged = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var transaction = cache.ExecuteTransactionAsync(async () => + { + await cache.UpsertAsync( + new NamedTestEntry { Name = "staged", Value = "committed" }, + new CultRecordHandle(new CultRecordKey("transaction:hydration-gate"))); + staged.SetResult(true); + await release.Task; + }); + + await staged.Task; + var hydration = cache.PullAllBackingStoresAsync(); + Assert.That(hydration.IsCompleted, Is.False, + "hydration must wait until the active mutation transaction has committed"); + + release.SetResult(true); + await transaction; + await hydration; + Assert.That(cache.Get(new CultRecordKey("transaction:hydration-gate"))?.Value, + Is.EqualTo("committed")); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } + [Test] public async Task CultCache_Transaction_Observer_Writes_In_A_New_Commit() { diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index 60e4a9a79526bec2695d2a4bfae6d016ddea4ef5..529f4d3db33eb1bcb35a54741b20b401b8a9c83d 100644 GIT binary patch delta 22456 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 Qga74c9oWy5HI<{9 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb index e8107ba8c3ddc20eb6547382b301c625c9e1e1c4..082f5c209f6e5461129dbd42715a5a0847442a27 100644 GIT binary patch delta 4990 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 TGE`fN4Kr-AOl_*+TFCzZ?o48) delta 4746 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index 902adced8e8d4e603450f234f6ad1047089f3834..d49d6f58f44aa425c51a86b0823206c98f0e19dd 100644 GIT binary patch delta 316 zcmZpuX{edd!E!Oc@WsX+14%}U$$KSL87($Hmi#Fs@Sb&T$ocp89k<;1SR;VyM5=6t=!v;Ha|95&B$rNz`(!=#1@;Un@KYVER hA%(#bD4xWSHhH6!DXSTS9)rPVK^rl~&5Snx83B7sWG(;z delta 316 zcmXYou}i~17{%Wwb+9h2f)E68ajK-3++A~(q6iL(i_qeprpcs>t{pl!R4BA1h?7!u z5i*r?aqwS|g0oW*7ZK4(MDz^h<2@cfeqXEEYWCWh+C!&3>@bVG_}*F}Pmisyxm>n- zEAIvyJH3<5v;Fxz)32*nnRRJ1KoE+K&1@+Pp<-O|fb&R-Q1MWRdQ=WY!wsZga@~WI@}JRlHH5rt%KR0ZY^daUY@}3rwRPGi`4%Z zoF08Xj^1zdSR^##0U#4kpO+S=NNTMTs!9|PLV?r+qQ`<3)9}m~$w1f?&^OXhCRQaVp#eQ0y diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index 08d6e79051262458ae9d50cae0a907bdfe5cf22a..076a4456a485c148fb52eccfe1ee4d4a901fcd05 100644 GIT binary patch delta 1034 zcmbP}FehO`jmXc(ZwjQBpK|HmsG3z|-tgpd(boqPCmaitRAXdN0Ah6@)&pWwAU+Ai z?|}F#5dTqQ00Kq^W_3n}SRn2N;vGPI0EkZj@kMozJP0r{+ye3iG#D9_G$uPT3X1?m zJTxFOuc7pW&6gQ3Gcj(SyqrbRR)j^JfsJ7wkYr~NVGmv%b_@mm!u1H;;o+gZ0A1ZvHH$g|n1E{SyQdwW4eBxrhzQ|zsnJMEX+ zFJ`Imt|>eFPQ!8jmHRKIPn{Z_d|52ygB`~b+v0Nn~+YSf9tlxWb zKM8qkY|Wp+U6Giqpl+bYQLr<0@+`AkG6yb)?U~;4`iuTNe%Z4XSJ<|R+LSo1*m7E>+bzO5UT8Yh3$ z=<^l|DYCh7Pggo>BL8p3wE9;*XP>>w`H{bG`_j+8_c=`dE7|Ovk+QjX&bh)#zMFVH zXS!Kk7Qg)SjZCTK@j1_qzK)FJyZ-k{?8R4i_uWl&HuM)|4mFJ1%2{pkQn`EK;h=SV z6AERHGqHYGt9{GV{rT=>^OM>hSrbcl_+0K@rKI&{KV7*(lMrsq&tppCOYkyfLOLEm~m>U_I8Yh~VB^ntT85$-gnHZ)f z8z!5Zm;ptT)65c+Qq58TCne!LMf^E~0|$f20nHv&@o3k+^8Cs1 qm(RIR;uqPxkWWTV*pYFW2jj9J#$|2!OYVWzlE;-zP`$OJwE$*K<;aHf28Y6=|5UT;PE)bgl@d+S) z3&dZ5__rDZ5HK<@sWURf0C5ixZwKQ2KztmCFQ|j$L4c9rCXmms!N{PfG1-w(SOh5I zt^tvG1*Q8pUuL|_#JFklau!8fVHR};Hio@GlAS@AJ&b{wL6)%$NJ=p&GqNxUvM8`{ zFr3yym5N& za~*u+XB8YSm0R8TwThL2VQt9mtlJI(wdOzM*=$vpME;DL!`*sCbz#7CGx6^Wza(yv z@?>5eyV=_7g8EnUU#Y22uie66htlKD99ydv~-|6S< zcgZ&WqU`k-^wcX3GFUUD=qhv+yScvL+qdSzG!X|Gk2N|s1g_j@I;JMD;p(~SOH7iX zKhB(Ez9kcPL*fKS4>wcHn;T0Otu7QYytlr&9Vx!XF;~Tuvp%c;kd@`%YqO7v#h7h8 zb<=iTwNXpszxIsFReIVtm4AMe4O8LB-n8&_e_2`e%bFMdfBTxg?XfJWm{jBPFuf`C z$DIvrsmUp>3z_^gT3+8M$oB019ALrkKW*t-AB&YInpHWQ%-rWU=jW{Pc#vb!@pux; z)H&a_m7i|A_T+G4L$Yao(C3{}U;m%|zO7*2JE_KJ@h$0FBz(n#ZRd)8megHlGCTDs z>)B78^B%wcxFn)SZHq*FLj7I;dry1sy}vDcP2_WkVSwo4(Ayh+Pqy16VWs!<7l-8W z!ZU#%5-yqRZLqj+S2=l4(-NVZEvHR%BUh;f-@5uT|9fUl&4V?Al;`%g)_J3H;` zhi!?I4~w67@}7Qq?eR@&+6#Ew79`#EpDe{yuaRnDU~FNWY;2rnYLaSUoN8i{lxCS~ zl46u>nrLQdlxAUIlxksUnYj5N*9sPmDSn6{sxfq~)6PQ{mg%RJmB8h1MdH`LAtO3KQmbzGA=7%Tvo%lte0`wlFb)&yqOsp0M=TWdjJ3c diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll index 476956c76d2ebc0373d6a465bdf66e8258b976f5..3de98925bea3db4a798257badbac810e1c983a55 100644 GIT binary patch delta 384 zcmZqJq1CWMYeEMLi=Xh+#-7$5#?~ID)*j~89+uV~*47@jtv&2N>KHAix7M@!%UUoX zfYE*gn-R#gnEtVzU7FFNowtDATrjKR=kOQhNK9X3l;0 zEX?!_7#K_#8T1YH4D?LRw9SnSO^p*x%o2?ZjSLMFlS~X#lMR#2P0WCz$!TVZNvUQj zmZnK*+fOBN>FKfj(5-Ks{$~PLfWVQ%-%c(|-R-vecFnmLZ&X{S$4um^R)C6%WkW?l z>Oajpt$!}p?sTV_P1W}A6S<}{af18)p!C<@KEUq-h?ToXy{xbpqFj{ce delta 384 zcmZqJq1CWMYeEN0UzWns#-7$5#?~ID)*j~89+uV~*47@jtv&2N>KM(Zx7M@!%bGJF zfYE*gn-Rz~pZ>9)U7FFnowtD3&p^-AR6Etez}Uh#+1NPE)FjoyIMu`?Da|s~B*iG%G||k^D9ysa zDAmHyGI9H; z1*+$aofvTYf^_|!;IuOD?cXPIO=sc+`3LOw?bD}l2{SR8ZeKl(E15CaltG&zmBE6+ zfWa6@8Z#sVu`xp$gDHavSlk#$ngC%EP|Om{PGK+tsxoCrWH4he1hUh>x{bi94S{MB crzg(fGG#So&|@&zE;x%Tjd45UEUy2I0F=XSPXGV_ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb index ab7c2c7de2bc496aa83be66397dac3c76ee16a55..c248f45a08fba86a41757731451d055a46d85c37 100644 GIT binary patch delta 827 zcmV-B1H}C7#SHAl43K;j#KZQ&=WcBm5y zsY^nkhLWW2L+Qh4HIlaM#q5k4^hx=Repy+?H8yQQrP&98cF&x1_UtfOcKV`gJjyb- z!dCogO^yNzm2djZxE>mR&7gcq@Q4gM02Cb{i1#Lq&>ei)psR^NJV>mpv>~wAfx?yv zv{OC=QUV1v_+9$|=t_9h#y~+}JPYV*$0iLuYiou;JMmr;uChOPe(7k&qn5R+;l5)b zdwKYq-ce;piT3U?Lcbt0*}`;--vR0M_H60x23>b85_HiD9VM231XV!MDmnf-n8j7P zZex{qdt7&92$}wcXwwBq|3*qU zIUa2DMjKjF;KLw)^Ykp8&C|2t*M09QA8hXv&EE)5E++Zx9H(=fVl**jV=-elG&VIeVPj@CVPa)AWH~ir zX1D9~0kr}c>qX?y0N?=t5VK=OaOZ7Jm+L}QXON2~1 zcW-iQX>MmOWNd5z00Bdk7v}mmy|DU2l-TSbm{Z+3rj+eJ= z0UduOH8yQQrQQdDcF&x1_UtfOc7D_~9Ap_>VI%&$B1Zv*iZ|V6To1KoP`)5|M1~yz z@)i)pdy_`!4!)|1w3kFASW=M1$4P% zlbW8jHAA4Ccqa*0*&jR~JDTC3VeKloZ&`oHULO9YcT^fuqP@F}&@aeLHaG3^cR+gm za<=kzi>|#E2|90tP7+IkDxheUoPO=i;!0gLvC6wWt~)Y>O#ec(ZG*0A*(3S!1Zxt6 z4LR=KM|t;r=mYnsen+f=-X1zUNmh-KHF3R!Li5XZ0fEqnwgi^Ah5`k1Wa`ve+s1!% z7mY@k95)Q54{(c?R*gyEY&eMRk_c>r|3*qUIqhxpMjP5t;KLxt=_sAg(^3EHzIUY$ zw)2VRZ-i&G7)=*bHJz6887-zXo8{#N&5DVd|ms})` zG505W&|>KSX*Ng5GPYmrfrHN8de%SUL;ES1GdMG2Gc#s2 zG-Wt5Wi&KmW;tavWHMtlVKy-`W;if1WjHZ8VYln_0kr}c&Id=20N?=tC(Nyl;1PsO zqwts8o+KV&m;Ltv9hcSj0T399z?Q!NfB*pWxgqIJrbtGSGmAn~fPJ5r{r3SQe`uQx zkpLh80JEaNeOlT@9x<_GcWkYprbS_M9XJ=_{XD(!HYybcO*6sME0Du4h zNrD9Q;m_wlgaO5aI8AqNM`3McLv?I)E>2~1cW-iQX>MmOWNd5z00E~$0L=hE2mrTT h)n$Nz;7_EM8ROt~giT3vZ*XsDhoATXx1aa|O)C4rZEXMm diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index a3a774034539002ffecc6d67395e8e526c77e24d..afe9b4467c9d843b612bf920ed968af04743b622 100644 GIT binary patch delta 306 zcmZoDX(*Y{!J?i&dEUmJ90^8?$v-7j87(%;ODggUY*=_Z_TAaLemx9xm84Cd@7f%o zAVRWXKJQxZe(a`oM>W}Xk=()XqcE}Vwjq2m~3uh1{6(BGfPZL zHA}HHO-kE5TUU^Y#dmY`zsVc)0|aI^N}u1tDeBRXzh}yKW;?&h0tRyxpn@x4f*{os zSaNDUKg_=QXx6%Ko6i~?WaPA9U|?VbVvEhwjkuW@%{DJKInNku#-Pn$&S1n~$Y9D~ z%#g@n!e9m@L3|K41hSKWVulQ<49P$;8K@Sd0-`pVAq}iL2`FpEkiuXI6i;GEn|##F Vl+}zukHKKGpam1-W=4zui~!wGSor_| delta 306 zcmZoDX(*Y{!Sd~${*H}3ITDQKlYdI6GMaCemsI2z5c>7)UfJ)t?t9KNH}mIj%-kHH zAVRWXKJdQYGGh(VVrDioMvj0YGIseVv>|*nQD?^lx&)4W@waV zVPKSMVQ87SdA6<~6H9`qe$V6$`T+t7Q^F%mwmo<4YI5m>b$P)*|G Yqh_Y8rVM%v2Ac&fm>4%RTKs1O08n692><{9 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index 9328d1646ad3496958ec1ed38474d025b9ab04af..2130b83ecc0613c24df326fcb1d0dea5a8be1f98 100644 GIT binary patch delta 350 zcmZoTA>VL9enJO}^Az#tjXkYBjIBLPtv$?Jdsue-WVD#>`isRCNOk{W(Fany!BqR} zUo6{S|6+aHC}46xvqx1t+O@Ade{%fgbMD(;)Ue&Hw=mN)U|=v|WY9O%Gte_N(>6CU zG&N2%F-tTuG%_?yOfoS{O*Tw6H!%Z>Ca0MtCZ(FCSehoKZU6g+ZSh5x&VyG^OkZ-2 zJwPD&giz5p<}2=7|9N~@IU->_o#i}xy8=}7I7}3zzC^#_(FXT|5AvUMxwfx9&;FQ) z(}ID4ff0x;wokvq{+N-`YVL9enJP!awmqfjXkYBjIBLPtv$?Jdsue-WHg`d`isRCNOk{W(Fany!BqR} zUo6{S|6+aHD6l>DYHCB{1OFw{Bp*L0Yw_LwqK55my@jcs0Rw{xBZI!7o`IgJsdlP` zfw6^gvaxZRsY$AZajJ<)QkrF|Ns3XjX`-2-QJRH;QL2TZW#aa~f7ljZWU&m;?4Q2m z9D9I3)e-$31Et$8b6W$YK7I3DGM(i-d%FTu)I=643RKVU<@dsT&XoV16;Z*0+gG1w zf6T*a&cMLH2*l>wr{7_J%*bfEz4{(|J7cgZgEm7dg9U>DgE5daW=IBNV}>*aQw9^T zxG|740m3Apm?fB$6&Bs@DY0v L<95bJ?Ee`7i12W< diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index ffef7b76db36fd7f7f74c7fd1f2a86f789cd7b3a..8810690ae6771a9e7a33adabd312c0649e3afc46 100644 GIT binary patch delta 745 zcmVY)BDZf!<67006C&&uZI15XSF;yu;vAOJgZ=Y==6b zkh&xkno#1@J(M0stC2KxwJUZ;F?yuFMxU&#(;B;fzya@JVQ0VjeZ%ZBX*xXUIzCEM zxWrEVup&nRm5Mj(U|bJ>H47MD5UfyOBY?aI1ohq~QM!@OYjhO}s0WRiC0l|9dob7u z0XySEAfu2|jgRaDpeta-)LAG$TiN1d>%V9|?G zbFq6&?y0n7l)bx%(l01NO>XCy+;AN`KR1aAMRaNb$HBb~v5 zOc&?`YOd;GOQH0kL1zdW62U6Ax%2@lY)Dd}@JP=wzh+BI3bJdSC8x<`nw;*>dGFSB z#TeTE0biYuvdI~Lj;A=vMp=gWI3tsp^YL_YhNhfiPSfJ`Bro^({TO`FneA7(!G>b< zk~`9s58v2T`{N#1EgcHBJV0OL2P;r<=kK;&SbgX}1v@x_L9N=#EKMucy2}&eoAh_x zY1}?=zBXKsgPZNq6bHxc;%&#afX{)I=DwiofK@|I!B`XoNn+klhqg8Wx3)F{or@?p zGBGtXVKg>jGBGkSF=1jfF=b;hV>dK5H8WvjW;S7BWj16vHDYGBd%^;$0vPK>q+x&j@STEzko7}9JszW{&$00Gn$v=1WuPQGvca|spm z)R*qM0v(s1#R3Kxp(OwO03ZPX3YAQAA5#cUts%9!oU+;}m%YUT7Z_6khu8ps001!w bbeFs#jYowv1<+QZR;`EZ#R9kN#RF6VL5y8hx@Nr!{sD;DB`@(CDAb*^(bSqy}=t>e$4;nKIwgfeHV6YVe zcE*Q5Mj@jLAK3>$m&2N^g^a*@QQ%@r(~6$FSs>8Xd}9fh=|5^dc56;gT47hhvJ<8H zV*i-jQ(?&{dv_6~Ur>bV%y#p?12SC z7~1~zHW<-tc#enrehfZu&F-t*U`?@k z$sOt1hi~l4!*LI+mW~Bm9igxBgXO5W_jemFtUh#~g6*9_uTpJfo~9*h)nMwmF*0U2FfwI0F*#wkd%^;$0vOH*M~?vD z0RSh=t&QLjgiNFGm)o8s9$}a6x&j@STEzko7>dA_zW{&$0Q9*b=}x9dMv*g%LQ{Z! zpO@~s0v(s1#R3KxXqyd@03ZPXv!cL#TG~NOp1_ZO`cUb*m%YUT7Z}#<_@w}V002pX b1oYw0=Rkx3#e+CacW;O6#R9kN#RF6Vu{c~u diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index e5e5c57f..fd173021 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.55", + "version": "1.0.56", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From 0a885afce7a833b25127bedfdf9bc70a5872a15f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 12:12:52 +0200 Subject: [PATCH 037/203] Authorize database subscription records --- .../CultNetDatabaseSubscriptionServer.cs | 52 ++++++++++++- .../NetworkingTests.cs | 75 +++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs index c5fe47bd..cd224816 100644 --- a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs +++ b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs @@ -20,6 +20,8 @@ namespace GameCult.Networking private readonly CultNetDatabase _database; private readonly Func _subscribe; private readonly Func _unsubscribe; + private readonly Func? _authorizeRequest; + private readonly Func? _authorizeRecord; private readonly ICultNetSchemaServerPeerLifecycle? _peerLifecycle; private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(SubscriptionKeyComparer.Instance); @@ -28,10 +30,16 @@ namespace GameCult.Networking private bool _disposed; /// Attaches live database subscription handlers to a schema server. - public CultNetDatabaseSubscriptionServer(ICultNetSchemaServer server, CultNetDatabase database) + public CultNetDatabaseSubscriptionServer( + ICultNetSchemaServer server, + CultNetDatabase database, + Func? authorizeRequest = null, + Func? authorizeRecord = null) { _server = server ?? throw new ArgumentNullException(nameof(server)); _database = database ?? throw new ArgumentNullException(nameof(database)); + _authorizeRequest = authorizeRequest; + _authorizeRecord = authorizeRecord; _subscribe = HandleSubscribeAsync; _unsubscribe = HandleUnsubscribeAsync; _server.OnCultNet(_subscribe); @@ -89,6 +97,11 @@ namespace GameCult.Networking peer.SendCultNet(new CultNetErrorMessage { Error = "Database subscription requires a subscriptionId or messageId." }); return Task.CompletedTask; } + if (_authorizeRequest?.Invoke(request, peer) == false) + { + peer.SendCultNet(new CultNetErrorMessage { Error = "Database subscription is not authorized for this peer." }); + return Task.CompletedTask; + } var key = new SubscriptionKey(peer, subscriptionId); if (_requests.TryGetValue(key, out var previous)) @@ -105,7 +118,7 @@ namespace GameCult.Networking PublishDemand(request, key, active: true); if (request.IncludeSnapshot) { - peer.SendCultNet(_database.Documents.CreateRawSnapshotResponse( + var snapshot = _database.Documents.CreateRawSnapshotResponse( _database.Cache, request.MessageId, new CultNetSnapshotRequestMessage @@ -113,7 +126,15 @@ namespace GameCult.Networking MessageId = request.MessageId, SchemaIds = request.SchemaIds, RecordKeys = request.RecordKeys - })); + }); + if (_authorizeRecord != null) + { + snapshot.Documents = snapshot.Documents + .Where(document => _authorizeRecord( + request, peer, document.RecordKey, document.SchemaId)) + .ToArray(); + } + peer.SendCultNet(snapshot); } else { @@ -183,15 +204,34 @@ namespace GameCult.Networking { return _database.WatchAllChanges().Subscribe(change => { - var message = CreateChange(change, request, subscriptionId); + var message = CreateAuthorizedChange(change, request, subscriptionId, peer); if (message != null) peer.SendCultNet(message); }); } + private CultNetDatabaseChangeRawMessage? CreateAuthorizedChange( + object change, + CultNetDatabaseSubscribeMessage request, + string subscriptionId, + ICultNetSchemaServerPeer peer) + { + return CreateChangeCore(change, request, subscriptionId, (recordKey, schemaId) => + _authorizeRecord?.Invoke(request, peer, recordKey, schemaId) != false); + } + private CultNetDatabaseChangeRawMessage? CreateChange( object change, CultNetDatabaseSubscribeMessage request, string subscriptionId) + { + return CreateChangeCore(change, request, subscriptionId, (_, _) => true); + } + + private CultNetDatabaseChangeRawMessage? CreateChangeCore( + object change, + CultNetDatabaseSubscribeMessage request, + string subscriptionId, + Func authorizeRecord) { var changeType = change.GetType(); var key = (CultRecordKey)(changeType.GetProperty("Key")?.GetValue(change) ?? new CultRecordKey("")); @@ -206,6 +246,8 @@ namespace GameCult.Networking var schemaId = ResolveWireSchemaId(previous, (string?)changeType.GetProperty("SchemaId")?.GetValue(change) ?? ""); if (!MatchesRequestedSchema(request.SchemaIds, previous, schemaId)) return null; + if (!authorizeRecord(key.Value, schemaId)) + return null; return new CultNetDatabaseChangeRawMessage { MessageId = Guid.NewGuid().ToString("N"), @@ -219,6 +261,8 @@ namespace GameCult.Networking var raw = CreateRawRecord(key, document); if (!MatchesRequestedSchema(request.SchemaIds, document, raw.SchemaId)) return null; + if (!authorizeRecord(key.Value, raw.SchemaId)) + return null; return new CultNetDatabaseChangeRawMessage { MessageId = Guid.NewGuid().ToString("N"), diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index 28245a3d..ae3265ca 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -1462,6 +1462,81 @@ namespace GameCult.Networking.Tests Assert.That(update.Document!.RecordKey, Is.EqualTo(recordKey)); } + [Test] + public async Task DatabaseSubscriptionServer_AuthorizesRequestAndFiltersSnapshotAndLiveRecords() + { + var cache = new CultCache(); + var database = new CultNetDatabase(cache); + using var server = new RudpCultNetSchemaServer(new RudpCultNetSchemaServerOptions + { + RuntimeId = "database-authorized-subscription-server", + Socket = BindUdpSocket() + }); + using var subscriptions = new CultNetDatabaseSubscriptionServer( + server, + database, + authorizeRequest: (request, _) => request.ConsumerRuntimeId == "allowed-runtime", + authorizeRecord: (_, _, recordKey, _) => recordKey.StartsWith("tests:public:", StringComparison.Ordinal)); + using var cancellation = new CancellationTokenSource(); + var serverThread = new Thread(() => + { + while (!cancellation.IsCancellationRequested) + { + _ = server.PollOnceAsync().GetAwaiter().GetResult(); + Thread.Sleep(1); + } + }) { IsBackground = true }; + serverThread.Start(); + + await database.PutAsync(new CultRecordKey("tests:public:initial"), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "public-initial" + }); + await database.PutAsync(new CultRecordKey("tests:private:initial"), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "private-initial" + }); + + using var client = CultNetSchemaClients.CreateRudp("database-authorized-subscription-client"); + var subscribed = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + var changed = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + client.OnCultNet(message => subscribed.TrySetResult(message)); + client.OnCultNet(message => changed.TrySetResult(message)); + client.Connect("127.0.0.1", server.LocalEndPoint.Port); + await WaitUntilAsync(() => client.Connected, TimeSpan.FromSeconds(2)); + client.SendCultNet(new CultNetDatabaseSubscribeMessage + { + MessageId = "subscribe-authorized", + SubscriptionId = "authorized", + ConsumerRuntimeId = "allowed-runtime", + IncludeSnapshot = true + }); + + var snapshot = await AwaitWithTimeout(subscribed.Task, TimeSpan.FromSeconds(2)); + Assert.That(snapshot.Documents.Select(document => document.RecordKey), + Is.EqualTo(new[] { "tests:public:initial" })); + + await database.PutAsync(new CultRecordKey("tests:private:live"), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "private-live" + }); + await database.PutAsync(new CultRecordKey("tests:public:live"), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "public-live" + }); + var update = await AwaitWithTimeout(changed.Task, TimeSpan.FromSeconds(2)); + + cancellation.Cancel(); + Assert.That(update.Document, Is.Not.Null); + Assert.That(update.Document!.RecordKey, Is.EqualTo("tests:public:live")); + } + [Test] public async Task DatabaseSubscriptionServer_ProjectsBodyDemandAndWithdrawalFromExactSubscription() { From eaf1189cc13ec357cb54e2722de2793faee2680c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 13:22:36 +0200 Subject: [PATCH 038/203] Project subscription records per peer --- .../CultNetDatabaseSubscriptionServer.cs | 20 ++++++++++++++++++- .../NetworkingTests.cs | 9 ++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs index cd224816..c1ca3c9d 100644 --- a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs +++ b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs @@ -13,6 +13,8 @@ namespace GameCult.Networking { /// /// Publishes live database changes through any schema-v0 server transport. + /// Optional record projection runs after authorization and owns both initial + /// snapshot and live-update delivery for a peer. /// public sealed class CultNetDatabaseSubscriptionServer : IDisposable { @@ -22,6 +24,7 @@ namespace GameCult.Networking private readonly Func _unsubscribe; private readonly Func? _authorizeRequest; private readonly Func? _authorizeRecord; + private readonly Func? _projectRecord; private readonly ICultNetSchemaServerPeerLifecycle? _peerLifecycle; private readonly ConcurrentDictionary _subscriptions = new ConcurrentDictionary(SubscriptionKeyComparer.Instance); @@ -34,12 +37,14 @@ namespace GameCult.Networking ICultNetSchemaServer server, CultNetDatabase database, Func? authorizeRequest = null, - Func? authorizeRecord = null) + Func? authorizeRecord = null, + Func? projectRecord = null) { _server = server ?? throw new ArgumentNullException(nameof(server)); _database = database ?? throw new ArgumentNullException(nameof(database)); _authorizeRequest = authorizeRequest; _authorizeRecord = authorizeRecord; + _projectRecord = projectRecord; _subscribe = HandleSubscribeAsync; _unsubscribe = HandleUnsubscribeAsync; _server.OnCultNet(_subscribe); @@ -134,6 +139,14 @@ namespace GameCult.Networking request, peer, document.RecordKey, document.SchemaId)) .ToArray(); } + if (_projectRecord != null) + { + snapshot.Documents = snapshot.Documents + .Select(document => _projectRecord(request, peer, document)) + .Where(document => document != null) + .Cast() + .ToArray(); + } peer.SendCultNet(snapshot); } else @@ -205,6 +218,11 @@ namespace GameCult.Networking return _database.WatchAllChanges().Subscribe(change => { var message = CreateAuthorizedChange(change, request, subscriptionId, peer); + if (message?.Document != null && _projectRecord != null) + { + message.Document = _projectRecord(request, peer, message.Document); + if (message.Document == null) return; + } if (message != null) peer.SendCultNet(message); }); } diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index ae3265ca..deac0d02 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -1476,7 +1476,12 @@ namespace GameCult.Networking.Tests server, database, authorizeRequest: (request, _) => request.ConsumerRuntimeId == "allowed-runtime", - authorizeRecord: (_, _, recordKey, _) => recordKey.StartsWith("tests:public:", StringComparison.Ordinal)); + authorizeRecord: (_, _, recordKey, _) => recordKey.StartsWith("tests:public:", StringComparison.Ordinal), + projectRecord: (request, _, record) => + { + record.SourceRuntimeId = "projection:" + request.ConsumerRuntimeId; + return record; + }); using var cancellation = new CancellationTokenSource(); var serverThread = new Thread(() => { @@ -1519,6 +1524,7 @@ namespace GameCult.Networking.Tests var snapshot = await AwaitWithTimeout(subscribed.Task, TimeSpan.FromSeconds(2)); Assert.That(snapshot.Documents.Select(document => document.RecordKey), Is.EqualTo(new[] { "tests:public:initial" })); + Assert.That(snapshot.Documents.Single().SourceRuntimeId, Is.EqualTo("projection:allowed-runtime")); await database.PutAsync(new CultRecordKey("tests:private:live"), new NetworkSchemaNote { @@ -1535,6 +1541,7 @@ namespace GameCult.Networking.Tests cancellation.Cancel(); Assert.That(update.Document, Is.Not.Null); Assert.That(update.Document!.RecordKey, Is.EqualTo("tests:public:live")); + Assert.That(update.Document.SourceRuntimeId, Is.EqualTo("projection:allowed-runtime")); } [Test] From 73ab8b65e12f9d63d96a85b266715320c67a4dfe Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 13:55:03 +0200 Subject: [PATCH 039/203] Reconcile live subscription authority --- .../CultNetDatabaseSubscriptionServer.cs | 355 ++++++++++++++---- .../NetworkingTests.cs | 91 +++++ 2 files changed, 376 insertions(+), 70 deletions(-) diff --git a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs index c1ca3c9d..3d284556 100644 --- a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs +++ b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs @@ -30,6 +30,9 @@ namespace GameCult.Networking new ConcurrentDictionary(SubscriptionKeyComparer.Instance); private readonly ConcurrentDictionary _requests = new ConcurrentDictionary(SubscriptionKeyComparer.Instance); + private readonly ConcurrentDictionary _projections = + new ConcurrentDictionary(SubscriptionKeyComparer.Instance); + private readonly object _lifecycleGate = new(); private bool _disposed; /// Attaches live database subscription handlers to a schema server. @@ -61,34 +64,48 @@ namespace GameCult.Networking /// public event Action? DemandChanged; + /// + /// Re-evaluates every live peer projection against current authorization and source state. + /// Records that lost visibility are explicitly removed and body demand is withdrawn while + /// the request is unauthorized. The subscription intent remains available for a later + /// authorized generation on the same established peer. + /// + public void Reconcile() + { + lock (_lifecycleGate) + { + if (_disposed) return; + foreach (var entry in _requests.ToArray()) + Reconcile(entry.Key, entry.Value); + } + } + /// Detaches handlers and releases all active watches. public void Dispose() { - if (_disposed) return; - _disposed = true; - _server.RemoveCultNetMessageListener(_subscribe); - _server.RemoveCultNetMessageListener(_unsubscribe); - if (_peerLifecycle != null) - _peerLifecycle.PeerDisconnected -= HandlePeerDisconnected; - foreach (var entry in _subscriptions) + lock (_lifecycleGate) { - entry.Value.Dispose(); - if (_requests.TryRemove(entry.Key, out var request)) - PublishDemand(request, entry.Key, active: false); + if (_disposed) return; + _disposed = true; + _server.RemoveCultNetMessageListener(_subscribe); + _server.RemoveCultNetMessageListener(_unsubscribe); + if (_peerLifecycle != null) + _peerLifecycle.PeerDisconnected -= HandlePeerDisconnected; + foreach (var entry in _subscriptions.ToArray()) + Withdraw(entry.Key, sendRemovals: false, forgetRequest: true); + _subscriptions.Clear(); + _requests.Clear(); + _projections.Clear(); } - _subscriptions.Clear(); - _requests.Clear(); } private void HandlePeerDisconnected(ICultNetSchemaServerPeer peer) { - foreach (var key in _subscriptions.Keys.Where(candidate => - SubscriptionKeyComparer.SamePeer(candidate.Peer, peer)).ToArray()) + lock (_lifecycleGate) { - if (_subscriptions.TryRemove(key, out var subscription)) - subscription.Dispose(); - if (_requests.TryRemove(key, out var request)) - PublishDemand(request, key, active: false); + foreach (var key in _subscriptions.Keys.Where(candidate => + SubscriptionKeyComparer.SamePeer(candidate.Peer, peer)).ToArray()) + Withdraw(key, sendRemovals: false, forgetRequest: true); } } @@ -108,54 +125,43 @@ namespace GameCult.Networking return Task.CompletedTask; } - var key = new SubscriptionKey(peer, subscriptionId); - if (_requests.TryGetValue(key, out var previous)) - PublishDemand(previous, key, active: false); - _subscriptions.AddOrUpdate( - key, - _ => Watch(request, subscriptionId, peer), - (_, current) => - { - current.Dispose(); - return Watch(request, subscriptionId, peer); - }); - _requests[key] = request; - PublishDemand(request, key, active: true); - if (request.IncludeSnapshot) + lock (_lifecycleGate) { - var snapshot = _database.Documents.CreateRawSnapshotResponse( - _database.Cache, - request.MessageId, - new CultNetSnapshotRequestMessage + var key = new SubscriptionKey(peer, subscriptionId); + Withdraw(key, sendRemovals: true, forgetRequest: true); + var projection = new SubscriptionProjectionState(); + _projections[key] = projection; + _requests[key] = request; + try + { + _subscriptions[key] = Watch(request, subscriptionId, peer, key); + if (request.IncludeSnapshot) { - MessageId = request.MessageId, - SchemaIds = request.SchemaIds, - RecordKeys = request.RecordKeys - }); - if (_authorizeRecord != null) - { - snapshot.Documents = snapshot.Documents - .Where(document => _authorizeRecord( - request, peer, document.RecordKey, document.SchemaId)) - .ToArray(); + var snapshot = CreateProjectedSnapshot(request, peer); + foreach (var entry in snapshot.BySourceRecordKey) + projection.DeliveredBySourceRecordKey[entry.Key] = entry.Value; + peer.SendCultNet(new CultNetSnapshotResponseRawMessage + { + MessageId = request.MessageId, + Documents = snapshot.Documents + }); + } + else + { + peer.SendCultNet(new CultNetSnapshotResponseRawMessage + { + MessageId = request.MessageId, + Documents = Array.Empty() + }); + } + PublishDemand(request, key, active: true); + projection.DemandActive = true; } - if (_projectRecord != null) + catch { - snapshot.Documents = snapshot.Documents - .Select(document => _projectRecord(request, peer, document)) - .Where(document => document != null) - .Cast() - .ToArray(); + Withdraw(key, sendRemovals: false, forgetRequest: true); + throw; } - peer.SendCultNet(snapshot); - } - else - { - peer.SendCultNet(new CultNetSnapshotResponseRawMessage - { - MessageId = request.MessageId, - Documents = Array.Empty() - }); } return Task.CompletedTask; } @@ -168,9 +174,8 @@ namespace GameCult.Networking if (!string.IsNullOrWhiteSpace(subscriptionId)) { var key = new SubscriptionKey(peer, subscriptionId); - if (_subscriptions.TryRemove(key, out var subscription)) subscription.Dispose(); - if (_requests.TryRemove(key, out var subscribed)) - PublishDemand(subscribed, key, active: false); + lock (_lifecycleGate) + Withdraw(key, sendRemovals: false, forgetRequest: true); } return Task.CompletedTask; } @@ -213,20 +218,209 @@ namespace GameCult.Networking private IDisposable Watch( CultNetDatabaseSubscribeMessage request, string subscriptionId, - ICultNetSchemaServerPeer peer) + ICultNetSchemaServerPeer peer, + SubscriptionKey key) { return _database.WatchAllChanges().Subscribe(change => { - var message = CreateAuthorizedChange(change, request, subscriptionId, peer); - if (message?.Document != null && _projectRecord != null) + lock (_lifecycleGate) { - message.Document = _projectRecord(request, peer, message.Document); - if (message.Document == null) return; + if (_disposed || !_requests.ContainsKey(key) || !_projections.TryGetValue(key, out var projection)) + return; + if (_authorizeRequest?.Invoke(request, peer) == false) + { + Reconcile(key, request); + return; + } + + var sourceRecordKey = ResolveChangeRecordKey(change); + var message = CreateAuthorizedChange(change, request, subscriptionId, peer); + var projected = message?.Document; + if (projected != null && _projectRecord != null) + projected = _projectRecord(request, peer, projected); + ApplyProjectedChange( + projection, + sourceRecordKey, + projected, + peer, + subscriptionId); } - if (message != null) peer.SendCultNet(message); }); } + private void Reconcile(SubscriptionKey key, CultNetDatabaseSubscribeMessage request) + { + if (!_projections.TryGetValue(key, out var projection)) return; + var authorized = _authorizeRequest?.Invoke(request, key.Peer) != false; + var next = authorized + ? CreateProjectedSnapshot(request, key.Peer) + : ProjectedSnapshot.Empty; + + foreach (var previous in projection.DeliveredBySourceRecordKey.ToArray()) + { + if (!next.BySourceRecordKey.TryGetValue(previous.Key, out var current)) + { + SendRemoval(key.Peer, key.Id, previous.Value); + continue; + } + if (!string.Equals(previous.Value.RecordKey, current.RecordKey, StringComparison.Ordinal) || + !string.Equals(previous.Value.SchemaId, current.SchemaId, StringComparison.Ordinal)) + { + SendRemoval(key.Peer, key.Id, previous.Value); + SendUpsert(key.Peer, key.Id, current, added: true); + } + else if (!Equivalent(previous.Value, current)) + { + SendUpsert(key.Peer, key.Id, current, added: false); + } + } + foreach (var current in next.BySourceRecordKey) + { + if (!projection.DeliveredBySourceRecordKey.ContainsKey(current.Key)) + SendUpsert(key.Peer, key.Id, current.Value, added: true); + } + + projection.DeliveredBySourceRecordKey.Clear(); + foreach (var current in next.BySourceRecordKey) + projection.DeliveredBySourceRecordKey[current.Key] = current.Value; + + if (projection.DemandActive != authorized) + { + projection.DemandActive = authorized; + PublishDemand(request, key, authorized); + } + } + + private ProjectedSnapshot CreateProjectedSnapshot( + CultNetDatabaseSubscribeMessage request, + ICultNetSchemaServerPeer peer) + { + var snapshot = _database.Documents.CreateRawSnapshotResponse( + _database.Cache, + request.MessageId, + new CultNetSnapshotRequestMessage + { + MessageId = request.MessageId, + SchemaIds = request.SchemaIds, + RecordKeys = request.RecordKeys + }); + var bySourceRecordKey = new Dictionary(StringComparer.Ordinal); + var projectedRecordKeys = new HashSet(StringComparer.Ordinal); + foreach (var source in snapshot.Documents) + { + var sourceRecordKey = source.RecordKey; + if (_authorizeRecord?.Invoke(request, peer, sourceRecordKey, source.SchemaId) == false) + continue; + var projected = _projectRecord == null ? source : _projectRecord(request, peer, source); + if (projected == null) continue; + if (!projectedRecordKeys.Add(projected.RecordKey)) + throw new InvalidOperationException( + $"Database subscription projection produced duplicate record key '{projected.RecordKey}'."); + bySourceRecordKey[sourceRecordKey] = projected; + } + return new ProjectedSnapshot(bySourceRecordKey); + } + + private void ApplyProjectedChange( + SubscriptionProjectionState projection, + string sourceRecordKey, + CultNetRawDocumentRecord? current, + ICultNetSchemaServerPeer peer, + string subscriptionId) + { + projection.DeliveredBySourceRecordKey.TryGetValue(sourceRecordKey, out var previous); + if (current == null) + { + if (previous != null) + { + projection.DeliveredBySourceRecordKey.Remove(sourceRecordKey); + SendRemoval(peer, subscriptionId, previous); + } + return; + } + if (previous == null) + { + projection.DeliveredBySourceRecordKey[sourceRecordKey] = current; + SendUpsert(peer, subscriptionId, current, added: true); + return; + } + if (!string.Equals(previous.RecordKey, current.RecordKey, StringComparison.Ordinal) || + !string.Equals(previous.SchemaId, current.SchemaId, StringComparison.Ordinal)) + { + SendRemoval(peer, subscriptionId, previous); + projection.DeliveredBySourceRecordKey[sourceRecordKey] = current; + SendUpsert(peer, subscriptionId, current, added: true); + return; + } + if (!Equivalent(previous, current)) + { + projection.DeliveredBySourceRecordKey[sourceRecordKey] = current; + SendUpsert(peer, subscriptionId, current, added: false); + } + } + + private static string ResolveChangeRecordKey(object change) + { + var value = change.GetType().GetProperty("Key")?.GetValue(change); + return value is CultRecordKey key ? key.Value : string.Empty; + } + + private static bool Equivalent(CultNetRawDocumentRecord left, CultNetRawDocumentRecord right) => + string.Equals(left.SchemaId, right.SchemaId, StringComparison.Ordinal) && + string.Equals(left.SchemaName, right.SchemaName, StringComparison.Ordinal) && + string.Equals(left.SchemaVersion, right.SchemaVersion, StringComparison.Ordinal) && + string.Equals(left.SchemaContentHash, right.SchemaContentHash, StringComparison.Ordinal) && + string.Equals(left.RecordKey, right.RecordKey, StringComparison.Ordinal) && + string.Equals(left.PayloadEncoding, right.PayloadEncoding, StringComparison.Ordinal) && + left.Payload.SequenceEqual(right.Payload) && + string.Equals(left.SourceRuntimeId, right.SourceRuntimeId, StringComparison.Ordinal) && + string.Equals(left.SourceAgentId, right.SourceAgentId, StringComparison.Ordinal) && + string.Equals(left.SourceRole, right.SourceRole, StringComparison.Ordinal); + + private static void SendUpsert( + ICultNetSchemaServerPeer peer, + string subscriptionId, + CultNetRawDocumentRecord document, + bool added) => + peer.SendCultNet(new CultNetDatabaseChangeRawMessage + { + MessageId = Guid.NewGuid().ToString("N"), + SubscriptionId = subscriptionId, + ChangeKind = added ? "added" : "updated", + Document = document + }); + + private static void SendRemoval( + ICultNetSchemaServerPeer peer, + string subscriptionId, + CultNetRawDocumentRecord document) => + peer.SendCultNet(new CultNetDatabaseChangeRawMessage + { + MessageId = Guid.NewGuid().ToString("N"), + SubscriptionId = subscriptionId, + ChangeKind = "removed", + RecordKey = document.RecordKey, + SchemaId = document.SchemaId + }); + + private void Withdraw(SubscriptionKey key, bool sendRemovals, bool forgetRequest) + { + if (_subscriptions.TryRemove(key, out var subscription)) + subscription.Dispose(); + if (_projections.TryRemove(key, out var projection)) + { + if (sendRemovals) + { + foreach (var delivered in projection.DeliveredBySourceRecordKey.Values) + SendRemoval(key.Peer, key.Id, delivered); + } + if (projection.DemandActive && _requests.TryGetValue(key, out var activeRequest)) + PublishDemand(activeRequest, key, active: false); + } + if (forgetRequest) + _requests.TryRemove(key, out _); + } + private CultNetDatabaseChangeRawMessage? CreateAuthorizedChange( object change, CultNetDatabaseSubscribeMessage request, @@ -320,6 +514,27 @@ namespace GameCult.Networking return ((CultNetDocumentPutRawMessage)put!).Document; } + private sealed class SubscriptionProjectionState + { + public Dictionary DeliveredBySourceRecordKey { get; } = + new(StringComparer.Ordinal); + public bool DemandActive { get; set; } + } + + private sealed class ProjectedSnapshot + { + public ProjectedSnapshot(Dictionary bySourceRecordKey) + { + BySourceRecordKey = bySourceRecordKey; + Documents = bySourceRecordKey.Values.ToArray(); + } + + public static ProjectedSnapshot Empty { get; } = + new(new Dictionary(StringComparer.Ordinal)); + public Dictionary BySourceRecordKey { get; } + public CultNetRawDocumentRecord[] Documents { get; } + } + private sealed class SubscriptionKey { public SubscriptionKey(ICultNetSchemaServerPeer peer, string id) diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index deac0d02..c87ec819 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -1544,6 +1544,97 @@ namespace GameCult.Networking.Tests Assert.That(update.Document.SourceRuntimeId, Is.EqualTo("projection:allowed-runtime")); } + [Test] + public async Task DatabaseSubscriptionServer_ReconcilesDeliveredProjectionAndBodyDemandWhenAuthorityChanges() + { + const string sourceRecordKey = "tests:authority:source"; + const string bodyId = "tests:authority:body"; + var authorized = true; + var projectionName = "one"; + var sourceCache = new CultCache(); + var sourceDatabase = new CultNetDatabase(sourceCache); + await sourceDatabase.PutAsync(new CultRecordKey(sourceRecordKey), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "initial" + }); + using var server = new RudpCultNetSchemaServer(new RudpCultNetSchemaServerOptions + { + RuntimeId = "database-reconciled-subscription-server", + Socket = BindUdpSocket() + }); + using var subscriptions = new CultNetDatabaseSubscriptionServer( + server, + sourceDatabase, + authorizeRequest: (_, _) => authorized, + authorizeRecord: (_, _, _, _) => authorized, + projectRecord: (_, _, record) => + { + record.RecordKey = $"tests:authority:projected:{projectionName}"; + return record; + }); + using var bodyDemand = new CultMeshBodyDemandTracker(subscriptions); + using var cancellation = new CancellationTokenSource(); + var serverThread = new Thread(() => + { + while (!cancellation.IsCancellationRequested) + { + _ = server.PollOnceAsync().GetAwaiter().GetResult(); + Thread.Sleep(1); + } + }) { IsBackground = true }; + serverThread.Start(); + + var targetCache = new CultCache(); + var targetDocuments = new CultNetDocumentRegistry(targetCache.Registry) + .Register(CultNetDocumentBinding.ForDocument(targetCache.Registry)); + var transport = CultNetSchemaClients.CreateRudp("database-reconciled-subscription-client"); + using var client = new CultNetDatabaseSubscriptionClient(transport, targetCache, targetDocuments); + var changes = new ConcurrentQueue(); + client.Changed += changes.Enqueue; + transport.Connect("127.0.0.1", server.LocalEndPoint.Port); + await WaitUntilAsync(() => transport.Connected, TimeSpan.FromSeconds(2)); + await AwaitWithTimeout( + client.SubscribeAsync( + "authority", + recordKeys: [sourceRecordKey], + consumerRuntimeId: "authority-client", + bodyIds: [bodyId], + supportedBodyTransports: [CultMeshBodyTransportKind.SharedMemory.ToString()]), + TimeSpan.FromSeconds(2)); + Assert.That(targetCache.Get(new CultRecordKey("tests:authority:projected:one")), Is.Not.Null); + Assert.That(bodyDemand.Plan(bodyId).HasConsumers, Is.True); + + projectionName = "two"; + subscriptions.Reconcile(); + await WaitUntilAsync( + () => targetCache.Get(new CultRecordKey("tests:authority:projected:one")) == null && + targetCache.Get(new CultRecordKey("tests:authority:projected:two")) != null, + TimeSpan.FromSeconds(2)); + Assert.That(changes.Any(change => change.ChangeKind == "removed" && + change.RecordKey == "tests:authority:projected:one"), Is.True); + Assert.That(changes.Any(change => change.ChangeKind == "added" && + change.RecordKey == "tests:authority:projected:two"), Is.True); + + authorized = false; + subscriptions.Reconcile(); + await WaitUntilAsync( + () => targetCache.Get(new CultRecordKey("tests:authority:projected:two")) == null, + TimeSpan.FromSeconds(2)); + Assert.That(bodyDemand.Plan(bodyId).HasConsumers, Is.False); + var changeCountAfterRevocation = changes.Count; + await sourceDatabase.PutAsync(new CultRecordKey(sourceRecordKey), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "must-not-escape" + }); + await Task.Delay(100); + cancellation.Cancel(); + + Assert.That(changes.Count, Is.EqualTo(changeCountAfterRevocation)); + Assert.That(targetCache.Get(new CultRecordKey("tests:authority:projected:two")), Is.Null); + } + [Test] public async Task DatabaseSubscriptionServer_ProjectsBodyDemandAndWithdrawalFromExactSubscription() { From 21fd4e07ebe0634b30b9da6aa63d983654cc9b62 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 14:10:13 +0200 Subject: [PATCH 040/203] Version hot-body demand generations --- src/GameCult.Mesh/CultMeshBodyDemand.cs | 47 ++++++++++++++++++- .../CultMeshBodyTransportTests.cs | 8 ++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/GameCult.Mesh/CultMeshBodyDemand.cs b/src/GameCult.Mesh/CultMeshBodyDemand.cs index 55fa3807..9c9e17da 100644 --- a/src/GameCult.Mesh/CultMeshBodyDemand.cs +++ b/src/GameCult.Mesh/CultMeshBodyDemand.cs @@ -225,9 +225,11 @@ namespace GameCult.Mesh { internal CultMeshBodyDemandPlan( string bodyId, + long generation, IReadOnlyList consumers) { BodyId = bodyId; + Generation = generation; Consumers = consumers; RequiresSharedMemory = consumers.Any(value => value.Transport == CultMeshBodyTransportKind.SharedMemory); RequiresSharedFileMapping = consumers.Any(value => value.Transport == CultMeshBodyTransportKind.SharedFileMapping); @@ -235,6 +237,11 @@ namespace GameCult.Mesh } public string BodyId { get; } + /// + /// Monotone generation of the exact routed consumer set. Publishers must rotate any + /// retained capability before writing after this value changes. + /// + public long Generation { get; } public bool HasConsumers => Consumers.Count > 0; public bool RequiresSharedMemory { get; } public bool RequiresSharedFileMapping { get; } @@ -271,6 +278,8 @@ namespace GameCult.Mesh private readonly object _gate = new(); private readonly Dictionary _demands = new(StringComparer.Ordinal); + private readonly Dictionary _bodyGenerations = + new(StringComparer.Ordinal); private bool _disposed; public CultMeshBodyDemandTracker(CultNetDatabaseSubscriptionServer subscriptions) @@ -298,7 +307,10 @@ namespace GameCult.Mesh .OrderBy(value => value.ConsumerRuntimeId, StringComparer.Ordinal) .ThenBy(value => value.SubscriptionId, StringComparer.Ordinal) .ToArray(); - return new CultMeshBodyDemandPlan(bodyId, routes); + return new CultMeshBodyDemandPlan( + bodyId, + _bodyGenerations.TryGetValue(bodyId, out var generation) ? generation : 0, + routes); } } @@ -310,6 +322,7 @@ namespace GameCult.Mesh _disposed = true; if (_subscriptions != null) _subscriptions.DemandChanged -= OnDemandChanged; _demands.Clear(); + _bodyGenerations.Clear(); } } @@ -322,11 +335,43 @@ namespace GameCult.Mesh lock (_gate) { if (_disposed) return; + _demands.TryGetValue(key, out var previous); + var affectedBodyIds = (previous?.BodyIds ?? Array.Empty()) + .Concat(demand.BodyIds) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var before = affectedBodyIds.ToDictionary( + bodyId => bodyId, + RoutedConsumerSignature, + StringComparer.Ordinal); if (demand.Active) _demands[key] = demand; else _demands.Remove(key); + foreach (var bodyId in affectedBodyIds) + { + var after = RoutedConsumerSignature(bodyId); + if (string.Equals(before[bodyId], after, StringComparison.Ordinal)) continue; + _bodyGenerations[bodyId] = checked( + (_bodyGenerations.TryGetValue(bodyId, out var generation) ? generation : 0) + 1); + } } } + private string RoutedConsumerSignature(string bodyId) => string.Join( + "\u001e", + _demands.Values + .Where(value => value.BodyIds.Contains(bodyId, StringComparer.Ordinal)) + .Select(Route) + .Where(value => value != null) + .Cast() + .OrderBy(value => value.ConsumerRuntimeId, StringComparer.Ordinal) + .ThenBy(value => value.SubscriptionId, StringComparer.Ordinal) + .Select(value => string.Join( + "\u001f", + value.ConsumerRuntimeId, + value.SubscriptionId, + value.Transport.ToString()))); + private void OnDemandChanged(CultNetDatabaseSubscriptionDemand demand) => Observe(demand); private static CultMeshBodyConsumerRoute? Route(CultNetDatabaseSubscriptionDemand demand) diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs index e644c3ff..0421aea7 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs @@ -318,7 +318,9 @@ public sealed class CultMeshBodyTransportTests public void BodyDemand_SelectsSharedMemoryLocallyAndNetworkRemotelyWithoutDuplicateFallbackWork() { using var tracker = new CultMeshBodyDemandTracker(); + tracker.Plan("eve:entity-soa:aetheria.daemon:pilot").Generation.Should().Be(0); tracker.Observe(Demand("unity-local", "local", sameMachine: true)); + tracker.Plan("eve:entity-soa:aetheria.daemon:pilot").Generation.Should().Be(1); tracker.Observe(Demand("unity-remote", "remote", sameMachine: false)); var mixed = tracker.Plan("eve:entity-soa:aetheria.daemon:pilot"); @@ -335,8 +337,14 @@ public sealed class CultMeshBodyTransportTests tracker.Observe(Demand("unity-remote", "remote", sameMachine: false, active: false)); var localOnly = tracker.Plan("eve:entity-soa:aetheria.daemon:pilot"); + localOnly.Generation.Should().Be(3); localOnly.RequiresSharedMemory.Should().BeTrue(); localOnly.RequiresNetwork.Should().BeFalse(); + + tracker.Observe(Demand("unity-local", "local", sameMachine: true, active: false)); + tracker.Plan("eve:entity-soa:aetheria.daemon:pilot").Generation.Should().Be(4); + tracker.Observe(Demand("unity-local", "local", sameMachine: true)); + tracker.Plan("eve:entity-soa:aetheria.daemon:pilot").Generation.Should().Be(5); } [Test] From 3cec5463b2c55d21f261cd68eeac56319d8e3e52 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 14:27:22 +0200 Subject: [PATCH 041/203] Fence body commits by demand generation --- src/GameCult.Mesh/CultMeshBodyDemand.cs | 20 +++++++++ .../CultMeshBodyTransportTests.cs | 42 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/GameCult.Mesh/CultMeshBodyDemand.cs b/src/GameCult.Mesh/CultMeshBodyDemand.cs index 9c9e17da..2ab90617 100644 --- a/src/GameCult.Mesh/CultMeshBodyDemand.cs +++ b/src/GameCult.Mesh/CultMeshBodyDemand.cs @@ -357,6 +357,26 @@ namespace GameCult.Mesh } } + /// + /// Executes one synchronous capability commit only while the exact routed consumer + /// generation remains current. Demand transitions serialize against the callback, so + /// either this work linearizes before the transition or it is rejected without running. + /// + public bool TryExecuteAtGeneration(string bodyId, long generation, Action action) + { + if (string.IsNullOrWhiteSpace(bodyId)) throw new ArgumentException("Body identity is required.", nameof(bodyId)); + if (generation < 0) throw new ArgumentOutOfRangeException(nameof(generation)); + if (action == null) throw new ArgumentNullException(nameof(action)); + lock (_gate) + { + ThrowIfDisposed(); + var current = _bodyGenerations.TryGetValue(bodyId, out var value) ? value : 0; + if (current != generation) return false; + action(); + return true; + } + } + private string RoutedConsumerSignature(string bodyId) => string.Join( "\u001e", _demands.Values diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs index 0421aea7..131bcf24 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using NUnit.Framework; @@ -347,6 +348,47 @@ public sealed class CultMeshBodyTransportTests tracker.Plan("eve:entity-soa:aetheria.daemon:pilot").Generation.Should().Be(5); } + [Test] + public async Task BodyDemand_GenerationGuardLinearizesCommitAgainstWithdrawal() + { + const string bodyId = "eve:entity-soa:aetheria.daemon:pilot"; + using var tracker = new CultMeshBodyDemandTracker(); + tracker.Observe(Demand("unity-local", "local", sameMachine: true)); + var activeGeneration = tracker.Plan(bodyId).Generation; + using var commitEntered = new ManualResetEventSlim(); + using var releaseCommit = new ManualResetEventSlim(); + var committed = false; + + var commit = Task.Run(() => tracker.TryExecuteAtGeneration( + bodyId, + activeGeneration, + () => + { + commitEntered.Set(); + releaseCommit.Wait(); + committed = true; + })); + commitEntered.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue(); + + var withdrawal = Task.Run(() => + tracker.Observe(Demand("unity-local", "local", sameMachine: true, active: false))); + await Task.Delay(50); + withdrawal.IsCompleted.Should().BeFalse( + "a withdrawal must serialize after an already-linearized synchronous commit"); + + releaseCommit.Set(); + (await commit).Should().BeTrue(); + await withdrawal; + committed.Should().BeTrue(); + tracker.Plan(bodyId).Generation.Should().BeGreaterThan(activeGeneration); + + var staleCallbackRan = false; + tracker.TryExecuteAtGeneration(bodyId, activeGeneration, () => staleCallbackRan = true) + .Should().BeFalse(); + staleCallbackRan.Should().BeFalse( + "work prepared for a withdrawn demand generation must be discarded before commit"); + } + [Test] public void BodyDemand_UnknownLocalityFailsTowardNetworkAndUnsupportedDemandCreatesNoRoute() { From 5b2c36329dbd74479c3ede5cdcec77c0d17ce534 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 17:37:22 +0200 Subject: [PATCH 042/203] Add CultGeometry isosurface extraction --- .../CultGeometryIsoSurface.cs | 193 ++++++++++++++++++ .../GameCult.Geometry.csproj | 4 +- .../ISOSURFACE-PROVENANCE.md | 32 +++ src/GameCult.Geometry/README.md | 23 ++- .../GeometryDocumentTests.cs | 7 +- .../IsoSurfaceTests.cs | 89 ++++++++ 6 files changed, 337 insertions(+), 11 deletions(-) create mode 100644 src/GameCult.Geometry/CultGeometryIsoSurface.cs create mode 100644 src/GameCult.Geometry/ISOSURFACE-PROVENANCE.md create mode 100644 tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs diff --git a/src/GameCult.Geometry/CultGeometryIsoSurface.cs b/src/GameCult.Geometry/CultGeometryIsoSurface.cs new file mode 100644 index 00000000..6fc9355c --- /dev/null +++ b/src/GameCult.Geometry/CultGeometryIsoSurface.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using CultMath; + +namespace GameCult.Geometry +{ + /// + /// Extracts engine-neutral triangle meshes from sampled scalar fields. + /// + public static class CultGeometryIsoSurface + { + private static readonly float3[] CubeCorners = + { + new(0f, 0f, 0f), + new(1f, 0f, 0f), + new(1f, 1f, 0f), + new(0f, 1f, 0f), + new(0f, 0f, 1f), + new(1f, 0f, 1f), + new(1f, 1f, 1f), + new(0f, 1f, 1f), + }; + + // A fixed six-tetrahedra decomposition keeps neighboring cube faces consistent. + private static readonly int[,] CubeTetrahedra = + { + { 0, 5, 1, 6 }, + { 0, 1, 2, 6 }, + { 0, 2, 3, 6 }, + { 0, 3, 7, 6 }, + { 0, 7, 4, 6 }, + { 0, 4, 5, 6 }, + }; + + /// + /// Extracts the surface with marching tetrahedra. + /// Values less than or equal to the isovalue are treated as inside. + /// + public static CultGeometryTriangleMesh Extract( + float[,,] samples, + float isoValue = 0f, + CultVec3 origin = default, + float cellSize = 1f) + { + if (samples == null) throw new ArgumentNullException(nameof(samples)); + if (samples.GetLength(0) < 2 || samples.GetLength(1) < 2 || samples.GetLength(2) < 2) + { + throw new ArgumentException("An isosurface field requires at least two samples on every axis.", nameof(samples)); + } + + if (!(cellSize > 0f) || float.IsInfinity(cellSize)) + { + throw new ArgumentOutOfRangeException(nameof(cellSize), "Cell size must be finite and positive."); + } + + var positions = new List(); + var normals = new List(); + var indices = new List(); + var originValue = new float3(origin.X, origin.Y, origin.Z); + var cornerPositions = new float3[8]; + var cornerValues = new float[8]; + var tetraPositions = new float3[4]; + var tetraValues = new float[4]; + + for (var x = 0; x < samples.GetLength(0) - 1; x++) + for (var y = 0; y < samples.GetLength(1) - 1; y++) + for (var z = 0; z < samples.GetLength(2) - 1; z++) + { + var cubeOrigin = originValue + new float3(x, y, z) * cellSize; + for (var corner = 0; corner < CubeCorners.Length; corner++) + { + var offset = CubeCorners[corner]; + cornerPositions[corner] = cubeOrigin + offset * cellSize; + cornerValues[corner] = samples[ + x + (int)offset.x, + y + (int)offset.y, + z + (int)offset.z]; + } + + for (var tetrahedron = 0; tetrahedron < CubeTetrahedra.GetLength(0); tetrahedron++) + { + for (var vertex = 0; vertex < 4; vertex++) + { + var corner = CubeTetrahedra[tetrahedron, vertex]; + tetraPositions[vertex] = cornerPositions[corner]; + tetraValues[vertex] = cornerValues[corner]; + } + + PolygonizeTetrahedron(tetraPositions, tetraValues, isoValue, positions, normals, indices); + } + } + + return new CultGeometryTriangleMesh + { + Positions = positions.ToArray(), + Normals = normals.ToArray(), + Indices = indices.ToArray(), + Uvs = Array.Empty(), + TriangleMaterials = new uint[indices.Count / 3], + }; + } + + private static void PolygonizeTetrahedron( + float3[] positions, + float[] values, + float isoValue, + List outputPositions, + List outputNormals, + List outputIndices) + { + Span inside = stackalloc int[4]; + Span outside = stackalloc int[4]; + var insideCount = 0; + var outsideCount = 0; + + for (var vertex = 0; vertex < 4; vertex++) + { + if (values[vertex] <= isoValue) inside[insideCount++] = vertex; + else outside[outsideCount++] = vertex; + } + + if (insideCount == 0 || insideCount == 4) return; + + var outward = Centroid(positions, outside, outsideCount) - Centroid(positions, inside, insideCount); + if (insideCount == 1 || outsideCount == 1) + { + var loneIsInside = insideCount == 1; + var lone = loneIsInside ? inside[0] : outside[0]; + var others = loneIsInside ? outside : inside; + var a = Interpolate(positions[lone], values[lone], positions[others[0]], values[others[0]], isoValue); + var b = Interpolate(positions[lone], values[lone], positions[others[1]], values[others[1]], isoValue); + var c = Interpolate(positions[lone], values[lone], positions[others[2]], values[others[2]], isoValue); + EmitOrientedTriangle(a, b, c, outward, outputPositions, outputNormals, outputIndices); + return; + } + + var p00 = Interpolate(positions[inside[0]], values[inside[0]], positions[outside[0]], values[outside[0]], isoValue); + var p01 = Interpolate(positions[inside[0]], values[inside[0]], positions[outside[1]], values[outside[1]], isoValue); + var p10 = Interpolate(positions[inside[1]], values[inside[1]], positions[outside[0]], values[outside[0]], isoValue); + var p11 = Interpolate(positions[inside[1]], values[inside[1]], positions[outside[1]], values[outside[1]], isoValue); + EmitOrientedTriangle(p00, p01, p11, outward, outputPositions, outputNormals, outputIndices); + EmitOrientedTriangle(p00, p11, p10, outward, outputPositions, outputNormals, outputIndices); + } + + private static float3 Interpolate(float3 first, float firstValue, float3 second, float secondValue, float isoValue) + { + var delta = secondValue - firstValue; + var amount = math.abs(delta) <= 1e-20f ? 0.5f : (isoValue - firstValue) / delta; + return math.lerp(first, second, amount); + } + + private static float3 Centroid(float3[] positions, Span vertices, int count) + { + var sum = float3.zero; + for (var index = 0; index < count; index++) sum += positions[vertices[index]]; + return sum / count; + } + + private static void EmitOrientedTriangle( + float3 a, + float3 b, + float3 c, + float3 outward, + List positions, + List normals, + List indices) + { + var normal = math.cross(b - a, c - a); + if (math.dot(normal, outward) < 0f) + { + (b, c) = (c, b); + normal = -normal; + } + + normal = math.normalize(normal); + var firstIndex = (uint)(positions.Count / 3); + Append(a, positions); + Append(b, positions); + Append(c, positions); + for (var vertex = 0; vertex < 3; vertex++) Append(normal, normals); + indices.Add(firstIndex); + indices.Add(firstIndex + 1); + indices.Add(firstIndex + 2); + } + + private static void Append(float3 value, List destination) + { + destination.Add(value.x); + destination.Add(value.y); + destination.Add(value.z); + } + } +} diff --git a/src/GameCult.Geometry/GameCult.Geometry.csproj b/src/GameCult.Geometry/GameCult.Geometry.csproj index a0192f69..a3ad9457 100644 --- a/src/GameCult.Geometry/GameCult.Geometry.csproj +++ b/src/GameCult.Geometry/GameCult.Geometry.csproj @@ -1,6 +1,6 @@ - Typed CultCache geometry domain and chunk artifact documents for VibeGeometry and Fensalir + CultGeometry algorithms and typed CultCache geometry artifacts GameCult.Geometry game;geometry;csg;lod;cultcache;cultmesh;vibegeometry;fensalir README.md @@ -8,6 +8,7 @@ + @@ -15,5 +16,6 @@ + diff --git a/src/GameCult.Geometry/ISOSURFACE-PROVENANCE.md b/src/GameCult.Geometry/ISOSURFACE-PROVENANCE.md new file mode 100644 index 00000000..a15a4933 --- /dev/null +++ b/src/GameCult.Geometry/ISOSURFACE-PROVENANCE.md @@ -0,0 +1,32 @@ +# Isosurface extraction provenance + +`CultGeometryIsoSurface` is a clean implementation of marching tetrahedra over +a regular scalar grid. The implementation follows the tetrahedral case shape +described by Peter Shirley and Allan Tuchman, “A Polygonal Approximation to +Direct Scalar Volume Rendering,” Computer Graphics 24(5), 1990, +doi:10.1145/99308.99322. + +The DELVE/HOLD port was behaviorally motivated by these unversioned local Unity +sources: + +- `D:\WIP4\Projects\VoxelTerrain\Assets\Scripts\Procedural\GenerateTerrain.cs` +- `D:\WIP4\Projects\VoxelTerrain\Assets\Plugins\MarchingCubes.cs` + +The source directory is not a Git checkout and the plugin carries no license or +author notice. Its code and lookup tables were therefore not copied. The +CultGeometry implementation uses a fresh six-tetrahedra decomposition, explicit +inside/outside classification, interpolation, winding, and flat-normal output. + +Deliberate behavior retained: + +- scalar samples less than or equal to the isovalue are inside; +- intersections are linearly interpolated along cell edges; +- vertices are emitted per triangle rather than welded; +- output is engine-neutral positions, normals, indices, and material slots. + +Deliberate changes: + +- no Unity mesh, physics, coroutine, navigation, or random dependency; +- no mutable global target, mode, or winding state; +- outward winding is derived from inside-to-outside direction; +- invalid fields and cell sizes fail at the public boundary. diff --git a/src/GameCult.Geometry/README.md b/src/GameCult.Geometry/README.md index 200f14ed..1ea60768 100644 --- a/src/GameCult.Geometry/README.md +++ b/src/GameCult.Geometry/README.md @@ -1,8 +1,17 @@ # GameCult.Geometry -`GameCult.Geometry` defines shared geometry values plus CultCache-native -documents for procedural geometry domains, LOD build requests, selected-cut -manifests, and mesh chunk artifacts. +The project is known as **CultGeometry**. `GameCult.Geometry` is its clean .NET +package and namespace identity. + +`GameCult.Geometry` owns reusable procedural geometry algorithms together with +the CultCache-native documents that carry their domains, build requests, +selected cuts, and mesh chunk artifacts. + +Its first engine-neutral algorithm is `CultGeometryIsoSurface.Extract`, a +marching-tetrahedra extractor over regular scalar grids. It uses CultMath for +numeric primitives and emits `CultGeometryTriangleMesh`; Godot, Unity, native +workers, and other consumers adapt that neutral result into their own runtime +objects. See `ISOSURFACE-PROVENANCE.md` for the clean-port boundary. The package is deliberately substrate-shaped: @@ -33,10 +42,10 @@ runtime equivalents instead of bespoke `{ minX, minY, maxX, maxY }` payloads. ## Runtime Path -`vg-csg` emits domain documents, selected-cut manifests, and chunk artifacts as -MessagePack CultCache documents. A Unity runtime, local worker, or remote -geometry process should receive the same records through CultMesh/CultNet and -read them back as `CultGeometry*` types. +CultGeometry CSG (`GameCult.Geometry.Csg`) emits domain documents, selected-cut +manifests, and chunk artifacts as MessagePack CultCache documents. A Godot +runtime, local worker, or remote geometry process receives the same records +through CultMesh/CultNet and reads them back as `CultGeometry*` types. ```csharp using GameCult.Caching; diff --git a/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs index b39b5182..16e66b8e 100644 --- a/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs +++ b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs @@ -192,7 +192,7 @@ namespace GameCult.Geometry.Tests chunk, new CultNetDocumentMessageOptions { - SourceRuntimeId = "vg-csg", + SourceRuntimeId = "gamecult.geometry.csg", SourceRole = "geometry-worker", Tags = ["ragnarok", "chunk"] }); @@ -282,7 +282,7 @@ namespace GameCult.Geometry.Tests StartServer = false, DatabaseOptions = new CultNetDatabaseOptions { - RuntimeId = "unity-runtime", + RuntimeId = "godot-runtime", DocumentRegistry = registry } })) @@ -314,7 +314,7 @@ namespace GameCult.Geometry.Tests { DomainId = "ragnarok-column", RootKey = "ragnarok-column", - SourceRuntime = "vg-csg", + SourceRuntime = "gamecult.geometry.csg", CreatedAt = "2026-05-29T00:00:00.0000000Z", Root = new CultGeometryDomainNode { @@ -359,6 +359,7 @@ namespace GameCult.Geometry.Tests return Path.Combine( TestContext.CurrentContext.TestDirectory, "Fixtures", + // Immutable interoperability fixtures emitted before the CSG package rename. "vg-csg-ragnarok", fileName); } diff --git a/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs b/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs new file mode 100644 index 00000000..90d5f7f6 --- /dev/null +++ b/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs @@ -0,0 +1,89 @@ +using System; +using FluentAssertions; +using NUnit.Framework; + +namespace GameCult.Geometry.Tests +{ + [TestFixture] + public sealed class IsoSurfaceTests + { + [TestCase(-1f)] + [TestCase(1f)] + public void Uniform_field_has_no_surface(float value) + { + var samples = Filled(2, value); + + var mesh = CultGeometryIsoSurface.Extract(samples); + + mesh.Positions.Should().BeEmpty(); + mesh.Normals.Should().BeEmpty(); + mesh.Indices.Should().BeEmpty(); + } + + [Test] + public void Plane_field_interpolates_to_the_isovalue_and_faces_outward() + { + var samples = new float[2, 2, 2]; + for (var y = 0; y < 2; y++) + for (var z = 0; z < 2; z++) + { + samples[0, y, z] = -0.5f; + samples[1, y, z] = 0.5f; + } + + var mesh = CultGeometryIsoSurface.Extract(samples); + + mesh.TriangleCount.Should().BeGreaterThan(0); + for (var index = 0; index < mesh.Positions.Length; index += 3) + { + mesh.Positions[index].Should().BeApproximately(0.5f, 1e-6f); + } + + for (var index = 0; index < mesh.Normals.Length; index += 3) + { + mesh.Normals[index].Should().BeApproximately(1f, 1e-6f); + mesh.Normals[index + 1].Should().BeApproximately(0f, 1e-6f); + mesh.Normals[index + 2].Should().BeApproximately(0f, 1e-6f); + } + } + + [Test] + public void Extraction_is_deterministic_and_applies_origin_and_cell_size() + { + var samples = new float[2, 2, 2]; + samples[0, 0, 0] = -1f; + samples[1, 0, 0] = 1f; + samples[0, 1, 0] = 1f; + samples[1, 1, 0] = 1f; + samples[0, 0, 1] = 1f; + samples[1, 0, 1] = 1f; + samples[0, 1, 1] = 1f; + samples[1, 1, 1] = 1f; + + var first = CultGeometryIsoSurface.Extract(samples, origin: new CultVec3(10f, 20f, 30f), cellSize: 2f); + var second = CultGeometryIsoSurface.Extract(samples, origin: new CultVec3(10f, 20f, 30f), cellSize: 2f); + + second.Positions.Should().Equal(first.Positions); + second.Normals.Should().Equal(first.Normals); + second.Indices.Should().Equal(first.Indices); + first.Positions.Should().OnlyContain(value => value >= 10f); + } + + [Test] + public void Invalid_field_shape_is_rejected() + { + Action act = () => CultGeometryIsoSurface.Extract(new float[1, 2, 2]); + + act.Should().Throw(); + } + + private static float[,,] Filled(int size, float value) + { + var samples = new float[size, size, size]; + for (var x = 0; x < size; x++) + for (var y = 0; y < size; y++) + for (var z = 0; z < size; z++) samples[x, y, z] = value; + return samples; + } + } +} From 63d6094c9412808730ef5d8043a096c655ae901a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 17:47:06 +0200 Subject: [PATCH 043/203] Clarify CultMath and CultGeometry ownership --- packages/cultmath/README.md | 16 ++++++++-------- src/GameCult.Geometry/README.md | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/cultmath/README.md b/packages/cultmath/README.md index edd24d37..8fd3888c 100644 --- a/packages/cultmath/README.md +++ b/packages/cultmath/README.md @@ -83,7 +83,7 @@ cargo build --release --manifest-path packages/cultmath/native/cultmath-core/Car ## Unity package CultMath's Unity 2021.3 surface is the tracked precompiled package at -`unity/org.gamecult.cultmath`. Unity consumes `CultMath.dll` and the numeric +`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. @@ -92,22 +92,22 @@ tree and does not require a consumer `csc.rsp` workaround. ``` 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 +`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. ## Shader Tooling -CultMath keeps portable DXC outside git under `.tools/`: +CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`: ```powershell -.\tools\get-dxc.ps1 -.\tools\get-spirv-cross.ps1 -.\tools\compile-hlsl-spirv.ps1 ` +.\packages\cultmath\tools\get-dxc.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\CultMath\shaders -.\tools\compile-spirv-metal.ps1 ` + -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 ``` diff --git a/src/GameCult.Geometry/README.md b/src/GameCult.Geometry/README.md index 1ea60768..2d12dc25 100644 --- a/src/GameCult.Geometry/README.md +++ b/src/GameCult.Geometry/README.md @@ -7,6 +7,10 @@ package and namespace identity. the CultCache-native documents that carry their domains, build requests, selected cuts, and mesh chunk artifacts. +It is the schema authority for the shared `CultGeometry*` records. +`GameCult.Geometry.Csg` owns CSG production behavior and proves wire parity +against those records; it does not independently evolve their identities. + Its first engine-neutral algorithm is `CultGeometryIsoSurface.Extract`, a marching-tetrahedra extractor over regular scalar grids. It uses CultMath for numeric primitives and emits `CultGeometryTriangleMesh`; Godot, Unity, native @@ -63,7 +67,7 @@ using var node = await CultMesh.CreateNodeAsync("ragnarok-geometry.ccmp", new Cu { DatabaseOptions = new CultNetDatabaseOptions { - RuntimeId = "unity-runtime", + RuntimeId = "godot-runtime", DocumentRegistry = geometryDocuments } }); From b672fb7a37a07ef499a644fcc1dbd7b7fcc538ab Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 22:44:32 +0200 Subject: [PATCH 044/203] Return the Rust CultCache engine to CultLib packages/cultcache-rs forked from GameCult/cultcache-rs at the 5c69bfc monorepo import and the two lineages then diverged in both directions. The standalone repo took 28 commits of engine work CultLib never saw: typed SoA projection, atomic heterogeneous batch publication, single-entry and conditional batch CAS, a transactional keyed redb backend with serialized cross-process opens, lockless provider snapshot reads, RAII read-only shared snapshots, snapshot-fenced replace/append, crash-atomic single-file snapshots, compaction fences, portable and canonical byte fields, and unified retained storage authority. That body is 3,714 lines with 48 tests against CultLib's 1,554 with 18. CultLib is the canonical home because wire parity conformance lives here and nothing outside it is tested for cross-runtime agreement. A runtime-specific core-stack repo cannot hold the engine every other runtime must agree with. The standalone body lands as the base rather than merging 28 commits backward. It is already proven against a CultLib-derived cultnet-rs: Ghostlight vendors exactly this pairing and compiles. Here, cultcache-rs passes 46 tests and the cultnet-rs RUDP gate passes 20 with no source change. Retained debt, not carried forward: a0b31b8 recovered schema-stamped records missing catalog entries (SchemaStamped, infer_schema_name, infer_schema_version_from_payload) and 743eaa5 hardened snapshot replacement (SnapshotReplacementStage, CommittedSnapshotDurabilityUncertain, stale candidate cleanup, with_exclusive_lock_using_unlock). Their tests were white-box against internals this body does not have. The standalone independently made single-file snapshots crash atomic and cleaned abandoned staging files, so the durability guarantee is probably held under other names -- probably is not proven. Re-establish both as black-box tests against the new API before the standalone repo is retired. Co-Authored-By: Claude Opus 5 --- packages/cultcache-rs/Cargo.toml | 7 +- packages/cultcache-rs/README.md | 78 +- .../cultcache-rs-derive/src/lib.rs | 51 +- packages/cultcache-rs/src/lib.rs | 3538 +++++++++++++---- 4 files changed, 2935 insertions(+), 739 deletions(-) diff --git a/packages/cultcache-rs/Cargo.toml b/packages/cultcache-rs/Cargo.toml index ef06727d..e6ec5e59 100644 --- a/packages/cultcache-rs/Cargo.toml +++ b/packages/cultcache-rs/Cargo.toml @@ -17,14 +17,15 @@ chrono = { version = "0.4", features = ["serde"] } cultcache-rs-derive = { path = "cultcache-rs-derive" } fs2 = "0.4" rmp-serde = "1.3" +redb = "4.1.0" serde = { version = "1", features = ["derive"] } serde_bytes = "0.11" serde_json = "1" uuid = { version = "1", features = ["v4"] } -[target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59", features = ["Win32_Storage_FileSystem"] } - [dev-dependencies] pretty_assertions = "1.4" tempfile = "3" + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Storage_FileSystem"] } diff --git a/packages/cultcache-rs/README.md b/packages/cultcache-rs/README.md index 74b20dd3..0176f474 100644 --- a/packages/cultcache-rs/README.md +++ b/packages/cultcache-rs/README.md @@ -53,7 +53,7 @@ struct Settings { let mut cache = CultCache::new(); cache.register_entry_type::()?; -cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new("cache.msgpack")); +cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new("cache.cc")); cache.pull_all_backing_stores()?; cache.put("app", &Settings { @@ -136,7 +136,7 @@ pub struct PlayerData { let mut cache = CultCache::builder() .with_generated_entries(game_entries()) - .with_generic_store(SingleFileMessagePackBackingStore::new("cache.msgpack")) + .with_generic_store(SingleFileMessagePackBackingStore::new("cache.cc")) .build()?; cache.pull_all_backing_stores()?; @@ -173,26 +173,59 @@ without pretending Rust has runtime reflection hiding under the floorboards. - `get_envelope::` - `get_required_envelope::` - `get_all::` +- `get_all_with_keys::` +- `soa::` - `put::` - `put_envelope::` - `update::` - `delete::` - `snapshot` +- `snapshot_soa` +- `load_soa` - `SingleFileMessagePackBackingStore` +## Structure Of Arrays + +The C# CultCache hot path exposes CPU-local SoA scans as: + +```csharp +Span x = cache.Soa() + .Column(nameof(PlayerTransform.PositionX)) + .Span; +``` + +Rust cannot discover those columns through runtime reflection, so each domain +type declares its column extractors explicitly with `SoaDocument` or the +`cultcache_soa!` helper: + +```rust +cultcache_soa!(PlayerTransform { + "position_x" => |row: &PlayerTransform| row.position_x, + "health" => |row: &PlayerTransform| row.health, +}); + +let table = cache.soa::()?; +let health = table.column::("health")?; +``` + +This is the same public shape as the C# rite: callers ask the cache for a typed +table, then pull contiguous typed columns by name. The Rust version is explicit +about the extractor table because the closed-world schema boundary is a virtue, +not a missing reflection party trick. + ## Backing Store Routing Generic store: ```rust -cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new("cache.msgpack")); +cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new("cache.cc")); ``` Type-specific store: ```rust cache.add_backing_store( - SingleFileMessagePackBackingStore::new("players.msgpack"), + SingleFileMessagePackBackingStore::new("players.cc"), ["player"], ); ``` @@ -250,38 +283,8 @@ marked `default` use `Default::default()` when older payloads are read. Deleted field slots should stay reserved until an explicit store migration rewrites the data. -The single-file MessagePack store rewrites one complete snapshot. Under the -exclusive sidecar lock it removes abandoned candidates without following them, -creates a unique same-directory candidate with create-new semantics, writes and -flushes it, and then replaces the committed pathname without deleting that -pathname first. A process interruption before replacement leaves the previous -snapshot authoritative. Once replacement succeeds, readers observe the complete -new snapshot rather than a partial file. - -Unix requests a parent-directory flush after replacement. Windows uses -`MoveFileExW` with replace-existing and write-through flags. If the final Unix -directory flush fails, the method returns a downcastable -`CommittedSnapshotDurabilityUncertain`: the complete new snapshot may already be -visible, so the caller must fail-stop or reopen and reconcile before issuing -higher-level work again. Treating that error as "not committed" and blindly -retrying can duplicate commands or receipts. - -Abandoned `..tmp` candidates are never read as committed state and are -removed by the next exclusive writer. Cleanup recognizes only the exact -canonical UUID form emitted for that target, including targets whose native -filename is not UTF-8; similar `.tmp` names remain untouched. Candidate creation -uses create-new semantics, so an unexpected file or symlink collision fails -instead of being opened or truncated. The canonical candidate filename namespace -is reserved to the store. - -These guarantees assume a local filesystem that honors the platform rename and -flush primitives, and writers that honor the sidecar lock. Network filesystems, -faulty storage hardware, and unrelated writers may provide weaker behavior. -Fault-injection tests prove the process-visible old-or-new outcomes around the -replacement boundary; they do not simulate sudden power loss or prove that a -device honored a successful flush. - -This is a sane starting point for small typed state surfaces, settings, Epiphany agent memory, +The single-file MessagePack store rewrites an atomic snapshot. That is a sane +starting point for small typed state surfaces, settings, Epiphany agent memory, heartbeat state, and other compact control-plane data. Large corpora should use a sharded store or a real database instead of asking one file to become a warehouse and then acting wounded when physics invoices us. @@ -290,9 +293,6 @@ The single-file store uses a sidecar lock file for shared reads and exclusive writes. That protects ordinary multi-process access to the same file. It is still not a multi-master replication protocol, and it is not a substitute for a coordinator when higher-level write ordering matters. -An explicit unlock error cannot replace the write outcome: dropping the lock-file -handle is the final OS lock-release path, so a committed-durability-uncertain -result remains inspectable rather than being masked by cleanup noise. ## Near-Term Ergonomic Improvements diff --git a/packages/cultcache-rs/cultcache-rs-derive/src/lib.rs b/packages/cultcache-rs/cultcache-rs-derive/src/lib.rs index f05a23f3..7d613194 100644 --- a/packages/cultcache-rs/cultcache-rs-derive/src/lib.rs +++ b/packages/cultcache-rs/cultcache-rs-derive/src/lib.rs @@ -75,6 +75,7 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { }; let mut key: Option = None; let mut default = false; + let mut bytes = false; for attribute in field .attrs .iter() @@ -88,6 +89,9 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { } else if meta.path.is_ident("default") { default = true; Ok(()) + } else if meta.path.is_ident("bytes") { + bytes = true; + Ok(()) } else { Err(meta.error("unsupported field cultcache attribute")) } @@ -117,6 +121,7 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { ident: field_ident, ty: field.ty, default, + bytes, }); } slots.sort_by_key(|slot| slot.key); @@ -125,8 +130,14 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { let serialize_elements = (0..=max_slot).map(|slot| { if let Some(field) = slots.iter().find(|field| field.key == slot) { let field_ident = &field.ident; - quote! { - tuple.serialize_element(&self.#field_ident)?; + if field.bytes { + quote! { + tuple.serialize_element(::serde_bytes::Bytes::new(&self.#field_ident))?; + } + } else { + quote! { + tuple.serialize_element(&self.#field_ident)?; + } } } else { quote! { @@ -138,8 +149,14 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { let option_declarations = slots.iter().map(|field| { let variable = format_ident!("slot_{}", field.key); let ty = &field.ty; - quote! { - let mut #variable: ::std::option::Option<#ty> = ::std::option::Option::None; + if field.bytes { + quote! { + let mut #variable: ::std::option::Option<::serde_bytes::ByteBuf> = ::std::option::Option::None; + } + } else { + quote! { + let mut #variable: ::std::option::Option<#ty> = ::std::option::Option::None; + } } }); @@ -147,9 +164,17 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { if let Some(field) = slots.iter().find(|field| field.key == slot) { let variable = format_ident!("slot_{}", field.key); let ty = &field.ty; - quote! { - #slot => { - #variable = seq.next_element::<#ty>()?; + if field.bytes { + quote! { + #slot => { + #variable = seq.next_element::<::serde_bytes::ByteBuf>()?; + } + } + } else { + quote! { + #slot => { + #variable = seq.next_element::<#ty>()?; + } } } } else { @@ -164,7 +189,16 @@ pub fn derive_database_entry(input: TokenStream) -> TokenStream { let build_fields = slots.iter().map(|field| { let field_ident = &field.ident; let variable = format_ident!("slot_{}", field.key); - if field.default { + if field.bytes && field.default { + quote! { + #field_ident: #variable.unwrap_or_default().into_vec() + } + } else if field.bytes { + let field_name = field_ident.to_string(); + quote! { + #field_ident: #variable.ok_or_else(|| ::serde::de::Error::missing_field(#field_name))?.into_vec() + } + } else if field.default { quote! { #field_ident: #variable.unwrap_or_default() } @@ -240,4 +274,5 @@ struct CultCacheSlot { ident: syn::Ident, ty: Type, default: bool, + bytes: bool, } diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index b85b763a..b8d6d8f9 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -1,18 +1,22 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; +use anyhow::bail; +use anyhow::ensure; use serde::Serialize; use serde::de::DeserializeOwned; +use std::any::Any; use std::collections::BTreeMap; use std::collections::BTreeSet; -use std::ffi::OsStr; -use std::fmt; use std::fs; use std::fs::File; use std::fs::OpenOptions; -use std::io::Write; +use std::marker::PhantomData; use std::path::Path; use std::path::PathBuf; +use std::sync::Arc; + +use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition}; extern crate self as cultcache_rs; @@ -27,6 +31,83 @@ pub trait CultCacheRegistry { fn register_entries(&self, cache: &mut CultCache) -> Result<()>; } +pub trait SoaDocument: DatabaseEntry { + fn soa_columns(rows: &[Self]) -> BTreeMap<&'static str, CultSoaColumnValues>; +} + +pub struct CultSoaColumnValues { + type_name: &'static str, + values: Box, +} + +impl CultSoaColumnValues { + pub fn new(values: Vec) -> Self { + Self { + type_name: std::any::type_name::(), + values: Box::new(values), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CultSoaColumn { + name: String, + values: Vec, +} + +impl CultSoaColumn { + pub fn name(&self) -> &str { + &self.name + } + + pub fn values(&self) -> &[TValue] { + &self.values + } +} + +pub struct CultSoaTable { + keys: Vec, + documents: Vec, + columns: BTreeMap, + _document: PhantomData, +} + +impl CultSoaTable { + pub fn keys(&self) -> &[String] { + &self.keys + } + + pub fn documents(&self) -> &[TDocument] { + &self.documents + } + + pub fn len(&self) -> usize { + self.keys.len() + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() + } + + pub fn column(&self, name: &str) -> Result> { + let Some(column) = self.columns.get(name) else { + return Err(anyhow!("SoA table has no column {name:?}")); + }; + let Some(values) = column.values.downcast_ref::>() else { + return Err(anyhow!( + "SoA table column {:?} stores {}, not {}", + name, + column.type_name, + std::any::type_name::() + )); + }; + Ok(CultSoaColumn { + name: name.to_string(), + values: values.clone(), + }) + } +} + #[macro_export] macro_rules! cultcache_registry { ($name:ident { $($entry:ty),* $(,)? }) => { @@ -44,6 +125,22 @@ macro_rules! cultcache_registry { }; } +#[macro_export] +macro_rules! cultcache_soa { + ($entry:ty { $($column:literal => $extractor:expr),* $(,)? }) => { + impl $crate::SoaDocument for $entry { + fn soa_columns(rows: &[Self]) -> ::std::collections::BTreeMap<&'static str, $crate::CultSoaColumnValues> { + let mut columns = ::std::collections::BTreeMap::new(); + $( + let values = rows.iter().map($extractor).collect::>(); + columns.insert($column, $crate::CultSoaColumnValues::new(values)); + )* + columns + } + } + }; +} + #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct CultCacheEnvelope { @@ -57,6 +154,93 @@ pub struct CultCacheEnvelope { pub schema_id: Option, } +#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CultCacheSoaTable { + pub keys: Vec, + pub types: Vec, + pub payloads: Vec>, + pub stored_ats: Vec, + pub schema_ids: Vec>, +} + +impl CultCacheSoaTable { + pub fn from_envelopes(entries: impl IntoIterator) -> Self { + let mut table = Self::default(); + for entry in entries { + table.keys.push(entry.key); + table.types.push(entry.r#type); + table.payloads.push(entry.payload); + table.stored_ats.push(entry.stored_at); + table.schema_ids.push(entry.schema_id); + } + table + } + + pub fn len(&self) -> usize { + self.keys.len() + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() + } + + pub fn validate(&self) -> Result<()> { + let len = self.keys.len(); + for (name, candidate) in [ + ("types", self.types.len()), + ("payloads", self.payloads.len()), + ("storedAts", self.stored_ats.len()), + ("schemaIds", self.schema_ids.len()), + ] { + if candidate != len { + return Err(anyhow!( + "CultCache SoA column {name} has length {candidate}, expected {len}" + )); + } + } + for (index, key) in self.keys.iter().enumerate() { + if key.trim().is_empty() { + return Err(anyhow!("CultCache SoA row {index} has an empty key")); + } + if self.types[index].trim().is_empty() { + return Err(anyhow!("CultCache SoA row {index} has an empty type")); + } + if self.stored_ats[index].trim().is_empty() { + return Err(anyhow!("CultCache SoA row {index} has an empty stored_at")); + } + } + Ok(()) + } + + pub fn into_envelopes(self) -> Result> { + self.validate()?; + let Self { + keys, + types, + payloads, + stored_ats, + schema_ids, + } = self; + Ok(keys + .into_iter() + .zip(types) + .zip(payloads) + .zip(stored_ats) + .zip(schema_ids) + .map( + |((((key, r#type), payload), stored_at), schema_id)| CultCacheEnvelope { + key, + r#type, + payload, + stored_at, + schema_id, + }, + ) + .collect()) + } +} + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] struct PersistedStoreSnapshot( String, @@ -100,45 +284,6 @@ pub struct PushAllOptions { pub soft: bool, } -/// A complete new snapshot replaced the committed pathname, but its final -/// durability barrier failed. Callers must fail-stop or reopen and reconcile; -/// retrying as though the write never committed can duplicate higher-level work. -#[derive(Debug)] -pub struct CommittedSnapshotDurabilityUncertain { - path: PathBuf, - detail: String, -} - -impl CommittedSnapshotDurabilityUncertain { - fn new(path: PathBuf, error: impl fmt::Display) -> Self { - Self { - path, - detail: error.to_string(), - } - } - - pub fn path(&self) -> &Path { - &self.path - } - - pub fn detail(&self) -> &str { - &self.detail - } -} - -impl fmt::Display for CommittedSnapshotDurabilityUncertain { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - formatter, - "snapshot {} was committed but its durability is uncertain: {}", - self.path.display(), - self.detail - ) - } -} - -impl std::error::Error for CommittedSnapshotDurabilityUncertain {} - pub trait CacheBackingStore: Send { fn pull_all(&self) -> Result>; fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()>; @@ -170,6 +315,305 @@ impl SingleFileMessagePackBackingStore { &self.path } + /// Holds the pre-created sibling lock shared while a read-only consumer + /// evaluates and acts on one snapshot. The reader never creates or writes + /// either file. Writers using the sibling lock cannot replace the snapshot + /// until `action` returns. + /// + /// Releasing the lock is RAII cleanup. A release failure therefore cannot + /// rewrite the completed action's result; consumers that need to observe + /// that cleanup failure can use + /// [`Self::with_read_only_shared_snapshot_and_unlock_diagnostic`]. + pub fn with_read_only_shared_snapshot( + &self, + action: impl FnOnce(Vec) -> Result, + ) -> Result { + self.with_read_only_shared_snapshot_and_unlock_diagnostic(action, |_| {}) + } + + /// The diagnostic-bearing form of + /// [`Self::with_read_only_shared_snapshot`]. `on_unlock_failure` observes a + /// cleanup failure separately and never changes the action result. + pub fn with_read_only_shared_snapshot_and_unlock_diagnostic( + &self, + action: impl FnOnce(Vec) -> Result, + on_unlock_failure: impl FnOnce(anyhow::Error), + ) -> Result { + self.with_read_only_shared_snapshot_using( + action, + |lock| { + fs2::FileExt::unlock(lock) + .with_context(|| format!("failed to unlock {}", self.lock_path().display())) + }, + on_unlock_failure, + ) + } + + fn with_read_only_shared_snapshot_using( + &self, + action: impl FnOnce(Vec) -> Result, + unlock: U, + on_unlock_failure: D, + ) -> Result + where + U: FnOnce(&File) -> Result<()>, + D: FnOnce(anyhow::Error), + { + let lock_path = self.lock_path(); + let lock = OpenOptions::new() + .read(true) + .open(&lock_path) + .with_context(|| format!("failed to open pre-created {}", lock_path.display()))?; + fs2::FileExt::lock_shared(&lock) + .with_context(|| format!("failed to lock {}", lock_path.display()))?; + let guard = ReadOnlySharedLockGuard::new(lock, unlock, on_unlock_failure); + let result = self.read_all_unlocked().and_then(action); + drop(guard); + result + } + + /// Replaces one exact envelope under the store's cross-process exclusive + /// lock. A changed or missing expected envelope returns false without a write. + pub fn compare_and_swap_entry( + &self, + expected: &CultCacheEnvelope, + replacement: CultCacheEnvelope, + ) -> Result { + self.with_exclusive_lock(|| { + let mut entries = self.read_all_unlocked()?; + let Some(index) = entries + .iter() + .position(|candidate| entry_id(candidate) == entry_id(expected)) + else { + return Ok(false); + }; + if entries[index] != *expected { + return Ok(false); + } + entries[index] = replacement; + entries.sort_by_key(entry_id); + self.write_all_unlocked(&entries)?; + Ok(true) + }) + } + + /// Inserts one envelope only when its polymorphic type/key identity is + /// absent, under the same cross-process lock used by all single-file writes. + pub fn insert_entry_if_absent(&self, entry: CultCacheEnvelope) -> Result { + self.with_exclusive_lock(|| { + let mut entries = self.read_all_unlocked()?; + if entries + .iter() + .any(|candidate| entry_id(candidate) == entry_id(&entry)) + { + return Ok(false); + } + entries.push(entry); + entries.sort_by_key(entry_id); + self.write_all_unlocked(&entries)?; + Ok(true) + }) + } + + /// Atomically replaces an exact set of existing envelopes and inserts any + /// additional companion envelopes. Every precondition is checked beneath + /// the same cross-process lock as the single snapshot write. + pub fn compare_and_swap_batch( + &self, + expected: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional batch requires a non-empty replacement set" + )); + } + let expected_ids = unique_batch_ids(expected, "expected")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + if !expected_ids.is_subset(&replacement_ids) { + return Err(anyhow!( + "conditional batch must replace every expected identity" + )); + } + + self.with_exclusive_lock(|| { + let mut entries = self.read_all_unlocked()?; + for expected_entry in expected { + let Some(current) = entries + .iter() + .find(|candidate| entry_id(candidate) == entry_id(expected_entry)) + else { + return Ok(false); + }; + if current != expected_entry { + return Ok(false); + } + } + for replacement in &replacements { + let identity = entry_id(replacement); + if !expected_ids.contains(&identity) + && entries + .iter() + .any(|candidate| entry_id(candidate) == identity) + { + return Ok(false); + } + } + + entries.retain(|candidate| !expected_ids.contains(&entry_id(candidate))); + entries.extend(replacements); + entries.sort_by_key(entry_id); + self.write_all_unlocked(&entries)?; + Ok(true) + }) + } + + /// Atomically appends companions only when the complete backing-store + /// snapshot is byte-for-byte unchanged. Unlike identity-scoped CAS, this + /// fences predicate cardinality: a concurrently inserted unknown identity + /// makes the expected snapshot stale. + pub fn append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + additions: Vec, + ) -> Result { + if additions.is_empty() { + return Err(anyhow!("conditional snapshot append requires additions")); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + let addition_ids = unique_batch_ids(&additions, "snapshot additions")?; + self.with_exclusive_lock(|| { + let mut current = self.read_all_unlocked()?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + if current != expected { + return Ok(false); + } + if current + .iter() + .any(|row| addition_ids.contains(&entry_id(row))) + { + return Ok(false); + } + current.extend(additions); + current.sort_by_key(entry_id); + self.write_all_unlocked(¤t)?; + Ok(true) + }) + } + + /// Atomically deletes an exact set of envelopes. Any changed or missing + /// member refuses the entire deletion without a partial write. + pub fn delete_batch_if_unchanged(&self, expected: &[CultCacheEnvelope]) -> Result { + if expected.is_empty() { + return Err(anyhow!( + "conditional delete requires a non-empty expected set" + )); + } + let expected_ids = unique_batch_ids(expected, "delete expected")?; + self.with_exclusive_lock(|| { + let mut entries = self.read_all_unlocked()?; + for expected_entry in expected { + let Some(current) = entries + .iter() + .find(|candidate| entry_id(candidate) == entry_id(expected_entry)) + else { + return Ok(false); + }; + if current != expected_entry { + return Ok(false); + } + } + entries.retain(|candidate| !expected_ids.contains(&entry_id(candidate))); + self.write_all_unlocked(&entries)?; + Ok(true) + }) + } + + /// Atomically replaces existing rows and appends companion rows only while + /// the complete backing-store snapshot remains byte-exact. + pub fn replace_and_append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional snapshot replacement requires replacements" + )); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + let replacement_ids = unique_batch_ids(&replacements, "snapshot replacements")?; + self.with_exclusive_lock(|| { + let mut current = self.read_all_unlocked()?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + if current != expected { + return Ok(false); + } + current.retain(|row| !replacement_ids.contains(&entry_id(row))); + current.extend(replacements); + current.sort_by_key(entry_id); + self.write_all_unlocked(¤t)?; + Ok(true) + }) + } + + /// Atomically replaces and deletes rows only while the complete store + /// snapshot remains byte-exact. This is the single-file compaction + /// primitive: summary publication and history deletion share one write. + pub fn replace_and_delete_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + deletions: &[CultCacheEnvelope], + ) -> Result { + if replacements.is_empty() || deletions.is_empty() { + return Err(anyhow!( + "conditional compaction requires replacements and deletions" + )); + } + let expected_ids = unique_batch_ids(expected_snapshot, "expected snapshot")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + let deletion_ids = unique_batch_ids(deletions, "deletion")?; + if !deletion_ids.is_subset(&expected_ids) { + return Err(anyhow!( + "compaction deletion is absent from expected snapshot" + )); + } + if !deletion_ids.is_disjoint(&replacement_ids) { + return Err(anyhow!("compaction cannot replace and delete one identity")); + } + self.with_exclusive_lock(|| { + let mut current = self.read_all_unlocked()?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + if current != expected { + return Ok(false); + } + for row in &replacements { + if !expected_ids.contains(&entry_id(row)) + && current + .iter() + .any(|candidate| entry_id(candidate) == entry_id(row)) + { + return Ok(false); + } + } + current.retain(|row| !deletion_ids.contains(&entry_id(row))); + for row in replacements { + current.retain(|candidate| entry_id(candidate) != entry_id(&row)); + current.push(row); + } + current.sort_by_key(entry_id); + self.write_all_unlocked(¤t)?; + Ok(true) + }) + } + fn read_all_unlocked(&self) -> Result> { if !self.path.exists() { return Ok(Vec::new()); @@ -179,57 +623,47 @@ impl SingleFileMessagePackBackingStore { if bytes.is_empty() { return Ok(Vec::new()); } - decode_store_snapshot(&bytes).or_else(|_| { - rmp_serde::from_slice(&bytes) - .with_context(|| format!("failed to decode MessagePack {}", self.path.display())) - }) + decode_store_snapshot(&bytes) + .or_else(|_| rmp_serde::from_slice(&bytes)) + .with_context(|| format!("failed to decode MessagePack {}", self.path.display())) + } + + /// Reads one filesystem snapshot without creating or opening the sibling + /// CultCache lock file. + /// + /// This is deliberately narrower than `pull_all`: use it only at a + /// provider-owned, read-only crossing where the producer replaces the + /// complete file atomically and the consumer has no authority to mutate + /// the provider directory. An absent file is an empty snapshot. Writers + /// and locally owned stores must continue to use the locked APIs. + pub fn pull_all_read_only_snapshot(&self) -> Result> { + self.read_all_unlocked() } fn write_all_unlocked(&self, entries: &[CultCacheEnvelope]) -> Result<()> { - self.write_all_unlocked_with_observer(entries, |_| Ok(())) - } - - fn write_all_unlocked_with_observer( - &self, - entries: &[CultCacheEnvelope], - mut observe: impl FnMut(SnapshotReplacementStage) -> Result<()>, - ) -> Result<()> { if let Some(parent) = self.path.parent() { fs::create_dir_all(parent) .with_context(|| format!("failed to create {}", parent.display()))?; } - remove_stale_candidates(&self.path)?; - let bytes = rmp_serde::to_vec(&encode_store_snapshot(entries)) + remove_abandoned_staging_files(&self.path)?; + let bytes = rmp_serde::to_vec(&encode_store_snapshot(entries)?) .context("failed to encode MessagePack")?; let tmp_path = temporary_path_for(&self.path); - let mut candidate = OpenOptions::new() + let mut staged = OpenOptions::new() .create_new(true) .write(true) .open(&tmp_path) - .with_context(|| format!("failed to open {}", tmp_path.display()))?; - candidate + .with_context(|| format!("failed to create {}", tmp_path.display()))?; + use std::io::Write; + staged .write_all(&bytes) .with_context(|| format!("failed to write {}", tmp_path.display()))?; - candidate + staged .sync_all() - .with_context(|| format!("failed to flush {}", tmp_path.display()))?; - drop(candidate); - - observe(SnapshotReplacementStage::CandidateSynced)?; - replace_file(&tmp_path, &self.path)?; - if let Err(error) = observe(SnapshotReplacementStage::TargetReplaced) { - return Err(anyhow::Error::new( - CommittedSnapshotDurabilityUncertain::new(self.path.clone(), error), - )); - } - - if let Some(parent) = self.path.parent() { - if let Err(error) = sync_parent_directory(parent) { - return Err(anyhow::Error::new( - CommittedSnapshotDurabilityUncertain::new(self.path.clone(), error), - )); - } - } + .with_context(|| format!("failed to sync {}", tmp_path.display()))?; + drop(staged); + replace_file_atomically(&tmp_path, &self.path)?; + sync_parent_directory(&self.path)?; Ok(()) } @@ -244,26 +678,13 @@ impl SingleFileMessagePackBackingStore { } fn with_exclusive_lock(&self, action: impl FnOnce() -> Result) -> Result { - self.with_exclusive_lock_using_unlock(action, |lock| fs2::FileExt::unlock(lock)) - } - - fn with_exclusive_lock_using_unlock( - &self, - action: impl FnOnce() -> Result, - unlock: impl FnOnce(&File) -> std::io::Result<()>, - ) -> Result { let lock = self.open_lock_file()?; fs2::FileExt::lock_exclusive(&lock) .with_context(|| format!("failed to lock {}", self.lock_path().display()))?; - let action_result = action(); - - // Closing the file handle releases the OS lock even when an explicit - // unlock reports an error. The action outcome remains authoritative: - // in particular, never replace a committed-durability-uncertain error - // with secondary lock-cleanup noise. - let _unlock_result = unlock(&lock); - drop(lock); - action_result + let result = action(); + fs2::FileExt::unlock(&lock) + .with_context(|| format!("failed to unlock {}", self.lock_path().display()))?; + result } fn open_lock_file(&self) -> Result { @@ -286,14 +707,71 @@ impl SingleFileMessagePackBackingStore { .path .file_name() .map(|value| value.to_os_string()) - .unwrap_or_else(|| "cultcache.msgpack".into()); + .unwrap_or_else(|| "cultcache.cc".into()); lock_name.push(".lock"); self.path.with_file_name(lock_name) } } +struct ReadOnlySharedLockGuard +where + U: FnOnce(&File) -> Result<()>, + D: FnOnce(anyhow::Error), +{ + lock: Option, + unlock: Option, + on_unlock_failure: Option, +} + +impl ReadOnlySharedLockGuard +where + U: FnOnce(&File) -> Result<()>, + D: FnOnce(anyhow::Error), +{ + fn new(lock: File, unlock: U, on_unlock_failure: D) -> Self { + Self { + lock: Some(lock), + unlock: Some(unlock), + on_unlock_failure: Some(on_unlock_failure), + } + } +} + +impl Drop for ReadOnlySharedLockGuard +where + U: FnOnce(&File) -> Result<()>, + D: FnOnce(anyhow::Error), +{ + fn drop(&mut self) { + let (Some(lock), Some(unlock)) = (self.lock.as_ref(), self.unlock.take()) else { + return; + }; + if let Err(error) = unlock(lock) + && let Some(on_unlock_failure) = self.on_unlock_failure.take() + { + on_unlock_failure(error); + } + } +} + +fn unique_batch_ids( + entries: &[CultCacheEnvelope], + label: &str, +) -> Result> { + let ids = entries.iter().map(entry_id).collect::>(); + if ids.len() != entries.len() { + return Err(anyhow!( + "conditional batch {label} set contains duplicate identities" + )); + } + Ok(ids) +} + impl CacheBackingStore for SingleFileMessagePackBackingStore { fn pull_all(&self) -> Result> { + if !self.path.exists() && !self.lock_path().exists() { + return Ok(Vec::new()); + } self.with_shared_lock(|| self.read_all_unlocked()) } @@ -324,6 +802,947 @@ impl CacheBackingStore for SingleFileMessagePackBackingStore { } } +const REDB_ENVELOPES: TableDefinition<&[u8], &[u8]> = TableDefinition::new("cultcache_envelopes"); + +/// Transactional keyed CultCache storage. Each polymorphic `(type, key)` +/// identity is an independent redb row whose value is the exact MessagePack +/// serialization of its `CultCacheEnvelope`. +#[derive(Clone)] +pub struct RedbMessagePackBackingStore { + path: PathBuf, +} + +impl std::fmt::Debug for RedbMessagePackBackingStore { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("RedbMessagePackBackingStore") + .field("path", &self.path) + .finish_non_exhaustive() + } +} + +impl RedbMessagePackBackingStore { + pub fn new(path: impl Into) -> Result { + let path = path.into(); + if let Some(parent) = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + } + let store = Self { path }; + store.with_database(|database| { + let write = database.begin_write()?; + { + write.open_table(REDB_ENVELOPES)?; + } + write.commit()?; + Ok(()) + })?; + Ok(store) + } + + pub fn path(&self) -> &Path { + &self.path + } + + fn open_database(&self) -> Result { + Database::create(&self.path) + .with_context(|| format!("failed to open redb CultCache {}", self.path.display())) + } + + /// redb permits one open writable database handle per path. The external + /// CultCache lock therefore owns the complete open/transaction/close + /// interval, preserving the fresh-handle and cross-process construction + /// model used by the other backing store. + fn with_database(&self, action: impl FnOnce(&Database) -> Result) -> Result { + let lock_path = self.lock_path(); + if let Some(parent) = lock_path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + } + let lock = OpenOptions::new() + .create(true) + .read(true) + .truncate(false) + .write(true) + .open(&lock_path) + .with_context(|| format!("failed to open {}", lock_path.display()))?; + fs2::FileExt::lock_exclusive(&lock) + .with_context(|| format!("failed to lock {}", lock_path.display()))?; + let result = self.open_database().and_then(|database| action(&database)); + fs2::FileExt::unlock(&lock) + .with_context(|| format!("failed to unlock {}", lock_path.display()))?; + result + } + + fn lock_path(&self) -> PathBuf { + let mut lock_name = self + .path + .file_name() + .map(|value| value.to_os_string()) + .unwrap_or_else(|| "cultcache.cc".into()); + lock_name.push(".lock"); + self.path.with_file_name(lock_name) + } + + pub fn compare_and_swap_entry( + &self, + expected: &CultCacheEnvelope, + replacement: CultCacheEnvelope, + ) -> Result { + if entry_id(expected) != entry_id(&replacement) { + return Err(anyhow!("entry CAS replacement must preserve identity")); + } + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + if read_redb_entry(&table, expected)? != Some(expected.clone()) { + false + } else { + insert_redb_entry(&mut table, &replacement)?; + true + } + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } + + pub fn insert_entry_if_absent(&self, entry: CultCacheEnvelope) -> Result { + self.with_database(|database| { + let write = database.begin_write()?; + let inserted = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let key = redb_identity(&entry)?; + if table.get(key.as_slice())?.is_some() { + false + } else { + insert_redb_entry(&mut table, &entry)?; + true + } + }; + if inserted { + write.commit()?; + } else { + write.abort()?; + } + Ok(inserted) + }) + } + + pub fn compare_and_swap_batch( + &self, + expected: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional batch requires a non-empty replacement set" + )); + } + let expected_ids = unique_batch_ids(expected, "expected")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + if !expected_ids.is_subset(&replacement_ids) { + return Err(anyhow!( + "conditional batch must replace every expected identity" + )); + } + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut valid = true; + for row in expected { + if read_redb_entry(&table, row)? != Some(row.clone()) { + valid = false; + break; + } + } + if valid { + for row in &replacements { + if !expected_ids.contains(&entry_id(row)) + && read_redb_entry(&table, row)?.is_some() + { + valid = false; + break; + } + } + } + if valid { + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } + + pub fn append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + additions: Vec, + ) -> Result { + if additions.is_empty() { + return Err(anyhow!("conditional snapshot append requires additions")); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + let addition_ids = unique_batch_ids(&additions, "snapshot additions")?; + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let valid = current == expected + && !current + .iter() + .any(|row| addition_ids.contains(&entry_id(row))); + if valid { + for row in &additions { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } + + pub fn replace_and_append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional snapshot replacement requires replacements" + )); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + unique_batch_ids(&replacements, "snapshot replacements")?; + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let valid = current == expected; + if valid { + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } + + /// Atomically replaces and deletes rows only while the complete store + /// snapshot remains byte-exact. This is the compaction primitive for a + /// non-owning keyed handle: a stale reader cannot summarize one history + /// while deleting another. + pub fn replace_and_delete_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + deletions: &[CultCacheEnvelope], + ) -> Result { + if replacements.is_empty() || deletions.is_empty() { + return Err(anyhow!( + "conditional compaction requires replacements and deletions" + )); + } + let expected_ids = unique_batch_ids(expected_snapshot, "expected snapshot")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + let deletion_ids = unique_batch_ids(deletions, "deletion")?; + if !deletion_ids.is_subset(&expected_ids) { + return Err(anyhow!( + "compaction deletion is absent from expected snapshot" + )); + } + if !deletion_ids.is_disjoint(&replacement_ids) { + return Err(anyhow!("compaction cannot replace and delete one identity")); + } + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let mut valid = current == expected; + if valid { + for row in &replacements { + if !expected_ids.contains(&entry_id(row)) + && read_redb_entry(&table, row)?.is_some() + { + valid = false; + break; + } + } + } + if valid { + for identity in deletion_ids { + let key = rmp_serde::to_vec(&identity) + .context("failed to encode CultCache identity")?; + table.remove(key.as_slice())?; + } + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } + + pub fn delete_batch_if_unchanged(&self, expected: &[CultCacheEnvelope]) -> Result { + if expected.is_empty() { + return Err(anyhow!( + "conditional delete requires a non-empty expected set" + )); + } + unique_batch_ids(expected, "delete expected")?; + self.with_database(|database| { + let write = database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut valid = true; + for row in expected { + if read_redb_entry(&table, row)? != Some(row.clone()) { + valid = false; + break; + } + } + if valid { + for row in expected { + let key = redb_identity(row)?; + table.remove(key.as_slice())?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + }) + } +} + +struct OwnedRedbInner { + database: Database, + _owner_lock: File, + file_identity: String, +} + +/// A pinned redb store for one long-lived service owner. Unlike the transient +/// redb store, this handle holds the CultCache external lock, an open file +/// handle, and the redb database for its entire lifetime. Clones share that +/// exact ownership authority. +#[derive(Clone)] +pub struct OwnedRedbMessagePackBackingStore { + path: PathBuf, + inner: Arc, +} + +impl std::fmt::Debug for OwnedRedbMessagePackBackingStore { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("OwnedRedbMessagePackBackingStore") + .field("path", &self.path) + .field("file_identity", &self.inner.file_identity) + .finish_non_exhaustive() + } +} + +impl OwnedRedbMessagePackBackingStore { + pub fn new(path: impl Into) -> Result { + let path = path.into(); + if let Some(parent) = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + } + let lock_path = redb_lock_path(&path); + let owner_lock = OpenOptions::new() + .create(true) + .read(true) + .truncate(false) + .write(true) + .open(&lock_path) + .with_context(|| format!("failed to open {}", lock_path.display()))?; + fs2::FileExt::try_lock_exclusive(&owner_lock).with_context(|| { + format!( + "redb CultCache {} already has an active owner", + path.display() + ) + })?; + + // Give redb ownership of the already pinned file, structurally closing + // path substitution during database creation. The post-open identity + // comparison separately verifies that the pathname still names it. + let database_file = OpenOptions::new() + .create(true) + .read(true) + .truncate(false) + .write(true) + .open(&path) + .with_context(|| format!("failed to pin redb CultCache {}", path.display()))?; + let held_identity = file_identity_from_file(&database_file)?; + let database = Database::builder() + .create_file(database_file) + .with_context(|| format!("failed to open redb CultCache {}", path.display()))?; + let path_identity_file = File::open(&path)?; + let path_identity = file_identity_from_file(&path_identity_file)?; + if held_identity != path_identity { + bail!( + "redb CultCache path identity changed while opening: held {held_identity}, path {path_identity}" + ); + } + let write = database.begin_write()?; + { + write.open_table(REDB_ENVELOPES)?; + } + write.commit()?; + Ok(Self { + path, + inner: Arc::new(OwnedRedbInner { + database, + _owner_lock: owner_lock, + file_identity: held_identity, + }), + }) + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn file_identity(&self) -> &str { + &self.inner.file_identity + } + + pub fn require_file_identity(&self, expected: &str) -> Result<()> { + if expected != self.file_identity() { + bail!( + "redb CultCache file identity mismatch: expected {expected}, owned {}", + self.file_identity() + ); + } + Ok(()) + } + + pub fn validate_path_identity(&self) -> Result<()> { + let current_file = File::open(&self.path) + .with_context(|| format!("owned redb path {} is missing", self.path.display()))?; + let current = file_identity_from_file(¤t_file)?; + self.require_file_identity(¤t).with_context(|| { + format!( + "owned redb path {} no longer names the pinned file", + self.path.display() + ) + }) + } + + pub fn compare_and_swap_entry( + &self, + expected: &CultCacheEnvelope, + replacement: CultCacheEnvelope, + ) -> Result { + if entry_id(expected) != entry_id(&replacement) { + return Err(anyhow!("entry CAS replacement must preserve identity")); + } + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + if read_redb_entry(&table, expected)? != Some(expected.clone()) { + false + } else { + insert_redb_entry(&mut table, &replacement)?; + true + } + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } + + pub fn insert_entry_if_absent(&self, entry: CultCacheEnvelope) -> Result { + let write = self.inner.database.begin_write()?; + let inserted = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let key = redb_identity(&entry)?; + if table.get(key.as_slice())?.is_some() { + false + } else { + insert_redb_entry(&mut table, &entry)?; + true + } + }; + if inserted { + write.commit()?; + } else { + write.abort()?; + } + Ok(inserted) + } + + pub fn compare_and_swap_batch( + &self, + expected: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional batch requires a non-empty replacement set" + )); + } + let expected_ids = unique_batch_ids(expected, "expected")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + if !expected_ids.is_subset(&replacement_ids) { + return Err(anyhow!( + "conditional batch must replace every expected identity" + )); + } + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut valid = true; + for row in expected { + if read_redb_entry(&table, row)? != Some(row.clone()) { + valid = false; + break; + } + } + if valid { + for row in &replacements { + if !expected_ids.contains(&entry_id(row)) + && read_redb_entry(&table, row)?.is_some() + { + valid = false; + break; + } + } + } + if valid { + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } + + pub fn append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + additions: Vec, + ) -> Result { + if additions.is_empty() { + return Err(anyhow!("conditional snapshot append requires additions")); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + let addition_ids = unique_batch_ids(&additions, "snapshot additions")?; + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let valid = current == expected + && !current + .iter() + .any(|row| addition_ids.contains(&entry_id(row))); + if valid { + for row in &additions { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } + + pub fn replace_and_append_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + ) -> Result { + if replacements.is_empty() { + return Err(anyhow!( + "conditional snapshot replacement requires replacements" + )); + } + unique_batch_ids(expected_snapshot, "expected snapshot")?; + unique_batch_ids(&replacements, "snapshot replacements")?; + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let valid = current == expected; + if valid { + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } + + pub fn delete_batch_if_unchanged(&self, expected: &[CultCacheEnvelope]) -> Result { + if expected.is_empty() { + return Err(anyhow!( + "conditional delete requires a non-empty expected set" + )); + } + let expected_ids = unique_batch_ids(expected, "delete expected")?; + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut valid = true; + for row in expected { + if read_redb_entry(&table, row)? != Some(row.clone()) { + valid = false; + break; + } + } + if valid { + for identity in expected_ids { + let key = rmp_serde::to_vec(&identity) + .context("failed to encode CultCache identity")?; + table.remove(key.as_slice())?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } + + /// Atomically replaces and deletes rows only while the complete store + /// snapshot remains byte-exact. This is the compaction primitive: a stale + /// reader cannot summarize one history while deleting another. + pub fn replace_and_delete_if_snapshot_unchanged( + &self, + expected_snapshot: &[CultCacheEnvelope], + replacements: Vec, + deletions: &[CultCacheEnvelope], + ) -> Result { + if replacements.is_empty() || deletions.is_empty() { + return Err(anyhow!( + "conditional compaction requires replacements and deletions" + )); + } + let expected_ids = unique_batch_ids(expected_snapshot, "expected snapshot")?; + let replacement_ids = unique_batch_ids(&replacements, "replacement")?; + let deletion_ids = unique_batch_ids(deletions, "deletion")?; + if !deletion_ids.is_subset(&expected_ids) { + return Err(anyhow!( + "compaction deletion is absent from expected snapshot" + )); + } + if !deletion_ids.is_disjoint(&replacement_ids) { + return Err(anyhow!("compaction cannot replace and delete one identity")); + } + let write = self.inner.database.begin_write()?; + let matched = { + let mut table = write.open_table(REDB_ENVELOPES)?; + let mut current = read_all_redb(&table)?; + let mut expected = expected_snapshot.to_vec(); + current.sort_by_key(entry_id); + expected.sort_by_key(entry_id); + let mut valid = current == expected; + if valid { + for row in &replacements { + if !expected_ids.contains(&entry_id(row)) + && read_redb_entry(&table, row)?.is_some() + { + valid = false; + break; + } + } + } + if valid { + for identity in deletion_ids { + let key = rmp_serde::to_vec(&identity) + .context("failed to encode CultCache identity")?; + table.remove(key.as_slice())?; + } + for row in &replacements { + insert_redb_entry(&mut table, row)?; + } + } + valid + }; + if matched { + write.commit()?; + } else { + write.abort()?; + } + Ok(matched) + } +} + +impl CacheBackingStore for OwnedRedbMessagePackBackingStore { + fn pull_all(&self) -> Result> { + let read = self.inner.database.begin_read()?; + let table = read.open_table(REDB_ENVELOPES)?; + read_all_redb(&table) + } + + fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + let write = self.inner.database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + insert_redb_entry(&mut table, entry)?; + } + write.commit()?; + Ok(()) + } + + fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + let write = self.inner.database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + let key = redb_identity(entry)?; + table.remove(key.as_slice())?; + } + write.commit()?; + Ok(()) + } + + fn push_all(&mut self, entries: &[CultCacheEnvelope], _options: PushAllOptions) -> Result<()> { + unique_batch_ids(entries, "push all")?; + let write = self.inner.database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + let keys = table + .iter()? + .map(|row| Ok(row?.0.value().to_vec())) + .collect::>>()?; + for key in keys { + table.remove(key.as_slice())?; + } + for entry in entries { + insert_redb_entry(&mut table, entry)?; + } + } + write.commit()?; + Ok(()) + } +} + +fn redb_lock_path(path: &Path) -> PathBuf { + let mut lock_name = path + .file_name() + .map(|value| value.to_os_string()) + .unwrap_or_else(|| "cultcache.cc".into()); + lock_name.push(".lock"); + path.with_file_name(lock_name) +} + +#[cfg(unix)] +fn file_identity_from_file(file: &File) -> Result { + use std::os::unix::fs::MetadataExt; + let metadata = file.metadata()?; + Ok(format!( + "unix:{:016x}:{:016x}", + metadata.dev(), + metadata.ino() + )) +} + +#[cfg(windows)] +fn file_identity_from_file(file: &File) -> Result { + use std::mem::zeroed; + use std::os::windows::io::AsRawHandle; + use windows_sys::Win32::Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle, + }; + let mut information: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() }; + let success = + unsafe { GetFileInformationByHandle(file.as_raw_handle() as _, &mut information) }; + if success == 0 { + return Err(std::io::Error::last_os_error()) + .context("failed to read Windows file identity"); + } + let index = ((information.nFileIndexHigh as u64) << 32) | information.nFileIndexLow as u64; + Ok(format!( + "windows:{:08x}:{index:016x}", + information.dwVolumeSerialNumber + )) +} + +fn redb_identity(entry: &CultCacheEnvelope) -> Result> { + rmp_serde::to_vec(&entry_id(entry)).context("failed to encode CultCache identity") +} + +fn read_redb_entry( + table: &impl ReadableTable<&'static [u8], &'static [u8]>, + identity: &CultCacheEnvelope, +) -> Result> { + let key = redb_identity(identity)?; + table + .get(key.as_slice())? + .map(|value| { + rmp_serde::from_slice(value.value()).context("failed to decode redb CultCache envelope") + }) + .transpose() +} + +fn insert_redb_entry( + table: &mut redb::Table<&[u8], &[u8]>, + entry: &CultCacheEnvelope, +) -> Result<()> { + let key = redb_identity(entry)?; + let value = rmp_serde::to_vec(entry).context("failed to encode redb CultCache envelope")?; + table.insert(key.as_slice(), value.as_slice())?; + Ok(()) +} + +fn read_all_redb( + table: &impl ReadableTable<&'static [u8], &'static [u8]>, +) -> Result> { + let mut entries = table + .iter()? + .map(|row| { + let (_, value) = row?; + rmp_serde::from_slice(value.value()).context("failed to decode redb CultCache envelope") + }) + .collect::>>()?; + entries.sort_by_key(entry_id); + Ok(entries) +} + +impl CacheBackingStore for RedbMessagePackBackingStore { + fn pull_all(&self) -> Result> { + self.with_database(|database| { + let read = database.begin_read()?; + let table = read.open_table(REDB_ENVELOPES)?; + read_all_redb(&table) + }) + } + + fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + self.with_database(|database| { + let write = database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + insert_redb_entry(&mut table, entry)?; + } + write.commit()?; + Ok(()) + }) + } + + fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + self.with_database(|database| { + let write = database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + let key = redb_identity(entry)?; + table.remove(key.as_slice())?; + } + write.commit()?; + Ok(()) + }) + } + + fn push_all(&mut self, entries: &[CultCacheEnvelope], _options: PushAllOptions) -> Result<()> { + unique_batch_ids(entries, "push all")?; + self.with_database(|database| { + let write = database.begin_write()?; + { + let mut table = write.open_table(REDB_ENVELOPES)?; + let keys = table + .iter()? + .map(|row| Ok(row?.0.value().to_vec())) + .collect::>>()?; + for key in keys { + table.remove(key.as_slice())?; + } + for entry in entries { + insert_redb_entry(&mut table, entry)?; + } + } + write.commit()?; + Ok(()) + }) + } +} + struct CultCacheStoreRegistration { store: Box, types: BTreeSet, @@ -331,8 +1750,7 @@ struct CultCacheStoreRegistration { pub struct CultCache { definitions: BTreeMap, - schema_name_definitions: BTreeMap, - entries: BTreeMap, + entries: BTreeMap<(String, String), CultCacheEnvelope>, stores: Vec, } @@ -340,7 +1758,6 @@ impl CultCache { pub fn new() -> Self { Self { definitions: BTreeMap::new(), - schema_name_definitions: BTreeMap::new(), entries: BTreeMap::new(), stores: Vec::new(), } @@ -360,18 +1777,7 @@ impl CultCache { T::TYPE )); } - if let Some(existing_type) = self.schema_name_definitions.get(T::SCHEMA_NAME) - && existing_type != T::TYPE - { - return Err(anyhow!( - "CultCache schema name {:?} is already registered for type {:?}", - T::SCHEMA_NAME, - existing_type - )); - } self.definitions.insert(T::TYPE.to_string(), T::SCHEMA_NAME); - self.schema_name_definitions - .insert(T::SCHEMA_NAME.to_string(), T::TYPE.to_string()); Ok(()) } @@ -379,6 +1785,10 @@ impl CultCache { self.register_entry_type::() } + pub fn registered_entry_types(&self) -> Vec { + self.definitions.keys().cloned().collect() + } + pub fn register_registry(&mut self, registry: R) -> Result<&mut Self> { registry.register_entries(self)?; Ok(self) @@ -402,26 +1812,27 @@ impl CultCache { pub fn pull_all_backing_stores(&mut self) -> Result<()> { self.entries.clear(); let known_types: BTreeSet = self.definitions.keys().cloned().collect(); - let schema_name_definitions = self.schema_name_definitions.clone(); for registration in &mut self.stores { - for mut entry in registration.store.pull_all()? { - let Some(canonical_type) = resolve_registered_type( - &known_types, - &schema_name_definitions, - &entry.r#type, - ) else { + for entry in registration.store.pull_all()? { + if !known_types.contains(&entry.r#type) { return Err(anyhow!( "No schema is registered for persisted entry type {:?}", entry.r#type )); - }; - entry.r#type = canonical_type; + } self.entries.insert(entry_id(&entry), entry); } } Ok(()) } + /// Returns the exact envelopes in the currently loaded cache image. + /// Callers that need optimistic concurrency can validate typed documents + /// and capture CAS expectations from one coherent read. + pub fn snapshot_envelopes(&self) -> Vec { + self.entries.values().cloned().collect() + } + pub fn get(&self, key: &str) -> Result> { self.require_entry_type::()?; let Some(entry) = self.entries.get(&entry_id_parts(T::TYPE, key)) else { @@ -472,32 +1883,45 @@ impl CultCache { Ok(values) } - pub fn put(&mut self, key: impl Into, value: &T) -> Result { + pub fn get_all_with_keys(&self) -> Result> { self.require_entry_type::()?; - let key = key.into(); - let payload = rmp_serde::to_vec(value).with_context(|| { - format!( - "failed to encode CultCache entry {:?} at key {:?} as {}", - T::TYPE, - key, - T::SCHEMA_NAME - ) - })?; - let parsed: T = rmp_serde::from_slice(&payload).with_context(|| { - format!( - "failed to validate CultCache entry {:?} at key {:?} as {}", - T::TYPE, - key, - T::SCHEMA_NAME - ) - })?; - let entry = CultCacheEnvelope { - key: key.clone(), - r#type: T::TYPE.to_string(), - payload, - stored_at: now_utc_second(), - schema_id: Some(T::TYPE.to_string()), - }; + let mut values = Vec::new(); + for entry in self.entries.values() { + if entry.r#type != T::TYPE { + continue; + } + values.push(( + entry.key.clone(), + rmp_serde::from_slice(&entry.payload).with_context(|| { + format!( + "failed to decode CultCache entry {:?} at key {:?} as {}", + T::TYPE, + entry.key, + T::SCHEMA_NAME + ) + })?, + )); + } + Ok(values) + } + + pub fn soa(&self) -> Result> { + let keyed_rows = self.get_all_with_keys::()?; + let (keys, documents): (Vec<_>, Vec<_>) = keyed_rows.into_iter().unzip(); + let columns = T::soa_columns(&documents) + .into_iter() + .map(|(name, values)| (name.to_string(), values)) + .collect(); + Ok(CultSoaTable { + keys, + documents, + columns, + _document: PhantomData, + }) + } + + pub fn put(&mut self, key: impl Into, value: &T) -> Result { + let (entry, parsed) = self.prepare_entry(key, value)?; let route = self.resolve_route_indices(T::TYPE); let Some(primary_index) = route.first().copied() else { return Err(anyhow!( @@ -513,7 +1937,176 @@ impl CultCache { Ok(parsed) } + pub fn prepare_entry( + &self, + key: impl Into, + value: &T, + ) -> Result<(CultCacheEnvelope, T)> { + self.require_entry_type::()?; + let key = key.into(); + let payload = rmp_serde::to_vec(value).with_context(|| { + format!( + "failed to encode CultCache entry {:?} at key {:?} as {}", + T::TYPE, + key, + T::SCHEMA_NAME + ) + })?; + self.finish_prepared_entry::(key, payload) + } + + /// Prepares a typed entry while encoding ordinary nested structs as + /// MessagePack maps. DatabaseEntry itself remains the stable numeric-slot + /// tuple; named nested fields prevent serde's omitted optional fields from + /// shifting later values into the wrong positions. + pub fn prepare_entry_named( + &self, + key: impl Into, + value: &T, + ) -> Result<(CultCacheEnvelope, T)> { + self.require_entry_type::()?; + let key = key.into(); + let payload = rmp_serde::to_vec_named(value).with_context(|| { + format!( + "failed to encode named CultCache entry {:?} at key {:?} as {}", + T::TYPE, + key, + T::SCHEMA_NAME + ) + })?; + self.finish_prepared_entry::(key, payload) + } + + fn finish_prepared_entry( + &self, + key: String, + payload: Vec, + ) -> Result<(CultCacheEnvelope, T)> { + let parsed: T = rmp_serde::from_slice(&payload).with_context(|| { + format!( + "failed to validate CultCache entry {:?} at key {:?} as {}", + T::TYPE, + key, + T::SCHEMA_NAME + ) + })?; + let entry = CultCacheEnvelope { + key: key.clone(), + r#type: T::TYPE.to_string(), + payload, + stored_at: now_utc_second(), + schema_id: Some(T::TYPE.to_string()), + }; + Ok((entry, parsed)) + } + + pub fn put_prepared_batch(&mut self, staged: Vec) -> Result<()> { + if staged.is_empty() { + return Err(anyhow!("CultCache batch must contain at least one entry")); + } + let mut store_index = None; + for entry in staged.iter().chain(self.entries.values()) { + if !self.definitions.contains_key(&entry.r#type) { + return Err(anyhow!( + "CultCache batch entry type {:?} is not registered", + entry.r#type + )); + } + let route = self.resolve_route_indices(&entry.r#type); + if route.len() != 1 { + return Err(anyhow!( + "CultCache atomic batch requires exactly one backing-store route for type {:?}, found {}", + entry.r#type, + route.len() + )); + } + match store_index { + Some(expected) if expected != route[0] => { + return Err(anyhow!("CultCache atomic batch cannot span backing stores")); + } + None => store_index = Some(route[0]), + _ => {} + } + } + let store_index = store_index.expect("non-empty batch establishes a store route"); + let mut next = self.entries.clone(); + for entry in staged { + next.insert(entry_id(&entry), entry); + } + let snapshot: Vec<_> = next.values().cloned().collect(); + self.stores[store_index] + .store + .push_all(&snapshot, PushAllOptions::default())?; + self.entries = next; + Ok(()) + } + pub fn put_envelope(&mut self, entry: CultCacheEnvelope) -> Result { + let parsed = self.validate_envelope::(&entry)?; + let route = self.resolve_route_indices(T::TYPE); + let Some(primary_index) = route.first().copied() else { + return Err(anyhow!( + "No backing store is registered for entry type {:?}", + T::TYPE + )); + }; + self.stores[primary_index].store.push(&entry)?; + for mirror_index in route.iter().skip(1).copied() { + self.stores[mirror_index].store.push(&entry)?; + } + self.entries.insert(entry_id(&entry), entry); + Ok(parsed) + } + + /// Publishes an envelope for a registered dynamic document type without + /// claiming compile-time knowledge of its payload schema. This is the + /// CultMesh/schema-registry boundary: callers own payload validation before + /// admission; CultCache still enforces registered type, identity, timestamp, + /// routing, and persistence. + pub fn put_raw_envelope(&mut self, entry: CultCacheEnvelope) -> Result<()> { + if !self.definitions.contains_key(&entry.r#type) { + return Err(anyhow!( + "No entry type registered for CultCache envelope type {:?}", + entry.r#type + )); + } + if entry.key.trim().is_empty() { + return Err(anyhow!( + "CultCache envelope keys for type {:?} must be non-empty", + entry.r#type + )); + } + if entry.stored_at.trim().is_empty() { + return Err(anyhow!( + "CultCache envelope stored_at for type {:?} must be non-empty", + entry.r#type + )); + } + let route = self.resolve_route_indices(&entry.r#type); + let Some(primary_index) = route.first().copied() else { + return Err(anyhow!( + "No backing store is registered for entry type {:?}", + entry.r#type + )); + }; + self.stores[primary_index].store.push(&entry)?; + for mirror_index in route.iter().skip(1).copied() { + self.stores[mirror_index].store.push(&entry)?; + } + self.entries.insert(entry_id(&entry), entry); + Ok(()) + } + + /// Validates and admits an existing envelope into this cache image without + /// publishing it to any backing store. This is the typed read primitive for + /// owner-filtered views over a shared polymorphic store. + pub fn load_envelope(&mut self, entry: CultCacheEnvelope) -> Result { + let parsed = self.validate_envelope::(&entry)?; + self.entries.insert(entry_id(&entry), entry); + Ok(parsed) + } + + fn validate_envelope(&self, entry: &CultCacheEnvelope) -> Result { self.require_entry_type::()?; if entry.r#type != T::TYPE { return Err(anyhow!( @@ -535,27 +2128,14 @@ impl CultCache { )); } - let parsed: T = rmp_serde::from_slice(&entry.payload).with_context(|| { + rmp_serde::from_slice(&entry.payload).with_context(|| { format!( "failed to validate CultCache envelope {:?} at key {:?} as {}", T::TYPE, entry.key, T::SCHEMA_NAME ) - })?; - let route = self.resolve_route_indices(T::TYPE); - let Some(primary_index) = route.first().copied() else { - return Err(anyhow!( - "No backing store is registered for entry type {:?}", - T::TYPE - )); - }; - self.stores[primary_index].store.push(&entry)?; - for mirror_index in route.iter().skip(1).copied() { - self.stores[mirror_index].store.push(&entry)?; - } - self.entries.insert(entry_id(&entry), entry); - Ok(parsed) + }) } pub fn update(&mut self, key: &str, updater: F) -> Result @@ -592,6 +2172,25 @@ impl CultCache { self.entries.values().cloned().collect() } + pub fn snapshot_soa(&self) -> CultCacheSoaTable { + CultCacheSoaTable::from_envelopes(self.snapshot()) + } + + pub fn load_soa(&mut self, table: CultCacheSoaTable) -> Result<()> { + let known_types: BTreeSet = self.definitions.keys().cloned().collect(); + self.entries.clear(); + for entry in table.into_envelopes()? { + if !known_types.contains(&entry.r#type) { + return Err(anyhow!( + "No schema is registered for SoA entry type {:?}", + entry.r#type + )); + } + self.entries.insert(entry_id(&entry), entry); + } + Ok(()) + } + fn require_entry_type(&self) -> Result<()> { match self.definitions.get(T::TYPE) { Some(schema_name) if *schema_name == T::SCHEMA_NAME => Ok(()), @@ -620,7 +2219,6 @@ impl CultCache { .filter_map(|(index, registration)| registration.types.is_empty().then_some(index)) .collect() } - } impl Default for CultCache { @@ -629,47 +2227,40 @@ impl Default for CultCache { } } -fn entry_id(entry: &CultCacheEnvelope) -> String { +fn entry_id(entry: &CultCacheEnvelope) -> (String, String) { entry_id_parts(&entry.r#type, &entry.key) } -fn entry_id_parts(r#type: &str, key: &str) -> String { - format!("{type}::{key}", type = r#type) -} - -fn resolve_registered_type( - known_types: &BTreeSet, - schema_name_definitions: &BTreeMap, - persisted_type: &str, -) -> Option { - if known_types.contains(persisted_type) { - return Some(persisted_type.to_string()); - } - - schema_name_definitions.get(persisted_type).cloned() +fn entry_id_parts(r#type: &str, key: &str) -> (String, String) { + (r#type.to_string(), key.to_string()) } fn now_utc_second() -> String { chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true) } -fn encode_store_snapshot(entries: &[CultCacheEnvelope]) -> PersistedStoreSnapshot { - let mut schema_names = BTreeSet::::new(); +fn encode_store_snapshot(entries: &[CultCacheEnvelope]) -> Result { + let mut schema_types = BTreeMap::::new(); for entry in entries { - schema_names.insert( - entry - .schema_id - .clone() - .unwrap_or_else(|| entry.r#type.clone()), - ); + let schema_id = entry + .schema_id + .clone() + .unwrap_or_else(|| entry.r#type.clone()); + if let Some(existing_type) = schema_types.insert(schema_id.clone(), entry.r#type.clone()) { + ensure!( + existing_type == entry.r#type, + "CultCache schema {schema_id:?} cannot identify both {existing_type:?} and {:?}", + entry.r#type + ); + } } - let catalog = schema_names + let catalog = schema_types .into_iter() - .map(|schema_id| { + .map(|(schema_id, document_type)| { PersistedSchemaCatalogEntry( schema_id.clone(), - schema_id.clone(), + document_type, format!("{schema_id}.v1"), schema_id.clone(), format!( @@ -697,7 +2288,11 @@ fn encode_store_snapshot(entries: &[CultCacheEnvelope]) -> PersistedStoreSnapsho }) .collect(); - PersistedStoreSnapshot("cultcache.store.v1".to_string(), catalog, records) + Ok(PersistedStoreSnapshot( + "cultcache.store.v1".to_string(), + catalog, + records, + )) } fn decode_store_snapshot(bytes: &[u8]) -> Result> { @@ -716,25 +2311,13 @@ fn decode_store_snapshot(bytes: &[u8]) -> Result> { .2 .into_iter() .map(|record| { - let r#type = match catalog.get(&record.1) { - Some(schema_name) => schema_name.clone(), - None => { - let schema_version = infer_schema_version_from_payload(&record.3).ok_or_else(|| { - anyhow!( - "CultCache record {:?} references missing schema {:?}", - record.0, - record.1 - ) - })?; - infer_schema_name(&schema_version).ok_or_else(|| { - anyhow!( - "CultCache record {:?} references missing schema {:?}", - record.0, - record.1 - ) - })? - } - }; + let r#type = catalog.get(&record.1).cloned().ok_or_else(|| { + anyhow!( + "CultCache record {:?} references missing schema {:?}", + record.0, + record.1 + ) + })?; Ok(CultCacheEnvelope { key: record.0, r#type, @@ -746,68 +2329,6 @@ fn decode_store_snapshot(bytes: &[u8]) -> Result> { .collect() } -fn infer_schema_version_from_payload(payload: &[u8]) -> Option { - let mut offset = 0usize; - read_array_header(payload, &mut offset)?; - read_string(payload, &mut offset) -} - -fn read_array_header(payload: &[u8], offset: &mut usize) -> Option { - let marker = *payload.get(*offset)?; - *offset += 1; - match marker { - 0x90..=0x9f => Some((marker & 0x0f) as u32), - 0xdc => { - let bytes = payload.get(*offset..(*offset + 2))?; - *offset += 2; - Some(u16::from_be_bytes([bytes[0], bytes[1]]) as u32) - } - 0xdd => { - let bytes = payload.get(*offset..(*offset + 4))?; - *offset += 4; - Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) - } - _ => None, - } -} - -fn read_string(payload: &[u8], offset: &mut usize) -> Option { - let marker = *payload.get(*offset)?; - *offset += 1; - let length = match marker { - 0xa0..=0xbf => (marker & 0x1f) as usize, - 0xd9 => { - let length = *payload.get(*offset)? as usize; - *offset += 1; - length - } - 0xda => { - let bytes = payload.get(*offset..(*offset + 2))?; - *offset += 2; - u16::from_be_bytes([bytes[0], bytes[1]]) as usize - } - 0xdb => { - let bytes = payload.get(*offset..(*offset + 4))?; - *offset += 4; - u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize - } - _ => return None, - }; - - let bytes = payload.get(*offset..(*offset + length))?; - std::str::from_utf8(bytes).ok().map(str::to_string) -} - -fn infer_schema_name(schema_version: &str) -> Option { - let marker = schema_version.rfind(".v")?; - let version = schema_version.get((marker + 2)..)?; - if marker == 0 || version.is_empty() || !version.bytes().all(|value| value.is_ascii_digit()) { - return None; - } - - Some(schema_version[..marker].to_string()) -} - fn escape_json_string(value: &str) -> String { value.replace('\\', "\\\\").replace('"', "\\\"") } @@ -816,188 +2337,108 @@ fn temporary_path_for(path: &Path) -> PathBuf { let mut file_name = path .file_name() .map(|value| value.to_os_string()) - .unwrap_or_else(|| "cultcache.msgpack".into()); + .unwrap_or_else(|| "cultcache.cc".into()); file_name.push(format!(".{}.tmp", uuid::Uuid::new_v4())); path.with_file_name(file_name) } -fn is_canonical_candidate_uuid(value: &[u8]) -> bool { - if value.len() != 36 || !value.is_ascii() { - return false; - } - let Ok(value) = std::str::from_utf8(value) else { - return false; +fn remove_abandoned_staging_files(destination: &Path) -> Result<()> { + let Some(parent) = destination.parent() else { + return Ok(()); }; - let Ok(uuid) = uuid::Uuid::parse_str(value) else { - return false; + let Some(destination_name) = destination.file_name().and_then(|value| value.to_str()) else { + return Ok(()); }; - uuid.hyphenated().to_string() == value -} - -#[cfg(unix)] -fn is_owned_candidate_name(target_name: &OsStr, candidate_name: &OsStr) -> bool { - use std::os::unix::ffi::OsStrExt; - - let target = target_name.as_bytes(); - let candidate = candidate_name.as_bytes(); - let Some(remainder) = candidate.strip_prefix(target) else { - return false; - }; - let Some(remainder) = remainder.strip_prefix(b".") else { - return false; - }; - let Some(uuid) = remainder.strip_suffix(b".tmp") else { - return false; - }; - is_canonical_candidate_uuid(uuid) -} - -#[cfg(windows)] -fn is_owned_candidate_name(target_name: &OsStr, candidate_name: &OsStr) -> bool { - use std::os::windows::ffi::OsStrExt; - - let target = target_name.encode_wide().collect::>(); - let candidate = candidate_name.encode_wide().collect::>(); - let Some(remainder) = candidate.strip_prefix(target.as_slice()) else { - return false; - }; - let Some(remainder) = remainder.strip_prefix(&[b'.' as u16]) else { - return false; - }; - let suffix = [b'.' as u16, b't' as u16, b'm' as u16, b'p' as u16]; - let Some(uuid_wide) = remainder.strip_suffix(&suffix) else { - return false; - }; - let Ok(uuid) = uuid_wide - .iter() - .map(|value| u8::try_from(*value)) - .collect::, _>>() - else { - return false; - }; - is_canonical_candidate_uuid(&uuid) -} - -#[cfg(not(any(unix, windows)))] -fn is_owned_candidate_name(target_name: &OsStr, candidate_name: &OsStr) -> bool { - let (Some(target), Some(candidate)) = (target_name.to_str(), candidate_name.to_str()) else { - return false; - }; - let Some(remainder) = candidate.strip_prefix(target) else { - return false; - }; - let Some(remainder) = remainder.strip_prefix('.') else { - return false; - }; - let Some(uuid) = remainder.strip_suffix(".tmp") else { - return false; - }; - is_canonical_candidate_uuid(uuid.as_bytes()) -} - -fn stale_candidate_paths(path: &Path) -> Result> { - let parent = path.parent().unwrap_or_else(|| Path::new(".")); - if !parent.exists() { - return Ok(Vec::new()); - } - let target_name = path - .file_name() - .unwrap_or_else(|| OsStr::new("cultcache.msgpack")); - let mut candidates = Vec::new(); - for entry in - fs::read_dir(parent).with_context(|| format!("failed to inspect {}", parent.display()))? - { - let entry = entry.with_context(|| format!("failed to inspect {}", parent.display()))?; - let name = entry.file_name(); - if is_owned_candidate_name(target_name, &name) { - candidates.push(entry.path()); + let prefix = format!("{destination_name}."); + for entry in fs::read_dir(parent).with_context(|| { + format!( + "failed to inspect {} for abandoned staging files", + parent.display() + ) + })? { + let entry = entry?; + let Some(name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + let Some(candidate) = name + .strip_prefix(&prefix) + .and_then(|value| value.strip_suffix(".tmp")) + else { + continue; + }; + if uuid::Uuid::parse_str(candidate).is_err() || !entry.file_type()?.is_file() { + continue; } - } - Ok(candidates) -} - -fn remove_stale_candidates(path: &Path) -> Result<()> { - for candidate in stale_candidate_paths(path)? { - fs::remove_file(&candidate) - .with_context(|| format!("failed to remove stale candidate {}", candidate.display()))?; + fs::remove_file(entry.path()).with_context(|| { + format!( + "failed to remove abandoned staging file {}", + entry.path().display() + ) + })?; } Ok(()) } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum SnapshotReplacementStage { - CandidateSynced, - TargetReplaced, -} - #[cfg(unix)] -fn replace_file(candidate: &Path, target: &Path) -> Result<()> { - fs::rename(candidate, target).with_context(|| { +fn replace_file_atomically(staged: &Path, destination: &Path) -> Result<()> { + fs::rename(staged, destination).with_context(|| { format!( "failed to atomically replace {} with {}", - target.display(), - candidate.display() + destination.display(), + staged.display() ) }) } #[cfg(windows)] -fn replace_file(candidate: &Path, target: &Path) -> Result<()> { +fn replace_file_atomically(staged: &Path, destination: &Path) -> Result<()> { use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::Storage::FileSystem::{ MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH, MoveFileExW, }; - let candidate_wide = candidate + let staged_wide = staged .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect::>(); - let target_wide = target + let destination_wide = destination .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect::>(); - let replaced = unsafe { + let success = unsafe { MoveFileExW( - candidate_wide.as_ptr(), - target_wide.as_ptr(), + staged_wide.as_ptr(), + destination_wide.as_ptr(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH, ) }; - if replaced == 0 { + if success == 0 { return Err(std::io::Error::last_os_error()).with_context(|| { format!( "failed to atomically replace {} with {}", - target.display(), - candidate.display() + destination.display(), + staged.display() ) }); } Ok(()) } -#[cfg(not(any(unix, windows)))] -fn replace_file(candidate: &Path, target: &Path) -> Result<()> { - fs::rename(candidate, target).with_context(|| { - format!( - "failed to replace {} with {}", - target.display(), - candidate.display() - ) - }) -} - #[cfg(unix)] -fn sync_parent_directory(parent: &Path) -> Result<()> { +fn sync_parent_directory(path: &Path) -> Result<()> { + let Some(parent) = path.parent() else { + return Ok(()); + }; File::open(parent) - .with_context(|| format!("failed to open directory {}", parent.display()))? - .sync_all() - .with_context(|| format!("failed to flush directory {}", parent.display())) + .and_then(|directory| directory.sync_all()) + .with_context(|| format!("failed to sync {}", parent.display())) } -#[cfg(not(unix))] -fn sync_parent_directory(_parent: &Path) -> Result<()> { +#[cfg(windows)] +fn sync_parent_directory(_path: &Path) -> Result<()> { + // MoveFileExW with MOVEFILE_WRITE_THROUGH owns publication durability. Ok(()) } @@ -1005,6 +2446,37 @@ fn sync_parent_directory(_parent: &Path) -> Result<()> { mod tests { use super::*; use pretty_assertions::assert_eq; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc; + use std::time::Duration; + + struct RefusingBatchStore { + inner: SingleFileMessagePackBackingStore, + refuse_batch: Arc, + } + + impl CacheBackingStore for RefusingBatchStore { + fn pull_all(&self) -> Result> { + self.inner.pull_all() + } + fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + self.inner.push(entry) + } + fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> { + self.inner.delete(entry) + } + fn push_all( + &mut self, + entries: &[CultCacheEnvelope], + options: PushAllOptions, + ) -> Result<()> { + if self.refuse_batch.load(Ordering::SeqCst) { + return Err(anyhow!("injected batch refusal")); + } + self.inner.push_all(entries, options) + } + } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "settings")] @@ -1025,279 +2497,111 @@ mod tests { } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] - #[cultcache(type = "runtime-policy", schema = "tests.schema_stamped_entry")] - struct SchemaStamped { - #[cultcache(key = 0)] - schema_version: String, - #[cultcache(key = 1)] - name: String, - #[cultcache(key = 2)] - value: String, + #[cultcache(type = "binary-record")] + struct BinaryRecord { + #[cultcache(key = 0, bytes)] + required: Vec, + #[cultcache(key = 1, bytes, default)] + optional: Vec, } + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + struct NestedOptionalValue { + label: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + symbol: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] + #[cultcache(type = "nested-note")] + struct NestedNote { + #[cultcache(key = 0)] + value: NestedOptionalValue, + } + + cultcache_soa!(Settings { + "theme" => |row: &Settings| row.theme.clone(), + "retries" => |row: &Settings| row.retries, + }); + cultcache_registry!(TestEntries { Settings, Note }); - fn test_envelope(value: &str) -> CultCacheEnvelope { - CultCacheEnvelope { - key: "state".to_string(), - r#type: "test-state".to_string(), - payload: value.as_bytes().to_vec(), - stored_at: "2026-07-15T00:00:00Z".to_string(), - schema_id: Some("tests.test_state.v1".to_string()), - } - } - #[test] - fn interruption_before_replacement_preserves_committed_snapshot() -> Result<()> { + fn captured_snapshot_cannot_outlive_a_shared_consequence_gate() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("authority.cc"); let mut store = SingleFileMessagePackBackingStore::new(&store_path); - let old = test_envelope("old"); - let new = test_envelope("new"); - store.push_all(std::slice::from_ref(&old), PushAllOptions::default())?; - - let result = store.write_all_unlocked_with_observer(std::slice::from_ref(&new), |stage| { - assert_eq!(stage, SnapshotReplacementStage::CandidateSynced); - assert!( - store_path.exists(), - "the committed pathname must never be removed" - ); - Err(anyhow!("simulated interruption before replacement")) - }); - - assert!(result.is_err()); - let recovered = store.pull_all()?; - assert_eq!(recovered.len(), 1); - assert_eq!(recovered[0].payload, old.payload); - assert_eq!(stale_candidate_paths(&store_path)?.len(), 1); - - store.push_all(std::slice::from_ref(&new), PushAllOptions::default())?; - let recovered = store.pull_all()?; - assert_eq!(recovered.len(), 1); - assert_eq!(recovered[0].payload, new.payload); - assert!(stale_candidate_paths(&store_path)?.is_empty()); - Ok(()) - } - - #[test] - fn interruption_after_replacement_recovers_new_snapshot() -> Result<()> { - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let mut store = SingleFileMessagePackBackingStore::new(&store_path); - let old = test_envelope("old"); - let new = test_envelope("new"); - store.push_all(std::slice::from_ref(&old), PushAllOptions::default())?; - - let result = store.write_all_unlocked_with_observer(std::slice::from_ref(&new), |stage| { - if stage == SnapshotReplacementStage::TargetReplaced { - return Err(anyhow!("simulated interruption after replacement")); - } - Ok(()) - }); - - let error = result.expect_err("the injected post-replacement failure must surface"); - let uncertain = error - .downcast_ref::() - .expect("post-replacement failures must be typed as committed but uncertain"); - assert_eq!(uncertain.path(), store_path.as_path()); - assert!(uncertain.detail().contains("simulated interruption")); - let recovered = store.pull_all()?; - assert_eq!(recovered.len(), 1); - assert_eq!(recovered[0].payload, new.payload); - assert!(stale_candidate_paths(&store_path)?.is_empty()); - Ok(()) - } - - #[test] - fn first_write_interruption_before_replacement_leaves_store_uncommitted() -> Result<()> { - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let store = SingleFileMessagePackBackingStore::new(&store_path); - let new = test_envelope("new"); - - let result = store.write_all_unlocked_with_observer(std::slice::from_ref(&new), |stage| { - assert_eq!(stage, SnapshotReplacementStage::CandidateSynced); - Err(anyhow!("simulated interruption before first replacement")) - }); - - assert!(result.is_err()); - assert!(!store_path.exists()); - assert!(store.pull_all()?.is_empty()); - assert_eq!(stale_candidate_paths(&store_path)?.len(), 1); - Ok(()) - } - - #[test] - fn first_write_interruption_after_replacement_exposes_complete_snapshot() -> Result<()> { - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let store = SingleFileMessagePackBackingStore::new(&store_path); - let new = test_envelope("new"); - - let result = store.write_all_unlocked_with_observer(std::slice::from_ref(&new), |stage| { - if stage == SnapshotReplacementStage::TargetReplaced { - return Err(anyhow!("simulated interruption after first replacement")); - } - Ok(()) - }); - - let error = result.expect_err("the injected post-replacement failure must surface"); - assert!( - error - .downcast_ref::() - .is_some() - ); - let recovered = store.pull_all()?; - assert_eq!(recovered.len(), 1); - assert_eq!(recovered[0].payload, new.payload); - assert!(stale_candidate_paths(&store_path)?.is_empty()); - Ok(()) - } - - #[test] - fn unlock_failure_never_masks_the_action_outcome() -> Result<()> { - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let store = SingleFileMessagePackBackingStore::new(&store_path); - - let completed = store.with_exclusive_lock_using_unlock( - || Ok(42_u32), - |_| Err(std::io::Error::other("simulated unlock failure")), - )?; - assert_eq!(completed, 42); - - let uncertain: Result<()> = store.with_exclusive_lock_using_unlock( - || { - Err(anyhow::Error::new( - CommittedSnapshotDurabilityUncertain::new( - store_path.clone(), - "primary post-commit failure", - ), - )) - }, - |_| Err(std::io::Error::other("secondary unlock failure")), - ); - let error = uncertain.expect_err("the action error must survive unlock cleanup"); - let error = error - .downcast_ref::() - .expect("the primary typed outcome must not be masked"); - assert!(error.detail().contains("primary post-commit failure")); - assert!(!error.detail().contains("secondary unlock failure")); - - // Both injected unlock failures still close their lock handles. A new - // exclusive operation must therefore acquire the lock normally. - store.with_exclusive_lock(|| Ok(()))?; - Ok(()) - } - - #[test] - fn stale_cleanup_preserves_non_uuid_lookalikes() -> Result<()> { - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let lookalike = temp.path().join("cache.msgpack.not-a-uuid.tmp"); - fs::write(&lookalike, b"belongs to somebody else")?; - - let mut store = SingleFileMessagePackBackingStore::new(&store_path); - store.push_all(&[test_envelope("new")], PushAllOptions::default())?; - - assert_eq!(fs::read(&lookalike)?, b"belongs to somebody else"); - assert!(stale_candidate_paths(&store_path)?.is_empty()); - Ok(()) - } - - #[cfg(unix)] - #[test] - fn stale_candidate_symlink_is_removed_without_clobbering_its_target() -> Result<()> { - use std::os::unix::fs::symlink; - - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let victim_path = temp.path().join("victim.txt"); - fs::write(&victim_path, b"untouched")?; - let stale_path = temporary_path_for(&store_path); - symlink(&victim_path, &stale_path)?; - - let mut store = SingleFileMessagePackBackingStore::new(&store_path); - store.push_all(&[test_envelope("new")], PushAllOptions::default())?; - - assert_eq!(fs::read(&victim_path)?, b"untouched"); - assert!(!stale_path.exists()); - assert!(stale_candidate_paths(&store_path)?.is_empty()); - Ok(()) - } - - #[cfg(unix)] - #[test] - fn non_utf8_target_names_recover_their_own_candidates() -> Result<()> { - use std::ffi::OsString; - use std::os::unix::ffi::OsStringExt; - - let temp = tempfile::tempdir()?; - let store_name = OsString::from_vec(b"cache-\xff.msgpack".to_vec()); - let store_path = temp.path().join(store_name); - let mut store = SingleFileMessagePackBackingStore::new(&store_path); - let new = test_envelope("new"); - - let interrupted = store - .write_all_unlocked_with_observer(std::slice::from_ref(&new), |_| { - Err(anyhow!("simulated interruption")) - }); - assert!(interrupted.is_err()); - assert_eq!(stale_candidate_paths(&store_path)?.len(), 1); - - store.push_all(std::slice::from_ref(&new), PushAllOptions::default())?; - assert!(stale_candidate_paths(&store_path)?.is_empty()); - assert_eq!(store.pull_all()?[0].payload, new.payload); - Ok(()) - } - - #[test] - fn two_contending_writers_leave_one_complete_snapshot() -> Result<()> { - use std::sync::{Arc, Barrier}; - - let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); - let barrier = Arc::new(Barrier::new(3)); - let spawn_writer = |marker: u8| { - let store_path = store_path.clone(); - let barrier = Arc::clone(&barrier); - std::thread::spawn(move || -> Result<()> { - let mut entries = Vec::new(); - for index in 0..128 { - let mut entry = test_envelope(std::str::from_utf8(&[marker]).unwrap()); - entry.key = format!("state-{index:03}"); - entry.payload = vec![marker; 4096]; - entries.push(entry); - } - let mut store = SingleFileMessagePackBackingStore::new(store_path); - barrier.wait(); - store.push_all(&entries, PushAllOptions::default()) - }) + let released = CultCacheEnvelope { + key: "brake".into(), + r#type: "brake".into(), + payload: b"released".to_vec(), + stored_at: "2026-07-20T00:00:00Z".into(), + schema_id: Some("brake".into()), }; + store.push(&released)?; - let writer_a = spawn_writer(b'a'); - let writer_b = spawn_writer(b'b'); - barrier.wait(); - writer_a.join().expect("writer A panicked")?; - writer_b.join().expect("writer B panicked")?; + let writer_path = store_path.clone(); + let (started_tx, started_rx) = mpsc::channel(); + let (finished_tx, finished_rx) = mpsc::channel(); + store.with_read_only_shared_snapshot(|captured| { + assert_eq!(captured, vec![released.clone()]); + std::thread::spawn(move || { + started_tx.send(()).unwrap(); + let engaged = CultCacheEnvelope { + payload: b"engaged".to_vec(), + ..released + }; + let mut writer = SingleFileMessagePackBackingStore::new(writer_path); + writer.push(&engaged).unwrap(); + finished_tx.send(()).unwrap(); + }); + started_rx.recv_timeout(Duration::from_secs(1))?; + assert!( + finished_rx + .recv_timeout(Duration::from_millis(100)) + .is_err() + ); + Ok(()) + })?; + finished_rx.recv_timeout(Duration::from_secs(1))?; + assert_eq!(store.pull_all()?[0].payload, b"engaged"); + Ok(()) + } - let recovered = SingleFileMessagePackBackingStore::new(&store_path).pull_all()?; - assert_eq!(recovered.len(), 128); - let winner = recovered[0].payload[0]; - assert!(winner == b'a' || winner == b'b'); - assert!( - recovered - .iter() - .all(|entry| entry.payload == vec![winner; 4096]) + #[test] + fn post_action_unlock_failure_is_diagnostic_not_action_result() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("authority.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + store.push(&CultCacheEnvelope { + key: "brake".into(), + r#type: "brake".into(), + payload: b"released".to_vec(), + stored_at: "2026-07-20T00:00:00Z".into(), + schema_id: Some("brake".into()), + })?; + + let mut diagnostic = None; + let result = store.with_read_only_shared_snapshot_using( + |_| Ok(42), + |_| Err(anyhow!("injected post-action unlock failure")), + |error| diagnostic = Some(error.to_string()), + ); + + assert_eq!(result?, 42); + assert_eq!( + diagnostic.as_deref(), + Some("injected post-action unlock failure") ); - assert!(stale_candidate_paths(&store_path)?.is_empty()); Ok(()) } #[test] fn familiar_cultcache_flow_persists_and_reloads_typed_documents() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let settings = Settings { theme: "ash".to_string(), retries: 3, @@ -1321,7 +2625,7 @@ mod tests { #[test] fn entry_identity_is_polymorphic_by_type_and_key() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let mut cache = CultCache::new(); cache.register_registry(TestEntries)?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); @@ -1347,11 +2651,751 @@ mod tests { Ok(()) } + #[test] + fn prepared_batch_publishes_heterogeneous_entries_in_one_snapshot() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("batch.cc"); + let mut cache = CultCache::new(); + cache.register_registry(TestEntries)?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + + let (settings, _) = cache.prepare_entry( + "app", + &Settings { + theme: "iron".to_string(), + retries: 4, + }, + )?; + let (note, _) = cache.prepare_entry( + "receipt", + &Note { + title: "committed".to_string(), + body: "same snapshot".to_string(), + }, + )?; + cache.put_prepared_batch(vec![settings, note])?; + + let mut reloaded = CultCache::new(); + reloaded.register_registry(TestEntries)?; + reloaded.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + reloaded.pull_all_backing_stores()?; + assert_eq!(reloaded.get_required::("app")?.theme, "iron"); + assert_eq!(reloaded.get_required::("receipt")?.title, "committed"); + Ok(()) + } + + #[test] + fn named_preparation_preserves_omitted_nested_optional_slots() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("named.cc"); + let mut cache = CultCache::new(); + cache.register_entry_type::()?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + cache.pull_all_backing_stores()?; + let note = NestedNote { + value: NestedOptionalValue { + label: "nested".into(), + count: None, + symbol: Some("kept-in-its-own-field".into()), + }, + }; + assert!(cache.prepare_entry("compact", ¬e).is_err()); + let (entry, parsed) = cache.prepare_entry_named("named", ¬e)?; + assert_eq!(parsed, note); + cache.put_prepared_batch(vec![entry])?; + assert_eq!(cache.get_required::("named")?, note); + Ok(()) + } + + #[test] + fn pulling_an_absent_store_does_not_create_its_parent_or_lock() -> Result<()> { + let temp = tempfile::tempdir()?; + let missing_parent = temp.path().join("missing-body"); + let store_path = missing_parent.join("cache.cc"); + let store = SingleFileMessagePackBackingStore::new(&store_path); + + assert!(store.pull_all()?.is_empty()); + assert!(!missing_parent.exists()); + assert!(!store_path.with_file_name("cache.cc.lock").exists()); + Ok(()) + } + + #[test] + fn provider_snapshot_read_does_not_create_or_open_a_lock() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("provider.cc"); + let lock_path = temp.path().join("provider.cc.lock"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let expected = test_envelope("provider.delivery", "one", b"payload"); + store.push(&expected)?; + std::fs::remove_file(&lock_path)?; + + assert_eq!(store.pull_all_read_only_snapshot()?, vec![expected]); + assert!(!lock_path.exists()); + Ok(()) + } + + #[test] + fn next_exclusive_write_removes_only_exact_abandoned_staging_files() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cache.cc"); + let abandoned = temporary_path_for(&store_path); + let unknown = temp.path().join("cache.cc.not-a-uuid.tmp"); + let foreign = temp + .path() + .join("other.cc.00000000-0000-0000-0000-000000000000.tmp"); + fs::write(&abandoned, b"partial staging bytes")?; + fs::write(&unknown, b"operator-owned unknown file")?; + fs::write(&foreign, b"foreign store staging")?; + + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let expected = test_envelope("model", "current", b"one"); + store.push(&expected)?; + + assert!(!abandoned.exists()); + assert_eq!(fs::read(&unknown)?, b"operator-owned unknown file"); + assert_eq!(fs::read(&foreign)?, b"foreign store staging"); + assert_eq!(store.pull_all()?, vec![expected]); + Ok(()) + } + + #[test] + fn failed_snapshot_publication_preserves_the_committed_destination() -> Result<()> { + let temp = tempfile::tempdir()?; + let destination = temp.path().join("cache.cc"); + let absent_staging = temp.path().join("absent.tmp"); + let committed = b"committed snapshot"; + fs::write(&destination, committed)?; + + assert!(replace_file_atomically(&absent_staging, &destination).is_err()); + assert_eq!(fs::read(&destination)?, committed); + Ok(()) + } + + #[test] + fn snapshot_publication_atomically_replaces_an_existing_destination() -> Result<()> { + let temp = tempfile::tempdir()?; + let destination = temp.path().join("cache.cc"); + let staged = temp.path().join("cache.cc.staged"); + fs::write(&destination, b"old snapshot")?; + fs::write(&staged, b"new snapshot")?; + + replace_file_atomically(&staged, &destination)?; + + assert_eq!(fs::read(&destination)?, b"new snapshot"); + assert!(!staged.exists()); + Ok(()) + } + + #[test] + #[ignore = "operator SIGKILL probe; requires CULTCACHE_INTERRUPTION_STORE"] + fn snapshot_publication_interruption_probe_writer() -> Result<()> { + let store_path = std::env::var_os("CULTCACHE_INTERRUPTION_STORE") + .map(PathBuf::from) + .ok_or_else(|| anyhow!("CULTCACHE_INTERRUPTION_STORE is required"))?; + let marker_path = std::env::var_os("CULTCACHE_INTERRUPTION_MARKER") + .map(PathBuf::from) + .ok_or_else(|| anyhow!("CULTCACHE_INTERRUPTION_MARKER is required"))?; + let mut store = SingleFileMessagePackBackingStore::new(store_path); + for sequence in 0_u64.. { + store.push(&test_envelope( + "interruption.probe", + "current", + &rmp_serde::to_vec(&sequence)?, + ))?; + fs::write(&marker_path, sequence.to_string())?; + } + unreachable!("the interruption writer is stopped by the operator") + } + + #[test] + #[ignore = "operator SIGKILL probe; requires CULTCACHE_INTERRUPTION_STORE"] + fn snapshot_publication_interruption_probe_reader() -> Result<()> { + let store_path = std::env::var_os("CULTCACHE_INTERRUPTION_STORE") + .map(PathBuf::from) + .ok_or_else(|| anyhow!("CULTCACHE_INTERRUPTION_STORE is required"))?; + let rows = SingleFileMessagePackBackingStore::new(store_path).pull_all()?; + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].r#type, "interruption.probe"); + assert_eq!(rows[0].key, "current"); + let _: u64 = rmp_serde::from_slice(&rows[0].payload)?; + Ok(()) + } + + fn test_envelope(r#type: &str, key: &str, payload: &[u8]) -> CultCacheEnvelope { + CultCacheEnvelope { + key: key.to_string(), + r#type: r#type.to_string(), + payload: payload.to_vec(), + stored_at: "2026-07-13T00:00:00Z".to_string(), + schema_id: Some(r#type.to_string()), + } + } + + #[test] + fn redb_store_crud_and_reopen_preserve_polymorphic_rows() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("keyed.cc"); + let model = test_envelope("model", "shared", b"one"); + let receipt = test_envelope("receipt", "shared", b"two"); + { + let mut store = RedbMessagePackBackingStore::new(&path)?; + store.push(&model)?; + store.push(&receipt)?; + assert_eq!(store.pull_all()?.len(), 2); + store.delete(&receipt)?; + } + let reopened = RedbMessagePackBackingStore::new(&path)?; + assert_eq!(reopened.pull_all()?, vec![model]); + Ok(()) + } + + #[test] + fn redb_exact_cas_refuses_stale_value_without_mutation() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut store = RedbMessagePackBackingStore::new(temp.path().join("cas.cc"))?; + let current = test_envelope("model", "current", b"one"); + store.push(¤t)?; + let stale = test_envelope("model", "current", b"stale"); + let replacement = test_envelope("model", "current", b"two"); + assert!(!store.compare_and_swap_entry(&stale, replacement.clone())?); + assert_eq!(store.pull_all()?, vec![current.clone()]); + assert!(store.compare_and_swap_entry(¤t, replacement.clone())?); + assert_eq!(store.pull_all()?, vec![replacement]); + Ok(()) + } + + #[test] + fn redb_batch_is_atomic_and_stale_member_refuses_every_write() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut store = RedbMessagePackBackingStore::new(temp.path().join("batch.cc"))?; + let first = test_envelope("model", "first", b"one"); + let second = test_envelope("model", "second", b"one"); + store.push(&first)?; + store.push(&second)?; + let stale_second = test_envelope("model", "second", b"stale"); + let first_two = test_envelope("model", "first", b"two"); + let second_two = test_envelope("model", "second", b"two"); + let receipt = test_envelope("receipt", "batch", b"committed"); + assert!(!store.compare_and_swap_batch( + &[first.clone(), stale_second], + vec![first_two.clone(), second_two.clone(), receipt.clone()], + )?); + assert_eq!(store.pull_all()?, vec![first.clone(), second.clone()]); + assert!(store.compare_and_swap_batch( + &[first, second], + vec![first_two.clone(), second_two.clone(), receipt.clone()], + )?); + assert_eq!(store.pull_all()?, vec![first_two, second_two, receipt]); + Ok(()) + } + + #[test] + fn redb_compaction_refuses_stale_snapshot_and_atomically_replaces_and_deletes() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut store = RedbMessagePackBackingStore::new(temp.path().join("compact.cc"))?; + let old_head = test_envelope("retention", "current", b"one"); + let retired = test_envelope("receipt", "old", b"terminal"); + store.push(&old_head)?; + store.push(&retired)?; + let stale = store.pull_all()?; + let concurrent = test_envelope("receipt", "running", b"active"); + assert!(store.insert_entry_if_absent(concurrent.clone())?); + let new_head = test_envelope("retention", "current", b"two"); + assert!(!store.replace_and_delete_if_snapshot_unchanged( + &stale, + vec![new_head.clone()], + std::slice::from_ref(&retired), + )?); + assert!(store.pull_all()?.contains(&retired)); + let exact = store.pull_all()?; + assert!(store.replace_and_delete_if_snapshot_unchanged( + &exact, + vec![new_head.clone()], + &[retired], + )?); + assert_eq!(store.pull_all()?, vec![concurrent, new_head]); + Ok(()) + } + + #[test] + fn redb_entry_cas_does_not_depend_on_unrelated_snapshot_changes() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = RedbMessagePackBackingStore::new(temp.path().join("independent.cc"))?; + let current = test_envelope("model", "current", b"one"); + assert!(store.insert_entry_if_absent(current.clone())?); + let unrelated = test_envelope("event", "later", b"noise"); + assert!(store.insert_entry_if_absent(unrelated.clone())?); + let replacement = test_envelope("model", "current", b"two"); + assert!(store.compare_and_swap_entry(¤t, replacement.clone())?); + assert_eq!(store.pull_all()?, vec![unrelated, replacement]); + Ok(()) + } + + #[test] + fn redb_fresh_handles_share_one_database_for_the_same_cultcache_path() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("shared.cc"); + let mut first = RedbMessagePackBackingStore::new(&path)?; + let second = RedbMessagePackBackingStore::new(&path)?; + let initial = test_envelope("model", "current", b"one"); + first.push(&initial)?; + assert_eq!(second.pull_all()?, vec![initial.clone()]); + let replacement = test_envelope("model", "current", b"two"); + assert!(second.compare_and_swap_entry(&initial, replacement.clone())?); + assert_eq!(first.pull_all()?, vec![replacement]); + Ok(()) + } + + #[test] + fn owned_redb_clones_share_authority_and_fresh_owner_is_refused() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("owned.cc"); + let owner = OwnedRedbMessagePackBackingStore::new(&path)?; + let clone = owner.clone(); + assert_eq!(owner.file_identity(), clone.file_identity()); + assert!(OwnedRedbMessagePackBackingStore::new(&path).is_err()); + drop(owner); + assert!(OwnedRedbMessagePackBackingStore::new(&path).is_err()); + drop(clone); + assert!(OwnedRedbMessagePackBackingStore::new(&path).is_ok()); + Ok(()) + } + + #[test] + fn owned_redb_file_identity_is_stable_across_writes_and_reopen() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("identity.cc"); + let identity = { + let mut owner = OwnedRedbMessagePackBackingStore::new(&path)?; + let identity = owner.file_identity().to_string(); + owner.push(&test_envelope("model", "current", b"one"))?; + owner.validate_path_identity()?; + owner.require_file_identity(&identity)?; + identity + }; + let reopened = OwnedRedbMessagePackBackingStore::new(&path)?; + assert_eq!(reopened.file_identity(), identity); + assert_eq!(reopened.pull_all()?.len(), 1); + Ok(()) + } + + #[test] + fn owned_redb_identity_helper_refuses_mismatch() -> Result<()> { + let temp = tempfile::tempdir()?; + let owner = OwnedRedbMessagePackBackingStore::new(temp.path().join("mismatch.cc"))?; + assert!(owner.require_file_identity("not-the-owned-file").is_err()); + owner.require_file_identity(owner.file_identity())?; + Ok(()) + } + + #[test] + fn owned_redb_batch_refuses_stale_member_and_commits_success_atomically() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut owner = OwnedRedbMessagePackBackingStore::new(temp.path().join("batch-owned.cc"))?; + let first = test_envelope("model", "first", b"one"); + let second = test_envelope("model", "second", b"one"); + owner.push(&first)?; + owner.push(&second)?; + let first_two = test_envelope("model", "first", b"two"); + let second_two = test_envelope("model", "second", b"two"); + let companion = test_envelope("receipt", "batch", b"committed"); + let stale_second = test_envelope("model", "second", b"stale"); + assert!(!owner.compare_and_swap_batch( + &[first.clone(), stale_second], + vec![first_two.clone(), second_two.clone(), companion.clone()], + )?); + assert_eq!(owner.pull_all()?, vec![first.clone(), second.clone()]); + assert!(owner.compare_and_swap_batch( + &[first, second], + vec![first_two.clone(), second_two.clone(), companion.clone()], + )?); + assert_eq!(owner.pull_all()?, vec![first_two, second_two, companion]); + Ok(()) + } + + #[test] + fn owned_redb_compaction_refuses_stale_snapshot_and_atomically_replaces_and_deletes() + -> Result<()> { + let temp = tempfile::tempdir()?; + let mut owner = + OwnedRedbMessagePackBackingStore::new(temp.path().join("compact-owned.cc"))?; + let old_head = test_envelope("retention", "current", b"one"); + let retired_a = test_envelope("claim", "a", b"terminal"); + let retired_b = test_envelope("receipt", "a", b"terminal"); + owner.push(&old_head)?; + owner.push(&retired_a)?; + owner.push(&retired_b)?; + let stale = owner.pull_all()?; + let concurrent = test_envelope("claim", "concurrent", b"running"); + assert!(owner.insert_entry_if_absent(concurrent.clone())?); + let new_head = test_envelope("retention", "current", b"two"); + assert!(!owner.replace_and_delete_if_snapshot_unchanged( + &stale, + vec![new_head.clone()], + &[retired_a.clone(), retired_b.clone()], + )?); + assert!(owner.pull_all()?.contains(&retired_a)); + let exact = owner.pull_all()?; + assert!(owner.replace_and_delete_if_snapshot_unchanged( + &exact, + vec![new_head.clone()], + &[retired_a, retired_b], + )?); + assert_eq!(owner.pull_all()?, vec![concurrent, new_head]); + Ok(()) + } + + #[test] + fn owned_redb_path_replacement_cannot_redirect_pinned_authority() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("pinned.cc"); + let displaced = temp.path().join("displaced.cc"); + let mut owner = OwnedRedbMessagePackBackingStore::new(&path)?; + let identity = owner.file_identity().to_string(); + owner.push(&test_envelope("model", "before", b"one"))?; + fs::rename(&path, &displaced)?; + File::create(&path)?; + assert!(owner.validate_path_identity().is_err()); + assert_eq!(owner.file_identity(), identity); + owner.push(&test_envelope("model", "after", b"two"))?; + assert_eq!(owner.pull_all()?.len(), 2); + assert_ne!( + file_identity_from_file(&File::open(&path)?)?, + owner.file_identity() + ); + Ok(()) + } + + #[test] + fn redb_overlapping_fresh_handles_serialize_the_database_open_interval() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("contended.cc"); + let first = RedbMessagePackBackingStore::new(&path)?; + let second = RedbMessagePackBackingStore::new(&path)?; + let barrier = std::sync::Arc::new(std::sync::Barrier::new(3)); + let first_barrier = barrier.clone(); + let first_thread = std::thread::spawn(move || -> Result<()> { + first_barrier.wait(); + for index in 0..32 { + first.insert_entry_if_absent(test_envelope( + "event", + &format!("first-{index}"), + b"first", + ))?; + } + Ok(()) + }); + let second_barrier = barrier.clone(); + let second_thread = std::thread::spawn(move || -> Result<()> { + second_barrier.wait(); + for index in 0..32 { + second.insert_entry_if_absent(test_envelope( + "event", + &format!("second-{index}"), + b"second", + ))?; + } + Ok(()) + }); + barrier.wait(); + first_thread.join().expect("first redb writer")?; + second_thread.join().expect("second redb writer")?; + assert_eq!( + RedbMessagePackBackingStore::new(path)?.pull_all()?.len(), + 64 + ); + Ok(()) + } + + #[test] + fn redb_accepts_a_bare_relative_cultcache_path() -> Result<()> { + let name = format!("cultcache-redb-relative-{}.cc", uuid::Uuid::new_v4()); + let path = PathBuf::from(&name); + let lock_path = PathBuf::from(format!("{name}.lock")); + let store = RedbMessagePackBackingStore::new(&path)?; + assert!(store.insert_entry_if_absent(test_envelope("model", "current", b"one"))?); + assert_eq!(store.pull_all()?.len(), 1); + fs::remove_file(path)?; + fs::remove_file(lock_path)?; + Ok(()) + } + + #[test] + fn conditional_batch_replaces_expected_and_inserts_companion_atomically() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("conditional-batch.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let current = test_envelope("model", "current", b"revision-1"); + store.push(¤t)?; + let replacement = test_envelope("model", "current", b"revision-2"); + let companion = test_envelope("receipt", "migration-1", b"applied"); + + assert!(store.compare_and_swap_batch( + std::slice::from_ref(¤t), + vec![replacement.clone(), companion.clone()], + )?); + assert_eq!(store.pull_all()?, vec![replacement, companion]); + Ok(()) + } + + #[test] + fn conditional_batch_allows_atomic_first_import_when_identities_are_absent() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("conditional-import.cc"); + let store = SingleFileMessagePackBackingStore::new(&store_path); + let model = test_envelope("model", "current", b"revision-1"); + let receipt = test_envelope("receipt", "migration-1", b"applied"); + + assert!(store.compare_and_swap_batch(&[], vec![model.clone(), receipt.clone()])?); + assert_eq!(store.pull_all()?, vec![model, receipt]); + Ok(()) + } + + #[test] + fn conditional_batch_stale_expected_and_companion_collision_preserve_exact_bytes() -> Result<()> + { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("conditional-refusal.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let current = test_envelope("model", "current", b"revision-1"); + let occupied = test_envelope("receipt", "migration-1", b"someone-else"); + store.push(¤t)?; + store.push(&occupied)?; + let before = fs::read(&store_path)?; + + let stale = test_envelope("model", "current", b"stale-copy"); + let replacement = test_envelope("model", "current", b"revision-2"); + let fresh_companion = test_envelope("receipt", "migration-2", b"applied"); + assert!( + !store.compare_and_swap_batch(&[stale], vec![replacement.clone(), fresh_companion],)? + ); + assert_eq!(fs::read(&store_path)?, before); + + assert!(!store.compare_and_swap_batch( + &[current], + vec![ + replacement, + test_envelope("receipt", "migration-1", b"collision") + ], + )?); + assert_eq!(fs::read(&store_path)?, before); + Ok(()) + } + + #[test] + fn conditional_batch_rejects_empty_replacements_and_duplicate_identities() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("invalid-batch.cc")); + let entry = test_envelope("model", "current", b"one"); + + assert!( + store + .compare_and_swap_batch(&[entry.clone()], Vec::new()) + .is_err() + ); + assert!( + store + .compare_and_swap_batch(&[], vec![entry.clone(), entry.clone()]) + .is_err() + ); + assert!( + store + .compare_and_swap_batch( + &[entry.clone(), entry.clone()], + vec![test_envelope("model", "current", b"two")], + ) + .is_err() + ); + assert!(!store.path().exists()); + Ok(()) + } + + #[test] + fn full_snapshot_replacement_and_append_refuses_concurrent_rows() -> Result<()> { + let temp = tempfile::tempdir()?; + let snapshot_path = temp.path().join("snapshot-replace-append.cc"); + let mut snapshot_store = SingleFileMessagePackBackingStore::new(&snapshot_path); + let current = test_envelope("session", "current", b"active"); + snapshot_store.push(¤t)?; + let stale_snapshot = snapshot_store.pull_all()?; + let concurrent = test_envelope("event", "concurrent", b"live"); + snapshot_store.push(&concurrent)?; + let completed = test_envelope("session", "current", b"completed"); + let receipt = test_envelope("receipt", "terminal", b"done"); + assert!(!snapshot_store.replace_and_append_if_snapshot_unchanged( + &stale_snapshot, + vec![completed.clone(), receipt.clone()], + )?); + assert_eq!( + snapshot_store.pull_all()?, + vec![concurrent.clone(), current.clone()] + ); + let exact_snapshot = snapshot_store.pull_all()?; + assert!(snapshot_store.replace_and_append_if_snapshot_unchanged( + &exact_snapshot, + vec![completed.clone(), receipt.clone()], + )?); + assert_eq!( + snapshot_store.pull_all()?, + vec![concurrent.clone(), receipt.clone(), completed.clone()] + ); + + let keyed_path = temp.path().join("keyed-replace-append.redb"); + let mut keyed_store = RedbMessagePackBackingStore::new(&keyed_path)?; + keyed_store.push(¤t)?; + let stale_snapshot = keyed_store.pull_all()?; + keyed_store.push(&concurrent)?; + assert!(!keyed_store.replace_and_append_if_snapshot_unchanged( + &stale_snapshot, + vec![completed.clone(), receipt.clone()], + )?); + let exact_snapshot = keyed_store.pull_all()?; + assert!(keyed_store.replace_and_append_if_snapshot_unchanged( + &exact_snapshot, + vec![completed.clone(), receipt.clone()], + )?); + assert_eq!( + keyed_store.pull_all()?, + vec![concurrent, receipt, completed] + ); + Ok(()) + } + + #[test] + fn single_file_compaction_atomically_replaces_summary_and_deletes_history() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("conditional-compaction.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let history = test_envelope("history", "old", b"closed"); + let head = test_envelope("retention", "head", b"revision-1"); + store.push(&history)?; + store.push(&head)?; + let snapshot = store.pull_all()?; + let next_head = test_envelope("retention", "head", b"revision-2"); + + assert!(store.replace_and_delete_if_snapshot_unchanged( + &snapshot, + vec![next_head.clone()], + std::slice::from_ref(&history), + )?); + assert_eq!(store.pull_all()?, vec![next_head]); + + let before = fs::read(&store_path)?; + assert!(!store.replace_and_delete_if_snapshot_unchanged( + &snapshot, + vec![test_envelope("retention", "head", b"revision-3")], + std::slice::from_ref(&history), + )?); + assert_eq!(fs::read(&store_path)?, before); + Ok(()) + } + + #[test] + fn snapshot_append_fences_concurrent_unknown_identity() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("snapshot-append.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let model = test_envelope("model", "current", b"revision-1"); + store.push(&model)?; + let expected = store.pull_all()?; + let duplicate_admission = test_envelope("admission", "unexpected", b"also-current"); + store.push(&duplicate_admission)?; + let before = fs::read(&store_path)?; + + assert!(!store.append_if_snapshot_unchanged( + &expected, + vec![test_envelope("readiness", "proof", b"ready")], + )?); + assert_eq!(fs::read(&store_path)?, before); + Ok(()) + } + + #[test] + fn snapshot_append_is_order_insensitive_and_refuses_collisions() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("snapshot-order.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let a = test_envelope("a", "one", b"1"); + let b = test_envelope("b", "two", b"2"); + store.push(&a)?; + store.push(&b)?; + assert!(store.append_if_snapshot_unchanged( + &[b.clone(), a.clone()], + vec![test_envelope("proof", "three", b"3")], + )?); + let current = store.pull_all()?; + assert!(!store.append_if_snapshot_unchanged( + ¤t, + vec![test_envelope("a", "one", b"collision")], + )?); + assert!( + store + .append_if_snapshot_unchanged(¤t, Vec::new()) + .is_err() + ); + Ok(()) + } + + #[test] + fn type_and_key_identity_is_not_delimiter_ambiguous() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("delimiter-identity.cc"); + let mut store = SingleFileMessagePackBackingStore::new(&store_path); + let left = test_envelope("a::b", "c", b"left"); + let right = test_envelope("a", "b::c", b"right"); + store.push(&left)?; + store.push(&right)?; + let rows = store.pull_all()?; + assert_eq!(rows.len(), 2); + assert!(rows.contains(&left)); + assert!(rows.contains(&right)); + Ok(()) + } + + #[test] + fn refused_batch_preserves_prior_snapshot() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("refused-batch.cc"); + let refusal = Arc::new(AtomicBool::new(false)); + let mut cache = CultCache::new(); + cache.register_registry(TestEntries)?; + cache.add_generic_backing_store(RefusingBatchStore { + inner: SingleFileMessagePackBackingStore::new(&store_path), + refuse_batch: refusal.clone(), + }); + cache.put( + "app", + &Settings { + theme: "before".to_string(), + retries: 1, + }, + )?; + let (note, _) = cache.prepare_entry( + "receipt", + &Note { + title: "after".to_string(), + body: "must not land".to_string(), + }, + )?; + refusal.store(true, Ordering::SeqCst); + assert!(cache.put_prepared_batch(vec![note]).is_err()); + assert!(cache.get::("receipt")?.is_none()); + + let mut reloaded = CultCache::new(); + reloaded.register_registry(TestEntries)?; + reloaded.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + reloaded.pull_all_backing_stores()?; + assert_eq!(reloaded.get_required::("app")?.theme, "before"); + assert!(reloaded.get::("receipt")?.is_none()); + Ok(()) + } + #[test] fn type_specific_store_routes_before_generic_store() -> Result<()> { let temp = tempfile::tempdir()?; - let generic_path = temp.path().join("generic.msgpack"); - let settings_path = temp.path().join("settings.msgpack"); + let generic_path = temp.path().join("generic.cc"); + let settings_path = temp.path().join("settings.cc"); let mut cache = CultCache::new(); cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -1386,7 +3430,7 @@ mod tests { #[test] fn update_and_delete_follow_the_cache_api() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let mut cache = CultCache::new(); cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); @@ -1408,7 +3452,7 @@ mod tests { #[test] fn pull_rejects_unregistered_persisted_entry_type() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let mut store = SingleFileMessagePackBackingStore::new(&store_path); store.push(&CultCacheEnvelope { key: "unknown".to_string(), @@ -1432,7 +3476,7 @@ mod tests { #[test] fn payload_is_binary_messagepack_not_json_value() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let mut cache = CultCache::new(); cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); @@ -1451,10 +3495,23 @@ mod tests { Ok(()) } + #[test] + fn byte_slots_use_messagepack_binary_and_round_trip() -> Result<()> { + let record = BinaryRecord { + required: vec![1, 2, 3], + optional: Vec::new(), + }; + + let payload = rmp_serde::to_vec(&record)?; + assert_eq!(payload, vec![0x92, 0xc4, 3, 1, 2, 3, 0xc4, 0]); + assert_eq!(rmp_serde::from_slice::(&payload)?, record); + Ok(()) + } + #[test] fn corrupted_payload_fails_during_typed_retrieval() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("cache.msgpack"); + let store_path = temp.path().join("cache.cc"); let mut store = SingleFileMessagePackBackingStore::new(&store_path); store.push(&CultCacheEnvelope { key: "app".to_string(), @@ -1480,8 +3537,8 @@ mod tests { #[test] fn put_envelope_reuses_existing_messagepack_payload() -> Result<()> { let temp = tempfile::tempdir()?; - let origin_store = temp.path().join("origin.msgpack"); - let target_store = temp.path().join("target.msgpack"); + let origin_store = temp.path().join("origin.cc"); + let target_store = temp.path().join("target.cc"); let mut origin = CultCache::new(); origin.register_entry_type::()?; @@ -1517,38 +3574,141 @@ mod tests { } #[test] - fn messagepack_store_recovers_schema_stamped_records_missing_catalog_entries() -> Result<()> { + fn raw_envelope_path_preserves_a_registered_dynamic_payload() -> Result<()> { let temp = tempfile::tempdir()?; - let store_path = temp.path().join("missing-catalog.msgpack"); - let expected = SchemaStamped { - schema_version: "tests.schema_stamped_entry.v1".to_string(), - name: "schema-stamped".to_string(), - value: "still readable".to_string(), + let store_path = temp.path().join("dynamic.cc"); + let envelope = CultCacheEnvelope { + key: "app".to_string(), + r#type: Settings::TYPE.to_string(), + payload: vec![0xc1], + stored_at: now_utc_second(), + schema_id: Some("dynamic.settings.v1".to_string()), }; - let snapshot = PersistedStoreSnapshot( - "cultcache.store.v1".to_string(), - Vec::new(), - vec![PersistedRecord( - "record-1".to_string(), - "sha256:stale-schema-id-from-cold-record".to_string(), - "2026-06-25T12:00:00Z".to_string(), - rmp_serde::to_vec(&expected)?, - )], - ); - fs::write(&store_path, rmp_serde::to_vec(&snapshot)?)?; let mut cache = CultCache::new(); - cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); - cache.pull_all_backing_stores()?; + cache.put_raw_envelope(envelope.clone())?; - assert_eq!(cache.get_required::("record-1")?, expected); + assert_eq!(cache.snapshot(), vec![envelope.clone()]); assert_eq!( + SingleFileMessagePackBackingStore::new(&store_path).pull_all()?, + vec![envelope] + ); + assert!(cache.get_required::("app").is_err()); + let before_collision = fs::read(&store_path)?; + assert!( cache - .get_required_envelope::("record-1")? - .schema_id, - Some("sha256:stale-schema-id-from-cold-record".to_string()) + .put_raw_envelope(CultCacheEnvelope { + key: "note".to_string(), + r#type: Note::TYPE.to_string(), + payload: Vec::new(), + stored_at: now_utc_second(), + schema_id: Some("dynamic.settings.v1".to_string()), + }) + .is_err() + ); + assert_eq!(fs::read(&store_path)?, before_collision); + assert!( + cache + .put_raw_envelope(CultCacheEnvelope { + key: "foreign".to_string(), + r#type: "unregistered".to_string(), + payload: Vec::new(), + stored_at: now_utc_second(), + schema_id: None, + }) + .is_err() ); Ok(()) } + + #[test] + fn soa_snapshot_round_trips_registered_entries() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cache.cc"); + let mut cache = CultCache::new(); + cache.register_registry(TestEntries)?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + cache.put( + "app", + &Settings { + theme: "ash".to_string(), + retries: 3, + }, + )?; + cache.put( + "memo", + &Note { + title: "machine".to_string(), + body: "awake".to_string(), + }, + )?; + + let soa = cache.snapshot_soa(); + assert_eq!(soa.len(), 2); + assert_eq!(soa.keys.len(), soa.payloads.len()); + + let mut restored = CultCache::new(); + restored.register_registry(TestEntries)?; + restored.add_generic_backing_store(SingleFileMessagePackBackingStore::new( + temp.path().join("restored.cc"), + )); + restored.load_soa(soa)?; + assert_eq!(restored.get_required::("app")?.theme, "ash"); + assert_eq!(restored.get_required::("memo")?.body, "awake"); + Ok(()) + } + + #[test] + fn typed_soa_columns_match_csharp_cache_ergonomics() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cache.cc"); + let mut cache = CultCache::new(); + cache.register_registry(TestEntries)?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + cache.put( + "settings:a", + &Settings { + theme: "ash".to_string(), + retries: 3, + }, + )?; + cache.put( + "settings:b", + &Settings { + theme: "bone".to_string(), + retries: 5, + }, + )?; + + let table = cache.soa::()?; + assert_eq!( + table.keys(), + &["settings:a".to_string(), "settings:b".to_string()] + ); + assert_eq!( + table.column::("theme")?.values(), + &["ash".to_string(), "bone".to_string()] + ); + assert_eq!(table.column::("retries")?.values(), &[3, 5]); + + let error = table.column::("retries").unwrap_err(); + assert!(error.to_string().contains("not alloc::string::String")); + Ok(()) + } + + #[test] + fn soa_rejects_column_length_drift() { + let table = CultCacheSoaTable { + keys: vec!["app".to_string()], + types: Vec::new(), + payloads: Vec::new(), + stored_ats: Vec::new(), + schema_ids: Vec::new(), + }; + let error = table.validate().unwrap_err(); + assert!(error.to_string().contains("column types has length 0")); + } } From a539d4fb1ce90f86cc4f79d214dc48c1f8c369ab Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 22:56:41 +0200 Subject: [PATCH 045/203] Return the Idunn and Odin authority contracts to CultLib The standalone GameCult/cultnet-rs holds six modules CultLib never received: runtime_authority_contracts, service_identity, provider_health_contracts, idunn_deployment_brake, idunn_lifecycle_brake, and snapshot_query. They own Idunn Expected incarnations, dual-proved runtime activation, process write leases, service-signed presence health, and Odin topology correlation -- the wire contracts every GameCult daemon must agree on. They belong here because wire parity conformance lives here and nothing outside it is checked for cross-runtime agreement. Held as source to be copied, this contract already forked: Ghostlight vendors it at e542225 while its own authority map names f4ab237, and the two disagree on whether Present may coexist with a disagreement. One copy makes them exclusive; the other separates presence from readiness and gates through into_undisputed_present. A single owner under the parity harness is the fix; re-vendoring is not. None of the six touch RUDP, so they land without disturbing the protocol core. rudp.rs remains genuinely forked -- 140 hunks between the lineages, with reliable windows and ACK horizons on this side and bounded session memory, exact acknowledgement receipts, and hostile rollover rejection on the other. That reconciliation needs the cross-runtime interop harness and is not attempted here. Adds ed25519-dalek, zeroize, and the Windows Foundation/Cryptography features the credential paths require. 96 tests pass. The two rudp_server_hub failures are pre-existing on this base and reproduce at 230ace6 untouched. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/Cargo.toml | 5 + .../cultnet-rs/src/idunn_deployment_brake.rs | 492 +++ .../cultnet-rs/src/idunn_lifecycle_brake.rs | 325 ++ packages/cultnet-rs/src/lib.rs | 12 + .../src/provider_health_contracts.rs | 653 ++++ .../src/runtime_authority_contracts.rs | 3309 +++++++++++++++++ packages/cultnet-rs/src/service_identity.rs | 920 +++++ packages/cultnet-rs/src/snapshot_query.rs | 339 ++ 8 files changed, 6055 insertions(+) create mode 100644 packages/cultnet-rs/src/idunn_deployment_brake.rs create mode 100644 packages/cultnet-rs/src/idunn_lifecycle_brake.rs create mode 100644 packages/cultnet-rs/src/provider_health_contracts.rs create mode 100644 packages/cultnet-rs/src/runtime_authority_contracts.rs create mode 100644 packages/cultnet-rs/src/service_identity.rs create mode 100644 packages/cultnet-rs/src/snapshot_query.rs diff --git a/packages/cultnet-rs/Cargo.toml b/packages/cultnet-rs/Cargo.toml index 41d14b52..f289d86e 100644 --- a/packages/cultnet-rs/Cargo.toml +++ b/packages/cultnet-rs/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1" base64 = "0.22" chrono = { version = "0.4", features = ["serde"] } cultcache-rs = { path = "../cultcache-rs" } +ed25519-dalek = "2.2" hmac = "0.12" rand = "0.9" rmp-serde = "1.3" @@ -24,9 +25,13 @@ serde = { version = "1", features = ["derive"] } serde_bytes = "0.11" serde_json = "1" sha2 = "0.10" +zeroize = "1" socket2 = "0.5" uuid = { version = "1", features = ["v4"] } +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Security_Cryptography"] } + [dev-dependencies] pretty_assertions = "1.4" tempfile = "3" diff --git a/packages/cultnet-rs/src/idunn_deployment_brake.rs b/packages/cultnet-rs/src/idunn_deployment_brake.rs new file mode 100644 index 00000000..e790f2a1 --- /dev/null +++ b/packages/cultnet-rs/src/idunn_deployment_brake.rs @@ -0,0 +1,492 @@ +use anyhow::{Result, bail}; +use cultcache_rs::DatabaseEntry; + +use crate::{ + IdunnDeploymentBrakeOperatorIdentity, IdunnDeploymentBrakeReleasePurpose, + ServiceIdentitySignature, ServiceIdentityTrustAnchor, verify_service_identity_signature, +}; + +pub const IDUNN_DEPLOYMENT_BRAKE_SCHEMA: &str = "idunn.deployment_brake.v1"; +pub const IDUNN_DEPLOYMENT_BRAKE_TYPE: &str = "idunn.deployment_brake"; +pub const IDUNN_DEPLOYMENT_BRAKE_ID: &str = "idunn/deployment-brake"; +pub const IDUNN_DEPLOYMENT_BRAKE_AUTHORITY: &str = "idunn.deployment-brake"; +pub const IDUNN_DEPLOYMENT_BRAKE_SCOPE: &str = "deployment"; +pub const IDUNN_DEPLOYMENT_RELEASE_PURPOSE: &str = "idunn.deployment-release.v1"; +pub const IDUNN_DEPLOYMENT_RELEASE_MAX_LIFETIME_MILLIS: u64 = 15 * 60 * 1_000; + +/// Canonical deployment-facing brake projection consumed by Idunn. A released +/// status is not permission by itself: the authorization fields bind one +/// release and one deployment attempt for a short, explicit interval. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "idunn.deployment_brake", schema = "idunn.deployment_brake.v1")] +pub struct IdunnDeploymentBrakeRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub brake_id: String, + #[cultcache(key = 2)] + pub authority: String, + #[cultcache(key = 3)] + pub runtime_id: String, + #[cultcache(key = 4)] + pub status: String, + #[cultcache(key = 5)] + pub scope: String, + #[cultcache(key = 6)] + pub reason: String, + #[cultcache(key = 7)] + pub observed_at_unix_millis: u64, + #[cultcache(key = 8)] + pub expires_at_unix_millis: Option, + #[cultcache(key = 9)] + pub authorization_id: Option, + #[cultcache(key = 10)] + pub authorization_purpose: Option, + #[cultcache(key = 11)] + pub authorized_release_id: Option, + #[cultcache(key = 12)] + pub authorized_deployment_id: Option, + #[cultcache(key = 13)] + pub authorized_by: Option, + #[cultcache(key = 14)] + pub authorization_issued_at_unix_millis: Option, + #[cultcache(key = 15)] + pub authorization_expires_at_unix_millis: Option, + #[cultcache(key = 16)] + pub signature_algorithm: Option, + #[cultcache(key = 17)] + pub signature: Option>, + #[cultcache(key = 18)] + pub private_state_exposed: bool, + /// Human/operator principal responsible for the latest transition. This is + /// distinct from `authorized_by`, which is the cryptographic identity id. + #[cultcache(key = 19)] + pub updated_by: String, +} + +impl IdunnDeploymentBrakeRecord { + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_DEPLOYMENT_BRAKE_SCHEMA { + bail!("deployment brake schema is unsupported"); + } + identifier(&self.brake_id, "brake id")?; + identifier(&self.authority, "authority")?; + identifier(&self.runtime_id, "runtime id")?; + identifier(&self.reason, "reason")?; + identifier(&self.scope, "scope")?; + identifier(&self.updated_by, "updating operator")?; + if !matches!(self.status.as_str(), "engaged" | "released") + || self.observed_at_unix_millis == 0 + || self + .expires_at_unix_millis + .is_some_and(|v| v <= self.observed_at_unix_millis) + || self.private_state_exposed + { + bail!("deployment brake shape, lifetime, or privacy is invalid"); + } + + let authorization = [ + self.authorization_id.as_ref(), + self.authorization_purpose.as_ref(), + self.authorized_release_id.as_ref(), + self.authorized_deployment_id.as_ref(), + self.authorized_by.as_ref(), + self.signature_algorithm.as_ref(), + ]; + let has_authorization = authorization.iter().any(|v| v.is_some()) + || self.authorization_issued_at_unix_millis.is_some() + || self.authorization_expires_at_unix_millis.is_some() + || self.signature.is_some(); + if self.status == "engaged" { + if has_authorization { + bail!("engaged deployment brake must not carry release authorization"); + } + return Ok(()); + } + if authorization.iter().any(|v| v.is_none()) + || self.authorization_issued_at_unix_millis.is_none() + || self.authorization_expires_at_unix_millis.is_none() + || self.signature.is_none() + { + bail!("released deployment brake authorization is incomplete"); + } + for (value, label) in [ + (&self.authorization_id, "authorization id"), + (&self.authorized_release_id, "authorized release id"), + (&self.authorized_deployment_id, "authorized deployment id"), + (&self.authorized_by, "authorizer"), + ] { + identifier(value.as_deref().unwrap(), label)?; + } + let issued = self.authorization_issued_at_unix_millis.unwrap(); + let expires = self.authorization_expires_at_unix_millis.unwrap(); + if self.authorization_purpose.as_deref() != Some(IDUNN_DEPLOYMENT_RELEASE_PURPOSE) + || self.signature_algorithm.as_deref() != Some("ed25519") + || self.signature.as_ref().is_none_or(|v| v.len() != 64) + || issued < self.observed_at_unix_millis + || expires <= issued + || expires - issued > IDUNN_DEPLOYMENT_RELEASE_MAX_LIFETIME_MILLIS + || self + .expires_at_unix_millis + .is_some_and(|brake_expiry| expires > brake_expiry) + { + bail!("deployment release authorization purpose, signature, or lifetime is invalid"); + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum IdunnDeploymentBrakeObservation<'a> { + Missing, + Corrupt, + Present(&'a IdunnDeploymentBrakeRecord), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IdunnDeploymentBrakeDenial { + Missing, + Corrupt, + Foreign, + WrongRuntime, + WrongScope, + Engaged, + Expired, + ReleaseMismatch, + DeploymentMismatch, + InvalidSignature, +} + +fn classify_idunn_deployment_brake( + observation: IdunnDeploymentBrakeObservation<'_>, + runtime_id: &str, + release_id: &str, + deployment_id: &str, + now_unix_millis: u64, +) -> std::result::Result<(), IdunnDeploymentBrakeDenial> { + let record = match observation { + IdunnDeploymentBrakeObservation::Missing => { + return Err(IdunnDeploymentBrakeDenial::Missing); + } + IdunnDeploymentBrakeObservation::Corrupt => { + return Err(IdunnDeploymentBrakeDenial::Corrupt); + } + IdunnDeploymentBrakeObservation::Present(record) => record, + }; + if record.validate().is_err() { + return Err(IdunnDeploymentBrakeDenial::Corrupt); + } + if record.brake_id != IDUNN_DEPLOYMENT_BRAKE_ID + || record.authority != IDUNN_DEPLOYMENT_BRAKE_AUTHORITY + { + return Err(IdunnDeploymentBrakeDenial::Foreign); + } + if record.runtime_id != runtime_id { + return Err(IdunnDeploymentBrakeDenial::WrongRuntime); + } + if !matches!(record.scope.as_str(), "all" | IDUNN_DEPLOYMENT_BRAKE_SCOPE) { + return Err(IdunnDeploymentBrakeDenial::WrongScope); + } + if record.status == "engaged" { + return Err(IdunnDeploymentBrakeDenial::Engaged); + } + if record + .expires_at_unix_millis + .is_some_and(|v| now_unix_millis >= v) + || record + .authorization_expires_at_unix_millis + .is_none_or(|v| now_unix_millis >= v) + || record + .authorization_issued_at_unix_millis + .is_none_or(|v| now_unix_millis < v) + { + return Err(IdunnDeploymentBrakeDenial::Expired); + } + if record.authorized_release_id.as_deref() != Some(release_id) { + return Err(IdunnDeploymentBrakeDenial::ReleaseMismatch); + } + if record.authorized_deployment_id.as_deref() != Some(deployment_id) { + return Err(IdunnDeploymentBrakeDenial::DeploymentMismatch); + } + Ok(()) +} + +pub fn evaluate_idunn_deployment_brake( + observation: IdunnDeploymentBrakeObservation<'_>, + operator_anchor: &ServiceIdentityTrustAnchor, + runtime_id: &str, + release_id: &str, + deployment_id: &str, + now_unix_millis: u64, +) -> std::result::Result<(), IdunnDeploymentBrakeDenial> { + classify_idunn_deployment_brake( + observation, + runtime_id, + release_id, + deployment_id, + now_unix_millis, + )?; + let IdunnDeploymentBrakeObservation::Present(record) = observation else { + unreachable!("successful classification always has a present record") + }; + verify_idunn_deployment_brake_authorization(record, operator_anchor) + .map_err(|_| IdunnDeploymentBrakeDenial::InvalidSignature) +} + +/// Verify the release grant using the dedicated operator public anchor. The +/// signed payload is the complete positional record with the signature field +/// empty, so authority, runtime, target IDs, and both time bounds are covered. +pub fn verify_idunn_deployment_brake_authorization( + record: &IdunnDeploymentBrakeRecord, + anchor: &ServiceIdentityTrustAnchor, +) -> Result<()> { + record.validate()?; + if record.status != "released" { + bail!("engaged deployment brake has no release authorization to verify"); + } + let identity_id = record + .authorized_by + .as_ref() + .ok_or_else(|| anyhow::anyhow!("deployment brake authorizer is absent"))?; + let signature = record + .signature + .as_ref() + .ok_or_else(|| anyhow::anyhow!("deployment brake signature is absent"))?; + let mut unsigned = record.clone(); + unsigned.signature = None; + verify_service_identity_signature::< + IdunnDeploymentBrakeOperatorIdentity, + IdunnDeploymentBrakeReleasePurpose, + >( + anchor, + &rmp_serde::to_vec(&unsigned)?, + &ServiceIdentitySignature { + identity_id: identity_id.clone(), + signature: signature.clone(), + }, + ) +} + +fn identifier(value: &str, label: &str) -> Result<()> { + if value.trim().is_empty() || value.len() > 256 || value.chars().any(char::is_control) { + bail!("{label} is empty, oversized, or contains control characters"); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::enroll_service_identity_at; + + fn released() -> IdunnDeploymentBrakeRecord { + IdunnDeploymentBrakeRecord { + schema_version: IDUNN_DEPLOYMENT_BRAKE_SCHEMA.into(), + brake_id: IDUNN_DEPLOYMENT_BRAKE_ID.into(), + authority: IDUNN_DEPLOYMENT_BRAKE_AUTHORITY.into(), + runtime_id: "yggdrasil".into(), + status: "released".into(), + scope: "deployment".into(), + reason: "operator authorized one attempt".into(), + observed_at_unix_millis: 100, + expires_at_unix_millis: Some(1000), + authorization_id: Some("auth/r4".into()), + authorization_purpose: Some(IDUNN_DEPLOYMENT_RELEASE_PURPOSE.into()), + authorized_release_id: Some("release-4".into()), + authorized_deployment_id: Some("deployment-4".into()), + authorized_by: Some("operator/metacrat".into()), + authorization_issued_at_unix_millis: Some(100), + authorization_expires_at_unix_millis: Some(900), + signature_algorithm: Some("ed25519".into()), + signature: Some(vec![7; 64]), + private_state_exposed: false, + updated_by: "operator/metacrat".into(), + } + } + + #[test] + fn only_exact_bounded_release_is_permitted() { + let value = released(); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Ok(()) + ); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Missing, + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::Missing) + ); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Corrupt, + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::Corrupt) + ); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "other", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::ReleaseMismatch) + ); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "other", + 500 + ), + Err(IdunnDeploymentBrakeDenial::DeploymentMismatch) + ); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 900 + ), + Err(IdunnDeploymentBrakeDenial::Expired) + ); + } + + #[test] + fn hostile_authority_scope_and_shape_substitutions_fail_closed() { + let mut value = released(); + value.authority = "idunn".into(); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::Foreign) + ); + let mut value = released(); + value.runtime_id = "nightwing".into(); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::WrongRuntime) + ); + let mut value = released(); + value.scope = "persona.public_speech".into(); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::WrongScope) + ); + let mut value = released(); + value.signature = Some(vec![0; 63]); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::Corrupt) + ); + let mut value = released(); + value.authorization_expires_at_unix_millis = + Some(100 + IDUNN_DEPLOYMENT_RELEASE_MAX_LIFETIME_MILLIS + 1); + assert!(value.validate().is_err()); + } + + #[test] + fn engaged_record_cannot_smuggle_authorization() { + let mut value = released(); + value.status = "engaged".into(); + assert!(value.validate().is_err()); + value.authorization_id = None; + value.authorization_purpose = None; + value.authorized_release_id = None; + value.authorized_deployment_id = None; + value.authorized_by = None; + value.authorization_issued_at_unix_millis = None; + value.authorization_expires_at_unix_millis = None; + value.signature_algorithm = None; + value.signature = None; + value.validate().unwrap(); + assert_eq!( + classify_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + "yggdrasil", + "release-4", + "deployment-4", + 500 + ), + Err(IdunnDeploymentBrakeDenial::Engaged) + ); + } + + #[test] + fn evaluation_requires_the_dedicated_operator_signature() -> Result<()> { + let temp = tempfile::tempdir()?; + let signer = enroll_service_identity_at::( + &temp.path().join("operator.cc"), + )?; + let anchor = signer.trust_anchor()?; + let mut value = released(); + value.authorized_by = Some(anchor.identity_id.clone()); + value.signature = None; + let proof = signer.sign::(&rmp_serde::to_vec(&value)?); + value.signature = Some(proof.signature); + assert_eq!( + evaluate_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + &anchor, + "yggdrasil", + "release-4", + "deployment-4", + 500, + ), + Ok(()) + ); + value.reason = "tampered after authorization".into(); + assert_eq!( + evaluate_idunn_deployment_brake( + IdunnDeploymentBrakeObservation::Present(&value), + &anchor, + "yggdrasil", + "release-4", + "deployment-4", + 500, + ), + Err(IdunnDeploymentBrakeDenial::InvalidSignature) + ); + Ok(()) + } +} diff --git a/packages/cultnet-rs/src/idunn_lifecycle_brake.rs b/packages/cultnet-rs/src/idunn_lifecycle_brake.rs new file mode 100644 index 00000000..86eefa64 --- /dev/null +++ b/packages/cultnet-rs/src/idunn_lifecycle_brake.rs @@ -0,0 +1,325 @@ +use anyhow::{Result, bail}; +use cultcache_rs::DatabaseEntry; + +pub const IDUNN_LIFECYCLE_BRAKE_SCHEMA: &str = "idunn.lifecycle_brake.v1"; +pub const IDUNN_LIFECYCLE_BRAKE_TYPE: &str = "idunn.lifecycle_brake"; +pub const IDUNN_LIFECYCLE_BRAKE_AUTHORITY: &str = "idunn.root"; +pub const IDUNN_LIFECYCLE_BRAKE_SCOPE: &str = "continuity-restart"; + +/// Root-owned, per-target continuity brake. This document has no authority over +/// Idunn startup, deployment, route admission, or any target other than the one +/// named in `target`. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "idunn.lifecycle_brake", schema = "idunn.lifecycle_brake.v1")] +pub struct IdunnLifecycleBrakeRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub authority: String, + #[cultcache(key = 2)] + pub runtime_id: String, + #[cultcache(key = 3)] + pub target: String, + #[cultcache(key = 4)] + pub scope: String, + #[cultcache(key = 5)] + pub status: String, + #[cultcache(key = 6)] + pub reason: String, + #[cultcache(key = 7)] + pub updated_at_unix_millis: u64, + #[cultcache(key = 8)] + pub released_until_unix_millis: Option, +} + +impl IdunnLifecycleBrakeRecord { + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_LIFECYCLE_BRAKE_SCHEMA { + bail!("lifecycle brake schema is unsupported"); + } + identifier(&self.authority, "authority")?; + identifier(&self.runtime_id, "runtime id")?; + identifier(&self.target, "target")?; + identifier(&self.scope, "scope")?; + identifier(&self.reason, "reason")?; + if !matches!(self.status.as_str(), "engaged" | "released") + || self.updated_at_unix_millis == 0 + { + bail!("lifecycle brake status or update time is invalid"); + } + if self.status == "engaged" && self.released_until_unix_millis.is_some() { + bail!("engaged lifecycle brake cannot carry a release expiry"); + } + if self + .released_until_unix_millis + .is_some_and(|expires| expires <= self.updated_at_unix_millis) + { + bail!("lifecycle brake release expiry is not after its update"); + } + Ok(()) + } + + pub fn canonical_bytes(&self) -> Result> { + self.validate()?; + Ok(rmp_serde::to_vec(self)?) + } + + pub fn decode_canonical(payload: &[u8]) -> Result { + if messagepack_array_len(payload) != Some(9) { + bail!("lifecycle brake is not the 9-field positional contract"); + } + let value: Self = rmp_serde::from_slice(payload)?; + value.validate()?; + if rmp_serde::to_vec(&value)? != payload { + bail!("lifecycle brake encoding is not canonical"); + } + Ok(value) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum IdunnLifecycleBrakeObservation<'a> { + Missing, + Corrupt, + Present(&'a IdunnLifecycleBrakeRecord), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IdunnLifecycleBrakeDenial { + Corrupt, + Foreign, + WrongRuntime, + WrongTarget, + Engaged, + Expired, +} + +/// Decide only whether Idunn may restart one already-admitted target to keep it +/// alive. Absence is the default allow state; a present record must bind the +/// exact Idunn runtime and target and must be explicitly released. +pub fn evaluate_idunn_continuity_restart( + observation: IdunnLifecycleBrakeObservation<'_>, + runtime_id: &str, + target: &str, + now_unix_millis: u64, +) -> std::result::Result<(), IdunnLifecycleBrakeDenial> { + let record = match observation { + IdunnLifecycleBrakeObservation::Missing => return Ok(()), + IdunnLifecycleBrakeObservation::Corrupt => { + return Err(IdunnLifecycleBrakeDenial::Corrupt); + } + IdunnLifecycleBrakeObservation::Present(record) => record, + }; + if record.validate().is_err() { + return Err(IdunnLifecycleBrakeDenial::Corrupt); + } + if record.authority != IDUNN_LIFECYCLE_BRAKE_AUTHORITY + || record.scope != IDUNN_LIFECYCLE_BRAKE_SCOPE + { + return Err(IdunnLifecycleBrakeDenial::Foreign); + } + if record.runtime_id != runtime_id { + return Err(IdunnLifecycleBrakeDenial::WrongRuntime); + } + if record.target != target { + return Err(IdunnLifecycleBrakeDenial::WrongTarget); + } + if record.status == "engaged" { + return Err(IdunnLifecycleBrakeDenial::Engaged); + } + if record + .released_until_unix_millis + .is_some_and(|expires| now_unix_millis >= expires) + { + return Err(IdunnLifecycleBrakeDenial::Expired); + } + Ok(()) +} + +fn identifier(value: &str, label: &str) -> Result<()> { + if value.trim().is_empty() || value.len() > 256 || value.chars().any(char::is_control) { + bail!("{label} is empty, oversized, or contains control characters"); + } + Ok(()) +} + +fn messagepack_array_len(payload: &[u8]) -> Option { + match *payload.first()? { + marker @ 0x90..=0x9f => Some((marker & 0x0f) as usize), + 0xdc if payload.len() >= 3 => Some(u16::from_be_bytes([payload[1], payload[2]]) as usize), + 0xdd if payload.len() >= 5 => { + Some(u32::from_be_bytes([payload[1], payload[2], payload[3], payload[4]]) as usize) + } + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn released() -> IdunnLifecycleBrakeRecord { + IdunnLifecycleBrakeRecord { + schema_version: IDUNN_LIFECYCLE_BRAKE_SCHEMA.into(), + authority: IDUNN_LIFECYCLE_BRAKE_AUTHORITY.into(), + runtime_id: "idunn-yggdrasil".into(), + target: "ghostlight".into(), + scope: IDUNN_LIFECYCLE_BRAKE_SCOPE.into(), + status: "released".into(), + reason: "continuity restart permitted".into(), + updated_at_unix_millis: 100, + released_until_unix_millis: None, + } + } + + fn evaluate( + observation: IdunnLifecycleBrakeObservation<'_>, + target: &str, + now: u64, + ) -> std::result::Result<(), IdunnLifecycleBrakeDenial> { + evaluate_idunn_continuity_restart(observation, "idunn-yggdrasil", target, now) + } + + #[test] + fn lifecycle_brake_round_trips_only_as_the_canonical_positional_contract() -> Result<()> { + let value = released(); + let encoded = value.canonical_bytes()?; + assert_eq!(encoded[0], 0x99); + assert_eq!( + IdunnLifecycleBrakeRecord::decode_canonical(&encoded)?, + value + ); + + let mut noncanonical = vec![0xdc, 0, 9]; + noncanonical.extend_from_slice(&encoded[1..]); + assert!(IdunnLifecycleBrakeRecord::decode_canonical(&noncanonical).is_err()); + + let mut extended = encoded; + extended[0] = 0x9a; + extended.push(0xc0); + assert!(IdunnLifecycleBrakeRecord::decode_canonical(&extended).is_err()); + Ok(()) + } + + #[test] + fn absence_and_explicit_release_allow_only_the_named_continuity_restart() { + assert_eq!( + evaluate(IdunnLifecycleBrakeObservation::Missing, "ghostlight", 500), + Ok(()) + ); + + let mut value = released(); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Ok(()) + ); + value.released_until_unix_millis = Some(900); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 899 + ), + Ok(()) + ); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 900 + ), + Err(IdunnLifecycleBrakeDenial::Expired) + ); + assert_eq!( + evaluate(IdunnLifecycleBrakeObservation::Missing, "odin", 900), + Ok(()) + ); + assert_eq!( + evaluate(IdunnLifecycleBrakeObservation::Present(&value), "odin", 500), + Err(IdunnLifecycleBrakeDenial::WrongTarget) + ); + } + + #[test] + fn corrupt_foreign_wrong_runtime_and_wrong_target_records_deny() { + assert_eq!( + evaluate(IdunnLifecycleBrakeObservation::Corrupt, "ghostlight", 500), + Err(IdunnLifecycleBrakeDenial::Corrupt) + ); + + let mut value = released(); + value.status = "paused".into(); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::Corrupt) + ); + + let mut value = released(); + value.authority = "service-owned".into(); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::Foreign) + ); + + let mut value = released(); + value.scope = "deployment".into(); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::Foreign) + ); + + let value = released(); + assert_eq!( + evaluate_idunn_continuity_restart( + IdunnLifecycleBrakeObservation::Present(&value), + "idunn-nightwing", + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::WrongRuntime) + ); + assert_eq!( + evaluate(IdunnLifecycleBrakeObservation::Present(&value), "odin", 500), + Err(IdunnLifecycleBrakeDenial::WrongTarget) + ); + } + + #[test] + fn engaged_always_denies_and_cannot_smuggle_a_release_expiry() { + let mut value = released(); + value.status = "engaged".into(); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::Engaged) + ); + value.released_until_unix_millis = Some(900); + assert_eq!( + evaluate( + IdunnLifecycleBrakeObservation::Present(&value), + "ghostlight", + 500 + ), + Err(IdunnLifecycleBrakeDenial::Corrupt) + ); + } +} diff --git a/packages/cultnet-rs/src/lib.rs b/packages/cultnet-rs/src/lib.rs index 6fb1d38d..3e8a3c94 100644 --- a/packages/cultnet-rs/src/lib.rs +++ b/packages/cultnet-rs/src/lib.rs @@ -1,4 +1,10 @@ mod contracts; +mod idunn_deployment_brake; +mod idunn_lifecycle_brake; +mod provider_health_contracts; +mod runtime_authority_contracts; +mod service_identity; +mod snapshot_query; mod cultmesh; mod framing; mod operation_service; @@ -11,6 +17,12 @@ mod shard_catalog; mod transport; pub use contracts::*; +pub use idunn_deployment_brake::*; +pub use idunn_lifecycle_brake::*; +pub use provider_health_contracts::*; +pub use runtime_authority_contracts::*; +pub use service_identity::*; +pub use snapshot_query::*; pub use cultmesh::*; pub use framing::*; pub use operation_service::*; diff --git a/packages/cultnet-rs/src/provider_health_contracts.rs b/packages/cultnet-rs/src/provider_health_contracts.rs new file mode 100644 index 00000000..5ca320f0 --- /dev/null +++ b/packages/cultnet-rs/src/provider_health_contracts.rs @@ -0,0 +1,653 @@ +use anyhow::{Context, Result, anyhow, bail}; +use cultcache_rs::DatabaseEntry; +use serde::{Deserialize, Serialize}; + +use crate::runtime_authority_contracts::{ + GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA, GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE, + ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA, ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE, +}; +use crate::{ + GameCultProviderHealthIdentity, IdunnServiceIdentity, OdinTopologyIdentity, + derive_service_identity_id, +}; + +pub const GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA: &str = "gamecult.service_trust_anchor.v1"; +pub const IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA: &str = + "idunn.authenticated_provider_health_projection.v1"; +pub const IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA: &str = "idunn.signed_daemon_health.v1"; +pub const IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE: &str = + "idunn.authenticated-provider-health-projection.v1"; +pub const IDUNN_PROVIDER_ACTIVE_REASON: &str = "authenticated_provider_active"; +pub const IDUNN_PROVIDER_WARMING_REASON: &str = "authenticated_provider_warming"; +pub const IDUNN_PROVIDER_DEGRADED_REASON: &str = "authenticated_provider_degraded"; +pub const IDUNN_PROVIDER_FAILED_REASON: &str = "authenticated_provider_failed"; + +/// Provider-owned health statement. Ingress verifies `signature` over the +/// complete positional record with this field empty before admitting it. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.signed_daemon_health", + schema = "idunn.signed_daemon_health.v1" +)] +pub struct IdunnSignedDaemonHealthRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub daemon_id: String, + #[cultcache(key = 2)] + pub health_contract: String, + #[cultcache(key = 3)] + pub source_runtime_id: String, + #[cultcache(key = 4)] + pub state: String, + #[cultcache(key = 5)] + pub detail: String, + #[cultcache(key = 6)] + pub signer_identity_id: String, + #[cultcache(key = 7)] + pub publisher_incarnation_id: String, + #[cultcache(key = 8)] + pub publisher_sequence: u64, + #[cultcache(key = 9)] + pub observed_at_unix_millis: u64, + #[cultcache(key = 10)] + pub release_id: Option, + #[cultcache(key = 11)] + pub release_witness_sha256: Option, + #[cultcache(key = 12)] + pub source_commit: Option, + #[cultcache(key = 13)] + pub deployment_id: Option, + #[cultcache(key = 14)] + pub signature_algorithm: String, + #[cultcache(key = 15, bytes)] + pub signature: Vec, + #[cultcache(key = 16)] + pub private_state_exposed: bool, + #[cultcache(key = 17, default)] + pub activation_witness_sha256: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct LegacyIdunnSignedDaemonHealthWire { + schema_version: String, + daemon_id: String, + health_contract: String, + source_runtime_id: String, + state: String, + detail: String, + signer_identity_id: String, + publisher_incarnation_id: String, + publisher_sequence: u64, + observed_at_unix_millis: u64, + release_id: Option, + release_witness_sha256: Option, + source_commit: Option, + deployment_id: Option, + signature_algorithm: String, + #[serde(with = "serde_bytes")] + signature: Vec, + private_state_exposed: bool, +} + +impl From for IdunnSignedDaemonHealthRecord { + fn from(value: LegacyIdunnSignedDaemonHealthWire) -> Self { + Self { + schema_version: value.schema_version, + daemon_id: value.daemon_id, + health_contract: value.health_contract, + source_runtime_id: value.source_runtime_id, + state: value.state, + detail: value.detail, + signer_identity_id: value.signer_identity_id, + publisher_incarnation_id: value.publisher_incarnation_id, + publisher_sequence: value.publisher_sequence, + observed_at_unix_millis: value.observed_at_unix_millis, + release_id: value.release_id, + release_witness_sha256: value.release_witness_sha256, + source_commit: value.source_commit, + deployment_id: value.deployment_id, + signature_algorithm: value.signature_algorithm, + signature: value.signature, + private_state_exposed: value.private_state_exposed, + activation_witness_sha256: None, + } + } +} + +impl IdunnSignedDaemonHealthRecord { + /// Decode the canonical current or pre-activation positional wire and + /// return the exact wire generation's unsigned signature payload. + pub fn decode_canonical_signed_payload(payload: &[u8]) -> Result<(Self, Vec)> { + match messagepack_array_len(payload) { + Some(18) => { + let statement: Self = rmp_serde::from_slice(payload) + .context("decoding generic signed daemon health")?; + if rmp_serde::to_vec(&statement)? != payload { + bail!("signed daemon health payload is not canonical positional MessagePack"); + } + let mut unsigned = statement.clone(); + unsigned.signature.clear(); + Ok((statement, rmp_serde::to_vec(&unsigned)?)) + } + Some(17) => { + let legacy: LegacyIdunnSignedDaemonHealthWire = rmp_serde::from_slice(payload) + .context("decoding legacy generic signed daemon health")?; + if rmp_serde::to_vec(&legacy)? != payload { + bail!("signed daemon health payload is not canonical positional MessagePack"); + } + let mut unsigned = legacy.clone(); + unsigned.signature.clear(); + let unsigned_payload = rmp_serde::to_vec(&unsigned)?; + Ok((legacy.into(), unsigned_payload)) + } + _ => bail!("signed daemon health payload is not canonical positional MessagePack"), + } + } + + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA { + bail!("signed daemon health schema is unsupported"); + } + validate_identifier(&self.daemon_id, "daemon id")?; + validate_identifier(&self.health_contract, "health contract")?; + validate_identifier(&self.source_runtime_id, "source runtime id")?; + validate_identifier(&self.signer_identity_id, "signer identity id")?; + validate_identifier(&self.publisher_incarnation_id, "publisher incarnation id")?; + if !matches!( + self.state.as_str(), + "active" | "warming" | "degraded" | "failed" + ) || self.detail.len() > 512 + || self.signature_algorithm != "ed25519" + || self.signature.len() != 64 + || self.publisher_sequence == 0 + || self.observed_at_unix_millis == 0 + { + bail!("signed daemon health shape is invalid"); + } + validate_optional_release_binding( + &self.release_id, + &self.release_witness_sha256, + &self.source_commit, + )?; + validate_optional_identifier(&self.deployment_id, "deployment id")?; + validate_optional_sha256(&self.activation_witness_sha256, "activation witness sha256")?; + if self.private_state_exposed { + bail!("signed daemon health exposes private state"); + } + Ok(()) + } +} + +fn messagepack_array_len(payload: &[u8]) -> Option { + match *payload.first()? { + marker @ 0x90..=0x9f => Some(usize::from(marker & 0x0f)), + 0xdc => Some(usize::from(u16::from_be_bytes([ + *payload.get(1)?, + *payload.get(2)?, + ]))), + 0xdd => usize::try_from(u32::from_be_bytes([ + *payload.get(1)?, + *payload.get(2)?, + *payload.get(3)?, + *payload.get(4)?, + ])) + .ok(), + _ => None, + } +} + +/// Root-distributed public key binding for one service-owned signed contract. +/// Consumers pin this document; a self-declared key inside a signed projection +/// is never authority. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.service_trust_anchor", + schema = "gamecult.service_trust_anchor.v1" +)] +pub struct GameCultServiceTrustAnchorRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub trust_anchor_id: String, + #[cultcache(key = 2)] + pub service_id: String, + #[cultcache(key = 3)] + pub runtime_id: String, + #[cultcache(key = 4)] + pub signer_identity_id: String, + #[cultcache(key = 5)] + pub signer_public_key: Vec, + #[cultcache(key = 6)] + pub signature_algorithm: String, + #[cultcache(key = 7)] + pub signing_purpose: String, + #[cultcache(key = 8)] + pub signed_schema: String, + #[cultcache(key = 9)] + pub binding_authority: String, + #[cultcache(key = 10)] + pub bound_at_unix_millis: u64, + #[cultcache(key = 11)] + pub expires_at_unix_millis: Option, + #[cultcache(key = 12)] + pub private_state_exposed: bool, +} + +impl GameCultServiceTrustAnchorRecord { + pub fn validate(&self) -> Result<()> { + if self.schema_version != GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA { + bail!("service trust anchor schema is unsupported"); + } + validate_identifier(&self.trust_anchor_id, "trust anchor id")?; + validate_identifier(&self.service_id, "service id")?; + validate_identifier(&self.runtime_id, "runtime id")?; + validate_identifier(&self.signer_identity_id, "signer identity id")?; + validate_identifier(&self.signing_purpose, "signing purpose")?; + validate_identifier(&self.signed_schema, "signed schema")?; + if self.signer_public_key.len() != 32 + || self.signature_algorithm != "ed25519" + || self.binding_authority != "root" + || self.bound_at_unix_millis == 0 + || self + .expires_at_unix_millis + .is_some_and(|expires_at| expires_at <= self.bound_at_unix_millis) + || self.private_state_exposed + { + bail!("service trust anchor authority, key, lifetime, or privacy is invalid"); + } + if self.signed_schema == IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA + && (self.service_id != "idunn" + || self.signing_purpose + != IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE + || self.signer_identity_id + != derive_service_identity_id::(&self.signer_public_key)?) + { + bail!("Idunn provider-health trust anchor profile is invalid"); + } + let is_runtime_presence_profile = self + .signed_schema + .starts_with("gamecult.runtime_presence_health.") + || self + .signing_purpose + .starts_with("gamecult.runtime_presence_health."); + if is_runtime_presence_profile + && (self.signed_schema != GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA + || self.signing_purpose != GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE + || self.signer_identity_id + != derive_service_identity_id::( + &self.signer_public_key, + )?) + { + bail!("runtime presence trust anchor profile is invalid"); + } + if (self.signed_schema == ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA + || self.signing_purpose == ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE) + && (self.signed_schema != ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA + || self.signing_purpose != ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE + || self.service_id != "odin" + || self.signer_identity_id + != derive_service_identity_id::(&self.signer_public_key)?) + { + bail!("Odin topology correlation trust anchor profile is invalid"); + } + Ok(()) + } +} + +/// Idunn-owned public projection of one current authenticated generic provider +/// admission. Absence and dependency failure produce no record; they are not +/// provider states Idunn may manufacture. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.authenticated_provider_health_projection", + schema = "idunn.authenticated_provider_health_projection.v1" +)] +pub struct IdunnAuthenticatedProviderHealthProjectionRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub projection_id: String, + #[cultcache(key = 2)] + pub daemon_id: String, + #[cultcache(key = 3)] + pub health_contract: String, + #[cultcache(key = 4)] + pub provider_state: String, + #[cultcache(key = 5)] + pub reason_code: String, + #[cultcache(key = 6)] + pub provider_observed_at_unix_millis: u64, + #[cultcache(key = 7)] + pub admitted_at_unix_millis: u64, + #[cultcache(key = 8)] + pub evaluated_at_unix_millis: u64, + #[cultcache(key = 9)] + pub trust_binding_id: String, + #[cultcache(key = 10)] + pub trust_binding_sha256: String, + #[cultcache(key = 11)] + pub signed_health_sha256: String, + #[cultcache(key = 12)] + pub authenticated_admission_sha256: String, + #[cultcache(key = 13)] + pub provider_signer_identity_id: String, + #[cultcache(key = 14)] + pub provider_incarnation_id: String, + #[cultcache(key = 15)] + pub provider_sequence: u64, + #[cultcache(key = 16)] + pub release_id: Option, + #[cultcache(key = 17)] + pub release_witness_sha256: Option, + #[cultcache(key = 18)] + pub source_commit: Option, + #[cultcache(key = 19)] + pub deployment_id: Option, + #[cultcache(key = 20)] + pub idunn_runtime_id: String, + #[cultcache(key = 21)] + pub idunn_signer_identity_id: String, + #[cultcache(key = 22)] + pub projection_incarnation_id: String, + #[cultcache(key = 23)] + pub projection_sequence: u64, + #[cultcache(key = 24)] + pub signature_algorithm: String, + #[cultcache(key = 25)] + pub signature: Vec, + #[cultcache(key = 26)] + pub private_state_exposed: bool, + #[cultcache(key = 27)] + pub expires_at_unix_millis: u64, +} + +/// Closed derivation for an authenticated provider state. `None` means no +/// provider-health projection may be published. +pub fn authenticated_provider_health_reason_code(state: &str) -> Option<&'static str> { + match state { + "active" => Some(IDUNN_PROVIDER_ACTIVE_REASON), + "warming" => Some(IDUNN_PROVIDER_WARMING_REASON), + "degraded" => Some(IDUNN_PROVIDER_DEGRADED_REASON), + "failed" => Some(IDUNN_PROVIDER_FAILED_REASON), + _ => None, + } +} + +impl IdunnAuthenticatedProviderHealthProjectionRecord { + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA { + bail!("authenticated provider health projection schema is unsupported"); + } + validate_identifier(&self.projection_id, "projection id")?; + validate_identifier(&self.daemon_id, "daemon id")?; + validate_identifier(&self.health_contract, "health contract")?; + validate_identifier(&self.trust_binding_id, "trust binding id")?; + validate_identifier( + &self.provider_signer_identity_id, + "provider signer identity id", + )?; + validate_identifier(&self.provider_incarnation_id, "provider incarnation id")?; + validate_identifier(&self.idunn_runtime_id, "Idunn runtime id")?; + validate_identifier(&self.idunn_signer_identity_id, "Idunn signer identity id")?; + validate_identifier(&self.projection_incarnation_id, "projection incarnation id")?; + let reason_is_valid = authenticated_provider_health_reason_code(&self.provider_state) + .is_some_and(|reason| reason == self.reason_code); + if !reason_is_valid + || self.provider_observed_at_unix_millis == 0 + || self.admitted_at_unix_millis < self.provider_observed_at_unix_millis + || self.evaluated_at_unix_millis < self.admitted_at_unix_millis + || self.expires_at_unix_millis <= self.evaluated_at_unix_millis + || self.provider_sequence == 0 + || self.projection_sequence == 0 + || !is_sha256(&self.trust_binding_sha256) + || !is_sha256(&self.signed_health_sha256) + || !is_sha256(&self.authenticated_admission_sha256) + || self.signature_algorithm != "ed25519" + || self.signature.len() != 64 + || self.private_state_exposed + { + bail!( + "authenticated provider health projection shape, lineage, reason, signature, or privacy is invalid" + ); + } + validate_optional_release_binding( + &self.release_id, + &self.release_witness_sha256, + &self.source_commit, + )?; + validate_optional_identifier(&self.deployment_id, "deployment id")?; + if self.deployment_id.is_some() != self.release_id.is_some() { + bail!("authenticated provider health projection release lineage is partial"); + } + Ok(()) + } +} + +fn validate_identifier(value: &str, label: &str) -> Result<()> { + if value.trim().is_empty() || value.len() > 256 || value.chars().any(char::is_control) { + bail!("{label} is empty, oversized, or contains control characters"); + } + Ok(()) +} + +fn validate_optional_identifier(value: &Option, label: &str) -> Result<()> { + if let Some(value) = value { + validate_identifier(value, label)?; + } + Ok(()) +} + +fn validate_optional_sha256(value: &Option, label: &str) -> Result<()> { + if value.as_deref().is_some_and(|value| !is_sha256(value)) { + bail!("{label} is malformed"); + } + Ok(()) +} + +fn validate_optional_release_binding( + release_id: &Option, + witness: &Option, + source_commit: &Option, +) -> Result<()> { + if release_id.is_some() || witness.is_some() || source_commit.is_some() { + validate_optional_identifier(release_id, "release id")?; + let witness = witness + .as_deref() + .ok_or_else(|| anyhow!("release witness is absent"))?; + let commit = source_commit + .as_deref() + .ok_or_else(|| anyhow!("source commit is absent"))?; + if release_id.is_none() || !is_sha256(witness) || !is_lower_hex(commit, 40) { + bail!("release binding is incomplete or malformed"); + } + } + Ok(()) +} + +fn is_sha256(value: &str) -> bool { + value + .strip_prefix("sha256-") + .is_some_and(|digest| is_lower_hex(digest, 64)) +} + +fn is_lower_hex(value: &str, length: usize) -> bool { + value.len() == length + && value + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn signed_health() -> IdunnSignedDaemonHealthRecord { + IdunnSignedDaemonHealthRecord { + schema_version: IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA.into(), + daemon_id: "epiphany".into(), + health_contract: "epiphany.cultnet-rudp-runtime-health".into(), + source_runtime_id: "epiphany-yggdrasil".into(), + state: "active".into(), + detail: "managed-services-current".into(), + signer_identity_id: "provider-signing-key".into(), + publisher_incarnation_id: "9c2cba2e-c2de-4750-b222-b732f97d0435".into(), + publisher_sequence: 1, + observed_at_unix_millis: 100, + release_id: Some("release-1".into()), + release_witness_sha256: Some(format!("sha256-{}", "d".repeat(64))), + source_commit: Some("e".repeat(40)), + deployment_id: Some("deployment-1".into()), + signature_algorithm: "ed25519".into(), + signature: vec![6; 64], + private_state_exposed: false, + activation_witness_sha256: Some(format!("sha256-{}", "f".repeat(64))), + } + } + + fn anchor() -> GameCultServiceTrustAnchorRecord { + let public_key = vec![5; 32]; + GameCultServiceTrustAnchorRecord { + schema_version: GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA.into(), + trust_anchor_id: "root/idunn/provider-health".into(), + service_id: "idunn".into(), + runtime_id: "idunn-yggdrasil".into(), + signer_identity_id: derive_service_identity_id::(&public_key) + .unwrap(), + signer_public_key: public_key, + signature_algorithm: "ed25519".into(), + signing_purpose: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE.into(), + signed_schema: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), + binding_authority: "root".into(), + bound_at_unix_millis: 100, + expires_at_unix_millis: Some(200), + private_state_exposed: false, + } + } + + fn projection() -> IdunnAuthenticatedProviderHealthProjectionRecord { + IdunnAuthenticatedProviderHealthProjectionRecord { + schema_version: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), + projection_id: "authenticated-provider-health:epiphany".into(), + daemon_id: "epiphany".into(), + health_contract: "health.v1".into(), + provider_state: "active".into(), + reason_code: IDUNN_PROVIDER_ACTIVE_REASON.into(), + provider_observed_at_unix_millis: 100, + admitted_at_unix_millis: 101, + evaluated_at_unix_millis: 102, + trust_binding_id: "root/epiphany".into(), + trust_binding_sha256: format!("sha256-{}", "a".repeat(64)), + signed_health_sha256: format!("sha256-{}", "b".repeat(64)), + authenticated_admission_sha256: format!("sha256-{}", "c".repeat(64)), + provider_signer_identity_id: "provider-signing-key".into(), + provider_incarnation_id: "provider/boot/1".into(), + provider_sequence: 1, + release_id: Some("release-1".into()), + release_witness_sha256: Some(format!("sha256-{}", "d".repeat(64))), + source_commit: Some("e".repeat(40)), + deployment_id: Some("deployment-1".into()), + idunn_runtime_id: "idunn-yggdrasil".into(), + idunn_signer_identity_id: "idunn-signing-key".into(), + projection_incarnation_id: "idunn/boot/1".into(), + projection_sequence: 1, + signature_algorithm: "ed25519".into(), + signature: vec![6; 64], + private_state_exposed: false, + expires_at_unix_millis: 200, + } + } + + #[test] + fn contracts_accept_exact_root_and_lineage_shapes() -> Result<()> { + let anchor = anchor(); + let projection = projection(); + let health = signed_health(); + anchor.validate()?; + projection.validate()?; + health.validate()?; + let encoded = rmp_serde::to_vec(&health)?; + assert_eq!(encoded.first().copied(), Some(0xdc)); + assert_eq!(&encoded[1..3], &[0, 18]); + assert!( + encoded + .windows(4) + .any(|window| window == [0xc4, 0x40, 6, 6]), + "provider signature must use the portable MessagePack binary shape" + ); + assert_eq!( + rmp_serde::from_slice::(&encoded)?, + health + ); + assert_eq!( + rmp_serde::from_slice::(&rmp_serde::to_vec( + &anchor + )?)?, + anchor + ); + assert_eq!( + rmp_serde::from_slice::( + &rmp_serde::to_vec(&projection)? + )?, + projection + ); + assert_eq!(authenticated_provider_health_reason_code("missing"), None); + Ok(()) + } + + #[test] + fn activation_binding_is_additive_for_legacy_generic_health_records() -> Result<()> { + let mut health = signed_health(); + health.activation_witness_sha256 = None; + let mut legacy = rmp_serde::to_vec(&health)?; + assert_eq!(&legacy[..3], &[0xdc, 0, 18]); + assert_eq!(legacy.pop(), Some(0xc0)); + legacy[2] = 17; + let decoded: IdunnSignedDaemonHealthRecord = rmp_serde::from_slice(&legacy)?; + assert_eq!(decoded.activation_witness_sha256, None); + decoded.validate()?; + let (decoded, unsigned) = + IdunnSignedDaemonHealthRecord::decode_canonical_signed_payload(&legacy)?; + assert_eq!(decoded.activation_witness_sha256, None); + assert_eq!(&unsigned[..3], &[0xdc, 0, 17]); + Ok(()) + } + + #[test] + fn contracts_refuse_substitution_and_manufactured_states() { + let mut value = anchor(); + value.binding_authority = "idunn".into(); + assert!(value.validate().is_err()); + let mut value = anchor(); + value.signer_identity_id = "caller-selected".into(); + assert!(value.validate().is_err()); + let mut value = projection(); + value.reason_code = "release_drift".into(); + assert!(value.validate().is_err()); + let mut value = projection(); + value.provider_state = "missing".into(); + assert!(value.validate().is_err()); + let mut value = projection(); + value.deployment_id = None; + assert!(value.validate().is_err()); + let mut value = projection(); + value.signature.clear(); + assert!(value.validate().is_err()); + let mut value = projection(); + value.signed_health_sha256 = format!("sha256-{}", "A".repeat(64)); + assert!(value.validate().is_err()); + let mut value = signed_health(); + value.publisher_sequence = 0; + assert!(value.validate().is_err()); + let mut value = signed_health(); + value.activation_witness_sha256 = Some(format!("sha256-{}", "F".repeat(64))); + assert!(value.validate().is_err()); + let mut value = signed_health(); + value.release_witness_sha256 = None; + assert!(value.validate().is_err()); + let mut value = signed_health(); + value.private_state_exposed = true; + assert!(value.validate().is_err()); + } +} diff --git a/packages/cultnet-rs/src/runtime_authority_contracts.rs b/packages/cultnet-rs/src/runtime_authority_contracts.rs new file mode 100644 index 00000000..426be1fb --- /dev/null +++ b/packages/cultnet-rs/src/runtime_authority_contracts.rs @@ -0,0 +1,3309 @@ +use anyhow::{Context, Result, bail, ensure}; +use cultcache_rs::DatabaseEntry; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::io::{Read, Write}; +use zeroize::Zeroizing; + +pub const GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA: &str = "gamecult.runtime_presence_health.v2"; +pub const GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE: &str = + "gamecult.runtime_presence_health.v2"; +pub const GAMECULT_RUNTIME_ACTIVATION_PROOF_SIGNING_PURPOSE: &str = + "gamecult.runtime_presence.activation-proof.v1"; +pub const IDUNN_RUNTIME_ACTIVATION_SCHEMA: &str = "idunn.runtime_activation.v2"; +pub const IDUNN_RUNTIME_ACTIVATION_SIGNING_PURPOSE: &str = "idunn.runtime_activation.v2"; +pub const IDUNN_RUNTIME_ACTIVATION_CREDENTIAL_NAME: &str = "gamecult-idunn-runtime-activation-key"; +pub const IDUNN_PROCESS_WRITE_LEASE_SCHEMA: &str = "idunn.process_write_lease.v1"; +pub const IDUNN_EXPECTED_INCARNATION_SCHEMA: &str = "idunn.expected_incarnation.v2"; +pub const ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA: &str = "odin.runtime_topology_correlation.v2"; +pub const ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE: &str = + "odin.runtime_topology_correlation.v2"; + +const MAX_RUNTIME_CAPABILITIES: usize = 256; +const MAX_RUNTIME_DEPENDENCIES: usize = 256; +const MAX_TOPOLOGY_DISAGREEMENTS: usize = 256; +const IDUNN_RUNTIME_ACTIVATION_ID_DOMAIN: &[u8] = b"idunn.runtime-activation.id.v1\0"; +const IDUNN_RUNTIME_ACTIVATION_SIGNATURE_DOMAIN: &[u8] = b"idunn.runtime-activation.signature.v1\0"; + +/// One provider-owned capability claim inside a runtime-presence statement. +/// The enclosing vector is canonical only when these records are strictly +/// increasing by `(capability, schema, compatibility)`. Capacity describes +/// that one claim and does not create a second identity for the same claim. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GameCultRuntimeCapability { + pub capability: String, + pub schema: String, + pub compatibility: String, + pub capacity: u32, +} + +impl GameCultRuntimeCapability { + fn identity(&self) -> (&str, &str, &str) { + (&self.capability, &self.schema, &self.compatibility) + } +} + +/// Idunn's admitted requirement for one provider-owned runtime capability. +/// The provider reports actual capacity in `GameCultRuntimeCapability`; this +/// record states only the minimum needed for this incarnation to be Ready. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct IdunnExpectedCapability { + pub capability: String, + pub schema: String, + pub compatibility: String, + pub minimum_capacity: u32, +} + +impl IdunnExpectedCapability { + fn identity(&self) -> (&str, &str, &str) { + (&self.capability, &self.schema, &self.compatibility) + } +} + +/// Dual-proved claim from one exact runtime launch. The stable provider key and +/// Idunn's activation-scoped ephemeral key both sign `canonical_proof_payload`: +/// the canonical positional encoding of this complete record with both proof +/// byte fields empty. Present authority requires a matching Idunn-published +/// current activation; neither proof can establish admission by itself. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.runtime_presence_health", + schema = "gamecult.runtime_presence_health.v2" +)] +pub struct GameCultRuntimePresenceHealthRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub target: String, + #[cultcache(key = 2)] + pub expected_projection_sha256: String, + #[cultcache(key = 3)] + pub plan_id: String, + #[cultcache(key = 4)] + pub incarnation_id: String, + #[cultcache(key = 5)] + pub sealed_release_id: String, + #[cultcache(key = 6)] + pub activation_witness_sha256: String, + #[cultcache(key = 7)] + pub state_schema_generation: Option, + #[cultcache(key = 8)] + pub state_contract_sha256: Option, + #[cultcache(key = 9)] + pub runtime_id: String, + #[cultcache(key = 10)] + pub runtime_instance_id: String, + #[cultcache(key = 11)] + pub bound_endpoint: Option, + #[cultcache(key = 12)] + pub capabilities: Vec, + #[cultcache(key = 13)] + pub health_contract: String, + #[cultcache(key = 14)] + pub state: String, + #[cultcache(key = 15)] + pub detail: String, + #[cultcache(key = 16)] + pub write_lease_sha256: Option, + #[cultcache(key = 17)] + pub signer_identity_id: String, + #[cultcache(key = 18)] + pub publisher_sequence: u64, + #[cultcache(key = 19)] + pub observed_at_unix_millis: u64, + #[cultcache(key = 20)] + pub signature_algorithm: String, + #[cultcache(key = 21, bytes)] + pub signature: Vec, + #[cultcache(key = 22)] + pub activation_signer_identity_id: String, + #[cultcache(key = 23, bytes)] + pub activation_signature: Vec, +} + +impl GameCultRuntimePresenceHealthRecord { + /// Return the one canonical payload covered by both the stable provider + /// signature and the activation-scoped proof. Both signature fields are + /// empty in these bytes, avoiding circular signatures while binding every + /// authority-bearing statement field and both signer identities. + pub fn canonical_proof_payload(&self) -> Result> { + self.validate_shape(true)?; + let mut unsigned = self.clone(); + unsigned.signature.clear(); + unsigned.activation_signature.clear(); + Ok(rmp_serde::to_vec(&unsigned)?) + } + + /// Digest the complete signed presence record for an Odin correlation + /// receipt. An unsigned construction value is deliberately not digestible. + pub fn canonical_sha256(&self) -> Result { + self.validate()?; + Ok(prefixed_sha256(&rmp_serde::to_vec(self)?)) + } + + pub fn validate(&self) -> Result<()> { + self.validate_shape(false) + } + + fn validate_shape(&self, allow_unsigned: bool) -> Result<()> { + if self.schema_version != GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA { + bail!("runtime presence schema is unsupported"); + } + validate_authority_identifier(&self.target, "target")?; + validate_required_sha256( + &self.expected_projection_sha256, + "expected projection sha256", + )?; + validate_required_sha256(&self.plan_id, "plan id")?; + validate_authority_identifier(&self.incarnation_id, "incarnation id")?; + validate_required_sha256(&self.sealed_release_id, "sealed release id")?; + validate_required_sha256(&self.activation_witness_sha256, "activation witness sha256")?; + match (&self.state_schema_generation, &self.state_contract_sha256) { + (Some(generation), Some(contract_sha256)) => { + validate_authority_identifier(generation, "state schema generation")?; + validate_required_sha256(contract_sha256, "state contract sha256")?; + } + (None, None) => {} + _ => bail!("runtime presence state lineage is partial"), + } + validate_authority_identifier(&self.runtime_id, "runtime id")?; + validate_required_sha256(&self.runtime_instance_id, "runtime instance id")?; + validate_optional_endpoint(&self.bound_endpoint)?; + validate_runtime_capabilities(&self.capabilities)?; + validate_authority_identifier(&self.health_contract, "health contract")?; + validate_authority_identifier(&self.signer_identity_id, "signer identity id")?; + validate_authority_identifier( + &self.activation_signer_identity_id, + "activation signer identity id", + )?; + validate_optional_sha256(&self.write_lease_sha256, "write lease sha256")?; + + let signature_is_valid = + self.signature.len() == 64 || (allow_unsigned && self.signature.is_empty()); + let activation_signature_is_valid = self.activation_signature.len() == 64 + || (allow_unsigned && self.activation_signature.is_empty()); + if self.publisher_sequence == 0 + || self.observed_at_unix_millis == 0 + || !matches!( + self.state.as_str(), + "warming" | "active" | "degraded" | "failed" + ) + || self.detail.len() > 512 + || self.detail.chars().any(char::is_control) + || self.signature_algorithm != "ed25519" + || !signature_is_valid + || !activation_signature_is_valid + { + bail!("runtime presence state, sequence, detail, or signature is invalid"); + } + if self.state == "warming" && self.write_lease_sha256.is_some() { + bail!("warming runtime presence cannot claim a process write lease"); + } + if self.state_schema_generation.is_none() && self.write_lease_sha256.is_some() { + bail!("stateless runtime presence cannot claim a process write lease"); + } + Ok(()) + } +} + +/// Public route facts admitted for one incarnation. These are service-facing +/// endpoints only; actuator paths, unit files, container sockets, and runner +/// credentials do not belong in the Expected projection. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct IdunnExpectedRoute { + pub route_id: String, + pub transport: String, + pub stable_endpoint: String, + pub candidate_endpoint: String, +} + +impl IdunnExpectedRoute { + fn validate(&self) -> Result<()> { + validate_authority_identifier(&self.route_id, "route id")?; + validate_endpoint(&self.stable_endpoint, "stable route endpoint")?; + validate_endpoint(&self.candidate_endpoint, "candidate route endpoint")?; + if self.stable_endpoint == self.candidate_endpoint { + bail!("stable and candidate route endpoints are identical"); + } + match self.transport.as_str() { + "http" => { + if !(self.stable_endpoint.starts_with("http://") + || self.stable_endpoint.starts_with("https://")) + || !self.candidate_endpoint.starts_with("http://") + { + bail!("HTTP route endpoints use an unsupported scheme"); + } + } + "tcp" => { + if !self.stable_endpoint.starts_with("tcp://") + || !self.candidate_endpoint.starts_with("tcp://") + { + bail!("TCP route endpoints use an unsupported scheme"); + } + } + "rudp" => { + if !self.stable_endpoint.starts_with("rudp://") + || !self.candidate_endpoint.starts_with("rudp://") + { + bail!("RUDP route endpoints use an unsupported scheme"); + } + } + _ => bail!("route transport is unsupported"), + } + Ok(()) + } +} + +/// One capability requirement and Idunn's exact admitted provider selection. +/// Provider fields are all absent while unresolved. Managed providers bind an +/// Expected digest; external operator bindings instead bind an endpoint. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct IdunnExpectedDependency { + pub kind: String, + pub capability: String, + pub schema: String, + pub compatibility: String, + pub minimum_capacity: u32, + pub startup: String, + pub provider_id: Option, + pub provider_authority: Option, + pub provider_expected_projection_sha256: Option, + pub provider_endpoint: Option, +} + +impl IdunnExpectedDependency { + fn identity(&self) -> (&str, &str, &str) { + (&self.capability, &self.schema, &self.compatibility) + } + + fn validate(&self) -> Result<()> { + if !matches!( + self.kind.as_str(), + "bootstrap" + | "required" + | "optional" + | "shared-infrastructure" + | "private" + | "external-operator-binding" + ) { + bail!("dependency kind is unsupported"); + } + validate_authority_identifier(&self.capability, "dependency capability")?; + validate_authority_identifier(&self.schema, "dependency schema")?; + validate_authority_identifier(&self.compatibility, "dependency compatibility")?; + if self.minimum_capacity == 0 + || !matches!(self.startup.as_str(), "before-start" | "before-promotion") + { + bail!("dependency capacity or startup condition is invalid"); + } + if self.kind == "bootstrap" && self.startup != "before-start" { + bail!("bootstrap dependency must be satisfied before start"); + } + validate_optional_identifier(&self.provider_id, "dependency provider id")?; + validate_optional_identifier(&self.provider_authority, "dependency provider authority")?; + validate_optional_sha256( + &self.provider_expected_projection_sha256, + "dependency provider expected projection sha256", + )?; + validate_optional_endpoint_labeled( + &self.provider_endpoint, + "dependency provider endpoint", + )?; + + match ( + &self.provider_id, + &self.provider_authority, + &self.provider_expected_projection_sha256, + &self.provider_endpoint, + ) { + (None, None, None, None) => {} + (Some(_), Some(authority), Some(_), _) if authority == "managed-incarnation" => { + if self.kind == "external-operator-binding" { + bail!("external operator dependency cannot select a managed incarnation"); + } + } + (Some(_), Some(authority), None, Some(_)) + if authority == "external-operator-binding" + && self.kind == "external-operator-binding" => {} + _ => bail!("dependency provider binding is partial or authority-incoherent"), + } + Ok(()) + } +} + +/// Idunn-owned desired deployment identity published into CultMesh before +/// promotion. It describes the admitted incarnation and its public contracts, +/// never the private details needed to execute it on a particular host. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.expected_incarnation", + schema = "idunn.expected_incarnation.v2" +)] +pub struct IdunnExpectedIncarnationRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub target: String, + #[cultcache(key = 2)] + pub plan_id: String, + #[cultcache(key = 3)] + pub incarnation_id: String, + #[cultcache(key = 4)] + pub sealed_release_id: String, + #[cultcache(key = 5)] + pub source_repository: String, + #[cultcache(key = 6)] + pub source_revision: String, + #[cultcache(key = 7)] + pub recipe_sha256: String, + #[cultcache(key = 8)] + pub runtime_id: String, + #[cultcache(key = 9)] + pub expected_signer_identity_id: String, + #[cultcache(key = 10)] + pub health_contract: String, + #[cultcache(key = 11)] + pub artifact_sha256: String, + #[cultcache(key = 12)] + pub state_schema_generation: Option, + #[cultcache(key = 13)] + pub state_contract_sha256: Option, + #[cultcache(key = 14)] + pub write_lease_required: bool, + #[cultcache(key = 15)] + pub route: Option, + #[cultcache(key = 16)] + pub capabilities: Vec, + #[cultcache(key = 17)] + pub dependencies: Vec, +} + +impl IdunnExpectedIncarnationRecord { + pub fn decode_canonical(payload: &[u8]) -> Result { + if messagepack_array_len(payload) != Some(18) { + bail!("expected incarnation is not the 18-field positional contract"); + } + let expected: Self = + rmp_serde::from_slice(payload).context("decoding expected incarnation")?; + if rmp_serde::to_vec(&expected)? != payload { + bail!("expected incarnation is not canonical positional MessagePack"); + } + expected.validate()?; + Ok(expected) + } + + pub fn canonical_bytes(&self) -> Result> { + self.validate()?; + Ok(rmp_serde::to_vec(self)?) + } + + pub fn canonical_sha256(&self) -> Result { + Ok(prefixed_sha256(&self.canonical_bytes()?)) + } + + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_EXPECTED_INCARNATION_SCHEMA { + bail!("expected incarnation schema is unsupported"); + } + validate_authority_identifier(&self.target, "target")?; + validate_required_sha256(&self.plan_id, "plan id")?; + validate_authority_identifier(&self.incarnation_id, "incarnation id")?; + validate_required_sha256(&self.sealed_release_id, "sealed release id")?; + validate_source_repository(&self.source_repository)?; + if !is_lower_hex(&self.source_revision, 40) { + bail!("source revision is not a lowercase Git object id"); + } + validate_required_sha256(&self.recipe_sha256, "recipe sha256")?; + validate_authority_identifier(&self.runtime_id, "runtime id")?; + validate_authority_identifier( + &self.expected_signer_identity_id, + "expected signer identity id", + )?; + validate_authority_identifier(&self.health_contract, "health contract")?; + validate_required_sha256(&self.artifact_sha256, "artifact sha256")?; + match (&self.state_schema_generation, &self.state_contract_sha256) { + (Some(generation), Some(contract_sha256)) => { + validate_authority_identifier(generation, "state schema generation")?; + validate_required_sha256(contract_sha256, "state contract sha256")?; + } + (None, None) => {} + _ => bail!("expected incarnation state lineage is partial"), + } + if self.write_lease_required && self.state_schema_generation.is_none() { + bail!("stateless incarnation cannot require a process write lease"); + } + if let Some(route) = &self.route { + route.validate()?; + } + validate_expected_capabilities(&self.capabilities)?; + validate_expected_dependencies(&self.dependencies)?; + Ok(()) + } +} + +/// Odin's observation of one dependency selected in the bound Expected +/// projection. Evidence digests identify the signed/provider-owned facts Odin +/// used; they do not copy those facts into Odin authority. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OdinRuntimeDependencyEvidence { + pub kind: String, + pub capability: String, + pub schema: String, + pub compatibility: String, + pub provider_id: Option, + pub provider_authority: Option, + pub provider_expected_projection_sha256: Option, + pub provider_endpoint: Option, + pub observed_capacity: Option, + pub provider_evidence_sha256: Option, + pub ready: bool, +} + +impl OdinRuntimeDependencyEvidence { + fn identity(&self) -> (&str, &str, &str) { + (&self.capability, &self.schema, &self.compatibility) + } + + fn validate(&self) -> Result<()> { + if !matches!( + self.kind.as_str(), + "bootstrap" + | "required" + | "optional" + | "shared-infrastructure" + | "private" + | "external-operator-binding" + ) { + bail!("dependency evidence kind is unsupported"); + } + validate_authority_identifier(&self.kind, "dependency evidence kind")?; + validate_authority_identifier(&self.capability, "dependency evidence capability")?; + validate_authority_identifier(&self.schema, "dependency evidence schema")?; + validate_authority_identifier(&self.compatibility, "dependency evidence compatibility")?; + validate_optional_identifier(&self.provider_id, "dependency evidence provider id")?; + validate_optional_identifier( + &self.provider_authority, + "dependency evidence provider authority", + )?; + validate_optional_sha256( + &self.provider_expected_projection_sha256, + "dependency evidence provider expected projection sha256", + )?; + validate_optional_endpoint_labeled( + &self.provider_endpoint, + "dependency evidence provider endpoint", + )?; + validate_optional_sha256( + &self.provider_evidence_sha256, + "dependency provider evidence sha256", + )?; + if self.observed_capacity == Some(0) { + bail!("dependency evidence capacity is zero"); + } + if self.provider_id.is_none() + && (self.provider_authority.is_some() + || self.provider_expected_projection_sha256.is_some() + || self.provider_endpoint.is_some() + || self.observed_capacity.is_some() + || self.provider_evidence_sha256.is_some() + || self.ready) + { + bail!("dependency evidence has facts without a provider"); + } + if self.provider_id.is_some() && self.provider_authority.is_none() { + bail!("dependency evidence provider authority is missing"); + } + match ( + &self.provider_id, + &self.provider_authority, + &self.provider_expected_projection_sha256, + &self.provider_endpoint, + ) { + (None, None, None, None) => {} + (Some(_), Some(authority), Some(_), _) if authority == "managed-incarnation" => { + if self.kind == "external-operator-binding" { + bail!("external dependency evidence names a managed incarnation"); + } + } + (Some(_), Some(authority), None, Some(_)) + if authority == "external-operator-binding" + && self.kind == "external-operator-binding" => {} + _ => bail!("dependency evidence provider binding is authority-incoherent"), + } + if self.ready + && (self.observed_capacity.is_none() || self.provider_evidence_sha256.is_none()) + { + bail!("ready dependency lacks capacity or signed evidence"); + } + Ok(()) + } +} + +/// One explicit Expected/observed disagreement. Codes are unique and sorted so +/// the signed receipt has one canonical explanation for each disagreement. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OdinTopologyDisagreement { + pub code: String, + pub expected: Option, + pub observed: Option, +} + +impl OdinTopologyDisagreement { + fn validate(&self) -> Result<()> { + validate_authority_identifier(&self.code, "topology disagreement code")?; + validate_optional_detail(&self.expected, "topology disagreement expected value")?; + validate_optional_detail(&self.observed, "topology disagreement observed value")?; + if self.expected.is_none() && self.observed.is_none() { + bail!("topology disagreement carries no expected or observed value"); + } + Ok(()) + } +} + +/// Odin-owned signed correlation of Idunn's exact Expected projection with +/// current Idunn activation and provider-signed presence. Presence state and +/// write-lease observation remain explicit so an opaque Ready bit cannot cross +/// Idunn's warming/fencing boundary. The signature covers the canonical +/// positional encoding with `signature` empty. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "odin.runtime_topology_correlation", + schema = "odin.runtime_topology_correlation.v2" +)] +pub struct OdinRuntimeTopologyCorrelationRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub target: String, + #[cultcache(key = 2)] + pub expected_projection_sha256: String, + #[cultcache(key = 3)] + pub expected: bool, + #[cultcache(key = 4)] + pub current_activation_sha256: Option, + #[cultcache(key = 5)] + pub signed_presence_sha256: Option, + #[cultcache(key = 6)] + pub observed_presence_state: Option, + #[cultcache(key = 7)] + pub observed_presence_publisher_sequence: Option, + #[cultcache(key = 8)] + pub observed_write_lease_sha256: Option, + #[cultcache(key = 9)] + pub observed_capabilities: Vec, + #[cultcache(key = 10)] + pub runtime_id: String, + #[cultcache(key = 11)] + pub runtime_instance_id: Option, + #[cultcache(key = 12)] + pub present: bool, + #[cultcache(key = 13)] + pub ready: bool, + #[cultcache(key = 14)] + pub dependencies: Vec, + #[cultcache(key = 15)] + pub disagreements: Vec, + #[cultcache(key = 16)] + pub signer_identity_id: String, + #[cultcache(key = 17)] + pub publisher_sequence: u64, + #[cultcache(key = 18)] + pub observed_at_unix_millis: u64, + #[cultcache(key = 19)] + pub signature_algorithm: String, + #[cultcache(key = 20, bytes)] + pub signature: Vec, +} + +impl OdinRuntimeTopologyCorrelationRecord { + pub fn decode_canonical_signed_payload(payload: &[u8]) -> Result<(Self, Vec)> { + if messagepack_array_len(payload) != Some(21) { + bail!("topology correlation is not the 21-field positional contract"); + } + let receipt: Self = + rmp_serde::from_slice(payload).context("decoding runtime topology correlation")?; + if rmp_serde::to_vec(&receipt)? != payload { + bail!("topology correlation is not canonical positional MessagePack"); + } + receipt.validate()?; + let unsigned = receipt.unsigned_signature_payload()?; + Ok((receipt, unsigned)) + } + + pub fn unsigned_signature_payload(&self) -> Result> { + self.validate_shape(true)?; + let mut unsigned = self.clone(); + unsigned.signature.clear(); + Ok(rmp_serde::to_vec(&unsigned)?) + } + + pub fn canonical_bytes(&self) -> Result> { + self.validate()?; + Ok(rmp_serde::to_vec(self)?) + } + + pub fn canonical_sha256(&self) -> Result { + Ok(prefixed_sha256(&self.canonical_bytes()?)) + } + + pub fn validate(&self) -> Result<()> { + self.validate_shape(false) + } + + pub fn validate_against_expected( + &self, + expected: &IdunnExpectedIncarnationRecord, + current_write_lease_sha256: Option<&str>, + ) -> Result<()> { + self.validate()?; + expected.validate()?; + if self.expected_projection_sha256 != expected.canonical_sha256()? + || self.target != expected.target + || self.runtime_id != expected.runtime_id + || self.dependencies.len() != expected.dependencies.len() + { + bail!("topology correlation substitutes or omits Expected authority"); + } + for (evidence, requirement) in self.dependencies.iter().zip(&expected.dependencies) { + if evidence.identity() != requirement.identity() + || evidence.kind != requirement.kind + || evidence.provider_id != requirement.provider_id + || evidence.provider_authority != requirement.provider_authority + || evidence.provider_expected_projection_sha256 + != requirement.provider_expected_projection_sha256 + || evidence.provider_endpoint != requirement.provider_endpoint + { + bail!("topology dependency evidence does not bind the Expected dependency"); + } + if evidence.ready + && evidence + .observed_capacity + .is_none_or(|capacity| capacity < requirement.minimum_capacity) + { + bail!("ready dependency does not meet Expected capacity"); + } + } + let mut capability_disagreements = Vec::new(); + correlate_capabilities( + &mut capability_disagreements, + &expected.capabilities, + &self.observed_capabilities, + ); + for disagreement in &capability_disagreements { + if !self.disagreements.contains(disagreement) { + bail!("topology correlation omits an observed capability disagreement"); + } + } + if let Some(current_write_lease_sha256) = current_write_lease_sha256 { + validate_required_sha256(current_write_lease_sha256, "current write lease sha256")?; + } + if expected.write_lease_required { + if self + .observed_write_lease_sha256 + .as_deref() + .is_some_and(|observed| Some(observed) != current_write_lease_sha256) + { + bail!("observed write lease is not the current Idunn grant"); + } + if self.ready + && (self.observed_write_lease_sha256.is_none() + || current_write_lease_sha256.is_none()) + { + bail!("Ready stateful topology lacks the exact current write lease"); + } + } else if self.observed_write_lease_sha256.is_some() || current_write_lease_sha256.is_some() + { + bail!("stateless Expected topology cannot carry a process write lease"); + } + Ok(()) + } + + fn validate_shape(&self, allow_unsigned: bool) -> Result<()> { + if self.schema_version != ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA { + bail!("topology correlation schema is unsupported"); + } + validate_authority_identifier(&self.target, "target")?; + validate_required_sha256( + &self.expected_projection_sha256, + "correlation expected projection sha256", + )?; + if !self.expected { + bail!("topology correlation cannot deny the Expected record it binds"); + } + validate_optional_sha256(&self.current_activation_sha256, "current activation sha256")?; + validate_optional_sha256(&self.signed_presence_sha256, "signed presence sha256")?; + validate_optional_identifier(&self.observed_presence_state, "observed presence state")?; + if self.observed_presence_publisher_sequence == Some(0) { + bail!("observed presence publisher sequence is zero"); + } + validate_optional_sha256( + &self.observed_write_lease_sha256, + "observed write lease sha256", + )?; + validate_runtime_capabilities(&self.observed_capabilities)?; + if self.signed_presence_sha256.is_none() && !self.observed_capabilities.is_empty() { + bail!("topology correlation has capabilities without signed presence"); + } + match ( + &self.signed_presence_sha256, + &self.observed_presence_state, + self.observed_presence_publisher_sequence, + &self.observed_write_lease_sha256, + ) { + (None, None, None, None) => {} + (Some(_), Some(state), Some(_), write_lease) + if matches!(state.as_str(), "warming" | "active" | "degraded" | "failed") => + { + if state == "warming" && write_lease.is_some() { + bail!("warming topology presence cannot carry a process write lease"); + } + } + _ => bail!("signed presence state, sequence, or write-lease observation is partial"), + } + validate_authority_identifier(&self.runtime_id, "runtime id")?; + validate_optional_sha256(&self.runtime_instance_id, "runtime instance id")?; + let has_runtime_evidence = + self.current_activation_sha256.is_some() || self.signed_presence_sha256.is_some(); + if self.runtime_instance_id.is_some() != has_runtime_evidence { + bail!("runtime instance identity and observation evidence are partial"); + } + if self.present + && (self.current_activation_sha256.is_none() + || self.signed_presence_sha256.is_none() + || self.observed_presence_publisher_sequence.is_none() + || self.runtime_instance_id.is_none()) + { + bail!("Present topology state lacks an authenticated runtime session"); + } + validate_dependency_evidence(&self.dependencies)?; + validate_topology_disagreements(&self.disagreements)?; + let has_activation = self.current_activation_sha256.is_some(); + let has_presence = self.signed_presence_sha256.is_some(); + if has_activation && has_presence && !self.present { + bail!("authenticated runtime session cannot be projected as not Present"); + } + if has_activation != has_presence && self.disagreements.is_empty() { + bail!("partial or rejected runtime evidence lacks an explicit disagreement"); + } + if self.ready + && (!self.present + || self.observed_presence_state.as_deref() != Some("active") + || !self.disagreements.is_empty() + || self + .dependencies + .iter() + .any(|dependency| dependency.kind != "optional" && !dependency.ready)) + { + bail!("Ready topology state is not supported by correlated evidence"); + } + validate_authority_identifier(&self.signer_identity_id, "signer identity id")?; + let signature_is_valid = + self.signature.len() == 64 || (allow_unsigned && self.signature.is_empty()); + if self.publisher_sequence == 0 + || self.observed_at_unix_millis == 0 + || self.signature_algorithm != "ed25519" + || !signature_is_valid + { + bail!("topology correlation sequence, time, or signature is invalid"); + } + Ok(()) + } +} + +/// An in-memory activation-scoped signer reconstructed by the launched service +/// from its protected systemd credential. The stable service identity never +/// receives this key, and the public activation record never contains it. +pub struct IdunnRuntimeActivationSigner { + signing_key: SigningKey, +} + +impl IdunnRuntimeActivationSigner { + fn generate() -> Self { + let mut seed = Zeroizing::new([0_u8; 32]); + rand::rng().fill_bytes(seed.as_mut()); + Self { + signing_key: SigningKey::from_bytes(&*seed), + } + } + + /// Open the exact raw 32-byte Ed25519 seed supplied through Idunn's systemd + /// credential without retaining an ordinary heap copy. No persistent key + /// schema exists: this authority lives for one activation and is replaced + /// on every launch. + pub fn from_credential_reader(mut credential: impl Read) -> Result { + let mut seed = Zeroizing::new([0_u8; 32]); + credential + .read_exact(seed.as_mut()) + .context("reading runtime activation credential")?; + let mut extra = [0_u8; 1]; + if credential.read(&mut extra)? != 0 { + bail!("runtime activation credential is longer than 32 bytes"); + } + Ok(Self { + signing_key: SigningKey::from_bytes(&*seed), + }) + } + + fn write_credential(&self, mut destination: impl Write) -> Result<()> { + let seed = Zeroizing::new(self.signing_key.to_bytes()); + destination + .write_all(seed.as_ref()) + .context("writing runtime activation credential") + } + + pub fn public_key(&self) -> Vec { + self.signing_key.verifying_key().to_bytes().to_vec() + } + + pub fn identity_id(&self) -> String { + derive_idunn_runtime_activation_identity_id(&self.public_key()) + .expect("an Ed25519 activation signer always has a 32-byte public key") + } + + pub fn sign_presence_proof( + &self, + presence: &GameCultRuntimePresenceHealthRecord, + ) -> Result> { + if presence.activation_signer_identity_id != self.identity_id() { + bail!("runtime presence names a different activation signer"); + } + let payload = presence.canonical_proof_payload()?; + Ok(self + .signing_key + .sign(&runtime_activation_signing_message(&payload)) + .to_bytes() + .to_vec()) + } +} + +/// Idunn's one-shot launch material. Issuance always creates a fresh key, and +/// writing the protected credential consumes the only Idunn-side signer. +pub struct IdunnRuntimeActivationLaunch { + activation: IdunnRuntimeActivationRecord, + signer: IdunnRuntimeActivationSigner, +} + +impl IdunnRuntimeActivationLaunch { + pub fn issue( + expected: &IdunnExpectedIncarnationRecord, + runtime_instance_id: String, + issued_at_unix_millis: u64, + idunn_signer: &crate::ServiceIdentitySigner, + ) -> Result { + let signer = IdunnRuntimeActivationSigner::generate(); + let activation = IdunnRuntimeActivationRecord::issue_with_signer( + expected, + runtime_instance_id, + &signer, + issued_at_unix_millis, + idunn_signer, + )?; + Ok(Self { activation, signer }) + } + + pub fn activation(&self) -> &IdunnRuntimeActivationRecord { + &self.activation + } + + pub fn write_credential(self, destination: impl Write) -> Result { + self.signer.write_credential(destination)?; + Ok(self.activation) + } +} + +/// Derive the activation-scoped identity from the Idunn-issued public key. The +/// domain is dedicated to runtime activation and cannot be substituted for a +/// stable provider, Idunn, or Odin service identity. +pub fn derive_idunn_runtime_activation_identity_id(public_key: &[u8]) -> Result { + if public_key.len() != 32 { + bail!("runtime activation public key has invalid length"); + } + Ok(format!( + "{:x}", + Sha256::digest([IDUNN_RUNTIME_ACTIVATION_ID_DOMAIN, public_key].concat()) + )) +} + +/// Current Idunn authority for one launch. Idunn's stable signature binds the +/// complete Expected digest and activation key; Expected in turn pins the +/// provider signer identity. The provider public key is lookup material, not a +/// second authority: its profile-derived identity must equal Idunn's signed +/// selection exactly. +#[derive(Clone, Debug)] +pub struct VerifiedRuntimeAuthority { + expected: IdunnExpectedIncarnationRecord, + activation: IdunnRuntimeActivationRecord, + provider_signer_public_key: Vec, + expected_sha256: String, + activation_sha256: String, +} + +impl VerifiedRuntimeAuthority { + pub fn expected(&self) -> &IdunnExpectedIncarnationRecord { + &self.expected + } + + pub fn activation(&self) -> &IdunnRuntimeActivationRecord { + &self.activation + } + + pub fn expected_sha256(&self) -> &str { + &self.expected_sha256 + } + + pub fn activation_sha256(&self) -> &str { + &self.activation_sha256 + } +} + +pub fn verify_runtime_authority( + expected: &IdunnExpectedIncarnationRecord, + activation: &IdunnRuntimeActivationRecord, + idunn_anchor: &crate::ServiceIdentityTrustAnchor, + provider_signer_public_key: &[u8], +) -> Result { + expected.validate()?; + activation.verify_for_expected(expected, idunn_anchor)?; + if crate::derive_service_identity_id::( + provider_signer_public_key, + )? != expected.expected_signer_identity_id + { + bail!("provider public key is not Idunn's Expected signer selection"); + } + Ok(VerifiedRuntimeAuthority { + expected: expected.clone(), + activation: activation.clone(), + provider_signer_public_key: provider_signer_public_key.to_vec(), + expected_sha256: expected.canonical_sha256()?, + activation_sha256: activation.canonical_sha256()?, + }) +} + +#[derive(Clone, Copy, Debug)] +pub struct RuntimePresenceAuthenticationContext { + pub trusted_received_at_unix_millis: u64, + pub maximum_age_millis: u64, + pub maximum_future_skew_millis: u64, +} + +/// Canonical runtime bytes authenticated by both the Expected provider key and +/// the current activation key. No semantic claim has yet been accepted as +/// Present; Odin must correlate this observation against current authority. +#[derive(Clone, Debug)] +pub struct AuthenticatedRuntimePresenceClaim { + record: GameCultRuntimePresenceHealthRecord, + canonical_bytes: Vec, + signed_presence_sha256: String, + received_at_unix_millis: u64, +} + +impl AuthenticatedRuntimePresenceClaim { + pub fn record(&self) -> &GameCultRuntimePresenceHealthRecord { + &self.record + } + + pub fn canonical_bytes(&self) -> &[u8] { + &self.canonical_bytes + } + + pub fn signed_presence_sha256(&self) -> &str { + &self.signed_presence_sha256 + } + + pub fn received_at_unix_millis(&self) -> u64 { + self.received_at_unix_millis + } +} + +/// A signed Odin receipt authenticated against the exact current Expected, +/// activation, write lease, Idunn-admitted topology key, and trusted receipt +/// time. This is evidence, not a replay cursor or route-promotion grant. The +/// caller must source the key from Idunn's admitted Odin binding. Idunn's +/// durable admission owner must atomically check and advance Odin's publisher +/// sequence before treating the receipt as current. +#[derive(Clone, Debug)] +pub struct AuthenticatedOdinRuntimeTopologyCorrelation { + record: OdinRuntimeTopologyCorrelationRecord, + canonical_bytes: Vec, +} + +impl AuthenticatedOdinRuntimeTopologyCorrelation { + pub fn record(&self) -> &OdinRuntimeTopologyCorrelationRecord { + &self.record + } + + pub fn canonical_bytes(&self) -> &[u8] { + &self.canonical_bytes + } +} + +#[derive(Clone, Copy, Debug)] +pub struct OdinTopologyAuthenticationContext { + pub trusted_received_at_unix_millis: u64, + pub maximum_age_millis: u64, + pub maximum_future_skew_millis: u64, +} + +pub fn authenticate_odin_runtime_topology_correlation( + canonical_correlation: &[u8], + authority: &VerifiedRuntimeAuthority, + current_write_lease_sha256: Option<&str>, + admitted_odin_signer_public_key: &[u8], + context: OdinTopologyAuthenticationContext, +) -> Result { + ensure!( + context.trusted_received_at_unix_millis > 0 && context.maximum_age_millis > 0, + "Odin topology trusted time and maximum age must be positive" + ); + let (record, unsigned) = OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload( + canonical_correlation, + )?; + record.validate_against_expected(authority.expected(), current_write_lease_sha256)?; + if record.current_activation_sha256.as_deref() != Some(authority.activation_sha256()) + || record.runtime_instance_id.as_deref() + != Some(authority.activation.runtime_instance_id.as_str()) + { + bail!("topology correlation does not bind the current activation"); + } + let latest_trusted_timestamp = context + .trusted_received_at_unix_millis + .saturating_add(context.maximum_future_skew_millis); + if record.observed_at_unix_millis > latest_trusted_timestamp + || context + .trusted_received_at_unix_millis + .saturating_sub(record.observed_at_unix_millis) + > context.maximum_age_millis + { + bail!("Odin topology correlation is outside the trusted observation window"); + } + crate::verify_service_identity_signature_with_public_key::< + crate::OdinTopologyIdentity, + crate::OdinRuntimeTopologyCorrelationPurpose, + >( + admitted_odin_signer_public_key, + &unsigned, + &crate::ServiceIdentitySignature { + identity_id: record.signer_identity_id.clone(), + signature: record.signature.clone(), + }, + )?; + Ok(AuthenticatedOdinRuntimeTopologyCorrelation { + record, + canonical_bytes: canonical_correlation.to_vec(), + }) +} + +/// Odin's deterministic interpretation of an authenticated provider claim. +/// Authentication of the stable provider and current activation establishes +/// Present. Correlation disagreements remain explicit and deny Ready; they do +/// not erase the observed session that produced them. +#[derive(Clone, Debug)] +pub struct RuntimePresenceCorrelation { + presence: VerifiedRuntimePresence, + disagreements: Vec, +} + +impl RuntimePresenceCorrelation { + pub fn claim(&self) -> &AuthenticatedRuntimePresenceClaim { + &self.presence.claim + } + + pub fn disagreements(&self) -> &[OdinTopologyDisagreement] { + &self.disagreements + } + + pub fn into_present(self) -> VerifiedRuntimePresence { + self.presence + } + + pub fn into_undisputed_present(self) -> Result { + if !self.disagreements.is_empty() { + bail!("runtime presence disagrees with current Expected projection"); + } + Ok(self.presence) + } +} + +/// A dual-authenticated claim that matches current authority. This is the only +/// value that represents semantic Present. Replay admission deliberately does +/// not live here: Odin's durable store must atomically check and advance every +/// authenticated provider sequence, including disagreement-bearing claims, +/// before it signs a topology correlation. +#[derive(Clone, Debug)] +pub struct VerifiedRuntimePresence { + claim: AuthenticatedRuntimePresenceClaim, +} + +impl VerifiedRuntimePresence { + pub fn record(&self) -> &GameCultRuntimePresenceHealthRecord { + self.claim.record() + } + + pub fn canonical_bytes(&self) -> &[u8] { + self.claim.canonical_bytes() + } + + pub fn signed_presence_sha256(&self) -> &str { + self.claim.signed_presence_sha256() + } + + pub fn accepted_at_unix_millis(&self) -> u64 { + self.claim.received_at_unix_millis() + } +} + +pub fn authenticate_runtime_presence_claim( + canonical_presence: &[u8], + authority: &VerifiedRuntimeAuthority, + context: RuntimePresenceAuthenticationContext, +) -> Result { + ensure!( + context.trusted_received_at_unix_millis > 0 && context.maximum_age_millis > 0, + "runtime presence trusted time and maximum age must be positive" + ); + if messagepack_array_len(canonical_presence) != Some(24) { + bail!("runtime presence payload is not the 24-field positional contract"); + } + let presence: GameCultRuntimePresenceHealthRecord = + rmp_serde::from_slice(canonical_presence).context("decoding runtime presence health")?; + if rmp_serde::to_vec(&presence)? != canonical_presence { + bail!("runtime presence payload is not canonical positional MessagePack"); + } + presence.validate()?; + let latest_trusted_timestamp = context + .trusted_received_at_unix_millis + .saturating_add(context.maximum_future_skew_millis); + if presence.observed_at_unix_millis > latest_trusted_timestamp + || authority.activation.issued_at_unix_millis > latest_trusted_timestamp + || context + .trusted_received_at_unix_millis + .saturating_sub(presence.observed_at_unix_millis) + > context.maximum_age_millis + { + bail!("runtime presence is outside the trusted observation window"); + } + if presence.signer_identity_id != authority.expected.expected_signer_identity_id { + bail!("runtime presence signer is not Idunn's Expected signer selection"); + } + + let proof_payload = presence.canonical_proof_payload()?; + crate::verify_service_identity_signature_with_public_key::< + crate::GameCultProviderHealthIdentity, + crate::GameCultRuntimePresenceHealthPurpose, + >( + &authority.provider_signer_public_key, + &proof_payload, + &crate::ServiceIdentitySignature { + identity_id: presence.signer_identity_id.clone(), + signature: presence.signature.clone(), + }, + )?; + let activation_public_key: [u8; 32] = authority + .activation + .activation_signer_public_key + .as_slice() + .try_into() + .map_err(|_| anyhow::anyhow!("runtime activation public key has invalid length"))?; + let activation_signature = Signature::from_slice(&presence.activation_signature) + .map_err(|_| anyhow::anyhow!("runtime activation signature has invalid length"))?; + VerifyingKey::from_bytes(&activation_public_key)? + .verify( + &runtime_activation_signing_message(&proof_payload), + &activation_signature, + ) + .map_err(|_| anyhow::anyhow!("runtime activation proof verification failed"))?; + + Ok(AuthenticatedRuntimePresenceClaim { + record: presence, + canonical_bytes: canonical_presence.to_vec(), + signed_presence_sha256: prefixed_sha256(canonical_presence), + received_at_unix_millis: context.trusted_received_at_unix_millis, + }) +} + +pub fn correlate_runtime_presence_claim( + claim: AuthenticatedRuntimePresenceClaim, + authority: &VerifiedRuntimeAuthority, +) -> Result { + let expected = authority.expected(); + let activation = authority.activation(); + let presence = claim.record(); + let expected_endpoint = expected + .route + .as_ref() + .map(|route| route.candidate_endpoint.clone()); + let mut disagreements = Vec::new(); + push_disagreement( + &mut disagreements, + "activation-signer-identity", + Some(&activation.activation_signer_identity_id), + Some(&presence.activation_signer_identity_id), + ); + push_disagreement( + &mut disagreements, + "activation-witness", + Some(authority.activation_sha256()), + Some(&presence.activation_witness_sha256), + ); + push_disagreement( + &mut disagreements, + "bound-endpoint", + expected_endpoint.as_deref(), + presence.bound_endpoint.as_deref(), + ); + push_disagreement( + &mut disagreements, + "expected-projection", + Some(authority.expected_sha256()), + Some(&presence.expected_projection_sha256), + ); + push_disagreement( + &mut disagreements, + "health-contract", + Some(&expected.health_contract), + Some(&presence.health_contract), + ); + push_disagreement( + &mut disagreements, + "incarnation-id", + Some(&expected.incarnation_id), + Some(&presence.incarnation_id), + ); + push_disagreement( + &mut disagreements, + "plan-id", + Some(&expected.plan_id), + Some(&presence.plan_id), + ); + push_disagreement( + &mut disagreements, + "runtime-id", + Some(&expected.runtime_id), + Some(&presence.runtime_id), + ); + push_disagreement( + &mut disagreements, + "runtime-instance-id", + Some(&activation.runtime_instance_id), + Some(&presence.runtime_instance_id), + ); + push_disagreement( + &mut disagreements, + "sealed-release-id", + Some(&expected.sealed_release_id), + Some(&presence.sealed_release_id), + ); + push_disagreement( + &mut disagreements, + "signer-identity", + Some(&expected.expected_signer_identity_id), + Some(&presence.signer_identity_id), + ); + push_disagreement( + &mut disagreements, + "state-contract", + expected.state_contract_sha256.as_deref(), + presence.state_contract_sha256.as_deref(), + ); + push_disagreement( + &mut disagreements, + "state-schema-generation", + expected.state_schema_generation.as_deref(), + presence.state_schema_generation.as_deref(), + ); + push_disagreement( + &mut disagreements, + "target", + Some(&expected.target), + Some(&presence.target), + ); + if activation.issued_at_unix_millis > presence.observed_at_unix_millis { + disagreements.push(OdinTopologyDisagreement { + code: "activation-issued-after-presence".into(), + expected: Some(format!("at-most:{}", presence.observed_at_unix_millis)), + observed: Some(activation.issued_at_unix_millis.to_string()), + }); + } + correlate_capabilities( + &mut disagreements, + &expected.capabilities, + &presence.capabilities, + ); + disagreements.sort_by(|left, right| left.code.cmp(&right.code)); + validate_topology_disagreements(&disagreements)?; + Ok(RuntimePresenceCorrelation { + presence: VerifiedRuntimePresence { claim }, + disagreements, + }) +} + +fn push_disagreement( + disagreements: &mut Vec, + code: &str, + expected: Option<&str>, + observed: Option<&str>, +) { + if expected != observed { + disagreements.push(OdinTopologyDisagreement { + code: code.into(), + expected: expected.map(str::to_string), + observed: observed.map(str::to_string), + }); + } +} + +fn correlate_capabilities( + disagreements: &mut Vec, + expected: &[IdunnExpectedCapability], + observed: &[GameCultRuntimeCapability], +) { + for (index, required) in expected.iter().enumerate() { + let actual = observed + .iter() + .find(|actual| actual.identity() == required.identity()); + let requirement = format!( + "{}/{}/{} capacity>={}", + required.capability, required.schema, required.compatibility, required.minimum_capacity + ); + match actual { + None => disagreements.push(OdinTopologyDisagreement { + code: format!("expected-capability-{index:03}-missing"), + expected: Some(requirement), + observed: None, + }), + Some(actual) if actual.capacity < required.minimum_capacity => { + disagreements.push(OdinTopologyDisagreement { + code: format!("expected-capability-{index:03}-capacity"), + expected: Some(requirement), + observed: Some(format!("capacity={}", actual.capacity)), + }); + } + Some(_) => {} + } + } +} + +fn runtime_activation_signing_message(payload: &[u8]) -> Vec { + let purpose = GAMECULT_RUNTIME_ACTIVATION_PROOF_SIGNING_PURPOSE.as_bytes(); + let mut out = Vec::with_capacity( + IDUNN_RUNTIME_ACTIVATION_SIGNATURE_DOMAIN.len() + purpose.len() + payload.len() + 16, + ); + out.extend_from_slice(IDUNN_RUNTIME_ACTIVATION_SIGNATURE_DOMAIN); + out.extend_from_slice(&(purpose.len() as u64).to_be_bytes()); + out.extend_from_slice(purpose); + out.extend_from_slice(&(payload.len() as u64).to_be_bytes()); + out.extend_from_slice(payload); + out +} + +/// Idunn-issued launch identity. Idunn's stable service identity signs the +/// complete activation, including the ephemeral public key, before the private +/// credential is passed to one workload alongside Expected. Odin still treats +/// it as current only after Idunn publishes an observed native process. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.runtime_activation", + schema = "idunn.runtime_activation.v2" +)] +pub struct IdunnRuntimeActivationRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub expected_projection_sha256: String, + #[cultcache(key = 2)] + pub runtime_id: String, + #[cultcache(key = 3)] + pub runtime_instance_id: String, + #[cultcache(key = 4)] + pub activation_signer_identity_id: String, + #[cultcache(key = 5, bytes)] + pub activation_signer_public_key: Vec, + #[cultcache(key = 6)] + pub issued_at_unix_millis: u64, + #[cultcache(key = 7)] + pub idunn_signer_identity_id: String, + #[cultcache(key = 8)] + pub signature_algorithm: String, + #[cultcache(key = 9, bytes)] + pub signature: Vec, +} + +impl IdunnRuntimeActivationRecord { + fn issue_with_signer( + expected: &IdunnExpectedIncarnationRecord, + runtime_instance_id: String, + activation_signer: &IdunnRuntimeActivationSigner, + issued_at_unix_millis: u64, + idunn_signer: &crate::ServiceIdentitySigner, + ) -> Result { + expected.validate()?; + let mut activation = Self { + schema_version: IDUNN_RUNTIME_ACTIVATION_SCHEMA.into(), + expected_projection_sha256: expected.canonical_sha256()?, + runtime_id: expected.runtime_id.clone(), + runtime_instance_id, + activation_signer_identity_id: activation_signer.identity_id(), + activation_signer_public_key: activation_signer.public_key(), + issued_at_unix_millis, + idunn_signer_identity_id: idunn_signer.entry().identity_id.clone(), + signature_algorithm: "ed25519".into(), + signature: Vec::new(), + }; + let proof = idunn_signer.sign::( + &activation.unsigned_signature_payload()?, + ); + activation.signature = proof.signature; + activation.validate()?; + Ok(activation) + } + + pub fn decode_canonical(payload: &[u8]) -> Result { + if messagepack_array_len(payload) != Some(10) { + bail!("runtime activation is not the 10-field positional contract"); + } + let activation: Self = + rmp_serde::from_slice(payload).context("decoding runtime activation")?; + if rmp_serde::to_vec(&activation)? != payload { + bail!("runtime activation is not canonical positional MessagePack"); + } + activation.validate()?; + Ok(activation) + } + + pub fn canonical_bytes(&self) -> Result> { + self.validate()?; + Ok(rmp_serde::to_vec(self)?) + } + + pub fn canonical_sha256(&self) -> Result { + Ok(prefixed_sha256(&self.canonical_bytes()?)) + } + + pub fn unsigned_signature_payload(&self) -> Result> { + self.validate_shape(true)?; + let mut unsigned = self.clone(); + unsigned.signature.clear(); + Ok(rmp_serde::to_vec(&unsigned)?) + } + + pub fn verify_for_expected( + &self, + expected: &IdunnExpectedIncarnationRecord, + idunn_anchor: &crate::ServiceIdentityTrustAnchor, + ) -> Result<()> { + self.validate()?; + expected.validate()?; + if self.expected_projection_sha256 != expected.canonical_sha256()? + || self.runtime_id != expected.runtime_id + { + bail!("runtime activation does not bind the current Expected incarnation"); + } + crate::verify_service_identity_signature::< + crate::IdunnServiceIdentity, + crate::IdunnRuntimeActivationPurpose, + >( + idunn_anchor, + &self.unsigned_signature_payload()?, + &crate::ServiceIdentitySignature { + identity_id: self.idunn_signer_identity_id.clone(), + signature: self.signature.clone(), + }, + ) + .context("verifying Idunn runtime activation signature") + } + + pub fn validate(&self) -> Result<()> { + self.validate_shape(false) + } + + fn validate_shape(&self, allow_unsigned: bool) -> Result<()> { + if self.schema_version != IDUNN_RUNTIME_ACTIVATION_SCHEMA { + bail!("runtime activation schema is unsupported"); + } + validate_required_sha256( + &self.expected_projection_sha256, + "activation expected projection sha256", + )?; + validate_authority_identifier(&self.runtime_id, "activation runtime id")?; + validate_required_sha256(&self.runtime_instance_id, "runtime instance id")?; + validate_authority_identifier( + &self.activation_signer_identity_id, + "activation signer identity id", + )?; + if self.activation_signer_identity_id + != derive_idunn_runtime_activation_identity_id(&self.activation_signer_public_key)? + { + bail!("runtime activation signer identity does not match its public key"); + } + validate_authority_identifier( + &self.idunn_signer_identity_id, + "Idunn activation signer identity id", + )?; + let signature_is_valid = + self.signature.len() == 64 || (allow_unsigned && self.signature.is_empty()); + if self.issued_at_unix_millis == 0 + || self.signature_algorithm != "ed25519" + || !signature_is_valid + { + bail!("runtime activation issue time or signature is invalid"); + } + Ok(()) + } +} + +/// Unsigned Idunn-owned local file that grants one exact process incarnation +/// state-write authority. It is authoritative only when read through the +/// root-owned admitted path and lock protocol; its bytes carry no route or +/// traffic authority. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.process_write_lease", + schema = "idunn.process_write_lease.v1" +)] +pub struct IdunnProcessWriteLeaseRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub target: String, + #[cultcache(key = 2)] + pub expected_projection_sha256: String, + #[cultcache(key = 3)] + pub plan_id: String, + #[cultcache(key = 4)] + pub incarnation_id: String, + #[cultcache(key = 5)] + pub sealed_release_id: String, + #[cultcache(key = 6)] + pub activation_witness_sha256: String, + #[cultcache(key = 7)] + pub state_schema_generation: String, + #[cultcache(key = 8)] + pub state_contract_sha256: String, + #[cultcache(key = 9)] + pub runtime_id: String, + #[cultcache(key = 10)] + pub runtime_instance_id: String, + #[cultcache(key = 11)] + pub warming_presence_sha256: String, + #[cultcache(key = 12)] + pub lease_epoch: u64, + #[cultcache(key = 13)] + pub issued_at_unix_millis: u64, +} + +impl IdunnProcessWriteLeaseRecord { + pub fn decode_canonical(payload: &[u8]) -> Result { + if messagepack_array_len(payload) != Some(14) { + bail!("process write lease is not the 14-field positional contract"); + } + let lease: Self = rmp_serde::from_slice(payload).context("decoding process write lease")?; + if rmp_serde::to_vec(&lease)? != payload { + bail!("process write lease is not canonical positional MessagePack"); + } + lease.validate()?; + Ok(lease) + } + + pub fn canonical_bytes(&self) -> Result> { + self.validate()?; + Ok(rmp_serde::to_vec(self)?) + } + + pub fn canonical_sha256(&self) -> Result { + Ok(prefixed_sha256(&self.canonical_bytes()?)) + } + + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_PROCESS_WRITE_LEASE_SCHEMA { + bail!("process write lease schema is unsupported"); + } + validate_authority_identifier(&self.target, "target")?; + validate_required_sha256( + &self.expected_projection_sha256, + "lease expected projection sha256", + )?; + validate_required_sha256(&self.plan_id, "plan id")?; + validate_authority_identifier(&self.incarnation_id, "incarnation id")?; + validate_required_sha256(&self.sealed_release_id, "sealed release id")?; + validate_required_sha256(&self.activation_witness_sha256, "activation witness sha256")?; + validate_authority_identifier(&self.state_schema_generation, "state schema generation")?; + validate_required_sha256(&self.state_contract_sha256, "state contract sha256")?; + validate_authority_identifier(&self.runtime_id, "runtime id")?; + validate_required_sha256(&self.runtime_instance_id, "runtime instance id")?; + validate_required_sha256(&self.warming_presence_sha256, "warming presence sha256")?; + if self.lease_epoch == 0 || self.issued_at_unix_millis == 0 { + bail!("process write lease epoch or issue time is invalid"); + } + Ok(()) + } +} + +fn messagepack_array_len(payload: &[u8]) -> Option { + match *payload.first()? { + marker @ 0x90..=0x9f => Some(usize::from(marker & 0x0f)), + 0xdc => Some(usize::from(u16::from_be_bytes([ + *payload.get(1)?, + *payload.get(2)?, + ]))), + 0xdd => usize::try_from(u32::from_be_bytes([ + *payload.get(1)?, + *payload.get(2)?, + *payload.get(3)?, + *payload.get(4)?, + ])) + .ok(), + _ => None, + } +} + +fn validate_authority_identifier(value: &str, label: &str) -> Result<()> { + if value.is_empty() + || value.len() > 256 + || value.trim() != value + || value.chars().any(char::is_control) + { + bail!("{label} is empty, oversized, padded, or contains control characters"); + } + Ok(()) +} + +fn validate_optional_identifier(value: &Option, label: &str) -> Result<()> { + if let Some(value) = value { + validate_authority_identifier(value, label)?; + } + Ok(()) +} + +fn validate_source_repository(value: &str) -> Result<()> { + validate_authority_identifier(value, "source repository")?; + let segments: Vec<&str> = value.split('/').collect(); + if segments.len() < 3 + || !segments[0].contains('.') + || segments.iter().any(|segment| { + segment.is_empty() + || matches!(*segment, "." | "..") + || !segment + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.')) + }) + { + bail!("source repository is not a canonical host/owner/repository identity"); + } + Ok(()) +} + +fn validate_endpoint(value: &str, label: &str) -> Result<()> { + if value.is_empty() + || value.len() > 2048 + || value.trim() != value + || value.chars().any(char::is_control) + { + bail!("{label} is empty, oversized, padded, or contains control characters"); + } + Ok(()) +} + +fn validate_optional_endpoint_labeled(value: &Option, label: &str) -> Result<()> { + if let Some(endpoint) = value { + validate_endpoint(endpoint, label)?; + } + Ok(()) +} + +fn validate_optional_endpoint(value: &Option) -> Result<()> { + validate_optional_endpoint_labeled(value, "bound endpoint") +} + +fn validate_runtime_capabilities(capabilities: &[GameCultRuntimeCapability]) -> Result<()> { + if capabilities.len() > MAX_RUNTIME_CAPABILITIES { + bail!("runtime presence capability count exceeds its contract bound"); + } + let mut previous: Option<(&str, &str, &str)> = None; + for capability in capabilities { + validate_authority_identifier(&capability.capability, "capability")?; + validate_authority_identifier(&capability.schema, "capability schema")?; + validate_authority_identifier(&capability.compatibility, "capability compatibility")?; + if capability.capacity == 0 { + bail!("runtime capability capacity is zero"); + } + let identity = ( + capability.capability.as_str(), + capability.schema.as_str(), + capability.compatibility.as_str(), + ); + if previous.is_some_and(|previous| previous >= identity) { + bail!("runtime capabilities are not strictly sorted and unique"); + } + previous = Some(identity); + } + Ok(()) +} + +fn validate_expected_capabilities(capabilities: &[IdunnExpectedCapability]) -> Result<()> { + if capabilities.len() > MAX_RUNTIME_CAPABILITIES { + bail!("Expected capability count exceeds its contract bound"); + } + let mut previous: Option<(&str, &str, &str)> = None; + for capability in capabilities { + validate_authority_identifier(&capability.capability, "Expected capability")?; + validate_authority_identifier(&capability.schema, "Expected capability schema")?; + validate_authority_identifier( + &capability.compatibility, + "Expected capability compatibility", + )?; + if capability.minimum_capacity == 0 { + bail!("Expected capability minimum capacity is zero"); + } + let identity = capability.identity(); + if previous.is_some_and(|previous| previous >= identity) { + bail!("Expected capabilities are not strictly sorted and unique"); + } + previous = Some(identity); + } + Ok(()) +} + +fn validate_expected_dependencies(dependencies: &[IdunnExpectedDependency]) -> Result<()> { + if dependencies.len() > MAX_RUNTIME_DEPENDENCIES { + bail!("Expected dependency count exceeds its contract bound"); + } + let mut previous: Option<(&str, &str, &str)> = None; + for dependency in dependencies { + dependency.validate()?; + let identity = dependency.identity(); + if previous.is_some_and(|previous| previous >= identity) { + bail!("Expected dependencies are not strictly sorted and unique"); + } + previous = Some(identity); + } + Ok(()) +} + +fn validate_dependency_evidence(dependencies: &[OdinRuntimeDependencyEvidence]) -> Result<()> { + if dependencies.len() > MAX_RUNTIME_DEPENDENCIES { + bail!("dependency evidence count exceeds its contract bound"); + } + let mut previous: Option<(&str, &str, &str)> = None; + for dependency in dependencies { + dependency.validate()?; + let identity = dependency.identity(); + if previous.is_some_and(|previous| previous >= identity) { + bail!("dependency evidence is not strictly sorted and unique"); + } + previous = Some(identity); + } + Ok(()) +} + +fn validate_topology_disagreements(disagreements: &[OdinTopologyDisagreement]) -> Result<()> { + if disagreements.len() > MAX_TOPOLOGY_DISAGREEMENTS { + bail!("topology disagreement count exceeds its contract bound"); + } + let mut previous: Option<&str> = None; + for disagreement in disagreements { + disagreement.validate()?; + if previous.is_some_and(|previous| previous >= disagreement.code.as_str()) { + bail!("topology disagreements are not strictly sorted and unique by code"); + } + previous = Some(&disagreement.code); + } + Ok(()) +} + +fn validate_optional_detail(value: &Option, label: &str) -> Result<()> { + if value.as_deref().is_some_and(|value| { + value.is_empty() + || value.len() > 1024 + || value.trim() != value + || value.chars().any(char::is_control) + }) { + bail!("{label} is empty, oversized, padded, or contains control characters"); + } + Ok(()) +} + +fn validate_required_sha256(value: &str, label: &str) -> Result<()> { + if !is_sha256(value) { + bail!("{label} is malformed"); + } + Ok(()) +} + +fn validate_optional_sha256(value: &Option, label: &str) -> Result<()> { + if value.as_deref().is_some_and(|value| !is_sha256(value)) { + bail!("{label} is malformed"); + } + Ok(()) +} + +fn prefixed_sha256(bytes: &[u8]) -> String { + format!("sha256-{:x}", Sha256::digest(bytes)) +} + +fn is_sha256(value: &str) -> bool { + value + .strip_prefix("sha256-") + .is_some_and(|digest| is_lower_hex(digest, 64)) +} + +fn is_lower_hex(value: &str, length: usize) -> bool { + value.len() == length + && value + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA, GameCultProviderHealthIdentity, + GameCultRuntimePresenceHealthPurpose, GameCultServiceTrustAnchorRecord, + IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA, IdunnServiceIdentity, + OdinRuntimeTopologyCorrelationPurpose, OdinTopologyIdentity, ServiceIdentitySignature, + ServiceIdentitySigner, ServiceSignaturePurpose, derive_service_identity_id, + enroll_service_identity_at, verify_service_identity_signature, + }; + + struct WrongOdinTopologyPurpose; + + impl ServiceSignaturePurpose for WrongOdinTopologyPurpose { + const PURPOSE: &'static [u8] = b"odin.not-runtime-topology-correlation.v1"; + } + + fn digest(byte: char) -> String { + format!("sha256-{}", byte.to_string().repeat(64)) + } + + fn runtime_capability(name: &str) -> GameCultRuntimeCapability { + GameCultRuntimeCapability { + capability: name.into(), + schema: format!("{name}.v1"), + compatibility: "v1".into(), + capacity: 1, + } + } + + fn expected_capability(name: &str) -> IdunnExpectedCapability { + IdunnExpectedCapability { + capability: name.into(), + schema: format!("{name}.v1"), + compatibility: "v1".into(), + minimum_capacity: 1, + } + } + + fn runtime_presence() -> GameCultRuntimePresenceHealthRecord { + GameCultRuntimePresenceHealthRecord { + schema_version: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA.into(), + target: "ghostlight".into(), + expected_projection_sha256: digest('9'), + plan_id: digest('e'), + incarnation_id: "ghostlight/2026-09-02/1".into(), + sealed_release_id: digest('f'), + activation_witness_sha256: digest('a'), + state_schema_generation: Some("world-v2".into()), + state_contract_sha256: Some(digest('b')), + runtime_id: "ghostlight-yggdrasil-1".into(), + runtime_instance_id: digest('8'), + bound_endpoint: Some("http://127.0.0.1:14103".into()), + capabilities: vec![ + runtime_capability("conversation"), + runtime_capability("state"), + ], + health_contract: "ghostlight.runtime-health.v1".into(), + state: "active".into(), + detail: "ready".into(), + write_lease_sha256: Some(digest('c')), + signer_identity_id: "provider-signing-key".into(), + publisher_sequence: 1, + observed_at_unix_millis: 100, + signature_algorithm: "ed25519".into(), + signature: vec![6; 64], + activation_signer_identity_id: "activation-signing-key".into(), + activation_signature: vec![7; 64], + } + } + + fn runtime_activation() -> IdunnRuntimeActivationRecord { + let signer = IdunnRuntimeActivationSigner::from_credential_reader(&[8; 32][..]).unwrap(); + IdunnRuntimeActivationRecord { + schema_version: IDUNN_RUNTIME_ACTIVATION_SCHEMA.into(), + expected_projection_sha256: digest('9'), + runtime_id: "ghostlight-yggdrasil-1".into(), + runtime_instance_id: digest('8'), + activation_signer_identity_id: signer.identity_id(), + activation_signer_public_key: signer.public_key(), + issued_at_unix_millis: 99, + idunn_signer_identity_id: "idunn-signing-key".into(), + signature_algorithm: "ed25519".into(), + signature: vec![5; 64], + } + } + + fn process_write_lease() -> IdunnProcessWriteLeaseRecord { + IdunnProcessWriteLeaseRecord { + schema_version: IDUNN_PROCESS_WRITE_LEASE_SCHEMA.into(), + target: "ghostlight".into(), + expected_projection_sha256: digest('9'), + plan_id: digest('e'), + incarnation_id: "ghostlight/2026-09-02/1".into(), + sealed_release_id: digest('f'), + activation_witness_sha256: digest('a'), + state_schema_generation: "world-v2".into(), + state_contract_sha256: digest('b'), + runtime_id: "ghostlight-yggdrasil-1".into(), + runtime_instance_id: digest('8'), + warming_presence_sha256: digest('d'), + lease_epoch: 1, + issued_at_unix_millis: 101, + } + } + + fn expected_dependency( + kind: &str, + capability: &str, + provider_id: Option<&str>, + ) -> IdunnExpectedDependency { + IdunnExpectedDependency { + kind: kind.into(), + capability: capability.into(), + schema: format!("{capability}.v1"), + compatibility: "v1".into(), + minimum_capacity: 1, + startup: if kind == "bootstrap" { + "before-start".into() + } else { + "before-promotion".into() + }, + provider_id: provider_id.map(str::to_string), + provider_authority: provider_id.map(|_| "managed-incarnation".into()), + provider_expected_projection_sha256: provider_id.map(|_| digest('1')), + provider_endpoint: provider_id.map(|_| "tcp://127.0.0.1:14100".into()), + } + } + + fn expected_incarnation() -> IdunnExpectedIncarnationRecord { + IdunnExpectedIncarnationRecord { + schema_version: IDUNN_EXPECTED_INCARNATION_SCHEMA.into(), + target: "ghostlight".into(), + plan_id: digest('2'), + incarnation_id: "ghostlight/2026-09-02/1".into(), + sealed_release_id: digest('3'), + source_repository: "github.com/GameCult/Ghostlight".into(), + source_revision: "4".repeat(40), + recipe_sha256: digest('5'), + runtime_id: "ghostlight-yggdrasil-1".into(), + expected_signer_identity_id: "ghostlight-provider-signing-key".into(), + health_contract: "ghostlight.runtime-health.v1".into(), + artifact_sha256: digest('6'), + state_schema_generation: Some("world-v2".into()), + state_contract_sha256: Some(digest('7')), + write_lease_required: true, + route: Some(IdunnExpectedRoute { + route_id: "ghostlight-public".into(), + transport: "http".into(), + stable_endpoint: "https://ghostlight.gamecult.net".into(), + candidate_endpoint: "http://127.0.0.1:14103".into(), + }), + capabilities: vec![ + expected_capability("conversation"), + expected_capability("state"), + ], + dependencies: vec![ + expected_dependency("shared-infrastructure", "rendezvous", Some("odin-ygg-1")), + expected_dependency("optional", "telemetry", None), + ], + } + } + + fn dependency_evidence(requirement: &IdunnExpectedDependency) -> OdinRuntimeDependencyEvidence { + let selected = requirement.provider_id.is_some(); + OdinRuntimeDependencyEvidence { + kind: requirement.kind.clone(), + capability: requirement.capability.clone(), + schema: requirement.schema.clone(), + compatibility: requirement.compatibility.clone(), + provider_id: requirement.provider_id.clone(), + provider_authority: requirement.provider_authority.clone(), + provider_expected_projection_sha256: requirement + .provider_expected_projection_sha256 + .clone(), + provider_endpoint: requirement.provider_endpoint.clone(), + observed_capacity: selected.then_some(1), + provider_evidence_sha256: selected.then(|| digest('8')), + ready: selected, + } + } + + fn topology_correlation( + expected: &IdunnExpectedIncarnationRecord, + ) -> OdinRuntimeTopologyCorrelationRecord { + OdinRuntimeTopologyCorrelationRecord { + schema_version: ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA.into(), + target: expected.target.clone(), + expected_projection_sha256: expected.canonical_sha256().unwrap(), + expected: true, + current_activation_sha256: Some(digest('9')), + signed_presence_sha256: Some(digest('a')), + observed_presence_state: Some("active".into()), + observed_presence_publisher_sequence: Some(1), + observed_write_lease_sha256: Some(digest('c')), + observed_capabilities: expected + .capabilities + .iter() + .map(|capability| GameCultRuntimeCapability { + capability: capability.capability.clone(), + schema: capability.schema.clone(), + compatibility: capability.compatibility.clone(), + capacity: capability.minimum_capacity, + }) + .collect(), + runtime_id: expected.runtime_id.clone(), + runtime_instance_id: Some(digest('b')), + present: true, + ready: true, + dependencies: expected + .dependencies + .iter() + .map(dependency_evidence) + .collect(), + disagreements: vec![], + signer_identity_id: "odin-topology-signing-key".into(), + publisher_sequence: 1, + observed_at_unix_millis: 102, + signature_algorithm: "ed25519".into(), + signature: vec![7; 64], + } + } + + fn validate_against_current_lease( + receipt: &OdinRuntimeTopologyCorrelationRecord, + expected: &IdunnExpectedIncarnationRecord, + ) -> Result<()> { + let current_lease = digest('c'); + receipt.validate_against_expected(expected, Some(¤t_lease)) + } + + fn topology_anchor() -> GameCultServiceTrustAnchorRecord { + let public_key = vec![9; 32]; + GameCultServiceTrustAnchorRecord { + schema_version: GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA.into(), + trust_anchor_id: "root/odin/runtime-topology".into(), + service_id: "odin".into(), + runtime_id: "odin-yggdrasil-1".into(), + signer_identity_id: derive_service_identity_id::(&public_key) + .unwrap(), + signer_public_key: public_key, + signature_algorithm: "ed25519".into(), + signing_purpose: ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE.into(), + signed_schema: ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA.into(), + binding_authority: "root".into(), + bound_at_unix_millis: 100, + expires_at_unix_millis: Some(200), + private_state_exposed: false, + } + } + + fn runtime_presence_anchor() -> GameCultServiceTrustAnchorRecord { + let public_key = vec![7; 32]; + GameCultServiceTrustAnchorRecord { + schema_version: GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA.into(), + trust_anchor_id: "root/ghostlight/runtime-presence".into(), + service_id: "ghostlight".into(), + runtime_id: "ghostlight-yggdrasil-1".into(), + signer_identity_id: derive_service_identity_id::( + &public_key, + ) + .unwrap(), + signer_public_key: public_key, + signature_algorithm: "ed25519".into(), + signing_purpose: GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE.into(), + signed_schema: GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA.into(), + binding_authority: "root".into(), + bound_at_unix_millis: 100, + expires_at_unix_millis: Some(200), + private_state_exposed: false, + } + } + + struct DualProofFixture { + stable_signer: ServiceIdentitySigner, + activation_signer: IdunnRuntimeActivationSigner, + odin_signer: ServiceIdentitySigner, + idunn_anchor: crate::ServiceIdentityTrustAnchor, + stable_public_key: Vec, + odin_anchor: GameCultServiceTrustAnchorRecord, + expected: IdunnExpectedIncarnationRecord, + activation: IdunnRuntimeActivationRecord, + presence: GameCultRuntimePresenceHealthRecord, + } + + fn dual_proof_fixture(identity_path: &std::path::Path) -> Result { + let stable_signer = + enroll_service_identity_at::(identity_path)?; + let stable_public = stable_signer.trust_anchor()?; + let idunn_signer = enroll_service_identity_at::( + &identity_path.with_extension("idunn.cc"), + )?; + let idunn_anchor = idunn_signer.trust_anchor()?; + let odin_signer = enroll_service_identity_at::( + &identity_path.with_extension("odin.cc"), + )?; + let odin_public = odin_signer.trust_anchor()?; + let mut expected = expected_incarnation(); + expected.expected_signer_identity_id = stable_public.identity_id.clone(); + expected.capabilities[0].minimum_capacity = 2; + let launch = + IdunnRuntimeActivationLaunch::issue(&expected, digest('8'), 99, &idunn_signer)?; + let activation = launch.activation().clone(); + let mut activation_credential = Vec::new(); + assert_eq!( + launch.write_credential(&mut activation_credential)?, + activation + ); + let activation_signer = + IdunnRuntimeActivationSigner::from_credential_reader(&activation_credential[..])?; + + let stable_public_key = stable_public.public_key.clone(); + let odin_anchor = GameCultServiceTrustAnchorRecord { + schema_version: GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA.into(), + trust_anchor_id: "root/odin/runtime-topology".into(), + service_id: "odin".into(), + runtime_id: "odin-yggdrasil-1".into(), + signer_identity_id: odin_public.identity_id, + signer_public_key: odin_public.public_key, + signature_algorithm: "ed25519".into(), + signing_purpose: ODIN_RUNTIME_TOPOLOGY_CORRELATION_SIGNING_PURPOSE.into(), + signed_schema: ODIN_RUNTIME_TOPOLOGY_CORRELATION_SCHEMA.into(), + binding_authority: "root".into(), + bound_at_unix_millis: 1, + expires_at_unix_millis: Some(1_000), + private_state_exposed: false, + }; + + let mut presence = runtime_presence(); + presence.target = expected.target.clone(); + presence.plan_id = expected.plan_id.clone(); + presence.incarnation_id = expected.incarnation_id.clone(); + presence.sealed_release_id = expected.sealed_release_id.clone(); + presence.state_schema_generation = expected.state_schema_generation.clone(); + presence.state_contract_sha256 = expected.state_contract_sha256.clone(); + presence.bound_endpoint = expected + .route + .as_ref() + .map(|route| route.candidate_endpoint.clone()); + presence.capabilities = expected + .capabilities + .iter() + .map(|capability| GameCultRuntimeCapability { + capability: capability.capability.clone(), + schema: capability.schema.clone(), + compatibility: capability.compatibility.clone(), + capacity: capability.minimum_capacity, + }) + .collect(); + presence.health_contract = expected.health_contract.clone(); + presence.runtime_id = activation.runtime_id.clone(); + presence.runtime_instance_id = activation.runtime_instance_id.clone(); + presence.expected_projection_sha256 = activation.expected_projection_sha256.clone(); + presence.activation_witness_sha256 = activation.canonical_sha256()?; + presence.signer_identity_id = stable_public.identity_id; + presence.activation_signer_identity_id = activation_signer.identity_id(); + presence.observed_at_unix_millis = 110; + presence.signature.clear(); + presence.activation_signature.clear(); + let proof_payload = presence.canonical_proof_payload()?; + presence.signature = stable_signer + .sign::(&proof_payload) + .signature; + presence.activation_signature = activation_signer.sign_presence_proof(&presence)?; + + Ok(DualProofFixture { + stable_signer, + activation_signer, + odin_signer, + idunn_anchor, + stable_public_key, + odin_anchor, + expected, + activation, + presence, + }) + } + + fn resign_presence(fixture: &mut DualProofFixture) -> Result> { + fixture.presence.signature.clear(); + fixture.presence.activation_signature.clear(); + let proof_payload = fixture.presence.canonical_proof_payload()?; + fixture.presence.signature = fixture + .stable_signer + .sign::(&proof_payload) + .signature; + fixture.presence.activation_signature = fixture + .activation_signer + .sign_presence_proof(&fixture.presence)?; + Ok(rmp_serde::to_vec(&fixture.presence)?) + } + + fn fixture_authority(fixture: &DualProofFixture) -> Result { + verify_runtime_authority( + &fixture.expected, + &fixture.activation, + &fixture.idunn_anchor, + &fixture.stable_public_key, + ) + } + + fn authenticate_fixture( + fixture: &DualProofFixture, + ) -> Result { + authenticate_runtime_presence_claim( + &rmp_serde::to_vec(&fixture.presence)?, + &fixture_authority(fixture)?, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + } + + fn correlate_fixture(fixture: &DualProofFixture) -> Result { + correlate_runtime_presence_claim( + authenticate_fixture(fixture)?, + &fixture_authority(fixture)?, + ) + } + + fn verify_fixture(fixture: &DualProofFixture) -> Result { + correlate_fixture(fixture)?.into_undisputed_present() + } + + fn authenticated_topology_receipt( + fixture: &DualProofFixture, + presence: &VerifiedRuntimePresence, + present: bool, + disagreements: Vec, + ) -> Result { + let authority = fixture_authority(fixture)?; + let mut receipt = topology_correlation(&fixture.expected); + receipt.current_activation_sha256 = Some(authority.activation_sha256().into()); + receipt.signed_presence_sha256 = Some(presence.signed_presence_sha256().into()); + receipt.observed_presence_state = Some(presence.record().state.clone()); + receipt.observed_presence_publisher_sequence = Some(presence.record().publisher_sequence); + receipt.observed_write_lease_sha256 = presence.record().write_lease_sha256.clone(); + receipt.observed_capabilities = presence.record().capabilities.clone(); + receipt.runtime_instance_id = Some(fixture.activation.runtime_instance_id.clone()); + receipt.present = present; + receipt.ready = present && presence.record().state == "active"; + receipt.disagreements = disagreements; + receipt.signer_identity_id = fixture.odin_anchor.signer_identity_id.clone(); + receipt.signature.clear(); + receipt.signature = fixture + .odin_signer + .sign::(&receipt.unsigned_signature_payload()?) + .signature; + let encoded = receipt.canonical_bytes()?; + authenticate_odin_runtime_topology_correlation( + &encoded, + &authority, + presence.record().write_lease_sha256.as_deref(), + &fixture.odin_anchor.signer_public_key, + OdinTopologyAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + } + + #[test] + fn runtime_authority_contracts_round_trip_as_canonical_positional_records() -> Result<()> { + let presence = runtime_presence(); + presence.validate()?; + let encoded_presence = rmp_serde::to_vec(&presence)?; + assert_eq!(messagepack_array_len(&encoded_presence), Some(24)); + let decoded_presence: GameCultRuntimePresenceHealthRecord = + rmp_serde::from_slice(&encoded_presence)?; + assert_eq!(decoded_presence, presence); + let unsigned = presence.canonical_proof_payload()?; + let unsigned_presence: GameCultRuntimePresenceHealthRecord = + rmp_serde::from_slice(&unsigned)?; + assert!(unsigned_presence.signature.is_empty()); + assert!(unsigned_presence.activation_signature.is_empty()); + + let activation = runtime_activation(); + let encoded_activation = activation.canonical_bytes()?; + assert_eq!(messagepack_array_len(&encoded_activation), Some(10)); + assert_eq!( + IdunnRuntimeActivationRecord::decode_canonical(&encoded_activation)?, + activation + ); + assert_eq!( + activation.canonical_sha256()?, + prefixed_sha256(&encoded_activation) + ); + + let lease = process_write_lease(); + lease.validate()?; + let encoded_lease = lease.canonical_bytes()?; + assert_eq!(messagepack_array_len(&encoded_lease), Some(14)); + assert_eq!( + IdunnProcessWriteLeaseRecord::decode_canonical(&encoded_lease)?, + lease + ); + assert_eq!(lease.canonical_sha256()?, prefixed_sha256(&encoded_lease)); + Ok(()) + } + + #[test] + fn exact_stable_and_activation_proofs_admit_runtime_presence() -> Result<()> { + let temp = tempfile::tempdir()?; + let fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + assert_eq!(fixture.activation.activation_signer_public_key.len(), 32); + let verified = verify_fixture(&fixture)?; + assert_eq!(verified.record(), &fixture.presence); + Ok(()) + } + + #[test] + fn authenticated_expected_disagreements_remain_typed_present_and_not_ready() -> Result<()> { + type Mutation = fn(&mut GameCultRuntimePresenceHealthRecord); + let cases: &[(&str, &str, Mutation)] = &[ + ("target", "target", |value| value.target = "epiphany".into()), + ("projection", "expected-projection", |value| { + value.expected_projection_sha256 = digest('0') + }), + ("plan", "plan-id", |value| value.plan_id = digest('0')), + ("incarnation", "incarnation-id", |value| { + value.incarnation_id = "ghostlight/other/1".into() + }), + ("release", "sealed-release-id", |value| { + value.sealed_release_id = digest('0') + }), + ("runtime", "runtime-id", |value| { + value.runtime_id = "ghostlight-other-runtime".into() + }), + ("process", "runtime-instance-id", |value| { + value.runtime_instance_id = digest('0') + }), + ("endpoint", "bound-endpoint", |value| { + value.bound_endpoint = Some("http://127.0.0.1:14104".into()) + }), + ("health", "health-contract", |value| { + value.health_contract = "ghostlight.other-health.v1".into() + }), + ("state-generation", "state-schema-generation", |value| { + value.state_schema_generation = Some("world-v3".into()) + }), + ("state-contract", "state-contract", |value| { + value.state_contract_sha256 = Some(digest('0')) + }), + ("activation", "activation-witness", |value| { + value.activation_witness_sha256 = digest('0') + }), + ]; + + let temp = tempfile::tempdir()?; + for (name, expected_code, mutate) in cases { + let mut fixture = dual_proof_fixture(&temp.path().join(format!("{name}.cc")))?; + mutate(&mut fixture.presence); + resign_presence(&mut fixture)?; + let correlation = correlate_fixture(&fixture)?; + assert!( + correlation + .disagreements() + .iter() + .any(|disagreement| disagreement.code == *expected_code), + "missing {expected_code} disagreement for {name}" + ); + assert_eq!( + correlation.claim().record().runtime_instance_id, + fixture.presence.runtime_instance_id + ); + assert!(correlation.into_undisputed_present().is_err()); + } + Ok(()) + } + + #[test] + fn provider_owns_observed_capacity_while_expected_owns_only_the_minimum() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + fixture.presence.capabilities[0].capacity = 50; + fixture + .presence + .capabilities + .push(runtime_capability("telemetry")); + resign_presence(&mut fixture)?; + verify_fixture(&fixture)?; + + fixture.presence.capabilities[0].capacity = 1; + fixture + .presence + .capabilities + .retain(|capability| capability.capability != "state"); + resign_presence(&mut fixture)?; + let correlation = correlate_fixture(&fixture)?; + let codes = correlation + .disagreements() + .iter() + .map(|disagreement| disagreement.code.as_str()) + .collect::>(); + assert!(codes.contains(&"expected-capability-000-capacity")); + assert!(codes.contains(&"expected-capability-001-missing")); + assert!(!codes.iter().any(|code| code.contains("unexpected"))); + Ok(()) + } + + #[test] + fn stable_proof_and_provider_key_substitution_fail_closed() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + fixture.presence.detail = "tampered after signing".into(); + assert!(authenticate_fixture(&fixture).is_err()); + + let other_idunn = enroll_service_identity_at::( + &temp.path().join("other-idunn.cc"), + )?; + assert!( + verify_runtime_authority( + &fixture.expected, + &fixture.activation, + &other_idunn.trust_anchor()?, + &fixture.stable_public_key, + ) + .is_err() + ); + + fixture.stable_public_key = vec![9; 32]; + assert!(fixture_authority(&fixture).is_err()); + Ok(()) + } + + #[test] + fn incumbent_stable_key_without_activation_key_cannot_claim_presence() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + fixture.presence.activation_signature = vec![0; 64]; + let proof_payload = fixture.presence.canonical_proof_payload()?; + fixture.presence.signature = fixture + .stable_signer + .sign::(&proof_payload) + .signature; + + let error = verify_fixture(&fixture).unwrap_err(); + assert!(error.to_string().contains("activation proof verification")); + Ok(()) + } + + #[test] + fn activation_cannot_substitute_another_expected_runtime_id() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + fixture.activation.runtime_id = "ghostlight-yggdrasil-2".into(); + fixture.presence.runtime_id = fixture.activation.runtime_id.clone(); + fixture.presence.activation_witness_sha256 = fixture.activation.canonical_sha256()?; + + // The stable service key can re-sign the altered canonical statement, + // but the activation proof still came from the other runtime id. + let proof_payload = fixture.presence.canonical_proof_payload()?; + fixture.presence.signature = fixture + .stable_signer + .sign::(&proof_payload) + .signature; + let error = fixture_authority(&fixture).unwrap_err(); + assert!(error.to_string().contains("current Expected")); + Ok(()) + } + + #[test] + fn idunn_signature_prevents_provider_minted_activation() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + fixture.activation.issued_at_unix_millis += 1; + fixture.presence.activation_witness_sha256 = fixture.activation.canonical_sha256()?; + resign_presence(&mut fixture)?; + let error = fixture_authority(&fixture).unwrap_err(); + assert!( + error + .to_string() + .contains("Idunn runtime activation signature") + ); + Ok(()) + } + + #[test] + fn authenticated_odin_receipt_is_fresh_evidence_not_replay_authority() -> Result<()> { + let temp = tempfile::tempdir()?; + let fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + let authority = fixture_authority(&fixture)?; + let presence = verify_fixture(&fixture)?; + let accepted = authenticated_topology_receipt(&fixture, &presence, true, Vec::new())?; + assert_eq!( + accepted.record().observed_presence_publisher_sequence, + Some(fixture.presence.publisher_sequence) + ); + assert_eq!( + accepted.record().signed_presence_sha256.as_deref(), + Some(presence.signed_presence_sha256()) + ); + + let mut tampered = accepted.record().clone(); + tampered.signature = vec![0; 64]; + assert!( + authenticate_odin_runtime_topology_correlation( + &tampered.canonical_bytes()?, + &authority, + presence.record().write_lease_sha256.as_deref(), + &fixture.odin_anchor.signer_public_key, + OdinTopologyAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + assert!( + authenticate_odin_runtime_topology_correlation( + accepted.canonical_bytes(), + &authority, + presence.record().write_lease_sha256.as_deref(), + &fixture.odin_anchor.signer_public_key, + OdinTopologyAuthenticationContext { + trusted_received_at_unix_millis: 200, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + Ok(()) + } + + #[test] + fn separate_launches_receive_distinct_activation_identities() -> Result<()> { + let temp = tempfile::tempdir()?; + let idunn = + enroll_service_identity_at::(&temp.path().join("idunn.cc"))?; + let expected = expected_incarnation(); + let first = IdunnRuntimeActivationLaunch::issue(&expected, digest('8'), 99, &idunn)?; + let second = IdunnRuntimeActivationLaunch::issue(&expected, digest('9'), 100, &idunn)?; + assert_ne!( + first.activation().activation_signer_identity_id, + second.activation().activation_signer_identity_id + ); + Ok(()) + } + + #[test] + fn trusted_time_rejects_stale_and_future_presence() -> Result<()> { + let temp = tempfile::tempdir()?; + let mut fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + let authority = fixture_authority(&fixture)?; + let canonical = rmp_serde::to_vec(&fixture.presence)?; + assert!( + authenticate_runtime_presence_claim( + &canonical, + &authority, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 200, + maximum_age_millis: 10, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + + fixture.presence.observed_at_unix_millis = 126; + resign_presence(&mut fixture)?; + assert!( + authenticate_runtime_presence_claim( + &rmp_serde::to_vec(&fixture.presence)?, + &authority, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + + Ok(()) + } + + #[test] + fn runtime_authority_decoders_reject_malformed_and_noncanonical_bytes() -> Result<()> { + let temp = tempfile::tempdir()?; + let fixture = dual_proof_fixture(&temp.path().join("runtime-presence.cc"))?; + assert!( + authenticate_runtime_presence_claim( + &[0x91, 0xc0], + &fixture_authority(&fixture)?, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + assert!(IdunnRuntimeActivationRecord::decode_canonical(&[0x91, 0xc0]).is_err()); + assert!(IdunnProcessWriteLeaseRecord::decode_canonical(&[0x91, 0xc0]).is_err()); + + let mut legacy_presence = fixture.presence.clone(); + legacy_presence.schema_version = "gamecult.runtime_presence_health.v1".into(); + assert!( + authenticate_runtime_presence_claim( + &rmp_serde::to_vec(&legacy_presence)?, + &fixture_authority(&fixture)?, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + let mut legacy_activation = fixture.activation.clone(); + legacy_activation.schema_version = "idunn.runtime_activation.v1".into(); + assert!( + IdunnRuntimeActivationRecord::decode_canonical(&rmp_serde::to_vec(&legacy_activation)?) + .is_err() + ); + let mut legacy_expected = fixture.expected.clone(); + legacy_expected.schema_version = "idunn.expected_incarnation.v1".into(); + assert!( + IdunnExpectedIncarnationRecord::decode_canonical(&rmp_serde::to_vec(&legacy_expected)?) + .is_err() + ); + let mut legacy_topology = topology_correlation(&fixture.expected); + legacy_topology.schema_version = "odin.runtime_topology_correlation.v1".into(); + assert!( + OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload( + &rmp_serde::to_vec(&legacy_topology)?, + ) + .is_err() + ); + + let presence = rmp_serde::to_vec(&runtime_presence())?; + assert_eq!(&presence[..3], &[0xdc, 0, 24]); + let mut noncanonical_presence = vec![0xdd, 0, 0, 0, 24]; + noncanonical_presence.extend_from_slice(&presence[3..]); + assert!( + authenticate_runtime_presence_claim( + &noncanonical_presence, + &fixture_authority(&fixture)?, + RuntimePresenceAuthenticationContext { + trusted_received_at_unix_millis: 120, + maximum_age_millis: 30, + maximum_future_skew_millis: 5, + }, + ) + .is_err() + ); + + let activation = runtime_activation().canonical_bytes()?; + assert_eq!(activation[0], 0x9a); + let mut noncanonical_activation = vec![0xdc, 0, 10]; + noncanonical_activation.extend_from_slice(&activation[1..]); + assert!(IdunnRuntimeActivationRecord::decode_canonical(&noncanonical_activation).is_err()); + + let lease = process_write_lease().canonical_bytes()?; + assert_eq!(lease[0], 0x9e); + let mut noncanonical_lease = vec![0xdc, 0, 14]; + noncanonical_lease.extend_from_slice(&lease[1..]); + assert!(IdunnProcessWriteLeaseRecord::decode_canonical(&noncanonical_lease).is_err()); + Ok(()) + } + + #[test] + fn runtime_presence_refuses_capability_and_warming_lease_ambiguity() { + let mut value = runtime_presence(); + value.capabilities.reverse(); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + let mut duplicate = runtime_capability("state"); + duplicate.capacity = 2; + value.capabilities.push(duplicate); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.capabilities[0].capacity = 0; + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.state = "warming".into(); + assert!(value.validate().is_err()); + value.write_lease_sha256 = None; + assert!(value.validate().is_ok()); + + let mut value = runtime_presence(); + value.state_schema_generation = None; + assert!(value.validate().is_err()); + value.state_contract_sha256 = None; + assert!(value.validate().is_err()); + value.write_lease_sha256 = None; + assert!(value.validate().is_ok()); + } + + #[test] + fn runtime_authority_refuses_malformed_digest_process_and_epoch_bindings() { + let mut value = runtime_presence(); + value.activation_witness_sha256 = digest('A'); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.plan_id = "plan-1".into(); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.sealed_release_id = "release-1".into(); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.runtime_instance_id = "systemd-invocation-1".into(); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.expected_projection_sha256 = "expected-1".into(); + assert!(value.validate().is_err()); + + let mut value = runtime_presence(); + value.write_lease_sha256 = Some("lease-1".into()); + assert!(value.validate().is_err()); + + let mut value = process_write_lease(); + value.warming_presence_sha256 = digest('D'); + assert!(value.validate().is_err()); + + let mut value = process_write_lease(); + value.plan_id = "plan-1".into(); + assert!(value.validate().is_err()); + + let mut value = process_write_lease(); + value.runtime_instance_id = "pid-4242".into(); + assert!(value.validate().is_err()); + + let mut value = process_write_lease(); + value.lease_epoch = 0; + assert!(value.validate().is_err()); + + let mut value = runtime_activation(); + value.runtime_instance_id = digest('A'); + assert!(value.validate().is_err()); + + let mut value = runtime_activation(); + value.expected_projection_sha256 = "expected-1".into(); + assert!(value.validate().is_err()); + } + + #[test] + fn runtime_presence_trust_anchor_is_provider_profile_and_purpose_bound() -> Result<()> { + let anchor = runtime_presence_anchor(); + anchor.validate()?; + + let mut value = anchor.clone(); + value.signing_purpose = "idunn.signed_daemon_health.v1".into(); + assert!(value.validate().is_err()); + + let mut value = anchor.clone(); + value.signed_schema = IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA.into(); + assert!(value.validate().is_err()); + + let mut value = anchor.clone(); + value.signing_purpose = "gamecult.runtime_presence_health.v1".into(); + value.signed_schema = "gamecult.runtime_presence_health.v1".into(); + assert!(value.validate().is_err()); + + let mut value = anchor; + value.signer_identity_id = + derive_service_identity_id::(&value.signer_public_key)?; + assert!(value.validate().is_err()); + Ok(()) + } + + #[test] + fn process_write_lease_cannot_encode_route_or_traffic_authority() -> Result<()> { + let canonical = process_write_lease().canonical_bytes()?; + assert_eq!(messagepack_array_len(&canonical), Some(14)); + + for forbidden in ["route", "traffic"] { + let mut with_extra_authority = vec![0xdc, 0, 15]; + with_extra_authority.extend_from_slice(&canonical[1..]); + with_extra_authority.extend_from_slice(&rmp_serde::to_vec(&forbidden)?); + assert!(IdunnProcessWriteLeaseRecord::decode_canonical(&with_extra_authority).is_err()); + } + Ok(()) + } + + #[test] + fn expected_and_topology_correlation_round_trip_canonically() -> Result<()> { + let expected = expected_incarnation(); + let expected_bytes = expected.canonical_bytes()?; + assert_eq!(&expected_bytes[..3], &[0xdc, 0, 18]); + assert_eq!( + IdunnExpectedIncarnationRecord::decode_canonical(&expected_bytes)?, + expected + ); + assert_eq!( + expected.canonical_sha256()?, + prefixed_sha256(&expected_bytes) + ); + + let receipt = topology_correlation(&expected); + validate_against_current_lease(&receipt, &expected)?; + let receipt_bytes = receipt.canonical_bytes()?; + assert_eq!(&receipt_bytes[..3], &[0xdc, 0, 21]); + let (decoded, unsigned) = + OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload(&receipt_bytes)?; + assert_eq!(decoded, receipt); + let unsigned_receipt: OdinRuntimeTopologyCorrelationRecord = + rmp_serde::from_slice(&unsigned)?; + assert!(unsigned_receipt.signature.is_empty()); + assert_eq!(unsigned, receipt.unsigned_signature_payload()?); + assert_eq!(receipt.canonical_sha256()?, prefixed_sha256(&receipt_bytes)); + Ok(()) + } + + #[test] + fn expected_and_topology_decoders_reject_noncanonical_or_extended_records() -> Result<()> { + assert!(IdunnExpectedIncarnationRecord::decode_canonical(&[0x91, 0xc0]).is_err()); + assert!( + OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload(&[0x91, 0xc0]) + .is_err() + ); + + let expected = expected_incarnation().canonical_bytes()?; + let mut noncanonical_expected = vec![0xdd, 0, 0, 0, 18]; + noncanonical_expected.extend_from_slice(&expected[3..]); + assert!(IdunnExpectedIncarnationRecord::decode_canonical(&noncanonical_expected).is_err()); + + let receipt = topology_correlation(&expected_incarnation()).canonical_bytes()?; + let mut noncanonical_receipt = vec![0xdd, 0, 0, 0, 21]; + noncanonical_receipt.extend_from_slice(&receipt[3..]); + assert!( + OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload( + &noncanonical_receipt, + ) + .is_err() + ); + + for forbidden in ["private-host-path", "runner-secret"] { + let mut extended = vec![0xdc, 0, 19]; + extended.extend_from_slice(&expected[3..]); + extended.extend_from_slice(&rmp_serde::to_vec(&forbidden)?); + assert!(IdunnExpectedIncarnationRecord::decode_canonical(&extended).is_err()); + } + Ok(()) + } + + #[test] + fn expected_refuses_partial_state_and_noncanonical_claim_sets() { + let mut value = expected_incarnation(); + value.state_contract_sha256 = None; + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.state_schema_generation = None; + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.state_schema_generation = None; + value.state_contract_sha256 = None; + assert!(value.validate().is_err()); + value.write_lease_required = false; + assert!(value.validate().is_ok()); + + let mut value = expected_incarnation(); + value.capabilities.reverse(); + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.capabilities.push(expected_capability("state")); + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.dependencies.reverse(); + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.dependencies.push(value.dependencies[1].clone()); + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + let mut duplicate = value.dependencies[1].clone(); + duplicate.kind = "required".into(); + value.dependencies.push(duplicate); + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.dependencies[0].provider_authority = None; + assert!(value.validate().is_err()); + + let mut value = expected_incarnation(); + value.source_revision = "A".repeat(40); + assert!(value.validate().is_err()); + + for private_source in [ + "F:\\Projects\\Ghostlight", + "/srv/build/Ghostlight", + "https://token@github.com/GameCult/Ghostlight", + ] { + let mut value = expected_incarnation(); + value.source_repository = private_source.into(); + assert!(value.validate().is_err()); + } + + let mut value = expected_incarnation(); + let stable_endpoint = value.route.as_ref().unwrap().stable_endpoint.clone(); + value.route.as_mut().unwrap().candidate_endpoint = stable_endpoint; + assert!(value.validate().is_err()); + } + + #[test] + fn expected_route_keeps_rudp_distinct_and_scheme_exact() { + let mut value = expected_incarnation(); + value.route = Some(IdunnExpectedRoute { + route_id: "odin-rendezvous".into(), + transport: "rudp".into(), + stable_endpoint: "rudp://10.77.0.1:17871".into(), + candidate_endpoint: "rudp://127.0.0.1:24171".into(), + }); + assert!(value.validate().is_ok()); + + value.route.as_mut().unwrap().candidate_endpoint = "tcp://127.0.0.1:24171".into(); + assert!(value.validate().is_err()); + + value.route.as_mut().unwrap().candidate_endpoint = "udp://127.0.0.1:24171".into(); + assert!(value.validate().is_err()); + } + + #[test] + fn topology_correlation_refuses_expected_substitution_and_dependency_omission() { + let expected = expected_incarnation(); + + let mut value = topology_correlation(&expected); + value.target = "epiphany".into(); + assert!(value.validate().is_ok()); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.runtime_id = "ghostlight-other-runtime".into(); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.expected_projection_sha256 = digest('c'); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies.pop(); + assert!(value.validate().is_ok()); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies[0].provider_id = Some("odin-substitute".into()); + assert!(value.validate().is_ok()); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies[0].kind = "required".into(); + assert!(value.validate().is_ok()); + assert!(validate_against_current_lease(&value, &expected).is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies[0].observed_capacity = Some(0); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies[0].observed_capacity = Some(1); + let mut stricter = expected.clone(); + stricter.dependencies[0].minimum_capacity = 2; + value.expected_projection_sha256 = stricter.canonical_sha256().unwrap(); + assert!(validate_against_current_lease(&value, &stricter).is_err()); + } + + #[test] + fn topology_correlation_requires_explicit_coherent_partial_states() { + let expected = expected_incarnation(); + let mut value = topology_correlation(&expected); + value.signed_presence_sha256 = None; + value.observed_presence_state = None; + value.observed_presence_publisher_sequence = None; + value.observed_write_lease_sha256 = None; + value.observed_capabilities.clear(); + value.present = false; + value.ready = false; + assert!(value.validate().is_err()); + + value.disagreements.push(OdinTopologyDisagreement { + code: "presence-missing".into(), + expected: Some("signed-runtime-presence".into()), + observed: None, + }); + assert!(value.validate().is_ok()); + + let mut value = topology_correlation(&expected); + value.runtime_instance_id = None; + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.current_activation_sha256 = None; + value.present = true; + value.ready = false; + value.disagreements.push(OdinTopologyDisagreement { + code: "activation-missing".into(), + expected: Some("current-activation".into()), + observed: None, + }); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.expected = false; + value.present = false; + value.ready = false; + assert!(value.validate().is_err()); + } + + #[test] + fn topology_correlation_refuses_unsorted_or_duplicate_claims() { + let expected = expected_incarnation(); + let mut value = topology_correlation(&expected); + value.dependencies.reverse(); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies.push(value.dependencies[1].clone()); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.ready = false; + value.disagreements = vec![ + OdinTopologyDisagreement { + code: "schema-mismatch".into(), + expected: Some("state.v2".into()), + observed: Some("state.v1".into()), + }, + OdinTopologyDisagreement { + code: "endpoint-mismatch".into(), + expected: Some("route-a".into()), + observed: Some("route-b".into()), + }, + ]; + assert!(value.validate().is_err()); + + value.disagreements[1].code = "schema-mismatch".into(); + assert!(value.validate().is_err()); + } + + #[test] + fn topology_correlation_preserves_warming_and_exact_write_lease_authority() { + let expected = expected_incarnation(); + let value = topology_correlation(&expected); + validate_against_current_lease(&value, &expected).unwrap(); + assert!( + value + .validate_against_expected(&expected, Some(&digest('d'))) + .is_err() + ); + assert!(value.validate_against_expected(&expected, None).is_err()); + + let mut digest_only_ready = topology_correlation(&expected); + digest_only_ready.observed_write_lease_sha256 = None; + digest_only_ready.validate().unwrap(); + assert!( + digest_only_ready + .validate_against_expected(&expected, Some(&digest('c'))) + .is_err() + ); + + let mut warming = topology_correlation(&expected); + warming.observed_presence_state = Some("warming".into()); + warming.ready = false; + assert!(warming.validate().is_err()); + warming.observed_write_lease_sha256 = None; + warming.validate().unwrap(); + warming.validate_against_expected(&expected, None).unwrap(); + warming.ready = true; + assert!(warming.validate().is_err()); + + let mut partial = topology_correlation(&expected); + partial.observed_presence_state = None; + assert!(partial.validate().is_err()); + let mut partial = topology_correlation(&expected); + partial.signed_presence_sha256 = None; + assert!(partial.validate().is_err()); + + let mut stateless = expected.clone(); + stateless.state_schema_generation = None; + stateless.state_contract_sha256 = None; + stateless.write_lease_required = false; + let mut stateless_receipt = topology_correlation(&stateless); + stateless_receipt.observed_write_lease_sha256 = None; + stateless_receipt + .validate_against_expected(&stateless, None) + .unwrap(); + stateless_receipt.observed_write_lease_sha256 = Some(digest('c')); + assert!( + stateless_receipt + .validate_against_expected(&stateless, Some(&digest('c'))) + .is_err() + ); + } + + #[test] + fn topology_correlation_cannot_manufacture_ready() { + let expected = expected_incarnation(); + let mut value = topology_correlation(&expected); + value.dependencies[0].ready = false; + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.disagreements.push(OdinTopologyDisagreement { + code: "capability-mismatch".into(), + expected: Some("conversation.v1".into()), + observed: Some("conversation.v0".into()), + }); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.present = false; + value.disagreements.push(OdinTopologyDisagreement { + code: "identity-mismatch".into(), + expected: Some("expected-runtime".into()), + observed: Some("other-runtime".into()), + }); + assert!(value.validate().is_err()); + + let mut value = topology_correlation(&expected); + value.dependencies[0].provider_evidence_sha256 = None; + assert!(value.validate().is_err()); + + let mut missing_capability = topology_correlation(&expected); + missing_capability.observed_capabilities.pop(); + assert!(missing_capability.validate().is_ok()); + assert!( + missing_capability + .validate_against_expected(&expected, Some(&digest('c'))) + .is_err() + ); + missing_capability.present = true; + missing_capability.ready = false; + missing_capability.disagreements = vec![OdinTopologyDisagreement { + code: "generic-mismatch".into(), + expected: Some("expected".into()), + observed: Some("observed".into()), + }]; + assert!( + missing_capability + .validate_against_expected(&expected, Some(&digest('c'))) + .is_err() + ); + correlate_capabilities( + &mut missing_capability.disagreements, + &expected.capabilities, + &missing_capability.observed_capabilities, + ); + missing_capability + .disagreements + .sort_by(|left, right| left.code.cmp(&right.code)); + missing_capability + .validate_against_expected(&expected, Some(&digest('c'))) + .unwrap(); + + let mut additional_capability = topology_correlation(&expected); + additional_capability + .observed_capabilities + .push(runtime_capability("telemetry")); + additional_capability + .validate_against_expected(&expected, Some(&digest('c'))) + .unwrap(); + } + + #[test] + fn topology_signature_is_exact_and_purpose_bound() -> Result<()> { + let temp = tempfile::tempdir()?; + let signer = enroll_service_identity_at::( + &temp.path().join("odin-topology.cc"), + )?; + let anchor = signer.trust_anchor()?; + let expected = expected_incarnation(); + let mut receipt = topology_correlation(&expected); + receipt.signer_identity_id = anchor.identity_id.clone(); + receipt.signature.clear(); + let unsigned = receipt.unsigned_signature_payload()?; + let proof = signer.sign::(&unsigned); + receipt.signature = proof.signature.clone(); + + let encoded = receipt.canonical_bytes()?; + let (decoded, decoded_unsigned) = + OdinRuntimeTopologyCorrelationRecord::decode_canonical_signed_payload(&encoded)?; + verify_service_identity_signature::< + OdinTopologyIdentity, + OdinRuntimeTopologyCorrelationPurpose, + >( + &anchor, + &decoded_unsigned, + &ServiceIdentitySignature { + identity_id: decoded.signer_identity_id, + signature: decoded.signature, + }, + )?; + + let mut substituted = decoded_unsigned.clone(); + substituted.push(0); + assert!( + verify_service_identity_signature::< + OdinTopologyIdentity, + OdinRuntimeTopologyCorrelationPurpose, + >(&anchor, &substituted, &proof) + .is_err() + ); + + let wrong_purpose = signer.sign::(&decoded_unsigned); + assert!( + verify_service_identity_signature::< + OdinTopologyIdentity, + OdinRuntimeTopologyCorrelationPurpose, + >(&anchor, &decoded_unsigned, &wrong_purpose) + .is_err() + ); + Ok(()) + } + + #[test] + fn topology_trust_anchor_is_odin_profile_and_purpose_bound() -> Result<()> { + let anchor = topology_anchor(); + anchor.validate()?; + + let mut value = anchor.clone(); + value.service_id = "idunn".into(); + assert!(value.validate().is_err()); + + let mut value = anchor.clone(); + value.signing_purpose = GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE.into(); + assert!(value.validate().is_err()); + + let mut value = anchor.clone(); + value.signed_schema = GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA.into(); + assert!(value.validate().is_err()); + + let mut value = anchor; + value.signer_identity_id = + derive_service_identity_id::(&value.signer_public_key)?; + assert!(value.validate().is_err()); + Ok(()) + } +} diff --git a/packages/cultnet-rs/src/service_identity.rs b/packages/cultnet-rs/src/service_identity.rs new file mode 100644 index 00000000..81b91a8c --- /dev/null +++ b/packages/cultnet-rs/src/service_identity.rs @@ -0,0 +1,920 @@ +use anyhow::{Context, Result, anyhow, bail}; +use cultcache_rs::{CacheBackingStore, CultCacheEnvelope, SingleFileMessagePackBackingStore}; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::io::{Read, Write}; +use std::marker::PhantomData; +use std::path::Path; +#[cfg(windows)] +use zeroize::Zeroize; +use zeroize::Zeroizing; + +pub const WINDOWS_SERVICE_IDENTITY_ASSURANCE: &str = "os_user_installation_bound_best_effort"; +pub const LINUX_SERVICE_IDENTITY_ASSURANCE: &str = "os_installation_file_bound_cloneable_baseline"; + +/// Fixes every authority-bearing name at the call site. A service cannot open +/// another service's identity by supplying a convenient runtime string. +pub trait ServiceIdentityProfile: Send + Sync + 'static { + const PRIVATE_TYPE: &'static str; + const PRIVATE_SCHEMA: &'static str; + const PRIVATE_KEY: &'static str; + const TRUST_ANCHOR_TYPE: &'static str; + const TRUST_ANCHOR_SCHEMA: &'static str; + const TRUST_ANCHOR_KEY: &'static str; + const ID_DOMAIN: &'static [u8]; + const SIGNATURE_DOMAIN: &'static [u8]; + const PROTECTOR_CONTEXT: &'static str; +} + +/// A purpose is a type, not caller-provided prose. Protocol implementations +/// define one zero-sized type per signed statement kind. +pub trait ServiceSignaturePurpose: Send + Sync + 'static { + const PURPOSE: &'static [u8]; +} + +pub enum IdunnServiceIdentity {} + +/// Odin-owned identity dedicated to signed Expected/Present/Ready topology +/// correlation. It is not a provider-health key and cannot issue Idunn +/// admission or lifecycle authority. +pub enum OdinTopologyIdentity {} + +pub struct OdinRuntimeTopologyCorrelationPurpose; + +impl ServiceSignaturePurpose for OdinRuntimeTopologyCorrelationPurpose { + const PURPOSE: &'static [u8] = b"odin.runtime_topology_correlation.v2"; +} + +impl ServiceIdentityProfile for OdinTopologyIdentity { + const PRIVATE_TYPE: &'static str = "odin.topology_identity.private.v1"; + const PRIVATE_SCHEMA: &'static str = "odin.topology_identity.private.v1"; + const PRIVATE_KEY: &'static str = "odin-topology-identity"; + const TRUST_ANCHOR_TYPE: &'static str = "odin.topology_identity.trust_anchor.v1"; + const TRUST_ANCHOR_SCHEMA: &'static str = "odin.topology_identity.trust_anchor.v1"; + const TRUST_ANCHOR_KEY: &'static str = "odin-topology-identity-public"; + const ID_DOMAIN: &'static [u8] = b"odin.topology-identity.id.v1\0"; + const SIGNATURE_DOMAIN: &'static [u8] = b"odin.topology-identity.signature.v1\0"; + const PROTECTOR_CONTEXT: &'static str = "odin-topology-identity-v1"; +} + +/// Root-operated identity dedicated to deployment-brake release grants. The +/// Idunn daemon receives only this profile's public trust anchor; opening the +/// private store is a provisioning operation, never daemon startup work. +pub enum IdunnDeploymentBrakeOperatorIdentity {} + +pub struct IdunnDeploymentBrakeReleasePurpose; + +impl ServiceSignaturePurpose + for IdunnDeploymentBrakeReleasePurpose +{ + const PURPOSE: &'static [u8] = b"idunn.deployment-release.v1"; +} + +impl ServiceIdentityProfile for IdunnDeploymentBrakeOperatorIdentity { + const PRIVATE_TYPE: &'static str = "idunn.deployment_brake_operator.private.v1"; + const PRIVATE_SCHEMA: &'static str = "idunn.deployment_brake_operator.private.v1"; + const PRIVATE_KEY: &'static str = "idunn-deployment-brake-operator"; + const TRUST_ANCHOR_TYPE: &'static str = "idunn.deployment_brake_operator.trust_anchor.v1"; + const TRUST_ANCHOR_SCHEMA: &'static str = "idunn.deployment_brake_operator.trust_anchor.v1"; + const TRUST_ANCHOR_KEY: &'static str = "idunn-deployment-brake-operator-public"; + const ID_DOMAIN: &'static [u8] = b"idunn.deployment-brake-operator.id.v1\0"; + const SIGNATURE_DOMAIN: &'static [u8] = b"idunn.deployment-brake-operator.signature.v1\0"; + const PROTECTOR_CONTEXT: &'static str = "idunn-deployment-brake-operator-v1"; +} + +/// Dedicated provider-owned identity for generic daemon-health statements. +/// It is intentionally independent of any one provider runtime or repository. +pub enum GameCultProviderHealthIdentity {} + +/// The only generic health statement purpose accepted from provider-health +/// identities by Idunn admission. +pub struct IdunnSignedDaemonHealthPurpose; + +impl ServiceSignaturePurpose for IdunnSignedDaemonHealthPurpose { + const PURPOSE: &'static [u8] = b"idunn.signed_daemon_health.v1"; +} + +/// Provider-owned runtime presence and health. This is deliberately distinct +/// from the legacy daemon-health purpose so an old signature cannot acquire +/// deployment-incarnation authority by substitution. +pub struct GameCultRuntimePresenceHealthPurpose; + +impl ServiceSignaturePurpose + for GameCultRuntimePresenceHealthPurpose +{ + const PURPOSE: &'static [u8] = b"gamecult.runtime_presence_health.v2"; +} + +impl ServiceIdentityProfile for GameCultProviderHealthIdentity { + const PRIVATE_TYPE: &'static str = "gamecult.provider_health_identity.private.v1"; + const PRIVATE_SCHEMA: &'static str = "gamecult.provider_health_identity.private.v1"; + const PRIVATE_KEY: &'static str = "gamecult-provider-health-identity"; + const TRUST_ANCHOR_TYPE: &'static str = "gamecult.provider_health_identity.trust_anchor.v1"; + const TRUST_ANCHOR_SCHEMA: &'static str = "gamecult.provider_health_identity.trust_anchor.v1"; + const TRUST_ANCHOR_KEY: &'static str = "gamecult-provider-health-identity-public"; + const ID_DOMAIN: &'static [u8] = b"gamecult.provider-health.identity.v1\0"; + const SIGNATURE_DOMAIN: &'static [u8] = b"gamecult.provider-health.signature.v1\0"; + const PROTECTOR_CONTEXT: &'static str = "gamecult-provider-health-identity-v1"; +} + +/// The only signing purpose accepted for Idunn's public projection of a +/// provider-authenticated health admission. Keeping this profile here makes +/// purpose selection a compile-time protocol choice rather than caller text. +pub struct IdunnAuthenticatedProviderHealthProjectionPurpose; + +impl ServiceSignaturePurpose + for IdunnAuthenticatedProviderHealthProjectionPurpose +{ + const PURPOSE: &'static [u8] = b"idunn.authenticated-provider-health-projection.v1"; +} + +/// Idunn's stable identity signs every activation before its ephemeral key is +/// handed to a workload. This makes the launch witness portable without +/// allowing a configuration file or provider to mint its own activation. +pub struct IdunnRuntimeActivationPurpose; + +impl ServiceSignaturePurpose for IdunnRuntimeActivationPurpose { + const PURPOSE: &'static [u8] = b"idunn.runtime_activation.v2"; +} + +impl ServiceIdentityProfile for IdunnServiceIdentity { + const PRIVATE_TYPE: &'static str = "idunn.service_identity.private.v1"; + const PRIVATE_SCHEMA: &'static str = "idunn.service_identity.private.v1"; + const PRIVATE_KEY: &'static str = "idunn-service-identity"; + const TRUST_ANCHOR_TYPE: &'static str = "idunn.service_identity.trust_anchor.v1"; + const TRUST_ANCHOR_SCHEMA: &'static str = "idunn.service_identity.trust_anchor.v1"; + const TRUST_ANCHOR_KEY: &'static str = "idunn-service-identity-public"; + const ID_DOMAIN: &'static [u8] = b"idunn.service-identity.id.v1\0"; + const SIGNATURE_DOMAIN: &'static [u8] = b"idunn.service-identity.signature.v1\0"; + const PROTECTOR_CONTEXT: &'static str = "idunn-service-identity-v1"; +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceIdentityPrivateEntry { + pub schema_version: String, + pub identity_id: String, + pub public_key: Vec, + pub protected_private_seed: Vec, + pub protector_kind: String, + pub protector_binding: String, + pub protector_version: String, + pub assurance: String, + pub created_at: String, + pub enrollment_nonce: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ServiceIdentityTrustAnchor { + pub schema_version: String, + pub identity_id: String, + pub public_key: Vec, + pub assurance: String, + pub identity_created_at: String, + pub source_identity_record_sha256: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ServiceIdentitySignature { + pub identity_id: String, + pub signature: Vec, +} + +/// The only value holding unprotected key material. It exposes signing, never +/// seed export or access to the underlying signing key. +pub struct ServiceIdentitySigner { + entry: ServiceIdentityPrivateEntry, + signing_key: SigningKey, + profile: PhantomData

    , +} + +impl ServiceIdentitySigner

    { + pub fn entry(&self) -> &ServiceIdentityPrivateEntry { + &self.entry + } + + pub fn trust_anchor(&self) -> Result { + Ok(ServiceIdentityTrustAnchor { + schema_version: P::TRUST_ANCHOR_SCHEMA.into(), + identity_id: self.entry.identity_id.clone(), + public_key: self.entry.public_key.clone(), + assurance: self.entry.assurance.clone(), + identity_created_at: self.entry.created_at.clone(), + source_identity_record_sha256: format!( + "sha256-{}", + hex(&Sha256::digest(rmp_serde::to_vec(&self.entry)?)) + ), + }) + } + + pub fn sign>(&self, payload: &[u8]) -> ServiceIdentitySignature { + ServiceIdentitySignature { + identity_id: self.entry.identity_id.clone(), + signature: self + .signing_key + .sign(&signing_message::(payload)) + .to_bytes() + .to_vec(), + } + } +} + +pub fn enroll_service_identity_at( + path: &Path, +) -> Result> { + if path.exists() { + bail!( + "service identity store {} already exists; enrollment is immutable", + path.display() + ); + } + prepare_parent(path)?; + let mut seed = Zeroizing::new([0u8; 32]); + rand::rng().fill_bytes(seed.as_mut()); + let signing_key = SigningKey::from_bytes(&*seed); + let mut nonce = [0u8; 32]; + rand::rng().fill_bytes(&mut nonce); + let public_key = signing_key.verifying_key().to_bytes(); + let binding = platform_binding::

    ()?; + let entry = ServiceIdentityPrivateEntry { + schema_version: P::PRIVATE_SCHEMA.into(), + identity_id: identity_id::

    (&public_key), + public_key: public_key.to_vec(), + protected_private_seed: protect_seed::

    (&*seed, &binding)?, + protector_kind: platform_protector_kind().into(), + protector_binding: binding, + protector_version: "v1".into(), + assurance: platform_assurance().into(), + created_at: chrono::Utc::now().to_rfc3339(), + enrollment_nonce: nonce.to_vec(), + }; + validate_private::

    (&entry)?; + let envelope = private_envelope::

    (&entry)?; + atomic_create_private_store(path, &envelope)?; + harden_store_permissions(path)?; + open_service_identity_at::

    (path) +} + +pub fn open_service_identity_at( + path: &Path, +) -> Result> { + if !path.is_file() { + bail!("service identity store {} does not exist", path.display()); + } + let entries = SingleFileMessagePackBackingStore::new(path).pull_all()?; + open_service_identity_envelopes::

    (&entries) +} + +/// Opens an immutable service-identity credential from a read-only stream. +/// +/// Unlike [`open_service_identity_at`], this path has no sibling-lock or +/// writable-filesystem requirement. It is intended for immutable credential +/// copies such as systemd `LoadCredential` files. +pub fn open_service_identity_credential_reader( + mut reader: impl Read, +) -> Result> { + let mut bytes = Zeroizing::new(Vec::new()); + reader + .read_to_end(&mut bytes) + .context("failed to read immutable service identity credential")?; + open_service_identity_credential_bytes::

    (&bytes) +} + +/// Opens an immutable service-identity credential from its exact bytes. +pub fn open_service_identity_credential_bytes( + bytes: &[u8], +) -> Result> { + let mut decoder = rmp_serde::Deserializer::new(std::io::Cursor::new(bytes)); + let entries = Vec::::deserialize(&mut decoder) + .context("immutable service identity credential is malformed MessagePack")?; + if decoder.position() != bytes.len() as u64 { + bail!("immutable service identity credential contains trailing data"); + } + open_service_identity_envelopes::

    (&entries) +} + +fn open_service_identity_envelopes( + entries: &[CultCacheEnvelope], +) -> Result> { + let [envelope] = entries else { + bail!("service identity store must contain exactly one immutable envelope"); + }; + if envelope.r#type != P::PRIVATE_TYPE + || envelope.key != P::PRIVATE_KEY + || envelope.schema_id.as_deref() != Some(P::PRIVATE_SCHEMA) + { + bail!("service identity store belongs to a different profile or schema"); + } + let entry: ServiceIdentityPrivateEntry = rmp_serde::from_slice(&envelope.payload) + .context("service identity private payload is malformed MessagePack")?; + validate_private::

    (&entry)?; + if entry.protector_kind != platform_protector_kind() || entry.assurance != platform_assurance() + { + bail!("service identity protector does not belong to this platform implementation"); + } + let binding = platform_binding::

    ()?; + if entry.protector_binding != binding { + bail!("service identity protector binding does not match this OS installation or profile"); + } + let seed = unprotect_seed::

    (&entry.protected_private_seed, &binding)?; + let seed: &[u8; 32] = seed + .as_slice() + .try_into() + .map_err(|_| anyhow!("unprotected service identity seed has invalid length"))?; + let signing_key = SigningKey::from_bytes(seed); + if signing_key.verifying_key().to_bytes().as_slice() != entry.public_key.as_slice() { + bail!("service identity private seed does not match enrolled public key"); + } + Ok(ServiceIdentitySigner { + entry, + signing_key, + profile: PhantomData, + }) +} + +pub fn export_service_identity_trust_anchor( + signer: &ServiceIdentitySigner

    , + path: &Path, +) -> Result { + let anchor = signer.trust_anchor()?; + prepare_parent(path)?; + let envelope = CultCacheEnvelope { + key: P::TRUST_ANCHOR_KEY.into(), + r#type: P::TRUST_ANCHOR_TYPE.into(), + payload: rmp_serde::to_vec(&anchor)?, + stored_at: anchor.identity_created_at.clone(), + schema_id: Some(P::TRUST_ANCHOR_SCHEMA.into()), + }; + let mut backing = SingleFileMessagePackBackingStore::new(path); + match backing.pull_all()?.as_slice() { + [] => backing.push(&envelope)?, + [current] if current == &envelope => {} + _ => bail!("service identity trust anchor output already contains different state"), + } + Ok(anchor) +} + +pub fn verify_service_identity_signature( + anchor: &ServiceIdentityTrustAnchor, + payload: &[u8], + proof: &ServiceIdentitySignature, +) -> Result<()> +where + P: ServiceIdentityProfile, + S: ServiceSignaturePurpose

    , +{ + validate_anchor::

    (anchor)?; + if proof.identity_id != anchor.identity_id { + bail!("service identity signature names a different identity"); + } + let key: [u8; 32] = anchor + .public_key + .as_slice() + .try_into() + .map_err(|_| anyhow!("service identity public key has invalid length"))?; + let signature = Signature::from_slice(&proof.signature) + .map_err(|_| anyhow!("service identity signature has invalid length"))?; + VerifyingKey::from_bytes(&key)? + .verify(&signing_message::(payload), &signature) + .map_err(|_| anyhow!("service identity signature verification failed")) +} + +/// Verify against a public key already admitted by an owning trust store. +/// The profile derives the identity, so callers cannot substitute a convenient +/// identity string while retaining the same signature bytes. +pub fn verify_service_identity_signature_with_public_key( + public_key: &[u8], + payload: &[u8], + proof: &ServiceIdentitySignature, +) -> Result<()> +where + P: ServiceIdentityProfile, + S: ServiceSignaturePurpose

    , +{ + if proof.identity_id != derive_service_identity_id::

    (public_key)? { + bail!("service identity signature names a different identity"); + } + let key: [u8; 32] = public_key + .try_into() + .map_err(|_| anyhow!("service identity public key has invalid length"))?; + let signature = Signature::from_slice(&proof.signature) + .map_err(|_| anyhow!("service identity signature has invalid length"))?; + VerifyingKey::from_bytes(&key)? + .verify(&signing_message::(payload), &signature) + .map_err(|_| anyhow!("service identity signature verification failed")) +} + +fn validate_private(entry: &ServiceIdentityPrivateEntry) -> Result<()> { + if entry.schema_version != P::PRIVATE_SCHEMA + || entry.public_key.len() != 32 + || entry.enrollment_nonce.len() != 32 + || entry.protected_private_seed.is_empty() + || entry.protector_version != "v1" + { + bail!("service identity private entry violates its profile schema"); + } + chrono::DateTime::parse_from_rfc3339(&entry.created_at) + .map_err(|_| anyhow!("service identity created_at is not RFC3339"))?; + if identity_id::

    (&entry.public_key) != entry.identity_id { + bail!("service identity id does not match public key"); + } + Ok(()) +} + +fn validate_anchor(anchor: &ServiceIdentityTrustAnchor) -> Result<()> { + if anchor.schema_version != P::TRUST_ANCHOR_SCHEMA + || anchor.public_key.len() != 32 + || derive_service_identity_id::

    (&anchor.public_key)? != anchor.identity_id + { + bail!("service identity trust anchor violates its profile schema"); + } + Ok(()) +} + +/// Derives the profile-bound identity id from an exact Ed25519 public key. +/// Consumers use this instead of accepting a caller-supplied identity string. +pub fn derive_service_identity_id(public_key: &[u8]) -> Result { + if public_key.len() != 32 { + bail!("service identity public key has invalid length"); + } + Ok(hex(&Sha256::digest([P::ID_DOMAIN, public_key].concat()))) +} + +fn identity_id(key: &[u8]) -> String { + derive_service_identity_id::

    (key).expect("internal service identity key is Ed25519") +} + +fn signing_message>( + payload: &[u8], +) -> Vec { + let mut out = + Vec::with_capacity(P::SIGNATURE_DOMAIN.len() + S::PURPOSE.len() + payload.len() + 16); + out.extend_from_slice(P::SIGNATURE_DOMAIN); + out.extend_from_slice(&(S::PURPOSE.len() as u64).to_be_bytes()); + out.extend_from_slice(S::PURPOSE); + out.extend_from_slice(&(payload.len() as u64).to_be_bytes()); + out.extend_from_slice(payload); + out +} + +fn private_envelope( + entry: &ServiceIdentityPrivateEntry, +) -> Result { + Ok(CultCacheEnvelope { + key: P::PRIVATE_KEY.into(), + r#type: P::PRIVATE_TYPE.into(), + payload: rmp_serde::to_vec(entry)?, + stored_at: entry.created_at.clone(), + schema_id: Some(P::PRIVATE_SCHEMA.into()), + }) +} + +fn prepare_parent(path: &Path) -> Result<()> { + let parent = path + .parent() + .ok_or_else(|| anyhow!("service identity path has no parent"))?; + std::fs::create_dir_all(parent)?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))?; + } + Ok(()) +} + +/// Enrollment is a one-shot authority transition. `create_new` makes two +/// simultaneous enrollments unable to overwrite one another, while the encoded +/// envelope vector remains a supported CultCache single-file representation. +fn atomic_create_private_store(path: &Path, envelope: &CultCacheEnvelope) -> Result<()> { + let bytes = rmp_serde::to_vec(&vec![envelope.clone()]) + .context("failed to encode immutable service identity store")?; + let mut file = std::fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(path) + .with_context(|| { + format!( + "service identity store {} already exists or cannot be created; enrollment is immutable", + path.display() + ) + })?; + if let Err(error) = file.write_all(&bytes).and_then(|()| file.sync_all()) { + drop(file); + let _ = std::fs::remove_file(path); + return Err(error).context("failed to persist immutable service identity store"); + } + Ok(()) +} +fn harden_store_permissions(_path: &Path) -> Result<()> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(_path, std::fs::Permissions::from_mode(0o600))?; + } + Ok(()) +} + +#[cfg(windows)] +fn platform_protector_kind() -> &'static str { + "windows_dpapi_current_user" +} +#[cfg(target_os = "linux")] +fn platform_protector_kind() -> &'static str { + "linux_file_mode_machine_id_binding" +} +#[cfg(windows)] +fn platform_assurance() -> &'static str { + WINDOWS_SERVICE_IDENTITY_ASSURANCE +} +#[cfg(target_os = "linux")] +fn platform_assurance() -> &'static str { + LINUX_SERVICE_IDENTITY_ASSURANCE +} + +#[cfg(windows)] +fn platform_binding() -> Result { + Ok(format!("dpapi-current-user:{}", P::PROTECTOR_CONTEXT)) +} +#[cfg(target_os = "linux")] +fn platform_binding() -> Result { + let raw = std::fs::read_to_string("/etc/machine-id") + .or_else(|_| std::fs::read_to_string("/var/lib/dbus/machine-id")) + .context("Linux machine-id is unavailable")?; + let id = raw.trim(); + if id.is_empty() { + bail!("Linux machine-id is empty"); + } + Ok(format!( + "{}:machine-id-sha256:{}", + P::PROTECTOR_CONTEXT, + hex(&Sha256::digest(id.as_bytes())) + )) +} + +#[cfg(windows)] +fn protect_seed(seed: &[u8; 32], binding: &str) -> Result> { + Ok(dpapi::

    (seed, binding, true)?.to_vec()) +} +#[cfg(windows)] +fn unprotect_seed( + seed: &[u8], + binding: &str, +) -> Result>> { + dpapi::

    (seed, binding, false) +} +#[cfg(windows)] +fn dpapi( + input: &[u8], + binding: &str, + protect: bool, +) -> Result>> { + use windows_sys::Win32::Foundation::LocalFree; + use windows_sys::Win32::Security::Cryptography::{ + CRYPT_INTEGER_BLOB, CRYPTPROTECT_UI_FORBIDDEN, CryptProtectData, CryptUnprotectData, + }; + let mut input = Zeroizing::new(input.to_vec()); + let mut entropy = Zeroizing::new( + Sha256::digest( + [ + b"gamecult-service-identity-dpapi-v1\0".as_slice(), + P::PROTECTOR_CONTEXT.as_bytes(), + binding.as_bytes(), + ] + .concat(), + ) + .to_vec(), + ); + let ib = CRYPT_INTEGER_BLOB { + cbData: input.len() as u32, + pbData: input.as_mut_ptr(), + }; + let eb = CRYPT_INTEGER_BLOB { + cbData: entropy.len() as u32, + pbData: entropy.as_mut_ptr(), + }; + let mut output = CRYPT_INTEGER_BLOB { + cbData: 0, + pbData: std::ptr::null_mut(), + }; + let ok = unsafe { + if protect { + CryptProtectData( + &ib, + std::ptr::null(), + &eb, + std::ptr::null(), + std::ptr::null(), + CRYPTPROTECT_UI_FORBIDDEN, + &mut output, + ) + } else { + CryptUnprotectData( + &ib, + std::ptr::null_mut(), + &eb, + std::ptr::null(), + std::ptr::null(), + CRYPTPROTECT_UI_FORBIDDEN, + &mut output, + ) + } + }; + if ok == 0 { + return Err(std::io::Error::last_os_error()) + .context("DPAPI service identity operation failed"); + } + if output.pbData.is_null() { + bail!("DPAPI service identity operation returned a null output"); + } + let output = unsafe { std::slice::from_raw_parts_mut(output.pbData, output.cbData as usize) }; + let bytes = Zeroizing::new(output.to_vec()); + output.zeroize(); + unsafe { + LocalFree(output.as_mut_ptr().cast()); + } + Ok(bytes) +} + +#[cfg(target_os = "linux")] +fn protect_seed(seed: &[u8; 32], binding: &str) -> Result> { + xor_linux::

    (seed, binding) +} +#[cfg(target_os = "linux")] +fn unprotect_seed( + seed: &[u8], + binding: &str, +) -> Result>> { + if seed.len() != 32 { + bail!("protected Linux service seed has invalid length"); + } + let mask = Sha256::digest( + [ + b"gamecult-linux-service-seed-v1\0".as_slice(), + P::PROTECTOR_CONTEXT.as_bytes(), + binding.as_bytes(), + ] + .concat(), + ); + let mut clear_seed = Zeroizing::new(Vec::with_capacity(seed.len())); + clear_seed.extend(seed.iter().zip(mask).map(|(a, b)| a ^ b)); + Ok(clear_seed) +} +#[cfg(target_os = "linux")] +fn xor_linux(seed: &[u8], binding: &str) -> Result> { + let mask = Sha256::digest( + [ + b"gamecult-linux-service-seed-v1\0".as_slice(), + P::PROTECTOR_CONTEXT.as_bytes(), + binding.as_bytes(), + ] + .concat(), + ); + Ok(seed.iter().zip(mask).map(|(a, b)| a ^ b).collect()) +} + +fn hex(bytes: &[u8]) -> String { + const D: &[u8; 16] = b"0123456789abcdef"; + let mut s = String::with_capacity(bytes.len() * 2); + for b in bytes { + s.push(D[(b >> 4) as usize] as char); + s.push(D[(b & 15) as usize] as char); + } + s +} + +#[cfg(test)] +mod tests { + use super::*; + enum OtherPurpose {} + impl ServiceSignaturePurpose for OtherPurpose { + const PURPOSE: &'static [u8] = b"idunn.other.v1"; + } + + #[test] + fn immutable_enrollment_reopens_and_is_purpose_and_payload_bound() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("idunn.ccmp"); + let signer = enroll_service_identity_at::(&path)?; + let proof = signer.sign::(b"healthy"); + let anchor = signer.trust_anchor()?; + verify_service_identity_signature::< + IdunnServiceIdentity, + IdunnAuthenticatedProviderHealthProjectionPurpose, + >(&anchor, b"healthy", &proof)?; + assert!( + verify_service_identity_signature::< + IdunnServiceIdentity, + IdunnAuthenticatedProviderHealthProjectionPurpose, + >(&anchor, b"unhealthy", &proof) + .is_err() + ); + assert!( + verify_service_identity_signature::( + &anchor, b"healthy", &proof + ) + .is_err() + ); + assert!(enroll_service_identity_at::(&path).is_err()); + assert_eq!( + derive_service_identity_id::(&anchor.public_key)?, + anchor.identity_id + ); + assert!(derive_service_identity_id::(&[0; 31]).is_err()); + assert_eq!( + open_service_identity_at::(&path)?.entry(), + signer.entry() + ); + Ok(()) + } + + #[test] + fn immutable_credential_reader_opens_without_a_path_and_retains_signing_authority() -> Result<()> + { + let temp = tempfile::tempdir()?; + let path = temp.path().join("provider-health.cc"); + let enrolled = enroll_service_identity_at::(&path)?; + let bytes = std::fs::read(path)?; + + let from_reader = open_service_identity_credential_reader::( + std::io::Cursor::new(bytes.clone()), + )?; + let from_bytes = + open_service_identity_credential_bytes::(&bytes)?; + assert_eq!(from_reader.entry(), enrolled.entry()); + assert_eq!(from_bytes.entry(), enrolled.entry()); + + let payload = b"runtime-presence"; + let proof = from_reader.sign::(payload); + verify_service_identity_signature::< + GameCultProviderHealthIdentity, + GameCultRuntimePresenceHealthPurpose, + >(&enrolled.trust_anchor()?, payload, &proof)?; + Ok(()) + } + + #[test] + fn immutable_credential_reader_rejects_ambiguous_profile_or_machine_state() -> Result<()> { + let temp = tempfile::tempdir()?; + let path = temp.path().join("idunn.ccmp"); + enroll_service_identity_at::(&path)?; + let bytes = std::fs::read(path)?; + + assert!( + open_service_identity_credential_bytes::(&bytes) + .is_err() + ); + + let mut envelopes: Vec = rmp_serde::from_slice(&bytes)?; + let mut duplicated = envelopes.clone(); + duplicated.push(envelopes[0].clone()); + assert!( + open_service_identity_credential_bytes::(&rmp_serde::to_vec( + &duplicated + )?) + .is_err() + ); + + let mut trailing = bytes.clone(); + trailing.push(0xc0); + assert!(open_service_identity_credential_bytes::(&trailing).is_err()); + + let mut wrong_seed_envelopes = envelopes.clone(); + let mut wrong_seed: ServiceIdentityPrivateEntry = + rmp_serde::from_slice(&wrong_seed_envelopes[0].payload)?; + wrong_seed.protected_private_seed[0] ^= 1; + wrong_seed_envelopes[0].payload = rmp_serde::to_vec(&wrong_seed)?; + assert!( + open_service_identity_credential_bytes::(&rmp_serde::to_vec( + &wrong_seed_envelopes + )?) + .is_err() + ); + + let mut entry: ServiceIdentityPrivateEntry = rmp_serde::from_slice(&envelopes[0].payload)?; + entry.protector_binding.push_str(":another-machine"); + envelopes[0].payload = rmp_serde::to_vec(&entry)?; + assert!( + open_service_identity_credential_bytes::(&rmp_serde::to_vec( + &envelopes + )?) + .is_err() + ); + Ok(()) + } + + #[test] + fn malformed_or_profile_substituted_state_fails_closed_without_replacement() -> Result<()> { + enum Alien {} + impl ServiceIdentityProfile for Alien { + const PRIVATE_TYPE: &'static str = "alien.private.v1"; + const PRIVATE_SCHEMA: &'static str = "alien.private.v1"; + const PRIVATE_KEY: &'static str = "alien"; + const TRUST_ANCHOR_TYPE: &'static str = "alien.anchor.v1"; + const TRUST_ANCHOR_SCHEMA: &'static str = "alien.anchor.v1"; + const TRUST_ANCHOR_KEY: &'static str = "alien-public"; + const ID_DOMAIN: &'static [u8] = b"alien.id\0"; + const SIGNATURE_DOMAIN: &'static [u8] = b"alien.sig\0"; + const PROTECTOR_CONTEXT: &'static str = "alien-v1"; + } + let temp = tempfile::tempdir()?; + let malformed = temp.path().join("bad.ccmp"); + std::fs::write(&malformed, b"rot")?; + let before = std::fs::read(&malformed)?; + assert!(open_service_identity_at::(&malformed).is_err()); + assert!(enroll_service_identity_at::(&malformed).is_err()); + assert_eq!(before, std::fs::read(&malformed)?); + let good = temp.path().join("good.ccmp"); + enroll_service_identity_at::(&good)?; + assert!(open_service_identity_at::(&good).is_err()); + Ok(()) + } + + #[test] + fn exported_anchor_is_public_only_immutable_and_rejects_identity_substitution() -> Result<()> { + let temp = tempfile::tempdir()?; + let signer = + enroll_service_identity_at::(&temp.path().join("private.ccmp"))?; + let output = temp.path().join("public.ccmp"); + let anchor = export_service_identity_trust_anchor(&signer, &output)?; + let bytes = std::fs::read(&output)?; + assert!( + !bytes + .windows(signer.entry().protected_private_seed.len()) + .any(|w| w == signer.entry().protected_private_seed) + ); + export_service_identity_trust_anchor(&signer, &output)?; + assert_eq!(bytes, std::fs::read(&output)?); + let other = + enroll_service_identity_at::(&temp.path().join("other.ccmp"))?; + let proof = other.sign::(b"healthy"); + assert!( + verify_service_identity_signature::< + IdunnServiceIdentity, + IdunnAuthenticatedProviderHealthProjectionPurpose, + >(&anchor, b"healthy", &proof) + .is_err() + ); + Ok(()) + } + + #[test] + fn provider_health_profile_is_domain_separated_and_purpose_bound() -> Result<()> { + let temp = tempfile::tempdir()?; + let signer = enroll_service_identity_at::( + &temp.path().join("provider-health.cc"), + )?; + let proof = signer.sign::(b"signed-health-record"); + let anchor = signer.trust_anchor()?; + verify_service_identity_signature::< + GameCultProviderHealthIdentity, + IdunnSignedDaemonHealthPurpose, + >(&anchor, b"signed-health-record", &proof)?; + verify_service_identity_signature_with_public_key::< + GameCultProviderHealthIdentity, + IdunnSignedDaemonHealthPurpose, + >(&anchor.public_key, b"signed-health-record", &proof)?; + assert_eq!( + anchor.schema_version, + "gamecult.provider_health_identity.trust_anchor.v1" + ); + assert_ne!( + derive_service_identity_id::(&anchor.public_key)?, + derive_service_identity_id::(&anchor.public_key)? + ); + Ok(()) + } + + #[test] + fn runtime_presence_and_legacy_health_purposes_cannot_be_substituted() -> Result<()> { + let temp = tempfile::tempdir()?; + let signer = enroll_service_identity_at::( + &temp.path().join("provider-runtime-presence.cc"), + )?; + let anchor = signer.trust_anchor()?; + let payload = b"runtime-presence"; + + let presence = signer.sign::(payload); + verify_service_identity_signature::< + GameCultProviderHealthIdentity, + GameCultRuntimePresenceHealthPurpose, + >(&anchor, payload, &presence)?; + assert!( + verify_service_identity_signature::< + GameCultProviderHealthIdentity, + IdunnSignedDaemonHealthPurpose, + >(&anchor, payload, &presence) + .is_err() + ); + + let legacy = signer.sign::(payload); + assert!( + verify_service_identity_signature::< + GameCultProviderHealthIdentity, + GameCultRuntimePresenceHealthPurpose, + >(&anchor, payload, &legacy) + .is_err() + ); + Ok(()) + } +} diff --git a/packages/cultnet-rs/src/snapshot_query.rs b/packages/cultnet-rs/src/snapshot_query.rs new file mode 100644 index 00000000..f6150d1d --- /dev/null +++ b/packages/cultnet-rs/src/snapshot_query.rs @@ -0,0 +1,339 @@ +use anyhow::{Result, anyhow}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{CultNetDocumentRegistry, CultNetMessage, CultNetRawDocumentRecord}; + +/// Read-only backing surface for a CultNet snapshot server. +/// +/// Records returned here are already the stored wire records. The snapshot +/// server selects and clones them; it never decodes payloads, rewrites source +/// metadata, or applies them to another cache. +pub trait CultNetRawSnapshotSource { + fn raw_snapshot(&self) -> Result>; +} + +impl CultNetRawSnapshotSource for Vec { + fn raw_snapshot(&self) -> Result> { + Ok(self.clone()) + } +} + +impl CultNetRawSnapshotSource for F +where + F: Fn() -> Result>, +{ + fn raw_snapshot(&self) -> Result> { + self() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CultNetReadOnlySnapshotPolicy { + allowed_records: BTreeSet<(String, String)>, +} + +impl CultNetReadOnlySnapshotPolicy { + pub fn new() -> Self { + Self::default() + } + + pub fn allow( + &mut self, + schema_id: impl Into, + record_key: impl Into, + ) -> Result<&mut Self> { + let schema_id = schema_id.into(); + let record_key = record_key.into(); + require_non_empty(&schema_id, "schema_id")?; + require_non_empty(&record_key, "record_key")?; + self.allowed_records.insert((schema_id, record_key)); + Ok(self) + } + + fn allows(&self, schema_id: &str, record_key: &str) -> bool { + self.allowed_records + .contains(&(schema_id.to_string(), record_key.to_string())) + } +} + +/// Serve one raw snapshot request from an explicitly bounded, read-only view. +/// +/// The document registry proves that every exposed schema belongs to the +/// runtime. The policy grants individual `(schema_id, record_key)` pairs. The +/// source owns the bytes and metadata; this function only selects records. +pub fn serve_read_only_raw_snapshot( + document_registry: &CultNetDocumentRegistry, + policy: &CultNetReadOnlySnapshotPolicy, + source: &S, + request: &CultNetMessage, +) -> Result { + let CultNetMessage::SnapshotRequest { + message_id, + schema_ids, + record_keys, + } = request + else { + return Err(anyhow!("expected cultnet.snapshot_request.v0")); + }; + require_non_empty(message_id, "message_id")?; + reject_duplicates(schema_ids.as_deref(), "requested schema id")?; + reject_duplicates(record_keys.as_deref(), "requested record key")?; + + let requested_schemas = schema_ids + .as_ref() + .map(|values| values.iter().map(String::as_str).collect::>()); + let requested_keys = record_keys + .as_ref() + .map(|values| values.iter().map(String::as_str).collect::>()); + let mut selected = Vec::new(); + let mut seen = BTreeSet::new(); + + for document in source.raw_snapshot()? { + let identity = (document.schema_id.clone(), document.record_key.clone()); + if !seen.insert(identity.clone()) { + return Err(anyhow!( + "snapshot source contains duplicate record {:?}/{:?}", + identity.0, + identity.1 + )); + } + if document_registry + .binding_by_schema_id(&document.schema_id) + .is_none() + { + return Err(anyhow!( + "snapshot source record {:?}/{:?} uses an unregistered schema", + document.schema_id, + document.record_key + )); + } + if !policy.allows(&document.schema_id, &document.record_key) { + continue; + } + if requested_schemas + .as_ref() + .is_some_and(|schemas| !schemas.contains(document.schema_id.as_str())) + { + continue; + } + if requested_keys + .as_ref() + .is_some_and(|keys| !keys.contains(document.record_key.as_str())) + { + continue; + } + selected.push(document); + } + + Ok(CultNetMessage::SnapshotResponseRaw { + message_id: message_id.clone(), + documents: selected, + }) +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CultNetSnapshotSourceExpectation { + pub runtime_id: Option, + pub agent_id: Option, + pub role: Option, + pub tags: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultNetSnapshotRecordExpectation { + pub schema_id: String, + pub record_key: String, + pub source: CultNetSnapshotSourceExpectation, +} + +impl CultNetSnapshotRecordExpectation { + pub fn new(schema_id: impl Into, record_key: impl Into) -> Self { + Self { + schema_id: schema_id.into(), + record_key: record_key.into(), + source: CultNetSnapshotSourceExpectation::default(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultNetRawSnapshotQuery { + message_id: String, + expectations: BTreeMap<(String, String), CultNetSnapshotSourceExpectation>, +} + +impl CultNetRawSnapshotQuery { + pub fn new( + message_id: impl Into, + records: Vec, + ) -> Result { + let message_id = message_id.into(); + require_non_empty(&message_id, "message_id")?; + if records.is_empty() { + return Err(anyhow!("snapshot query must request at least one record")); + } + let mut expectations = BTreeMap::new(); + for record in records { + require_non_empty(&record.schema_id, "schema_id")?; + require_non_empty(&record.record_key, "record_key")?; + let identity = (record.schema_id, record.record_key); + if expectations + .insert(identity.clone(), record.source) + .is_some() + { + return Err(anyhow!( + "snapshot query contains duplicate requested record {:?}/{:?}", + identity.0, + identity.1 + )); + } + } + Ok(Self { + message_id, + expectations, + }) + } + + pub fn request(&self) -> CultNetMessage { + CultNetMessage::SnapshotRequest { + message_id: self.message_id.clone(), + schema_ids: Some( + self.expectations + .keys() + .map(|(schema_id, _)| schema_id.clone()) + .collect::>() + .into_iter() + .collect(), + ), + record_keys: Some( + self.expectations + .keys() + .map(|(_, record_key)| record_key.clone()) + .collect::>() + .into_iter() + .collect(), + ), + } + } + + /// Validate a response and return records without mutating any local cache. + pub fn accept_response( + &self, + response: CultNetMessage, + ) -> Result> { + let CultNetMessage::SnapshotResponseRaw { + message_id, + documents, + } = response + else { + return Err(anyhow!("expected cultnet.snapshot_response_raw.v0")); + }; + if message_id != self.message_id { + return Err(anyhow!( + "snapshot response message id {:?} does not match request {:?}", + message_id, + self.message_id + )); + } + + let mut seen = BTreeSet::new(); + for document in &documents { + let identity = (document.schema_id.clone(), document.record_key.clone()); + if !seen.insert(identity.clone()) { + return Err(anyhow!( + "snapshot response contains duplicate record {:?}/{:?}", + identity.0, + identity.1 + )); + } + let expected = self.expectations.get(&identity).ok_or_else(|| { + anyhow!( + "snapshot response contains unexpected record {:?}/{:?}", + identity.0, + identity.1 + ) + })?; + require_expected_metadata( + "source_runtime_id", + expected.runtime_id.as_deref(), + document.source_runtime_id.as_deref(), + &identity, + )?; + require_expected_metadata( + "source_agent_id", + expected.agent_id.as_deref(), + document.source_agent_id.as_deref(), + &identity, + )?; + require_expected_metadata( + "source_role", + expected.role.as_deref(), + document.source_role.as_deref(), + &identity, + )?; + if let Some(expected_tags) = expected.tags.as_ref() + && document.tags.as_ref() != Some(expected_tags) + { + return Err(anyhow!( + "snapshot response record {:?}/{:?} has unexpected tags: expected {:?}, got {:?}", + identity.0, + identity.1, + expected_tags, + document.tags + )); + } + } + Ok(documents) + } +} + +pub fn query_read_only_raw_snapshot( + query: &CultNetRawSnapshotQuery, + exchange: F, +) -> Result> +where + F: FnOnce(CultNetMessage) -> Result, +{ + query.accept_response(exchange(query.request())?) +} + +fn require_expected_metadata( + field: &str, + expected: Option<&str>, + actual: Option<&str>, + identity: &(String, String), +) -> Result<()> { + if let Some(expected) = expected + && actual != Some(expected) + { + return Err(anyhow!( + "snapshot response record {:?}/{:?} has unexpected {field}: expected {:?}, got {:?}", + identity.0, + identity.1, + expected, + actual + )); + } + Ok(()) +} + +fn reject_duplicates(values: Option<&[String]>, label: &str) -> Result<()> { + let Some(values) = values else { + return Ok(()); + }; + let mut seen = BTreeSet::new(); + for value in values { + require_non_empty(value, label)?; + if !seen.insert(value) { + return Err(anyhow!("duplicate {label} {value:?}")); + } + } + Ok(()) +} + +fn require_non_empty(value: &str, field: &str) -> Result<()> { + if value.trim().is_empty() { + return Err(anyhow!("{field} must be non-empty")); + } + Ok(()) +} From 67cbb4ea36b1c9952e7e3408e1bbb57267f5325f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 23:09:45 +0200 Subject: [PATCH 046/203] Give the Rust CultMesh runtime a home The only Rust CultMesh implementation in existence was 1,363 lines inside Ghostlight's vendor directory. It was in no library repo and no standalone repo, so it had never been checked against the browser, Kotlin, and TypeScript CultMesh bodies it must interoperate with. It is not a duplicate of the facade in cultnet-rs. That one is the small surface the parity table claims for Rust: peer cards, authority leases, endpoint parsing. This is the stream and session layer -- node, document sets, RUDP document publication, stream descriptors, negotiation, stream catalog, and shared-memory frame rings -- which the same table lists under Rust's "Not claimed". A consumer grew the unclaimed surface because there was nowhere else to put it. Its path dependencies already matched this layout and it builds against CultLib's cultnet-rs unchanged, so it does not depend on the forked RUDP extras. Seven runtime-local tests pass, and CI now runs them. The parity table gains no claim from this. The Rust row says plainly that no interop lane exercises the stream surface, and expansion work records that runtime-local tests are not a parity claim. Landing code is not evidence. packages/cultmesh-rs needed an explicit .gitignore re-include; the NuGet packages rule hides every language package until one is added by name. Co-Authored-By: Claude Opus 5 --- .github/workflows/cultnet-interop.yml | 3 + .gitignore | 2 + docs/runtime-parity-scope.md | 6 +- packages/cultmesh-rs/Cargo.lock | 1183 +++++++++++++++++++++ packages/cultmesh-rs/Cargo.toml | 20 + packages/cultmesh-rs/src/lib.rs | 1363 +++++++++++++++++++++++++ 6 files changed, 2576 insertions(+), 1 deletion(-) create mode 100644 packages/cultmesh-rs/Cargo.lock create mode 100644 packages/cultmesh-rs/Cargo.toml create mode 100644 packages/cultmesh-rs/src/lib.rs diff --git a/.github/workflows/cultnet-interop.yml b/.github/workflows/cultnet-interop.yml index 33699446..3437add3 100644 --- a/.github/workflows/cultnet-interop.yml +++ b/.github/workflows/cultnet-interop.yml @@ -92,6 +92,9 @@ jobs: - name: Verify Rust RUDP reliability run: cargo test --manifest-path packages/cultnet-rs/Cargo.toml rudp + - name: Verify Rust CultMesh runtime-local surface + run: cargo test --manifest-path packages/cultmesh-rs/Cargo.toml + - name: Verify C# RUDP reliability run: dotnet test tests/GameCult.Networking.Tests/GameCult.Networking.Tests.csproj --filter "FullyQualifiedName~Rudp" diff --git a/.gitignore b/.gitignore index d639c709..ce48c2dc 100644 --- a/.gitignore +++ b/.gitignore @@ -307,6 +307,8 @@ node_modules/ !packages/cultcache-rs/** !packages/cultnet-rs/ !packages/cultnet-rs/** +!packages/cultmesh-rs/ +!packages/cultmesh-rs/** packages/**/node_modules/ packages/**/dist/ packages/**/dist-test/ diff --git a/docs/runtime-parity-scope.md b/docs/runtime-parity-scope.md index cd3761d7..681f1ad3 100644 --- a/docs/runtime-parity-scope.md +++ b/docs/runtime-parity-scope.md @@ -33,7 +33,7 @@ CultMesh. | C# | Reference implementation and production game/server substrate | CultCache typed documents and SoA, CultNet schema-v0, LiteNetLib production path with adapter profile, shared transport profiles, shared RUDP socket transport, C# multi-peer RUDP listener and schema service host, C# interop RUDP serve/dial path, connected RUDP CultMesh client helpers, RUDP-backed schema-client selection for `rudp://` service endpoints, RUDP reconnect policy/controller and reconnect-loop helper, LiteNetLib client reconnect-controller adoption, schema-client transport port for service request/response clients, CultMesh node/session/catalog/stream/simulation/RUDP surfaces, authorized peer lookup | No need to abandon LiteNetLib before the CultNet adapter fully owns production service adoption | | Python | Package runtime, local daemon endpoint, raw interop peer, typed state participant | CultCache/CultNet/CultMesh package surface, branded CultMesh node/schema/shard/Verse/peer/authority/stream/RUDP ergonomics, authorized peer lookup, shard catalogs/logs, schema-transport factory for raw client request/subscription flows, local TCP and RUDP CultMesh server listeners, Python interop RUDP serve/dial path, session/prediction/simulation fact helpers, RUDP codec/session/socket transport, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, benchmark evidence against C# cache paths | Universal C# throughput, full C# daemon/server stack, and RUDP as the only public service pipe | | TypeScript | Node/browser-adjacent projection and interop runtime | CultCache parity tests, CultNet schema-v0 peer, advertised schema/shard catalog wire contracts plus request/sync helpers, transport profiles, TCP-framed transport port and peer helper, shared RUDP socket transport, RUDP interop serve/dial path, RUDP reconnect policy/controller, RUDP reconnect-loop helper, branded CultMesh node/schema/shard/Verse/peer/authority/stream/RUDP ergonomics, authorized peer lookup | Full C# game session/server stack | -| Rust | Low-level cache/transport/runtime substrate | CultCache/CultNet interop lane, schema and shard catalog helpers, typed same-schema alias hydration from raw snapshots, transport profiles, TCP-framed port, shared RUDP codec/session/socket transport, Rust interop RUDP serve/dial path, connected RUDP CultMesh client helpers, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, small CultMesh facade for schema registry, shard catalog, peer cards, authority leases, authorized peer lookup, RUDP endpoint parsing, and peer-card/authorized-peer RUDP client construction | Full CultMesh game-session/server facade, stream catalog, or production game-session server | +| Rust | Low-level cache/transport/runtime substrate | CultCache/CultNet interop lane, schema and shard catalog helpers, typed same-schema alias hydration from raw snapshots, transport profiles, TCP-framed port, shared RUDP codec/session/socket transport, Rust interop RUDP serve/dial path, connected RUDP CultMesh client helpers, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence, small CultMesh facade for schema registry, shard catalog, peer cards, authority leases, authorized peer lookup, RUDP endpoint parsing, and peer-card/authorized-peer RUDP client construction | Full CultMesh game-session/server facade or production game-session server. `packages/cultmesh-rs` now holds a Rust stream and session surface -- node, document sets, RUDP document publication, stream descriptors, negotiation, stream catalog, and shared-memory frame rings -- but no interop lane exercises it, so it carries no cross-runtime parity claim | | Kotlin | JVM/Android client substrate | Typed MessagePack cache, one-call typed snapshot document sync through local same-schema aliases, built-in schema catalog advertisement, schema/shard catalogs with shared schema-message transport fetch/sync helpers, authority leases, authorized peer lookup, stream catalog, branded `CultMesh` facade, WebSocket transport profile/connection helper, RUDP client/server sugar, connected RUDP CultMesh client helpers, Kotlin interop RUDP serve/dial path, RUDP reconnect policy/controller and reconnect-loop helper, schema-v0 MessagePack over RUDP evidence | C# SoA store, full C# server daemon, or Unity hot-loop memory layout | ## Evidence Gates @@ -85,6 +85,10 @@ server body, is deliberately not made. backfilling C# server responsibilities it should not own. Kotlin WebSocket and RUDP adapters now share one `CultNetSchemaMessageTransport` helper shape for schema/shard catalog request and sync flows. +- Bring `packages/cultmesh-rs` under the wire claim. It arrived from a consumer repo where it grew + outside any conformance harness, and its stream, negotiation, and shared-memory frame-ring + surfaces have never been checked against the browser, Kotlin, and TypeScript CultMesh bodies + they must interoperate with. Runtime-local tests pass; that is not a parity claim. - Keep performance claims evidence-bound: C# owns SoA; Python has benchmark comparison gates; other runtimes should advertise measured native hot paths only when those paths exist. diff --git a/packages/cultmesh-rs/Cargo.lock b/packages/cultmesh-rs/Cargo.lock new file mode 100644 index 00000000..c515ded2 --- /dev/null +++ b/packages/cultmesh-rs/Cargo.lock @@ -0,0 +1,1183 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cultcache-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs-derive", + "fs2", + "redb", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "uuid", + "windows-sys 0.59.0", +] + +[[package]] +name = "cultcache-rs-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cultmesh-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "cultcache-rs", + "cultnet-rs", + "pretty_assertions", + "rmp-serde", + "serde", + "tempfile", +] + +[[package]] +name = "cultnet-rs" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "base64", + "chrono", + "cultcache-rs", + "ed25519-dalek", + "hmac", + "rand", + "rmp-serde", + "rmpv", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "socket2", + "uuid", + "windows-sys 0.61.2", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redb" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de6c3b63e007e90ce536ec2ae4690826136a20ec8dbbbb400daef1bb999d2e36" +dependencies = [ + "libc", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rmpv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" +dependencies = [ + "rmp", + "serde", + "serde_bytes", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/packages/cultmesh-rs/Cargo.toml b/packages/cultmesh-rs/Cargo.toml new file mode 100644 index 00000000..9a719d32 --- /dev/null +++ b/packages/cultmesh-rs/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cultmesh-rs" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +name = "cultmesh_rs" +path = "src/lib.rs" + +[dependencies] +anyhow = "1" +cultcache-rs = { path = "../cultcache-rs" } +cultnet-rs = { path = "../cultnet-rs" } +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +pretty_assertions = "1.4.1" +rmp-serde = "1" +tempfile = "3" diff --git a/packages/cultmesh-rs/src/lib.rs b/packages/cultmesh-rs/src/lib.rs new file mode 100644 index 00000000..5adbb53f --- /dev/null +++ b/packages/cultmesh-rs/src/lib.rs @@ -0,0 +1,1363 @@ +use anyhow::{Context, Result}; +use cultcache_rs::CultCache; +use cultcache_rs::CultCacheEnvelope; +use cultcache_rs::DatabaseEntry; +use cultcache_rs::SingleFileMessagePackBackingStore; +use cultnet_rs::CultNetDocumentPutOptions; +use cultnet_rs::CultNetDocumentRegistry; +use cultnet_rs::CultNetRudpSocketTransportConnection; +use cultnet_rs::CultNetRudpSocketTransportOptions; +use cultnet_rs::CultNetWireContract; +use cultnet_rs::decode_cultnet_message_from_slice; +use cultnet_rs::encode_cultnet_message_to_vec; +use serde::Serialize; +use std::collections::BTreeMap; +use std::env; +use std::net::SocketAddr; +use std::net::UdpSocket; +use std::path::Path; +use std::thread; +use std::time::Duration; +use std::time::Instant; + +pub const CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID: u32 = 0x0d1d_0002; + +pub trait CultMeshDocumentSet: Clone + Send + Sync + 'static { + fn register_cache(&self, cache: &mut CultCache) -> Result<()>; + fn register_documents(&self, registry: &mut CultNetDocumentRegistry) -> Result<()>; +} + +#[macro_export] +macro_rules! cultmesh_documents { + ($name:ident { $($entry:ty => $schema_version:expr),* $(,)? }) => { + #[derive(Clone, Copy, Debug, Default)] + pub struct $name; + + impl $crate::CultMeshDocumentSet for $name { + fn register_cache( + &self, + cache: &mut cultcache_rs::CultCache, + ) -> ::anyhow::Result<()> { + $( + cache.register_entry_type::<$entry>()?; + )* + Ok(()) + } + + fn register_documents( + &self, + registry: &mut cultnet_rs::CultNetDocumentRegistry, + ) -> ::anyhow::Result<()> { + $( + registry.register( + cultnet_rs::CultNetDocumentBinding::for_entry_with_schema_id::<$entry>( + $schema_version.to_string(), + $schema_version.to_string(), + ), + ); + )* + Ok(()) + } + } + }; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshNodeOptions { + pub runtime_id: String, + pub pull_on_start: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpDocumentPublishOptions { + pub target: SocketAddr, + pub runtime_id: String, + pub connection_id: u32, + pub connect_timeout: Duration, + pub flush_timeout: Duration, + pub poll_interval: Duration, + pub resend_delay_ms: u64, + pub source_agent_id: Option, + pub source_role: Option, + pub tags: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpSnapshotOptions { + pub target: SocketAddr, + pub runtime_id: String, + pub connection_id: u32, + pub connect_timeout: Duration, + pub response_timeout: Duration, + pub poll_interval: Duration, + pub resend_delay_ms: u64, + pub schema_ids: Option>, + pub record_keys: Option>, +} + +impl CultMeshRudpSnapshotOptions { + pub fn odin(target: SocketAddr, runtime_id: impl Into) -> Self { + Self { + target, + runtime_id: runtime_id.into(), + ..Self::default() + } + } +} + +impl Default for CultMeshRudpSnapshotOptions { + fn default() -> Self { + Self { + target: SocketAddr::from(([0, 0, 0, 0], 0)), + runtime_id: "cultmesh-rudp-snapshot-client".to_string(), + connection_id: CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, + connect_timeout: Duration::from_secs(3), + response_timeout: Duration::from_secs(3), + poll_interval: Duration::from_millis(10), + resend_delay_ms: 50, + schema_ids: None, + record_keys: None, + } + } +} + +impl CultMeshRudpDocumentPublishOptions { + pub fn odin(target: SocketAddr, runtime_id: impl Into) -> Self { + Self { + target, + runtime_id: runtime_id.into(), + ..Self::default() + } + } +} + +impl Default for CultMeshRudpDocumentPublishOptions { + fn default() -> Self { + Self { + target: SocketAddr::from(([0, 0, 0, 0], 0)), + runtime_id: "cultmesh-rudp-document-publisher".to_string(), + connection_id: CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, + connect_timeout: Duration::from_secs(3), + flush_timeout: Duration::from_secs(30), + poll_interval: Duration::from_millis(10), + resend_delay_ms: 50, + source_agent_id: None, + source_role: None, + tags: Vec::new(), + } + } +} + +impl Default for CultMeshNodeOptions { + fn default() -> Self { + Self { + runtime_id: "cultmesh-local".to_string(), + pull_on_start: true, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpEndpoint { + pub host: String, + pub port: u16, + pub addr: SocketAddr, +} + +pub struct CultMeshNode { + runtime_id: String, + cache: CultCache, + documents: CultNetDocumentRegistry, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultMeshStreamKind { + Audio, + Video, + Tensor, + Bytes, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultMeshStreamBodyTransport { + SharedMemory, + SharedD3d12Texture, + SharedD3d11Texture, + DmaBuf, + IoSurface, + AHardwareBuffer, + CultCachePage, + InlineBytes, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultMeshStreamAccess { + Read, + Write, + ReadWrite, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultMeshStreamCopyBudget { + ZeroCopyTarget, + OneCopyFallback, + OpaqueRuntime, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CultMeshStreamClock { + pub clock_domain_id: String, + pub source_id: Option, + pub sample_rate: i32, + pub offset_to_verse_time_ns: i64, + pub confidence: f64, + pub evidence_kind: Option, +} + +impl CultMeshStreamClock { + pub fn new(clock_domain_id: impl Into) -> Result { + let clock_domain_id = require_non_empty(clock_domain_id.into(), "clock_domain_id")?; + Ok(Self { + clock_domain_id, + source_id: None, + sample_rate: 0, + offset_to_verse_time_ns: 0, + confidence: 0.0, + evidence_kind: None, + }) + } + + pub fn source_id(mut self, source_id: impl Into) -> Result { + self.source_id = Some(require_non_empty(source_id.into(), "source_id")?); + Ok(self) + } + + pub fn confidence(mut self, confidence: f64) -> Self { + self.confidence = confidence; + self + } + + pub fn evidence_kind(mut self, evidence_kind: impl Into) -> Result { + self.evidence_kind = Some(require_non_empty(evidence_kind.into(), "evidence_kind")?); + Ok(self) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CultMeshStreamDescriptor { + pub stream_id: String, + pub verse_id: String, + pub owner_peer_id: String, + pub kind: CultMeshStreamKind, + pub clock: CultMeshStreamClock, + pub preferred_transports: Vec, + pub label: Option, + pub required_access: CultMeshStreamAccess, + pub max_in_flight_frames: u32, + pub metadata_schema_id: Option, +} + +impl CultMeshStreamDescriptor { + pub fn new( + stream_id: impl Into, + verse_id: impl Into, + owner_peer_id: impl Into, + kind: CultMeshStreamKind, + clock: CultMeshStreamClock, + preferred_transports: Vec, + ) -> Result { + if preferred_transports.is_empty() { + anyhow::bail!("preferred_transports must not be empty"); + } + Ok(Self { + stream_id: require_non_empty(stream_id.into(), "stream_id")?, + verse_id: require_non_empty(verse_id.into(), "verse_id")?, + owner_peer_id: require_non_empty(owner_peer_id.into(), "owner_peer_id")?, + kind, + clock, + preferred_transports, + label: None, + required_access: CultMeshStreamAccess::Read, + max_in_flight_frames: 0, + metadata_schema_id: None, + }) + } + + pub fn label(mut self, label: impl Into) -> Result { + self.label = Some(require_non_empty(label.into(), "label")?); + Ok(self) + } + + pub fn max_in_flight_frames(mut self, max_in_flight_frames: u32) -> Self { + self.max_in_flight_frames = max_in_flight_frames; + self + } + + pub fn metadata_schema_id(mut self, metadata_schema_id: impl Into) -> Result { + self.metadata_schema_id = Some(require_non_empty( + metadata_schema_id.into(), + "metadata_schema_id", + )?); + Ok(self) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshStreamConsumerProfile { + pub peer_id: String, + pub verse_id: String, + pub supported_transports: Vec, + pub accepted_kinds: Vec, + pub max_in_flight_frames: u32, +} + +impl CultMeshStreamConsumerProfile { + pub fn new( + peer_id: impl Into, + verse_id: impl Into, + supported_transports: Vec, + ) -> Result { + if supported_transports.is_empty() { + anyhow::bail!("supported_transports must not be empty"); + } + Ok(Self { + peer_id: require_non_empty(peer_id.into(), "peer_id")?, + verse_id: require_non_empty(verse_id.into(), "verse_id")?, + supported_transports, + accepted_kinds: Vec::new(), + max_in_flight_frames: 0, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshStreamNegotiation { + pub stream_id: String, + pub producer_peer_id: String, + pub consumer_peer_id: String, + pub transport: CultMeshStreamBodyTransport, + pub access: CultMeshStreamAccess, + pub max_in_flight_frames: u32, + pub copy_budget: CultMeshStreamCopyBudget, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshStreamFrameHandle { + pub stream_id: String, + pub sequence: u64, + pub timestamp_ns: i64, + pub duration_ns: i64, + pub transport: CultMeshStreamBodyTransport, + pub byte_length: usize, + pub resource_key: Option, + pub unavoidable_copy_count: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshSharedMemoryFrameRingStats { + pub stream_id: String, + pub slot_count: usize, + pub slot_byte_length: usize, + pub published_frames: u64, + pub dropped_frames: u64, + pub blocked_writes: u64, + pub latest_sequence: u64, + pub unavoidable_copy_count: u64, +} + +pub struct CultMeshFrameReadLease<'a> { + pub handle: CultMeshStreamFrameHandle, + bytes: &'a [u8], +} + +impl CultMeshFrameReadLease<'_> { + pub fn bytes(&self) -> &[u8] { + self.bytes + } +} + +#[derive(Clone, Debug)] +pub struct CultMeshSharedMemoryFrameRing { + stream_id: String, + slot_byte_length: usize, + slots: Vec)>>, + write_cursor: usize, + latest_slot: Option, + next_sequence: u64, + published_frames: u64, + dropped_frames: u64, + blocked_writes: u64, + unavoidable_copy_count: u64, +} + +impl CultMeshSharedMemoryFrameRing { + pub fn new( + stream_id: impl Into, + slot_count: usize, + slot_byte_length: usize, + ) -> Result { + let stream_id = require_non_empty(stream_id.into(), "stream_id")?; + if slot_count == 0 { + anyhow::bail!("slot_count must be greater than zero"); + } + if slot_byte_length == 0 { + anyhow::bail!("slot_byte_length must be greater than zero"); + } + Ok(Self { + stream_id, + slot_byte_length, + slots: vec![None; slot_count], + write_cursor: 0, + latest_slot: None, + next_sequence: 0, + published_frames: 0, + dropped_frames: 0, + blocked_writes: 0, + unavoidable_copy_count: 0, + }) + } + + pub fn try_publish_copy( + &mut self, + bytes: &[u8], + timestamp_ns: i64, + duration_ns: i64, + ) -> Result> { + if bytes.len() > self.slot_byte_length { + anyhow::bail!("frame byte length exceeds slot size"); + } + if self.slots.is_empty() { + self.blocked_writes += 1; + return Ok(None); + } + + let slot_index = self.write_cursor; + if self.slots[slot_index].is_some() { + self.dropped_frames += 1; + } + let handle = CultMeshStreamFrameHandle { + stream_id: self.stream_id.clone(), + sequence: self.next_sequence, + timestamp_ns, + duration_ns, + transport: CultMeshStreamBodyTransport::SharedMemory, + byte_length: bytes.len(), + resource_key: Some(format!("{}:slot:{slot_index}", self.stream_id)), + unavoidable_copy_count: 1, + }; + self.slots[slot_index] = Some((handle.clone(), bytes.to_vec())); + self.latest_slot = Some(slot_index); + self.write_cursor = (slot_index + 1) % self.slots.len(); + self.next_sequence = self.next_sequence.saturating_add(1); + self.published_frames = self.published_frames.saturating_add(1); + self.unavoidable_copy_count = self.unavoidable_copy_count.saturating_add(1); + Ok(Some(handle)) + } + + pub fn try_acquire_latest_read(&self) -> Option> { + let slot_index = self.latest_slot?; + let (handle, bytes) = self.slots[slot_index].as_ref()?; + Some(CultMeshFrameReadLease { + handle: handle.clone(), + bytes, + }) + } + + pub fn stats(&self) -> CultMeshSharedMemoryFrameRingStats { + let latest_sequence = self + .latest_slot + .and_then(|slot| self.slots[slot].as_ref().map(|(handle, _)| handle.sequence)) + .unwrap_or(0); + CultMeshSharedMemoryFrameRingStats { + stream_id: self.stream_id.clone(), + slot_count: self.slots.len(), + slot_byte_length: self.slot_byte_length, + published_frames: self.published_frames, + dropped_frames: self.dropped_frames, + blocked_writes: self.blocked_writes, + latest_sequence, + unavoidable_copy_count: self.unavoidable_copy_count, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct CultMeshStreamCatalog { + streams: BTreeMap, + latest_frames: BTreeMap, + rings: BTreeMap, +} + +impl CultMeshStreamCatalog { + pub fn declare(&mut self, stream: CultMeshStreamDescriptor) { + self.streams.insert(stream.stream_id.clone(), stream); + } + + pub fn get(&self, stream_id: &str) -> Option<&CultMeshStreamDescriptor> { + self.streams.get(stream_id) + } + + pub fn streams(&self) -> Vec<&CultMeshStreamDescriptor> { + self.streams.values().collect() + } + + pub fn find( + &self, + verse_id: &str, + kind: Option, + ) -> Vec<&CultMeshStreamDescriptor> { + self.streams + .values() + .filter(|stream| stream.verse_id == verse_id && kind.is_none_or(|k| stream.kind == k)) + .collect() + } + + pub fn negotiate( + &self, + stream_id: &str, + consumer: &CultMeshStreamConsumerProfile, + ) -> Result { + let stream = self + .streams + .get(stream_id) + .ok_or_else(|| anyhow::anyhow!("unknown CultMesh stream '{stream_id}'"))?; + if stream.verse_id != consumer.verse_id { + anyhow::bail!("stream and consumer must belong to the same Verse"); + } + if !consumer.accepted_kinds.is_empty() && !consumer.accepted_kinds.contains(&stream.kind) { + anyhow::bail!("consumer does not accept stream kind"); + } + let transport = stream + .preferred_transports + .iter() + .copied() + .find(|candidate| consumer.supported_transports.contains(candidate)) + .ok_or_else(|| { + anyhow::anyhow!("stream and consumer have no compatible body transport") + })?; + let producer_max = if stream.max_in_flight_frames == 0 { + u32::MAX + } else { + stream.max_in_flight_frames + }; + let consumer_max = if consumer.max_in_flight_frames == 0 { + u32::MAX + } else { + consumer.max_in_flight_frames + }; + Ok(CultMeshStreamNegotiation { + stream_id: stream.stream_id.clone(), + producer_peer_id: stream.owner_peer_id.clone(), + consumer_peer_id: consumer.peer_id.clone(), + transport, + access: stream.required_access, + max_in_flight_frames: producer_max.min(consumer_max), + copy_budget: copy_budget_for(transport), + }) + } + + pub fn create_shared_memory_ring( + &mut self, + stream_id: &str, + slot_count: usize, + slot_byte_length: usize, + ) -> Result<&mut CultMeshSharedMemoryFrameRing> { + if !self.streams.contains_key(stream_id) { + anyhow::bail!("unknown CultMesh stream '{stream_id}'"); + } + self.rings.insert( + stream_id.to_string(), + CultMeshSharedMemoryFrameRing::new(stream_id, slot_count, slot_byte_length)?, + ); + Ok(self + .rings + .get_mut(stream_id) + .expect("ring was inserted for stream")) + } + + pub fn ring(&self, stream_id: &str) -> Option<&CultMeshSharedMemoryFrameRing> { + self.rings.get(stream_id) + } + + pub fn ring_mut(&mut self, stream_id: &str) -> Option<&mut CultMeshSharedMemoryFrameRing> { + self.rings.get_mut(stream_id) + } + + pub fn publish_frame(&mut self, handle: CultMeshStreamFrameHandle) -> Result<()> { + if !self.streams.contains_key(&handle.stream_id) { + anyhow::bail!("unknown CultMesh stream '{}'", handle.stream_id); + } + self.latest_frames.insert(handle.stream_id.clone(), handle); + Ok(()) + } + + pub fn latest_frame(&self, stream_id: &str) -> Option<&CultMeshStreamFrameHandle> { + self.latest_frames.get(stream_id) + } +} + +fn copy_budget_for(transport: CultMeshStreamBodyTransport) -> CultMeshStreamCopyBudget { + match transport { + CultMeshStreamBodyTransport::SharedMemory + | CultMeshStreamBodyTransport::SharedD3d12Texture + | CultMeshStreamBodyTransport::SharedD3d11Texture + | CultMeshStreamBodyTransport::DmaBuf + | CultMeshStreamBodyTransport::IoSurface + | CultMeshStreamBodyTransport::AHardwareBuffer => CultMeshStreamCopyBudget::ZeroCopyTarget, + CultMeshStreamBodyTransport::CultCachePage => CultMeshStreamCopyBudget::OneCopyFallback, + CultMeshStreamBodyTransport::InlineBytes => CultMeshStreamCopyBudget::OpaqueRuntime, + } +} + +fn require_non_empty(value: String, name: &str) -> Result { + if value.trim().is_empty() { + anyhow::bail!("{name} must be non-empty"); + } + Ok(value) +} + +impl CultMeshNode { + pub fn runtime_id(&self) -> &str { + &self.runtime_id + } + + pub fn cache(&self) -> &CultCache { + &self.cache + } + + pub fn documents(&self) -> &CultNetDocumentRegistry { + &self.documents + } + + pub fn get(&self, key: &str) -> Result> { + self.cache.get::(key) + } + + pub fn get_required(&self, key: &str) -> Result { + self.cache.get_required::(key) + } + + pub fn put(&mut self, key: impl Into, value: &T) -> Result { + self.cache.put(key, value) + } + + pub fn pull_all_backing_stores(&mut self) -> Result<()> { + self.cache.pull_all_backing_stores() + } + + pub fn delete(&mut self, key: &str) -> Result { + self.cache.delete::(key) + } + + pub fn flush(&mut self) -> Result<()> { + Ok(()) + } + + pub fn publish_document_to_rudp_catalog( + &self, + key: impl Into, + value: &T, + options: CultMeshRudpDocumentPublishOptions, + ) -> Result<()> + where + T: DatabaseEntry + Serialize, + { + let message = self.create_rudp_document_message(key, value, &options)?; + publish_cultnet_messages_to_rudp_catalog(std::slice::from_ref(&message), options) + } + + pub fn create_rudp_document_message( + &self, + key: impl Into, + value: &T, + options: &CultMeshRudpDocumentPublishOptions, + ) -> Result + where + T: DatabaseEntry + Serialize, + { + let key = key.into(); + self.documents.create_raw_document_put_message( + format!("{}:{}:{}", options.runtime_id, T::TYPE, key), + key, + value, + CultNetDocumentPutOptions { + source_runtime_id: Some(options.runtime_id.clone()), + source_agent_id: options.source_agent_id.clone(), + source_role: options.source_role.clone(), + tags: if options.tags.is_empty() { + None + } else { + Some(options.tags.clone()) + }, + ..CultNetDocumentPutOptions::default() + }, + ) + } + + pub fn pull_rudp_catalog_snapshot( + &mut self, + options: CultMeshRudpSnapshotOptions, + ) -> Result { + let response = request_raw_snapshot_from_rudp_catalog(options)?; + let cultnet_rs::CultNetMessage::SnapshotResponseRaw { documents, .. } = response else { + anyhow::bail!("expected cultnet.snapshot_response_raw.v0"); + }; + let mut applied = 0usize; + for document in documents { + let Some(document_type) = + registered_document_type_for_schema(&self.documents, &document.schema_id) + else { + continue; + }; + self.cache.put_raw_envelope(CultCacheEnvelope { + key: document.record_key, + r#type: document_type, + payload: document.payload, + stored_at: document.stored_at, + schema_id: Some(document.schema_id), + })?; + applied += 1; + } + Ok(applied) + } +} + +fn registered_document_type_for_schema( + documents: &CultNetDocumentRegistry, + schema_id: &str, +) -> Option { + let normalized = strip_schema_version_suffix(schema_id); + if normalized != schema_id { + return Some(normalized.to_string()); + } + documents + .binding_by_schema_id(schema_id) + .map(|binding| binding.document_type.clone()) + .or_else(|| documents.binding(schema_id).map(|_| schema_id.to_string())) +} + +fn strip_schema_version_suffix(value: &str) -> &str { + let Some((prefix, suffix)) = value.rsplit_once(".v") else { + return value; + }; + if suffix.chars().all(|character| character.is_ascii_digit()) { + prefix + } else { + value + } +} + +fn request_raw_snapshot_from_rudp_catalog( + options: CultMeshRudpSnapshotOptions, +) -> Result { + if options.runtime_id.trim().is_empty() { + anyhow::bail!("runtime_id must be non-empty"); + } + let bind_addr = if options.target.is_ipv4() { + SocketAddr::from(([0, 0, 0, 0], 0)) + } else { + SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)) + }; + let socket = UdpSocket::bind(bind_addr)?; + socket.set_read_timeout(Some(options.poll_interval))?; + let mut client = + CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + runtime_id: options.runtime_id.clone(), + socket, + mode: cultnet_rs::CultNetRudpSocketMode::Client, + remote_addr: Some(options.target), + connection_id: options.connection_id, + initial_sequence: 1, + resend_delay_ms: options.resend_delay_ms, + transport_id: Some("cultmesh-rudp-snapshot-client".to_string()), + max_payload_bytes: None, + max_fragment_bytes: Some(1200), + max_pending_reliable_packets: None, + reconnect_policy: None, + })?; + client.connect(Vec::new())?; + let connect_deadline = Instant::now() + options.connect_timeout; + while !client.connected() && Instant::now() < connect_deadline { + let _ = client.receive_once()?; + client.poll_resends()?; + thread::sleep(options.poll_interval); + } + if !client.connected() { + anyhow::bail!( + "timed out connecting CultMesh RUDP snapshot client {} to {}", + options.runtime_id, + options.target + ); + } + + let message_id = format!("{}:snapshot:{}", options.runtime_id, unix_millis()); + let request = cultnet_rs::CultNetMessage::SnapshotRequest { + message_id: message_id.clone(), + schema_ids: options.schema_ids, + record_keys: options.record_keys, + }; + let payload = encode_cultnet_message_to_vec(&request, CultNetWireContract::CultNetSchemaV0)?; + client.send("schema", payload)?; + let response_deadline = Instant::now() + options.response_timeout; + let mut snapshot_response = None; + while Instant::now() < response_deadline { + if let Some(frame) = client.receive_once()? { + if frame.channel_id == "schema" { + let message = decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + )?; + match &message { + cultnet_rs::CultNetMessage::SnapshotResponseRaw { + message_id: received, + .. + } if received == &message_id => { + snapshot_response = Some(message); + break; + } + cultnet_rs::CultNetMessage::Error { error } => { + let _ = client.disconnect(b"snapshot-error".to_vec()); + anyhow::bail!("CultMesh RUDP snapshot failed: {error}"); + } + _ => {} + } + } + } + client.poll_resends()?; + thread::sleep(options.poll_interval); + } + if let Some(message) = snapshot_response { + let _ = client.disconnect(b"snapshot-complete".to_vec()); + return Ok(message); + } + let _ = client.disconnect(b"snapshot-timeout".to_vec()); + anyhow::bail!( + "timed out waiting for CultMesh RUDP snapshot from {}", + options.target + ) +} + +fn unix_millis() -> u128 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() +} + +pub fn publish_cultnet_messages_to_rudp_catalog( + messages: &[cultnet_rs::CultNetMessage], + options: CultMeshRudpDocumentPublishOptions, +) -> Result<()> { + if options.runtime_id.trim().is_empty() { + anyhow::bail!("runtime_id must be non-empty"); + } + let bind_addr = if options.target.is_ipv4() { + SocketAddr::from(([0, 0, 0, 0], 0)) + } else { + SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)) + }; + let socket = UdpSocket::bind(bind_addr)?; + socket.set_read_timeout(Some(options.poll_interval))?; + let mut client = + CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + runtime_id: options.runtime_id.clone(), + socket, + mode: cultnet_rs::CultNetRudpSocketMode::Client, + remote_addr: Some(options.target), + connection_id: options.connection_id, + initial_sequence: 1, + resend_delay_ms: options.resend_delay_ms, + transport_id: Some("cultmesh-rudp-document-publisher".to_string()), + max_payload_bytes: None, + max_fragment_bytes: Some(1200), + max_pending_reliable_packets: None, + reconnect_policy: None, + })?; + client.connect(Vec::new())?; + let connect_deadline = Instant::now() + options.connect_timeout; + while !client.connected() && Instant::now() < connect_deadline { + let _ = client.receive_once()?; + client.poll_resends()?; + thread::sleep(options.poll_interval); + } + if !client.connected() { + anyhow::bail!( + "timed out connecting CultMesh RUDP document publisher {} to {}", + options.runtime_id, + options.target + ); + } + + for message in messages { + let payload = encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?; + client.send("schema", payload)?; + } + client.flush_reliable(options.flush_timeout) +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct CultMesh; + +impl CultMesh { + pub fn resolve_rudp_endpoint(endpoint: &str) -> Result { + let text = require_non_empty(endpoint.trim().to_string(), "endpoint")?; + if text.starts_with("rudp://") { + return parse_rudp_socket_addr(&text); + } + if text.starts_with("cultmesh://") { + let resolved = default_cultmesh_rudp_endpoint_resolver(&text)?.ok_or_else(|| { + anyhow::anyhow!("CultMesh URI {text} did not resolve to a RUDP endpoint") + })?; + return parse_rudp_socket_addr(&resolved); + } + parse_socket_addr(&text) + } + + pub fn create_node( + store_path: impl AsRef, + documents: D, + options: CultMeshNodeOptions, + ) -> Result + where + D: CultMeshDocumentSet, + { + let mut cache = CultCache::new(); + documents.register_cache(&mut cache)?; + cache + .add_generic_backing_store(SingleFileMessagePackBackingStore::new(store_path.as_ref())); + if options.pull_on_start { + cache.pull_all_backing_stores()?; + } + + let mut registry = CultNetDocumentRegistry::new(); + documents.register_documents(&mut registry)?; + + Ok(CultMeshNode { + runtime_id: options.runtime_id, + cache, + documents: registry, + }) + } + + pub fn start_node( + store_path: impl AsRef, + documents: D, + options: CultMeshNodeOptions, + ) -> Result + where + D: CultMeshDocumentSet, + { + Self::create_node(store_path, documents, options) + } + + pub fn create_stream_catalog() -> CultMeshStreamCatalog { + CultMeshStreamCatalog::default() + } +} + +pub fn create_node( + store_path: impl AsRef, + documents: D, + options: CultMeshNodeOptions, +) -> Result +where + D: CultMeshDocumentSet, +{ + CultMesh::create_node(store_path, documents, options) +} + +pub fn start_node( + store_path: impl AsRef, + documents: D, + options: CultMeshNodeOptions, +) -> Result +where + D: CultMeshDocumentSet, +{ + CultMesh::start_node(store_path, documents, options) +} + +pub fn create_stream_catalog() -> CultMeshStreamCatalog { + CultMesh::create_stream_catalog() +} + +pub fn resolve_rudp_endpoint(endpoint: &str) -> Result { + CultMesh::resolve_rudp_endpoint(endpoint) +} + +fn parse_rudp_socket_addr(value: &str) -> Result { + let text = value.trim().strip_prefix("rudp://").unwrap_or(value.trim()); + parse_socket_addr(text) +} + +fn parse_socket_addr(value: &str) -> Result { + value + .parse() + .with_context(|| format!("RUDP endpoint must be a socket address, got {value:?}")) +} + +fn default_cultmesh_rudp_endpoint_resolver(uri: &str) -> Result> { + let authority = cultmesh_uri_authority(uri)?; + let slug = authority + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() { + ch.to_ascii_uppercase() + } else { + '_' + } + }) + .collect::() + .trim_matches('_') + .to_string(); + if slug.is_empty() { + return Ok(None); + } + + for key in [ + format!("CULTMESH_URI_{slug}_RUDP"), + format!("{slug}_CULTMESH_RUDP_ENDPOINT"), + ] { + if let Ok(value) = env::var(&key) { + let text = value.trim(); + if !text.is_empty() { + return Ok(Some(if text.starts_with("rudp://") { + text.to_string() + } else { + format!("rudp://{text}") + })); + } + } + } + Ok(None) +} + +fn cultmesh_uri_authority(uri: &str) -> Result { + let rest = uri + .strip_prefix("cultmesh://") + .ok_or_else(|| anyhow::anyhow!("CultMesh URI must start with cultmesh://"))?; + let authority = rest.split(['/', '?', '#']).next().unwrap_or("").trim(); + if authority.is_empty() { + anyhow::bail!("CultMesh URI must include an authority"); + } + Ok(authority.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[derive(Clone, Debug, PartialEq, Eq, cultcache_rs::DatabaseEntry)] + #[cultcache(type = "cultmesh.test.note", schema = "CultMeshTestNote")] + struct Note { + #[cultcache(key = 0)] + body: String, + } + + cultmesh_documents!(TestDocuments { + Note => "cultmesh.test.note.v0", + }); + + #[test] + fn node_round_trips_registered_documents() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cultmesh.cc"); + let note = Note { + body: "blessed circuit".to_string(), + }; + + let mut node = CultMesh::create_node( + &store_path, + TestDocuments, + CultMeshNodeOptions { + runtime_id: "test-runtime".to_string(), + ..CultMeshNodeOptions::default() + }, + )?; + assert_eq!(node.runtime_id(), "test-runtime"); + node.put("note", ¬e)?; + node.flush()?; + + let reloaded = CultMesh::create_node(&store_path, TestDocuments, Default::default())?; + assert_eq!(reloaded.get_required::("note")?, note); + assert!( + reloaded + .documents() + .binding_by_schema_id("cultmesh.test.note.v0") + .is_some() + ); + Ok(()) + } + + #[test] + fn node_publishes_registered_document_to_rudp_catalog() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cultmesh.cc"); + let note = Note { + body: "respect odin once".to_string(), + }; + let node = CultMesh::create_node( + &store_path, + TestDocuments, + CultMeshNodeOptions { + runtime_id: "muninn-test".to_string(), + ..CultMeshNodeOptions::default() + }, + )?; + + let socket = UdpSocket::bind("127.0.0.1:0")?; + socket.set_read_timeout(Some(Duration::from_millis(10)))?; + let target = socket.local_addr()?; + let (sender, receiver) = std::sync::mpsc::channel(); + let server = thread::spawn(move || -> Result<()> { + let mut server = CultNetRudpSocketTransportConnection::new( + CultNetRudpSocketTransportOptions::server( + "odin-test-catalog", + socket, + CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, + ), + )?; + let deadline = Instant::now() + Duration::from_secs(2); + while Instant::now() < deadline { + if let Some(frame) = server.receive_once()? { + let message = cultnet_rs::decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + )?; + if let cultnet_rs::CultNetMessage::DocumentPutRaw { document, .. } = message { + sender + .send(( + document.schema_id, + document.record_key, + document.source_runtime_id, + )) + .ok(); + return Ok(()); + } + } + server.poll_resends()?; + } + anyhow::bail!("timed out waiting for RUDP document put") + }); + + node.publish_document_to_rudp_catalog( + "note", + ¬e, + CultMeshRudpDocumentPublishOptions::odin(target, "muninn-test"), + )?; + let received = receiver.recv_timeout(Duration::from_secs(2))?; + server.join().expect("server thread should not panic")?; + + assert_eq!(received.0, "cultmesh.test.note.v0"); + assert_eq!(received.1, "note"); + assert_eq!(received.2.as_deref(), Some("muninn-test")); + Ok(()) + } + + #[test] + fn large_rudp_publication_waits_for_acknowledged_delivery() -> Result<()> { + let temp = tempfile::tempdir()?; + let node = CultMesh::create_node( + temp.path().join("cultmesh.cc"), + TestDocuments, + CultMeshNodeOptions { + runtime_id: "large-publisher-test".into(), + ..CultMeshNodeOptions::default() + }, + )?; + let socket = UdpSocket::bind("127.0.0.1:0")?; + socket.set_read_timeout(Some(Duration::from_millis(10)))?; + let target = socket.local_addr()?; + let options = CultMeshRudpDocumentPublishOptions::odin(target, "large-publisher-test"); + let messages = [ + node.create_rudp_document_message( + "large-a", + &Note { + body: "a".repeat(256 * 1024), + }, + &options, + )?, + node.create_rudp_document_message( + "large-b", + &Note { + body: "b".repeat(256 * 1024), + }, + &options, + )?, + ]; + + let server = thread::spawn(move || -> Result> { + thread::sleep(Duration::from_millis(500)); + let mut server = CultNetRudpSocketTransportConnection::new( + CultNetRudpSocketTransportOptions::server( + "delayed-odin-test", + socket, + CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, + ), + )?; + let deadline = Instant::now() + Duration::from_secs(5); + let mut record_keys = Vec::new(); + while Instant::now() < deadline && record_keys.len() < 2 { + if let Some(frame) = server.receive_once()? { + let message = decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + )?; + if let cultnet_rs::CultNetMessage::DocumentPutRaw { document, .. } = message { + record_keys.push(document.record_key); + } + } + server.poll_resends()?; + } + let linger_deadline = Instant::now() + Duration::from_millis(250); + while Instant::now() < linger_deadline { + let _ = server.receive_once()?; + server.poll_resends()?; + } + if record_keys.len() != 2 { + anyhow::bail!("timed out waiting for both large RUDP documents"); + } + Ok(record_keys) + }); + + publish_cultnet_messages_to_rudp_catalog(&messages, options)?; + assert_eq!( + server.join().expect("server thread should not panic")?, + vec!["large-a", "large-b"] + ); + Ok(()) + } + + #[test] + fn resolves_rudp_endpoint_from_cultmesh_uri_bootstrap_env() -> Result<()> { + let previous = env::var("CULTMESH_URI_ODIN_RUDP").ok(); + unsafe { + env::set_var("CULTMESH_URI_ODIN_RUDP", "127.0.0.1:17871"); + } + let result = CultMesh::resolve_rudp_endpoint("cultmesh://odin/rendezvous/provider-catalog"); + unsafe { + if let Some(value) = previous { + env::set_var("CULTMESH_URI_ODIN_RUDP", value); + } else { + env::remove_var("CULTMESH_URI_ODIN_RUDP"); + } + } + + assert_eq!(result?, "127.0.0.1:17871".parse()?); + Ok(()) + } + + #[test] + fn rejects_unresolved_cultmesh_rudp_endpoint() { + let previous = env::var("CULTMESH_URI_MISSING_ODIN_RUDP").ok(); + unsafe { + env::remove_var("CULTMESH_URI_MISSING_ODIN_RUDP"); + } + let error = + CultMesh::resolve_rudp_endpoint("cultmesh://missing-odin/rendezvous/provider-catalog") + .expect_err("unresolved CultMesh URI should fail"); + unsafe { + if let Some(value) = previous { + env::set_var("CULTMESH_URI_MISSING_ODIN_RUDP", value); + } + } + assert!(error.to_string().contains("did not resolve")); + } + + #[test] + fn raw_snapshot_application_uses_registered_document_type() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("cultmesh.cc"); + let mut node = CultMesh::create_node( + &store_path, + TestDocuments, + CultMeshNodeOptions { + runtime_id: "snapshot-apply-test".to_string(), + ..CultMeshNodeOptions::default() + }, + )?; + let payload = rmp_serde::to_vec(&Note { + body: "catalog truth".to_string(), + })?; + + node.cache + .put_raw_envelope(cultcache_rs::CultCacheEnvelope { + key: "note".to_string(), + r#type: node + .documents + .binding_by_schema_id("cultmesh.test.note.v0") + .expect("test binding") + .document_type + .clone(), + payload, + stored_at: "2026-07-03T00:00:00Z".to_string(), + schema_id: Some("cultmesh.test.note.v0".to_string()), + })?; + node.flush()?; + + let reloaded = CultMesh::create_node(&store_path, TestDocuments, Default::default())?; + assert_eq!(reloaded.get_required::("note")?.body, "catalog truth"); + assert_eq!(reloaded.cache().snapshot()[0].r#type, Note::TYPE); + Ok(()) + } + + #[test] + fn stream_catalog_negotiates_and_publishes_latest_shared_memory_frame() -> Result<()> { + let mut catalog = CultMesh::create_stream_catalog(); + let stream = CultMeshStreamDescriptor::new( + "muninn:nightwing:move-evidence", + "mimir-live", + "muninn:nightwing", + CultMeshStreamKind::Bytes, + CultMeshStreamClock::new("muninn:nightwing:clock")? + .source_id("muninn:nightwing:move-evidence")? + .confidence(1.0) + .evidence_kind("muninn-move-evidence")?, + vec![ + CultMeshStreamBodyTransport::SharedMemory, + CultMeshStreamBodyTransport::CultCachePage, + ], + )? + .label("Muninn Move evidence")? + .max_in_flight_frames(4) + .metadata_schema_id("mimir.muninn_move_evidence_stream_frame.v1")?; + catalog.declare(stream); + + let consumer = CultMeshStreamConsumerProfile::new( + "mimir:starfire", + "mimir-live", + vec![CultMeshStreamBodyTransport::SharedMemory], + )?; + let negotiation = catalog.negotiate("muninn:nightwing:move-evidence", &consumer)?; + + assert_eq!( + negotiation.transport, + CultMeshStreamBodyTransport::SharedMemory + ); + assert_eq!( + negotiation.copy_budget, + CultMeshStreamCopyBudget::ZeroCopyTarget + ); + + let handle = { + let ring = + catalog.create_shared_memory_ring("muninn:nightwing:move-evidence", 4, 128)?; + ring.try_publish_copy(&[1, 2, 3], 42, 0)? + .expect("frame should publish") + }; + catalog.publish_frame(handle.clone())?; + + assert_eq!( + catalog.latest_frame("muninn:nightwing:move-evidence"), + Some(&handle) + ); + let lease = catalog + .ring("muninn:nightwing:move-evidence") + .and_then(CultMeshSharedMemoryFrameRing::try_acquire_latest_read) + .expect("latest frame should be readable"); + assert_eq!(lease.handle.sequence, 0); + assert_eq!(lease.bytes(), &[1, 2, 3]); + Ok(()) + } +} From 9f822b75780225f13ca239a0670d8666ab975e8b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 05:15:51 +0200 Subject: [PATCH 047/203] Expose exact RUDP acknowledgement receipts Ported from GameCult/cultnet-rs 9dcb88d into CultLib, where the wire contract belongs. Ghostlight's committed idunn_health.rs imports CultNetRudpReliableSendStatus to hold an Idunn process write lease current, and the symbol existed only in the standalone lineage, so nothing could consume this contract from its canonical home. Three reconciliations were needed because both lineages reworked RUDP independently since the 014f998 copy-in: - reset_peer_state: kept this lineage's version, which also resets the sequence and fragment counters and clears queued_reliable, and folded in only the imported change that matters -- rotating session_scope so a peer reset invalidates outstanding receipts. The imported body also cleared highest_delivered_sequenced_by_channel, a field the sequence-domain work renamed away here. - channel_send_options is a free function in this lineage, not a method. - The imported guard rejected reliable channels with an expiry. This lineage has no reliable-expiry concept at all -- the only mentions of expiry in the file arrived with this patch -- so every reliable channel is already non-expiring and the guard reduces to the reliability check. A comment marks where to restore the expiry half if expiry is ever added. 94 tests pass. The one rudp_server_hub failure is pre-existing and reproduces on clean origin/main. The other two standalone RUDP commits are deliberately not ported. Bounding session memory and rejecting hostile sequence rollover rebuild the receive path -- stale-sequence rejection, per-channel tracking caps, reassembly and ordered-buffer byte accounting -- in the same code this lineage reworked with sequence domains and ACK horizons. Merging them changes wire-observable drop and reassembly behavior, which needs the cross-runtime interop harness to validate, not a local build. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/rudp.rs | 111 +++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index 25382264..674b2996 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -6,7 +6,9 @@ use std::collections::VecDeque; use std::io::ErrorKind; use std::net::SocketAddr; use std::net::UdpSocket; +use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use uuid::Uuid; use crate::CultNetMessage; use crate::CultNetReconnectController; @@ -105,6 +107,28 @@ pub struct CultNetRudpSendOptions { pub now_ms: u64, } +/// Identifies every transport packet belonging to one non-expiring reliable +/// send. Receipts are local to the session that issued them and cannot survive +/// a peer reset. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultNetRudpReliableSendReceipt { + session_scope: Uuid, + sequences: Vec, +} + +impl CultNetRudpReliableSendReceipt { + pub fn packet_sequences(&self) -> &[u32] { + &self.sequences + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultNetRudpReliableSendStatus { + Pending, + Acknowledged, + Invalidated, +} + #[derive(Clone, Debug, PartialEq, Eq)] struct PendingReliablePacket { packet: CultNetRudpPacket, @@ -127,6 +151,7 @@ struct FragmentBuffer { } pub struct CultNetRudpSession { + session_scope: Uuid, connection_id: u32, resend_delay_ms: u64, max_pending_reliable_packets: Option, @@ -149,6 +174,7 @@ pub struct CultNetRudpSession { impl CultNetRudpSession { pub fn new(options: CultNetRudpSessionOptions) -> Self { Self { + session_scope: Uuid::new_v4(), connection_id: options.connection_id, resend_delay_ms: options.resend_delay_ms, max_pending_reliable_packets: options.max_pending_reliable_packets, @@ -181,6 +207,11 @@ impl CultNetRudpSession { self.connected } + pub fn assume_connected(&mut self, now_ms: u64) { + self.connected = true; + self.last_received_at_ms = Some(now_ms); + } + pub fn pending_reliable_sequences(&self) -> Vec { self.pending_reliable.keys().copied().collect() } @@ -202,6 +233,39 @@ impl CultNetRudpSession { Some(pending.packet.clone()) } + fn reliable_send_status( + &self, + receipt: &CultNetRudpReliableSendReceipt, + ) -> CultNetRudpReliableSendStatus { + if receipt.session_scope != self.session_scope { + return CultNetRudpReliableSendStatus::Invalidated; + } + if receipt + .sequences + .iter() + .any(|sequence| self.pending_reliable.contains_key(sequence)) + { + CultNetRudpReliableSendStatus::Pending + } else { + CultNetRudpReliableSendStatus::Acknowledged + } + } + + fn non_expiring_reliable_receipt( + &self, + packets: &[CultNetRudpPacket], + ) -> Result { + if packets.is_empty() || packets.iter().any(|packet| !packet.reliable) { + return Err(anyhow!( + "RUDP acknowledgement receipts require a non-empty reliable packet set" + )); + } + Ok(CultNetRudpReliableSendReceipt { + session_scope: self.session_scope, + sequences: packets.iter().map(|packet| packet.sequence).collect(), + }) + } + pub fn last_received_at_ms(&self) -> Option { self.last_received_at_ms } @@ -224,6 +288,7 @@ impl CultNetRudpSession { } pub fn reset_peer_state(&mut self) { + self.session_scope = Uuid::new_v4(); self.next_sequence = self.initial_sequence; self.next_fragment_id = 1; self.connected = false; @@ -1508,13 +1573,53 @@ impl CultNetRudpSocketTransportConnection { let packets = self.session .send_many(channel_id, payload, options, self.max_fragment_bytes)?; - for packet in packets { - self.send_packet(&packet)?; - } + self.send_data_packets(&packets)?; self.stats.frames_sent += 1; Ok(()) } + /// Sends one frame on a reliable channel whose packets cannot expire and + /// returns the transport receipt used to observe its exact acknowledgement. + pub fn send_reliable( + &mut self, + channel_id: &str, + payload: Vec, + ) -> Result { + let options = channel_send_options(channel_id, now_ms()); + // This lineage has no reliable-expiry concept, so every reliable channel is + // already non-expiring. Restore the expiry half of this guard if one is added. + if !options.reliable { + return Err(anyhow!( + "RUDP acknowledgement receipts require a non-expiring reliable channel" + )); + } + let packets = + self.session + .send_many(channel_id, payload, options, self.max_fragment_bytes)?; + self.send_data_packets(&packets)?; + let receipt = self.session.non_expiring_reliable_receipt(&packets)?; + self.stats.frames_sent += 1; + Ok(receipt) + } + + pub fn reliable_send_status( + &self, + receipt: &CultNetRudpReliableSendReceipt, + ) -> CultNetRudpReliableSendStatus { + self.session.reliable_send_status(receipt) + } + + fn send_data_packets(&mut self, packets: &[CultNetRudpPacket]) -> Result<()> { + let fragmented = packets.len() > 1; + for (index, packet) in packets.iter().enumerate() { + self.send_packet(packet)?; + if fragmented && (index + 1) % 16 == 0 { + thread::sleep(Duration::from_millis(1)); + } + } + Ok(()) + } + pub fn send_schema_message(&mut self, message: &CultNetMessage) -> Result<()> { let payload = encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?; self.send("schema", payload) From 62ac7e348394346e4873a45d4f70c754980ce5c5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 23:14:45 +0200 Subject: [PATCH 048/203] Record the unported RUDP hardening as owed work Exact acknowledgement receipts landed; bounded session memory and hostile sequence rollover rejection did not. Until they do, this runtime accepts sequence and reassembly input the retiring lineage rejects, so its repository cannot be deleted yet. Co-Authored-By: Claude Opus 5 --- docs/runtime-parity-scope.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/runtime-parity-scope.md b/docs/runtime-parity-scope.md index 681f1ad3..9d351ac2 100644 --- a/docs/runtime-parity-scope.md +++ b/docs/runtime-parity-scope.md @@ -89,6 +89,14 @@ server body, is deliberately not made. outside any conformance harness, and its stream, negotiation, and shared-memory frame-ring surfaces have never been checked against the browser, Kotlin, and TypeScript CultMesh bodies they must interoperate with. Runtime-local tests pass; that is not a parity claim. +- Finish porting RUDP hardening from the retiring GameCult/cultnet-rs lineage. Its exact + acknowledgement receipts are in. Two commits are not: bounded session memory (4a54f46) and + hostile sequence rollover rejection (91609ff). They rebuild the receive path -- stale-sequence + rejection, per-channel tracking caps, reassembly and ordered-buffer byte accounting -- in the + same code this lineage reworked with sequence domains and ACK horizons, and they change when + packets are dropped and reassembly refused. That is wire-observable, so the interop lanes are + the gate, not a local build. Until they land, this runtime accepts sequence and reassembly + input the other lineage rejects. Do not delete that repository before this is resolved. - Keep performance claims evidence-bound: C# owns SoA; Python has benchmark comparison gates; other runtimes should advertise measured native hot paths only when those paths exist. From 4c0dc07ba5b19c85d3942bab0163df3ef6c5b2a6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:01:17 +0200 Subject: [PATCH 049/203] Make the Rust suite green and stop silently ignoring packages exact_reliable_acks_clear_more_than_one_ack_window_of_fragments has been failing on main. It was not a defect: send_many returned 32 packets where the test expected 40, and 32 is exactly CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS. The reliable send window landed and this test kept asserting the pre-window behaviour, so a test whose name is about crossing an ack window had stopped crossing one. It now asserts that a window's worth leaves immediately, that the remaining eight fragments queue, and that acknowledgements promote them until every fragment is delivered -- the same loop packages/cultnet-rs/tests /cultnet.rs already uses for this contract. All three Rust crates are green: 46, 95 and 7. The NuGet packages rule hides every language package until one is named in the re-include list, and cultmath, cultmesh-browser and cultmesh-kotlin were never added. Nothing is currently lost -- every source file in them is tracked -- but any new file added to those three would have been dropped in silence. cultmesh-rs hit this on the way in. Co-Authored-By: Claude Opus 5 --- .gitignore | 6 +++++ packages/cultnet-rs/tests/rudp_server_hub.rs | 27 ++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ce48c2dc..656fafa0 100644 --- a/.gitignore +++ b/.gitignore @@ -309,6 +309,12 @@ node_modules/ !packages/cultnet-rs/** !packages/cultmesh-rs/ !packages/cultmesh-rs/** +!packages/cultmath/ +!packages/cultmath/** +!packages/cultmesh-browser/ +!packages/cultmesh-browser/** +!packages/cultmesh-kotlin/ +!packages/cultmesh-kotlin/** packages/**/node_modules/ packages/**/dist/ packages/**/dist-test/ diff --git a/packages/cultnet-rs/tests/rudp_server_hub.rs b/packages/cultnet-rs/tests/rudp_server_hub.rs index a8a2c114..cdc6e390 100644 --- a/packages/cultnet-rs/tests/rudp_server_hub.rs +++ b/packages/cultnet-rs/tests/rudp_server_hub.rs @@ -1,3 +1,4 @@ +use std::collections::VecDeque; use std::net::UdpSocket; use std::time::Duration; @@ -317,16 +318,32 @@ fn exact_reliable_acks_clear_more_than_one_ack_window_of_fragments() -> Result<( }, Some(1), )?; - assert_eq!(packets.len(), 40); + // Forty one-byte fragments cannot leave inside one reliable send window. The + // window's worth goes out now and the rest queue until acknowledgements free + // space, which is the crossing this test exists to prove. + assert_eq!(packets.len(), CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS); + assert_eq!( + sender.queued_reliable_packet_count(), + 40 - CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS + ); + + let mut wire = VecDeque::from(packets); let mut delivered = Vec::new(); - for packet in &packets { - let result = receiver.receive(packet, 2)?; - delivered.extend(result.delivered); - sender.receive(&receiver.create_ack_for(packet.sequence), 3)?; + while !wire.is_empty() { + let admitted = wire.len(); + for _ in 0..admitted { + let packet = wire.pop_front().expect("admitted packet remains queued"); + let received = receiver.receive(&packet, 2)?; + delivered.extend(received.delivered); + } + let acknowledged = sender.receive(&receiver.create_ack(), 3)?; + wire.extend(acknowledged.ready_to_send); } + assert_eq!(delivered.len(), 1); assert_eq!(delivered[0].payload, vec![0x5a; 40]); assert!(sender.pending_reliable_sequences().is_empty()); + assert_eq!(sender.outstanding_reliable_packet_count(), 0); Ok(()) } From ca9895c06ac852a17cdbcef8a8cecfb49fba1100 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 3 Sep 2026 22:22:36 +0200 Subject: [PATCH 050/203] Wire AGENTS.md into Claude Code memory Claude Code reads CLAUDE.md, not AGENTS.md, so this repo's doctrine was invisible to it. This one-line import makes the existing AGENTS.md load without duplicating or renaming it. Codex is unaffected. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..640c1a14 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +Repository doctrine for this project lives in AGENTS.md, shared with Codex. + +@AGENTS.md From 0736ae4824e7effab73cdd3619c0bcea29caad5d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 17:16:57 +0200 Subject: [PATCH 051/203] Separate RUDP reliable and lossy sequence domains --- packages/cultnet-rs/src/rudp.rs | 65 ++++++--- packages/cultnet-rs/tests/cultnet.rs | 127 ++++++++++++++++++ packages/cultnet-ts/src/rudp.ts | 55 +++++--- packages/cultnet-ts/test/cultnet.test.ts | 47 +++++++ src/GameCult.Networking/CultNetTransport.cs | 68 ++++++---- .../NetworkingTests.cs | 118 +++++++++------- 6 files changed, 372 insertions(+), 108 deletions(-) diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index 674b2996..ab16bf0d 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -159,11 +159,13 @@ pub struct CultNetRudpSession { max_pending_fragment_sets: usize, initial_sequence: u32, next_sequence: u32, + next_sequenced_by_channel: BTreeMap, next_fragment_id: u16, connected: bool, last_received_at_ms: Option, highest_received_sequence: Option, received_sequences: BTreeSet, + latest_sequenced_by_channel: BTreeMap, pending_reliable: BTreeMap, queued_reliable: VecDeque, ordered_next_sequence_by_channel: BTreeMap, @@ -182,11 +184,13 @@ impl CultNetRudpSession { max_pending_fragment_sets: 64, initial_sequence: options.initial_sequence, next_sequence: options.initial_sequence, + next_sequenced_by_channel: BTreeMap::new(), next_fragment_id: 1, connected: false, last_received_at_ms: None, highest_received_sequence: None, received_sequences: BTreeSet::new(), + latest_sequenced_by_channel: BTreeMap::new(), pending_reliable: BTreeMap::new(), queued_reliable: VecDeque::new(), ordered_next_sequence_by_channel: BTreeMap::new(), @@ -290,11 +294,13 @@ impl CultNetRudpSession { pub fn reset_peer_state(&mut self) { self.session_scope = Uuid::new_v4(); self.next_sequence = self.initial_sequence; + self.next_sequenced_by_channel.clear(); self.next_fragment_id = 1; self.connected = false; self.last_received_at_ms = None; self.highest_received_sequence = None; self.received_sequences.clear(); + self.latest_sequenced_by_channel.clear(); self.pending_reliable.clear(); self.queued_reliable.clear(); self.ordered_next_sequence_by_channel.clear(); @@ -377,6 +383,9 @@ impl CultNetRudpSession { "Cannot send RUDP data before the session is connected" )); } + if options.ordered && !options.reliable { + return Err(anyhow!("RUDP ordered delivery requires reliability")); + } self.require_payload_size(payload.len())?; if let Some(max_fragment_bytes) = max_fragment_bytes { @@ -459,7 +468,6 @@ impl CultNetRudpSession { } if packet.packet_type == CultNetRudpPacketType::Ping { - self.remember_received(packet.sequence); return Ok(CultNetRudpReceiveResult { delivered: Vec::new(), ready_to_send, @@ -481,9 +489,6 @@ impl CultNetRudpSession { if packet.packet_type == CultNetRudpPacketType::Ack || packet.packet_type == CultNetRudpPacketType::Pong { - if packet.packet_type == CultNetRudpPacketType::Pong { - self.remember_received(packet.sequence); - } return Ok(CultNetRudpReceiveResult { delivered: Vec::new(), ready_to_send, @@ -500,7 +505,6 @@ impl CultNetRudpSession { } if packet.packet_type == CultNetRudpPacketType::Disconnect { - self.remember_received(packet.sequence); self.connected = false; return Ok(CultNetRudpReceiveResult { delivered: Vec::new(), @@ -525,12 +529,15 @@ impl CultNetRudpSession { }); } - let duplicate = self.received_sequences.contains(&packet.sequence) - || self.highest_received_sequence.is_some_and(|highest| { - packet.sequence < highest - && highest - packet.sequence >= RUDP_RECEIVED_SEQUENCE_WINDOW as u32 - }); - self.remember_received(packet.sequence); + let duplicate = packet.reliable + && (self.received_sequences.contains(&packet.sequence) + || self.highest_received_sequence.is_some_and(|highest| { + packet.sequence < highest + && highest - packet.sequence >= RUDP_RECEIVED_SEQUENCE_WINDOW as u32 + })); + if packet.reliable { + self.remember_received(packet.sequence); + } if duplicate { return Ok(CultNetRudpReceiveResult { delivered: Vec::new(), @@ -556,6 +563,19 @@ impl CultNetRudpSession { }; let delivered = if ordered { self.deliver_ordered(frame, next_sequence, expected_sequence_if_uninitialized) + } else if packet.sequenced { + let latest = self + .latest_sequenced_by_channel + .get(&frame.channel_id) + .copied(); + let newest_sequence = next_sequence.saturating_sub(1); + if latest.is_some_and(|latest| newest_sequence <= latest) { + Vec::new() + } else { + self.latest_sequenced_by_channel + .insert(frame.channel_id.clone(), newest_sequence); + vec![frame] + } } else { vec![frame] }; @@ -700,11 +720,24 @@ impl CultNetRudpSession { fragment_index: u16, fragment_count: u16, ) -> CultNetRudpPacket { - let sequence = self.next_sequence; - self.next_sequence = self - .next_sequence - .checked_add(1) - .expect("sequence overflow"); + let sequence = if reliable { + let sequence = self.next_sequence; + self.next_sequence = self + .next_sequence + .checked_add(1) + .expect("reliable sequence overflow"); + sequence + } else if sequenced { + let next = self + .next_sequenced_by_channel + .entry(channel_id.to_string()) + .or_insert(1); + let sequence = *next; + *next = next.checked_add(1).expect("sequenced channel overflow"); + sequence + } else { + 0 + }; let (ack, ack_mask) = self.ack_state(); CultNetRudpPacket { packet_type, diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index a66411f0..293b63a6 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -757,6 +757,133 @@ fn rudp_session_pings_and_detects_receive_timeout() -> Result<()> { Ok(()) } +#[test] +fn rudp_lossy_packets_cannot_create_reliable_ordered_gaps() -> Result<()> { + let mut client = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 198, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut server = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 198, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = client.create_connect(0, Vec::new())?; + let accept = server.accept_connect(&connect, 1, Vec::new())?; + client.receive(&accept, 2)?; + + let ping = client.create_ping(b"pulse".to_vec()); + let pong = server + .receive(&ping, 3)? + .reply + .expect("ping should produce pong"); + let realtime = server.send( + "realtime", + b"discarded realtime".to_vec(), + CultNetRudpSendOptions::default(), + )?; + let latest = server.send( + "latest", + b"discarded latest state".to_vec(), + CultNetRudpSendOptions { + sequenced: true, + ..CultNetRudpSendOptions::default() + }, + )?; + let schema = server.send( + "schema", + b"committed response".to_vec(), + CultNetRudpSendOptions { + reliable: true, + ordered: true, + ..CultNetRudpSendOptions::default() + }, + )?; + + assert_eq!(ping.sequence, 0); + assert_eq!(pong.sequence, 0); + assert_eq!(realtime.sequence, 0); + assert_eq!(latest.sequence, 1); + assert_eq!(schema.sequence, accept.sequence + 1); + assert_eq!( + client.receive(&schema, 4)?.delivered[0].payload, + b"committed response" + ); + Ok(()) +} + +#[test] +fn rudp_unreliable_sequenced_delivery_is_scoped_to_its_channel() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 197, + initial_sequence: 50, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 197, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + let options = CultNetRudpSendOptions { + sequenced: true, + ..CultNetRudpSendOptions::default() + }; + let older = sender.send("latest", b"older".to_vec(), options.clone())?; + let newer = sender.send("latest", b"newer".to_vec(), options)?; + + assert_eq!(older.sequence, 1); + assert_eq!(newer.sequence, 2); + assert_eq!(receiver.receive(&newer, 3)?.delivered[0].payload, b"newer"); + assert!(receiver.receive(&older, 4)?.delivered.is_empty()); + Ok(()) +} + +#[test] +fn rudp_rejects_unreliable_ordered_delivery() -> Result<()> { + let mut session = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 196, + ..CultNetRudpSessionOptions::default() + }); + session.receive( + &CultNetRudpPacket { + packet_type: CultNetRudpPacketType::Accept, + connection_id: 196, + sequence: 50, + ack: 0, + ack_mask: 0, + channel_id: "control".to_string(), + reliable: false, + ordered: false, + sequenced: false, + fragment_id: 0, + fragment_index: 0, + fragment_count: 0, + payload: Vec::new(), + }, + 0, + )?; + let error = session + .send( + "schema", + b"cannot order what will not retransmit".to_vec(), + CultNetRudpSendOptions { + ordered: true, + ..CultNetRudpSendOptions::default() + }, + ) + .unwrap_err(); + assert!( + error + .to_string() + .contains("ordered delivery requires reliability") + ); + Ok(()) +} + #[test] fn rudp_session_bounds_pending_reliable_packets_before_enqueue() -> Result<()> { let mut session = CultNetRudpSession::new(CultNetRudpSessionOptions { diff --git a/packages/cultnet-ts/src/rudp.ts b/packages/cultnet-ts/src/rudp.ts index 51b5cc5b..b6cae87d 100644 --- a/packages/cultnet-ts/src/rudp.ts +++ b/packages/cultnet-ts/src/rudp.ts @@ -147,12 +147,14 @@ export class CultNetRudpSession { readonly connectionId: number; readonly resendDelayMs: number; #nextSequence: number; + readonly #nextSequencedByChannel = new Map(); #nextFragmentId = 1; #connected = false; readonly #maxPendingReliablePackets: number | undefined; #lastReceivedAtMs: number | undefined; #highestReceivedSequence: number | undefined; readonly #receivedSequences = new Set(); + readonly #latestSequencedByChannel = new Map(); readonly #pendingReliable = new Map(); readonly #queuedReliable: CultNetRudpPacket[] = []; readonly #orderedNextSequenceByChannel = new Map(); @@ -194,6 +196,8 @@ export class CultNetRudpSession { this.#lastReceivedAtMs = undefined; this.#highestReceivedSequence = undefined; this.#receivedSequences.clear(); + this.#nextSequencedByChannel.clear(); + this.#latestSequencedByChannel.clear(); this.#pendingReliable.clear(); this.#queuedReliable.splice(0); this.#orderedNextSequenceByChannel.clear(); @@ -271,6 +275,9 @@ export class CultNetRudpSession { if (!this.#connected) { throw new Error("Cannot send RUDP data before the session is connected."); } + if (options.ordered && !options.reliable) { + throw new Error("RUDP ordered delivery requires reliability."); + } const maxFragmentBytes = options.maxFragmentBytes; if (maxFragmentBytes !== undefined && maxFragmentBytes <= 0) { @@ -336,7 +343,6 @@ export class CultNetRudpSession { } if (packet.packetType === "ping") { - this.#rememberReceived(packet.sequence); return { delivered: [], readyToSend, @@ -349,9 +355,8 @@ export class CultNetRudpSession { } if (packet.packetType === "ack" || packet.packetType === "pong") { - if (packet.packetType === "pong") this.#rememberReceived(packet.sequence); return { - delivered: this.#drainUnblockedOrdered(), + delivered: [], readyToSend, pong: packet.packetType === "pong", pongPayload: packet.packetType === "pong" ? packet.payload ?? new Uint8Array() : undefined, @@ -359,7 +364,6 @@ export class CultNetRudpSession { } if (packet.packetType === "disconnect") { - this.#rememberReceived(packet.sequence); this.#connected = false; return { delivered: [], @@ -373,11 +377,12 @@ export class CultNetRudpSession { return { delivered: [], readyToSend }; } - const isDuplicate = this.#receivedSequences.has(packet.sequence) - || (this.#highestReceivedSequence !== undefined - && packet.sequence < this.#highestReceivedSequence - && this.#highestReceivedSequence - packet.sequence >= RUDP_RECEIVED_SEQUENCE_WINDOW); - this.#rememberReceived(packet.sequence); + const isDuplicate = (packet.reliable ?? false) + && (this.#receivedSequences.has(packet.sequence) + || (this.#highestReceivedSequence !== undefined + && packet.sequence < this.#highestReceivedSequence + && this.#highestReceivedSequence - packet.sequence >= RUDP_RECEIVED_SEQUENCE_WINDOW)); + if (packet.reliable ?? false) this.#rememberReceived(packet.sequence); if (isDuplicate) { return { delivered: [], readyToSend }; } @@ -387,6 +392,16 @@ export class CultNetRudpSession { return { delivered: [], readyToSend }; } + if (!reassembled.ordered && (packet.sequenced ?? false)) { + const newestSequence = reassembled.nextSequence - 1; + const latestSequence = this.#latestSequencedByChannel.get(reassembled.frame.channelId); + if (latestSequence !== undefined && newestSequence <= latestSequence) { + return { delivered: [], readyToSend }; + } + this.#latestSequencedByChannel.set(reassembled.frame.channelId, newestSequence); + return { delivered: [reassembled.frame], readyToSend }; + } + if (!reassembled.ordered) { return { delivered: [reassembled.frame], readyToSend }; } @@ -492,8 +507,17 @@ export class CultNetRudpSession { fragmentIndex?: number; fragmentCount?: number; }): CultNetRudpPacket { - const sequence = this.#nextSequence; - this.#nextSequence = toUint32(this.#nextSequence + 1, "sequence"); + let sequence = 0; + if (packet.reliable ?? false) { + sequence = this.#nextSequence; + this.#nextSequence = toUint32(this.#nextSequence + 1, "reliable sequence"); + } else if (packet.sequenced ?? false) { + sequence = this.#nextSequencedByChannel.get(packet.channelId) ?? 1; + this.#nextSequencedByChannel.set( + packet.channelId, + toUint32(sequence + 1, "sequenced channel sequence"), + ); + } const { ack, ackMask } = this.#ackState(); return { packetType: packet.packetType, @@ -712,15 +736,6 @@ export class CultNetRudpSession { return delivered; } - #drainUnblockedOrdered(): CultNetRudpDeliveredFrame[] { - const delivered: CultNetRudpDeliveredFrame[] = []; - for (const channelId of this.#orderedBuffers.keys()) { - this.#skipReceivedNonChannelSequences(channelId); - delivered.push(...this.#drainOrdered(channelId)); - } - return delivered; - } - #skipReceivedNonChannelSequences(channelId: string): void { let next = this.#orderedNextSequenceByChannel.get(channelId); while (next !== undefined && this.#receivedSequences.has(next) && !this.#orderedBuffers.get(channelId)?.has(next)) { diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 111b7361..7ffbafb9 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -553,6 +553,53 @@ test("rudp session pings and detects receive timeout", () => { assert.equal(client.connected, false); }); +test("rudp lossy packets cannot create reliable ordered gaps", () => { + const client = new CultNetRudpSession({ connectionId: 198, initialSequence: 1 }); + const server = new CultNetRudpSession({ connectionId: 198, initialSequence: 100 }); + const connect = client.createConnect(0); + const accept = server.acceptConnect(connect, 1); + client.receive(accept, 2); + + const ping = client.createPing(Buffer.from("pulse")); + const pong = server.receive(ping, 3).reply!; + const realtime = server.send("realtime", Buffer.from("discarded realtime")); + const latest = server.send("latest", Buffer.from("discarded latest state"), { sequenced: true }); + const schema = server.send("schema", Buffer.from("committed response"), { reliable: true, ordered: true }); + + assert.equal(ping.sequence, 0); + assert.equal(pong.sequence, 0); + assert.equal(realtime.sequence, 0); + assert.equal(latest.sequence, 1); + assert.equal(schema.sequence, accept.sequence + 1); + assert.deepEqual(client.receive(schema, 4).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), [ + "committed response", + ]); +}); + +test("rudp unreliable sequenced delivery is scoped to its channel", () => { + const sender = new CultNetRudpSession({ connectionId: 197, initialSequence: 50 }); + const receiver = new CultNetRudpSession({ connectionId: 197, initialSequence: 100 }); + const connect = sender.createConnect(0); + const accept = receiver.acceptConnect(connect, 1); + sender.receive(accept, 2); + const older = sender.send("latest", Buffer.from("older"), { sequenced: true }); + const newer = sender.send("latest", Buffer.from("newer"), { sequenced: true }); + + assert.equal(older.sequence, 1); + assert.equal(newer.sequence, 2); + assert.deepEqual(receiver.receive(newer, 3).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), ["newer"]); + assert.deepEqual(receiver.receive(older, 4).delivered, []); +}); + +test("rudp rejects unreliable ordered delivery", () => { + const session = new CultNetRudpSession({ connectionId: 196 }); + session.receive({ packetType: "accept", connectionId: 196, sequence: 50, ack: 0, ackMask: 0, channelId: "control" }); + assert.throws( + () => session.send("schema", Buffer.from("cannot order what will not retransmit"), { ordered: true }), + /ordered delivery requires reliability/, + ); +}); + test("rudp session bounds pending reliable packets before enqueue", () => { const session = new CultNetRudpSession({ connectionId: 102, initialSequence: 1, maxPendingReliablePackets: 2 }); session.receive({ packetType: "accept", connectionId: 102, sequence: 50, ack: 0, ackMask: 0, channelId: "control" }); diff --git a/src/GameCult.Networking/CultNetTransport.cs b/src/GameCult.Networking/CultNetTransport.cs index 6e706fbc..f549f735 100644 --- a/src/GameCult.Networking/CultNetTransport.cs +++ b/src/GameCult.Networking/CultNetTransport.cs @@ -894,12 +894,14 @@ namespace GameCult.Networking } private uint _nextSequence; + private readonly Dictionary _nextSequencedByChannel = new Dictionary(StringComparer.Ordinal); private ushort _nextFragmentId = 1; private readonly int? _maxPendingReliablePackets; private volatile bool _connected; private long? _lastReceivedAtMs; private uint? _highestReceivedSequence; private readonly HashSet _receivedSequences = new HashSet(); + private readonly Dictionary _latestSequencedByChannel = new Dictionary(StringComparer.Ordinal); private readonly object _pendingReliableGate = new object(); private readonly Dictionary _pendingReliable = new Dictionary(); private readonly Queue _queuedReliable = new Queue(); @@ -1022,6 +1024,10 @@ namespace GameCult.Networking } options ??= new CultNetRudpSendOptions(); + if (options.Ordered && !options.Reliable) + { + throw new InvalidOperationException("RUDP ordered delivery requires reliability."); + } payload ??= Array.Empty(); if (maxFragmentBytes.HasValue && maxFragmentBytes.Value <= 0) { @@ -1099,7 +1105,6 @@ namespace GameCult.Networking if (packet.PacketType == CultNetRudpPacketType.Ping) { - RememberReceived(packet.Sequence); return new CultNetRudpReceiveResult { ReadyToSend = readyToSend, @@ -1115,11 +1120,9 @@ namespace GameCult.Networking if (packet.PacketType == CultNetRudpPacketType.Ack || packet.PacketType == CultNetRudpPacketType.Pong) { - if (packet.PacketType == CultNetRudpPacketType.Pong) - RememberReceived(packet.Sequence); return new CultNetRudpReceiveResult { - Delivered = DrainUnblockedOrdered(), + Delivered = Array.Empty(), ReadyToSend = readyToSend, Pong = packet.PacketType == CultNetRudpPacketType.Pong, PongPayload = packet.PacketType == CultNetRudpPacketType.Pong @@ -1130,7 +1133,6 @@ namespace GameCult.Networking if (packet.PacketType == CultNetRudpPacketType.Disconnect) { - RememberReceived(packet.Sequence); _connected = false; return new CultNetRudpReceiveResult { @@ -1145,11 +1147,15 @@ namespace GameCult.Networking return new CultNetRudpReceiveResult { ReadyToSend = readyToSend }; } - var duplicate = _receivedSequences.Contains(packet.Sequence) - || (_highestReceivedSequence.HasValue - && packet.Sequence < _highestReceivedSequence.Value - && _highestReceivedSequence.Value - packet.Sequence >= ReceivedSequenceWindow); - RememberReceived(packet.Sequence); + var duplicate = packet.Reliable + && (_receivedSequences.Contains(packet.Sequence) + || (_highestReceivedSequence.HasValue + && packet.Sequence < _highestReceivedSequence.Value + && _highestReceivedSequence.Value - packet.Sequence >= ReceivedSequenceWindow)); + if (packet.Reliable) + { + RememberReceived(packet.Sequence); + } if (duplicate) { return new CultNetRudpReceiveResult { ReadyToSend = readyToSend }; @@ -1161,6 +1167,17 @@ namespace GameCult.Networking return new CultNetRudpReceiveResult { ReadyToSend = readyToSend }; } + if (!reassembled.Ordered && packet.Sequenced) + { + var newestSequence = reassembled.NextSequence - 1; + if (_latestSequencedByChannel.TryGetValue(reassembled.Frame.ChannelId, out var latestSequence) + && newestSequence <= latestSequence) + { + return new CultNetRudpReceiveResult { ReadyToSend = readyToSend }; + } + _latestSequencedByChannel[reassembled.Frame.ChannelId] = newestSequence; + } + return new CultNetRudpReceiveResult { ReadyToSend = readyToSend, @@ -1282,8 +1299,24 @@ namespace GameCult.Networking ushort fragmentIndex, ushort fragmentCount) { - var sequence = _nextSequence; - _nextSequence = checked(_nextSequence + 1); + uint sequence; + if (reliable) + { + sequence = _nextSequence; + _nextSequence = checked(_nextSequence + 1); + } + else if (sequenced) + { + if (!_nextSequencedByChannel.TryGetValue(channelId, out sequence)) + { + sequence = 1; + } + _nextSequencedByChannel[channelId] = checked(sequence + 1); + } + else + { + sequence = 0; + } var (ack, ackMask) = AckState(); return new CultNetRudpPacket { @@ -1563,17 +1596,6 @@ namespace GameCult.Networking return delivered; } - private IReadOnlyList DrainUnblockedOrdered() - { - var delivered = new List(); - foreach (var channelId in _orderedBuffers.Keys.ToArray()) - { - SkipReceivedNonChannelSequences(channelId); - delivered.AddRange(DrainOrdered(channelId)); - } - return delivered; - } - private void SkipReceivedNonChannelSequences(string channelId) { if (!_orderedNextSequenceByChannel.TryGetValue(channelId, out var next)) diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index 9ff8953e..c065d0af 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -889,6 +889,75 @@ namespace GameCult.Networking.Tests Assert.That(client.Connected, Is.False); } + [Test] + public void RudpSession_LossyPacketsCannotCreateReliableOrderedGaps() + { + var client = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 198, InitialSequence = 1 }); + var server = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 198, InitialSequence = 100 }); + var connect = client.CreateConnect(0); + var accept = server.AcceptConnect(connect, 1); + client.Receive(accept, 2); + + var ping = client.CreatePing(Encoding.UTF8.GetBytes("pulse")); + var pong = server.Receive(ping, 3).Reply!; + var realtime = server.Send("realtime", Encoding.UTF8.GetBytes("discarded realtime")); + var latest = server.Send( + "latest", + Encoding.UTF8.GetBytes("discarded latest state"), + new CultNetRudpSendOptions { Sequenced = true }); + var schema = server.Send( + "schema", + Encoding.UTF8.GetBytes("committed response"), + new CultNetRudpSendOptions { Reliable = true, Ordered = true }); + + Assert.That(ping.Sequence, Is.Zero); + Assert.That(pong.Sequence, Is.Zero); + Assert.That(realtime.Sequence, Is.Zero); + Assert.That(latest.Sequence, Is.EqualTo(1)); + Assert.That(schema.Sequence, Is.EqualTo(accept.Sequence + 1)); + Assert.That( + client.Receive(schema, 4).Delivered.Select(frame => Encoding.UTF8.GetString(frame.Payload)).ToArray(), + Is.EqualTo(new[] { "committed response" })); + } + + [Test] + public void RudpSession_UnreliableSequencedDeliveryIsScopedToItsChannel() + { + var sender = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 197, InitialSequence = 50 }); + var receiver = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 197, InitialSequence = 100 }); + var connect = sender.CreateConnect(0); + var accept = receiver.AcceptConnect(connect, 1); + sender.Receive(accept, 2); + var older = sender.Send("latest", Encoding.UTF8.GetBytes("older"), new CultNetRudpSendOptions { Sequenced = true }); + var newer = sender.Send("latest", Encoding.UTF8.GetBytes("newer"), new CultNetRudpSendOptions { Sequenced = true }); + + Assert.That(older.Sequence, Is.EqualTo(1)); + Assert.That(newer.Sequence, Is.EqualTo(2)); + Assert.That( + receiver.Receive(newer, 3).Delivered.Select(frame => Encoding.UTF8.GetString(frame.Payload)).ToArray(), + Is.EqualTo(new[] { "newer" })); + Assert.That(receiver.Receive(older, 4).Delivered, Is.Empty); + } + + [Test] + public void RudpSession_RejectsUnreliableOrderedDelivery() + { + var session = new CultNetRudpSession(new CultNetRudpSessionOptions { ConnectionId = 196 }); + session.Receive(new CultNetRudpPacket + { + PacketType = CultNetRudpPacketType.Accept, + ConnectionId = 196, + Sequence = 50, + ChannelId = "control" + }); + var error = Assert.Throws(() => + session.Send( + "schema", + Encoding.UTF8.GetBytes("cannot order what will not retransmit"), + new CultNetRudpSendOptions { Ordered = true })); + Assert.That(error!.Message, Does.Contain("ordered delivery requires reliability")); + } + [Test] public void RudpSession_BoundsPendingReliablePacketsBeforeEnqueue() { @@ -955,55 +1024,6 @@ namespace GameCult.Networking.Tests Is.EqualTo(new[] { "second", "third" })); } - [Test] - public void RudpSession_SequencedControlPacketReleasesBufferedOrderedFrame() - { - var receiver = new CultNetRudpSession(new CultNetRudpSessionOptions - { - ConnectionId = 199, - InitialSequence = 1 - }); - receiver.Receive(new CultNetRudpPacket - { - PacketType = CultNetRudpPacketType.Accept, - ConnectionId = 199, - Sequence = 100, - ChannelId = "control" - }); - var first = receiver.Receive(new CultNetRudpPacket - { - PacketType = CultNetRudpPacketType.Data, - ConnectionId = 199, - Sequence = 101, - ChannelId = "schema", - Reliable = true, - Ordered = true, - Payload = Encoding.UTF8.GetBytes("first") - }); - var buffered = receiver.Receive(new CultNetRudpPacket - { - PacketType = CultNetRudpPacketType.Data, - ConnectionId = 199, - Sequence = 103, - ChannelId = "schema", - Reliable = true, - Ordered = true, - Payload = Encoding.UTF8.GetBytes("second") - }); - var released = receiver.Receive(new CultNetRudpPacket - { - PacketType = CultNetRudpPacketType.Pong, - ConnectionId = 199, - Sequence = 102, - ChannelId = "control" - }); - - Assert.That(first.Delivered, Has.Count.EqualTo(1)); - Assert.That(buffered.Delivered, Is.Empty); - Assert.That(released.Delivered, Has.Count.EqualTo(1)); - Assert.That(Encoding.UTF8.GetString(released.Delivered[0].Payload), Is.EqualTo("second")); - } - [Test] public void RudpSession_SkipsControlPacketsWhileOrderingSchemaPayloads() { From 8d3025816dc71b0091cacad20bfa07cf94f09829 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 17:39:55 +0200 Subject: [PATCH 052/203] Align RUDP reliability tests with sequence domains --- .../NetworkingTests.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index c065d0af..56681839 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -708,7 +708,10 @@ namespace GameCult.Networking.Tests Assert.That(ack.Ack, Is.EqualTo(100)); server.Receive(ack, 30); Assert.That(server.PendingReliableSequences, Is.Empty); - var firstData = client.Send("schema", Encoding.UTF8.GetBytes("after-ack")); + var firstData = client.Send( + "schema", + Encoding.UTF8.GetBytes("after-ack"), + new CultNetRudpSendOptions { Reliable = true, Ordered = true }); Assert.That(firstData.Sequence, Is.EqualTo(2)); } @@ -795,11 +798,19 @@ namespace GameCult.Networking.Tests new byte[] { 1 }, new CultNetRudpSendOptions { Reliable = true, Ordered = true, NowMs = 0 }); receiver.Receive(oldPacket); - foreach (var packet in Enumerable.Range(0, 40).Select(index => sender.Send( - "activity", - new byte[] { (byte)index }, - new CultNetRudpSendOptions { Reliable = false, Ordered = false, NowMs = 0 }))) - receiver.Receive(packet); + foreach (var sequence in Enumerable.Range(2, 40).Select(value => (uint)value)) + { + receiver.Receive(new CultNetRudpPacket + { + PacketType = CultNetRudpPacketType.Data, + ConnectionId = 991, + Sequence = sequence, + ChannelId = "activity", + Reliable = true, + Ordered = false, + Payload = new[] { (byte)sequence } + }); + } sender.Receive(receiver.CreateAck()); Assert.That(sender.PendingReliableSequences.First(), Is.EqualTo(1u)); From 85bc4c71b55d1e49d972ad885a55e8c5aba9fd12 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:36:36 +0200 Subject: [PATCH 053/203] Align the replay-window test with the reliable sequence domain Separating the sequence domains gated duplicate suppression on packet.reliable in Rust, TypeScript and C# alike, so lossy datagrams are remembered nowhere and a replayed one is delivered again. That is the intended contract -- lossy traffic is fire-and-forget and its history is bounded by not existing -- but replay_history_is_bounded_and_idle_hub_sessions_expire still asserted the old behaviour with a lossy packet and had been left failing on the branch. The three tests the change added in each runtime cover ordered-requires- reliable, per-channel sequenced scope, and lossy gaps. None covers the bounded replay window, which was this test's real subject, so removing the assertion would have dropped the only coverage of a security property. The lossy assertion now states the new contract, and the reliable domain gets its own replay check: 4,100 acknowledged reliable sends, then the oldest replayed and rejected by the window. Rust 151, C# 148, TypeScript 48, Python 138 all pass. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/tests/rudp_server_hub.rs | 34 +++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/cultnet-rs/tests/rudp_server_hub.rs b/packages/cultnet-rs/tests/rudp_server_hub.rs index cdc6e390..801312aa 100644 --- a/packages/cultnet-rs/tests/rudp_server_hub.rs +++ b/packages/cultnet-rs/tests/rudp_server_hub.rs @@ -453,10 +453,42 @@ fn replay_history_is_bounded_and_idle_hub_sessions_expire() -> Result<()> { } assert_eq!(receiver.receive(&packet, sequence)?.delivered.len(), 1); } - assert!( + // Separating the sequence domains moved duplicate suppression onto the + // reliable domain, so a replayed lossy datagram is delivered again rather + // than silently dropped. Lossy traffic is remembered nowhere, which is what + // bounds its history. + assert_eq!( receiver .receive(&oldest.expect("oldest packet"), 4_101)? .delivered + .len(), + 1 + ); + + // The bounded replay window still guards the reliable domain, and nothing + // else asserts it once the lossy path stopped being remembered. + let mut oldest_reliable = None; + for step in 0..4_100u64 { + let packet = sender.send( + "schema", + vec![(step % 251) as u8], + CultNetRudpSendOptions { + reliable: true, + ordered: false, + sequenced: false, + now_ms: step, + }, + )?; + if oldest_reliable.is_none() { + oldest_reliable = Some(packet.clone()); + } + assert_eq!(receiver.receive(&packet, step)?.delivered.len(), 1); + sender.receive(&receiver.create_ack_for(packet.sequence), step)?; + } + assert!( + receiver + .receive(&oldest_reliable.expect("oldest reliable packet"), 4_101)? + .delivered .is_empty() ); From fd676ce74da1dc06cfd903049e5d438134ae1248 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:40:05 +0200 Subject: [PATCH 054/203] Finish the sequence-domain split in Python and Kotlin Separating the reliable and lossy sequence domains landed in Rust, TypeScript and C# on a branch. The Python and Kotlin halves were written at the same time and left uncommitted in the worktree, so three runtimes spoke the new contract and two spoke the old one -- the exact cross-runtime disagreement this repository exists to prevent. Both were already complete and implement the same three rules as the others: ordered delivery requires reliability, sequenced channels carry their own per-channel counters with latest-wins delivery, and duplicate suppression is gated on packet.reliable so lossy datagrams are remembered nowhere. Python also gains the three tests Rust, TypeScript and C# each received and it did not: lossy packets cannot create reliable ordered gaps, unreliable sequenced delivery is scoped to its channel, and unreliable ordered delivery is rejected. Python passes 141. Kotlin is committed unverified. This machine has no JVM or Gradle, and the package carries no runtime-local test file, so its only gate is the interop lane in CI. The change mirrors the four runtimes proven here line for line, but mirroring is not evidence and CI is the witness. Co-Authored-By: Claude Opus 5 --- .../cultcache-py/src/cultnet_py/transport.py | 40 ++++++++++++---- packages/cultcache-py/tests/test_cultcache.py | 46 +++++++++++++++++++ .../kotlin/org/gamecult/cultmesh/CultMesh.kt | 31 +++++++++---- 3 files changed, 99 insertions(+), 18 deletions(-) diff --git a/packages/cultcache-py/src/cultnet_py/transport.py b/packages/cultcache-py/src/cultnet_py/transport.py index 9ce4e4de..2b9fe462 100644 --- a/packages/cultcache-py/src/cultnet_py/transport.py +++ b/packages/cultcache-py/src/cultnet_py/transport.py @@ -406,11 +406,13 @@ class CultNetRudpSession: raise ValueError("RUDP max_pending_reliable_packets must be greater than zero") self.max_pending_reliable_packets = options.max_pending_reliable_packets self._next_sequence = _uint32(options.initial_sequence, "initial_sequence") + self._next_sequenced_by_channel: dict[str, int] = {} self._next_fragment_id = 1 self._connected = False self._last_received_at_ms: int | None = None self._highest_received_sequence: int | None = None self._received_sequences: set[int] = set() + self._latest_sequenced_by_channel: dict[str, int] = {} self._pending_reliable: dict[int, _PendingReliablePacket] = {} self._queued_reliable: deque[CultNetRudpPacket] = deque() self._ordered_next_sequence_by_channel: dict[str, int] = {} @@ -495,6 +497,8 @@ class CultNetRudpSession: raise ValueError("Cannot send RUDP data before the session is connected") options = options or CultNetRudpSendOptions() + if options.ordered and not options.reliable: + raise ValueError("RUDP ordered delivery requires reliability") if max_fragment_bytes is not None and max_fragment_bytes <= 0: raise ValueError("RUDP max_fragment_bytes must be greater than zero") @@ -550,14 +554,12 @@ class CultNetRudpSession: return CultNetRudpReceiveResult(ready_to_send=ready_to_send) if packet.packet_type == CultNetRudpPacketType.PING: - self._remember_received(packet.sequence) return CultNetRudpReceiveResult( ready_to_send=ready_to_send, reply=self._create_packet(CultNetRudpPacketType.PONG, "control", packet.payload) ) if packet.packet_type in (CultNetRudpPacketType.ACK, CultNetRudpPacketType.PONG): - self._remember_received(packet.sequence) return CultNetRudpReceiveResult( ready_to_send=ready_to_send, pong=packet.packet_type == CultNetRudpPacketType.PONG, @@ -565,7 +567,6 @@ class CultNetRudpSession: ) if packet.packet_type == CultNetRudpPacketType.DISCONNECT: - self._remember_received(packet.sequence) self._connected = False return CultNetRudpReceiveResult( ready_to_send=ready_to_send, @@ -576,12 +577,15 @@ class CultNetRudpSession: if packet.packet_type != CultNetRudpPacketType.DATA: return CultNetRudpReceiveResult(ready_to_send=ready_to_send) - duplicate = packet.sequence in self._received_sequences or ( - self._highest_received_sequence is not None - and packet.sequence < self._highest_received_sequence - and self._highest_received_sequence - packet.sequence >= self.RECEIVED_SEQUENCE_WINDOW + duplicate = packet.reliable and ( + packet.sequence in self._received_sequences or ( + self._highest_received_sequence is not None + and packet.sequence < self._highest_received_sequence + and self._highest_received_sequence - packet.sequence >= self.RECEIVED_SEQUENCE_WINDOW + ) ) - self._remember_received(packet.sequence) + if packet.reliable: + self._remember_received(packet.sequence) if duplicate: return CultNetRudpReceiveResult(ready_to_send=ready_to_send) @@ -589,6 +593,13 @@ class CultNetRudpSession: if reassembled is None: return CultNetRudpReceiveResult(ready_to_send=ready_to_send) frame, ordered, next_sequence = reassembled + if not ordered and packet.sequenced: + newest_sequence = next_sequence - 1 + latest_sequence = self._latest_sequenced_by_channel.get(frame.channel_id) + if latest_sequence is not None and newest_sequence <= latest_sequence: + return CultNetRudpReceiveResult(ready_to_send=ready_to_send) + self._latest_sequenced_by_channel[frame.channel_id] = newest_sequence + return CultNetRudpReceiveResult(delivered=(frame,), ready_to_send=ready_to_send) if not ordered: return CultNetRudpReceiveResult(delivered=(frame,), ready_to_send=ready_to_send) return CultNetRudpReceiveResult( @@ -664,8 +675,17 @@ class CultNetRudpSession: fragment_index: int = 0, fragment_count: int = 0, ) -> CultNetRudpPacket: - sequence = self._next_sequence - self._next_sequence = _uint32(self._next_sequence + 1, "sequence") + if reliable: + sequence = self._next_sequence + self._next_sequence = _uint32(self._next_sequence + 1, "reliable sequence") + elif sequenced: + sequence = self._next_sequenced_by_channel.get(channel_id, 1) + self._next_sequenced_by_channel[channel_id] = _uint32( + sequence + 1, + "sequenced channel sequence", + ) + else: + sequence = 0 ack, ack_mask = self._ack_state() return CultNetRudpPacket( packet_type=packet_type, diff --git a/packages/cultcache-py/tests/test_cultcache.py b/packages/cultcache-py/tests/test_cultcache.py index 2dbbeb2d..29fdefef 100644 --- a/packages/cultcache-py/tests/test_cultcache.py +++ b/packages/cultcache-py/tests/test_cultcache.py @@ -1110,6 +1110,52 @@ class CultCacheTests(unittest.TestCase): ["second", "third"], ) + def test_cultnet_rudp_lossy_packets_cannot_create_reliable_ordered_gaps(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 91, 0, 0, "control")) + + realtime = sender.send("realtime", b"discarded realtime", CultNetRudpSendOptions()) + latest = sender.send("latest", b"discarded latest state", CultNetRudpSendOptions(sequenced=True)) + schema = sender.send("schema", b"committed response", CultNetRudpSendOptions(reliable=True, ordered=True)) + + self.assertEqual(realtime.sequence, 0) + self.assertEqual(latest.sequence, 1) + self.assertEqual( + [frame.payload.decode("utf-8") for frame in receiver.receive(schema).delivered], + ["committed response"], + ) + + def test_cultnet_rudp_unreliable_sequenced_delivery_is_scoped_to_its_channel(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=50)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 91, 0, 0, "control")) + + older = sender.send("latest", b"older", CultNetRudpSendOptions(sequenced=True)) + newer = sender.send("latest", b"newer", CultNetRudpSendOptions(sequenced=True)) + + self.assertEqual(older.sequence, 1) + self.assertEqual(newer.sequence, 2) + self.assertEqual( + [frame.payload.decode("utf-8") for frame in receiver.receive(newer).delivered], + ["newer"], + ) + self.assertEqual(receiver.receive(older).delivered, ()) + + def test_cultnet_rudp_rejects_unreliable_ordered_delivery(self) -> None: + session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=196, initial_sequence=1)) + session.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 196, 50, 0, 0, "control")) + + with self.assertRaises(ValueError) as raised: + session.send( + "schema", + b"cannot order what will not retransmit", + CultNetRudpSendOptions(ordered=True), + ) + self.assertIn("ordered delivery requires reliability", str(raised.exception)) + def test_cultnet_rudp_session_skips_control_packets_while_ordering_schema_payloads(self) -> None: sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=1)) receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=100)) diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt index 6245fb9e..104bd1b9 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt @@ -2629,6 +2629,7 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { val resendDelayMs: Long = options.resendDelayMs private val maxPendingReliablePackets: Int? = options.maxPendingReliablePackets private var nextSequence = uint32(options.initialSequence, "initialSequence") + private val nextSequencedByChannel = linkedMapOf() private var nextFragmentId = 1 var connected: Boolean = false private set @@ -2636,6 +2637,7 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { private set private var highestReceivedSequence: Long? = null private val receivedSequences = linkedSetOf() + private val latestSequencedByChannel = linkedMapOf() private val pendingReliable = linkedMapOf() private val queuedReliable = ArrayDeque() private val orderedNextSequenceByChannel = linkedMapOf() @@ -2687,6 +2689,7 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { maxFragmentBytes: Int? = null, ): List { if (!connected) throw IOException("Cannot send RUDP data before the session is connected") + if (options.ordered && !options.reliable) throw IOException("RUDP ordered delivery requires reliability") if (maxFragmentBytes != null && maxFragmentBytes <= 0) throw IOException("RUDP maxFragmentBytes must be greater than zero") if (maxFragmentBytes != null && payload.size > maxFragmentBytes) { val fragmentCount = (payload.size + maxFragmentBytes - 1) / maxFragmentBytes @@ -2730,11 +2733,9 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { return CultNetRudpReceiveResult(readyToSend = readyToSend) } CultNetRudpPacketType.Ping -> { - rememberReceived(packet.sequence) return CultNetRudpReceiveResult(readyToSend = readyToSend, reply = createPacket(CultNetRudpPacketType.Pong, "control", packet.payload)) } CultNetRudpPacketType.Ack, CultNetRudpPacketType.Pong -> { - rememberReceived(packet.sequence) return CultNetRudpReceiveResult( readyToSend = readyToSend, pong = packet.packetType == CultNetRudpPacketType.Pong, @@ -2742,7 +2743,6 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { ) } CultNetRudpPacketType.Disconnect -> { - rememberReceived(packet.sequence) connected = false return CultNetRudpReceiveResult(readyToSend = readyToSend, disconnected = true, disconnectReason = packet.payload.copyOf()) } @@ -2750,14 +2750,22 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { else -> return CultNetRudpReceiveResult(readyToSend = readyToSend) } - val duplicate = receivedSequences.contains(packet.sequence) || ( + val duplicate = packet.reliable && (receivedSequences.contains(packet.sequence) || ( highestReceivedSequence != null && packet.sequence < highestReceivedSequence!! && highestReceivedSequence!! - packet.sequence >= ReceivedSequenceWindow.toLong() - ) - rememberReceived(packet.sequence) + )) + if (packet.reliable) rememberReceived(packet.sequence) if (duplicate) return CultNetRudpReceiveResult(readyToSend = readyToSend) val reassembled = reassemble(packet) ?: return CultNetRudpReceiveResult(readyToSend = readyToSend) + if (!reassembled.ordered && packet.sequenced) { + val newestSequence = reassembled.nextSequence - 1 + val latestSequence = latestSequencedByChannel[reassembled.frame.channelId] + if (latestSequence != null && newestSequence <= latestSequence) { + return CultNetRudpReceiveResult(readyToSend = readyToSend) + } + latestSequencedByChannel[reassembled.frame.channelId] = newestSequence + } return CultNetRudpReceiveResult( delivered = if (reassembled.ordered) deliverOrdered(reassembled.frame, reassembled.nextSequence, expectedSequenceIfUninitialized) else listOf(reassembled.frame), readyToSend = readyToSend, @@ -2828,8 +2836,15 @@ class CultNetRudpSession(options: CultNetRudpSessionOptions) { fragmentIndex: Int = 0, fragmentCount: Int = 0, ): CultNetRudpPacket { - val sequence = nextSequence - nextSequence = uint32(nextSequence + 1, "sequence") + val sequence = when { + reliable -> nextSequence.also { + nextSequence = uint32(nextSequence + 1, "reliable sequence") + } + sequenced -> (nextSequencedByChannel[channelId] ?: 1).also { + nextSequencedByChannel[channelId] = uint32(it + 1, "sequenced channel sequence") + } + else -> 0 + } val (ack, ackMask) = ackState() return CultNetRudpPacket(packetType, connectionId, sequence, ack, ackMask, channelId, reliable, ordered, sequenced, fragmentId, fragmentIndex, fragmentCount, payload.copyOf()) } From eb74ead837ceba288304da9403acf8f2590bdaf4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:45:48 +0200 Subject: [PATCH 055/203] Restore cross-runtime schema recovery and prove staging cleanup Taking the standalone body as the CultCache base dropped a0b31b8, and it was a real regression rather than a missing convenience. A record whose catalog entry is absent -- written by another runtime, or older than the catalog it was stamped against -- made decode_store_snapshot fail, and one such record failed the entire pull. Recovering the type from the payload is what "across runtimes" meant in that commit's title. Restored in full: the payload is a MessagePack array whose first field is the schema version, so the schema name is recoverable from the record itself; the registry now also indexes schema name to entry type and refuses a name claimed by a second type; and pull resolves a persisted type that is either a registered type or a recovered schema name. The stale schema id is preserved on the envelope rather than rewritten. 743eaa5's durability half needed no code. The standalone lineage reached the same place independently through crash-atomic single-file snapshots and abandoned staging cleanup, and its cleanup carries the same protections: only a candidate named for this exact store plus a canonical UUID is removed, and anything that is not a regular file is skipped. Untested, though, which is how a careful deletion rule quietly becomes a careless one. Now asserted: an abandoned canonical candidate is removed while a non-UUID lookalike, a bare .tmp suffix, and another store's candidate all survive, and the committed snapshot still reloads. 48 tests pass. This clears the cultcache half of the retained debt from b672fb7. Co-Authored-By: Claude Opus 5 --- packages/cultcache-rs/src/lib.rs | 235 +++++++++++++++++++++++++++++-- 1 file changed, 225 insertions(+), 10 deletions(-) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index b8d6d8f9..576270a0 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -1750,6 +1750,7 @@ struct CultCacheStoreRegistration { pub struct CultCache { definitions: BTreeMap, + schema_name_definitions: BTreeMap, entries: BTreeMap<(String, String), CultCacheEnvelope>, stores: Vec, } @@ -1758,6 +1759,7 @@ impl CultCache { pub fn new() -> Self { Self { definitions: BTreeMap::new(), + schema_name_definitions: BTreeMap::new(), entries: BTreeMap::new(), stores: Vec::new(), } @@ -1777,7 +1779,18 @@ impl CultCache { T::TYPE )); } + if let Some(existing_type) = self.schema_name_definitions.get(T::SCHEMA_NAME) + && existing_type != T::TYPE + { + return Err(anyhow!( + "CultCache schema name {:?} is already registered for type {:?}", + T::SCHEMA_NAME, + existing_type + )); + } self.definitions.insert(T::TYPE.to_string(), T::SCHEMA_NAME); + self.schema_name_definitions + .insert(T::SCHEMA_NAME.to_string(), T::TYPE.to_string()); Ok(()) } @@ -1812,14 +1825,20 @@ impl CultCache { pub fn pull_all_backing_stores(&mut self) -> Result<()> { self.entries.clear(); let known_types: BTreeSet = self.definitions.keys().cloned().collect(); + let schema_name_definitions = self.schema_name_definitions.clone(); for registration in &mut self.stores { - for entry in registration.store.pull_all()? { - if !known_types.contains(&entry.r#type) { + for mut entry in registration.store.pull_all()? { + let Some(canonical_type) = resolve_registered_type( + &known_types, + &schema_name_definitions, + &entry.r#type, + ) else { return Err(anyhow!( "No schema is registered for persisted entry type {:?}", entry.r#type )); - } + }; + entry.r#type = canonical_type; self.entries.insert(entry_id(&entry), entry); } } @@ -2311,13 +2330,32 @@ fn decode_store_snapshot(bytes: &[u8]) -> Result> { .2 .into_iter() .map(|record| { - let r#type = catalog.get(&record.1).cloned().ok_or_else(|| { - anyhow!( - "CultCache record {:?} references missing schema {:?}", - record.0, - record.1 - ) - })?; + let r#type = match catalog.get(&record.1) { + Some(schema_name) => schema_name.clone(), + None => { + // A record can outlive its catalog entry when another runtime + // wrote it, or when the record is older than the catalog it + // was stamped against. The payload is a MessagePack array + // whose first field is the schema version, so the type can be + // recovered from the record itself rather than failing the + // whole snapshot. + let schema_version = + infer_schema_version_from_payload(&record.3).ok_or_else(|| { + anyhow!( + "CultCache record {:?} references missing schema {:?}", + record.0, + record.1 + ) + })?; + infer_schema_name(&schema_version).ok_or_else(|| { + anyhow!( + "CultCache record {:?} references missing schema {:?}", + record.0, + record.1 + ) + })? + } + }; Ok(CultCacheEnvelope { key: record.0, r#type, @@ -2329,6 +2367,83 @@ fn decode_store_snapshot(bytes: &[u8]) -> Result> { .collect() } +/// A record recovered from its payload carries a schema name, while the +/// registry is keyed by entry type. Accept either, so a snapshot whose catalog +/// entry is gone still resolves to the registered definition. +fn resolve_registered_type( + known_types: &BTreeSet, + schema_name_definitions: &BTreeMap, + persisted_type: &str, +) -> Option { + if known_types.contains(persisted_type) { + return Some(persisted_type.to_string()); + } + + schema_name_definitions.get(persisted_type).cloned() +} + +fn infer_schema_version_from_payload(payload: &[u8]) -> Option { + let mut offset = 0usize; + read_array_header(payload, &mut offset)?; + read_string(payload, &mut offset) +} + +fn read_array_header(payload: &[u8], offset: &mut usize) -> Option { + let marker = *payload.get(*offset)?; + *offset += 1; + match marker { + 0x90..=0x9f => Some((marker & 0x0f) as u32), + 0xdc => { + let bytes = payload.get(*offset..(*offset + 2))?; + *offset += 2; + Some(u16::from_be_bytes([bytes[0], bytes[1]]) as u32) + } + 0xdd => { + let bytes = payload.get(*offset..(*offset + 4))?; + *offset += 4; + Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) + } + _ => None, + } +} + +fn read_string(payload: &[u8], offset: &mut usize) -> Option { + let marker = *payload.get(*offset)?; + *offset += 1; + let length = match marker { + 0xa0..=0xbf => (marker & 0x1f) as usize, + 0xd9 => { + let length = *payload.get(*offset)? as usize; + *offset += 1; + length + } + 0xda => { + let bytes = payload.get(*offset..(*offset + 2))?; + *offset += 2; + u16::from_be_bytes([bytes[0], bytes[1]]) as usize + } + 0xdb => { + let bytes = payload.get(*offset..(*offset + 4))?; + *offset += 4; + u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize + } + _ => return None, + }; + + let bytes = payload.get(*offset..(*offset + length))?; + std::str::from_utf8(bytes).ok().map(str::to_string) +} + +fn infer_schema_name(schema_version: &str) -> Option { + let marker = schema_version.rfind(".v")?; + let version = schema_version.get((marker + 2)..)?; + if marker == 0 || version.is_empty() || !version.bytes().all(|value| value.is_ascii_digit()) { + return None; + } + + Some(schema_version[..marker].to_string()) +} + fn escape_json_string(value: &str) -> String { value.replace('\\', "\\\\").replace('"', "\\\"") } @@ -2514,6 +2629,17 @@ mod tests { symbol: Option, } + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] + #[cultcache(type = "runtime-policy", schema = "tests.schema_stamped_entry")] + struct SchemaStamped { + #[cultcache(key = 0)] + schema_version: String, + #[cultcache(key = 1)] + name: String, + #[cultcache(key = 2)] + value: String, + } + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "nested-note")] struct NestedNote { @@ -3711,4 +3837,93 @@ mod tests { let error = table.validate().unwrap_err(); assert!(error.to_string().contains("column types has length 0")); } + + #[test] + fn messagepack_store_recovers_schema_stamped_records_missing_catalog_entries() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("missing-catalog.msgpack"); + let expected = SchemaStamped { + schema_version: "tests.schema_stamped_entry.v1".to_string(), + name: "schema-stamped".to_string(), + value: "still readable".to_string(), + }; + // An empty catalog with a stale schema id is what a record written by + // another runtime, or one older than its catalog, looks like on disk. + let snapshot = PersistedStoreSnapshot( + "cultcache.store.v1".to_string(), + Vec::new(), + vec![PersistedRecord( + "record-1".to_string(), + "sha256:stale-schema-id-from-cold-record".to_string(), + "2026-06-25T12:00:00Z".to_string(), + rmp_serde::to_vec(&expected)?, + )], + ); + std::fs::write(&store_path, rmp_serde::to_vec(&snapshot)?)?; + + let mut cache = CultCache::new(); + cache.register_entry_type::()?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + cache.pull_all_backing_stores()?; + + assert_eq!(cache.get_required::("record-1")?, expected); + assert_eq!( + cache + .get_required_envelope::("record-1")? + .schema_id, + Some("sha256:stale-schema-id-from-cold-record".to_string()) + ); + Ok(()) + } + + #[test] + fn abandoned_staging_cleanup_removes_only_canonical_candidates() -> Result<()> { + let temp = tempfile::tempdir()?; + let store_path = temp.path().join("store.cc"); + let mut cache = CultCache::new(); + cache.register_entry_type::()?; + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + cache.put( + "primary", + &Settings { + theme: "dark".to_string(), + retries: 2, + }, + )?; + + // A crashed writer leaves a canonically named candidate behind. + let abandoned = temp + .path() + .join(format!("store.cc.{}.tmp", uuid::Uuid::new_v4())); + std::fs::write(&abandoned, b"partial snapshot")?; + // These only look like candidates. Cleanup must not touch them: the + // staging name is the store's own file name plus a canonical UUID, and + // anything else is somebody's file. + let lookalike = temp.path().join("store.cc.not-a-uuid.tmp"); + std::fs::write(&lookalike, b"keep me")?; + let bare_suffix = temp.path().join("store.cc.tmp"); + std::fs::write(&bare_suffix, b"keep me too")?; + let neighbour = temp.path().join("other.cc.00000000-0000-0000-0000-000000000000.tmp"); + std::fs::write(&neighbour, b"not this store")?; + + cache.put( + "primary", + &Settings { + theme: "light".to_string(), + retries: 3, + }, + )?; + + assert!(!abandoned.exists(), "canonical abandoned candidate survived"); + assert!(lookalike.exists(), "non-UUID lookalike was deleted"); + assert!(bare_suffix.exists(), "bare .tmp suffix was deleted"); + assert!(neighbour.exists(), "another store's candidate was deleted"); + + let mut reloaded = CultCache::new(); + reloaded.register_entry_type::()?; + reloaded.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store_path)); + reloaded.pull_all_backing_stores()?; + assert_eq!(reloaded.get_required::("primary")?.theme, "light"); + Ok(()) + } } From 5dbc0c29f58414ce52f4ca74f9eb975a2d5e9381 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 00:57:07 +0200 Subject: [PATCH 056/203] Bound the RUDP receive path and refuse hostile sequences Clears the cultnet half of the retained debt. The two unported commits from the retiring GameCult/cultnet-rs lineage were not portable as patches: this lineage had reworked the same receive path with sequence domains and ACK horizons, and applying them raised nine conflicts that would have reverted that work. Most of what they added already existed here by another route -- received-sequence history, fragment-set count and reassembly bytes are all bounded already. So the gaps were taken rather than the patches. Three were real. The per-channel maps grew without limit, and a peer picks its own channel ids, so ordered and sequenced channel tracking is now capped at 64 each. The out-of-order hold buffer grew without limit, and a peer picks its own send order, so it is capped at 1024 frames and 4 MB; sizes are computed from the buffer rather than tracked in counters, which cannot drift. The fourth was the sharpest. remember_received ran before the duplicate early-return, so a single forged packet far past the current highest advanced the receive state beyond every sequence still in flight and starved the session. Such a packet is now refused without being remembered. Reliable sequence exhaustion aborted the process through expect(). The capacity gate that already guards the send window now also refuses when the sequence space cannot cover the request, and allocation saturates rather than panicking so no future path can reintroduce the abort. Five tests cover the bounds and both refusals. cultnet-rs passes 103, cultcache-rs 48, cultmesh-rs 7, and the TypeScript-Rust RUDP interop lanes pass 5 of 5 with the hardened peer, so legitimate traffic is unchanged. The parity ledger now records what this actually leaves owed: these are refusals, not wire-format changes, so the interop lanes stay green whether or not the other four runtimes match. The gap will not announce itself. Co-Authored-By: Claude Opus 5 --- docs/runtime-parity-scope.md | 14 +- packages/cultnet-rs/src/rudp.rs | 81 +++++++++- packages/cultnet-rs/tests/cultnet.rs | 219 +++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 16 deletions(-) diff --git a/docs/runtime-parity-scope.md b/docs/runtime-parity-scope.md index 9d351ac2..75d980d8 100644 --- a/docs/runtime-parity-scope.md +++ b/docs/runtime-parity-scope.md @@ -89,14 +89,12 @@ server body, is deliberately not made. outside any conformance harness, and its stream, negotiation, and shared-memory frame-ring surfaces have never been checked against the browser, Kotlin, and TypeScript CultMesh bodies they must interoperate with. Runtime-local tests pass; that is not a parity claim. -- Finish porting RUDP hardening from the retiring GameCult/cultnet-rs lineage. Its exact - acknowledgement receipts are in. Two commits are not: bounded session memory (4a54f46) and - hostile sequence rollover rejection (91609ff). They rebuild the receive path -- stale-sequence - rejection, per-channel tracking caps, reassembly and ordered-buffer byte accounting -- in the - same code this lineage reworked with sequence domains and ACK horizons, and they change when - packets are dropped and reassembly refused. That is wire-observable, so the interop lanes are - the gate, not a local build. Until they land, this runtime accepts sequence and reassembly - input the other lineage rejects. Do not delete that repository before this is resolved. +- Carry the RUDP receive-path bounds to the other runtimes. Rust now caps tracked ordered + and sequenced channels, caps the out-of-order hold buffer by frames and bytes, refuses a + packet far ahead of the highest received so a forged sequence cannot starve a session, and + reports reliable sequence exhaustion instead of aborting. C#, TypeScript, Python and Kotlin + still accept all of it. These are refusals rather than wire-format changes, so the interop + lanes stay green either way -- which is exactly why the gap will not announce itself. - Keep performance claims evidence-bound: C# owns SoA; Python has benchmark comparison gates; other runtimes should advertise measured native hot paths only when those paths exist. diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index ab16bf0d..0ff9f322 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -33,6 +33,15 @@ const RUDP_FIXED_HEADER_BYTES: usize = 36; pub const CULTNET_RUDP_DEFAULT_MAX_PAYLOAD_BYTES: usize = 16 * 1024 * 1024; pub const CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS: usize = 32; const RUDP_RECEIVED_SEQUENCE_WINDOW: usize = 4_096; +/// A peer chooses its own channel ids and its own send order, so the per-channel +/// maps and the out-of-order hold buffer are both attacker-sized unless capped. +const RUDP_MAX_TRACKED_CHANNELS: usize = 64; +/// A peer cannot be trusted to pick the next sequence honestly. One packet far +/// ahead of the current highest would otherwise advance the receive state past +/// every legitimate sequence still in flight and starve the session. +const RUDP_RECEIVE_AHEAD_WINDOW: u32 = 1_024; +const RUDP_MAX_ORDERED_BUFFERED_FRAMES: usize = 1_024; +const RUDP_MAX_ORDERED_BUFFERED_BYTES: usize = 4 * 1024 * 1024; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CultNetRudpPacketType { @@ -529,6 +538,25 @@ impl CultNetRudpSession { }); } + let far_ahead = packet.reliable + && self.highest_received_sequence.is_some_and(|highest| { + packet.sequence > highest + && packet.sequence - highest > RUDP_RECEIVE_AHEAD_WINDOW + }); + if far_ahead { + // Refuse without remembering. Recording it would move the highest + // received sequence out of reach of everything still in flight. + return Ok(CultNetRudpReceiveResult { + delivered: Vec::new(), + ready_to_send, + reply: None, + pong: false, + pong_payload: Vec::new(), + disconnected: false, + disconnect_reason: Vec::new(), + }); + } + let duplicate = packet.reliable && (self.received_sequences.contains(&packet.sequence) || self.highest_received_sequence.is_some_and(|highest| { @@ -562,8 +590,15 @@ impl CultNetRudpSession { }); }; let delivered = if ordered { - self.deliver_ordered(frame, next_sequence, expected_sequence_if_uninitialized) + self.deliver_ordered(frame, next_sequence, expected_sequence_if_uninitialized)? } else if packet.sequenced { + if !self + .latest_sequenced_by_channel + .contains_key(&frame.channel_id) + && self.latest_sequenced_by_channel.len() >= RUDP_MAX_TRACKED_CHANNELS + { + return Err(anyhow!("RUDP session has too many sequenced channels")); + } let latest = self .latest_sequenced_by_channel .get(&frame.channel_id) @@ -724,8 +759,7 @@ impl CultNetRudpSession { let sequence = self.next_sequence; self.next_sequence = self .next_sequence - .checked_add(1) - .expect("reliable sequence overflow"); + .saturating_add(1); sequence } else if sequenced { let next = self @@ -733,7 +767,7 @@ impl CultNetRudpSession { .entry(channel_id.to_string()) .or_insert(1); let sequence = *next; - *next = next.checked_add(1).expect("sequenced channel overflow"); + *next = next.saturating_add(1); sequence } else { 0 @@ -808,6 +842,14 @@ impl CultNetRudpSession { return Err(anyhow!("RUDP reliable send queue is full")); } } + // Refuse before allocation rather than at it. A session that runs out of + // reliable sequence space must report it, not abort the process. + let required = u32::try_from(packet_count) + .ok() + .filter(|required| self.next_sequence.checked_add(*required).is_some()); + if required.is_none() { + return Err(anyhow!("RUDP reliable sequence space is exhausted")); + } Ok(()) } @@ -958,13 +1000,30 @@ impl CultNetRudpSession { Ok(()) } + fn ordered_buffered_frames(&self) -> usize { + self.ordered_buffers.values().map(BTreeMap::len).sum() + } + + fn ordered_buffered_bytes(&self) -> usize { + self.ordered_buffers + .values() + .flat_map(BTreeMap::values) + .map(|pending| pending.frame.payload.len()) + .sum() + } + fn deliver_ordered( &mut self, frame: CultNetRudpDeliveredFrame, next_sequence_after_frame: u32, expected_sequence_if_uninitialized: u32, - ) -> Vec { + ) -> Result> { let channel_id = frame.channel_id.clone(); + if !self.ordered_next_sequence_by_channel.contains_key(&channel_id) + && self.ordered_next_sequence_by_channel.len() >= RUDP_MAX_TRACKED_CHANNELS + { + return Err(anyhow!("RUDP session has too many ordered channels")); + } let mut next = if let Some(next) = self .ordered_next_sequence_by_channel .get(&channel_id) @@ -992,10 +1051,16 @@ impl CultNetRudpSession { } if frame.sequence < next { - return Vec::new(); + return Ok(Vec::new()); } if frame.sequence > next { + if self.ordered_buffered_frames() >= RUDP_MAX_ORDERED_BUFFERED_FRAMES + || self.ordered_buffered_bytes() + frame.payload.len() + > RUDP_MAX_ORDERED_BUFFERED_BYTES + { + return Err(anyhow!("RUDP ordered hold buffer is full")); + } self.ordered_buffers.entry(channel_id).or_default().insert( frame.sequence, PendingOrderedFrame { @@ -1003,14 +1068,14 @@ impl CultNetRudpSession { next_sequence: next_sequence_after_frame, }, ); - return Vec::new(); + return Ok(Vec::new()); } self.ordered_next_sequence_by_channel .insert(channel_id.clone(), next_sequence_after_frame); let mut delivered = vec![frame]; delivered.extend(self.drain_ordered(&channel_id)); - delivered + Ok(delivered) } fn drain_ordered(&mut self, channel_id: &str) -> Vec { diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index 293b63a6..b56c14ba 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -2417,3 +2417,222 @@ fn schema_discovery_round_trips_over_legacy_gamecult_contract_when_inline_schema ); Ok(()) } + +#[test] +fn rudp_sequenced_channel_tracking_is_bounded() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 401, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 401, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + + // A peer picks its own channel ids, so the sequenced map is attacker-sized. + let mut error = None; + for channel in 0..256u32 { + let packet = sender.send( + &format!("channel-{channel}"), + b"state".to_vec(), + CultNetRudpSendOptions { + sequenced: true, + ..CultNetRudpSendOptions::default() + }, + )?; + if let Err(refused) = receiver.receive(&packet, 3) { + error = Some(refused); + break; + } + } + let error = error.expect("unbounded sequenced channel tracking"); + assert!( + error.to_string().contains("too many sequenced channels"), + "unexpected refusal: {error}" + ); + Ok(()) +} + +#[test] +fn rudp_ordered_channel_tracking_is_bounded() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 402, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 402, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + + let mut error = None; + for channel in 0..256u32 { + let packet = sender.send( + &format!("ordered-{channel}"), + b"state".to_vec(), + CultNetRudpSendOptions { + reliable: true, + ordered: true, + ..CultNetRudpSendOptions::default() + }, + )?; + match receiver.receive(&packet, 3) { + Ok(_) => { + sender.receive(&receiver.create_ack_for(packet.sequence), 3)?; + } + Err(refused) => { + error = Some(refused); + break; + } + } + } + let error = error.expect("unbounded ordered channel tracking"); + assert!( + error.to_string().contains("too many ordered channels"), + "unexpected refusal: {error}" + ); + Ok(()) +} + +#[test] +fn rudp_ordered_hold_buffer_is_bounded() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 403, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 403, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + + let options = CultNetRudpSendOptions { + reliable: true, + ordered: true, + ..CultNetRudpSendOptions::default() + }; + // Withhold the first frame so every later one has to be held. + let withheld = sender.send("schema", b"first".to_vec(), options.clone())?; + sender.receive(&receiver.create_ack_for(withheld.sequence), 3)?; + + let mut error = None; + for step in 0..4_096u32 { + let packet = sender.send("schema", vec![(step % 251) as u8], options.clone())?; + match receiver.receive(&packet, 4) { + Ok(result) => { + assert!(result.delivered.is_empty(), "gap should hold every frame"); + sender.receive(&receiver.create_ack_for(packet.sequence), 4)?; + } + Err(refused) => { + error = Some(refused); + break; + } + } + } + let error = error.expect("unbounded ordered hold buffer"); + assert!( + error.to_string().contains("ordered hold buffer is full"), + "unexpected refusal: {error}" + ); + Ok(()) +} + +#[test] +fn rudp_far_ahead_sequence_cannot_starve_the_session() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 404, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 404, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + + let options = CultNetRudpSendOptions { + reliable: true, + ..CultNetRudpSendOptions::default() + }; + let first = sender.send("schema", b"before".to_vec(), options.clone())?; + assert_eq!(receiver.receive(&first, 3)?.delivered.len(), 1); + sender.receive(&receiver.create_ack_for(first.sequence), 3)?; + + // A forged packet far past the current highest. Recording it would push the + // receive state beyond every sequence still in flight. + let forged = CultNetRudpPacket { + packet_type: CultNetRudpPacketType::Data, + connection_id: 404, + sequence: first.sequence.saturating_add(500_000), + ack: 0, + ack_mask: 0, + channel_id: "schema".to_string(), + reliable: true, + ordered: false, + sequenced: false, + fragment_id: 0, + fragment_index: 0, + fragment_count: 0, + payload: b"hostile".to_vec(), + }; + assert!(receiver.receive(&forged, 4)?.delivered.is_empty()); + + // The session must still accept the next legitimate packet. + let second = sender.send("schema", b"after".to_vec(), options)?; + assert_eq!( + receiver.receive(&second, 5)?.delivered[0].payload, + b"after".to_vec() + ); + Ok(()) +} + +#[test] +fn rudp_reliable_sequence_exhaustion_is_reported_not_fatal() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 405, + // One sequence left: the handshake takes it, so the next send is the + // first allocation with nowhere to go. + initial_sequence: u32::MAX - 1, + ..CultNetRudpSessionOptions::default() + }); + let mut receiver = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 405, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = sender.create_connect(0, Vec::new())?; + let accept = receiver.accept_connect(&connect, 1, Vec::new())?; + sender.receive(&accept, 2)?; + + let error = sender + .send( + "schema", + b"one past the end".to_vec(), + CultNetRudpSendOptions { + reliable: true, + ..CultNetRudpSendOptions::default() + }, + ) + .unwrap_err(); + assert!( + error.to_string().contains("sequence space is exhausted"), + "unexpected error: {error}" + ); + Ok(()) +} From 85f7024a35ea42cf9f0a0199a064526427150191 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 01:09:11 +0200 Subject: [PATCH 057/203] Refuse a framed length beyond the advertised maximum Found while checking what would be lost by deleting Ghostlight's vendored fork. Its copy of transport.rs carried a guard CultLib never had: tcp_framed receive compared the declared payload length against the max_payload_bytes the profile advertises for the schema channel, and refused before allocating. Without it the length in a four-byte header is taken on trust and used directly to size a buffer, so one header reserves whatever the peer names before a single payload byte arrives. The guard sits ahead of the allocation, which is the only place it does any good. It is inert unless a profile advertises a maximum, so the test advertises one. This is the last thing unique to that fork. Everything else in it is now here or was already here, which is what makes the fork safe to delete. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/transport.rs | 21 ++++++++++++++++ packages/cultnet-rs/tests/cultnet.rs | 36 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/cultnet-rs/src/transport.rs b/packages/cultnet-rs/src/transport.rs index d7aad261..ec16cbf1 100644 --- a/packages/cultnet-rs/src/transport.rs +++ b/packages/cultnet-rs/src/transport.rs @@ -258,6 +258,27 @@ where let mut header = [0_u8; FRAME_HEADER_BYTES]; self.stream.read_exact(&mut header)?; let payload_len = u32::from_be_bytes(header) as usize; + // Refuse before allocating. The length is the peer's claim, so honouring + // it without checking the advertised maximum lets one header reserve as + // much memory as the peer cares to name. + if let Some(max_payload_bytes) = self + .profile + .transports + .iter() + .find(|transport| transport.protocol == CultNetTransportProtocol::TcpFramed) + .and_then(|transport| { + transport + .channels + .iter() + .find(|channel| channel.channel_id == "schema") + }) + .and_then(|channel| channel.max_payload_bytes) + && payload_len > max_payload_bytes as usize + { + return Err(anyhow!( + "tcp_framed payload length {payload_len} exceeds advertised maximum {max_payload_bytes}" + )); + } let mut payload = vec![0_u8; payload_len]; self.stream.read_exact(&mut payload)?; self.stats.bytes_received += (FRAME_HEADER_BYTES + payload_len) as u64; diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index b56c14ba..d9baaac7 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -2636,3 +2636,39 @@ fn rudp_reliable_sequence_exhaustion_is_reported_not_fatal() -> Result<()> { ); Ok(()) } + +#[test] +fn tcp_framed_receive_refuses_a_length_beyond_the_advertised_maximum() -> Result<()> { + let profile = create_tcp_framed_transport_profile( + "rust-transport", + TcpFramedTransportProfileOptions { + transport_id: Some("bounded-tcp".to_string()), + max_payload_bytes: Some(64), + ..TcpFramedTransportProfileOptions::default() + }, + ); + let advertised = profile + .transports + .iter() + .find(|transport| transport.protocol == CultNetTransportProtocol::TcpFramed) + .and_then(|transport| { + transport + .channels + .iter() + .find(|channel| channel.channel_id == "schema") + }) + .and_then(|channel| channel.max_payload_bytes) + .expect("tcp_framed schema channel advertises a maximum payload"); + + // Only a header, declaring more than the profile allows. Honouring it would + // reserve the peer's number before a single payload byte arrives. + let mut header = ((advertised as u64 + 1) as u32).to_be_bytes().to_vec(); + header.extend_from_slice(b"short"); + let mut receiver = TcpFramedTransportConnection::new(std::io::Cursor::new(header), profile); + let error = receiver.receive().unwrap_err(); + assert!( + error.to_string().contains("exceeds advertised maximum"), + "unexpected error: {error}" + ); + Ok(()) +} From bac5be88b909ed5736ff9f557d3245b3b00af35f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 03:21:01 +0200 Subject: [PATCH 058/203] Restore reliable expiry and snapshot compare-exchange to the Rust core Two capabilities existed only in Odin's vendored forks, which is why the forks could not be deleted. Both are ported here with tests so Odin can consume CultLib instead. cultnet-rs gains reliable send expiry. A reliable packet may now carry a deadline; past it the packet is dropped instead of retransmitted, and the count is reported through session and transport stats. Live media tolerates loss but not lateness -- a frame retransmitted after its moment has passed is worse than a missing one -- so the media channel takes a 75ms default while state channels never expire. Handshake packets never expire either: a dropped connect is not a late frame. The deadline is fixed when a send is admitted, so time spent waiting in the backlog spends it exactly as time spent awaiting an acknowledgement does. This lineage has a queued_reliable backlog that Odin's does not, and a queued packet promoted after its moment had passed would be as useless as a retransmitted one. Expiry runs before the window is measured, so an expiring stream is not refused for a backlog it no longer owns. cultcache-rs gains compare_exchange_snapshot and a nonblocking try_compare_exchange_snapshot. CultLib had neither, despite carrying the larger body -- the two lineages had diverged in both directions. Contention is returned as data rather than waited on, so the caller keeps retry and scheduling authority. This is the primitive an admitted-generation CAS needs. No wire format changes. Expiry is a sender-side policy and compare-exchange is local storage, so the other four runtimes stay in agreement without matching these refusals. They are still worth carrying eventually, and the parity ledger already tracks that this kind of gap does not announce itself. cultnet-rs: 106 tests. cultcache-rs: 51 tests. Co-Authored-By: Claude Opus 5 --- packages/cultcache-rs/src/lib.rs | 162 ++++++++++++++++++ .../examples/cultnet_interop_peer.rs | 1 + packages/cultnet-rs/src/cultmesh.rs | 2 + packages/cultnet-rs/src/rudp.rs | 106 ++++++++++-- packages/cultnet-rs/src/transport.rs | 3 + packages/cultnet-rs/tests/cultnet.rs | 145 ++++++++++++++++ packages/cultnet-rs/tests/rudp_server_hub.rs | 5 + 7 files changed, 408 insertions(+), 16 deletions(-) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index 576270a0..352c5e22 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -284,6 +284,16 @@ pub struct PushAllOptions { pub soft: bool, } +/// Result of one nonblocking whole-snapshot compare-and-exchange. Contention is +/// reported rather than waited on, so the caller keeps retry and scheduling +/// authority instead of blocking inside the store. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TryCompareExchangeSnapshotOutcome { + Exchanged, + Mismatch, + LockContended, +} + pub trait CacheBackingStore: Send { fn pull_all(&self) -> Result>; fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()>; @@ -687,6 +697,80 @@ impl SingleFileMessagePackBackingStore { result } + /// Replaces the whole snapshot only if it still equals `expected`, waiting + /// for the exclusive lock. Returns false when another writer has moved the + /// snapshot on, which is a lost race rather than an error. + pub fn compare_exchange_snapshot( + &self, + expected: &[CultCacheEnvelope], + replacements: &[CultCacheEnvelope], + ) -> Result { + self.with_exclusive_lock(|| self.compare_exchange_snapshot_unlocked(expected, replacements)) + } + + /// Makes one nonblocking attempt at the same exchange. Once the lock is + /// held, comparison, validation, ordering and persistence are identical to + /// [`Self::compare_exchange_snapshot`]. + pub fn try_compare_exchange_snapshot( + &self, + expected: &[CultCacheEnvelope], + replacements: &[CultCacheEnvelope], + ) -> Result { + let Some(exchanged) = self.try_with_exclusive_lock(|| { + self.compare_exchange_snapshot_unlocked(expected, replacements) + })? + else { + return Ok(TryCompareExchangeSnapshotOutcome::LockContended); + }; + Ok(if exchanged { + TryCompareExchangeSnapshotOutcome::Exchanged + } else { + TryCompareExchangeSnapshotOutcome::Mismatch + }) + } + + fn compare_exchange_snapshot_unlocked( + &self, + expected: &[CultCacheEnvelope], + replacements: &[CultCacheEnvelope], + ) -> Result { + let current = self.read_all_unlocked()?; + if current != expected { + return Ok(false); + } + let mut replacement_ids = BTreeSet::new(); + for replacement in replacements { + if replacement.key.trim().is_empty() || replacement.r#type.trim().is_empty() { + return Err(anyhow!("CultCache snapshot replacement identity is empty")); + } + if !replacement_ids.insert(entry_id(replacement)) { + return Err(anyhow!( + "CultCache snapshot replacement contains a duplicate identity" + )); + } + } + let mut ordered = replacements.to_vec(); + ordered.sort_by_key(entry_id); + self.write_all_unlocked(&ordered)?; + Ok(true) + } + + fn try_with_exclusive_lock(&self, action: impl FnOnce() -> Result) -> Result> { + let lock = self.open_lock_file()?; + match fs2::FileExt::try_lock_exclusive(&lock) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => return Ok(None), + Err(error) => { + return Err(error) + .with_context(|| format!("failed to lock {}", self.lock_path().display())); + } + } + let result = action(); + fs2::FileExt::unlock(&lock) + .with_context(|| format!("failed to unlock {}", self.lock_path().display()))?; + result.map(Some) + } + fn open_lock_file(&self) -> Result { let lock_path = self.lock_path(); if let Some(parent) = lock_path.parent() { @@ -3926,4 +4010,82 @@ mod tests { assert_eq!(reloaded.get_required::("primary")?.theme, "light"); Ok(()) } + + fn snapshot_envelope(key: &str, payload: &[u8]) -> CultCacheEnvelope { + CultCacheEnvelope { + key: key.to_string(), + r#type: "snapshot.record".to_string(), + payload: payload.to_vec(), + stored_at: "2026-01-01T00:00:00Z".to_string(), + // The store stamps the schema id from the type on write, so a + // fixture that omits it would not survive its own round trip. + schema_id: Some("snapshot.record".to_string()), + } + } + + #[test] + fn snapshot_exchange_commits_only_against_the_observed_snapshot() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("generation.cc")); + + let first = vec![snapshot_envelope("admitted", b"one")]; + assert!(store.compare_exchange_snapshot(&[], &first)?); + assert_eq!(store.pull_all_read_only_snapshot()?, first); + + // A writer holding a stale view loses the race instead of clobbering it. + let stale = vec![snapshot_envelope("admitted", b"stale")]; + assert!(!store.compare_exchange_snapshot(&[], &stale)?); + assert_eq!(store.pull_all_read_only_snapshot()?, first); + + let second = vec![snapshot_envelope("admitted", b"two")]; + assert!(store.compare_exchange_snapshot(&first, &second)?); + assert_eq!(store.pull_all_read_only_snapshot()?, second); + Ok(()) + } + + #[test] + fn try_snapshot_exchange_separates_a_lost_race_from_a_commit() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("generation.cc")); + + let first = vec![snapshot_envelope("admitted", b"one")]; + assert_eq!( + store.try_compare_exchange_snapshot(&[], &first)?, + TryCompareExchangeSnapshotOutcome::Exchanged + ); + assert_eq!( + store.try_compare_exchange_snapshot(&[], &first)?, + TryCompareExchangeSnapshotOutcome::Mismatch, + "a stale expectation is a lost race, not contention" + ); + assert_eq!(store.pull_all_read_only_snapshot()?, first); + Ok(()) + } + + #[test] + fn snapshot_exchange_refuses_replacements_that_cannot_be_addressed() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("generation.cc")); + + assert!( + store + .compare_exchange_snapshot(&[], &[snapshot_envelope(" ", b"one")]) + .is_err(), + "an unaddressable record must not reach the store" + ); + assert!( + store + .compare_exchange_snapshot( + &[], + &[ + snapshot_envelope("admitted", b"one"), + snapshot_envelope("admitted", b"two"), + ], + ) + .is_err(), + "a duplicate identity would make the snapshot ambiguous" + ); + assert_eq!(store.pull_all_read_only_snapshot()?, Vec::new()); + Ok(()) + } } diff --git a/packages/cultnet-rs/examples/cultnet_interop_peer.rs b/packages/cultnet-rs/examples/cultnet_interop_peer.rs index 1ed70a35..562756b8 100644 --- a/packages/cultnet-rs/examples/cultnet_interop_peer.rs +++ b/packages/cultnet-rs/examples/cultnet_interop_peer.rs @@ -1474,6 +1474,7 @@ impl SchemaMessageSender for RudpSessionSender<'_> { ordered: true, sequenced: false, now_ms: rudp_now_ms(), + reliable_expire_after_ms: None, }, Some(RUDP_INTEROP_MAX_FRAGMENT_BYTES), )?; diff --git a/packages/cultnet-rs/src/cultmesh.rs b/packages/cultnet-rs/src/cultmesh.rs index 20d21232..efc8acad 100644 --- a/packages/cultnet-rs/src/cultmesh.rs +++ b/packages/cultnet-rs/src/cultmesh.rs @@ -318,6 +318,7 @@ impl CultMesh { ) -> Result { let socket = bind_rudp_socket(&options)?; CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_reliable_expire_after_ms: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Server, @@ -341,6 +342,7 @@ impl CultMesh { ) -> Result { let socket = bind_rudp_socket(&options)?; CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_reliable_expire_after_ms: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Client, diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index 0ff9f322..e54cae54 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -33,6 +33,10 @@ const RUDP_FIXED_HEADER_BYTES: usize = 36; pub const CULTNET_RUDP_DEFAULT_MAX_PAYLOAD_BYTES: usize = 16 * 1024 * 1024; pub const CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS: usize = 32; const RUDP_RECEIVED_SEQUENCE_WINDOW: usize = 4_096; +/// Live media tolerates loss but not lateness: a frame retransmitted after this +/// long describes a moment that has already passed. State channels never +/// expire, so this default applies only to the media channel. +const DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS: u64 = 75; /// A peer chooses its own channel ids and its own send order, so the per-channel /// maps and the out-of-order hold buffer are both attacker-sized unless capped. const RUDP_MAX_TRACKED_CHANNELS: usize = 64; @@ -114,6 +118,11 @@ pub struct CultNetRudpSendOptions { pub ordered: bool, pub sequenced: bool, pub now_ms: u64, + /// Drop a reliable packet instead of retransmitting it once this many + /// milliseconds have passed since the send call. `None` retransmits until + /// acknowledged, which is correct for state and wrong for live media, where + /// a late frame is worse than a missing one. + pub reliable_expire_after_ms: Option, } /// Identifies every transport packet belonging to one non-expiring reliable @@ -142,6 +151,7 @@ pub enum CultNetRudpReliableSendStatus { struct PendingReliablePacket { packet: CultNetRudpPacket, last_sent_at_ms: u64, + expires_at_ms: Option, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -176,7 +186,8 @@ pub struct CultNetRudpSession { received_sequences: BTreeSet, latest_sequenced_by_channel: BTreeMap, pending_reliable: BTreeMap, - queued_reliable: VecDeque, + queued_reliable: VecDeque<(CultNetRudpPacket, Option)>, + reliable_packets_expired: u64, ordered_next_sequence_by_channel: BTreeMap, ordered_buffers: BTreeMap>, fragment_buffers: BTreeMap<(String, u16), FragmentBuffer>, @@ -202,6 +213,7 @@ impl CultNetRudpSession { latest_sequenced_by_channel: BTreeMap::new(), pending_reliable: BTreeMap::new(), queued_reliable: VecDeque::new(), + reliable_packets_expired: 0, ordered_next_sequence_by_channel: BTreeMap::new(), ordered_buffers: BTreeMap::new(), fragment_buffers: BTreeMap::new(), @@ -229,6 +241,12 @@ impl CultNetRudpSession { self.pending_reliable.keys().copied().collect() } + /// Reliable packets dropped by their expiry deadline rather than + /// acknowledged. A rising count means the peer is not keeping up. + pub fn reliable_packets_expired(&self) -> u64 { + self.reliable_packets_expired + } + pub fn queued_reliable_packet_count(&self) -> usize { self.queued_reliable.len() } @@ -327,7 +345,7 @@ impl CultNetRudpSession { true, false, ); - self.track_reliable(packet.clone(), now_ms); + self.track_reliable(packet.clone(), now_ms, None); Ok(packet) } @@ -357,7 +375,7 @@ impl CultNetRudpSession { true, false, ); - self.track_reliable(response.clone(), now_ms); + self.track_reliable(response.clone(), now_ms, None); Ok(response) } @@ -396,6 +414,12 @@ impl CultNetRudpSession { return Err(anyhow!("RUDP ordered delivery requires reliability")); } self.require_payload_size(payload.len())?; + // Reclaim deadline-passed sends before measuring window capacity, so an + // expiring stream cannot be refused for a backlog it no longer owns. + self.purge_expired_reliable(options.now_ms); + let expires_at_ms = options + .reliable_expire_after_ms + .map(|ttl| options.now_ms.saturating_add(ttl)); if let Some(max_fragment_bytes) = max_fragment_bytes { if max_fragment_bytes == 0 { @@ -426,7 +450,7 @@ impl CultNetRudpSession { packets.push(packet); } return Ok(if options.reliable { - self.admit_reliable_packets(packets, options.now_ms) + self.admit_reliable_packets(packets, options.now_ms, expires_at_ms) } else { packets }); @@ -443,7 +467,7 @@ impl CultNetRudpSession { options.sequenced, ); if packet.reliable { - return Ok(self.admit_reliable_packets(vec![packet], options.now_ms)); + return Ok(self.admit_reliable_packets(vec![packet], options.now_ms, expires_at_ms)); } Ok(vec![packet]) } @@ -455,6 +479,7 @@ impl CultNetRudpSession { ) -> Result { self.require_connection(packet)?; self.apply_acknowledgements(packet); + self.purge_expired_reliable(now_ms); let ready_to_send = self.promote_queued_reliable(now_ms); self.last_received_at_ms = Some(now_ms); let expected_sequence_if_uninitialized = self @@ -790,30 +815,52 @@ impl CultNetRudpSession { } } - fn track_reliable(&mut self, packet: CultNetRudpPacket, now_ms: u64) { + fn track_reliable( + &mut self, + packet: CultNetRudpPacket, + now_ms: u64, + expires_at_ms: Option, + ) { self.pending_reliable.insert( packet.sequence, PendingReliablePacket { packet, last_sent_at_ms: now_ms, + expires_at_ms, }, ); } + /// Drop pending and queued reliable packets past their deadline. The + /// deadline is fixed when the send is admitted, so time spent waiting in + /// the backlog counts against it exactly as time spent awaiting an ACK does. + fn purge_expired_reliable(&mut self, now_ms: u64) { + let before = self.pending_reliable.len() + self.queued_reliable.len(); + self.pending_reliable + .retain(|_, pending| pending.expires_at_ms.is_none_or(|at| now_ms <= at)); + self.queued_reliable + .retain(|(_, expires_at_ms)| expires_at_ms.is_none_or(|at| now_ms <= at)); + let after = self.pending_reliable.len() + self.queued_reliable.len(); + self.reliable_packets_expired = self + .reliable_packets_expired + .saturating_add((before - after) as u64); + } + fn admit_reliable_packets( &mut self, packets: Vec, now_ms: u64, + expires_at_ms: Option, ) -> Vec { let available = CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS.saturating_sub(self.pending_reliable.len()); let mut ready = Vec::with_capacity(available.min(packets.len())); for packet in packets { if ready.len() < available { - self.track_reliable(packet.clone(), now_ms); + self.track_reliable(packet.clone(), now_ms, expires_at_ms); ready.push(packet); } else { - self.queued_reliable.push_back(packet); + self.queued_reliable.push_back((packet, expires_at_ms)); } } ready @@ -824,10 +871,10 @@ impl CultNetRudpSession { CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS.saturating_sub(self.pending_reliable.len()); let mut ready = Vec::with_capacity(available.min(self.queued_reliable.len())); for _ in 0..available { - let Some(packet) = self.queued_reliable.pop_front() else { + let Some((packet, expires_at_ms)) = self.queued_reliable.pop_front() else { break; }; - self.track_reliable(packet.clone(), now_ms); + self.track_reliable(packet.clone(), now_ms, expires_at_ms); ready.push(packet); } ready @@ -1162,6 +1209,7 @@ pub struct CultNetRudpSocketTransportOptions { pub max_fragment_bytes: Option, pub max_pending_reliable_packets: Option, pub reconnect_policy: Option, + pub media_reliable_expire_after_ms: Option, } impl CultNetRudpSocketTransportOptions { @@ -1172,6 +1220,7 @@ impl CultNetRudpSocketTransportOptions { connection_id: u32, ) -> Self { Self { + media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Client, @@ -1189,6 +1238,7 @@ impl CultNetRudpSocketTransportOptions { pub fn server(runtime_id: impl Into, socket: UdpSocket, connection_id: u32) -> Self { Self { + media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Server, @@ -1216,6 +1266,7 @@ pub struct CultNetRudpSocketTransportConnection { max_fragment_bytes: Option, disconnect_reason: Option>, pong_payloads: VecDeque>, + media_reliable_expire_after_ms: Option, } pub struct CultNetRudpServerHubOptions { @@ -1229,11 +1280,13 @@ pub struct CultNetRudpServerHubOptions { pub max_fragment_bytes: Option, pub max_pending_reliable_packets: Option, pub max_peers: usize, + pub media_reliable_expire_after_ms: Option, } impl CultNetRudpServerHubOptions { pub fn new(runtime_id: impl Into, socket: UdpSocket, connection_id: u32) -> Self { Self { + media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), runtime_id: runtime_id.into(), socket, connection_id, @@ -1298,6 +1351,7 @@ pub struct CultNetRudpServerHub { max_payload_bytes: usize, max_fragment_bytes: Option, max_peers: usize, + media_reliable_expire_after_ms: Option, next_session_generation: u64, peers: BTreeMap, pending_events: VecDeque, @@ -1329,6 +1383,7 @@ impl CultNetRudpServerHub { }, ); Ok(Self { + media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, socket: options.socket, connection_id: options.connection_id, initial_sequence: options.initial_sequence, @@ -1353,7 +1408,13 @@ impl CultNetRudpServerHub { } pub fn stats(&self) -> CultNetTransportStats { - self.stats.clone() + let mut stats = self.stats.clone(); + stats.reliable_packets_expired = self + .peers + .values() + .map(|peer| peer.session.reliable_packets_expired()) + .fold(0_u64, |total, expired| total.saturating_add(expired)); + stats } pub fn sessions(&self) -> Vec { @@ -1388,7 +1449,7 @@ impl CultNetRudpServerHub { peer.session.send_many( channel_id, payload, - channel_send_options(channel_id, now_ms()), + channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms), self.max_fragment_bytes, )? }; @@ -1627,6 +1688,7 @@ impl CultNetRudpSocketTransportConnection { }); session.set_max_payload_bytes(max_payload_bytes.map(|value| value as usize)); Ok(Self { + media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, socket: options.socket, session, mode: options.mode, @@ -1645,7 +1707,9 @@ impl CultNetRudpSocketTransportConnection { } pub fn stats(&self) -> CultNetTransportStats { - self.stats.clone() + let mut stats = self.stats.clone(); + stats.reliable_packets_expired = self.session.reliable_packets_expired(); + stats } pub fn disconnect_reason(&self) -> Option<&[u8]> { @@ -1667,7 +1731,8 @@ impl CultNetRudpSocketTransportConnection { } pub fn send(&mut self, channel_id: &str, payload: Vec) -> Result<()> { - let options = channel_send_options(channel_id, now_ms()); + let options = + channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms); let packets = self.session .send_many(channel_id, payload, options, self.max_fragment_bytes)?; @@ -1683,7 +1748,8 @@ impl CultNetRudpSocketTransportConnection { channel_id: &str, payload: Vec, ) -> Result { - let options = channel_send_options(channel_id, now_ms()); + let options = + channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms); // This lineage has no reliable-expiry concept, so every reliable channel is // already non-expiring. Restore the expiry half of this guard if one is added. if !options.reliable { @@ -2158,31 +2224,39 @@ fn packet_type_to_code(packet_type: CultNetRudpPacketType) -> u8 { } } -fn channel_send_options(channel_id: &str, now_ms: u64) -> CultNetRudpSendOptions { +fn channel_send_options( + channel_id: &str, + now_ms: u64, + media_reliable_expire_after_ms: Option, +) -> CultNetRudpSendOptions { match channel_id { "schema" => CultNetRudpSendOptions { reliable: true, ordered: true, sequenced: false, now_ms, + reliable_expire_after_ms: None, }, "latest" => CultNetRudpSendOptions { reliable: false, ordered: false, sequenced: true, now_ms, + reliable_expire_after_ms: None, }, "media" => CultNetRudpSendOptions { reliable: true, ordered: false, sequenced: false, now_ms, + reliable_expire_after_ms: media_reliable_expire_after_ms, }, _ => CultNetRudpSendOptions { reliable: false, ordered: false, sequenced: false, now_ms, + reliable_expire_after_ms: None, }, } } diff --git a/packages/cultnet-rs/src/transport.rs b/packages/cultnet-rs/src/transport.rs index ec16cbf1..f10ad87d 100644 --- a/packages/cultnet-rs/src/transport.rs +++ b/packages/cultnet-rs/src/transport.rs @@ -19,6 +19,9 @@ pub struct CultNetTransportStats { pub bytes_sent: u64, pub frames_received: u64, pub frames_sent: u64, + /// Reliable packets dropped by a send-side expiry deadline instead of being + /// retransmitted. Always zero for transports without reliable expiry. + pub reliable_packets_expired: u64, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index d9baaac7..de84d2d9 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -1258,6 +1258,7 @@ fn rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames() max_fragment_bytes: None, max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { @@ -1273,6 +1274,7 @@ fn rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames() max_fragment_bytes: None, max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; client.connect(b"join".to_vec())?; @@ -1348,6 +1350,7 @@ fn rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames() -> max_fragment_bytes: Some(8), max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { @@ -1363,6 +1366,7 @@ fn rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames() -> max_fragment_bytes: Some(8), max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; client.connect(b"join".to_vec())?; @@ -1476,6 +1480,7 @@ fn rudp_socket_flush_waits_for_large_fragment_delivery() -> Result<()> { max_fragment_bytes: Some(8), max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { @@ -1491,6 +1496,7 @@ fn rudp_socket_flush_waits_for_large_fragment_delivery() -> Result<()> { max_fragment_bytes: Some(8), max_pending_reliable_packets: None, reconnect_policy: None, + media_reliable_expire_after_ms: None, })?; client.connect(b"join".to_vec())?; pump_rudp_handshake(&mut client, &mut server)?; @@ -2672,3 +2678,142 @@ fn tcp_framed_receive_refuses_a_length_beyond_the_advertised_maximum() -> Result ); Ok(()) } + +#[test] +fn rudp_reliable_expiry_drops_late_packets_and_leaves_durable_sends_alone() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 7, + initial_sequence: 10, + resend_delay_ms: 100, + ..CultNetRudpSessionOptions::default() + }); + accept_rudp_session(&mut sender, 7)?; + + let expiring = sender.send( + "media", + b"late".to_vec(), + CultNetRudpSendOptions { + reliable: true, + now_ms: 0, + reliable_expire_after_ms: Some(50), + ..CultNetRudpSendOptions::default() + }, + )?; + let durable = sender.send( + "schema", + b"state".to_vec(), + CultNetRudpSendOptions { + reliable: true, + now_ms: 0, + ..CultNetRudpSendOptions::default() + }, + )?; + assert_eq!(sender.pending_reliable_sequences().len(), 2); + assert_eq!(sender.reliable_packets_expired(), 0); + + // One millisecond before the deadline nothing is dropped: the boundary is + // inclusive, so a packet is never discarded early. + sender.send( + "schema", + b"tick".to_vec(), + CultNetRudpSendOptions { + reliable: false, + now_ms: 50, + ..CultNetRudpSendOptions::default() + }, + )?; + assert_eq!(sender.reliable_packets_expired(), 0); + + sender.send( + "schema", + b"tick".to_vec(), + CultNetRudpSendOptions { + reliable: false, + now_ms: 51, + ..CultNetRudpSendOptions::default() + }, + )?; + assert_eq!(sender.reliable_packets_expired(), 1); + assert_eq!( + sender.pending_reliable_sequences(), + vec![durable.sequence], + "a send without an expiry deadline is never dropped" + ); + assert_ne!(expiring.sequence, durable.sequence); + Ok(()) +} + +#[test] +fn rudp_reliable_expiry_also_reclaims_the_send_backlog() -> Result<()> { + let mut sender = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 9, + initial_sequence: 10, + resend_delay_ms: 100, + ..CultNetRudpSessionOptions::default() + }); + accept_rudp_session(&mut sender, 9)?; + + let fragment_count = CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS + 5; + let payload = (0..fragment_count * 8) + .map(|index| (index % 251) as u8) + .collect::>(); + sender.send_many( + "media", + payload, + CultNetRudpSendOptions { + reliable: true, + now_ms: 0, + reliable_expire_after_ms: Some(30), + ..CultNetRudpSendOptions::default() + }, + Some(8), + )?; + assert_eq!( + sender.pending_reliable_sequences().len(), + CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS + ); + assert_eq!(sender.queued_reliable_packet_count(), 5); + + // The deadline is fixed at admission, so waiting in the backlog spends it + // exactly as waiting for an acknowledgement does. A queued packet promoted + // after its moment has passed would be as useless as a retransmitted one. + sender.send( + "schema", + b"tick".to_vec(), + CultNetRudpSendOptions { + reliable: false, + now_ms: 31, + ..CultNetRudpSendOptions::default() + }, + )?; + assert_eq!(sender.pending_reliable_sequences().len(), 0); + assert_eq!(sender.queued_reliable_packet_count(), 0); + assert_eq!( + sender.reliable_packets_expired(), + fragment_count as u64, + "both the in-flight window and the backlog are counted" + ); + Ok(()) +} + +fn accept_rudp_session(session: &mut CultNetRudpSession, connection_id: u32) -> Result<()> { + session.receive( + &CultNetRudpPacket { + packet_type: CultNetRudpPacketType::Accept, + connection_id, + sequence: 0, + ack: 0, + ack_mask: 0, + channel_id: "control".into(), + reliable: false, + ordered: false, + sequenced: false, + fragment_id: 0, + fragment_index: 0, + fragment_count: 0, + payload: Vec::new(), + }, + 0, + )?; + Ok(()) +} diff --git a/packages/cultnet-rs/tests/rudp_server_hub.rs b/packages/cultnet-rs/tests/rudp_server_hub.rs index 801312aa..cb765a33 100644 --- a/packages/cultnet-rs/tests/rudp_server_hub.rs +++ b/packages/cultnet-rs/tests/rudp_server_hub.rs @@ -315,6 +315,7 @@ fn exact_reliable_acks_clear_more_than_one_ack_window_of_fragments() -> Result<( ordered: true, sequenced: false, now_ms: 1, + reliable_expire_after_ms: None, }, Some(1), )?; @@ -375,6 +376,7 @@ fn session_and_hub_reject_configured_memory_bounds() -> Result<()> { ordered: true, sequenced: false, now_ms: 1, + reliable_expire_after_ms: None, }, Some(2), )?; @@ -387,6 +389,7 @@ fn session_and_hub_reject_configured_memory_bounds() -> Result<()> { ordered: true, sequenced: false, now_ms: 2, + reliable_expire_after_ms: None, }; let first_set = sender.send_many( "schema", @@ -446,6 +449,7 @@ fn replay_history_is_bounded_and_idle_hub_sessions_expire() -> Result<()> { ordered: false, sequenced: false, now_ms: sequence, + reliable_expire_after_ms: None, }, )?; if oldest.is_none() { @@ -477,6 +481,7 @@ fn replay_history_is_bounded_and_idle_hub_sessions_expire() -> Result<()> { ordered: false, sequenced: false, now_ms: step, + reliable_expire_after_ms: None, }, )?; if oldest_reliable.is_none() { From 8f29ee568d8dbc100d72a444feb57f0e29387a94 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 03:32:07 +0200 Subject: [PATCH 059/203] Add the per-entry snapshot compare-exchange Odin consumes A first pass at Odin's vendored CultCache compared only its uncommitted diff against CultLib and concluded nothing else was missing. That was wrong: a whole-file symbol comparison found CultCacheExpectedEnvelope and a per-entry compare_exchange that CultLib lacks and Odin's crates import. Comparing a diff tells you what changed recently, not what the fork uniquely holds. Unlike the snapshot form this constrains only the identities it names, so an unrelated concurrent write does not lose the race. Expecting `None` makes it a create-once claim, which is what an admitted-generation owner needs. The expectation-identity guard is carried over as written even though it is unreachable: a condition whose envelope names a different identity is already rejected by the value comparison above it. Left in place rather than quietly dropped, since it costs nothing and the lookup it depends on may change. cultcache-rs: 53 tests. Co-Authored-By: Claude Opus 5 --- packages/cultcache-rs/src/lib.rs | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index 352c5e22..ba7088e5 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -284,6 +284,16 @@ pub struct PushAllOptions { pub soft: bool, } +/// One per-entry expectation for [`SingleFileMessagePackBackingStore::compare_exchange`]. +/// `current` is the envelope the caller believes is stored, or `None` when it +/// believes the identity is absent. +#[derive(Clone, Debug, PartialEq)] +pub struct CultCacheExpectedEnvelope { + pub key: String, + pub r#type: String, + pub current: Option, +} + /// Result of one nonblocking whole-snapshot compare-and-exchange. Contention is /// reported rather than waited on, so the caller keeps retry and scheduling /// authority instead of blocking inside the store. @@ -697,6 +707,52 @@ impl SingleFileMessagePackBackingStore { result } + /// Replaces the named entries only if each still matches its expectation. + /// Unlike the snapshot form this constrains only the identities named, so + /// unrelated concurrent writes do not lose the race. + pub fn compare_exchange( + &self, + expected: &[CultCacheExpectedEnvelope], + replacements: &[CultCacheEnvelope], + ) -> Result { + self.with_exclusive_lock(|| { + let mut entries = self.read_all_unlocked()?; + for condition in expected { + let current = entries + .iter() + .find(|entry| entry.r#type == condition.r#type && entry.key == condition.key); + if current != condition.current.as_ref() { + return Ok(false); + } + if let Some(envelope) = condition.current.as_ref() + && (envelope.r#type != condition.r#type || envelope.key != condition.key) + { + return Err(anyhow!( + "CultCache compare-exchange expectation identity differs from its envelope" + )); + } + } + let mut replacement_ids = BTreeSet::new(); + for replacement in replacements { + if replacement.key.trim().is_empty() || replacement.r#type.trim().is_empty() { + return Err(anyhow!( + "CultCache compare-exchange replacement identity is empty" + )); + } + if !replacement_ids.insert(entry_id(replacement)) { + return Err(anyhow!( + "CultCache compare-exchange contains a duplicate replacement" + )); + } + } + entries.retain(|entry| !replacement_ids.contains(&entry_id(entry))); + entries.extend_from_slice(replacements); + entries.sort_by_key(entry_id); + self.write_all_unlocked(&entries)?; + Ok(true) + }) + } + /// Replaces the whole snapshot only if it still equals `expected`, waiting /// for the exclusive lock. Returns false when another writer has moved the /// snapshot on, which is a lost race rather than an error. @@ -4088,4 +4144,97 @@ mod tests { assert_eq!(store.pull_all_read_only_snapshot()?, Vec::new()); Ok(()) } + + #[test] + fn per_entry_exchange_constrains_only_the_identities_it_names() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("generation.cc")); + + let admitted = snapshot_envelope("admitted", b"one"); + let unrelated = snapshot_envelope("unrelated", b"other"); + assert!(store.compare_exchange( + &[ + CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: None, + }, + CultCacheExpectedEnvelope { + key: "unrelated".into(), + r#type: "snapshot.record".into(), + current: None, + }, + ], + &[admitted.clone(), unrelated.clone()], + )?); + + // The snapshot moved, but an expectation naming only `admitted` still + // holds. A whole-snapshot exchange would have lost this race. + let next = snapshot_envelope("admitted", b"two"); + assert!(store.compare_exchange( + &[CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: Some(admitted.clone()), + }], + &[next.clone()], + )?); + assert_eq!( + store.pull_all_read_only_snapshot()?, + vec![next.clone(), unrelated.clone()] + ); + + // A stale expectation for that identity now loses. + assert!(!store.compare_exchange( + &[CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: Some(admitted), + }], + &[snapshot_envelope("admitted", b"three")], + )?); + assert_eq!(store.pull_all_read_only_snapshot()?, vec![next, unrelated]); + Ok(()) + } + + #[test] + fn per_entry_exchange_distinguishes_absent_from_stored() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = SingleFileMessagePackBackingStore::new(temp.path().join("generation.cc")); + let admitted = snapshot_envelope("admitted", b"one"); + + // Expecting a stored value where nothing is stored must not commit. + assert!(!store.compare_exchange( + &[CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: Some(admitted.clone()), + }], + &[admitted.clone()], + )?); + assert_eq!(store.pull_all_read_only_snapshot()?, Vec::new()); + + // Expecting absence commits the first writer. + assert!(store.compare_exchange( + &[CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: None, + }], + &[admitted.clone()], + )?); + + // And the second writer expecting absence now loses, which is what + // makes this usable as a create-once claim. + assert!(!store.compare_exchange( + &[CultCacheExpectedEnvelope { + key: "admitted".into(), + r#type: "snapshot.record".into(), + current: None, + }], + &[snapshot_envelope("admitted", b"two")], + )?); + assert_eq!(store.pull_all_read_only_snapshot()?, vec![admitted]); + Ok(()) + } } From cbde940df2f5ee0ccac50e8dbfe9f36ffbebb585 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 03:57:44 +0200 Subject: [PATCH 060/203] Let the advertised profile own each channel's reliable expiry The first pass put the media deadline on the transport options and carried it as a runtime field on the connection and hub. That is a second answer to what a channel does, able to disagree with the profile the peer was actually shown. CultNetTransportChannel now carries reliable_expire_after_ms and the send path resolves the deadline from the profile, so there is one answer. The runtime fields are deleted rather than left as a shadow copy. The transport option remains as the knob that populates the profile, not as a parallel authority. Odin's fork had the field in exactly this place, which is how the gap surfaced: its muninn test reads it off the channel descriptor. This is wire-visible, unlike the rest of the expiry work. The field is optional and skipped when absent, so a profile that does not set it is byte-identical to one from a runtime that has never heard of it -- but the RUDP profile does set it on the media channel, so Rust peers now advertise it. Verified against the interop lane: TypeScript, C# and Kotlin all accept the profile unchanged, in five passing lanes each. The Python lanes cannot run here because this machine's Python runtime has no msgpack, and they fail identically before and after this change. The other four runtimes should grow the field so a profile round-trips through them without losing the channel's declared behaviour. cultnet-rs: 107 tests. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/contracts.rs | 6 ++++ packages/cultnet-rs/src/rudp.rs | 48 ++++++++++++++++++++++------ packages/cultnet-rs/src/transport.rs | 1 + packages/cultnet-rs/tests/cultnet.rs | 32 +++++++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/packages/cultnet-rs/src/contracts.rs b/packages/cultnet-rs/src/contracts.rs index e637883c..b5c89ff7 100644 --- a/packages/cultnet-rs/src/contracts.rs +++ b/packages/cultnet-rs/src/contracts.rs @@ -142,6 +142,12 @@ pub struct CultNetTransportChannel { pub max_fragment_bytes: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub max_pending_reliable_packets: Option, + /// Drop a reliable packet on this channel instead of retransmitting it once + /// this many milliseconds have passed since the send. Omitted when absent, + /// so a profile that does not set it is byte-identical to one from a + /// runtime that does not know the field. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reliable_expire_after_ms: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index e54cae54..f1fd4ca4 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -1266,7 +1266,6 @@ pub struct CultNetRudpSocketTransportConnection { max_fragment_bytes: Option, disconnect_reason: Option>, pong_payloads: VecDeque>, - media_reliable_expire_after_ms: Option, } pub struct CultNetRudpServerHubOptions { @@ -1351,7 +1350,6 @@ pub struct CultNetRudpServerHub { max_payload_bytes: usize, max_fragment_bytes: Option, max_peers: usize, - media_reliable_expire_after_ms: Option, next_session_generation: u64, peers: BTreeMap, pending_events: VecDeque, @@ -1373,6 +1371,7 @@ impl CultNetRudpServerHub { let profile = create_rudp_transport_profile( options.runtime_id, RudpTransportProfileOptions { + media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, transport_id: options.transport_id, host: Some(local_addr.ip().to_string()), port: Some(local_addr.port()), @@ -1383,7 +1382,6 @@ impl CultNetRudpServerHub { }, ); Ok(Self { - media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, socket: options.socket, connection_id: options.connection_id, initial_sequence: options.initial_sequence, @@ -1449,7 +1447,11 @@ impl CultNetRudpServerHub { peer.session.send_many( channel_id, payload, - channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms), + channel_send_options( + channel_id, + now_ms(), + channel_reliable_expire_after_ms(&self.profile, channel_id), + ), self.max_fragment_bytes, )? }; @@ -1668,6 +1670,7 @@ impl CultNetRudpSocketTransportConnection { let profile = create_rudp_transport_profile( options.runtime_id, RudpTransportProfileOptions { + media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, transport_id: options.transport_id, host: Some(local_addr.ip().to_string()), port: Some(local_addr.port()), @@ -1688,7 +1691,6 @@ impl CultNetRudpSocketTransportConnection { }); session.set_max_payload_bytes(max_payload_bytes.map(|value| value as usize)); Ok(Self { - media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, socket: options.socket, session, mode: options.mode, @@ -1732,7 +1734,11 @@ impl CultNetRudpSocketTransportConnection { pub fn send(&mut self, channel_id: &str, payload: Vec) -> Result<()> { let options = - channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms); + channel_send_options( + channel_id, + now_ms(), + channel_reliable_expire_after_ms(&self.profile, channel_id), + ); let packets = self.session .send_many(channel_id, payload, options, self.max_fragment_bytes)?; @@ -1749,7 +1755,11 @@ impl CultNetRudpSocketTransportConnection { payload: Vec, ) -> Result { let options = - channel_send_options(channel_id, now_ms(), self.media_reliable_expire_after_ms); + channel_send_options( + channel_id, + now_ms(), + channel_reliable_expire_after_ms(&self.profile, channel_id), + ); // This lineage has no reliable-expiry concept, so every reliable channel is // already non-expiring. Restore the expiry half of this guard if one is added. if !options.reliable { @@ -2062,6 +2072,7 @@ pub struct RudpTransportProfileOptions { pub max_fragment_bytes: Option, pub max_pending_reliable_packets: Option, pub reconnect_policy: Option, + pub media_reliable_expire_after_ms: Option, } pub fn create_rudp_transport_profile( @@ -2092,6 +2103,7 @@ pub fn create_rudp_transport_profile( max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, max_pending_reliable_packets: options.max_pending_reliable_packets, + reliable_expire_after_ms: None, }, CultNetTransportChannel { channel_id: "latest".to_string(), @@ -2100,6 +2112,7 @@ pub fn create_rudp_transport_profile( max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, max_pending_reliable_packets: options.max_pending_reliable_packets, + reliable_expire_after_ms: None, }, CultNetTransportChannel { channel_id: "realtime".to_string(), @@ -2108,6 +2121,7 @@ pub fn create_rudp_transport_profile( max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, max_pending_reliable_packets: options.max_pending_reliable_packets, + reliable_expire_after_ms: None, }, CultNetTransportChannel { channel_id: "media".to_string(), @@ -2116,6 +2130,7 @@ pub fn create_rudp_transport_profile( max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, max_pending_reliable_packets: options.max_pending_reliable_packets, + reliable_expire_after_ms: options.media_reliable_expire_after_ms, }, ], }], @@ -2224,10 +2239,25 @@ fn packet_type_to_code(packet_type: CultNetRudpPacketType) -> u8 { } } +/// The advertised profile owns each channel's expiry. Resolving it here keeps +/// one answer for what a channel does, rather than a runtime field that can +/// disagree with the profile the peer was shown. +fn channel_reliable_expire_after_ms( + profile: &CultNetTransportProfile, + channel_id: &str, +) -> Option { + profile + .transports + .iter() + .flat_map(|transport| transport.channels.iter()) + .find(|channel| channel.channel_id == channel_id) + .and_then(|channel| channel.reliable_expire_after_ms) +} + fn channel_send_options( channel_id: &str, now_ms: u64, - media_reliable_expire_after_ms: Option, + reliable_expire_after_ms: Option, ) -> CultNetRudpSendOptions { match channel_id { "schema" => CultNetRudpSendOptions { @@ -2249,7 +2279,7 @@ fn channel_send_options( ordered: false, sequenced: false, now_ms, - reliable_expire_after_ms: media_reliable_expire_after_ms, + reliable_expire_after_ms, }, _ => CultNetRudpSendOptions { reliable: false, diff --git a/packages/cultnet-rs/src/transport.rs b/packages/cultnet-rs/src/transport.rs index f10ad87d..cf28079e 100644 --- a/packages/cultnet-rs/src/transport.rs +++ b/packages/cultnet-rs/src/transport.rs @@ -204,6 +204,7 @@ pub fn create_tcp_framed_transport_profile( max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, max_pending_reliable_packets: None, + reliable_expire_after_ms: None, }], }], } diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index de84d2d9..851646cf 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -258,6 +258,7 @@ fn cultnet_schema_messages_round_trip_through_messagepack_frames() -> Result<()> max_payload_bytes: None, max_fragment_bytes: None, max_pending_reliable_packets: None, + reliable_expire_after_ms: None, }], }], }]), @@ -365,6 +366,7 @@ fn rudp_transport_profile_advertises_state_and_realtime_channels() { max_fragment_bytes: Some(1000), max_pending_reliable_packets: Some(64), reconnect_policy: None, + media_reliable_expire_after_ms: None, }, ); @@ -2817,3 +2819,33 @@ fn accept_rudp_session(session: &mut CultNetRudpSession, connection_id: u32) -> )?; Ok(()) } + +#[test] +fn rudp_profile_expires_only_the_media_channel() -> Result<()> { + let socket = std::net::UdpSocket::bind("127.0.0.1:0")?; + let peer = socket.local_addr()?; + let options = CultNetRudpSocketTransportOptions::client("expiry-profile", socket, peer, 11); + let connection = CultNetRudpSocketTransportConnection::new(options)?; + + let channels = connection + .profile + .transports + .iter() + .flat_map(|transport| transport.channels.iter()) + .map(|channel| (channel.channel_id.as_str(), channel.reliable_expire_after_ms)) + .collect::>(); + + assert!( + channels.contains(&("media", Some(75))), + "the media channel advertises its expiry so a peer can see it: {channels:?}" + ); + for (channel_id, expiry) in &channels { + if *channel_id != "media" { + assert_eq!( + *expiry, None, + "state channel {channel_id} must never expire a reliable send" + ); + } + } + Ok(()) +} From e171eca32329baa928f4a1d810401a8b4c857029 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 04:15:59 +0200 Subject: [PATCH 061/203] Expose what the consumer forks had to reach for Surveying the swarm's vendored copies, the divergence is far smaller than the number of forks suggests. Against CultLib, Epiphany's CultCache holds nothing unique, CodexConnector's holds nothing, VoidBot's CultCache-TS holds nothing, and Ghostlight's CultCache-Py holds nothing. What the forks did hold was mostly reach: things CultLib has but does not expose. raw_document_record_from_envelope was private here and public in every Rust consumer fork. It is public now. A first pass called it missing, which was a visibility difference read as an absence -- a `pub fn` scan cannot tell those apart. CultMesh gains get_all_with_keys and soa, thin delegations to CultCache, which does support both. Those were genuinely absent rather than hidden. cultmesh-rs also needed the media expiry field on its document transports; it was not rebuilt when cultnet-rs grew that field because the earlier pass tested only two of the three Rust packages. cultcache-rs 53, cultnet-rs 107, cultmesh-rs 7. Co-Authored-By: Claude Opus 5 --- packages/cultmesh-rs/src/lib.rs | 12 ++++++++++++ packages/cultnet-rs/src/replication.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/cultmesh-rs/src/lib.rs b/packages/cultmesh-rs/src/lib.rs index 5adbb53f..e460ac78 100644 --- a/packages/cultmesh-rs/src/lib.rs +++ b/packages/cultmesh-rs/src/lib.rs @@ -1,7 +1,9 @@ use anyhow::{Context, Result}; use cultcache_rs::CultCache; use cultcache_rs::CultCacheEnvelope; +use cultcache_rs::CultSoaTable; use cultcache_rs::DatabaseEntry; +use cultcache_rs::SoaDocument; use cultcache_rs::SingleFileMessagePackBackingStore; use cultnet_rs::CultNetDocumentPutOptions; use cultnet_rs::CultNetDocumentRegistry; @@ -637,6 +639,14 @@ impl CultMeshNode { self.cache.get_required::(key) } + pub fn get_all_with_keys(&self) -> Result> { + self.cache.get_all_with_keys::() + } + + pub fn soa(&self) -> Result> { + self.cache.soa::() + } + pub fn put(&mut self, key: impl Into, value: &T) -> Result { self.cache.put(key, value) } @@ -762,6 +772,7 @@ fn request_raw_snapshot_from_rudp_catalog( socket.set_read_timeout(Some(options.poll_interval))?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_reliable_expire_after_ms: None, runtime_id: options.runtime_id.clone(), socket, mode: cultnet_rs::CultNetRudpSocketMode::Client, @@ -860,6 +871,7 @@ pub fn publish_cultnet_messages_to_rudp_catalog( socket.set_read_timeout(Some(options.poll_interval))?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_reliable_expire_after_ms: None, runtime_id: options.runtime_id.clone(), socket, mode: cultnet_rs::CultNetRudpSocketMode::Client, diff --git a/packages/cultnet-rs/src/replication.rs b/packages/cultnet-rs/src/replication.rs index d02a141f..7073f1a9 100644 --- a/packages/cultnet-rs/src/replication.rs +++ b/packages/cultnet-rs/src/replication.rs @@ -540,7 +540,7 @@ impl CultNetDocumentRegistry { }) } - fn raw_document_record_from_envelope( + pub fn raw_document_record_from_envelope( &self, envelope: &CultCacheEnvelope, ) -> Result { From 8cb3b7282b62129d23e6e5bd58bc7060f8ae7a8b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 04:41:44 +0200 Subject: [PATCH 062/203] Publish the non-Rust packages and make the licensing uniform Rust consumers pin a CultLib revision through cargo git dependencies, which is how Ghostlight, Epiphany, CodexConnector and Odin already consume the core stack. npm and pip cannot take a package from a subdirectory of a git repo at all, which is why the TypeScript and Python consumers vendored forks instead: there was nothing else to depend on. This gives them a versioned artifact. Tagging cultcache-ts-v* or cultcache-py-v* builds, tests and publishes. The tag version must match the manifest version or the workflow refuses rather than shipping a mislabelled artifact. PyPI uses trusted publishing, so no long-lived token is stored; npm needs an NPM_TOKEN secret. cultcache-ts is now @gamecult/cultcache-ts. The scope is not cosmetic: it makes the package unambiguously org-owned on a public registry, and it is what lets CultLib hold the name rather than racing for an unscoped one. CultLib's own cultmesh-ts and cultnet-ts and their imports move with it; all three packages typecheck and cultcache-ts passes 12 of 13 tests, the exception being the cross-runtime store test that shells out to Python, which cannot run on this machine because its Python has no msgpack. Licensing was inconsistent with the repo. cultcache-py declared itself "Private GameCult infrastructure" and cultcache-rs and cultmesh-rs declared nothing, inside a repo whose LICENSE is MIT and whose every other package says MIT. All three now say MIT. That was load-bearing here: a package that calls itself private is not one to push to a public registry. Co-Authored-By: Claude Opus 5 --- .github/workflows/publish-packages.yml | 114 ++++++++++++++++++ package-lock.json | 15 +-- packages/cultcache-py/pyproject.toml | 3 +- packages/cultcache-rs/Cargo.toml | 1 + packages/cultcache-ts/package.json | 2 +- packages/cultmesh-rs/Cargo.toml | 1 + packages/cultmesh-ts/package.json | 2 +- packages/cultmesh-ts/src/index.ts | 2 +- packages/cultmesh-ts/test/cultmesh.test.ts | 2 +- packages/cultnet-ts/package.json | 2 +- .../cultnet-ts/src/json-schema-contract.ts | 2 +- packages/cultnet-ts/src/replication.ts | 2 +- packages/cultnet-ts/test/cultnet.test.ts | 2 +- .../test/interop/cultnet-interop-peer.ts | 2 +- 14 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..f92868aa --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,114 @@ +name: Publish packages + +# Non-Rust consumers cannot take a package from a subdirectory of a git repo the +# way cargo can, which is why they ended up vendoring forks instead. This gives +# them a versioned artifact to depend on so CultLib stays the only source. +# +# Rust needs nothing here: cargo git dependencies pin a CultLib revision +# directly, which is already how Ghostlight, Epiphany, CodexConnector and Odin +# consume the core stack. +# +# Tag a release to publish, for example `cultcache-ts-v0.13.6`. The tag's +# version must match the version in the package manifest; the workflow refuses +# the publish otherwise rather than shipping a mislabelled artifact. + +on: + push: + tags: + - "cultcache-ts-v*" + - "cultcache-py-v*" + workflow_dispatch: + inputs: + package: + description: Package to build and test (publishes nothing on manual runs) + required: true + type: choice + options: + - cultcache-ts + - cultcache-py + +permissions: + contents: read + +jobs: + cultcache-ts: + name: "@gamecult/cultcache-ts to npm" + if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') || inputs.package == 'cultcache-ts' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out CultLib + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24.x + registry-url: https://registry.npmjs.org + + - name: Install workspace dependencies + run: npm ci + + - name: Verify the tag matches the manifest version + if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') + run: | + manifest="$(node -p "require('./packages/cultcache-ts/package.json').version")" + tagged="${GITHUB_REF_NAME#cultcache-ts-v}" + if [ "$manifest" != "$tagged" ]; then + echo "tag $GITHUB_REF_NAME does not match manifest version $manifest" >&2 + exit 1 + fi + + - name: Build and test + working-directory: packages/cultcache-ts + run: npm test + + # Scoped packages default to restricted even on public npm, and this is + # MIT like the rest of the repo, so say public explicitly. + - name: Publish + if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') + working-directory: packages/cultcache-ts + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + cultcache-py: + name: cultcache-py to PyPI + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') || inputs.package == 'cultcache-py' + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: pypi + permissions: + contents: read + # PyPI trusted publishing exchanges this for a short-lived token, so no + # long-lived API token is stored in the repository. + id-token: write + steps: + - name: Check out CultLib + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Verify the tag matches the manifest version + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + run: | + manifest="$(python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('packages/cultcache-py/pyproject.toml').read_text())['project']['version'])")" + tagged="${GITHUB_REF_NAME#cultcache-py-v}" + if [ "$manifest" != "$tagged" ]; then + echo "tag $GITHUB_REF_NAME does not match manifest version $manifest" >&2 + exit 1 + fi + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade build + python -m build packages/cultcache-py + + - name: Publish + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: packages/cultcache-py/dist diff --git a/package-lock.json b/package-lock.json index b105fb5c..391f8dfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -478,6 +478,10 @@ "node": ">=18" } }, + "node_modules/@gamecult/cultcache-ts": { + "resolved": "packages/cultcache-ts", + "link": true + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -541,10 +545,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/cultcache-ts": { - "resolved": "packages/cultcache-ts", - "link": true - }, "node_modules/cultmesh-browser": { "resolved": "packages/cultmesh-browser", "link": true @@ -831,6 +831,7 @@ } }, "packages/cultcache-ts": { + "name": "@gamecult/cultcache-ts", "version": "0.13.5", "license": "MIT", "dependencies": { @@ -864,8 +865,8 @@ "version": "0.1.1", "license": "MIT", "dependencies": { + "@gamecult/cultcache-ts": "^0.13.5", "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "^0.13.5", "cultnet-ts": "^0.2.0" }, "devDependencies": { @@ -881,9 +882,9 @@ "version": "0.2.0", "license": "MIT", "dependencies": { + "@gamecult/cultcache-ts": "^0.13.5", "@msgpack/msgpack": "^3.1.3", - "ajv": "^8.17.1", - "cultcache-ts": "^0.13.5" + "ajv": "^8.17.1" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/cultcache-py/pyproject.toml b/packages/cultcache-py/pyproject.toml index 93ebf849..9f515ac2 100644 --- a/packages/cultcache-py/pyproject.toml +++ b/packages/cultcache-py/pyproject.toml @@ -8,9 +8,10 @@ version = "0.1.0" description = "Python CultCache, CultNet, and CultMesh runtime package for GameCult typed state." readme = "README.md" requires-python = ">=3.10" -license = { text = "Private GameCult infrastructure" } +license = "MIT" authors = [{ name = "GameCult" }] classifiers = [ + "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/packages/cultcache-rs/Cargo.toml b/packages/cultcache-rs/Cargo.toml index e6ec5e59..6ca593bc 100644 --- a/packages/cultcache-rs/Cargo.toml +++ b/packages/cultcache-rs/Cargo.toml @@ -2,6 +2,7 @@ name = "cultcache-rs" version = "0.1.0" edition = "2024" +license = "MIT" publish = false [workspace] diff --git a/packages/cultcache-ts/package.json b/packages/cultcache-ts/package.json index b9df4300..93db0237 100644 --- a/packages/cultcache-ts/package.json +++ b/packages/cultcache-ts/package.json @@ -1,5 +1,5 @@ { - "name": "cultcache-ts", + "name": "@gamecult/cultcache-ts", "version": "0.13.5", "description": "CultCache-style typed persistence for TypeScript with schema-backed polymorphic MessagePack storage.", "main": "dist/index.js", diff --git a/packages/cultmesh-rs/Cargo.toml b/packages/cultmesh-rs/Cargo.toml index 9a719d32..820050c3 100644 --- a/packages/cultmesh-rs/Cargo.toml +++ b/packages/cultmesh-rs/Cargo.toml @@ -2,6 +2,7 @@ name = "cultmesh-rs" version = "0.1.0" edition = "2024" +license = "MIT" publish = false [lib] diff --git a/packages/cultmesh-ts/package.json b/packages/cultmesh-ts/package.json index 58a3c159..eff7fec4 100644 --- a/packages/cultmesh-ts/package.json +++ b/packages/cultmesh-ts/package.json @@ -43,7 +43,7 @@ }, "dependencies": { "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "^0.13.5", + "@gamecult/cultcache-ts": "^0.13.5", "cultnet-ts": "^0.2.0" }, "devDependencies": { diff --git a/packages/cultmesh-ts/src/index.ts b/packages/cultmesh-ts/src/index.ts index 2ed6e4b7..5cf26466 100644 --- a/packages/cultmesh-ts/src/index.ts +++ b/packages/cultmesh-ts/src/index.ts @@ -8,7 +8,7 @@ import { type CacheBackingStore, type CultCacheEnvelope, type CultCacheDocumentValue, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetRudpSession, diff --git a/packages/cultmesh-ts/test/cultmesh.test.ts b/packages/cultmesh-ts/test/cultmesh.test.ts index 07ac0559..16d51e1e 100644 --- a/packages/cultmesh-ts/test/cultmesh.test.ts +++ b/packages/cultmesh-ts/test/cultmesh.test.ts @@ -5,7 +5,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { encode } from "@msgpack/msgpack"; import { z } from "zod"; -import { defineDocumentType } from "cultcache-ts"; +import { defineDocumentType } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetPeer, diff --git a/packages/cultnet-ts/package.json b/packages/cultnet-ts/package.json index df129a42..5f87eccc 100644 --- a/packages/cultnet-ts/package.json +++ b/packages/cultnet-ts/package.json @@ -53,7 +53,7 @@ "dependencies": { "@msgpack/msgpack": "^3.1.3", "ajv": "^8.17.1", - "cultcache-ts": "^0.13.5" + "@gamecult/cultcache-ts": "^0.13.5" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/cultnet-ts/src/json-schema-contract.ts b/packages/cultnet-ts/src/json-schema-contract.ts index b97a6762..4d8fb85d 100644 --- a/packages/cultnet-ts/src/json-schema-contract.ts +++ b/packages/cultnet-ts/src/json-schema-contract.ts @@ -1,5 +1,5 @@ import Ajv2020, { type ErrorObject, type ValidateFunction } from "ajv/dist/2020"; -import type { CultCacheSchema } from "cultcache-ts"; +import type { CultCacheSchema } from "@gamecult/cultcache-ts"; export interface JsonSchemaContract extends CultCacheSchema { readonly schemaId: string; diff --git a/packages/cultnet-ts/src/replication.ts b/packages/cultnet-ts/src/replication.ts index d5706bea..77dab70f 100644 --- a/packages/cultnet-ts/src/replication.ts +++ b/packages/cultnet-ts/src/replication.ts @@ -6,7 +6,7 @@ import type { CultCacheDocumentFormatter, CultCacheDocumentValue, CultCacheEnvelope, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { type CultNetDocumentDeleteMessage, diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 7ffbafb9..f0c3c3c3 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -12,7 +12,7 @@ import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { CultNetClientSecurityOptions, diff --git a/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts b/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts index 0af43301..57b7fa37 100644 --- a/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts +++ b/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts @@ -6,7 +6,7 @@ import process from "node:process"; import { setTimeout as delay } from "node:timers/promises"; import { decode, encode } from "@msgpack/msgpack"; -import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, type AnyCultCacheDocumentDefinition } from "cultcache-ts"; +import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, type AnyCultCacheDocumentDefinition } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetPeer, From 77313ed48cdc219313fc5594c872b4f1ac9eaac3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 16:32:10 +0200 Subject: [PATCH 063/203] Stop the interop harness leaking peers and hanging after it passes The suite finished its work in about 60 seconds and then never exited. Four runs were left alive for hours, holding 11 node and 2 dotnet peers, and a stranded peer holding cultnet_interop_peer.exe is what made two unrelated cargo builds fail with LNK1104. In CI this is a job that burns its whole timeout on green tests. Two real defects, found by asking the stuck process what it was waiting on rather than by guessing. process.getActiveResourcesInfo reported four UDPWraps and two PipeWraps still held after the last test. The four UDP sockets: eight tests bind a server socket with bindUdpSocket and close only the transport wrapper in their finally, never the socket itself. Four leaked per run, matching the four Python lanes that fail on a machine without msgpack -- the wrapper is built inside the try, so when the peer's readiness rejects it never exists to be closed, and the raw socket is orphaned. Now closed through a guarded helper that tolerates a transport having adopted it. The teardown registration: t.after was registered after every `await ready`, so a peer whose readiness rejected skipped the registration entirely and stranded every peer already spawned. Moved ahead of the spawns at all four sites. With those fixed the suite still does not drain on its own, so test:interop now passes --test-force-exit. That is safe only because cleanup demonstrably completes first: a forced run now leaves zero peers behind, where before it left nine. Forcing the exit without the fixes above would have guaranteed orphans rather than prevented them. Verified: 65 seconds, exit 1, the expected 15 pass and 12 fail, no surviving processes. The 12 failures are the Python lanes, which cannot run on this machine and are unrelated. Co-Authored-By: Claude Opus 5 --- packages/cultnet-ts/package.json | 2 +- .../test/interop/cultnet-interop.test.ts | 62 ++++++++++++++----- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/packages/cultnet-ts/package.json b/packages/cultnet-ts/package.json index 5f87eccc..f94f0bfa 100644 --- a/packages/cultnet-ts/package.json +++ b/packages/cultnet-ts/package.json @@ -26,7 +26,7 @@ "prepack": "node tools/generate-swarm-contracts.mjs && node ../../node_modules/typescript/bin/tsc -p tsconfig.json", "typecheck": "node tools/generate-swarm-contracts.mjs && node ../../node_modules/typescript/bin/tsc -p tsconfig.json --pretty false", "test": "node tools/generate-swarm-contracts.mjs && node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node ../../node_modules/typescript/bin/tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js", - "test:interop": "node tools/generate-swarm-contracts.mjs && node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node ../../node_modules/typescript/bin/tsc -p tsconfig.test.json && node --test dist-test/test/interop/*.test.js" + "test:interop": "node tools/generate-swarm-contracts.mjs && node ../../node_modules/typescript/bin/tsc -p tsconfig.json && node ../../node_modules/typescript/bin/tsc -p tsconfig.test.json && node --test --test-force-exit dist-test/test/interop/*.test.js" }, "keywords": [ "messagepack", diff --git a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts index bec86942..4353e132 100644 --- a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts +++ b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts @@ -414,6 +414,12 @@ test("CultNet TS/Rust/C#/Python peers discover each other and exchange raw state const advertiseHost = findAdvertiseHost(); const servers: RunningServeProcess[] = []; + // Registered before anything is spawned. A peer whose readiness rejects + // would otherwise skip this and strand every peer already running. + t.after(async () => { + await Promise.all(servers.map(stopProcess)); + }); + logInteropPhase("schema-v0", "start rust peer"); servers.push(await spawnServeProcess("rust", { command: rustBinaryPath, @@ -498,10 +504,6 @@ test("CultNet TS/Rust/C#/Python peers discover each other and exchange raw state })); await servers[servers.length - 1].ready; - t.after(async () => { - await Promise.all(servers.map(stopProcess)); - }); - logInteropPhase("schema-v0", "typescript dials rust"); const tsDial = await runJsonCommand("ts-dial", process.execPath, [ tsPeerScript, @@ -989,6 +991,12 @@ test("CultNet TypeScript and Rust full interop peers exchange schema-v0 over RUD const discoveryPort = await getFreePort(); const advertiseHost = findAdvertiseHost(); const servers: RunningServeProcess[] = []; + // Registered before anything is spawned. A peer whose readiness rejects + // would otherwise skip this and strand every peer already running. + t.after(async () => { + await Promise.all(servers.map(stopProcess)); + }); + servers.push(await spawnServeProcess("rust-rudp", { command: rustBinaryPath, args: [ @@ -1030,10 +1038,6 @@ test("CultNet TypeScript and Rust full interop peers exchange schema-v0 over RUD })); await servers[servers.length - 1].ready; - t.after(async () => { - await Promise.all(servers.map(stopProcess)); - }); - logInteropPhase("ts-rust-full-rudp", "typescript dials rust over rudp"); const tsRudpDial = await runJsonCommand("ts-rust-rudp-dial", process.execPath, [ tsPeerScript, @@ -1139,6 +1143,7 @@ test("CultNet Python and TypeScript exchange schema frames when Python dials a T assert.equal(exitCode, 0, pythonClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (pythonClient.child.exitCode === null && !pythonClient.child.killed) { pythonClient.child.kill("SIGTERM"); await once(pythonClient.child, "exit").catch(() => undefined); @@ -1246,6 +1251,7 @@ test("CultNet Python and TypeScript exchange schema-v0 MessagePack messages over assert.equal(exitCode, 0, pythonClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (pythonClient.child.exitCode === null && !pythonClient.child.killed) { pythonClient.child.kill("SIGTERM"); await once(pythonClient.child, "exit").catch(() => undefined); @@ -1322,6 +1328,7 @@ test("CultNet Rust and TypeScript exchange schema frames when Rust dials a TypeS assert.equal(exitCode, 0, rustClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (rustClient.child.exitCode === null && !rustClient.child.killed) { rustClient.child.kill("SIGTERM"); await once(rustClient.child, "exit").catch(() => undefined); @@ -1431,6 +1438,7 @@ test("CultNet Rust and TypeScript exchange schema-v0 MessagePack messages over R assert.equal(exitCode, 0, rustClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (rustClient.child.exitCode === null && !rustClient.child.killed) { rustClient.child.kill("SIGTERM"); await once(rustClient.child, "exit").catch(() => undefined); @@ -1507,6 +1515,7 @@ test("CultNet C# and TypeScript exchange schema frames when C# dials a TypeScrip assert.equal(exitCode, 0, csharpClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (csharpClient.child.exitCode === null && !csharpClient.child.killed) { csharpClient.child.kill("SIGTERM"); await once(csharpClient.child, "exit").catch(() => undefined); @@ -1616,6 +1625,7 @@ test("CultNet C# and TypeScript exchange schema-v0 MessagePack messages over RUD assert.equal(exitCode, 0, csharpClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (csharpClient.child.exitCode === null && !csharpClient.child.killed) { csharpClient.child.kill("SIGTERM"); await once(csharpClient.child, "exit").catch(() => undefined); @@ -1692,6 +1702,7 @@ test("CultNet Kotlin and TypeScript exchange schema frames when Kotlin dials a T assert.equal(exitCode, 0, kotlinClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (kotlinClient.child.exitCode === null && !kotlinClient.child.killed) { kotlinClient.child.kill("SIGTERM"); await once(kotlinClient.child, "exit").catch(() => undefined); @@ -1801,6 +1812,7 @@ test("CultNet Kotlin and TypeScript exchange schema-v0 MessagePack messages over assert.equal(exitCode, 0, kotlinClient.stderr.join("")); } finally { server.close(); + closeUdpSocket(serverSocket); if (kotlinClient.child.exitCode === null && !kotlinClient.child.killed) { kotlinClient.child.kill("SIGTERM"); await once(kotlinClient.child, "exit").catch(() => undefined); @@ -1823,6 +1835,12 @@ test("CultNet TypeScript and C# full interop peers exchange schema-v0 over RUDP" const discoveryPort = await getFreePort(); const advertiseHost = findAdvertiseHost(); const servers: RunningServeProcess[] = []; + // Registered before anything is spawned. A peer whose readiness rejects + // would otherwise skip this and strand every peer already running. + t.after(async () => { + await Promise.all(servers.map(stopProcess)); + }); + servers.push(await spawnServeProcess("csharp-rudp", { command: dotnetCommand, args: [ @@ -1865,10 +1883,6 @@ test("CultNet TypeScript and C# full interop peers exchange schema-v0 over RUDP" })); await servers[servers.length - 1].ready; - t.after(async () => { - await Promise.all(servers.map(stopProcess)); - }); - logInteropPhase("ts-csharp-full-rudp", "typescript dials csharp over rudp"); const tsRudpDial = await runJsonCommand("ts-csharp-rudp-dial", process.execPath, [ tsPeerScript, @@ -1923,6 +1937,12 @@ test("CultNet TypeScript and Kotlin full interop peers exchange schema-v0 over R const discoveryPort = await getFreePort(); const advertiseHost = findAdvertiseHost(); const servers: RunningServeProcess[] = []; + // Registered before anything is spawned. A peer whose readiness rejects + // would otherwise skip this and strand every peer already running. + t.after(async () => { + await Promise.all(servers.map(stopProcess)); + }); + servers.push(await spawnServeProcess("kotlin-rudp", { command: kotlinJavaCommand, args: [ @@ -1966,10 +1986,6 @@ test("CultNet TypeScript and Kotlin full interop peers exchange schema-v0 over R })); await servers[servers.length - 1].ready; - t.after(async () => { - await Promise.all(servers.map(stopProcess)); - }); - logInteropPhase("ts-kotlin-full-rudp", "typescript dials kotlin over rudp"); const tsRudpDial = await runJsonCommand("ts-kotlin-rudp-dial", process.execPath, [ tsPeerScript, @@ -3160,6 +3176,20 @@ function spawnRustRudpMessageClient(remotePort: number): Omit { const socket = dgram.createSocket("udp4"); await new Promise((resolveBind, rejectBind) => { From ae0ac0d3c92ccaee1a7eee2bbe033740c9ea2e55 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 5 Sep 2026 17:26:44 +0200 Subject: [PATCH 064/203] Reconcile cultmesh-rs: adopt Odin's lineage, keep CultLib's additions CultLib's packages/cultmesh-rs and Odin's vendor/cultmesh-rs had diverged independently -- 87 shared symbols, 6 only here, 9 only there. Neither was simply ahead of the other, so this is a merge rather than a fast-forward. Odin's copy is the base because it is the deployed code, running in production via Idunn, and because it carries a deliberate semantic upgrade: connection_id was replaced by a per-request message_id correlation with validation requiring raw callers to supply one. That brings in the RUDP document server (647 lines) and its tests (553), which the original migration into CultLib left behind. Layered back in from CultLib's lineage: soa(), get_all_with_keys(), create_rudp_document_message(), and the plural batch publisher. The batch function was rewritten off the removed connection_id onto the connect helper. Both publish paths survive deliberately. They are different jobs, not rival implementations: the singular path polls one receipt for application-level acknowledgement and can report Invalidated, while the batch path sends and then flushes at the transport level. That is also why the census framed the 30s-versus-300ms flush_timeout as a value conflict and was wrong -- one field name, two mechanisms. The distinction is now documented at the call site. Surviving connection_id references are the RUDP protocol's own connection epoch and are unrelated to the removed options field. cargo test: 16 passed, 0 failed -- 10 lib, 6 document-server integration. Co-Authored-By: Claude Opus 5 --- packages/cultmesh-rs/src/lib.rs | 568 ++++++++++----- .../cultmesh-rs/src/rudp_document_server.rs | 647 ++++++++++++++++++ .../cultmesh-rs/tests/rudp_document_server.rs | 554 +++++++++++++++ 3 files changed, 1602 insertions(+), 167 deletions(-) create mode 100644 packages/cultmesh-rs/src/rudp_document_server.rs create mode 100644 packages/cultmesh-rs/tests/rudp_document_server.rs diff --git a/packages/cultmesh-rs/src/lib.rs b/packages/cultmesh-rs/src/lib.rs index e460ac78..7999f18b 100644 --- a/packages/cultmesh-rs/src/lib.rs +++ b/packages/cultmesh-rs/src/lib.rs @@ -3,8 +3,8 @@ use cultcache_rs::CultCache; use cultcache_rs::CultCacheEnvelope; use cultcache_rs::CultSoaTable; use cultcache_rs::DatabaseEntry; -use cultcache_rs::SoaDocument; use cultcache_rs::SingleFileMessagePackBackingStore; +use cultcache_rs::SoaDocument; use cultnet_rs::CultNetDocumentPutOptions; use cultnet_rs::CultNetDocumentRegistry; use cultnet_rs::CultNetRudpSocketTransportConnection; @@ -14,7 +14,9 @@ use cultnet_rs::decode_cultnet_message_from_slice; use cultnet_rs::encode_cultnet_message_to_vec; use serde::Serialize; use std::collections::BTreeMap; +use std::collections::hash_map::RandomState; use std::env; +use std::hash::{BuildHasher, Hasher}; use std::net::SocketAddr; use std::net::UdpSocket; use std::path::Path; @@ -22,6 +24,10 @@ use std::thread; use std::time::Duration; use std::time::Instant; +mod rudp_document_server; + +pub use rudp_document_server::*; + pub const CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID: u32 = 0x0d1d_0002; pub trait CultMeshDocumentSet: Clone + Send + Sync + 'static { @@ -74,7 +80,6 @@ pub struct CultMeshNodeOptions { pub struct CultMeshRudpDocumentPublishOptions { pub target: SocketAddr, pub runtime_id: String, - pub connection_id: u32, pub connect_timeout: Duration, pub flush_timeout: Duration, pub poll_interval: Duration, @@ -88,7 +93,10 @@ pub struct CultMeshRudpDocumentPublishOptions { pub struct CultMeshRudpSnapshotOptions { pub target: SocketAddr, pub runtime_id: String, - pub connection_id: u32, + /// Exact correlation id for the SnapshotRequest. Raw callers must supply + /// this; `CultMeshNode::pull_rudp_catalog_snapshot` generates one when it + /// owns the request. + pub message_id: String, pub connect_timeout: Duration, pub response_timeout: Duration, pub poll_interval: Duration, @@ -112,7 +120,7 @@ impl Default for CultMeshRudpSnapshotOptions { Self { target: SocketAddr::from(([0, 0, 0, 0], 0)), runtime_id: "cultmesh-rudp-snapshot-client".to_string(), - connection_id: CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, + message_id: String::new(), connect_timeout: Duration::from_secs(3), response_timeout: Duration::from_secs(3), poll_interval: Duration::from_millis(10), @@ -138,9 +146,8 @@ impl Default for CultMeshRudpDocumentPublishOptions { Self { target: SocketAddr::from(([0, 0, 0, 0], 0)), runtime_id: "cultmesh-rudp-document-publisher".to_string(), - connection_id: CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, connect_timeout: Duration::from_secs(3), - flush_timeout: Duration::from_secs(30), + flush_timeout: Duration::from_millis(300), poll_interval: Duration::from_millis(10), resend_delay_ms: 50, source_agent_id: None, @@ -673,7 +680,7 @@ impl CultMeshNode { T: DatabaseEntry + Serialize, { let message = self.create_rudp_document_message(key, value, &options)?; - publish_cultnet_messages_to_rudp_catalog(std::slice::from_ref(&message), options) + publish_cultnet_message_to_rudp_catalog(&message, options) } pub fn create_rudp_document_message( @@ -706,8 +713,16 @@ impl CultMeshNode { pub fn pull_rudp_catalog_snapshot( &mut self, - options: CultMeshRudpSnapshotOptions, + mut options: CultMeshRudpSnapshotOptions, ) -> Result { + if options.message_id.trim().is_empty() { + options.message_id = format!( + "{}:snapshot:{}:{}", + options.runtime_id, + unix_millis(), + fresh_rudp_connection_epoch() + ); + } let response = request_raw_snapshot_from_rudp_catalog(options)?; let cultnet_rs::CultNetMessage::SnapshotResponseRaw { documents, .. } = response else { anyhow::bail!("expected cultnet.snapshot_response_raw.v0"); @@ -757,58 +772,38 @@ fn strip_schema_version_suffix(value: &str) -> &str { } } -fn request_raw_snapshot_from_rudp_catalog( +/// Request a raw CultNet snapshot without importing it into a local cache. +/// +/// The returned value is the exact validated `SnapshotResponseRaw`, including +/// its correlation `message_id`. Each call binds a fresh source port, chooses a +/// fresh connection epoch, and succeeds only after the reliable request is +/// acknowledged. +pub fn request_raw_snapshot_from_rudp_catalog( options: CultMeshRudpSnapshotOptions, ) -> Result { if options.runtime_id.trim().is_empty() { anyhow::bail!("runtime_id must be non-empty"); } - let bind_addr = if options.target.is_ipv4() { - SocketAddr::from(([0, 0, 0, 0], 0)) - } else { - SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)) - }; - let socket = UdpSocket::bind(bind_addr)?; - socket.set_read_timeout(Some(options.poll_interval))?; - let mut client = - CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { - media_reliable_expire_after_ms: None, - runtime_id: options.runtime_id.clone(), - socket, - mode: cultnet_rs::CultNetRudpSocketMode::Client, - remote_addr: Some(options.target), - connection_id: options.connection_id, - initial_sequence: 1, - resend_delay_ms: options.resend_delay_ms, - transport_id: Some("cultmesh-rudp-snapshot-client".to_string()), - max_payload_bytes: None, - max_fragment_bytes: Some(1200), - max_pending_reliable_packets: None, - reconnect_policy: None, - })?; - client.connect(Vec::new())?; - let connect_deadline = Instant::now() + options.connect_timeout; - while !client.connected() && Instant::now() < connect_deadline { - let _ = client.receive_once()?; - client.poll_resends()?; - thread::sleep(options.poll_interval); - } - if !client.connected() { - anyhow::bail!( - "timed out connecting CultMesh RUDP snapshot client {} to {}", - options.runtime_id, - options.target - ); + if options.message_id.trim().is_empty() { + anyhow::bail!("raw CultMesh RUDP snapshot requires a message_id"); } + let message_id = options.message_id.clone(); + let mut client = connect_rudp_client( + options.target, + &options.runtime_id, + options.connect_timeout, + options.poll_interval, + options.resend_delay_ms, + "cultmesh-rudp-snapshot-client", + )?; - let message_id = format!("{}:snapshot:{}", options.runtime_id, unix_millis()); let request = cultnet_rs::CultNetMessage::SnapshotRequest { message_id: message_id.clone(), schema_ids: options.schema_ids, record_keys: options.record_keys, }; let payload = encode_cultnet_message_to_vec(&request, CultNetWireContract::CultNetSchemaV0)?; - client.send("schema", payload)?; + let receipt = client.send_reliable("schema", payload)?; let response_deadline = Instant::now() + options.response_timeout; let mut snapshot_response = None; while Instant::now() < response_deadline { @@ -824,7 +819,6 @@ fn request_raw_snapshot_from_rudp_catalog( .. } if received == &message_id => { snapshot_response = Some(message); - break; } cultnet_rs::CultNetMessage::Error { error } => { let _ = client.disconnect(b"snapshot-error".to_vec()); @@ -834,14 +828,28 @@ fn request_raw_snapshot_from_rudp_catalog( } } } + match client.reliable_send_status(&receipt) { + cultnet_rs::CultNetRudpReliableSendStatus::Acknowledged + if snapshot_response.is_some() => + { + let _ = client.disconnect(b"snapshot-complete".to_vec()); + return Ok(snapshot_response.expect("response was checked as present")); + } + cultnet_rs::CultNetRudpReliableSendStatus::Invalidated => { + anyhow::bail!("CultMesh RUDP snapshot request receipt was invalidated") + } + _ => {} + } client.poll_resends()?; thread::sleep(options.poll_interval); } - if let Some(message) = snapshot_response { - let _ = client.disconnect(b"snapshot-complete".to_vec()); - return Ok(message); - } let _ = client.disconnect(b"snapshot-timeout".to_vec()); + if snapshot_response.is_some() { + anyhow::bail!( + "timed out waiting for reliable acknowledgement of CultMesh RUDP snapshot request to {}", + options.target + ); + } anyhow::bail!( "timed out waiting for CultMesh RUDP snapshot from {}", options.target @@ -855,6 +863,58 @@ fn unix_millis() -> u128 { .as_millis() } +/// Publish one raw CultNet schema message to a RUDP catalog. +/// +/// Each call owns a fresh connection epoch and returns only after the catalog +/// acknowledges reliable application admission. +pub fn publish_cultnet_message_to_rudp_catalog( + message: &cultnet_rs::CultNetMessage, + options: CultMeshRudpDocumentPublishOptions, +) -> Result<()> { + if options.runtime_id.trim().is_empty() { + anyhow::bail!("runtime_id must be non-empty"); + } + let mut client = connect_rudp_client( + options.target, + &options.runtime_id, + options.connect_timeout, + options.poll_interval, + options.resend_delay_ms, + "cultmesh-rudp-document-publisher", + )?; + + let payload = encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?; + let receipt = client.send_reliable("schema", payload)?; + let flush_deadline = Instant::now() + options.flush_timeout; + while Instant::now() < flush_deadline { + let _ = client.receive_once()?; + match client.reliable_send_status(&receipt) { + cultnet_rs::CultNetRudpReliableSendStatus::Acknowledged => { + let _ = client.disconnect(b"document-acknowledged".to_vec()); + return Ok(()); + } + cultnet_rs::CultNetRudpReliableSendStatus::Invalidated => { + anyhow::bail!("CultMesh RUDP document receipt was invalidated") + } + cultnet_rs::CultNetRudpReliableSendStatus::Pending => {} + } + client.poll_resends()?; + thread::sleep(options.poll_interval); + } + let _ = client.disconnect(b"document-unacknowledged".to_vec()); + anyhow::bail!( + "timed out waiting for reliable acknowledgement from CultMesh RUDP catalog {}", + options.target + ) +} + +/// Publish a batch of raw CultNet schema messages to a RUDP catalog. +/// +/// Unlike [`publish_cultnet_message_to_rudp_catalog`], which polls for +/// per-message application-level acknowledgement, this sends every message on +/// one connection and waits only for a transport-level `flush_reliable`. It +/// trades per-receipt confirmation for batch throughput; callers that need a +/// stronger delivery guarantee should use the singular publish path instead. pub fn publish_cultnet_messages_to_rudp_catalog( messages: &[cultnet_rs::CultNetMessage], options: CultMeshRudpDocumentPublishOptions, @@ -862,43 +922,14 @@ pub fn publish_cultnet_messages_to_rudp_catalog( if options.runtime_id.trim().is_empty() { anyhow::bail!("runtime_id must be non-empty"); } - let bind_addr = if options.target.is_ipv4() { - SocketAddr::from(([0, 0, 0, 0], 0)) - } else { - SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)) - }; - let socket = UdpSocket::bind(bind_addr)?; - socket.set_read_timeout(Some(options.poll_interval))?; - let mut client = - CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { - media_reliable_expire_after_ms: None, - runtime_id: options.runtime_id.clone(), - socket, - mode: cultnet_rs::CultNetRudpSocketMode::Client, - remote_addr: Some(options.target), - connection_id: options.connection_id, - initial_sequence: 1, - resend_delay_ms: options.resend_delay_ms, - transport_id: Some("cultmesh-rudp-document-publisher".to_string()), - max_payload_bytes: None, - max_fragment_bytes: Some(1200), - max_pending_reliable_packets: None, - reconnect_policy: None, - })?; - client.connect(Vec::new())?; - let connect_deadline = Instant::now() + options.connect_timeout; - while !client.connected() && Instant::now() < connect_deadline { - let _ = client.receive_once()?; - client.poll_resends()?; - thread::sleep(options.poll_interval); - } - if !client.connected() { - anyhow::bail!( - "timed out connecting CultMesh RUDP document publisher {} to {}", - options.runtime_id, - options.target - ); - } + let mut client = connect_rudp_client( + options.target, + &options.runtime_id, + options.connect_timeout, + options.poll_interval, + options.resend_delay_ms, + "cultmesh-rudp-document-publisher", + )?; for message in messages { let payload = encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?; @@ -907,6 +938,57 @@ pub fn publish_cultnet_messages_to_rudp_catalog( client.flush_reliable(options.flush_timeout) } +fn connect_rudp_client( + target: SocketAddr, + runtime_id: &str, + connect_timeout: Duration, + poll_interval: Duration, + resend_delay_ms: u64, + transport_id: &str, +) -> Result { + let bind_addr = if target.is_ipv4() { + SocketAddr::from(([0, 0, 0, 0], 0)) + } else { + SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)) + }; + let socket = UdpSocket::bind(bind_addr)?; + socket.set_read_timeout(Some(poll_interval))?; + let mut client = + CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + runtime_id: runtime_id.to_string(), + socket, + mode: cultnet_rs::CultNetRudpSocketMode::Client, + remote_addr: Some(target), + connection_id: fresh_rudp_connection_epoch(), + initial_sequence: 1, + resend_delay_ms, + transport_id: Some(transport_id.to_string()), + max_payload_bytes: None, + max_fragment_bytes: Some(1200), + max_pending_reliable_packets: Some(1024), + media_reliable_expire_after_ms: None, + reconnect_policy: None, + })?; + client.connect(Vec::new())?; + let connect_deadline = Instant::now() + connect_timeout; + while !client.connected() && Instant::now() < connect_deadline { + let _ = client.receive_once()?; + client.poll_resends()?; + thread::sleep(poll_interval); + } + if !client.connected() { + anyhow::bail!("timed out connecting CultMesh RUDP client {runtime_id} to {target}"); + } + Ok(client) +} + +fn fresh_rudp_connection_epoch() -> u32 { + let mut hasher = RandomState::new().build_hasher(); + hasher.write_u128(unix_millis()); + let epoch = hasher.finish() as u32; + if epoch == 0 { 1 } else { epoch } +} + #[derive(Clone, Copy, Debug, Default)] pub struct CultMesh; @@ -1122,33 +1204,33 @@ mod tests { socket.set_read_timeout(Some(Duration::from_millis(10)))?; let target = socket.local_addr()?; let (sender, receiver) = std::sync::mpsc::channel(); + let delivered = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let delivered_by_sink = delivered.clone(); let server = thread::spawn(move || -> Result<()> { - let mut server = CultNetRudpSocketTransportConnection::new( - CultNetRudpSocketTransportOptions::server( - "odin-test-catalog", - socket, - CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, - ), + let mut server = CultMeshRudpDocumentServer::new( + socket, + move |receipt: CultMeshRudpRawDocumentReceipt| { + sender + .send(( + receipt.document.schema_id, + receipt.document.record_key, + receipt.document.source_runtime_id, + )) + .ok(); + delivered_by_sink.store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) + }, + |_query: &CultMeshRudpSnapshotQuery| Ok(Vec::new()), + CultMeshSystemClock::default(), + CultMeshRudpDocumentServerOptions::default(), )?; let deadline = Instant::now() + Duration::from_secs(2); while Instant::now() < deadline { - if let Some(frame) = server.receive_once()? { - let message = cultnet_rs::decode_cultnet_message_from_slice( - &frame.payload, - CultNetWireContract::CultNetSchemaV0, - )?; - if let cultnet_rs::CultNetMessage::DocumentPutRaw { document, .. } = message { - sender - .send(( - document.schema_id, - document.record_key, - document.source_runtime_id, - )) - .ok(); - return Ok(()); - } + server.poll_once()?; + if delivered.load(std::sync::atomic::Ordering::SeqCst) { + return Ok(()); } - server.poll_resends()?; + thread::sleep(Duration::from_millis(1)); } anyhow::bail!("timed out waiting for RUDP document put") }); @@ -1168,79 +1250,231 @@ mod tests { } #[test] - fn large_rudp_publication_waits_for_acknowledged_delivery() -> Result<()> { + fn document_publish_fails_when_catalog_is_unreachable() -> Result<()> { let temp = tempfile::tempdir()?; let node = CultMesh::create_node( temp.path().join("cultmesh.cc"), TestDocuments, - CultMeshNodeOptions { - runtime_id: "large-publisher-test".into(), - ..CultMeshNodeOptions::default() - }, + Default::default(), + )?; + let unused = UdpSocket::bind("127.0.0.1:0")?; + let target = unused.local_addr()?; + drop(unused); + + let error = node + .publish_document_to_rudp_catalog( + "note", + &Note { + body: "no false success".into(), + }, + CultMeshRudpDocumentPublishOptions { + target, + connect_timeout: Duration::from_millis(30), + poll_interval: Duration::from_millis(2), + ..CultMeshRudpDocumentPublishOptions::default() + }, + ) + .expect_err("unreachable catalog must fail closed"); + assert!(error.to_string().contains("timed out connecting")); + Ok(()) + } + + #[test] + fn document_publish_requires_application_acknowledgement() -> Result<()> { + let temp = tempfile::tempdir()?; + let node = CultMesh::create_node( + temp.path().join("cultmesh.cc"), + TestDocuments, + Default::default(), )?; let socket = UdpSocket::bind("127.0.0.1:0")?; - socket.set_read_timeout(Some(Duration::from_millis(10)))?; let target = socket.local_addr()?; - let options = CultMeshRudpDocumentPublishOptions::odin(target, "large-publisher-test"); - let messages = [ - node.create_rudp_document_message( - "large-a", - &Note { - body: "a".repeat(256 * 1024), + let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let server_stop = stop.clone(); + let server = thread::spawn(move || -> Result<()> { + let mut server = CultMeshRudpDocumentServer::new( + socket, + |_receipt: CultMeshRudpRawDocumentReceipt| { + anyhow::bail!("injected durable sink failure") }, - &options, - )?, - node.create_rudp_document_message( - "large-b", - &Note { - body: "b".repeat(256 * 1024), - }, - &options, - )?, - ]; - - let server = thread::spawn(move || -> Result> { - thread::sleep(Duration::from_millis(500)); - let mut server = CultNetRudpSocketTransportConnection::new( - CultNetRudpSocketTransportOptions::server( - "delayed-odin-test", - socket, - CULTMESH_RUDP_DOCUMENT_CATALOG_CONNECTION_ID, - ), + |_query: &CultMeshRudpSnapshotQuery| Ok(Vec::new()), + CultMeshSystemClock::default(), + CultMeshRudpDocumentServerOptions::default(), )?; - let deadline = Instant::now() + Duration::from_secs(5); - let mut record_keys = Vec::new(); - while Instant::now() < deadline && record_keys.len() < 2 { - if let Some(frame) = server.receive_once()? { - let message = decode_cultnet_message_from_slice( - &frame.payload, - CultNetWireContract::CultNetSchemaV0, - )?; - if let cultnet_rs::CultNetMessage::DocumentPutRaw { document, .. } = message { - record_keys.push(document.record_key); - } - } - server.poll_resends()?; + while !server_stop.load(std::sync::atomic::Ordering::SeqCst) { + let _ = server.poll_once(); + thread::sleep(Duration::from_millis(1)); } - let linger_deadline = Instant::now() + Duration::from_millis(250); - while Instant::now() < linger_deadline { - let _ = server.receive_once()?; - server.poll_resends()?; - } - if record_keys.len() != 2 { - anyhow::bail!("timed out waiting for both large RUDP documents"); - } - Ok(record_keys) + Ok(()) }); - publish_cultnet_messages_to_rudp_catalog(&messages, options)?; + let error = node + .publish_document_to_rudp_catalog( + "note", + &Note { + body: "still not accepted".into(), + }, + CultMeshRudpDocumentPublishOptions { + target, + connect_timeout: Duration::from_millis(100), + flush_timeout: Duration::from_millis(50), + poll_interval: Duration::from_millis(2), + resend_delay_ms: 5, + ..CultMeshRudpDocumentPublishOptions::default() + }, + ) + .expect_err("unacknowledged document must fail closed"); + stop.store(true, std::sync::atomic::Ordering::SeqCst); + server.join().expect("server thread should not panic")?; + assert!(error.to_string().contains("reliable acknowledgement")); + Ok(()) + } + + #[test] + fn raw_snapshot_preserves_caller_challenge_and_raw_response() -> Result<()> { + let expected = cultnet_rs::CultNetRawDocumentRecord { + schema_id: "cultmesh.test.raw.v0".into(), + record_key: "route-proof".into(), + stored_at: "2026-09-03T00:00:00Z".into(), + payload_encoding: cultnet_rs::CultNetRawPayloadEncoding::Messagepack, + payload: vec![0xc4, 0x03, 0, 0xff, 1], + source_runtime_id: Some("odin-test".into()), + source_agent_id: None, + source_role: None, + tags: None, + }; + let socket = UdpSocket::bind("127.0.0.1:0")?; + let target = socket.local_addr()?; + let challenge = "9aa63ad8-83b5-4cc4-ae4a-91d361d5f845".to_string(); + let server_document = expected.clone(); + let server_challenge = challenge.clone(); + let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let server_stop = stop.clone(); + let server = thread::spawn(move || -> Result<()> { + let mut server = CultMeshRudpDocumentServer::new( + socket, + |_receipt: CultMeshRudpRawDocumentReceipt| Ok(()), + move |query: &CultMeshRudpSnapshotQuery| { + if query.message_id != server_challenge { + anyhow::bail!("snapshot challenge changed") + } + Ok(vec![server_document.clone()]) + }, + CultMeshSystemClock::default(), + CultMeshRudpDocumentServerOptions::default(), + )?; + while !server_stop.load(std::sync::atomic::Ordering::SeqCst) { + server.poll_once()?; + thread::sleep(Duration::from_millis(1)); + } + Ok(()) + }); + + let response = request_raw_snapshot_from_rudp_catalog(CultMeshRudpSnapshotOptions { + target, + runtime_id: "route-proof-test".into(), + message_id: challenge.clone(), + connect_timeout: Duration::from_millis(100), + response_timeout: Duration::from_millis(500), + poll_interval: Duration::from_millis(2), + resend_delay_ms: 5, + schema_ids: Some(vec!["cultmesh.test.raw.v0".into()]), + record_keys: Some(vec!["route-proof".into()]), + }); + stop.store(true, std::sync::atomic::Ordering::SeqCst); + server.join().expect("server thread should not panic")?; + let response = response?; assert_eq!( - server.join().expect("server thread should not panic")?, - vec!["large-a", "large-b"] + response, + cultnet_rs::CultNetMessage::SnapshotResponseRaw { + message_id: challenge, + documents: vec![expected], + } ); Ok(()) } + #[test] + fn raw_snapshot_rejects_response_without_request_acknowledgement() -> Result<()> { + let socket = UdpSocket::bind("127.0.0.1:0")?; + socket.set_read_timeout(Some(Duration::from_millis(200)))?; + let target = socket.local_addr()?; + let server = thread::spawn(move || -> Result<()> { + let mut wire = vec![0_u8; 65_535]; + let (received, peer) = socket.recv_from(&mut wire)?; + let connect = cultnet_rs::decode_rudp_packet(&wire[..received])?; + let mut session = + cultnet_rs::CultNetRudpSession::new(cultnet_rs::CultNetRudpSessionOptions { + connection_id: connect.connection_id, + initial_sequence: 1, + resend_delay_ms: 5, + max_pending_reliable_packets: Some(64), + }); + let accept = session.accept_connect(&connect, 1, Vec::new())?; + socket.send_to(&cultnet_rs::encode_rudp_packet(&accept)?, peer)?; + + loop { + let (received, received_peer) = socket.recv_from(&mut wire)?; + if received_peer != peer { + continue; + } + let packet = cultnet_rs::decode_rudp_packet(&wire[..received])?; + let result = session.receive(&packet, 2)?; + let Some(frame) = result.delivered.into_iter().next() else { + continue; + }; + let request = decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + )?; + let cultnet_rs::CultNetMessage::SnapshotRequest { message_id, .. } = request else { + anyhow::bail!("expected snapshot request") + }; + let payload = encode_cultnet_message_to_vec( + &cultnet_rs::CultNetMessage::SnapshotResponseRaw { + message_id, + documents: Vec::new(), + }, + CultNetWireContract::CultNetSchemaV0, + )?; + let mut packets = session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { + reliable: true, + ordered: true, + sequenced: false, + now_ms: 2, + reliable_expire_after_ms: None, + }, + Some(1200), + )?; + for packet in &mut packets { + packet.ack = 0; + packet.ack_mask = 0; + socket.send_to(&cultnet_rs::encode_rudp_packet(packet)?, peer)?; + } + return Ok(()); + } + }); + + let error = request_raw_snapshot_from_rudp_catalog(CultMeshRudpSnapshotOptions { + target, + runtime_id: "unacknowledged-snapshot-test".into(), + message_id: "b7987efb-69c5-4c42-9355-b6e09535ad21".into(), + connect_timeout: Duration::from_millis(100), + response_timeout: Duration::from_millis(50), + poll_interval: Duration::from_millis(2), + resend_delay_ms: 5, + schema_ids: None, + record_keys: None, + }) + .expect_err("response without request ACK must fail closed"); + server.join().expect("server thread should not panic")?; + assert!(error.to_string().contains("reliable acknowledgement")); + Ok(()) + } + #[test] fn resolves_rudp_endpoint_from_cultmesh_uri_bootstrap_env() -> Result<()> { let previous = env::var("CULTMESH_URI_ODIN_RUDP").ok(); diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs new file mode 100644 index 00000000..bebeb6c3 --- /dev/null +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -0,0 +1,647 @@ +use anyhow::{Result, anyhow}; +use cultnet_rs::{ + CultNetMessage, CultNetRawDocumentRecord, CultNetRudpPacket, CultNetRudpPacketType, + CultNetRudpSendOptions, CultNetRudpSession, CultNetRudpSessionOptions, CultNetWireContract, + decode_cultnet_message_from_slice, decode_rudp_packet, encode_cultnet_message_to_vec, + encode_rudp_packet, +}; +use std::collections::BTreeMap; +use std::io::ErrorKind; +use std::net::{SocketAddr, UdpSocket}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +const MAX_UDP_DATAGRAM_BYTES: usize = 65_535; + +/// The transport identity of one remote CultNet RUDP session. +/// +/// Connection identifiers are scoped to a remote socket address. Two clients +/// may therefore use the same connection identifier without sharing ordering, +/// acknowledgement, resend, or fragment state. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CultMeshRudpSessionKey { + pub remote_addr: SocketAddr, + pub connection_id: u32, +} + +/// A raw document plus the receipt fact owned by the receiving process. +/// +/// `document.payload` is the exact byte vector decoded from the CultNet raw +/// record. The trusted receipt time lives beside the sender-owned record so the +/// transport never rewrites its metadata or payload. +#[derive(Clone, Debug, PartialEq)] +pub struct CultMeshRudpRawDocumentReceipt { + pub session: CultMeshRudpSessionKey, + pub message_id: String, + pub transport_sequence: u32, + pub received_at_unix_millis: u64, + pub document: CultNetRawDocumentRecord, +} + +/// The caller-visible query for a read-only raw snapshot. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpSnapshotQuery { + pub session: CultMeshRudpSessionKey, + pub message_id: String, + pub requested_at_unix_millis: u64, + pub schema_ids: Option>, + pub record_keys: Option>, +} + +/// Caller-owned admission/persistence port for received raw documents. +pub trait CultMeshRudpRawDocumentSink { + fn accept_raw_document(&mut self, receipt: CultMeshRudpRawDocumentReceipt) -> Result<()>; +} + +impl CultMeshRudpRawDocumentSink for F +where + F: FnMut(CultMeshRudpRawDocumentReceipt) -> Result<()>, +{ + fn accept_raw_document(&mut self, receipt: CultMeshRudpRawDocumentReceipt) -> Result<()> { + self(receipt) + } +} + +/// Caller-owned catalog port for serving raw snapshot requests. +/// +/// The caller decides which records the requester may see. CultMesh preserves +/// those records as raw CultNet documents and does not interpret their schemas. +pub trait CultMeshRudpSnapshotSource { + fn raw_snapshot( + &mut self, + query: &CultMeshRudpSnapshotQuery, + ) -> Result>; +} + +impl CultMeshRudpSnapshotSource for F +where + F: FnMut(&CultMeshRudpSnapshotQuery) -> Result>, +{ + fn raw_snapshot( + &mut self, + query: &CultMeshRudpSnapshotQuery, + ) -> Result> { + self(query) + } +} + +/// Trusted local clocks for receipt facts and transport maintenance. +/// +/// Receipt time is wall time. Expiry and resend decisions use the monotonic +/// value, so a wall-clock correction cannot resurrect or pin a session. +pub trait CultMeshRudpServerClock { + fn now_unix_millis(&self) -> u64; + fn now_monotonic_millis(&self) -> u64; +} + +#[derive(Clone, Debug)] +pub struct CultMeshSystemClock { + monotonic_origin: Instant, +} + +impl Default for CultMeshSystemClock { + fn default() -> Self { + Self { + monotonic_origin: Instant::now(), + } + } +} + +impl CultMeshRudpServerClock for CultMeshSystemClock { + fn now_unix_millis(&self) -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() + .try_into() + .unwrap_or(u64::MAX) + } + + fn now_monotonic_millis(&self) -> u64 { + self.monotonic_origin + .elapsed() + .as_millis() + .try_into() + .unwrap_or(u64::MAX) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpDocumentServerOptions { + pub max_sessions: usize, + pub session_idle_timeout: Duration, + pub session_max_lifetime: Duration, + /// Conservative payload admission budget across all live sessions. + /// + /// Admitted ingress and queued snapshot-response bytes are charged until + /// the session ends even when CultNet releases them sooner. Together with + /// CultNet's per-session receive caps, this makes retained payload memory + /// finite without duplicating its fragment/ordering machinery here. + pub max_admitted_payload_bytes: usize, + pub max_admitted_payload_bytes_per_session: usize, + pub max_snapshot_response_bytes: usize, + pub max_snapshot_documents: usize, + pub resend_delay: Duration, + pub max_pending_reliable_packets_per_session: usize, + pub max_fragment_bytes: usize, +} + +impl Default for CultMeshRudpDocumentServerOptions { + fn default() -> Self { + Self { + max_sessions: 64, + session_idle_timeout: Duration::from_secs(30), + session_max_lifetime: Duration::from_secs(15 * 60), + max_admitted_payload_bytes: 32 * 1024 * 1024, + max_admitted_payload_bytes_per_session: 4 * 1024 * 1024, + max_snapshot_response_bytes: 1024 * 1024, + max_snapshot_documents: 4096, + resend_delay: Duration::from_millis(50), + max_pending_reliable_packets_per_session: 1024, + max_fragment_bytes: 1200, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CultMeshRudpMaintenance { + pub sessions_expired: usize, + pub packets_resent: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CultMeshRudpApplicationOperation { + DocumentPutRaw, + SnapshotRequest, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CultMeshRudpApplicationRejection { + pub session: CultMeshRudpSessionKey, + pub operation: CultMeshRudpApplicationOperation, + pub message_id: String, + pub reason: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CultMeshRudpPollOutcome { + Idle, + Handled, + ApplicationRejected(CultMeshRudpApplicationRejection), +} + +struct SessionEntry { + session: CultNetRudpSession, + created_at_monotonic_millis: u64, + last_activity_monotonic_millis: u64, + admitted_payload_bytes: usize, +} + +/// A synchronous, multi-session CultNet RUDP raw-document server. +/// +/// The server owns exactly one UDP socket. Call `poll_once` from an existing +/// daemon loop; no executor or background thread is created here. +pub struct CultMeshRudpDocumentServer { + socket: UdpSocket, + sessions: BTreeMap, + sink: S, + snapshot_source: Q, + clock: C, + options: CultMeshRudpDocumentServerOptions, +} + +impl CultMeshRudpDocumentServer +where + S: CultMeshRudpRawDocumentSink, + Q: CultMeshRudpSnapshotSource, + C: CultMeshRudpServerClock, +{ + pub fn new( + socket: UdpSocket, + sink: S, + snapshot_source: Q, + clock: C, + options: CultMeshRudpDocumentServerOptions, + ) -> Result { + validate_options(&options)?; + socket.local_addr().map_err(|error| { + anyhow!("CultMesh RUDP server requires a bound UDP socket: {error}") + })?; + socket.set_nonblocking(true)?; + Ok(Self { + socket, + sessions: BTreeMap::new(), + sink, + snapshot_source, + clock, + options, + }) + } + + pub fn local_addr(&self) -> Result { + Ok(self.socket.local_addr()?) + } + + pub fn session_count(&self) -> usize { + self.sessions.len() + } + + /// Poll transport maintenance and receive at most one UDP datagram. + /// + /// Application rejection invalidates only the responsible peer session and + /// is returned as data so a daemon can log it and keep serving. Local + /// socket and server-state failures remain errors. + pub fn poll_once(&mut self) -> Result { + self.maintain()?; + let mut wire = vec![0_u8; MAX_UDP_DATAGRAM_BYTES]; + let (received, remote_addr) = match self.socket.recv_from(&mut wire) { + Ok(value) => value, + Err(error) if error.kind() == ErrorKind::WouldBlock => { + return Ok(CultMeshRudpPollOutcome::Idle); + } + Err(error) => return Err(error.into()), + }; + wire.truncate(received); + + let packet = match decode_rudp_packet(&wire) { + Ok(packet) => packet, + Err(_) => return Ok(CultMeshRudpPollOutcome::Handled), + }; + let now_unix = self.clock.now_unix_millis(); + let now_monotonic = self.clock.now_monotonic_millis(); + let key = CultMeshRudpSessionKey { + remote_addr, + connection_id: packet.connection_id, + }; + + if packet.packet_type == CultNetRudpPacketType::Connect { + self.accept_connection(key, &packet, now_monotonic)?; + return Ok(CultMeshRudpPollOutcome::Handled); + } + + if !self.sessions.contains_key(&key) { + return Ok(CultMeshRudpPollOutcome::Handled); + } + if packet.packet_type == CultNetRudpPacketType::Data + && packet.channel_id == "schema" + && !packet.reliable + { + return Ok(CultMeshRudpPollOutcome::Handled); + } + let payload_charge = if packet.packet_type == CultNetRudpPacketType::Data { + packet.payload.len() + } else { + 0 + }; + if !self.payload_budget_allows(key, payload_charge) { + return Ok(CultMeshRudpPollOutcome::Handled); + } + + let result = { + let entry = self + .sessions + .get_mut(&key) + .expect("checked session must remain present"); + entry.last_activity_monotonic_millis = now_monotonic; + entry.admitted_payload_bytes = + entry.admitted_payload_bytes.saturating_add(payload_charge); + entry.session.receive(&packet, now_monotonic) + }; + let result = match result { + Ok(result) => result, + Err(_) => { + self.sessions.remove(&key); + return Ok(CultMeshRudpPollOutcome::Handled); + } + }; + + if let Some(reply) = result.reply { + self.send_packet(key.remote_addr, &reply)?; + } + + if result.disconnected { + self.sessions.remove(&key); + return Ok(CultMeshRudpPollOutcome::Handled); + } + + for frame in result.delivered { + if frame.channel_id != "schema" { + continue; + } + let message = match decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + ) { + Ok(message) => message, + Err(_) => continue, + }; + if let Some(rejection) = self.deliver_application_message( + key, + frame.sequence, + message, + now_unix, + now_monotonic, + )? { + self.sessions.remove(&key); + return Ok(CultMeshRudpPollOutcome::ApplicationRejected(rejection)); + } + } + + if packet.reliable { + let ack = self + .sessions + .get_mut(&key) + .ok_or_else(|| anyhow!("CultMesh RUDP session disappeared before ACK"))? + .session + .create_ack(); + self.send_packet(key.remote_addr, &ack)?; + } + Ok(CultMeshRudpPollOutcome::Handled) + } + + /// Expire idle sessions and resend reliable packets whose deadline passed. + pub fn maintain(&mut self) -> Result { + let now = self.clock.now_monotonic_millis(); + let idle_timeout_ms = duration_millis(self.options.session_idle_timeout); + let lifetime_ms = duration_millis(self.options.session_max_lifetime); + let before = self.sessions.len(); + self.sessions.retain(|_, entry| { + now.saturating_sub(entry.last_activity_monotonic_millis) <= idle_timeout_ms + && now.saturating_sub(entry.created_at_monotonic_millis) <= lifetime_ms + }); + let expired = before - self.sessions.len(); + + let mut resends = Vec::new(); + for (key, entry) in &mut self.sessions { + for packet in entry.session.due_resends(now) { + resends.push((key.remote_addr, packet)); + } + } + for (remote_addr, packet) in &resends { + self.send_packet(*remote_addr, packet)?; + } + Ok(CultMeshRudpMaintenance { + sessions_expired: expired, + packets_resent: resends.len(), + }) + } + + fn accept_connection( + &mut self, + key: CultMeshRudpSessionKey, + packet: &CultNetRudpPacket, + now: u64, + ) -> Result<()> { + if !self.sessions.contains_key(&key) { + if self.sessions.len() >= self.options.max_sessions { + return Ok(()); + } + self.sessions.insert( + key, + SessionEntry { + session: CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: key.connection_id, + initial_sequence: 1, + resend_delay_ms: duration_millis(self.options.resend_delay), + max_pending_reliable_packets: Some( + self.options.max_pending_reliable_packets_per_session, + ), + }), + created_at_monotonic_millis: now, + last_activity_monotonic_millis: now, + admitted_payload_bytes: 0, + }, + ); + } + + let accept = { + let entry = self + .sessions + .get_mut(&key) + .expect("accepted session must be present"); + // The same key is a retransmitted Connect for the existing epoch. + // A genuinely fresh client incarnation must choose a fresh id. + entry.last_activity_monotonic_millis = now; + entry.session.accept_connect(packet, now, Vec::new())? + }; + self.send_packet(key.remote_addr, &accept) + } + + fn deliver_application_message( + &mut self, + key: CultMeshRudpSessionKey, + transport_sequence: u32, + message: CultNetMessage, + now_unix: u64, + now: u64, + ) -> Result> { + match message { + CultNetMessage::DocumentPutRaw { + message_id, + document, + } => { + let receipt = CultMeshRudpRawDocumentReceipt { + session: key, + message_id: message_id.clone(), + transport_sequence, + received_at_unix_millis: now_unix, + document, + }; + if let Err(error) = self.sink.accept_raw_document(receipt) { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::DocumentPutRaw, + message_id, + reason: format!("{error:#}"), + })); + } + } + CultNetMessage::SnapshotRequest { + message_id, + schema_ids, + record_keys, + } => { + let query = CultMeshRudpSnapshotQuery { + session: key, + message_id: message_id.clone(), + requested_at_unix_millis: now_unix, + schema_ids, + record_keys, + }; + let documents = match self.snapshot_source.raw_snapshot(&query) { + Ok(documents) => documents, + Err(error) => { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: format!("{error:#}"), + })); + } + }; + if documents.len() > self.options.max_snapshot_documents { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: format!( + "snapshot returned {} documents; limit is {}", + documents.len(), + self.options.max_snapshot_documents + ), + })); + } + let response = CultNetMessage::SnapshotResponseRaw { + message_id: message_id.clone(), + documents, + }; + let payload = match encode_cultnet_message_to_vec( + &response, + CultNetWireContract::CultNetSchemaV0, + ) { + Ok(payload) => payload, + Err(error) => { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: format!("snapshot response could not be encoded: {error:#}"), + })); + } + }; + if payload.len() > self.options.max_snapshot_response_bytes { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: format!( + "snapshot response is {} bytes; limit is {}", + payload.len(), + self.options.max_snapshot_response_bytes + ), + })); + } + if !self.payload_budget_allows(key, payload.len()) { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: "retained payload budget is full".into(), + })); + } + let payload_bytes = payload.len(); + let entry = self + .sessions + .get_mut(&key) + .ok_or_else(|| anyhow!("CultMesh RUDP session disappeared before response"))?; + let packets = match entry.session.send_many( + "schema", + payload, + CultNetRudpSendOptions { + reliable: true, + ordered: true, + sequenced: false, + now_ms: now, + reliable_expire_after_ms: None, + }, + Some(self.options.max_fragment_bytes), + ) { + Ok(packets) => packets, + Err(error) => { + return Ok(Some(CultMeshRudpApplicationRejection { + session: key, + operation: CultMeshRudpApplicationOperation::SnapshotRequest, + message_id, + reason: format!("snapshot response could not be queued: {error:#}"), + })); + } + }; + entry.admitted_payload_bytes = + entry.admitted_payload_bytes.saturating_add(payload_bytes); + for packet in &packets { + self.send_packet(key.remote_addr, packet)?; + } + } + _ => {} + } + Ok(None) + } + + fn payload_budget_allows(&self, key: CultMeshRudpSessionKey, bytes: usize) -> bool { + let Some(session_bytes) = self + .sessions + .get(&key) + .map(|entry| entry.admitted_payload_bytes) + else { + return false; + }; + if session_bytes + .checked_add(bytes) + .is_none_or(|total| total > self.options.max_admitted_payload_bytes_per_session) + { + return false; + } + self.sessions + .values() + .try_fold(0_usize, |total, entry| { + total.checked_add(entry.admitted_payload_bytes) + }) + .and_then(|total| total.checked_add(bytes)) + .is_some_and(|total| total <= self.options.max_admitted_payload_bytes) + } + + fn send_packet(&mut self, remote_addr: SocketAddr, packet: &CultNetRudpPacket) -> Result<()> { + let wire = encode_rudp_packet(packet)?; + self.socket.send_to(&wire, remote_addr)?; + Ok(()) + } +} + +fn validate_options(options: &CultMeshRudpDocumentServerOptions) -> Result<()> { + if options.max_sessions == 0 { + return Err(anyhow!("max_sessions must be greater than zero")); + } + if options.session_idle_timeout.is_zero() { + return Err(anyhow!("session_idle_timeout must be greater than zero")); + } + if options.session_max_lifetime.is_zero() { + return Err(anyhow!("session_max_lifetime must be greater than zero")); + } + if options.max_admitted_payload_bytes == 0 { + return Err(anyhow!( + "max_admitted_payload_bytes must be greater than zero" + )); + } + if options.max_admitted_payload_bytes_per_session == 0 + || options.max_admitted_payload_bytes_per_session > options.max_admitted_payload_bytes + { + return Err(anyhow!( + "per-session admitted payload limit must be non-zero and no larger than the server limit" + )); + } + if options.max_snapshot_response_bytes == 0 + || options.max_snapshot_response_bytes > options.max_admitted_payload_bytes_per_session + { + return Err(anyhow!( + "snapshot response limit must be non-zero and fit in one session's payload budget" + )); + } + if options.max_snapshot_documents == 0 { + return Err(anyhow!("max_snapshot_documents must be greater than zero")); + } + if options.resend_delay.is_zero() { + return Err(anyhow!("resend_delay must be greater than zero")); + } + if options.max_pending_reliable_packets_per_session == 0 { + return Err(anyhow!( + "max_pending_reliable_packets_per_session must be greater than zero" + )); + } + if options.max_fragment_bytes == 0 { + return Err(anyhow!("max_fragment_bytes must be greater than zero")); + } + Ok(()) +} + +fn duration_millis(duration: Duration) -> u64 { + duration.as_millis().try_into().unwrap_or(u64::MAX) +} diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs new file mode 100644 index 00000000..c0f191b4 --- /dev/null +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -0,0 +1,554 @@ +use anyhow::Result; +use cultmesh_rs::{ + CultMeshRudpApplicationOperation, CultMeshRudpDocumentServer, + CultMeshRudpDocumentServerOptions, CultMeshRudpPollOutcome, CultMeshRudpRawDocumentReceipt, + CultMeshRudpRawDocumentSink, CultMeshRudpServerClock, CultMeshRudpSnapshotQuery, + CultMeshRudpSnapshotSource, +}; +use cultnet_rs::{ + CultNetMessage, CultNetRawDocumentRecord, CultNetRawPayloadEncoding, + CultNetRudpReliableSendReceipt, CultNetRudpReliableSendStatus, CultNetRudpSocketMode, + CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, CultNetWireContract, + decode_cultnet_message_from_slice, encode_cultnet_message_to_vec, +}; +use pretty_assertions::assert_eq; +use std::net::{SocketAddr, UdpSocket}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +#[derive(Default)] +struct SinkState { + receipts: Vec, + failures_remaining: usize, +} + +#[derive(Clone, Default)] +struct Sink(Arc>); + +impl Sink { + fn fail_once() -> Self { + Self(Arc::new(Mutex::new(SinkState { + failures_remaining: 1, + ..Default::default() + }))) + } +} + +impl CultMeshRudpRawDocumentSink for Sink { + fn accept_raw_document(&mut self, receipt: CultMeshRudpRawDocumentReceipt) -> Result<()> { + let mut state = self.0.lock().unwrap(); + if state.failures_remaining > 0 { + state.failures_remaining -= 1; + anyhow::bail!("injected sink failure"); + } + state.receipts.push(receipt); + Ok(()) + } +} + +#[derive(Default)] +struct SourceState { + documents: Vec, + failures_remaining: usize, + calls: usize, +} + +#[derive(Clone, Default)] +struct Source(Arc>); + +impl Source { + fn documents(documents: Vec) -> Self { + Self(Arc::new(Mutex::new(SourceState { + documents, + ..Default::default() + }))) + } + + fn fail_once(documents: Vec) -> Self { + Self(Arc::new(Mutex::new(SourceState { + documents, + failures_remaining: 1, + calls: 0, + }))) + } +} + +impl CultMeshRudpSnapshotSource for Source { + fn raw_snapshot( + &mut self, + _: &CultMeshRudpSnapshotQuery, + ) -> Result> { + let mut state = self.0.lock().unwrap(); + state.calls += 1; + if state.failures_remaining > 0 { + state.failures_remaining -= 1; + anyhow::bail!("injected source failure"); + } + Ok(state.documents.clone()) + } +} + +#[derive(Clone)] +struct Clock { + unix: Arc, + monotonic: Arc, +} + +impl Clock { + fn new(now: u64) -> Self { + Self { + unix: Arc::new(AtomicU64::new(now)), + monotonic: Arc::new(AtomicU64::new(now)), + } + } + + fn set(&self, now: u64) { + self.unix.store(now, Ordering::SeqCst); + self.monotonic.store(now, Ordering::SeqCst); + } + + fn set_unix(&self, now: u64) { + self.unix.store(now, Ordering::SeqCst); + } + + fn set_monotonic(&self, now: u64) { + self.monotonic.store(now, Ordering::SeqCst); + } +} + +impl CultMeshRudpServerClock for Clock { + fn now_unix_millis(&self) -> u64 { + self.unix.load(Ordering::SeqCst) + } + + fn now_monotonic_millis(&self) -> u64 { + self.monotonic.load(Ordering::SeqCst) + } +} + +type Server = CultMeshRudpDocumentServer; + +fn document(key: &str, payload: Vec) -> CultNetRawDocumentRecord { + CultNetRawDocumentRecord { + schema_id: "test.raw.v1".into(), + record_key: key.into(), + stored_at: "2026-09-03T00:00:00Z".into(), + payload_encoding: CultNetRawPayloadEncoding::Messagepack, + payload, + source_runtime_id: Some(format!("runtime-{key}")), + source_agent_id: None, + source_role: None, + tags: None, + } +} + +fn server( + options: CultMeshRudpDocumentServerOptions, + clock: Clock, + sink: Sink, + source: Source, +) -> Result { + CultMeshRudpDocumentServer::new( + UdpSocket::bind("127.0.0.1:0")?, + sink, + source, + clock, + options, + ) +} + +fn client(target: SocketAddr, id: u32) -> Result { + let socket = UdpSocket::bind("127.0.0.1:0")?; + socket.set_nonblocking(true)?; + CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + runtime_id: "test-client".into(), + socket, + mode: CultNetRudpSocketMode::Client, + remote_addr: Some(target), + connection_id: id, + initial_sequence: 1, + resend_delay_ms: 10, + transport_id: None, + max_payload_bytes: None, + max_fragment_bytes: Some(1200), + max_pending_reliable_packets: Some(64), + media_reliable_expire_after_ms: None, + reconnect_policy: None, + }) +} + +fn connect( + server: &mut Server, + clients: &mut [&mut CultNetRudpSocketTransportConnection], +) -> Result<()> { + for client in &mut *clients { + client.connect(Vec::new())?; + } + for _ in 0..500 { + server.poll_once()?; + for client in &mut *clients { + client.receive_once()?; + } + if clients.iter().all(|client| client.connected()) { + for _ in 0..clients.len() { + server.poll_once()?; + } + return Ok(()); + } + thread::sleep(Duration::from_millis(1)); + } + anyhow::bail!("client connection timed out") +} + +fn send(client: &mut CultNetRudpSocketTransportConnection, message: &CultNetMessage) -> Result<()> { + client.send( + "schema", + encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?, + ) +} + +fn send_reliable( + client: &mut CultNetRudpSocketTransportConnection, + message: &CultNetMessage, +) -> Result { + client.send_reliable( + "schema", + encode_cultnet_message_to_vec(message, CultNetWireContract::CultNetSchemaV0)?, + ) +} + +#[test] +fn same_connection_id_is_peer_scoped_and_raw_bytes_are_untouched() -> Result<()> { + let clock = Clock::new(42_000); + let sink = Sink::default(); + let mut server = server(Default::default(), clock, sink.clone(), Source::default())?; + let mut a = client(server.local_addr()?, 77)?; + let mut b = client(server.local_addr()?, 77)?; + connect(&mut server, &mut [&mut a, &mut b])?; + + let a_bytes = vec![0xc4, 0x04, 0x00, 0xff, 0x80, 0x01]; + let b_bytes = vec![0x92, 0x01, 0xc4, 0x02, 0x00, 0xff]; + send( + &mut a, + &CultNetMessage::DocumentPutRaw { + message_id: "a".into(), + document: document("a", a_bytes.clone()), + }, + )?; + send( + &mut b, + &CultNetMessage::DocumentPutRaw { + message_id: "b".into(), + document: document("b", b_bytes.clone()), + }, + )?; + for _ in 0..500 { + server.poll_once()?; + a.receive_once()?; + b.receive_once()?; + if sink.0.lock().unwrap().receipts.len() == 2 { + break; + } + } + + let receipts = sink.0.lock().unwrap(); + assert_eq!(server.session_count(), 2); + assert_eq!(receipts.receipts.len(), 2); + assert_ne!( + receipts.receipts[0].session.remote_addr, + receipts.receipts[1].session.remote_addr + ); + assert_eq!( + receipts + .receipts + .iter() + .find(|r| r.message_id == "a") + .unwrap() + .document + .payload, + a_bytes + ); + assert_eq!( + receipts + .receipts + .iter() + .find(|r| r.message_id == "b") + .unwrap() + .document + .payload, + b_bytes + ); + assert!( + receipts + .receipts + .iter() + .all(|r| r.received_at_unix_millis == 42_000) + ); + Ok(()) +} + +#[test] +fn duplicate_connect_preserves_epoch_and_fresh_epoch_is_separate() -> Result<()> { + let clock = Clock::new(45_000); + let sink = Sink::default(); + let mut server = server(Default::default(), clock, sink.clone(), Source::default())?; + let target = server.local_addr()?; + let mut incumbent = client(target, 91)?; + connect(&mut server, &mut [&mut incumbent])?; + + incumbent.connect(Vec::new())?; + server.poll_once()?; + incumbent.receive_once()?; + assert_eq!(server.session_count(), 1); + + send( + &mut incumbent, + &CultNetMessage::DocumentPutRaw { + message_id: "after-duplicate".into(), + document: document("incumbent", vec![1, 2, 3]), + }, + )?; + for _ in 0..20 { + server.poll_once()?; + if sink.0.lock().unwrap().receipts.len() == 1 { + break; + } + } + assert_eq!(sink.0.lock().unwrap().receipts.len(), 1); + + let mut fresh_epoch = client(target, 92)?; + connect(&mut server, &mut [&mut fresh_epoch])?; + assert_eq!(server.session_count(), 2); + Ok(()) +} + +#[test] +fn application_rejection_is_nonfatal_peer_scoped_and_unacknowledged() -> Result<()> { + let clock = Clock::new(48_000); + let sink = Sink::fail_once(); + let source = Source::fail_once(vec![document("snapshot", vec![4, 5, 6])]); + let mut server = server( + Default::default(), + clock.clone(), + sink.clone(), + source.clone(), + )?; + let target = server.local_addr()?; + + let mut publisher = client(target, 101)?; + let mut snapshot_client = client(target, 102)?; + let mut survivor = client(target, 103)?; + connect( + &mut server, + &mut [&mut publisher, &mut snapshot_client, &mut survivor], + )?; + let publish_receipt = send_reliable( + &mut publisher, + &CultNetMessage::DocumentPutRaw { + message_id: "rejected-put".into(), + document: document("rejected", vec![0, 255, 1]), + }, + )?; + let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = server.poll_once()? else { + panic!("sink rejection must be returned as a nonfatal poll outcome"); + }; + assert_eq!(rejection.session.connection_id, 101); + assert_eq!( + rejection.operation, + CultMeshRudpApplicationOperation::DocumentPutRaw + ); + assert_eq!(rejection.message_id, "rejected-put"); + assert!(rejection.reason.contains("injected sink failure")); + assert_eq!(server.session_count(), 2); + publisher.receive_once()?; + assert_eq!( + publisher.reliable_send_status(&publish_receipt), + CultNetRudpReliableSendStatus::Pending + ); + + let snapshot_receipt = send_reliable( + &mut snapshot_client, + &CultNetMessage::SnapshotRequest { + message_id: "rejected-snapshot".into(), + schema_ids: None, + record_keys: None, + }, + )?; + let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = server.poll_once()? else { + panic!("source rejection must be returned as a nonfatal poll outcome"); + }; + assert_eq!(rejection.session.connection_id, 102); + assert_eq!( + rejection.operation, + CultMeshRudpApplicationOperation::SnapshotRequest + ); + assert_eq!(rejection.message_id, "rejected-snapshot"); + assert!(rejection.reason.contains("injected source failure")); + assert_eq!(server.session_count(), 1); + snapshot_client.receive_once()?; + assert_eq!( + snapshot_client.reliable_send_status(&snapshot_receipt), + CultNetRudpReliableSendStatus::Pending + ); + + let survivor_receipt = send_reliable( + &mut survivor, + &CultNetMessage::DocumentPutRaw { + message_id: "accepted-after-rejections".into(), + document: document("survivor", vec![9, 8, 7]), + }, + )?; + assert_eq!(server.poll_once()?, CultMeshRudpPollOutcome::Handled); + for _ in 0..20 { + survivor.receive_once()?; + if survivor.reliable_send_status(&survivor_receipt) + == CultNetRudpReliableSendStatus::Acknowledged + { + break; + } + } + assert_eq!( + survivor.reliable_send_status(&survivor_receipt), + CultNetRudpReliableSendStatus::Acknowledged + ); + assert_eq!(sink.0.lock().unwrap().receipts.len(), 1); + assert_eq!(source.0.lock().unwrap().calls, 1); + Ok(()) +} + +#[test] +fn snapshot_response_resends_until_acknowledged() -> Result<()> { + let clock = Clock::new(51_000); + let expected = document("catalog", vec![0x81, 0xa1, 0x78, 0x2a]); + let source = Source::documents(vec![expected.clone()]); + let options = CultMeshRudpDocumentServerOptions { + resend_delay: Duration::from_millis(10), + ..Default::default() + }; + let mut server = server(options, clock.clone(), Sink::default(), source)?; + let mut client = client(server.local_addr()?, 88)?; + connect(&mut server, &mut [&mut client])?; + send( + &mut client, + &CultNetMessage::SnapshotRequest { + message_id: "snapshot".into(), + schema_ids: Some(vec!["test.raw.v1".into()]), + record_keys: Some(vec!["catalog".into()]), + }, + )?; + server.poll_once()?; + + clock.set(51_011); + assert_eq!(server.maintain()?.packets_resent, 1); + let response = loop { + if let Some(frame) = client.receive_once()? { + break decode_cultnet_message_from_slice( + &frame.payload, + CultNetWireContract::CultNetSchemaV0, + )?; + } + }; + assert_eq!( + response, + CultNetMessage::SnapshotResponseRaw { + message_id: "snapshot".into(), + documents: vec![expected], + } + ); + + for _ in 0..20 { + server.poll_once()?; + } + clock.set(51_022); + assert_eq!(server.maintain()?.packets_resent, 0); + Ok(()) +} + +#[test] +fn payload_and_snapshot_output_budgets_fail_closed() -> Result<()> { + let clock = Clock::new(55_000); + let sink = Sink::default(); + let message = CultNetMessage::DocumentPutRaw { + message_id: "budget".into(), + document: document("budget", vec![7; 64]), + }; + let encoded = encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)?; + let options = CultMeshRudpDocumentServerOptions { + max_admitted_payload_bytes: encoded.len(), + max_admitted_payload_bytes_per_session: encoded.len(), + max_snapshot_response_bytes: encoded.len(), + ..Default::default() + }; + let mut budget_server = server(options, clock, sink.clone(), Source::default())?; + let target = budget_server.local_addr()?; + let mut first = client(target, 111)?; + let mut second = client(target, 112)?; + connect(&mut budget_server, &mut [&mut first, &mut second])?; + send(&mut first, &message)?; + budget_server.poll_once()?; + send(&mut second, &message)?; + budget_server.poll_once()?; + assert_eq!(sink.0.lock().unwrap().receipts.len(), 1); + + let oversized = Source::documents(vec![document("oversized", vec![8; 512])]); + let options = CultMeshRudpDocumentServerOptions { + max_snapshot_response_bytes: 128, + ..Default::default() + }; + let mut snapshot_server = server(options, Clock::new(56_000), Sink::default(), oversized)?; + let mut snapshot_client = client(snapshot_server.local_addr()?, 113)?; + connect(&mut snapshot_server, &mut [&mut snapshot_client])?; + let receipt = send_reliable( + &mut snapshot_client, + &CultNetMessage::SnapshotRequest { + message_id: "too-large".into(), + schema_ids: None, + record_keys: None, + }, + )?; + let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = snapshot_server.poll_once()? + else { + panic!("oversized snapshot must reject only its session"); + }; + assert_eq!( + rejection.operation, + CultMeshRudpApplicationOperation::SnapshotRequest + ); + assert_eq!(rejection.message_id, "too-large"); + assert!(rejection.reason.contains("snapshot response is")); + assert_eq!(snapshot_server.session_count(), 0); + snapshot_client.receive_once()?; + assert_eq!( + snapshot_client.reliable_send_status(&receipt), + CultNetRudpReliableSendStatus::Pending + ); + Ok(()) +} + +#[test] +fn session_cap_and_expiry_use_monotonic_time() -> Result<()> { + let clock = Clock::new(60_000); + let options = CultMeshRudpDocumentServerOptions { + max_sessions: 1, + session_idle_timeout: Duration::from_secs(1), + session_max_lifetime: Duration::from_millis(100), + ..Default::default() + }; + let mut server = server(options, clock.clone(), Sink::default(), Source::default())?; + let target = server.local_addr()?; + let mut admitted = client(target, 1)?; + connect(&mut server, &mut [&mut admitted])?; + let mut rejected = client(target, 1)?; + rejected.connect(Vec::new())?; + server.poll_once()?; + assert_eq!(server.session_count(), 1); + assert!(!rejected.connected()); + + clock.set_unix(1); + clock.set_monotonic(60_101); + assert_eq!(server.maintain()?.sessions_expired, 1); + assert_eq!(server.session_count(), 0); + Ok(()) +} From c13b6ba07e99f2535b455da74d07d88c308731f5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 5 Sep 2026 17:39:46 +0200 Subject: [PATCH 065/203] Move IdunnDaemonHealthTrustBindingRecord into cultnet-rs odin-core only re-exported this contract from Odin's tree while the sibling health/trust records already live in provider_health_contracts.rs. Moving it here lets idunn-daemon depend on cultnet-rs directly instead of odin-core, clearing the way to extract idunn-daemon into its own repo. Co-Authored-By: Claude Opus 5 --- .../src/provider_health_contracts.rs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/packages/cultnet-rs/src/provider_health_contracts.rs b/packages/cultnet-rs/src/provider_health_contracts.rs index 5ca320f0..632c74f2 100644 --- a/packages/cultnet-rs/src/provider_health_contracts.rs +++ b/packages/cultnet-rs/src/provider_health_contracts.rs @@ -21,6 +21,7 @@ pub const IDUNN_PROVIDER_ACTIVE_REASON: &str = "authenticated_provider_active"; pub const IDUNN_PROVIDER_WARMING_REASON: &str = "authenticated_provider_warming"; pub const IDUNN_PROVIDER_DEGRADED_REASON: &str = "authenticated_provider_degraded"; pub const IDUNN_PROVIDER_FAILED_REASON: &str = "authenticated_provider_failed"; +pub const IDUNN_DAEMON_HEALTH_TRUST_BINDING_SCHEMA: &str = "idunn.daemon_health_trust_binding.v1"; /// Provider-owned health statement. Ingress verifies `signature` over the /// complete positional record with this field empty before admitting it. @@ -198,6 +199,68 @@ fn messagepack_array_len(payload: &[u8]) -> Option { } } +/// Root-owned admission binding. Store ownership is the physical authority; +/// this document names the only signer accepted for one daemon/contract/runtime. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "idunn.daemon_health_trust_binding", + schema = "idunn.daemon_health_trust_binding.v1" +)] +pub struct IdunnDaemonHealthTrustBindingRecord { + #[cultcache(key = 0)] + pub schema_version: String, + #[cultcache(key = 1)] + pub binding_id: String, + #[cultcache(key = 2)] + pub daemon_id: String, + #[cultcache(key = 3)] + pub health_contract: String, + #[cultcache(key = 4)] + pub source_runtime_id: String, + #[cultcache(key = 5)] + pub signer_identity_id: String, + #[cultcache(key = 6)] + pub signer_public_key: Vec, + #[cultcache(key = 7)] + pub binding_authority: String, + #[cultcache(key = 8)] + pub bound_at_unix_millis: u64, + #[cultcache(key = 9)] + pub release_binding_required: bool, + #[cultcache(key = 10)] + pub private_state_exposed: bool, +} + +impl IdunnDaemonHealthTrustBindingRecord { + pub fn validate(&self) -> Result<()> { + if self.schema_version != IDUNN_DAEMON_HEALTH_TRUST_BINDING_SCHEMA { + bail!("daemon health trust binding schema is unsupported"); + } + validate_identifier(&self.binding_id, "binding id")?; + validate_identifier(&self.daemon_id, "daemon id")?; + validate_identifier(&self.health_contract, "health contract")?; + validate_identifier(&self.source_runtime_id, "source runtime id")?; + validate_identifier(&self.signer_identity_id, "signer identity id")?; + if self.signer_public_key.len() != 32 + || self.binding_authority != "root" + || self.bound_at_unix_millis == 0 + { + bail!("daemon health trust binding authority or key is invalid"); + } + if self.private_state_exposed { + bail!("daemon health trust binding exposes private state"); + } + if self.signer_identity_id + != derive_service_identity_id::( + &self.signer_public_key, + )? + { + bail!("daemon health trust binding identity does not match its public key"); + } + Ok(()) + } +} + /// Root-distributed public key binding for one service-owned signed contract. /// Consumers pin this document; a self-declared key inside a signed projection /// is never authority. From 5407be713fdd4d66cf865ca643750ac7b167be3a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 6 Sep 2026 19:29:33 +0200 Subject: [PATCH 066/203] Add generic signed daemon-health publication to cultnet-ts Odin carried a 324-line module named idunn-rudp.cjs. Thirteen of those lines mentioned Idunn, and most were error strings. The rest was CultNet client work: socket binding, RUDP handshake, Ed25519 signing, packet receipt, msgpack framing. It sat in a consumer because that is where it was written, not because that is where it belonged, and it was about to be duplicated when Hermodr moves to its own repository. The transport now lives here as signed-daemon-health, beside the RUDP session it builds on. The receiving service supplies a contract naming its connection id, signed schema, and unsigned diagnostic schema; nothing service-specific is hardcoded. The signature and identity domains are deliberately not parameterised. They are the shared gamecult.provider-health domains, and a caller able to vary them could silently produce statements no verifier accepts. The signing purpose is the caller's signed schema id, so a statement signed for one service cannot verify as another's. The module also loses the sibling-path assumption it was carrying: it no longer resolves CultLib by walking up three directories from wherever it happens to live, because it now lives in CultLib. Type-checks clean and builds. Verified faithful by Odin's existing signed-health test, which asserts exact canonical statement fields, the observation timestamp, and Ed25519 signature verification against the ported implementation. Co-Authored-By: Claude Opus 5 --- packages/cultnet-ts/src/index.ts | 12 + .../cultnet-ts/src/signed-daemon-health.ts | 377 ++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 packages/cultnet-ts/src/signed-daemon-health.ts diff --git a/packages/cultnet-ts/src/index.ts b/packages/cultnet-ts/src/index.ts index 731cafb5..db24d17b 100644 --- a/packages/cultnet-ts/src/index.ts +++ b/packages/cultnet-ts/src/index.ts @@ -144,3 +144,15 @@ export { validateGhostlightAgentStateGenerated, type GhostlightAgentStateShape, } from "./generated/swarm-contracts.generated"; +export { + CULTNET_RUDP_PROTOCOL_ID, + createSignedDaemonHealthPublisher, + parseEndpoint as parseDaemonHealthEndpoint, + publishSignedDaemonHealth, + signedDaemonHealthPayload, + signedDaemonHealthSigningMessage, + type DaemonHealth, + type SignedDaemonHealthContract, + type SignedDaemonHealthPublisher, + type SignedDaemonHealthPublisherOptions, +} from "./signed-daemon-health"; diff --git a/packages/cultnet-ts/src/signed-daemon-health.ts b/packages/cultnet-ts/src/signed-daemon-health.ts new file mode 100644 index 00000000..d4f0f0ce --- /dev/null +++ b/packages/cultnet-ts/src/signed-daemon-health.ts @@ -0,0 +1,377 @@ +// Signed daemon-health publication over CultNet RUDP. +// +// This is the transport half of daemon health publication: bind a socket, shake +// hands, sign the statement, put the document, wait for the ack. It is generic +// across services. The receiving service owns the connection id and the schema +// names, and supplies them as a contract; nothing service-specific is hardcoded +// here. +// +// The signature and identity domains are deliberately not parameterised. They +// are the GameCult provider-health domains, shared by every publisher and +// verifier, and a caller that could vary them could silently produce statements +// nobody verifies. + +import dgram from "node:dgram"; +import crypto from "node:crypto"; +import fs from "node:fs"; +import { encode } from "@msgpack/msgpack"; + +import { CultNetRudpSession, decodeRudpPacket, encodeRudpPacket } from "./rudp"; +import type { CultNetRudpPacket } from "./rudp"; +import { encodeCultNetMessageForWire } from "./contracts"; + +export const CULTNET_RUDP_PROTOCOL_ID = "cultnet.transport.rudp.v0"; + +const SIGNATURE_DOMAIN = Buffer.from("gamecult.provider-health.signature.v1\0", "utf8"); +const ID_DOMAIN = Buffer.from("gamecult.provider-health.identity.v1\0", "utf8"); + +/** What the receiving service owns: where to connect and what to call the documents. */ +export interface SignedDaemonHealthContract { + /** RUDP connection id the receiving service listens on. */ + connectionId: number; + /** Schema id for a signed statement. Also the signing purpose. */ + signedSchemaId: string; + /** Schema id used when no signing key is configured. */ + unsignedSchemaId: string; + /** Prefix for generated message ids. Defaults to "daemon-health". */ + messageIdPrefix?: string; +} + +export interface SignedDaemonHealthPublisherOptions { + /** host:port, or [ipv6]:port. */ + endpoint: string; + daemonId: string; + healthContract: string; + sourceRuntimeId?: string; + /** Omit to publish unsigned. */ + privateKeyPath?: string; + contract: SignedDaemonHealthContract; +} + +export interface DaemonHealth { + state: string; + detail?: string; + observedAt?: string; +} + +export interface SignedDaemonHealthPublisher { + daemonId: string; + endpoint: { host: string; port: number }; + healthContract: string; + publisherIncarnationId: string; + publisherSequence: number; + sourceRuntimeId: string; + contract: SignedDaemonHealthContract; + privateKey?: crypto.KeyObject; + publicKey?: Buffer; + signerIdentityId?: string; +} + +export function createSignedDaemonHealthPublisher( + options: SignedDaemonHealthPublisherOptions | null | undefined, +): SignedDaemonHealthPublisher | null { + if (!options) return null; + if (!options.contract) { + throw new Error("Signed daemon health publisher requires a contract naming the connection id and schema ids."); + } + + const publisher: SignedDaemonHealthPublisher = { + daemonId: options.daemonId, + endpoint: parseEndpoint(options.endpoint), + healthContract: options.healthContract, + publisherIncarnationId: crypto.randomUUID(), + publisherSequence: 0, + sourceRuntimeId: options.sourceRuntimeId || "daemon-health-publisher", + contract: options.contract, + }; + + if (options.privateKeyPath) { + publisher.privateKey = crypto.createPrivateKey(fs.readFileSync(options.privateKeyPath)); + publisher.publicKey = rawEd25519PublicKey(publisher.privateKey); + publisher.signerIdentityId = crypto + .createHash("sha256") + .update(ID_DOMAIN) + .update(publisher.publicKey) + .digest("hex"); + } + return publisher; +} + +export async function publishSignedDaemonHealth( + publisher: SignedDaemonHealthPublisher | null, + health: DaemonHealth, +): Promise { + if (!publisher) return; + const { contract } = publisher; + + const socket = dgram.createSocket(endpointFamily(publisher.endpoint.host)); + await bindSocket(socket, publisher.endpoint); + const receiver = createPacketReceiver(socket); + const session = new CultNetRudpSession({ + connectionId: contract.connectionId, + initialSequence: 1, + resendDelayMs: 100, + }); + + try { + const connect = session.createConnect(Date.now(), new Uint8Array()); + await sendPacket(socket, publisher.endpoint, connect); + await receiveUntil(receiver, session, publisher.endpoint, (packet) => packet.packetType === "accept", 5000, "accept"); + + const observedAt = health.observedAt || new Date().toISOString(); + const signed = publisher.privateKey ? signedDaemonHealthPayload(publisher, health, observedAt) : null; + const payload = signed?.payload || encode([ + publisher.daemonId, + health.state, + String(health.detail || "").slice(0, 512), + observedAt, + publisher.healthContract, + "daemon-published", + CULTNET_RUDP_PROTOCOL_ID, + ]); + + const message = { + schemaVersion: "cultnet.document_put_raw.v0", + messageId: `${contract.messageIdPrefix || "daemon-health"}:${publisher.daemonId}:${observedAt.replace(/[:.]/g, "-")}`, + document: { + schemaId: signed ? contract.signedSchemaId : contract.unsignedSchemaId, + recordKey: publisher.daemonId, + storedAt: observedAt, + payloadEncoding: "messagepack", + payload, + sourceRuntimeId: publisher.sourceRuntimeId, + sourceRole: "daemon-health-publisher", + tags: [CULTNET_RUDP_PROTOCOL_ID], + }, + }; + + const wirePayload = encode(encodeCultNetMessageForWire(message as never, "cultnet.schema.v0")); + const dataPackets = session.sendMany("schema", wirePayload, { reliable: true, ordered: true, nowMs: Date.now() }); + const ack = receiveUntil( + receiver, + session, + publisher.endpoint, + (packet) => packet.packetType === "ack", + 500, + "ack", + ).catch(() => undefined); + for (const packet of dataPackets) { + await sendPacket(socket, publisher.endpoint, packet); + } + await ack; + } finally { + receiver.close(); + socket.close(); + } +} + +export function signedDaemonHealthPayload( + publisher: SignedDaemonHealthPublisher, + health: DaemonHealth, + observedAt: string, +): { payload: Uint8Array; statement: unknown[]; unsignedPayload: Uint8Array } { + if (!publisher.privateKey) { + throw new Error("Signed daemon health requires a configured private key."); + } + publisher.publisherSequence += 1; + const observedAtUnixMillis = Date.parse(observedAt); + if (!Number.isSafeInteger(observedAtUnixMillis) || observedAtUnixMillis <= 0) { + throw new Error("Signed daemon health observation time is invalid."); + } + + const unsigned: unknown[] = [ + publisher.contract.signedSchemaId, + publisher.daemonId, + publisher.healthContract, + publisher.sourceRuntimeId, + health.state, + String(health.detail || "").slice(0, 512), + publisher.signerIdentityId, + publisher.publisherIncarnationId, + publisher.publisherSequence, + observedAtUnixMillis, + null, + null, + null, + null, + "ed25519", + new Uint8Array(), + false, + ]; + + const unsignedPayload = encode(unsigned); + const signature = crypto.sign( + null, + signedDaemonHealthSigningMessage(publisher.contract.signedSchemaId, unsignedPayload), + publisher.privateKey, + ); + const statement = unsigned.slice(); + statement[15] = new Uint8Array(signature); + return { payload: encode(statement), statement, unsignedPayload }; +} + +/** + * Domain-separated signing preimage. The signing purpose is the signed schema + * id, so a statement signed for one service's schema cannot verify as another's. + */ +export function signedDaemonHealthSigningMessage(signedSchemaId: string, payload: Uint8Array): Buffer { + const purpose = Buffer.from(signedSchemaId, "utf8"); + const purposeLength = Buffer.alloc(8); + purposeLength.writeBigUInt64BE(BigInt(purpose.length)); + const payloadLength = Buffer.alloc(8); + payloadLength.writeBigUInt64BE(BigInt(payload.length)); + return Buffer.concat([SIGNATURE_DOMAIN, purposeLength, purpose, payloadLength, Buffer.from(payload)]); +} + +function rawEd25519PublicKey(privateKey: crypto.KeyObject): Buffer { + const der = crypto.createPublicKey(privateKey).export({ type: "spki", format: "der" }) as Buffer; + if (der.length < 32) throw new Error("Ed25519 public key export is too short."); + return Buffer.from(der.subarray(der.length - 32)); +} + +async function bindSocket(socket: dgram.Socket, endpoint: { host: string }): Promise { + await new Promise((resolve, reject) => { + socket.once("error", reject); + socket.bind(0, endpoint.host.includes(":") ? "::" : "0.0.0.0", () => { + socket.off("error", reject); + resolve(); + }); + }); +} + +export function parseEndpoint(value: string): { host: string; port: number } { + const text = String(value || "").trim(); + const ipv6 = text.match(/^\[([^\]]+)\]:(\d+)$/); + if (ipv6) return { host: ipv6[1], port: parsePort(ipv6[2]) }; + const index = text.lastIndexOf(":"); + if (index <= 0) { + throw new Error(`Daemon health RUDP endpoint must be host:port, got "${value}".`); + } + return { host: text.slice(0, index), port: parsePort(text.slice(index + 1)) }; +} + +function parsePort(value: string): number { + const port = Number(value); + if (!Number.isInteger(port) || port <= 0 || port > 65535) { + throw new Error(`Daemon health RUDP endpoint port is invalid: ${value}`); + } + return port; +} + +function endpointFamily(host: string): "udp4" | "udp6" { + return host.includes(":") ? "udp6" : "udp4"; +} + +interface PacketReceiver { + socket: dgram.Socket; + next(timeoutMs: number, label?: string): Promise; + close(): void; +} + +async function receiveUntil( + receiver: PacketReceiver, + session: CultNetRudpSession, + endpoint: { host: string; port: number }, + predicate: (packet: CultNetRudpPacket) => boolean, + timeoutMs: number, + label: string, +): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + try { + const packet = await receiver.next(Math.min(100, deadline - Date.now()), label); + const result = session.receive(packet, Date.now()); + if (result.reply) { + throw new Error("Daemon health publisher received an unexpected reply-required packet."); + } + if (predicate(packet)) return; + } catch (error) { + if ((error as NodeJS.ErrnoException)?.code !== "ETIMEDOUT") throw error; + } + for (const packet of session.dueResends(Date.now())) { + await sendPacket(receiver.socket, endpoint, packet); + } + } + throw new Error(`timed out waiting for daemon health RUDP ${label} response after ${timeoutMs}ms`); +} + +function createPacketReceiver(socket: dgram.Socket): PacketReceiver { + const packets: CultNetRudpPacket[] = []; + const waiters: Array<{ + resolve: (packet: CultNetRudpPacket) => void; + reject: (error: Error) => void; + timer: NodeJS.Timeout; + }> = []; + const errors: Error[] = []; + + const resolveNext = () => { + while (waiters.length > 0 && (packets.length > 0 || errors.length > 0)) { + const waiter = waiters.shift()!; + clearTimeout(waiter.timer); + if (errors.length > 0) waiter.reject(errors.shift()!); + else waiter.resolve(packets.shift()!); + } + }; + const onMessage = (wire: Buffer) => { + try { + packets.push(decodeRudpPacket(wire)); + } catch (error) { + errors.push(error as Error); + } + resolveNext(); + }; + const onError = (error: Error) => { + errors.push(error); + resolveNext(); + }; + + socket.on("message", onMessage); + socket.on("error", onError); + + return { + socket, + next(timeoutMs: number, label = "packet") { + if (packets.length > 0) return Promise.resolve(packets.shift()!); + if (errors.length > 0) return Promise.reject(errors.shift()!); + return new Promise((resolve, reject) => { + const waiter = { + resolve, + reject, + timer: setTimeout(() => { + const index = waiters.indexOf(waiter); + if (index >= 0) waiters.splice(index, 1); + const error = new Error(`timed out waiting for daemon health RUDP ${label}`) as NodeJS.ErrnoException; + error.code = "ETIMEDOUT"; + reject(error); + }, Math.max(1, timeoutMs)), + }; + waiters.push(waiter); + }); + }, + close() { + socket.off("message", onMessage); + socket.off("error", onError); + while (waiters.length > 0) { + const waiter = waiters.shift()!; + clearTimeout(waiter.timer); + const error = new Error("Daemon health publisher closed.") as NodeJS.ErrnoException; + error.code = "ECLOSED"; + waiter.reject(error); + } + }, + }; +} + +async function sendPacket( + socket: dgram.Socket, + endpoint: { host: string; port: number }, + packet: CultNetRudpPacket, +): Promise { + const wire = encodeRudpPacket(packet); + await new Promise((resolve, reject) => { + socket.send(wire, endpoint.port, endpoint.host, (error) => { + if (error) reject(error); + else resolve(); + }); + }); +} From b304df5e479ad5e37d1a8d37be9ccf85182b6e22 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 6 Sep 2026 19:40:37 +0200 Subject: [PATCH 067/203] Promote the swarm document catalog into cultcache-ts Odin carried defineOdinDocuments: 45 CultCache document-type definitions for documents that cross service boundaries. Eve surfaces and provider advertisements, Ghostlight, Heimdall, Idunn, Muninn, Sleipnir, Aetheria, VoidBot, Stonks. Almost none of it is Odin. It lived there because Odin was written first. That was tolerable while Odin was the only consumer of consequence. It stops being tolerable when Hermodr moves to its own repository, because the choice then is a published contract or two definitions of one wire format, and the second is the failure the Hermodr extraction exists to remove. The catalog is now defineSwarmDocuments here, beside defineDocumentType, which is what builds it. defineDocumentType stays injected rather than imported: that is how it worked in Odin, it keeps the catalog clear of the generic signature in ./document, and it lets a caller supply another runtime. Ported as a mechanical transform rather than retyped, then verified rather than assumed: serialising all 45 definitions from both implementations against the same probe gives byte-identical output and identical key sets. The 43 callbacks that gained annotations are named DocumentValue rather than bare any, because these definitions predate typing and read fields defensively, and pretending otherwise would be inventing types nobody has verified. Co-Authored-By: Claude Opus 5 --- packages/cultcache-ts/src/index.ts | 5 + packages/cultcache-ts/src/swarm-documents.ts | 561 +++++++++++++++++++ 2 files changed, 566 insertions(+) create mode 100644 packages/cultcache-ts/src/swarm-documents.ts diff --git a/packages/cultcache-ts/src/index.ts b/packages/cultcache-ts/src/index.ts index 97687de8..92f7b481 100644 --- a/packages/cultcache-ts/src/index.ts +++ b/packages/cultcache-ts/src/index.ts @@ -27,3 +27,8 @@ export type { InspectedCatalogMember, InspectedRecord, } from "./cult-cache-inspector"; +export { + defineSwarmDocuments, + type DefineDocumentType, + type SwarmDocumentCatalog, +} from "./swarm-documents"; diff --git a/packages/cultcache-ts/src/swarm-documents.ts b/packages/cultcache-ts/src/swarm-documents.ts new file mode 100644 index 00000000..b1c0b40d --- /dev/null +++ b/packages/cultcache-ts/src/swarm-documents.ts @@ -0,0 +1,561 @@ +// GameCult swarm document-type catalog. +// +// These are CultCache document definitions for documents that cross service +// boundaries: Eve surfaces and provider advertisements, Ghostlight, Heimdall, +// Idunn, Muninn, Sleipnir, Aetheria, VoidBot, Stonks. The catalog lived in Odin +// because Odin was written first, not because Odin owns it; every service that +// decodes another service documents needs the same definitions, and a second +// copy would be a second definition of one wire format. +// +// defineDocumentType is injected rather than imported. That is how it worked in +// Odin, it keeps this file clear of the generic signature in ./document, and it +// lets a caller supply a runtime other than this package. + +/** An undecoded document payload. These definitions predate typing and name fields defensively. */ +export type DocumentValue = any; + +export type DefineDocumentType = (definition: Record) => unknown; +export type SwarmDocumentCatalog = Record; + +function parseObjectDocument(label: string) { + return { + parse(value: unknown) { + if (!value || typeof value !== "object") { + throw new Error(`${label} must be an object`); + } + return value; + }, + }; +} + +export function defineSwarmDocuments(defineDocumentType?: DefineDocumentType | null): SwarmDocumentCatalog { + if (!defineDocumentType) { + return { + interfaceBindingDefinition: null, + evePluginAdvertisementDefinition: null, + ghostlightSchemaCatalogDefinition: null, + heimdallCommandBoundaryDefinition: null, + heimdallTransportProfileDefinition: null, + idunnCommandBoundaryDefinition: null, + idunnDaemonHealthDefinition: null, + idunnTransportProfileDefinition: null, + interfaceLayoutDefinition: null, + muninnCaptureStreamCommandDefinition: null, + muninnCaptureStreamDefinition: null, + muninnCommandBoundaryDefinition: null, + muninnMediaAudioPacketDefinition: null, + muninnMediaReceiverFeedbackDefinition: null, + muninnMediaVideoAccessUnitDefinition: null, + muninnHidControllerStateDefinition: null, + muninnMoveControllerStateDefinition: null, + muninnMoveIdentityDefinition: null, + muninnMoveLightCommandDefinition: null, + muninnMoveMarkerCandidateDefinition: null, + muninnObsStreamCatalogDefinition: null, + muninnQuestAccessDefinition: null, + muninnTelemetrySurfaceDefinition: null, + muninnTransportProfileDefinition: null, + SleipnirInputMappingDefinition: null, + aetheriaAssetManifestDefinition: null, + aetheriaGravityViewportDefinition: null, + aetheriaObjectsViewportDefinition: null, + aetheriaRenderSplatsViewportDefinition: null, + operatorStateDefinition: null, + providerAdvertisementDefinition: null, + voidbotProviderCatalogDefinition: null, + stonksCommandBoundaryDefinition: null, + stonksMarketSnapshotDefinition: null, + stonksRequestEventDefinition: null, + stonksTransportProfileDefinition: null, + streamPixelsCommandBoundaryDefinition: null, + streamPixelsTransportProfileDefinition: null, + surfaceDefinition: null, + viliCommandBoundaryDefinition: null, + viliTransportProfileDefinition: null, + weksaCommandBoundaryDefinition: null, + weksaOperatorStateDefinition: null, + weksaTransportProfileDefinition: null, + voidbotSwarmSnapshotDefinition: null, + }; + } + + const surfaceDefinition = defineDocumentType({ + type: "gamecult.eve.surface_state", + schemaName: "gamecult.eve.surface_state", + schemaId: "gamecult.eve.surface_state.v1", + schemaVersion: "gamecult.eve.surface_state.v1", + global: false, + name: (value: DocumentValue) => value?.providerId || "surface", + schema: { parse: (value: DocumentValue) => value }, + members: [ + { slot: 0, memberName: "providerId", typeName: "string", isName: true }, + { slot: 1, memberName: "title", typeName: "string" }, + { slot: 2, memberName: "version", typeName: "long" }, + { slot: 3, memberName: "updatedAt", typeName: "string" }, + { slot: 4, memberName: "surface", typeName: "object" }, + ], + }); + const interfaceBindingDefinition = defineDocumentType({ + type: "gamecult.eve.interface_binding", + schemaName: "gamecult.eve.interface_binding", + schemaId: "gamecult.eve.interface_binding.v1", + schemaVersion: "gamecult.eve.interface_binding.v1", + global: false, + name: (value: DocumentValue) => value?.bindingId || value?.providerId || "interface", + schema: parseObjectDocument("Eve interface binding"), + }); + const providerAdvertisementDefinition = defineDocumentType({ + type: "gamecult.eve.provider_advertisement", + schemaName: "gamecult.eve.provider_advertisement", + schemaId: "gamecult.eve.provider_advertisement.v1", + schemaVersion: "gamecult.eve.provider_advertisement.v1", + global: false, + name: (value: DocumentValue) => value?.providerId || value?.provider?.id || "provider", + schema: parseObjectDocument("Eve provider advertisement"), + }); + const evePluginAdvertisementDefinition = defineDocumentType({ + type: "gamecult.eve.plugin_advertisement", + schemaName: "gamecult.eve.plugin_advertisement", + schemaId: "gamecult.eve.plugin_advertisement.v1", + schemaVersion: "gamecult.eve.plugin_advertisement.v1", + global: false, + name: (value: DocumentValue) => value?.pluginId || value?.plugin?.id || "plugin", + schema: parseObjectDocument("Eve plugin advertisement"), + }); + const ghostlightSchemaCatalogDefinition = defineDocumentType({ + type: "ghostlight.schema_catalog", + schemaName: "ghostlight.schema_catalog", + schemaId: "ghostlight.schema_catalog.v1", + schemaVersion: "ghostlight.schema_catalog.v1", + global: false, + name: (value: DocumentValue) => value?.providerId || "gamecult.ghostlight.dungeon", + schema: parseObjectDocument("Ghostlight public schema catalog"), + }); + const heimdallCommandBoundaryDefinition = defineDocumentType({ + type: "heimdall.command_boundary", + schemaName: "heimdall.command_boundary", + schemaId: "heimdall.command_boundary.v1", + schemaVersion: "heimdall.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundaryId || value?.daemonId || "heimdall", + schema: parseObjectDocument("Heimdall command boundary"), + }); + const heimdallTransportProfileDefinition = defineDocumentType({ + type: "heimdall.transport_profile", + schemaName: "heimdall.transport_profile", + schemaId: "heimdall.transport_profile.v1", + schemaVersion: "heimdall.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profileId || value?.daemonId || "heimdall", + schema: parseObjectDocument("Heimdall transport profile"), + }); + const interfaceLayoutDefinition = defineDocumentType({ + type: "odin.interface_layout", + schemaName: "odin.interface_layout", + schemaId: "odin.interface_layout.v1", + schemaVersion: "odin.interface_layout.v1", + global: false, + name: (value: DocumentValue) => value?.layoutId || value?.providerId || "odin.providers", + schema: parseObjectDocument("Odin interface layout"), + members: [ + { slot: 0, memberName: "layoutId", typeName: "string", isName: true }, + { slot: 1, memberName: "updatedAt", typeName: "string" }, + { slot: 2, memberName: "tiles", typeName: "object" }, + ], + }); + const idunnDaemonHealthDefinition = defineDocumentType({ + type: "idunn.daemon_health", + schemaName: "idunn.daemon_health", + schemaId: "idunn.daemon_health", + schemaVersion: "idunn.daemon_health.v1", + global: false, + name: (value: DocumentValue) => value?.daemonId || value?.daemon_id || "daemon", + schema: parseObjectDocument("Idunn daemon health"), + }); + const idunnCommandBoundaryDefinition = defineDocumentType({ + type: "idunn.command_boundary", + schemaName: "idunn.command_boundary", + schemaId: "idunn.command_boundary.v1", + schemaVersion: "idunn.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundary_id || value?.boundaryId || value?.daemon_id || value?.daemonId || "daemon", + schema: parseObjectDocument("Idunn command boundary"), + }); + const idunnTransportProfileDefinition = defineDocumentType({ + type: "idunn.daemon_transport_profile", + schemaName: "idunn.daemon_transport_profile", + schemaId: "idunn.daemon_transport_profile.v1", + schemaVersion: "idunn.daemon_transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profile_id || value?.profileId || value?.daemon_id || value?.daemonId || "daemon", + schema: parseObjectDocument("Idunn daemon transport profile"), + }); + const muninnCaptureStreamDefinition = defineDocumentType({ + type: "muninn.capture_stream", + schemaName: "muninn.capture_stream", + schemaId: "muninn.capture_stream.v1", + schemaVersion: "muninn.capture_stream.v1", + global: false, + name: (value: DocumentValue) => value?.stream_id || value?.streamId || "capture-stream", + schema: parseObjectDocument("Muninn capture stream"), + }); + const muninnCaptureStreamCommandDefinition = defineDocumentType({ + type: "muninn.capture_stream_command", + schemaName: "muninn.capture_stream_command", + schemaId: "muninn.capture_stream_command.v1", + schemaVersion: "muninn.capture_stream_command.v1", + global: false, + name: (value: DocumentValue) => value?.command_id || value?.commandId || value?.stream_id || value?.streamId || "capture-stream-command", + schema: parseObjectDocument("Muninn capture stream command"), + }); + const muninnCommandBoundaryDefinition = defineDocumentType({ + type: "muninn.command_boundary", + schemaName: "muninn.command_boundary", + schemaId: "muninn.command_boundary.v1", + schemaVersion: "muninn.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundary_id || value?.boundaryId || value?.daemon_id || value?.daemonId || "muninn", + schema: parseObjectDocument("Muninn command boundary"), + }); + const muninnMediaVideoAccessUnitDefinition = defineDocumentType({ + type: "muninn.media_video_access_unit", + schemaName: "muninn.media_video_access_unit", + schemaId: "muninn.media_video_access_unit.v1", + schemaVersion: "muninn.media_video_access_unit.v1", + global: false, + name: (value: DocumentValue) => { + const streamId = value?.stream_id || value?.streamId || "stream"; + const sessionId = value?.session_id || value?.sessionId || "session"; + const frameId = value?.frame_id ?? value?.frameId ?? "frame"; + const chunkIndex = value?.chunk_index ?? value?.chunkIndex ?? "chunk"; + return `${streamId}:${sessionId}:video:${frameId}:${chunkIndex}`; + }, + schema: parseObjectDocument("Muninn media video access unit"), + }); + const muninnMediaAudioPacketDefinition = defineDocumentType({ + type: "muninn.media_audio_packet", + schemaName: "muninn.media_audio_packet", + schemaId: "muninn.media_audio_packet.v1", + schemaVersion: "muninn.media_audio_packet.v1", + global: false, + name: (value: DocumentValue) => { + const streamId = value?.stream_id || value?.streamId || "stream"; + const sessionId = value?.session_id || value?.sessionId || "session"; + const packetId = value?.packet_id ?? value?.packetId ?? "packet"; + return `${streamId}:${sessionId}:audio:${packetId}`; + }, + schema: parseObjectDocument("Muninn media audio packet"), + }); + const muninnMediaReceiverFeedbackDefinition = defineDocumentType({ + type: "muninn.media_receiver_feedback", + schemaName: "muninn.media_receiver_feedback", + schemaId: "muninn.media_receiver_feedback.v1", + schemaVersion: "muninn.media_receiver_feedback.v1", + global: false, + name: (value: DocumentValue) => { + const streamId = value?.stream_id || value?.streamId || "stream"; + const sessionId = value?.session_id || value?.sessionId || "session"; + const receiverId = value?.receiver_id || value?.receiverId || "receiver"; + return `${streamId}:${sessionId}:feedback:${receiverId}`; + }, + schema: parseObjectDocument("Muninn media receiver feedback"), + }); + const muninnMoveControllerStateDefinition = defineDocumentType({ + type: "muninn.move_controller_state", + schemaName: "muninn.move_controller_state", + schemaId: "muninn.move_controller_state.v1", + schemaVersion: "muninn.move_controller_state.v1", + global: false, + name: (value: DocumentValue) => value?.stream_id || value?.streamId || value?.move_id || value?.moveId || "move-controller-state", + schema: parseObjectDocument("Muninn Move controller state"), + }); + const muninnHidControllerStateDefinition = defineDocumentType({ + type: "muninn.hid_controller_state", + schemaName: "muninn.hid_controller_state", + schemaId: "muninn.hid_controller_state.v1", + schemaVersion: "muninn.hid_controller_state.v1", + global: false, + name: (value: DocumentValue) => value?.stream_id || value?.streamId || value?.device_id || value?.deviceId || "hid-controller-state", + schema: parseObjectDocument("Muninn HID controller state"), + }); + const muninnMoveIdentityDefinition = defineDocumentType({ + type: "muninn.move_identity", + schemaName: "muninn.move_identity", + schemaId: "muninn.move_identity.v1", + schemaVersion: "muninn.move_identity.v1", + global: false, + name: (value: DocumentValue) => value?.identity_id || value?.identityId || value?.move_id || value?.moveId || "move-identity", + schema: parseObjectDocument("Muninn Move identity"), + }); + const muninnMoveLightCommandDefinition = defineDocumentType({ + type: "muninn.move_light_command", + schemaName: "muninn.move_light_command", + schemaId: "muninn.move_light_command.v1", + schemaVersion: "muninn.move_light_command.v1", + global: false, + name: (value: DocumentValue) => value?.command_id || value?.commandId || value?.move_id || value?.moveId || "move-light-command", + schema: parseObjectDocument("Muninn Move light command"), + }); + const muninnMoveMarkerCandidateDefinition = defineDocumentType({ + type: "muninn.move_marker_candidate", + schemaName: "muninn.move_marker_candidate", + schemaId: "muninn.move_marker_candidate.v1", + schemaVersion: "muninn.move_marker_candidate.v1", + global: false, + name: (value: DocumentValue) => value?.stream_id || value?.streamId || value?.camera_id || value?.cameraId || "move-marker-candidate", + schema: parseObjectDocument("Muninn Move marker candidate"), + }); + const muninnObsStreamCatalogDefinition = defineDocumentType({ + type: "muninn.obs_stream_catalog", + schemaName: "muninn.obs_stream_catalog", + schemaId: "muninn.obs_stream_catalog.v1", + schemaVersion: "muninn.obs_stream_catalog.v1", + global: false, + name: (value: DocumentValue) => value?.catalog_id || value?.catalogId || value?.host_id || value?.hostId || "muninn", + schema: parseObjectDocument("Muninn OBS stream catalog"), + }); + const muninnQuestAccessDefinition = defineDocumentType({ + type: "muninn.quest_access", + schemaName: "muninn.quest_access", + schemaId: "muninn.quest_access.v1", + schemaVersion: "muninn.quest_access.v1", + global: false, + name: (value: DocumentValue) => value?.access_id || value?.accessId || value?.serial || "quest-access", + schema: parseObjectDocument("Muninn Quest access"), + }); + const muninnTelemetrySurfaceDefinition = defineDocumentType({ + type: "muninn.telemetry_surface", + schemaName: "muninn.telemetry_surface", + schemaId: "muninn.telemetry_surface.v1", + schemaVersion: "muninn.telemetry_surface.v1", + global: false, + name: (value: DocumentValue) => value?.surface_id || value?.surfaceId || value?.host_id || value?.hostId || "muninn-telemetry", + schema: parseObjectDocument("Muninn telemetry surface"), + }); + const muninnTransportProfileDefinition = defineDocumentType({ + type: "muninn.transport_profile", + schemaName: "muninn.transport_profile", + schemaId: "muninn.transport_profile.v1", + schemaVersion: "muninn.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profile_id || value?.profileId || value?.daemon_id || value?.daemonId || "muninn", + schema: parseObjectDocument("Muninn transport profile"), + }); + const SleipnirInputMappingDefinition = defineDocumentType({ + type: "sleipnir.input_mapping", + schemaName: "sleipnir.input_mapping", + schemaId: "sleipnir.input_mapping.v1", + schemaVersion: "sleipnir.input_mapping.v1", + global: false, + name: (value: DocumentValue) => value?.providerId || value?.provider_id || "sleipnir", + schema: parseObjectDocument("Sleipnir input mapping"), + }); + const aetheriaAssetManifestDefinition = defineDocumentType({ + type: "gamecult.aetheria.asset_manifest", + schemaName: "gamecult.aetheria.asset_manifest", + schemaId: "gamecult.aetheria.asset_manifest.v1", + schemaVersion: "gamecult.aetheria.asset_manifest.v1", + global: false, + name: (value: DocumentValue) => value?.runId || value?.run_id || "aetheria-assets", + schema: parseObjectDocument("Aetheria asset manifest"), + }); + const aetheriaGravityViewportDefinition = defineDocumentType({ + type: "gamecult.fields.gravity", + schemaName: "gamecult.fields.gravity", + schemaId: "gamecult.fields.gravity.v1", + schemaVersion: "gamecult.fields.gravity.v1", + global: false, + name: (value: DocumentValue) => value?.viewportId || value?.viewport_id || "aetheria-gravity", + schema: parseObjectDocument("Aetheria gravity viewport"), + }); + const aetheriaObjectsViewportDefinition = defineDocumentType({ + type: "gamecult.fields.objects", + schemaName: "gamecult.fields.objects", + schemaId: "gamecult.fields.objects.v1", + schemaVersion: "gamecult.fields.objects.v1", + global: false, + name: (value: DocumentValue) => value?.viewportId || value?.viewport_id || "aetheria-objects", + schema: parseObjectDocument("Aetheria objects viewport"), + }); + const aetheriaRenderSplatsViewportDefinition = defineDocumentType({ + type: "gamecult.fields.splats", + schemaName: "gamecult.fields.splats", + schemaId: "gamecult.fields.splats.v1", + schemaVersion: "gamecult.fields.splats.v1", + global: false, + name: (value: DocumentValue) => value?.viewportId || value?.viewport_id || "aetheria-render-splats", + schema: parseObjectDocument("Aetheria render splats viewport"), + }); + const operatorStateDefinition = defineDocumentType({ + type: "gamecult.vili.operator_state", + schemaName: "gamecult.vili.operator_state", + schemaId: "gamecult.vili.operator_state.v1", + schemaVersion: "gamecult.vili.operator_state.v1", + global: false, + name: (value: DocumentValue) => value?.service || value?.providerId || "operator", + schema: parseObjectDocument("Vili operator state"), + }); + const viliCommandBoundaryDefinition = defineDocumentType({ + type: "gamecult.vili.command_boundary", + schemaName: "gamecult.vili.command_boundary", + schemaId: "gamecult.vili.command_boundary.v1", + schemaVersion: "gamecult.vili.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundaryId || value?.daemonId || "vili", + schema: parseObjectDocument("Vili command boundary"), + }); + const viliTransportProfileDefinition = defineDocumentType({ + type: "gamecult.vili.transport_profile", + schemaName: "gamecult.vili.transport_profile", + schemaId: "gamecult.vili.transport_profile.v1", + schemaVersion: "gamecult.vili.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profileId || value?.daemonId || "vili", + schema: parseObjectDocument("Vili transport profile"), + }); + const weksaOperatorStateDefinition = defineDocumentType({ + type: "weksa.operator_state", + schemaName: "weksa.operator_state", + schemaId: "weksa.operator_state.v0", + schemaVersion: "weksa.operator_state.v0", + global: false, + name: (value: DocumentValue) => value?.daemon_id || value?.daemonId || value?.provider_id || value?.providerId || "weksa", + schema: parseObjectDocument("Weksa operator state"), + }); + const weksaCommandBoundaryDefinition = defineDocumentType({ + type: "weksa.command_boundary", + schemaName: "weksa.command_boundary", + schemaId: "weksa.command_boundary.v1", + schemaVersion: "weksa.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundary_id || value?.boundaryId || value?.daemon_id || value?.daemonId || "weksa", + schema: parseObjectDocument("Weksa command boundary"), + }); + const weksaTransportProfileDefinition = defineDocumentType({ + type: "weksa.transport_profile", + schemaName: "weksa.transport_profile", + schemaId: "weksa.transport_profile.v1", + schemaVersion: "weksa.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profile_id || value?.profileId || value?.daemon_id || value?.daemonId || "weksa", + schema: parseObjectDocument("Weksa transport profile"), + }); + const stonksRequestEventDefinition = defineDocumentType({ + type: "stonks.request_event", + schemaName: "stonks.request_event", + schemaId: "stonks.request_event.v1", + schemaVersion: "stonks.request_event.v1", + global: false, + name: (value: DocumentValue) => value?.id || "request", + schema: parseObjectDocument("Stonks request event"), + }); + const stonksMarketSnapshotDefinition = defineDocumentType({ + type: "stonks.market_snapshot", + schemaName: "stonks.market_snapshot", + schemaId: "stonks.market_snapshot.v1", + schemaVersion: "stonks.market_snapshot.v1", + global: true, + schema: parseObjectDocument("Stonks market snapshot"), + }); + const stonksCommandBoundaryDefinition = defineDocumentType({ + type: "stonks.command_boundary", + schemaName: "stonks.command_boundary", + schemaId: "stonks.command_boundary.v1", + schemaVersion: "stonks.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundaryId || value?.daemonId || "stonks", + schema: parseObjectDocument("Stonks command boundary"), + }); + const stonksTransportProfileDefinition = defineDocumentType({ + type: "stonks.transport_profile", + schemaName: "stonks.transport_profile", + schemaId: "stonks.transport_profile.v1", + schemaVersion: "stonks.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profileId || value?.daemonId || "stonks", + schema: parseObjectDocument("Stonks transport profile"), + }); + const streamPixelsCommandBoundaryDefinition = defineDocumentType({ + type: "streampixels.command_boundary", + schemaName: "streampixels.command_boundary", + schemaId: "streampixels.command_boundary.v1", + schemaVersion: "streampixels.command_boundary.v1", + global: false, + name: (value: DocumentValue) => value?.boundaryId || value?.daemonId || "streampixels", + schema: parseObjectDocument("StreamPixels command boundary"), + }); + const streamPixelsTransportProfileDefinition = defineDocumentType({ + type: "streampixels.transport_profile", + schemaName: "streampixels.transport_profile", + schemaId: "streampixels.transport_profile.v1", + schemaVersion: "streampixels.transport_profile.v1", + global: false, + name: (value: DocumentValue) => value?.profileId || value?.daemonId || "streampixels", + schema: parseObjectDocument("StreamPixels transport profile"), + }); + const voidbotSwarmSnapshotDefinition = defineDocumentType({ + type: "voidbot.swarm_state_snapshot", + schemaName: "voidbot.swarm_state_snapshot", + schemaId: "voidbot.swarm_state_snapshot.v1", + schemaVersion: "voidbot.swarm_state_snapshot.v1", + global: true, + schema: parseObjectDocument("VoidBot swarm snapshot"), + }); + const voidbotProviderCatalogDefinition = defineDocumentType({ + type: "voidbot.provider_advertisement_catalog", + schemaName: "voidbot.provider_advertisement_catalog", + schemaId: "voidbot.provider_advertisement_catalog.v0", + schemaVersion: "voidbot.provider_advertisement_catalog.v0", + global: true, + schema: parseObjectDocument("VoidBot provider advertisement catalog"), + }); + + return { + interfaceBindingDefinition, + evePluginAdvertisementDefinition, + ghostlightSchemaCatalogDefinition, + heimdallCommandBoundaryDefinition, + heimdallTransportProfileDefinition, + interfaceLayoutDefinition, + idunnCommandBoundaryDefinition, + idunnDaemonHealthDefinition, + idunnTransportProfileDefinition, + muninnCaptureStreamCommandDefinition, + muninnCaptureStreamDefinition, + muninnCommandBoundaryDefinition, + muninnMediaAudioPacketDefinition, + muninnMediaReceiverFeedbackDefinition, + muninnMediaVideoAccessUnitDefinition, + muninnHidControllerStateDefinition, + muninnMoveControllerStateDefinition, + muninnMoveIdentityDefinition, + muninnMoveLightCommandDefinition, + muninnMoveMarkerCandidateDefinition, + muninnObsStreamCatalogDefinition, + muninnQuestAccessDefinition, + muninnTelemetrySurfaceDefinition, + muninnTransportProfileDefinition, + SleipnirInputMappingDefinition, + aetheriaAssetManifestDefinition, + aetheriaGravityViewportDefinition, + aetheriaObjectsViewportDefinition, + aetheriaRenderSplatsViewportDefinition, + operatorStateDefinition, + providerAdvertisementDefinition, + stonksCommandBoundaryDefinition, + stonksMarketSnapshotDefinition, + stonksRequestEventDefinition, + stonksTransportProfileDefinition, + streamPixelsCommandBoundaryDefinition, + streamPixelsTransportProfileDefinition, + surfaceDefinition, + viliCommandBoundaryDefinition, + viliTransportProfileDefinition, + weksaCommandBoundaryDefinition, + weksaOperatorStateDefinition, + weksaTransportProfileDefinition, + voidbotProviderCatalogDefinition, + voidbotSwarmSnapshotDefinition, + }; +} + From d1a9edad3a116be341b0b5198c6998c31a083bda Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 7 Sep 2026 21:42:54 +0200 Subject: [PATCH 068/203] Port gamecult.runtime_presence_health.v2 to cultnet-ts The dual-proved runtime presence contract existed only in cultnet-rs, so no TypeScript service could make a statement Odin admits as presence. Odin files anything else away as an ordinary document and never produces a topology correlation for it, which is why Heimdall could warm forever without being promoted. The port is what it should be: the same fields at the same key slots. The Rust derive serialises a positional tuple indexed by those slots, so the TypeScript side names the slots and fills the same 24. Both proofs cover one payload -- the complete record with both signature fields empty -- and the two signing messages use the domain separation their respective identities already define. Conformance is against vectors emitted by cultnet-rs, not a fixture written for both sides at once, which would agree with itself and prove nothing: `cargo run --example runtime_presence_vector -p cultnet-rs` regenerates them. Co-Authored-By: Claude Opus 5 --- .../examples/runtime_presence_vector.rs | 67 +++++ packages/cultnet-ts/src/index.ts | 18 ++ .../cultnet-ts/src/runtime-presence-health.ts | 256 ++++++++++++++++++ .../fixtures/runtime-presence-vectors.json | 4 + .../test/runtime-presence-health.test.ts | 123 +++++++++ 5 files changed, 468 insertions(+) create mode 100644 packages/cultnet-rs/examples/runtime_presence_vector.rs create mode 100644 packages/cultnet-ts/src/runtime-presence-health.ts create mode 100644 packages/cultnet-ts/test/fixtures/runtime-presence-vectors.json create mode 100644 packages/cultnet-ts/test/runtime-presence-health.test.ts diff --git a/packages/cultnet-rs/examples/runtime_presence_vector.rs b/packages/cultnet-rs/examples/runtime_presence_vector.rs new file mode 100644 index 00000000..04571a8b --- /dev/null +++ b/packages/cultnet-rs/examples/runtime_presence_vector.rs @@ -0,0 +1,67 @@ +//! Emit canonical bytes for one fixed runtime-presence record. +//! +//! This is the reference the TypeScript fork is tested against. The two forks +//! agree only if `gamecult.runtime_presence_health.v2` means the same bytes in +//! both, and the only way to know that is to have one produce the vector and +//! the other reproduce it. A fixture written by hand for both sides would agree +//! with itself and prove nothing. + +use cultnet_rs::{GameCultRuntimeCapability, GameCultRuntimePresenceHealthRecord}; + +fn main() -> anyhow::Result<()> { + let record = GameCultRuntimePresenceHealthRecord { + schema_version: "gamecult.runtime_presence_health.v2".into(), + target: "heimdall".into(), + expected_projection_sha256: format!("sha256-{}", "a".repeat(64)), + plan_id: format!("sha256-{}", "b".repeat(64)), + incarnation_id: "service-incarnation-1".into(), + sealed_release_id: format!("sha256-{}", "c".repeat(64)), + activation_witness_sha256: format!("sha256-{}", "d".repeat(64)), + state_schema_generation: Some("v1".into()), + state_contract_sha256: Some(format!("sha256-{}", "e".repeat(64))), + runtime_id: "heimdall-yggdrasil".into(), + runtime_instance_id: format!("sha256-{}", "f".repeat(64)), + bound_endpoint: Some("http://127.0.0.1:14101".into()), + capabilities: vec![ + GameCultRuntimeCapability { + capability: "heimdall.access".into(), + schema: "heimdall.access.v1".into(), + compatibility: "v1".into(), + capacity: 2, + }, + GameCultRuntimeCapability { + capability: "zeta.runtime".into(), + schema: "zeta.runtime.v1".into(), + compatibility: "v1".into(), + capacity: 1, + }, + ], + health_contract: "heimdall.cultnet-rudp-provider-health".into(), + state: "warming".into(), + detail: "Heimdall candidate warming".into(), + write_lease_sha256: None, + signer_identity_id: "1".repeat(64), + publisher_sequence: 7, + observed_at_unix_millis: 1_757_000_000_000, + signature_algorithm: "ed25519".into(), + signature: Vec::new(), + activation_signer_identity_id: "2".repeat(64), + activation_signature: Vec::new(), + }; + + // The proof payload: both signature fields empty. This is what both keys + // sign, so it is the vector that matters most. + let proof = record.canonical_proof_payload()?; + println!("proof {}", hex(&proof)); + + // And the complete record, with both proofs filled, to pin the signed shape. + let mut signed = record; + signed.signature = vec![0x11; 64]; + signed.activation_signature = vec![0x22; 64]; + println!("signed {}", hex(&rmp_serde::to_vec(&signed)?)); + Ok(()) +} + +fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|byte| format!("{byte:02x}")).collect() +} diff --git a/packages/cultnet-ts/src/index.ts b/packages/cultnet-ts/src/index.ts index db24d17b..140526c5 100644 --- a/packages/cultnet-ts/src/index.ts +++ b/packages/cultnet-ts/src/index.ts @@ -156,3 +156,21 @@ export { type SignedDaemonHealthPublisher, type SignedDaemonHealthPublisherOptions, } from "./signed-daemon-health"; + +export { + GAMECULT_RUNTIME_ACTIVATION_PROOF_SIGNING_PURPOSE, + GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA, + GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE, + GAMECULT_RUNTIME_PRESENCE_IDENTITY_NAME, + IDUNN_RUNTIME_ACTIVATION_CREDENTIAL_NAME, + RUNTIME_PRESENCE_FIELD_COUNT, + RUNTIME_PRESENCE_SLOT, + encodeRuntimePresenceHealth, + runtimePresenceActivationSigningMessage, + runtimePresenceProofPayload, + runtimePresenceProviderSigningMessage, + validateRuntimePresence, + type GameCultRuntimeCapability, + type RuntimePresenceHealth, + type RuntimePresenceState, +} from "./runtime-presence-health"; diff --git a/packages/cultnet-ts/src/runtime-presence-health.ts b/packages/cultnet-ts/src/runtime-presence-health.ts new file mode 100644 index 00000000..8d70da8e --- /dev/null +++ b/packages/cultnet-ts/src/runtime-presence-health.ts @@ -0,0 +1,256 @@ +// Runtime presence health: the dual-proved statement one launched incarnation +// makes about itself. +// +// This is the TypeScript half of `gamecult.runtime_presence_health.v2`, the +// contract `cultnet-rs` defines in `runtime_authority_contracts.rs`. Odin admits +// a document as runtime presence only when it carries this schema id and its +// record key equals the signed target; anything else it files away as an +// ordinary document, and no topology correlation is ever produced for it. +// +// Two proofs cover one payload. The stable provider key says "this service made +// this statement"; the activation key Idunn hands the process at launch says +// "and it is the incarnation Idunn started". Neither establishes admission +// alone, which is why both signature fields are empty in the signed bytes: it +// avoids a circular signature while binding every authority-bearing field and +// both signer identities. +// +// The wire shape is a positional tuple indexed by the same field slots the Rust +// derive emits. Field N here is field N there; that correspondence is the whole +// contract, and the constants below name the slots so it stays checkable. + +import { encode } from "@msgpack/msgpack"; + +export const GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA = "gamecult.runtime_presence_health.v2"; + +/** Purpose for the stable provider-health signature over the proof payload. */ +export const GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE = + "gamecult.runtime_presence_health.v2"; + +/** Purpose for the activation-scoped proof over the same payload. */ +export const GAMECULT_RUNTIME_ACTIVATION_PROOF_SIGNING_PURPOSE = + "gamecult.runtime_presence.activation-proof.v1"; + +/** systemd file-descriptor name Idunn uses for the activation signing key. */ +export const IDUNN_RUNTIME_ACTIVATION_CREDENTIAL_NAME = "gamecult-idunn-runtime-activation-key"; + +/** systemd file-descriptor name Idunn uses for the provider-health identity. */ +export const GAMECULT_RUNTIME_PRESENCE_IDENTITY_NAME = "gamecult-runtime-presence-identity"; + +const PROVIDER_HEALTH_SIGNATURE_DOMAIN = Buffer.from( + "gamecult.provider-health.signature.v1\0", + "utf8", +); + +const RUNTIME_ACTIVATION_SIGNATURE_DOMAIN = Buffer.from( + "idunn.runtime-activation.signature.v1\0", + "utf8", +); + +/** + * Positional slots, mirroring the `#[cultcache(key = N)]` attributes on + * `GameCultRuntimePresenceHealthRecord`. The tuple is always this long: the + * Rust derive serialises `max_slot + 1` elements regardless of which are set. + */ +export const RUNTIME_PRESENCE_SLOT = { + schemaVersion: 0, + target: 1, + expectedProjectionSha256: 2, + planId: 3, + incarnationId: 4, + sealedReleaseId: 5, + activationWitnessSha256: 6, + stateSchemaGeneration: 7, + stateContractSha256: 8, + runtimeId: 9, + runtimeInstanceId: 10, + boundEndpoint: 11, + capabilities: 12, + healthContract: 13, + state: 14, + detail: 15, + writeLeaseSha256: 16, + signerIdentityId: 17, + publisherSequence: 18, + observedAtUnixMillis: 19, + signatureAlgorithm: 20, + signature: 21, + activationSignerIdentityId: 22, + activationSignature: 23, +} as const; + +export const RUNTIME_PRESENCE_FIELD_COUNT = 24; + +/** A capability claim. Nested as its own positional tuple, not a map. */ +export interface GameCultRuntimeCapability { + capability: string; + schema: string; + compatibility: string; + capacity: number; +} + +/** The states the contract admits. */ +export type RuntimePresenceState = "warming" | "active" | "degraded" | "failed"; + +export interface RuntimePresenceHealth { + target: string; + expectedProjectionSha256: string; + planId: string; + incarnationId: string; + sealedReleaseId: string; + activationWitnessSha256: string; + stateSchemaGeneration: string | null; + stateContractSha256: string | null; + runtimeId: string; + runtimeInstanceId: string; + boundEndpoint: string | null; + capabilities: GameCultRuntimeCapability[]; + healthContract: string; + state: RuntimePresenceState; + detail: string; + writeLeaseSha256: string | null; + signerIdentityId: string; + publisherSequence: number; + observedAtUnixMillis: number; + activationSignerIdentityId: string; +} + +function encodeCapability(capability: GameCultRuntimeCapability): unknown[] { + // Plain serde struct on the Rust side, and the record is encoded with + // rmp_serde's compact form, so a capability is a positional tuple too. + return [capability.capability, capability.schema, capability.compatibility, capability.capacity]; +} + +/** + * Capabilities are canonical only when strictly increasing by + * `(capability, schema, compatibility)`. Sorting here rather than trusting the + * caller keeps a correct statement from being rejected over field order. + */ +function canonicalCapabilities(capabilities: GameCultRuntimeCapability[]): unknown[][] { + const sorted = [...capabilities].sort((left, right) => { + const leftKey = [left.capability, left.schema, left.compatibility]; + const rightKey = [right.capability, right.schema, right.compatibility]; + for (let index = 0; index < leftKey.length; index += 1) { + if (leftKey[index]! < rightKey[index]!) return -1; + if (leftKey[index]! > rightKey[index]!) return 1; + } + return 0; + }); + return sorted.map(encodeCapability); +} + +function presenceTuple( + presence: RuntimePresenceHealth, + signature: Uint8Array, + activationSignature: Uint8Array, +): unknown[] { + const fields = new Array(RUNTIME_PRESENCE_FIELD_COUNT); + fields[RUNTIME_PRESENCE_SLOT.schemaVersion] = GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA; + fields[RUNTIME_PRESENCE_SLOT.target] = presence.target; + fields[RUNTIME_PRESENCE_SLOT.expectedProjectionSha256] = presence.expectedProjectionSha256; + fields[RUNTIME_PRESENCE_SLOT.planId] = presence.planId; + fields[RUNTIME_PRESENCE_SLOT.incarnationId] = presence.incarnationId; + fields[RUNTIME_PRESENCE_SLOT.sealedReleaseId] = presence.sealedReleaseId; + fields[RUNTIME_PRESENCE_SLOT.activationWitnessSha256] = presence.activationWitnessSha256; + fields[RUNTIME_PRESENCE_SLOT.stateSchemaGeneration] = presence.stateSchemaGeneration; + fields[RUNTIME_PRESENCE_SLOT.stateContractSha256] = presence.stateContractSha256; + fields[RUNTIME_PRESENCE_SLOT.runtimeId] = presence.runtimeId; + fields[RUNTIME_PRESENCE_SLOT.runtimeInstanceId] = presence.runtimeInstanceId; + fields[RUNTIME_PRESENCE_SLOT.boundEndpoint] = presence.boundEndpoint; + fields[RUNTIME_PRESENCE_SLOT.capabilities] = canonicalCapabilities(presence.capabilities); + fields[RUNTIME_PRESENCE_SLOT.healthContract] = presence.healthContract; + fields[RUNTIME_PRESENCE_SLOT.state] = presence.state; + fields[RUNTIME_PRESENCE_SLOT.detail] = presence.detail; + fields[RUNTIME_PRESENCE_SLOT.writeLeaseSha256] = presence.writeLeaseSha256; + fields[RUNTIME_PRESENCE_SLOT.signerIdentityId] = presence.signerIdentityId; + fields[RUNTIME_PRESENCE_SLOT.publisherSequence] = presence.publisherSequence; + fields[RUNTIME_PRESENCE_SLOT.observedAtUnixMillis] = presence.observedAtUnixMillis; + fields[RUNTIME_PRESENCE_SLOT.signatureAlgorithm] = "ed25519"; + fields[RUNTIME_PRESENCE_SLOT.signature] = signature; + fields[RUNTIME_PRESENCE_SLOT.activationSignerIdentityId] = presence.activationSignerIdentityId; + fields[RUNTIME_PRESENCE_SLOT.activationSignature] = activationSignature; + return fields; +} + +/** + * Reject statements the contract forbids, before they are signed. + * + * These are the same conditions `validate_shape` enforces on the Rust side. A + * statement that fails here would be refused after a round trip and a signature; + * failing early names the actual problem instead. + */ +export function validateRuntimePresence(presence: RuntimePresenceHealth): void { + if (presence.publisherSequence <= 0 || !Number.isSafeInteger(presence.publisherSequence)) { + throw new Error("runtime presence publisher sequence must be a positive integer"); + } + if (presence.observedAtUnixMillis <= 0 || !Number.isSafeInteger(presence.observedAtUnixMillis)) { + throw new Error("runtime presence observation time must be a positive integer"); + } + if (presence.detail.length > 512 || /\p{Cc}/u.test(presence.detail)) { + throw new Error("runtime presence detail is too long or carries control characters"); + } + if (presence.state === "warming" && presence.writeLeaseSha256 !== null) { + throw new Error("warming runtime presence cannot claim a process write lease"); + } + if (presence.stateSchemaGeneration === null && presence.writeLeaseSha256 !== null) { + throw new Error("stateless runtime presence cannot claim a process write lease"); + } + const generationPresent = presence.stateSchemaGeneration !== null; + const contractPresent = presence.stateContractSha256 !== null; + if (generationPresent !== contractPresent) { + throw new Error("runtime presence state lineage is partial"); + } +} + +/** + * The one payload both proofs cover: the complete record with both signature + * fields empty. + */ +export function runtimePresenceProofPayload(presence: RuntimePresenceHealth): Uint8Array { + validateRuntimePresence(presence); + return encode(presenceTuple(presence, new Uint8Array(), new Uint8Array())); +} + +/** The complete signed record, as put on the wire. */ +export function encodeRuntimePresenceHealth( + presence: RuntimePresenceHealth, + signature: Uint8Array, + activationSignature: Uint8Array, +): Uint8Array { + if (signature.length !== 64 || activationSignature.length !== 64) { + throw new Error("runtime presence proofs must both be 64-byte ed25519 signatures"); + } + validateRuntimePresence(presence); + return encode(presenceTuple(presence, signature, activationSignature)); +} + +function domainSeparated(domain: Buffer, purpose: string, payload: Uint8Array): Buffer { + const purposeBytes = Buffer.from(purpose, "utf8"); + const lengths = Buffer.alloc(16); + lengths.writeBigUInt64BE(BigInt(purposeBytes.length), 0); + lengths.writeBigUInt64BE(BigInt(payload.length), 8); + return Buffer.concat([ + domain, + lengths.subarray(0, 8), + purposeBytes, + lengths.subarray(8, 16), + Buffer.from(payload), + ]); +} + +/** Message the stable provider-health key signs. */ +export function runtimePresenceProviderSigningMessage(payload: Uint8Array): Buffer { + return domainSeparated( + PROVIDER_HEALTH_SIGNATURE_DOMAIN, + GAMECULT_RUNTIME_PRESENCE_HEALTH_SIGNING_PURPOSE, + payload, + ); +} + +/** Message the activation key Idunn passed at launch signs. */ +export function runtimePresenceActivationSigningMessage(payload: Uint8Array): Buffer { + return domainSeparated( + RUNTIME_ACTIVATION_SIGNATURE_DOMAIN, + GAMECULT_RUNTIME_ACTIVATION_PROOF_SIGNING_PURPOSE, + payload, + ); +} diff --git a/packages/cultnet-ts/test/fixtures/runtime-presence-vectors.json b/packages/cultnet-ts/test/fixtures/runtime-presence-vectors.json new file mode 100644 index 00000000..043689f4 --- /dev/null +++ b/packages/cultnet-ts/test/fixtures/runtime-presence-vectors.json @@ -0,0 +1,4 @@ +{ + "proof": "dc0018d92367616d6563756c742e72756e74696d655f70726573656e63655f6865616c74682e7632a86865696d64616c6cd9477368613235362d61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161d9477368613235362d62626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262b5736572766963652d696e6361726e6174696f6e2d31d9477368613235362d63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363d9477368613235362d64646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a27631d9477368613235362d65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565b26865696d64616c6c2d79676764726173696cd9477368613235362d66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666b6687474703a2f2f3132372e302e302e313a31343130319294af6865696d64616c6c2e616363657373b26865696d64616c6c2e6163636573732e7631a276310294ac7a6574612e72756e74696d65af7a6574612e72756e74696d652e7631a2763101d9256865696d64616c6c2e63756c746e65742d727564702d70726f76696465722d6865616c7468a77761726d696e67ba4865696d64616c6c2063616e646964617465207761726d696e67c0d9403131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313107cf00000199155c6200a765643235353139c400d94032323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232c400", + "signed": "dc0018d92367616d6563756c742e72756e74696d655f70726573656e63655f6865616c74682e7632a86865696d64616c6cd9477368613235362d61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161d9477368613235362d62626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262b5736572766963652d696e6361726e6174696f6e2d31d9477368613235362d63636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363d9477368613235362d64646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a27631d9477368613235362d65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565b26865696d64616c6c2d79676764726173696cd9477368613235362d66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666b6687474703a2f2f3132372e302e302e313a31343130319294af6865696d64616c6c2e616363657373b26865696d64616c6c2e6163636573732e7631a276310294ac7a6574612e72756e74696d65af7a6574612e72756e74696d652e7631a2763101d9256865696d64616c6c2e63756c746e65742d727564702d70726f76696465722d6865616c7468a77761726d696e67ba4865696d64616c6c2063616e646964617465207761726d696e67c0d9403131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313107cf00000199155c6200a765643235353139c44011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111d94032323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232c44022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" +} diff --git a/packages/cultnet-ts/test/runtime-presence-health.test.ts b/packages/cultnet-ts/test/runtime-presence-health.test.ts new file mode 100644 index 00000000..fa21aeaa --- /dev/null +++ b/packages/cultnet-ts/test/runtime-presence-health.test.ts @@ -0,0 +1,123 @@ +// Cross-fork conformance for `gamecult.runtime_presence_health.v2`. +// +// The vectors below were produced by `cultnet-rs`, not written by hand: +// +// cargo run --example runtime_presence_vector -p cultnet-rs +// +// That matters. A fixture authored for both sides at once agrees with itself +// and proves nothing; the contract is only real if one fork emits the bytes and +// the other reproduces them. Regenerate these vectors from the Rust example if +// the record ever changes, and never by editing them to match this code. + +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { + RUNTIME_PRESENCE_FIELD_COUNT, + encodeRuntimePresenceHealth, + runtimePresenceProofPayload, + validateRuntimePresence, + type RuntimePresenceHealth, +} from "../src/runtime-presence-health"; + +// Read from the source tree, not a copy beside the compiled test: the vectors +// are generated output and must have exactly one home. +const VECTORS = JSON.parse( + readFileSync( + join(__dirname, "..", "..", "test", "fixtures", "runtime-presence-vectors.json"), + "utf8", + ), +) as { proof: string; signed: string }; + +/** The same statement the Rust example builds. */ +function referencePresence(): RuntimePresenceHealth { + return { + target: "heimdall", + expectedProjectionSha256: `sha256-${"a".repeat(64)}`, + planId: `sha256-${"b".repeat(64)}`, + incarnationId: "service-incarnation-1", + sealedReleaseId: `sha256-${"c".repeat(64)}`, + activationWitnessSha256: `sha256-${"d".repeat(64)}`, + stateSchemaGeneration: "v1", + stateContractSha256: `sha256-${"e".repeat(64)}`, + runtimeId: "heimdall-yggdrasil", + runtimeInstanceId: `sha256-${"f".repeat(64)}`, + boundEndpoint: "http://127.0.0.1:14101", + capabilities: [ + { + capability: "heimdall.access", + schema: "heimdall.access.v1", + compatibility: "v1", + capacity: 2, + }, + { capability: "zeta.runtime", schema: "zeta.runtime.v1", compatibility: "v1", capacity: 1 }, + ], + healthContract: "heimdall.cultnet-rudp-provider-health", + state: "warming", + detail: "Heimdall candidate warming", + writeLeaseSha256: null, + signerIdentityId: "1".repeat(64), + publisherSequence: 7, + observedAtUnixMillis: 1_757_000_000_000, + activationSignerIdentityId: "2".repeat(64), + }; +} + +function hex(bytes: Uint8Array): string { + return Buffer.from(bytes).toString("hex"); +} + +test("the proof payload is byte-identical to the one cultnet-rs signs", () => { + assert.equal(hex(runtimePresenceProofPayload(referencePresence())), VECTORS.proof); +}); + +test("the signed record carries both proofs in their own slots", () => { + const signed = encodeRuntimePresenceHealth( + referencePresence(), + new Uint8Array(64).fill(0x11), + new Uint8Array(64).fill(0x22), + ); + // Byte-identical to the fully signed record cultnet-rs emits, so the proofs + // land in their own slots rather than being appended. + assert.equal(hex(signed), VECTORS.signed); +}); + +test("capabilities are ordered canonically regardless of caller order", () => { + const presence = referencePresence(); + const reversed = { ...presence, capabilities: [...presence.capabilities].reverse() }; + assert.equal( + hex(runtimePresenceProofPayload(reversed)), + hex(runtimePresenceProofPayload(presence)), + ); +}); + +test("the tuple is always the full contract width", () => { + assert.equal(RUNTIME_PRESENCE_FIELD_COUNT, 24); +}); + +test("statements the contract forbids are refused before they are signed", () => { + const warmingWithLease = { + ...referencePresence(), + writeLeaseSha256: `sha256-${"0".repeat(64)}`, + }; + assert.throws(() => validateRuntimePresence(warmingWithLease), /cannot claim a process write/); + + const statelessWithLease = { + ...referencePresence(), + state: "active" as const, + stateSchemaGeneration: null, + stateContractSha256: null, + writeLeaseSha256: `sha256-${"0".repeat(64)}`, + }; + assert.throws(() => validateRuntimePresence(statelessWithLease), /stateless runtime presence/); + + assert.throws( + () => validateRuntimePresence({ ...referencePresence(), publisherSequence: 0 }), + /publisher sequence/, + ); + + const partialLineage = { ...referencePresence(), stateContractSha256: null }; + assert.throws(() => validateRuntimePresence(partialLineage), /state lineage is partial/); +}); From a250ec69eb91a2a89b34e8899e9a6b0114ba46dd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 00:17:19 +0200 Subject: [PATCH 069/203] Give the media stream records a home that is not one producer's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four record types described a media stream and nothing about who produced it, while living in odin-core under a `muninn.` prefix. Every field is producer-agnostic; the name was the only coupling, and a second producer would have had to impersonate Muninn or fork the shape. They move to cultnet-rs as `gamecult.media_*`, matching the prefix already used for cross-service contracts like gamecult.runtime_presence_health and gamecult.service_trust_anchor, while cultmesh.* stays reserved for session primitives. Field order and key indices are carried over untouched, including the `default` on missing_video_chunk_keys. The tests pin what a consumer in another language is entitled to rely on: positional layout, the slot each field occupies, and that a record written before that field was appended still decodes. One test is ignored rather than passing. Byte payloads currently serialize as MessagePack arrays of integers because the derive writes its own Serialize and ignores serde attributes; C#, TypeScript and Python all emit bin, which makes Rust the divergent runtime and costs 1.45x on every media byte. Fixing it needs an opt-in hint in cultcache-rs-derive rather than a global change, because signatures are taken over the whole positional serialization and canonical hashes over the same bytes — flipping byte encoding estate-wide would invalidate the live Idunn authority chain. That work is sequenced by the Swarm Migration session; the ignored test un-ignores when the hint lands. Parked on its own branch. The Muninn and Odin ends of this move are not done: odin-core still declares these types, and muninn-daemon still imports them from there. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/lib.rs | 2 + .../cultnet-rs/src/media_stream_contracts.rs | 194 +++++++++++++++ .../tests/media_stream_contracts.rs | 227 ++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 packages/cultnet-rs/src/media_stream_contracts.rs create mode 100644 packages/cultnet-rs/tests/media_stream_contracts.rs diff --git a/packages/cultnet-rs/src/lib.rs b/packages/cultnet-rs/src/lib.rs index 3e8a3c94..5aa121af 100644 --- a/packages/cultnet-rs/src/lib.rs +++ b/packages/cultnet-rs/src/lib.rs @@ -1,6 +1,7 @@ mod contracts; mod idunn_deployment_brake; mod idunn_lifecycle_brake; +mod media_stream_contracts; mod provider_health_contracts; mod runtime_authority_contracts; mod service_identity; @@ -19,6 +20,7 @@ mod transport; pub use contracts::*; pub use idunn_deployment_brake::*; pub use idunn_lifecycle_brake::*; +pub use media_stream_contracts::*; pub use provider_health_contracts::*; pub use runtime_authority_contracts::*; pub use service_identity::*; diff --git a/packages/cultnet-rs/src/media_stream_contracts.rs b/packages/cultnet-rs/src/media_stream_contracts.rs new file mode 100644 index 00000000..e47b3a28 --- /dev/null +++ b/packages/cultnet-rs/src/media_stream_contracts.rs @@ -0,0 +1,194 @@ +//! CultMesh Media Stream: the typed contract for carrying live audio and video +//! over CultNet. +//! +//! These records describe a media stream and nothing about who produced it. A +//! capture broker, an editor runtime, a recorder, or a synthetic source may all +//! publish them; an OBS bridge, a compositor, a recorder, or a browser lowering +//! may all consume them. Producers advertise their streams through Odin and +//! consumers resolve them there, so neither end needs to name the other. +//! +//! They lived in `odin-core` as `muninn.media_*`, named after the one producer +//! that happened to emit them first. Every field here is producer-agnostic — the +//! name was the only coupling, and a second producer would have had to +//! impersonate Muninn or fork the shape. +//! +//! # Layout is wire contract +//! +//! CultCache encodes these positionally. Field order and `key` indices are the +//! wire format: append new fields with `default` at the next free index, never +//! reorder or renumber. `missing_video_chunk_keys` shows the pattern. +//! +//! # Timebase +//! +//! `pts_ticks`, `duration_ticks` and `deadline_ticks` are expressed in +//! `timebase_num / timebase_den` units, carried per record so a consumer never +//! has to assume a producer's clock. `deadline_ticks` is the presentation +//! deadline past which a late arrival is worth dropping rather than displaying. +//! +//! # Reliability +//! +//! Nothing here specifies a delivery guarantee. Whether a stream rides a +//! reliable or lossy CultNet channel, and whether it carries parity, is a +//! transport decision made per stream — not a property of the payload shape. +//! Video parity shards exist for producers that choose forward error correction; +//! audio currently has no parity record, and a producer emitting audio on a +//! lossy channel needs one. + +use cultcache_rs::DatabaseEntry; + +pub const GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA: &str = "gamecult.media_video_access_unit.v1"; +pub const GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA: &str = "gamecult.media_video_parity_shard.v2"; +pub const GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA: &str = "gamecult.media_audio_packet.v1"; +pub const GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA: &str = "gamecult.media_receiver_feedback.v1"; + +/// One coded video access unit, possibly split across `chunk_count` records +/// sharing a `frame_id`. A consumer reassembles by `(stream_id, session_id, +/// frame_id)` and orders by `chunk_index`. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_video_access_unit", + schema = "gamecult.media_video_access_unit.v1" +)] +pub struct GameCultMediaVideoAccessUnitRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub session_id: String, + #[cultcache(key = 2)] + pub frame_id: u64, + #[cultcache(key = 3)] + pub codec: String, + #[cultcache(key = 4)] + pub pts_ticks: i64, + #[cultcache(key = 5)] + pub duration_ticks: u32, + #[cultcache(key = 6)] + pub timebase_num: u32, + #[cultcache(key = 7)] + pub timebase_den: u32, + #[cultcache(key = 8)] + pub keyframe: bool, + #[cultcache(key = 9)] + pub dependency_frame_id: Option, + #[cultcache(key = 10)] + pub deadline_ticks: i64, + #[cultcache(key = 11)] + pub chunk_index: u16, + #[cultcache(key = 12)] + pub chunk_count: u16, + #[cultcache(key = 13)] + pub payload: Vec, +} + +/// Forward-error-correction parity for one video access unit. Carries the +/// framing fields of the access unit it protects so a receiver can recover +/// without having seen the original, plus the shard geometry needed to run +/// recovery: `parity_index` within `parity_count`, and the chunk sizes the +/// parity was computed over. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_video_parity_shard", + schema = "gamecult.media_video_parity_shard.v2" +)] +pub struct GameCultMediaVideoParityShardRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub session_id: String, + #[cultcache(key = 2)] + pub frame_id: u64, + #[cultcache(key = 3)] + pub codec: String, + #[cultcache(key = 4)] + pub pts_ticks: i64, + #[cultcache(key = 5)] + pub duration_ticks: u32, + #[cultcache(key = 6)] + pub timebase_num: u32, + #[cultcache(key = 7)] + pub timebase_den: u32, + #[cultcache(key = 8)] + pub keyframe: bool, + #[cultcache(key = 9)] + pub dependency_frame_id: Option, + #[cultcache(key = 10)] + pub deadline_ticks: i64, + #[cultcache(key = 11)] + pub chunk_count: u16, + #[cultcache(key = 12)] + pub parity_index: u16, + #[cultcache(key = 13)] + pub parity_count: u16, + #[cultcache(key = 14)] + pub chunk_payload_bytes: u32, + #[cultcache(key = 15)] + pub last_chunk_payload_bytes: u32, + #[cultcache(key = 16)] + pub payload: Vec, +} + +/// One coded audio packet. Unlike video these are not chunked: a packet is +/// whole or it is absent, and `payload` carries whatever `codec` names. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_audio_packet", + schema = "gamecult.media_audio_packet.v1" +)] +pub struct GameCultMediaAudioPacketRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub session_id: String, + #[cultcache(key = 2)] + pub packet_id: u64, + #[cultcache(key = 3)] + pub codec: String, + #[cultcache(key = 4)] + pub pts_ticks: i64, + #[cultcache(key = 5)] + pub duration_ticks: u32, + #[cultcache(key = 6)] + pub timebase_num: u32, + #[cultcache(key = 7)] + pub timebase_den: u32, + #[cultcache(key = 8)] + pub deadline_ticks: i64, + #[cultcache(key = 9)] + pub payload: Vec, +} + +/// What a receiver observed, published back to the producer so it can adapt: +/// what decoded, what is missing, what arrived too late, and how much queue and +/// jitter the receiver is carrying. +/// +/// This is the only record here that flows consumer-to-producer. A producer is +/// free to ignore it; it reports observation, it does not issue commands. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_receiver_feedback", + schema = "gamecult.media_receiver_feedback.v1" +)] +pub struct GameCultMediaReceiverFeedbackRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub session_id: String, + #[cultcache(key = 2)] + pub receiver_id: String, + #[cultcache(key = 3)] + pub highest_decodable_frame_id: Option, + #[cultcache(key = 4)] + pub missing_frame_ids: Vec, + #[cultcache(key = 5)] + pub late_frame_ids: Vec, + #[cultcache(key = 6)] + pub requested_keyframe: bool, + #[cultcache(key = 7)] + pub jitter_us: i64, + #[cultcache(key = 8)] + pub decode_queue_us: i64, + #[cultcache(key = 9)] + pub observed_at: String, + #[cultcache(key = 10, default)] + pub missing_video_chunk_keys: Vec, +} diff --git a/packages/cultnet-rs/tests/media_stream_contracts.rs b/packages/cultnet-rs/tests/media_stream_contracts.rs new file mode 100644 index 00000000..9d2fc664 --- /dev/null +++ b/packages/cultnet-rs/tests/media_stream_contracts.rs @@ -0,0 +1,227 @@ +//! The CultMesh Media Stream records are a wire contract shared by producers and +//! consumers that never link each other. These tests pin the properties a +//! consumer is entitled to rely on: positional layout, declared field order, and +//! tolerance of records written before an appended field existed. + +use cultcache_rs::DatabaseEntry; +use cultnet_rs::{ + GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA, GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA, + GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA, GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA, + GameCultMediaAudioPacketRecord, GameCultMediaReceiverFeedbackRecord, + GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, +}; +use rmpv::Value; + +fn access_unit() -> GameCultMediaVideoAccessUnitRecord { + GameCultMediaVideoAccessUnitRecord { + stream_id: "raven-primary-av".to_string(), + session_id: "session-1".to_string(), + frame_id: 4242, + codec: "h264".to_string(), + pts_ticks: 90_000, + duration_ticks: 3_000, + timebase_num: 1, + timebase_den: 90_000, + keyframe: true, + dependency_frame_id: None, + deadline_ticks: 108_000, + chunk_index: 2, + chunk_count: 5, + payload: vec![0xDE, 0xAD, 0xBE, 0xEF], + } +} + +fn audio_packet() -> GameCultMediaAudioPacketRecord { + GameCultMediaAudioPacketRecord { + stream_id: "raven-primary-av".to_string(), + session_id: "session-1".to_string(), + packet_id: 77, + codec: "opus".to_string(), + pts_ticks: 48_000, + duration_ticks: 960, + timebase_num: 1, + timebase_den: 48_000, + deadline_ticks: 96_000, + payload: vec![1, 2, 3], + } +} + +fn decode_as_value(bytes: &[u8]) -> Value { + rmp_serde::from_slice(bytes).expect("records decode as generic MessagePack") +} + +/// The declared `key` indices are the wire layout, not documentation. A consumer +/// in another language reads position 2 for `frame_id` and position 13 for +/// `payload`; reordering the struct would silently break it. +#[test] +fn video_access_unit_encodes_as_a_positional_array() { + let encoded = rmp_serde::to_vec(&access_unit()).expect("encodes"); + let Value::Array(slots) = decode_as_value(&encoded) else { + panic!("access units must encode as a positional array"); + }; + + assert_eq!(slots.len(), 14, "field count is wire contract"); + assert_eq!(slots[0].as_str(), Some("raven-primary-av")); + assert_eq!(slots[2].as_u64(), Some(4242), "slot 2 is frame_id"); + assert_eq!(slots[3].as_str(), Some("h264")); + assert_eq!(slots[8].as_bool(), Some(true), "slot 8 is keyframe"); + assert!(slots[9].is_nil(), "an absent dependency stays nil, not 0"); + assert_eq!(slots[12].as_u64(), Some(5), "slot 12 is chunk_count"); + assert!( + matches!(&slots[13], Value::Binary(_) | Value::Array(_)), + "slot 13 is the payload; see payload_should_encode_as_messagepack_bin \ + for which of the two it ought to be" + ); +} + +/// Ignored until `cultcache-rs-derive` grows a `bytes` hint. +/// +/// The derive writes its own `Serialize` and calls `serialize_element` per slot, +/// so a `Vec` field goes out as a MessagePack array of integers. C#, +/// TypeScript and Python all emit `bin` for byte fields, which makes Rust the +/// divergent runtime and these records unreadable by the reference +/// implementation. `#[serde(with = "serde_bytes")]` cannot fix it — the derive +/// ignores serde attributes. +/// +/// It also costs 1.45x on every media byte: an 848-byte payload encodes to 1234 +/// bytes as an integer array against 851 as `bin`. +/// +/// The fix is not local to this file. Changing byte encoding globally would +/// invalidate every signature in the Idunn authority chain, because signatures +/// are taken over the whole positional serialization and canonical hashes over +/// the same bytes. It needs an opt-in hint so these three media records can +/// adopt `bin` without moving the signature-bearing ones. Owned by the Swarm +/// Migration work; un-ignore this when the hint lands. +#[test] +#[ignore = "blocked on a `bytes` opt-in hint in cultcache-rs-derive"] +fn payload_should_encode_as_messagepack_bin() { + let encoded = rmp_serde::to_vec(&access_unit()).expect("encodes"); + let Value::Array(slots) = decode_as_value(&encoded) else { + panic!("access units must encode as a positional array"); + }; + + assert_eq!( + slots[13].as_slice(), + Some([0xDE, 0xAD, 0xBE, 0xEF].as_slice()), + "payloads belong on the wire as bin, not as an array of integers" + ); +} + +#[test] +fn audio_packets_are_whole_and_carry_their_own_timebase() { + let encoded = rmp_serde::to_vec(&audio_packet()).expect("encodes"); + let Value::Array(slots) = decode_as_value(&encoded) else { + panic!("audio packets must encode as a positional array"); + }; + + assert_eq!(slots.len(), 10, "field count is wire contract"); + assert_eq!(slots[2].as_u64(), Some(77), "slot 2 is packet_id"); + assert_eq!(slots[6].as_u64(), Some(1), "slot 6 is timebase_num"); + assert_eq!(slots[7].as_u64(), Some(48_000), "slot 7 is timebase_den"); + + let decoded: GameCultMediaAudioPacketRecord = + rmp_serde::from_slice(&encoded).expect("round-trips"); + assert_eq!(decoded, audio_packet()); +} + +#[test] +fn parity_shards_carry_the_geometry_recovery_needs() { + let shard = GameCultMediaVideoParityShardRecord { + stream_id: "raven-primary-av".to_string(), + session_id: "session-1".to_string(), + frame_id: 4242, + codec: "h264".to_string(), + pts_ticks: 90_000, + duration_ticks: 3_000, + timebase_num: 1, + timebase_den: 90_000, + keyframe: false, + dependency_frame_id: Some(4241), + deadline_ticks: 108_000, + chunk_count: 5, + parity_index: 1, + parity_count: 2, + chunk_payload_bytes: 1024, + last_chunk_payload_bytes: 512, + payload: vec![9, 9, 9], + }; + + let encoded = rmp_serde::to_vec(&shard).expect("encodes"); + let Value::Array(slots) = decode_as_value(&encoded) else { + panic!("parity shards must encode as a positional array"); + }; + + assert_eq!(slots.len(), 17, "field count is wire contract"); + assert_eq!(slots[12].as_u64(), Some(1), "slot 12 is parity_index"); + assert_eq!(slots[13].as_u64(), Some(2), "slot 13 is parity_count"); + assert_eq!( + slots[15].as_u64(), + Some(512), + "slot 15 is last_chunk_payload_bytes, which recovery needs to size the tail" + ); + + let decoded: GameCultMediaVideoParityShardRecord = + rmp_serde::from_slice(&encoded).expect("round-trips"); + assert_eq!(decoded, shard); +} + +/// `missing_video_chunk_keys` was appended after producers were already writing +/// feedback. A record written without it must still decode, or the append was a +/// breaking change wearing a `default` attribute. +#[test] +fn receiver_feedback_decodes_records_written_before_its_last_field() { + let without_appended_field = Value::Array(vec![ + Value::from("raven-primary-av"), + Value::from("session-1"), + Value::from("starfire-obs"), + Value::from(4200u64), + Value::Array(vec![Value::from(4198u64)]), + Value::Array(vec![Value::from(4199u64)]), + Value::from(true), + Value::from(1_500i64), + Value::from(9_000i64), + Value::from("2026-09-09T00:00:00Z"), + ]); + + let mut bytes = Vec::new(); + rmpv::encode::write_value(&mut bytes, &without_appended_field).expect("writes legacy record"); + + let decoded: GameCultMediaReceiverFeedbackRecord = + rmp_serde::from_slice(&bytes).expect("a pre-append record must still decode"); + + assert_eq!(decoded.receiver_id, "starfire-obs"); + assert_eq!(decoded.highest_decodable_frame_id, Some(4200)); + assert!(decoded.requested_keyframe); + assert!( + decoded.missing_video_chunk_keys.is_empty(), + "the appended field defaults rather than failing the decode" + ); +} + +/// The published constants and the derived type identity must not drift apart; +/// a consumer resolving by schema id and a producer registering by type would +/// then disagree without either failing to compile. +#[test] +fn published_schema_constants_match_the_derived_identity() { + assert_eq!( + GameCultMediaVideoAccessUnitRecord::SCHEMA_NAME, + GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA + ); + assert_eq!( + GameCultMediaVideoParityShardRecord::SCHEMA_NAME, + GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA + ); + assert_eq!( + GameCultMediaAudioPacketRecord::SCHEMA_NAME, + GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA + ); + assert_eq!( + GameCultMediaReceiverFeedbackRecord::SCHEMA_NAME, + GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA + ); + + assert_eq!( + GameCultMediaAudioPacketRecord::TYPE, + "gamecult.media_audio_packet" + ); +} From 2ba404cc0b57a5447f1c93988830c3c1c310080e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 00:31:11 +0200 Subject: [PATCH 070/203] Carry media payloads as bin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three media payload fields take the derive's `bytes` attribute, so they serialize as MessagePack `bin` instead of an array of integers. An 848-byte payload goes from 1234 bytes on the wire to 851 — 1.45x off every media byte, on the one link measured to be starved. This is a correction to the previous commit's reasoning, not a new capability. That commit claimed the derive could not emit bin and left a test ignored against a hint that needed designing. The hint has existed all along: cultcache-rs-derive parses `bytes` at lib.rs:92, serializes through serde_bytes::Bytes at :135, and decodes through ByteBuf at :154 and :170. What is inert on a DatabaseEntry struct is `#[serde(with = "serde_bytes")]`, because the derive reads only its own attribute namespace — which is a different and much smaller claim than the one that was made. The ignored test is now un-ignored and passing, and the tolerant assertion it was propping up is exact again. Four fields elsewhere are missing `bytes` and are deliberately untouched here: IdunnDeploymentBrakeRecord.signature, IdunnDaemonHealthTrustBindingRecord .signer_public_key, GameCultServiceTrustAnchorRecord.signer_public_key, and IdunnAuthenticatedProviderHealthProjectionRecord.signature. Those carry stored state and signing history — six trust anchors live in the deployment projection on yggdrasil, and the brake store is byte-exact checked — so they need sequencing with a re-sign rather than a flag flip. Owned by the Swarm Migration work. Co-Authored-By: Claude Opus 5 --- .../cultnet-rs/src/media_stream_contracts.rs | 12 +++++-- .../tests/media_stream_contracts.rs | 34 +++++++------------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/packages/cultnet-rs/src/media_stream_contracts.rs b/packages/cultnet-rs/src/media_stream_contracts.rs index e47b3a28..815e3392 100644 --- a/packages/cultnet-rs/src/media_stream_contracts.rs +++ b/packages/cultnet-rs/src/media_stream_contracts.rs @@ -18,6 +18,12 @@ //! wire format: append new fields with `default` at the next free index, never //! reorder or renumber. `missing_video_chunk_keys` shows the pattern. //! +//! Payloads carry `bytes` so they serialize as MessagePack `bin` rather than an +//! array of integers, which is what the C#, TypeScript and Python runtimes +//! expect and what keeps a 848-byte payload at 851 bytes instead of 1234. Note +//! that this is the derive's own attribute — `#[serde(with = "serde_bytes")]` +//! is inert here, because `DatabaseEntry` writes its own `Serialize`. +//! //! # Timebase //! //! `pts_ticks`, `duration_ticks` and `deadline_ticks` are expressed in @@ -76,7 +82,7 @@ pub struct GameCultMediaVideoAccessUnitRecord { pub chunk_index: u16, #[cultcache(key = 12)] pub chunk_count: u16, - #[cultcache(key = 13)] + #[cultcache(key = 13, bytes)] pub payload: Vec, } @@ -123,7 +129,7 @@ pub struct GameCultMediaVideoParityShardRecord { pub chunk_payload_bytes: u32, #[cultcache(key = 15)] pub last_chunk_payload_bytes: u32, - #[cultcache(key = 16)] + #[cultcache(key = 16, bytes)] pub payload: Vec, } @@ -153,7 +159,7 @@ pub struct GameCultMediaAudioPacketRecord { pub timebase_den: u32, #[cultcache(key = 8)] pub deadline_ticks: i64, - #[cultcache(key = 9)] + #[cultcache(key = 9, bytes)] pub payload: Vec, } diff --git a/packages/cultnet-rs/tests/media_stream_contracts.rs b/packages/cultnet-rs/tests/media_stream_contracts.rs index 9d2fc664..963d78a2 100644 --- a/packages/cultnet-rs/tests/media_stream_contracts.rs +++ b/packages/cultnet-rs/tests/media_stream_contracts.rs @@ -67,33 +67,23 @@ fn video_access_unit_encodes_as_a_positional_array() { assert_eq!(slots[8].as_bool(), Some(true), "slot 8 is keyframe"); assert!(slots[9].is_nil(), "an absent dependency stays nil, not 0"); assert_eq!(slots[12].as_u64(), Some(5), "slot 12 is chunk_count"); - assert!( - matches!(&slots[13], Value::Binary(_) | Value::Array(_)), - "slot 13 is the payload; see payload_should_encode_as_messagepack_bin \ - for which of the two it ought to be" + assert_eq!( + slots[13].as_slice(), + Some([0xDE, 0xAD, 0xBE, 0xEF].as_slice()), + "slot 13 is the payload, carried as bin" ); } -/// Ignored until `cultcache-rs-derive` grows a `bytes` hint. +/// Payloads must reach the wire as MessagePack `bin`, not as an array of +/// integers. The C#, TypeScript and Python runtimes all read byte fields as +/// `bin`, so a payload serialized as a sequence is unreadable by the reference +/// implementation, and costs 1.45x besides: 848 bytes encode to 1234 as an +/// integer array against 851 as `bin`. /// -/// The derive writes its own `Serialize` and calls `serialize_element` per slot, -/// so a `Vec` field goes out as a MessagePack array of integers. C#, -/// TypeScript and Python all emit `bin` for byte fields, which makes Rust the -/// divergent runtime and these records unreadable by the reference -/// implementation. `#[serde(with = "serde_bytes")]` cannot fix it — the derive -/// ignores serde attributes. -/// -/// It also costs 1.45x on every media byte: an 848-byte payload encodes to 1234 -/// bytes as an integer array against 851 as `bin`. -/// -/// The fix is not local to this file. Changing byte encoding globally would -/// invalidate every signature in the Idunn authority chain, because signatures -/// are taken over the whole positional serialization and canonical hashes over -/// the same bytes. It needs an opt-in hint so these three media records can -/// adopt `bin` without moving the signature-bearing ones. Owned by the Swarm -/// Migration work; un-ignore this when the hint lands. +/// This is what the derive's `bytes` attribute buys. `#[serde(with = +/// "serde_bytes")]` will not do it — `DatabaseEntry` emits its own `Serialize` +/// and reads only its own `cultcache` attribute namespace. #[test] -#[ignore = "blocked on a `bytes` opt-in hint in cultcache-rs-derive"] fn payload_should_encode_as_messagepack_bin() { let encoded = rmp_serde::to_vec(&access_unit()).expect("encodes"); let Value::Array(slots) = decode_as_value(&encoded) else { From 9e6027e8d8c0ff4caa1e93f9ee2c2de707949110 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 00:42:26 +0200 Subject: [PATCH 071/203] Measure what adding a bytes slot does to records already written Four of cultnet-rs's ten DatabaseEntry byte fields lack #[cultcache(key = N, bytes)] and so serialize as MessagePack array-of-integers rather than bin: IdunnDeploymentBrakeRecord.signature, IdunnDaemonHealthTrustBindingRecord.signer_public_key, GameCultServiceTrustAnchorRecord.signer_public_key, and IdunnAuthenticatedProviderHealthProjectionRecord.signature. Converting them touches live stores, so the question is what breaks. Measured rather than reasoned, and the answer is the opposite of what I expected. Reading is compatible in both directions: serde_bytes::ByteBuf accepts an array marker, and a plain Vec accepts bin, so the flip is neither a hard read break nor a one-way door. No dual-read path is needed and reverting the binary is survivable. What does change is reproduction. A record written before the flip can be read but can never re-encode to the bytes it was stored as, and its canonical digest moves with it. That is the cost, and it lands on byte-exact store checks and canonical_sha256, not on readability -- so the migration is a re-encode question, not a compatibility one. Also pins the size difference the media work is chasing: 406 bytes as an array against 278 as bin for a 256-byte payload, 1.46x, independently matching the 1.45x measured on an 848-byte payload. cargo test --test byte_slot_migration -> test result: ok. 4 passed; 0 failed Co-Authored-By: Claude Opus 5 --- .../cultnet-rs/tests/byte_slot_migration.rs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 packages/cultnet-rs/tests/byte_slot_migration.rs diff --git a/packages/cultnet-rs/tests/byte_slot_migration.rs b/packages/cultnet-rs/tests/byte_slot_migration.rs new file mode 100644 index 00000000..0acdcd4d --- /dev/null +++ b/packages/cultnet-rs/tests/byte_slot_migration.rs @@ -0,0 +1,113 @@ +//! Does adding `#[cultcache(key = N, bytes)]` to a field break reading the +//! records already written without it? +//! +//! The attribute changes a slot from a MessagePack array-of-integers (serde's +//! default for `Vec`) to a real `bin`. Six of cultnet-rs's byte fields +//! already carry it and four do not, so converting the remaining four is a +//! stored-data question before it is a signature question: every record +//! written before the flip holds an array in that slot. +//! +//! Both directions are tested, because a migration has to survive a rollback +//! as well as a roll-forward. + +use cultcache_rs::DatabaseEntry; +use serde::Serialize; + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "test.byte_slot", schema = "test.byte_slot.v1")] +struct ArrayEncoded { + #[cultcache(key = 0)] + schema_version: String, + #[cultcache(key = 1)] + payload: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "test.byte_slot", schema = "test.byte_slot.v1")] +struct BinEncoded { + #[cultcache(key = 0)] + schema_version: String, + #[cultcache(key = 1, bytes)] + payload: Vec, +} + +fn sample() -> Vec { + (0u8..=255).collect() +} + +#[test] +fn the_attribute_is_what_switches_the_wire_shape() { + let array = rmp_serde::to_vec(&ArrayEncoded { + schema_version: "test.byte_slot.v1".into(), + payload: sample(), + }) + .expect("array encodes"); + let bin = rmp_serde::to_vec(&BinEncoded { + schema_version: "test.byte_slot.v1".into(), + payload: sample(), + }) + .expect("bin encodes"); + + // 256 bytes as `bin` is a 3-byte header plus the payload. As an array it + // is one or two bytes per element, because every value above 0x7f needs a + // u8 marker of its own. + println!("array-encoded {} bytes, bin-encoded {} bytes", array.len(), bin.len()); + assert!( + array.len() > bin.len(), + "array {} should exceed bin {}", + array.len(), + bin.len() + ); +} + +#[test] +fn a_bytes_reader_still_reads_records_written_before_the_attribute() { + let written_before = rmp_serde::to_vec(&ArrayEncoded { + schema_version: "test.byte_slot.v1".into(), + payload: sample(), + }) + .expect("array encodes"); + + // serde_bytes::ByteBuf accepts an array marker as well as bin, so the + // flip is NOT a hard read break. This was worth measuring rather than + // assuming: the opposite would have forced a dual-read path or a rewrite + // migration onto every field with persisted records. + let read_after = + rmp_serde::from_slice::(&written_before).expect("bytes reader reads an array"); + assert_eq!(read_after.payload, sample()); +} + +#[test] +fn a_pre_attribute_reader_still_reads_records_written_after_it() { + let written_after = rmp_serde::to_vec(&BinEncoded { + schema_version: "test.byte_slot.v1".into(), + payload: sample(), + }) + .expect("bin encodes"); + + // The rollback direction, so reverting the binary is survivable. + let read_by_old = + rmp_serde::from_slice::(&written_after).expect("plain reader reads bin"); + assert_eq!(read_by_old.payload, sample()); +} + +#[test] +fn re_encoding_an_old_record_under_the_attribute_does_not_reproduce_its_bytes() { + // Where the cost actually lands. Reading is compatible; reproducing is + // not. Idunn byte-exact-checks its control store by re-encoding what it + // decoded, and canonical_sha256 hashes the same serialization -- so a + // record written before the flip can be read, but can never re-encode to + // the bytes it was stored as, and its canonical digest changes. + let written_before = rmp_serde::to_vec(&ArrayEncoded { + schema_version: "test.byte_slot.v1".into(), + payload: sample(), + }) + .expect("array encodes"); + let decoded = rmp_serde::from_slice::(&written_before).expect("reads"); + let re_encoded = rmp_serde::to_vec(&decoded).expect("re-encodes"); + + assert_ne!( + written_before, re_encoded, + "identical bytes would mean the attribute changed nothing" + ); +} From 36f31920ce69a80ad4c340a7bdfb23d63a9273e9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 00:49:32 +0200 Subject: [PATCH 072/203] Let a caller choose what the media channel does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `media` was hardcoded Reliable in two places — the profile builder and the per-channel send options — so no caller could ask for anything else. Measured on Raven through `cultnet-impair`, 848-byte packets at 12 Mbps, which is Muninn's real media shape: profile mode delivered ack bytes clean unreliable 100.00% 43 clean reliable 69.10% 131,322 loss 1% unreliable 98.95% 43 loss 1% reliable 72.00% 135,837 loss 3% unreliable 96.73% 43 loss 3% reliable 73.30% 138,030 A reliable media channel loses 31% of a clean 12 Mbps offer, and its delivery barely moves as loss rises, so loss is not the binding constraint — it cannot carry the rate. An unreliable one holds everything on a clean link and then degrades by about the loss injected. The reverse channel differs by three orders of magnitude. Muninn wanted the unreliable lane and had no way to say so. It signalled the intent the only way the API allowed, by passing no reliable expiry, and its own test has asserted Unreliable and failed since it was written. With this change it passes. Delivery now comes from the advertised profile, resolved by `channel_delivery` alongside the `channel_reliable_expire_after_ms` that already worked this way. That removes the send path's private opinion about reliability, so the profile a peer was shown and the behaviour it gets cannot disagree. The channels whose semantics are fixed by design — schema reliable and ordered, realtime unreliable — are untouched and pinned by a test. `media_delivery: None` means Reliable, so no existing consumer changes behaviour. It is still source-breaking for anyone constructing the three options types with a struct literal rather than `::client(...)`: two sites in cultmesh-rs, four in Muninn, seven across the test suites here, all mechanical. An earlier draft claimed Muninn was unaffected; the compiler disagreed. The `examples/media_delivery_probe.rs` added here is what produced the table, so the claim can be re-run rather than believed. Note its sender-side wire-cost figure is unsound for the reliable case: the sender stops draining after five seconds while retransmission is still in flight, so those byte counts under-report. The delivery percentages are receiver-measured and stand. Co-Authored-By: Claude Opus 5 --- packages/cultmesh-rs/src/lib.rs | 1 + .../cultmesh-rs/tests/rudp_document_server.rs | 1 + .../examples/media_delivery_probe.rs | 177 ++++++++++++++++++ packages/cultnet-rs/src/cultmesh.rs | 2 + packages/cultnet-rs/src/rudp.rs | 43 ++++- packages/cultnet-rs/tests/cultnet.rs | 7 + .../tests/media_channel_delivery.rs | 92 +++++++++ 7 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 packages/cultnet-rs/examples/media_delivery_probe.rs create mode 100644 packages/cultnet-rs/tests/media_channel_delivery.rs diff --git a/packages/cultmesh-rs/src/lib.rs b/packages/cultmesh-rs/src/lib.rs index 7999f18b..135314bb 100644 --- a/packages/cultmesh-rs/src/lib.rs +++ b/packages/cultmesh-rs/src/lib.rs @@ -955,6 +955,7 @@ fn connect_rudp_client( socket.set_read_timeout(Some(poll_interval))?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: runtime_id.to_string(), socket, mode: cultnet_rs::CultNetRudpSocketMode::Client, diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index c0f191b4..df14b4ea 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -163,6 +163,7 @@ fn client(target: SocketAddr, id: u32) -> Result +//! media_delivery_probe send +//! ``` +//! +//! The sender reports amplification: wire bytes divided by offered payload +//! bytes. At 1.0 nothing was retransmitted. Well above 1.0 under loss is the +//! pathology. + +use std::env; +use std::net::{SocketAddr, UdpSocket}; +use std::time::{Duration, Instant}; + +use anyhow::{Result, anyhow}; +use cultnet_rs::{ + CultNetRudpServerHubOptions, CultNetRudpSocketTransportConnection, + CultNetRudpSocketTransportOptions, CultNetTransportDelivery, +}; + +const CHANNEL: &str = "media"; +const DRAIN: Duration = Duration::from_secs(5); + +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + match args.get(1).map(String::as_str) { + Some("recv") => recv( + args.get(2).ok_or_else(|| anyhow!("bind addr"))?.parse()?, + args.get(3).ok_or_else(|| anyhow!("expect count"))?.parse()?, + ), + Some("send") => send( + args.get(2).ok_or_else(|| anyhow!("endpoint"))?.parse()?, + match args.get(3).map(String::as_str) { + Some("unreliable") => CultNetTransportDelivery::Unreliable, + Some("reliable") => CultNetTransportDelivery::Reliable, + other => return Err(anyhow!("delivery must be reliable|unreliable, got {other:?}")), + }, + args.get(4).ok_or_else(|| anyhow!("payload bytes"))?.parse()?, + args.get(5).ok_or_else(|| anyhow!("count"))?.parse()?, + args.get(6).map(|v| v.parse()).transpose()?.unwrap_or(12.0), + ), + _ => Err(anyhow!( + "usage: media_delivery_probe recv | send " + )), + } +} + +fn recv(bind: SocketAddr, expect: u64) -> Result<()> { + let socket = UdpSocket::bind(bind)?; + socket.set_nonblocking(true)?; + let mut hub = cultnet_rs::CultNetRudpServerHub::new(CultNetRudpServerHubOptions::new( + "probe-recv", + socket, + 0x0BE0_0001, + ))?; + + println!("listening on {bind}, expecting {expect} frames"); + let started = Instant::now(); + let mut frames = 0u64; + let mut payload_bytes = 0u64; + let mut last_progress = Instant::now(); + + loop { + while let Some(event) = hub.receive_event_once()? { + if let cultnet_rs::CultNetRudpServerEvent::Frame { frame, .. } = event + && frame.channel_id == CHANNEL + { + frames += 1; + payload_bytes += frame.payload.len() as u64; + last_progress = Instant::now(); + } + } + if frames >= expect || last_progress.elapsed() > DRAIN { + break; + } + std::thread::sleep(Duration::from_micros(200)); + } + + let stats = hub.stats(); + let elapsed = started.elapsed().as_secs_f64(); + println!("RESULT recv frames={frames} expected={expect} payload_bytes={payload_bytes}"); + println!( + "RESULT recv wire_bytes_in={} wire_bytes_out={} elapsed_s={elapsed:.2} delivered_pct={:.2}", + stats.bytes_received, + stats.bytes_sent, + (frames as f64 / expect as f64) * 100.0 + ); + Ok(()) +} + +fn send( + endpoint: SocketAddr, + delivery: CultNetTransportDelivery, + payload_bytes: usize, + count: u64, + target_mbps: f64, +) -> Result<()> { + let socket = UdpSocket::bind("0.0.0.0:0")?; + socket.set_nonblocking(true)?; + let mut options = + CultNetRudpSocketTransportOptions::client("probe-send", socket, endpoint, 0x0BE0_0001); + options.media_delivery = Some(delivery); + options.media_reliable_expire_after_ms = None; + + let mut transport = CultNetRudpSocketTransportConnection::new(options)?; + transport.connect(b"probe".to_vec())?; + let deadline = Instant::now() + Duration::from_secs(10); + while !transport.connected() { + let _ = transport.receive_once()?; + transport.poll_resends()?; + if Instant::now() >= deadline { + return Err(anyhow!("timed out connecting to {endpoint}")); + } + std::thread::sleep(Duration::from_millis(2)); + } + + // Pace to a realistic media rate. An unpaced blast overruns the receiver's + // socket buffer and manufactures loss that has nothing to do with the link. + let per_packet = Duration::from_secs_f64((payload_bytes as f64 * 8.0) / (target_mbps * 1e6)); + let payload = vec![0xA5u8; payload_bytes]; + let offered = payload_bytes as u64 * count; + let started = Instant::now(); + let mut sent = 0u64; + let mut would_block = 0u64; + + while sent < count { + match transport.send(CHANNEL, payload.clone()) { + Ok(()) => sent += 1, + Err(error) if error.to_string().contains("would block") => { + would_block += 1; + transport.poll_resends()?; + let _ = transport.receive_once()?; + std::thread::sleep(Duration::from_micros(500)); + } + Err(error) => return Err(error), + } + if sent % 64 == 0 { + let _ = transport.receive_once()?; + transport.poll_resends()?; + } + let due = started + per_packet.mul_f64(sent as f64); + if let Some(wait) = due.checked_duration_since(Instant::now()) { + std::thread::sleep(wait); + } + } + let send_elapsed = started.elapsed(); + + // Let retransmission run: this is where a reliable channel spends what an + // unreliable one does not. + let drain_start = Instant::now(); + while drain_start.elapsed() < DRAIN { + let _ = transport.receive_once()?; + transport.poll_resends()?; + std::thread::sleep(Duration::from_millis(1)); + } + + let stats = transport.stats(); + let amplification = stats.bytes_sent as f64 / offered as f64; + println!( + "RESULT send delivery={delivery:?} payload_bytes={payload_bytes} count={count} offered={offered} target_mbps={target_mbps}" + ); + println!( + "RESULT send wire_bytes={} amplification={amplification:.3} send_s={:.2} total_s={:.2} would_block={would_block} reliable_expired={}", + stats.bytes_sent, + send_elapsed.as_secs_f64(), + started.elapsed().as_secs_f64(), + stats.reliable_packets_expired + ); + Ok(()) +} diff --git a/packages/cultnet-rs/src/cultmesh.rs b/packages/cultnet-rs/src/cultmesh.rs index efc8acad..14998396 100644 --- a/packages/cultnet-rs/src/cultmesh.rs +++ b/packages/cultnet-rs/src/cultmesh.rs @@ -319,6 +319,7 @@ impl CultMesh { let socket = bind_rudp_socket(&options)?; CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { media_reliable_expire_after_ms: None, + media_delivery: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Server, @@ -343,6 +344,7 @@ impl CultMesh { let socket = bind_rudp_socket(&options)?; CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { media_reliable_expire_after_ms: None, + media_delivery: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Client, diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index f1fd4ca4..b7019b1b 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -1210,6 +1210,8 @@ pub struct CultNetRudpSocketTransportOptions { pub max_pending_reliable_packets: Option, pub reconnect_policy: Option, pub media_reliable_expire_after_ms: Option, + /// `None` means the historical default, `Reliable`. + pub media_delivery: Option, } impl CultNetRudpSocketTransportOptions { @@ -1221,6 +1223,7 @@ impl CultNetRudpSocketTransportOptions { ) -> Self { Self { media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), + media_delivery: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Client, @@ -1239,6 +1242,7 @@ impl CultNetRudpSocketTransportOptions { pub fn server(runtime_id: impl Into, socket: UdpSocket, connection_id: u32) -> Self { Self { media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), + media_delivery: None, runtime_id: runtime_id.into(), socket, mode: CultNetRudpSocketMode::Server, @@ -1280,12 +1284,15 @@ pub struct CultNetRudpServerHubOptions { pub max_pending_reliable_packets: Option, pub max_peers: usize, pub media_reliable_expire_after_ms: Option, + /// `None` means the historical default, `Reliable`. + pub media_delivery: Option, } impl CultNetRudpServerHubOptions { pub fn new(runtime_id: impl Into, socket: UdpSocket, connection_id: u32) -> Self { Self { media_reliable_expire_after_ms: Some(DEFAULT_MEDIA_RELIABLE_EXPIRE_AFTER_MS), + media_delivery: None, runtime_id: runtime_id.into(), socket, connection_id, @@ -1372,6 +1379,7 @@ impl CultNetRudpServerHub { options.runtime_id, RudpTransportProfileOptions { media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, + media_delivery: options.media_delivery, transport_id: options.transport_id, host: Some(local_addr.ip().to_string()), port: Some(local_addr.port()), @@ -1451,6 +1459,7 @@ impl CultNetRudpServerHub { channel_id, now_ms(), channel_reliable_expire_after_ms(&self.profile, channel_id), + channel_delivery(&self.profile, channel_id), ), self.max_fragment_bytes, )? @@ -1671,6 +1680,7 @@ impl CultNetRudpSocketTransportConnection { options.runtime_id, RudpTransportProfileOptions { media_reliable_expire_after_ms: options.media_reliable_expire_after_ms, + media_delivery: options.media_delivery, transport_id: options.transport_id, host: Some(local_addr.ip().to_string()), port: Some(local_addr.port()), @@ -1738,6 +1748,7 @@ impl CultNetRudpSocketTransportConnection { channel_id, now_ms(), channel_reliable_expire_after_ms(&self.profile, channel_id), + channel_delivery(&self.profile, channel_id), ); let packets = self.session @@ -1759,6 +1770,7 @@ impl CultNetRudpSocketTransportConnection { channel_id, now_ms(), channel_reliable_expire_after_ms(&self.profile, channel_id), + channel_delivery(&self.profile, channel_id), ); // This lineage has no reliable-expiry concept, so every reliable channel is // already non-expiring. Restore the expiry half of this guard if one is added. @@ -2073,6 +2085,8 @@ pub struct RudpTransportProfileOptions { pub max_pending_reliable_packets: Option, pub reconnect_policy: Option, pub media_reliable_expire_after_ms: Option, + /// `None` means the historical default, `Reliable`. + pub media_delivery: Option, } pub fn create_rudp_transport_profile( @@ -2125,7 +2139,9 @@ pub fn create_rudp_transport_profile( }, CultNetTransportChannel { channel_id: "media".to_string(), - delivery: CultNetTransportDelivery::Reliable, + delivery: options + .media_delivery + .unwrap_or(CultNetTransportDelivery::Reliable), ordering: CultNetTransportOrdering::Unordered, max_payload_bytes: options.max_payload_bytes, max_fragment_bytes: options.max_fragment_bytes, @@ -2254,10 +2270,30 @@ fn channel_reliable_expire_after_ms( .and_then(|channel| channel.reliable_expire_after_ms) } +/// The advertised profile owns each channel's delivery, for the same reason it +/// owns expiry: a send path that hardcodes reliability can disagree with the +/// profile the peer was shown, and then no one can say what the channel does. +/// +/// Falls back to `Reliable` for a channel the profile does not describe, which +/// is what every channel got before delivery was configurable. +fn channel_delivery( + profile: &CultNetTransportProfile, + channel_id: &str, +) -> CultNetTransportDelivery { + profile + .transports + .iter() + .flat_map(|transport| transport.channels.iter()) + .find(|channel| channel.channel_id == channel_id) + .map(|channel| channel.delivery) + .unwrap_or(CultNetTransportDelivery::Reliable) +} + fn channel_send_options( channel_id: &str, now_ms: u64, reliable_expire_after_ms: Option, + delivery: CultNetTransportDelivery, ) -> CultNetRudpSendOptions { match channel_id { "schema" => CultNetRudpSendOptions { @@ -2274,8 +2310,11 @@ fn channel_send_options( now_ms, reliable_expire_after_ms: None, }, + // The one channel whose delivery a caller chooses. Media that has gone + // stale is worth dropping, not retransmitting: a reliable media channel + // under loss adds load exactly when the link has least to give. "media" => CultNetRudpSendOptions { - reliable: true, + reliable: matches!(delivery, CultNetTransportDelivery::Reliable), ordered: false, sequenced: false, now_ms, diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index 851646cf..7be6ea18 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -359,6 +359,7 @@ fn rudp_transport_profile_advertises_state_and_realtime_channels() { let profile = create_rudp_transport_profile( "rust-rudp", cultnet_rs::RudpTransportProfileOptions { + media_delivery: None, transport_id: Some("public-rudp".to_string()), host: Some("127.0.0.1".to_string()), port: Some(7777), @@ -1248,6 +1249,7 @@ fn rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames() let connection_id = 0x1020_3040; let mut server = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-server".to_string(), socket: server_socket, mode: CultNetRudpSocketMode::Server, @@ -1264,6 +1266,7 @@ fn rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames() })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-client".to_string(), socket: client_socket, mode: CultNetRudpSocketMode::Client, @@ -1340,6 +1343,7 @@ fn rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames() -> let connection_id = 0x1020_3041; let mut server = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-fragment-server".to_string(), socket: server_socket, mode: CultNetRudpSocketMode::Server, @@ -1356,6 +1360,7 @@ fn rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames() -> })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-fragment-client".to_string(), socket: client_socket, mode: CultNetRudpSocketMode::Client, @@ -1470,6 +1475,7 @@ fn rudp_socket_flush_waits_for_large_fragment_delivery() -> Result<()> { let connection_id = 0x1020_3043; let mut server = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-window-server".into(), socket: server_socket, mode: CultNetRudpSocketMode::Server, @@ -1486,6 +1492,7 @@ fn rudp_socket_flush_waits_for_large_fragment_delivery() -> Result<()> { })?; let mut client = CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions { + media_delivery: None, runtime_id: "rust-rudp-window-client".into(), socket: client_socket, mode: CultNetRudpSocketMode::Client, diff --git a/packages/cultnet-rs/tests/media_channel_delivery.rs b/packages/cultnet-rs/tests/media_channel_delivery.rs new file mode 100644 index 00000000..499be0d3 --- /dev/null +++ b/packages/cultnet-rs/tests/media_channel_delivery.rs @@ -0,0 +1,92 @@ +//! Media delivery is the caller's choice, and the advertised profile is where +//! that choice lives. +//! +//! Before this, `media` was hardcoded `Reliable` in two places — the profile +//! builder and the per-channel send options — so no caller could say otherwise. +//! A producer asking for an unreliable media lane got a reliable one that +//! retransmits under loss, which adds load exactly when the link has least to +//! give. Muninn asked for that lane by passing no expiry, meaning "unreliable, +//! so expiry is moot", and got reliable-with-no-expiry instead: retransmit +//! forever. +//! +//! The default stays `Reliable` so every existing consumer is untouched. + +use std::net::UdpSocket; + +use cultnet_rs::{ + CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, + CultNetTransportDelivery, +}; + +fn media_channel_delivery( + media_delivery: Option, +) -> (CultNetTransportDelivery, Option) { + let socket = UdpSocket::bind("127.0.0.1:0").expect("binds"); + let endpoint = "127.0.0.1:5204".parse().expect("parses"); + let mut options = CultNetRudpSocketTransportOptions::client("test-media", socket, endpoint, 7); + options.media_delivery = media_delivery; + options.media_reliable_expire_after_ms = None; + + let transport = CultNetRudpSocketTransportConnection::new(options).expect("opens"); + let channel = transport + .profile + .transports + .first() + .expect("a transport") + .channels + .iter() + .find(|channel| channel.channel_id == "media") + .expect("a media channel"); + (channel.delivery, channel.reliable_expire_after_ms) +} + +#[test] +fn media_defaults_to_reliable_so_existing_consumers_do_not_move() { + let (delivery, _) = media_channel_delivery(None); + assert_eq!(delivery, CultNetTransportDelivery::Reliable); +} + +#[test] +fn a_caller_can_ask_for_an_unreliable_media_lane() { + let (delivery, expiry) = media_channel_delivery(Some(CultNetTransportDelivery::Unreliable)); + assert_eq!(delivery, CultNetTransportDelivery::Unreliable); + assert_eq!( + expiry, None, + "an unreliable channel has nothing to expire; the pairing should stay honest" + ); +} + +#[test] +fn asking_for_reliable_explicitly_matches_the_default() { + let (explicit, _) = media_channel_delivery(Some(CultNetTransportDelivery::Reliable)); + let (defaulted, _) = media_channel_delivery(None); + assert_eq!(explicit, defaulted); +} + +/// The channels whose semantics are fixed by design must not drift with this. +#[test] +fn the_other_channels_keep_their_fixed_delivery() { + let socket = UdpSocket::bind("127.0.0.1:0").expect("binds"); + let endpoint = "127.0.0.1:5204".parse().expect("parses"); + let mut options = CultNetRudpSocketTransportOptions::client("test-media", socket, endpoint, 8); + options.media_delivery = Some(CultNetTransportDelivery::Unreliable); + + let transport = CultNetRudpSocketTransportConnection::new(options).expect("opens"); + let channels = &transport + .profile + .transports + .first() + .expect("a transport") + .channels; + + let delivery_of = |id: &str| { + channels + .iter() + .find(|channel| channel.channel_id == id) + .unwrap_or_else(|| panic!("channel {id}")) + .delivery + }; + + assert_eq!(delivery_of("schema"), CultNetTransportDelivery::Reliable); + assert_eq!(delivery_of("realtime"), CultNetTransportDelivery::Unreliable); +} From 4111fbb46849e10640d5c32f01afcb1cd1ba78b6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 01:18:45 +0200 Subject: [PATCH 073/203] Write down that a stored record's encoding is part of its contract Three byte-exact re-encode guards exist across three separate Idunn stores -- the runtime projection, the deployment brake, and the trust bindings -- each decoding a record, re-encoding it, and refusing on any byte difference. Idunn's control store does the same for its own transactions. Three independent instances is a house pattern, and it means changing how any persisted CultCache record serializes is never a flag flip. Records the measured behaviour of #[cultcache(key = N, bytes)] rather than the assumed one: reading stays compatible both ways, so the cost lands on reproduction and therefore on those guards, not on compatibility. A field with no live producer is free; a field with persisted records needs its store rewritten in the same change. Also records that Option> cannot take the attribute today, and that a shape-validated field nobody produces may be a fossil rather than an omission -- the estate has at least one other case of a live consumer outliving its producer. This was learned by nearly deploying an attribute flip that would have made Idunn refuse the projection store Odin reads. Co-Authored-By: Claude Opus 5 --- docs/persisted-record-encoding.md | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/persisted-record-encoding.md diff --git a/docs/persisted-record-encoding.md b/docs/persisted-record-encoding.md new file mode 100644 index 00000000..b8e13f8a --- /dev/null +++ b/docs/persisted-record-encoding.md @@ -0,0 +1,68 @@ +# The encoding of a persisted CultCache record is part of its contract + +Changing how a stored record serializes is never a flag flip in this estate. +Consumers verify canonicality by **re-encoding what they decoded and comparing +bytes**, so a record that reads back correctly but re-encodes differently is +refused — and the refusal happens on live data, at startup or on the next read. + +Three guards, three separate stores, one idiom: + +| store | guard | refusal | +|---|---|---| +| Idunn runtime projection | `Idunn/src/drivers.rs:3611` | `service trust-anchor projection is noncanonical` | +| Idunn deployment brake | `Idunn/src/control_plane.rs` (`read_deployment_brake`) | `deployment brake is noncanonical or keyed by another authority` | +| Idunn trust bindings | `Idunn/src/provisioning.rs:546`, `:630` | `trust store contains a noncanonical or mismatched binding` | + +Idunn's control store applies the same rule to its own transactions. Assume the +guard exists and go looking for it before changing any persisted record's +serialization; three independent instances is a house pattern, not a +coincidence. + +## What this means for `#[cultcache(key = N, bytes)]` + +The attribute switches a `Vec` slot from a MessagePack array-of-integers +(serde's default) to a real `bin`. Measured, not assumed +(`packages/cultnet-rs/tests/byte_slot_migration.rs`): + +- **Reading stays compatible in both directions.** `serde_bytes::ByteBuf` + accepts an array marker, and a plain `Vec` accepts `bin`. No dual-read + path is needed, and reverting a binary is survivable. +- **Reproduction does not.** A record written before the flip can be read but + can never re-encode to the bytes it was stored as, and its `canonical_sha256` + moves with it. + +So the cost lands entirely on the guards above, not on readability. A field with +persisted records must flip the attribute **and** rewrite its store in the same +change, one target at a time, and never during a deployment — the projection +store is what the observer reads, and refusing it takes the observer down. + +A field with no live producer is free. Check for one before assuming a +migration: `IdunnAuthenticatedProviderHealthProjectionRecord` is constructed +only inside a test module, so its byte field costs nothing to convert. + +Size, for whether it is worth doing at all: 406 bytes as an array against 278 as +`bin` for a 256-byte payload, and 1234 against 851 for an 848-byte one. About +1.45x on every byte, which matters on a media path and is noise on a control +record. + +## `Option>` cannot take the attribute yet + +The derive emits `serde_bytes::Bytes::new(&self.field)`, which needs `&[u8]`, +and builds the field via `.into_vec()`, which yields `Vec`. Both are type +errors against an `Option>`, so the derive fails loudly rather than +collapsing "absent slot" into "unsigned". `IdunnDeploymentBrakeRecord.signature` +needs an `Option`-aware path in `cultcache-rs-derive` before it can move. + +## Fields validated for shape and produced by nothing + +`trust_binding_sha256` is declared on two records with different schemas +(`idunn.authenticated_provider_health_projection.v1` in cultnet-rs, +`idunn.authenticated_daemon_health_admission.v1` in odin-core), shape-validated +in both, and computed by nothing in CultLib, Idunn or Odin. + +Do not assume that means "omission". It may be a fossil — a contract whose +producer was deleted. The estate has form: Muninn's receiver runs a Reed-Solomon +audio FEC path against parity shards the Rust sender has never emitted, while an +acceptance ledger describes that FEC as having worked. Settling which requires +reading history, not source, and the answer changes whether the right move is to +implement the field or delete it. From a3e6877945f22134af99999a60f85cf7acc16056 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 02:00:19 +0200 Subject: [PATCH 074/203] Move the media wire envelope where both ends can reach it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A media record travels wrapped in cultnet.document_put_raw.v0 with a schema id, a record key and provenance. Both ends have to agree on that exactly, and until now only Muninn had it: the producer was Rust in one repository and the consumer was 6,515 lines of C++ in another, each with its own reading of the same bytes. Ratatoskr could not decode a real stream without it. Two things change in the move rather than after it. The producer namespace is now the caller's. The Muninn implementation hardcoded "muninn-media:" into every message id and "muninn.media" into every tag, so a second producer would have had to label its traffic as Muninn's to be understood. `MediaWireProvenance` carries it instead, and a test asserts the envelope names no producer the caller did not supply. The four hand-written wire payload structs are gone. Each restated a record's entire field list as a positional tuple, in order, kept in step by discipline alone — and existed for one reason: `#[serde(with = "serde_bytes")]` on the payload, which cannot be applied to a DatabaseEntry struct because the derive does not read serde attributes. Now that the records carry `#[cultcache(key = N, bytes)]` the derive emits bin itself, so the records serialize directly. That last part is a wire claim, so it is tested rather than asserted: `the_derive_reproduces_the_hand_written_wire_payload_byte_for_byte` reconstructs the legacy struct and compares output byte for byte. It matches, which makes the deletion a simplification instead of a silent break. Record key shapes move here too. A consumer parses them to tell a chunk from its siblings and a retransmission from a new frame, so they are contract and belong beside the records rather than in whichever producer wrote them first. Muninn's copies are not removed yet; that is the other half of this move. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/lib.rs | 2 + packages/cultnet-rs/src/media_stream_wire.rs | 185 +++++++++++++++ .../cultnet-rs/tests/media_stream_wire.rs | 217 ++++++++++++++++++ 3 files changed, 404 insertions(+) create mode 100644 packages/cultnet-rs/src/media_stream_wire.rs create mode 100644 packages/cultnet-rs/tests/media_stream_wire.rs diff --git a/packages/cultnet-rs/src/lib.rs b/packages/cultnet-rs/src/lib.rs index 5aa121af..074c2a29 100644 --- a/packages/cultnet-rs/src/lib.rs +++ b/packages/cultnet-rs/src/lib.rs @@ -2,6 +2,7 @@ mod contracts; mod idunn_deployment_brake; mod idunn_lifecycle_brake; mod media_stream_contracts; +mod media_stream_wire; mod provider_health_contracts; mod runtime_authority_contracts; mod service_identity; @@ -21,6 +22,7 @@ pub use contracts::*; pub use idunn_deployment_brake::*; pub use idunn_lifecycle_brake::*; pub use media_stream_contracts::*; +pub use media_stream_wire::*; pub use provider_health_contracts::*; pub use runtime_authority_contracts::*; pub use service_identity::*; diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs new file mode 100644 index 00000000..2959eef8 --- /dev/null +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -0,0 +1,185 @@ +//! How a CultMesh Media Stream record travels: the envelope both ends must +//! agree on. +//! +//! The records in `media_stream_contracts` describe media. This describes how +//! one reaches a peer — wrapped in `cultnet.document_put_raw.v0` with a schema +//! id, a record key, and provenance. It lives here because a producer and a +//! consumer that never link each other still have to agree on it exactly, and +//! the last time they did not, one end was Rust and the other was 6,515 lines +//! of C++ that had drifted. +//! +//! # No producer's name belongs in a shared envelope +//! +//! The Muninn implementation this replaces hardcoded `"muninn-media:"` into +//! every message id and `"muninn.media"` into every tag, so a second producer +//! would have had to label its traffic as Muninn's. The producer namespace is a +//! parameter here: see [`MediaWireProvenance`]. +//! +//! # Record keys are addresses, not decoration +//! +//! A key identifies one piece of media within a session, so a consumer can tell +//! a retransmission from a new frame and a chunk from its siblings. The shapes +//! are fixed by this module rather than by each producer, because a consumer +//! parses them. + +use anyhow::{Result, anyhow}; + +use crate::contracts::{ + CultNetMessage, CultNetRawDocumentRecord, CultNetRawPayloadEncoding, CultNetWireContract, + decode_cultnet_message_from_slice, encode_cultnet_message_to_vec, +}; +use crate::media_stream_contracts::{ + GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA, GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA, + GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA, GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA, + GameCultMediaAudioPacketRecord, GameCultMediaReceiverFeedbackRecord, + GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, +}; + +/// The CultNet channel media rides. +pub const GAMECULT_MEDIA_CHANNEL: &str = "media"; + +/// One media record on its way somewhere. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum GameCultMediaWireRecord { + Video(GameCultMediaVideoAccessUnitRecord), + VideoParity(GameCultMediaVideoParityShardRecord), + Audio(GameCultMediaAudioPacketRecord), + Feedback(GameCultMediaReceiverFeedbackRecord), +} + +/// Who is sending, and when. `producer` namespaces message ids and tags so a +/// stream can be attributed without the envelope knowing any producer by name. +#[derive(Clone, Copy, Debug)] +pub struct MediaWireProvenance<'a> { + pub stored_at: &'a str, + pub runtime_id: &'a str, + pub role: &'a str, + pub producer: &'a str, +} + +impl MediaWireProvenance<'_> { + fn validate(&self) -> Result<()> { + for (name, value) in [ + ("stored_at", self.stored_at), + ("runtime_id", self.runtime_id), + ("role", self.role), + ("producer", self.producer), + ] { + if value.is_empty() { + return Err(anyhow!("media wire provenance {name} must be non-empty")); + } + } + Ok(()) + } +} + +impl GameCultMediaWireRecord { + pub fn schema_id(&self) -> &'static str { + match self { + Self::Video(_) => GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA, + Self::VideoParity(_) => GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA, + Self::Audio(_) => GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA, + Self::Feedback(_) => GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA, + } + } + + /// Addresses this record within its session. + pub fn record_key(&self) -> String { + match self { + Self::Video(record) => format!( + "{}:{}:video:{}:{}", + record.stream_id, record.session_id, record.frame_id, record.chunk_index + ), + Self::VideoParity(record) => format!( + "{}:{}:video-parity:{}:{}", + record.stream_id, record.session_id, record.frame_id, record.parity_index + ), + Self::Audio(record) => format!( + "{}:{}:audio:{}", + record.stream_id, record.session_id, record.packet_id + ), + Self::Feedback(record) => format!( + "{}:{}:feedback:{}", + record.stream_id, record.session_id, record.receiver_id + ), + } + } + + /// The record's own MessagePack form. + /// + /// Serializing the record directly is only correct because the payload + /// fields carry `#[cultcache(key = N, bytes)]`. Muninn previously mirrored + /// each record into a parallel tuple struct — every field restated, in + /// order, by hand — solely so `#[serde(with = "serde_bytes")]` could be + /// applied to the payload, because the `DatabaseEntry` derive does not read + /// serde attributes. Those shadow structs are gone; the derive emits `bin` + /// itself now. Do not reintroduce one. + fn payload_bytes(&self) -> Result> { + match self { + Self::Video(record) => rmp_serde::to_vec(record), + Self::VideoParity(record) => rmp_serde::to_vec(record), + Self::Audio(record) => rmp_serde::to_vec(record), + Self::Feedback(record) => rmp_serde::to_vec(record), + } + .map_err(Into::into) + } +} + +/// Wraps a media record for the wire. +pub fn encode_media_wire_record( + record: &GameCultMediaWireRecord, + provenance: MediaWireProvenance<'_>, +) -> Result> { + provenance.validate()?; + + let schema_id = record.schema_id(); + let record_key = record.record_key(); + let message = CultNetMessage::DocumentPutRaw { + message_id: format!( + "{}-media:{}:{}", + provenance.producer, + schema_id, + record_key.replace(':', "-") + ), + document: CultNetRawDocumentRecord { + schema_id: schema_id.to_string(), + record_key, + stored_at: provenance.stored_at.to_string(), + payload_encoding: CultNetRawPayloadEncoding::Messagepack, + payload: record.payload_bytes()?, + source_runtime_id: Some(provenance.runtime_id.to_string()), + source_agent_id: None, + source_role: Some(provenance.role.to_string()), + tags: Some(vec![format!("{}.media", provenance.producer)]), + }, + }; + + encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0).map_err(Into::into) +} + +/// Unwraps a media record from the wire. +pub fn decode_media_wire_record(payload: &[u8]) -> Result { + let message = decode_cultnet_message_from_slice(payload, CultNetWireContract::CultNetSchemaV0)?; + let CultNetMessage::DocumentPutRaw { document, .. } = message else { + return Err(anyhow!("expected cultnet.document_put_raw.v0")); + }; + if document.payload_encoding != CultNetRawPayloadEncoding::Messagepack { + return Err(anyhow!("media document payload must be MessagePack")); + } + + match document.schema_id.as_str() { + GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA => Ok(GameCultMediaWireRecord::Video( + rmp_serde::from_slice(&document.payload)?, + )), + GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA => Ok(GameCultMediaWireRecord::VideoParity( + rmp_serde::from_slice(&document.payload)?, + )), + GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA => Ok(GameCultMediaWireRecord::Audio( + rmp_serde::from_slice(&document.payload)?, + )), + GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA => Ok(GameCultMediaWireRecord::Feedback( + rmp_serde::from_slice(&document.payload)?, + )), + other => Err(anyhow!("unknown media schema {other}")), + } +} diff --git a/packages/cultnet-rs/tests/media_stream_wire.rs b/packages/cultnet-rs/tests/media_stream_wire.rs new file mode 100644 index 00000000..cee31a04 --- /dev/null +++ b/packages/cultnet-rs/tests/media_stream_wire.rs @@ -0,0 +1,217 @@ +//! The envelope, and one proof that removing Muninn's shadow structs changed +//! nothing on the wire. + +use cultnet_rs::{ + GAMECULT_MEDIA_CHANNEL, GameCultMediaAudioPacketRecord, GameCultMediaReceiverFeedbackRecord, + GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, + GameCultMediaWireRecord, MediaWireProvenance, decode_media_wire_record, + encode_media_wire_record, +}; +use serde::Serialize; + +fn provenance() -> MediaWireProvenance<'static> { + MediaWireProvenance { + stored_at: "unix:1000", + runtime_id: "raven", + role: "muninn.media", + producer: "muninn", + } +} + +fn access_unit() -> GameCultMediaVideoAccessUnitRecord { + GameCultMediaVideoAccessUnitRecord { + stream_id: "raven-primary-av".to_string(), + session_id: "session-1".to_string(), + frame_id: 4242, + codec: "h264".to_string(), + pts_ticks: 90_000, + duration_ticks: 3_000, + timebase_num: 1, + timebase_den: 90_000, + keyframe: true, + dependency_frame_id: None, + deadline_ticks: 108_000, + chunk_index: 2, + chunk_count: 5, + payload: (0u8..=255).collect(), + } +} + +/// Muninn's `VideoAccessUnitWirePayload`, reproduced exactly as it was. +/// +/// It existed only so `#[serde(with = "serde_bytes")]` could reach the payload, +/// because the `DatabaseEntry` derive ignores serde attributes. Every other +/// field was restated by hand, in order, and had to stay in step with the record +/// by discipline alone. +#[derive(Serialize)] +struct LegacyVideoAccessUnitWirePayload<'a>( + &'a str, + &'a str, + u64, + &'a str, + i64, + u32, + u32, + u32, + bool, + Option, + i64, + u16, + u16, + #[serde(with = "serde_bytes")] &'a [u8], +); + +/// If these differ, deleting the shadow structs was a wire break rather than a +/// simplification, and every existing consumer would need to move in step. +#[test] +fn the_derive_reproduces_the_hand_written_wire_payload_byte_for_byte() { + let record = access_unit(); + let legacy = LegacyVideoAccessUnitWirePayload( + &record.stream_id, + &record.session_id, + record.frame_id, + &record.codec, + record.pts_ticks, + record.duration_ticks, + record.timebase_num, + record.timebase_den, + record.keyframe, + record.dependency_frame_id, + record.deadline_ticks, + record.chunk_index, + record.chunk_count, + &record.payload, + ); + + let from_derive = rmp_serde::to_vec(&record).expect("record encodes"); + let from_shadow = rmp_serde::to_vec(&legacy).expect("shadow struct encodes"); + + assert_eq!( + from_derive, from_shadow, + "the `bytes` attribute must reproduce what serde_bytes produced by hand" + ); +} + +#[test] +fn a_video_record_round_trips_through_the_envelope() { + let record = GameCultMediaWireRecord::Video(access_unit()); + let wire = encode_media_wire_record(&record, provenance()).expect("encodes"); + assert_eq!(decode_media_wire_record(&wire).expect("decodes"), record); +} + +#[test] +fn every_variant_round_trips() { + let variants = vec![ + GameCultMediaWireRecord::Video(access_unit()), + GameCultMediaWireRecord::VideoParity(GameCultMediaVideoParityShardRecord { + stream_id: "s".to_string(), + session_id: "x".to_string(), + frame_id: 1, + codec: "h264".to_string(), + pts_ticks: 0, + duration_ticks: 1, + timebase_num: 1, + timebase_den: 90_000, + keyframe: false, + dependency_frame_id: Some(0), + deadline_ticks: 1, + chunk_count: 4, + parity_index: 1, + parity_count: 2, + chunk_payload_bytes: 848, + last_chunk_payload_bytes: 400, + payload: vec![1, 2, 3], + }), + GameCultMediaWireRecord::Audio(GameCultMediaAudioPacketRecord { + stream_id: "s".to_string(), + session_id: "x".to_string(), + packet_id: 9, + codec: "opus".to_string(), + pts_ticks: 0, + duration_ticks: 960, + timebase_num: 1, + timebase_den: 48_000, + deadline_ticks: 1, + payload: vec![4, 5, 6], + }), + GameCultMediaWireRecord::Feedback(GameCultMediaReceiverFeedbackRecord { + stream_id: "s".to_string(), + session_id: "x".to_string(), + receiver_id: "starfire-obs".to_string(), + highest_decodable_frame_id: Some(41), + missing_frame_ids: vec![42], + late_frame_ids: vec![43], + requested_keyframe: true, + jitter_us: 500, + decode_queue_us: 2_000, + observed_at: "unix:1000".to_string(), + missing_video_chunk_keys: vec!["42:1".to_string()], + }), + ]; + + for record in variants { + let wire = encode_media_wire_record(&record, provenance()).expect("encodes"); + assert_eq!( + decode_media_wire_record(&wire).expect("decodes"), + record, + "variant {} did not survive the envelope", + record.schema_id() + ); + } +} + +/// Record keys address one piece of media within a session. A consumer parses +/// them, so their shape is contract. +#[test] +fn record_keys_distinguish_chunks_parity_and_senders() { + let mut second_chunk = access_unit(); + second_chunk.chunk_index = 3; + + let first = GameCultMediaWireRecord::Video(access_unit()).record_key(); + let second = GameCultMediaWireRecord::Video(second_chunk).record_key(); + assert_ne!(first, second, "chunks of one frame must not share a key"); + assert!(first.contains(":video:"), "got {first}"); +} + +/// A shared envelope must not label another producer's traffic as Muninn's. +#[test] +fn the_producer_namespace_is_the_callers_not_the_envelopes() { + let record = GameCultMediaWireRecord::Video(access_unit()); + let theirs = MediaWireProvenance { + stored_at: "unix:1000", + runtime_id: "nightwing", + role: "brokkr.media", + producer: "brokkr", + }; + + let wire = encode_media_wire_record(&record, theirs).expect("encodes"); + let text = String::from_utf8_lossy(&wire); + assert!(text.contains("brokkr-media:"), "message id names the producer"); + assert!(text.contains("brokkr.media"), "tag names the producer"); + assert!( + !text.contains("muninn"), + "the envelope must not name a producer the caller did not supply" + ); +} + +#[test] +fn provenance_must_be_complete() { + let record = GameCultMediaWireRecord::Video(access_unit()); + for blank in ["stored_at", "runtime_id", "role", "producer"] { + let mut incomplete = provenance(); + match blank { + "stored_at" => incomplete.stored_at = "", + "runtime_id" => incomplete.runtime_id = "", + "role" => incomplete.role = "", + _ => incomplete.producer = "", + } + let error = encode_media_wire_record(&record, incomplete) + .expect_err("empty provenance must be refused"); + assert!(error.to_string().contains(blank), "got {error}"); + } +} + +#[test] +fn the_media_channel_is_named_once() { + assert_eq!(GAMECULT_MEDIA_CHANNEL, "media"); +} From 96138ffcfa7d48fe00d31079d7859baa1f3c9a58 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 02:23:56 +0200 Subject: [PATCH 075/203] Validate on decode, where both ends inherit it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving the envelope moved less than it should have. Muninn's decoder validated every record and checked the envelope's key against the key derived from the record's own contents; the version that landed here did neither, so the move was quietly a downgrade. Six of Muninn's tests failed on the switch and said so, which is the only reason this is a paragraph rather than a bug. The four record validators and the video chunk key move here with it. They check the contract, not one producer's use of it: non-empty identifiers, non-zero timebases, sane pressure figures, well-formed chunk keys. Left in Muninn they would have protected the producer and left every consumer to invent its own idea of a well-formed record — which is how a C++ receiver and a Rust sender came to disagree while each looked correct alone. The record-key check is the sharper one. A key is derived from the record it addresses, so a mismatch means envelope and payload disagree about what the thing is. Admitting that would let a consumer address media by a key its producer never meant. VideoChunkKey comes too. Its string form ("42:1") travels in receiver feedback and is parsed at the other end, so it was already contract living in a producer. Co-Authored-By: Claude Opus 5 --- packages/cultnet-rs/src/media_stream_wire.rs | 290 ++++++++++++++++++- 1 file changed, 276 insertions(+), 14 deletions(-) diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs index 2959eef8..7083d927 100644 --- a/packages/cultnet-rs/src/media_stream_wire.rs +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -167,19 +167,281 @@ pub fn decode_media_wire_record(payload: &[u8]) -> Result Ok(GameCultMediaWireRecord::Video( - rmp_serde::from_slice(&document.payload)?, - )), - GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA => Ok(GameCultMediaWireRecord::VideoParity( - rmp_serde::from_slice(&document.payload)?, - )), - GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA => Ok(GameCultMediaWireRecord::Audio( - rmp_serde::from_slice(&document.payload)?, - )), - GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA => Ok(GameCultMediaWireRecord::Feedback( - rmp_serde::from_slice(&document.payload)?, - )), - other => Err(anyhow!("unknown media schema {other}")), + let record = match document.schema_id.as_str() { + GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA => { + let record: GameCultMediaVideoAccessUnitRecord = + rmp_serde::from_slice(&document.payload)?; + validate_video_record(&record)?; + GameCultMediaWireRecord::Video(record) + } + GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA => { + let record: GameCultMediaVideoParityShardRecord = + rmp_serde::from_slice(&document.payload)?; + validate_video_parity_record(&record)?; + GameCultMediaWireRecord::VideoParity(record) + } + GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA => { + let record: GameCultMediaAudioPacketRecord = + rmp_serde::from_slice(&document.payload)?; + validate_audio_record(&record)?; + GameCultMediaWireRecord::Audio(record) + } + GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA => { + let record: GameCultMediaReceiverFeedbackRecord = + rmp_serde::from_slice(&document.payload)?; + validate_feedback_record(&record)?; + GameCultMediaWireRecord::Feedback(record) + } + other => return Err(anyhow!("unsupported media schema {other}")), + }; + + // The key is derived from the record's own contents, so a mismatch means the + // envelope and its payload disagree about what this is. Admitting it would + // let a consumer address media by a key its producer never meant. + let expected = record.record_key(); + if document.record_key != expected { + return Err(anyhow!( + "record key mismatch: envelope says {}, record derives {expected}", + document.record_key + )); + } + + Ok(record) +} + +// --------------------------------------------------------------------------- +// Validation +// +// Moved here from Muninn with the envelope. A decoder that does not validate +// leaves each consumer to invent its own idea of a well-formed record, which is +// how the C++ receiver and the Rust sender came to disagree while both looked +// correct in isolation. Muninn's tests for these came too; they now cover the +// contract rather than one producer's copy of it. +// --------------------------------------------------------------------------- + +/// Addresses one chunk of a video frame. The string form appears in receiver +/// feedback and is parsed by consumers, so its shape is contract. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct VideoChunkKey { + pub frame_id: u64, + pub chunk_index: u16, +} + +impl VideoChunkKey { + pub fn new(frame_id: u64, chunk_index: u16) -> Self { + Self { + frame_id, + chunk_index, + } + } + + pub fn parse(value: &str) -> Result { + if value.matches(':').count() != 1 { + return Err(anyhow!( + "video chunk key must contain exactly one ':' separator" + )); + } + let (frame_id, chunk_index) = value + .split_once(':') + .ok_or_else(|| anyhow!("video chunk key must be ':'"))?; + Ok(Self { + frame_id: frame_id + .parse() + .map_err(|_| anyhow!("video chunk key frame_id must be an integer"))?, + chunk_index: chunk_index + .parse() + .map_err(|_| anyhow!("video chunk key chunk_index must be an integer"))?, + }) + } + + pub fn as_feedback_key(&self) -> String { + format!("{}:{}", self.frame_id, self.chunk_index) } } + +pub fn video_chunk_feedback_key(frame_id: u64, chunk_index: u16) -> String { + VideoChunkKey::new(frame_id, chunk_index).as_feedback_key() +} + +pub fn normalize_video_chunk_feedback_keys(keys: Vec) -> Result> { + let mut parsed = keys + .iter() + .map(|key| VideoChunkKey::parse(key)) + .collect::>>()?; + parsed.sort_unstable(); + parsed.dedup(); + Ok(parsed + .into_iter() + .map(|key| key.as_feedback_key()) + .collect()) +} + +pub fn validate_video_record(record: &GameCultMediaVideoAccessUnitRecord) -> Result<()> { + if record.stream_id.is_empty() { + return Err(anyhow!("video media record stream_id must be non-empty")); + } + if record.session_id.is_empty() { + return Err(anyhow!("video media record session_id must be non-empty")); + } + if record.codec.is_empty() { + return Err(anyhow!("video media record codec must be non-empty")); + } + if record.timebase_num == 0 || record.timebase_den == 0 { + return Err(anyhow!("video media record timebase must be non-zero")); + } + if record.duration_ticks == 0 { + return Err(anyhow!( + "video media record duration_ticks must be greater than zero" + )); + } + if record.deadline_ticks < record.pts_ticks { + return Err(anyhow!( + "video media record deadline_ticks must not precede pts_ticks" + )); + } + if record.chunk_count == 0 { + return Err(anyhow!("video media record chunk_count must be non-zero")); + } + if record.chunk_index >= record.chunk_count { + return Err(anyhow!( + "video media record chunk_index {} is outside chunk_count {}", + record.chunk_index, + record.chunk_count + )); + } + if record.payload.is_empty() { + return Err(anyhow!("video media record payload must be non-empty")); + } + Ok(()) +} + +pub fn validate_video_parity_record(record: &GameCultMediaVideoParityShardRecord) -> Result<()> { + if record.stream_id.is_empty() { + return Err(anyhow!( + "video parity media record stream_id must be non-empty" + )); + } + if record.session_id.is_empty() { + return Err(anyhow!( + "video parity media record session_id must be non-empty" + )); + } + if record.codec.is_empty() { + return Err(anyhow!("video parity media record codec must be non-empty")); + } + if record.timebase_num == 0 || record.timebase_den == 0 { + return Err(anyhow!( + "video parity media record timebase must be non-zero" + )); + } + if record.duration_ticks == 0 { + return Err(anyhow!( + "video parity media record duration_ticks must be greater than zero" + )); + } + if record.deadline_ticks < record.pts_ticks { + return Err(anyhow!( + "video parity media record deadline_ticks must not precede pts_ticks" + )); + } + if record.chunk_count == 0 { + return Err(anyhow!( + "video parity media record chunk_count must be non-zero" + )); + } + if record.parity_count == 0 || record.parity_index >= record.parity_count { + return Err(anyhow!( + "video parity media record has invalid parity stripe metadata" + )); + } + if record.parity_count > record.chunk_count { + return Err(anyhow!( + "video parity media record parity_count exceeds chunk_count" + )); + } + if record.chunk_payload_bytes == 0 || record.last_chunk_payload_bytes == 0 { + return Err(anyhow!( + "video parity media record chunk lengths must be non-zero" + )); + } + if record.last_chunk_payload_bytes > record.chunk_payload_bytes { + return Err(anyhow!( + "video parity media record last chunk length exceeds regular chunk length" + )); + } + if record.payload.is_empty() { + return Err(anyhow!( + "video parity media record payload must be non-empty" + )); + } + if record.payload.len() > record.chunk_payload_bytes as usize { + return Err(anyhow!( + "video parity media record payload exceeds declared chunk length" + )); + } + Ok(()) +} + +pub fn validate_audio_record(record: &GameCultMediaAudioPacketRecord) -> Result<()> { + if record.stream_id.is_empty() { + return Err(anyhow!("audio media record stream_id must be non-empty")); + } + if record.session_id.is_empty() { + return Err(anyhow!("audio media record session_id must be non-empty")); + } + if record.codec.is_empty() { + return Err(anyhow!("audio media record codec must be non-empty")); + } + if record.timebase_num == 0 || record.timebase_den == 0 { + return Err(anyhow!("audio media record timebase must be non-zero")); + } + if record.duration_ticks == 0 { + return Err(anyhow!( + "audio media record duration_ticks must be greater than zero" + )); + } + if record.deadline_ticks < record.pts_ticks { + return Err(anyhow!( + "audio media record deadline_ticks must not precede pts_ticks" + )); + } + if record.payload.is_empty() { + return Err(anyhow!("audio media record payload must be non-empty")); + } + Ok(()) +} + +pub fn validate_feedback_record(record: &GameCultMediaReceiverFeedbackRecord) -> Result<()> { + if record.stream_id.is_empty() { + return Err(anyhow!( + "receiver feedback media record stream_id must be non-empty" + )); + } + if record.session_id.is_empty() { + return Err(anyhow!( + "receiver feedback media record session_id must be non-empty" + )); + } + if record.receiver_id.is_empty() { + return Err(anyhow!( + "receiver feedback media record receiver_id must be non-empty" + )); + } + if record.observed_at.is_empty() { + return Err(anyhow!( + "receiver feedback media record observed_at must be non-empty" + )); + } + if record.jitter_us < 0 { + return Err(anyhow!( + "receiver feedback media record jitter_us must be non-negative" + )); + } + if record.decode_queue_us < 0 { + return Err(anyhow!( + "receiver feedback media record decode_queue_us must be non-negative" + )); + } + normalize_video_chunk_feedback_keys(record.missing_video_chunk_keys.clone())?; + Ok(()) +} From 2041a0e0ed96ebcfcf9e129fb8cba2ab9f425ef2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 11:42:13 +0200 Subject: [PATCH 076/203] Bound the TypeScript fragment reassembly map cultnet-ts removed a fragment buffer only on successful reassembly, with no cap, no eviction and no timeout. A fragment set that loses one fragment was stranded for the life of the session, so the map grew without bound under any loss on a fragmenting channel. cultnet-rs at least fails closed at 64 pending sets; TypeScript just consumes memory. This is live, not theoretical. Ghostlight sends Heimdall sealed envelopes over CultMesh RUDP with max_fragment_bytes = 2048 (Ghostlight/crates/ghostlight-dungeon/src/heimdall.rs:697), and Heimdall's private command plane reassembles them through this path. Every lost fragment on that link left a buffer nothing would ever collect, on a long-running auth service. Caps pending sets at 64 to match cultnet-rs's max_pending_fragment_sets, and evicts the oldest stranded set rather than refusing the payload: a receiver that stops accepting fragmented traffic after a bounded number of losses is a denial of service delivered by the network. Map iteration is insertion-ordered, so the first key is the oldest. fragmentSetsEvicted and pendingFragmentSetCount make it observable. Found by the Muninn media load test, which hit the Rust half of the same defect; cultcache-py has the same unbounded map and is not touched here. npm run test --workspace=packages/cultnet-ts -> pass 54, fail 0 Co-Authored-By: Claude Opus 5 --- packages/cultnet-ts/src/rudp.ts | 32 +++++++++++++++++ packages/cultnet-ts/test/cultnet.test.ts | 46 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/packages/cultnet-ts/src/rudp.ts b/packages/cultnet-ts/src/rudp.ts index b6cae87d..56dc67fa 100644 --- a/packages/cultnet-ts/src/rudp.ts +++ b/packages/cultnet-ts/src/rudp.ts @@ -160,6 +160,12 @@ export class CultNetRudpSession { readonly #orderedNextSequenceByChannel = new Map(); readonly #orderedBuffers = new Map>(); readonly #fragmentBuffers = new Map(); + /// Matches cultnet-rs's `max_pending_fragment_sets`. A fragment set is only + /// removed on successful reassembly, so a set that loses one fragment is + /// stranded for the life of the session. Without a bound the map grows + /// forever under any loss on a fragmenting channel. + #maxPendingFragmentSets = 64; + #fragmentSetsEvicted = 0; constructor(options: CultNetRudpSessionOptions) { this.connectionId = toUint32(options.connectionId, "connectionId"); @@ -187,6 +193,17 @@ export class CultNetRudpSession { return this.#pendingReliable.size + this.#queuedReliable.length; } + /// Incomplete fragment sets dropped to stay within the pending bound. + /// Non-zero means the peer is losing fragments: the receiver is still + /// serving, and something upstream is not. + get fragmentSetsEvicted(): number { + return this.#fragmentSetsEvicted; + } + + get pendingFragmentSetCount(): number { + return this.#fragmentBuffers.size; + } + get lastReceivedAtMs(): number | undefined { return this.#lastReceivedAtMs; } @@ -203,6 +220,7 @@ export class CultNetRudpSession { this.#orderedNextSequenceByChannel.clear(); this.#orderedBuffers.clear(); this.#fragmentBuffers.clear(); + this.#fragmentSetsEvicted = 0; } createConnect(nowMs = 0, payload = new Uint8Array()): CultNetRudpPacket { @@ -636,6 +654,20 @@ export class CultNetRudpSession { payloads: new Map(), sequences: new Map(), }; + // Evict the oldest stranded set rather than refusing the payload. A + // receiver that stops accepting fragmented traffic after a bounded + // number of losses is a denial of service delivered by the network; + // dropping the least recent incomplete set costs one payload that was + // already incomplete. Map iteration is insertion-ordered, so the first + // key is the oldest. + while (this.#fragmentBuffers.size >= this.#maxPendingFragmentSets) { + const oldest = this.#fragmentBuffers.keys().next(); + if (oldest.done) { + break; + } + this.#fragmentBuffers.delete(oldest.value); + this.#fragmentSetsEvicted += 1; + } this.#fragmentBuffers.set(key, buffer); } if (buffer.fragmentCount !== fragmentCount || buffer.ordered !== (packet.ordered ?? false)) { diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index f0c3c3c3..2f540a86 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -1821,3 +1821,49 @@ test("Generated Ghostlight contracts can feed CultCacheTS directly without a Zod rmSync(tempDir, { recursive: true, force: true }); } }); + +test("rudp receiver evicts stranded fragment sets instead of refusing new ones", () => { + // A fragment set is removed only on successful reassembly, so every set that + // loses a fragment is stranded for the life of the session. Ghostlight sends + // Heimdall fragmented envelopes (max_fragment_bytes = 2048), so on the live + // auth path this map grew for every lost fragment and was never swept. + const receiver = new CultNetRudpSession({ connectionId: 9, initialSequence: 1, resendDelayMs: 50 }); + receiver.receive({ packetType: "accept", connectionId: 9, sequence: 1, ack: 0, ackMask: 0, channelId: "control" }); + + // 200 fragment sets that each lose their second fragment. + for (let id = 1; id <= 200; id += 1) { + const stranded = receiver.receive({ + packetType: "data", + connectionId: 9, + sequence: 100 + id, + ack: 0, + ackMask: 0, + channelId: "schema", + fragmentId: id, + fragmentIndex: 0, + fragmentCount: 2, + payload: Buffer.from([id & 0xff]), + }); + assert.equal(stranded.delivered.length, 0, "an incomplete set must not reassemble"); + } + + // Bounded rather than unbounded, and still serving. + assert.ok(receiver.pendingFragmentSetCount <= 64, `pending ${receiver.pendingFragmentSetCount}`); + assert.ok(receiver.fragmentSetsEvicted > 0, "nothing was evicted"); + + // The receiver still completes a fresh fragmented payload, which is what + // erroring at the cap took away: any nonzero loss killed the channel. + const first = receiver.receive({ + packetType: "data", connectionId: 9, sequence: 900, ack: 0, ackMask: 0, + channelId: "schema", fragmentId: 1000, fragmentIndex: 0, fragmentCount: 2, + payload: Buffer.from("he"), + }); + assert.equal(first.delivered.length, 0); + const second = receiver.receive({ + packetType: "data", connectionId: 9, sequence: 901, ack: 0, ackMask: 0, + channelId: "schema", fragmentId: 1000, fragmentIndex: 1, fragmentCount: 2, + payload: Buffer.from("llo"), + }); + assert.equal(second.delivered.length, 1); + assert.equal(Buffer.from(second.delivered[0]!.payload).toString(), "hello"); +}); From b6b1d9c66049bbdb9c4dbca8ab7cbac1e69d0f11 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 11:42:17 +0200 Subject: [PATCH 077/203] Bound the Python fragment reassembly map cultcache-py removed a fragment buffer only on successful reassembly, the same shape cultnet-ts had: a set that loses one fragment is kept for the life of the session and the map grows under any loss on a fragmenting channel. Cap at 64 to match the other runtimes, evict the oldest stranded set (dict insertion order) rather than refuse the payload, and expose fragment_sets_evicted so the drop is observable. The fragment id already wrapped correctly here; only the Rust allocator saturated. PYTHONPATH=src python -m unittest tests.test_cultcache -> 142 run, 1 pre-existing error (interop fixture path is cwd-relative, same on main) Co-Authored-By: Claude Fable 5.1 --- .../cultcache-py/src/cultnet_py/transport.py | 15 ++++++++ packages/cultcache-py/tests/test_cultcache.py | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/packages/cultcache-py/src/cultnet_py/transport.py b/packages/cultcache-py/src/cultnet_py/transport.py index 2b9fe462..73bd1e62 100644 --- a/packages/cultcache-py/src/cultnet_py/transport.py +++ b/packages/cultcache-py/src/cultnet_py/transport.py @@ -418,11 +418,21 @@ class CultNetRudpSession: self._ordered_next_sequence_by_channel: dict[str, int] = {} self._ordered_buffers: dict[str, dict[int, tuple[CultNetRudpDeliveredFrame, int]]] = {} self._fragment_buffers: dict[tuple[str, int], dict[str, Any]] = {} + # Matches cultnet-rs max_pending_fragment_sets. A set that loses one + # fragment is never completed; without a bound it is kept for the life + # of the session and the map grows under any loss on a fragmenting channel. + self._max_pending_fragment_sets = 64 + self._fragment_sets_evicted = 0 @property def connected(self) -> bool: return self._connected + @property + def fragment_sets_evicted(self) -> int: + """Incomplete fragment sets dropped to admit newer ones; one lost payload each.""" + return self._fragment_sets_evicted + @property def last_received_at_ms(self) -> int | None: return self._last_received_at_ms @@ -785,6 +795,11 @@ class CultNetRudpSession: raise ValueError("RUDP fragment index must be lower than fragment count") key = (packet.channel_id, packet.fragment_id) + if key not in self._fragment_buffers and len(self._fragment_buffers) >= self._max_pending_fragment_sets: + # Evict the oldest stranded set rather than refusing the payload; dicts + # iterate in insertion order, so the first key is the oldest. + del self._fragment_buffers[next(iter(self._fragment_buffers))] + self._fragment_sets_evicted += 1 buffer = self._fragment_buffers.setdefault( key, { diff --git a/packages/cultcache-py/tests/test_cultcache.py b/packages/cultcache-py/tests/test_cultcache.py index 29fdefef..639b5508 100644 --- a/packages/cultcache-py/tests/test_cultcache.py +++ b/packages/cultcache-py/tests/test_cultcache.py @@ -6654,5 +6654,40 @@ class CultCacheTests(unittest.TestCase): self.assertEqual(streams.latest_frame("mimir:kiyo-pro").sequence, 42) +class CultNetRudpFragmentBoundTests(unittest.TestCase): + def _pair(self) -> tuple[CultNetRudpSession, CultNetRudpSession]: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 91, 0, 0, "control")) + return sender, receiver + + def test_stranded_fragment_sets_are_bounded_and_evicted_oldest_first(self) -> None: + sender, receiver = self._pair() + receiver._max_pending_fragment_sets = 4 + + # Eight payloads each lose their last fragment; the map must not grow past four. + stranded = [] + for fill in range(1, 9): + packets = sender.send_many("media", bytes([fill]) * 2500, max_fragment_bytes=1000) + self.assertEqual(len(packets), 3) + for packet in packets[:2]: + self.assertEqual(receiver.receive(packet).delivered, ()) + stranded.append(packets[2]) + self.assertEqual(len(receiver._fragment_buffers), 4) + self.assertEqual(receiver.fragment_sets_evicted, 4) + + # A complete payload still lands. + delivered = [] + for packet in sender.send_many("media", bytes([9]) * 2500, max_fragment_bytes=1000): + delivered.extend(receiver.receive(packet).delivered) + self.assertEqual([frame.payload for frame in delivered], [bytes([9]) * 2500]) + self.assertEqual(receiver.fragment_sets_evicted, 5) + + # Oldest-first: set 8 still pends and completes late; set 1 was evicted. + self.assertEqual(receiver.receive(stranded[7]).delivered[0].payload, bytes([8]) * 2500) + self.assertEqual(receiver.receive(stranded[0]).delivered, ()) + + if __name__ == "__main__": unittest.main() From d3bc6e66048f87798e8cac0861a6f2a2a771a996 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 11:37:50 +0200 Subject: [PATCH 078/203] Keep receiving when fragment sets go stale A fragment set that lost one fragment was never reclaimed: the buffer map only shrank on successful reassembly or a payload-size violation. Once 64 sets were stranded, every new fragmented payload returned an error and the receiver's loop terminated. Any nonzero loss on any fragmenting channel was therefore a countdown, not a threshold: 3 fragments at 3% loss killed the receiver at ~700 payloads, 1% at ~2,100. Found by the media load test. When the bound is hit, evict the set untouched longest and count it via fragment_sets_evicted(). A stranded set's siblings were delivered long ago; dropping it is the right call and it was already lost. Also stop saturating the fragment id. saturating_add parked every set after the 65,535th on one shared id, and the reassembler stitched them together. That is the July 2026 eight-minute failure; the id now wraps to 1. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/src/rudp.rs | 37 +++++++++++- packages/cultnet-rs/tests/cultnet.rs | 85 ++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index b7019b1b..7e871288 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -167,6 +167,10 @@ struct FragmentBuffer { fragment_count: u16, payloads: BTreeMap>, sequences: BTreeMap, + /// Session-local arrival ordinal of the newest fragment in this set. A + /// set that stops being touched is one that lost a fragment; it is the + /// first to go when the pending-set bound is reached. + last_touched: u64, } pub struct CultNetRudpSession { @@ -191,6 +195,8 @@ pub struct CultNetRudpSession { ordered_next_sequence_by_channel: BTreeMap, ordered_buffers: BTreeMap>, fragment_buffers: BTreeMap<(String, u16), FragmentBuffer>, + fragment_touches: u64, + fragment_sets_evicted: u64, } impl CultNetRudpSession { @@ -217,6 +223,8 @@ impl CultNetRudpSession { ordered_next_sequence_by_channel: BTreeMap::new(), ordered_buffers: BTreeMap::new(), fragment_buffers: BTreeMap::new(), + fragment_touches: 0, + fragment_sets_evicted: 0, } } @@ -247,6 +255,14 @@ impl CultNetRudpSession { self.reliable_packets_expired } + /// Incomplete fragment sets discarded to admit a newer one. Each is a + /// payload that lost at least one fragment on the wire; a rising count is + /// the link's loss rate multiplied by the fragment count, and the reason + /// media payloads should fit in one packet. + pub fn fragment_sets_evicted(&self) -> u64 { + self.fragment_sets_evicted + } + pub fn queued_reliable_packet_count(&self) -> usize { self.queued_reliable.len() } @@ -968,7 +984,19 @@ impl CultNetRudpSession { if !self.fragment_buffers.contains_key(&key) && self.fragment_buffers.len() >= self.max_pending_fragment_sets { - return Err(anyhow!("RUDP pending fragment-set limit reached")); + // A set that is still incomplete when the bound is reached has + // almost certainly lost a fragment; its siblings were delivered + // long ago. Drop the one untouched longest so the session keeps + // receiving. Erroring here made any lossy fragmenting channel a + // countdown to a dead receiver. + let stalest = self + .fragment_buffers + .iter() + .min_by_key(|(_, buffer)| buffer.last_touched) + .map(|(key, _)| key.clone()) + .expect("bound reached implies a pending set exists"); + self.fragment_buffers.remove(&stalest); + self.fragment_sets_evicted += 1; } let buffered_bytes = self .fragment_buffers @@ -995,12 +1023,15 @@ impl CultNetRudpSession { fragment_count: packet.fragment_count, payloads: BTreeMap::new(), sequences: BTreeMap::new(), + last_touched: 0, }); if buffer.fragment_count != packet.fragment_count || buffer.ordered != packet.ordered { return Err(anyhow!( "RUDP fragment metadata changed within a fragment set" )); } + self.fragment_touches += 1; + buffer.last_touched = self.fragment_touches; buffer .payloads .insert(packet.fragment_index, packet.payload.clone()); @@ -1171,7 +1202,9 @@ impl CultNetRudpSession { fn allocate_fragment_id(&mut self) -> u16 { let fragment_id = self.next_fragment_id; - self.next_fragment_id = self.next_fragment_id.saturating_add(1); + // Wrap, never saturate: `saturating_add` parked every set after the + // 65,535th on one shared id, and the reassembler stitched them together. + self.next_fragment_id = self.next_fragment_id.wrapping_add(1); if self.next_fragment_id == 0 { self.next_fragment_id = 1; } diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index 7be6ea18..92401305 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -2856,3 +2856,88 @@ fn rudp_profile_expires_only_the_media_channel() -> Result<()> { } Ok(()) } + +fn connected_rudp_pair() -> Result<(CultNetRudpSession, CultNetRudpSession)> { + let mut client = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 311, + initial_sequence: 1, + ..CultNetRudpSessionOptions::default() + }); + let mut server = CultNetRudpSession::new(CultNetRudpSessionOptions { + connection_id: 311, + initial_sequence: 100, + ..CultNetRudpSessionOptions::default() + }); + let connect = client.create_connect(0, Vec::new())?; + let accept = server.accept_connect(&connect, 1, Vec::new())?; + client.receive(&accept, 2)?; + Ok((client, server)) +} + +#[test] +fn rudp_evicts_stalest_incomplete_fragment_set_instead_of_dying() -> Result<()> { + let (mut client, mut server) = connected_rudp_pair()?; + client.set_max_pending_fragment_sets(4)?; + let fragmented = |server: &mut CultNetRudpSession, fill: u8| { + server.send_many( + "media", + vec![fill; 2500], + CultNetRudpSendOptions::default(), + Some(1000), + ) + }; + + // Eight payloads each lose their last fragment on the wire. Before, the + // fifth new set errored the receiver out and every later fragmented + // payload with it. + let mut stranded = Vec::new(); + for fill in 1..=8u8 { + let packets = fragmented(&mut server, fill)?; + assert_eq!(packets.len(), 3); + for packet in &packets[..2] { + assert!(client.receive(packet, 10)?.delivered.is_empty()); + } + stranded.push(packets[2].clone()); + } + assert_eq!(client.fragment_sets_evicted(), 4); + + // A complete payload still lands. + let complete = fragmented(&mut server, 9)?; + let mut delivered = Vec::new(); + for packet in &complete { + delivered.extend(client.receive(packet, 11)?.delivered); + } + assert_eq!(delivered.len(), 1); + assert_eq!(delivered[0].payload, vec![9u8; 2500]); + assert_eq!(client.fragment_sets_evicted(), 5); + + // The eviction order is stalest-first: set 8 is still pending and completes + // when its late fragment shows up; set 1 was evicted long ago, so its late + // fragment opens a fresh set and delivers nothing. + assert_eq!( + client.receive(&stranded[7], 12)?.delivered[0].payload, + vec![8u8; 2500] + ); + assert!(client.receive(&stranded[0], 13)?.delivered.is_empty()); + Ok(()) +} + +#[test] +fn rudp_fragment_ids_wrap_instead_of_saturating() -> Result<()> { + let (_client, mut server) = connected_rudp_pair()?; + let mut ids = Vec::new(); + for _ in 0..(u16::MAX as usize + 1) { + let packets = server.send_many( + "media", + vec![0u8; 1500], + CultNetRudpSendOptions::default(), + Some(1000), + )?; + ids.push(packets[0].fragment_id); + } + assert_eq!(ids[0], 1); + assert_eq!(ids[u16::MAX as usize - 1], u16::MAX); + // The 65,536th set must not share an id with the 65,535th. + assert_eq!(ids[u16::MAX as usize], 1); + Ok(()) +} From 66f05e161ae0697fd2d376d0c666a5ad8f46984f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 11:40:11 +0200 Subject: [PATCH 079/203] Expose fragment-set evictions in transport stats A receiver that survives loss by discarding stranded sets has to say so, or the drop is invisible and the next diagnosis starts from "delivered less than sent" again. CultNetTransportStats now carries fragment_sets_evicted, summed across hub peers like reliable_packets_expired, and the media delivery probe prints it beside delivered_pct. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/examples/media_delivery_probe.rs | 5 +++-- packages/cultnet-rs/src/rudp.rs | 6 ++++++ packages/cultnet-rs/src/transport.rs | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/cultnet-rs/examples/media_delivery_probe.rs b/packages/cultnet-rs/examples/media_delivery_probe.rs index 75058eaf..e1f2ac97 100644 --- a/packages/cultnet-rs/examples/media_delivery_probe.rs +++ b/packages/cultnet-rs/examples/media_delivery_probe.rs @@ -87,10 +87,11 @@ fn recv(bind: SocketAddr, expect: u64) -> Result<()> { let elapsed = started.elapsed().as_secs_f64(); println!("RESULT recv frames={frames} expected={expect} payload_bytes={payload_bytes}"); println!( - "RESULT recv wire_bytes_in={} wire_bytes_out={} elapsed_s={elapsed:.2} delivered_pct={:.2}", + "RESULT recv wire_bytes_in={} wire_bytes_out={} elapsed_s={elapsed:.2} delivered_pct={:.2} fragment_sets_evicted={}", stats.bytes_received, stats.bytes_sent, - (frames as f64 / expect as f64) * 100.0 + (frames as f64 / expect as f64) * 100.0, + stats.fragment_sets_evicted ); Ok(()) } diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index 7e871288..b6a8fe95 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -1453,6 +1453,11 @@ impl CultNetRudpServerHub { .values() .map(|peer| peer.session.reliable_packets_expired()) .fold(0_u64, |total, expired| total.saturating_add(expired)); + stats.fragment_sets_evicted = self + .peers + .values() + .map(|peer| peer.session.fragment_sets_evicted()) + .fold(0_u64, |total, evicted| total.saturating_add(evicted)); stats } @@ -1754,6 +1759,7 @@ impl CultNetRudpSocketTransportConnection { pub fn stats(&self) -> CultNetTransportStats { let mut stats = self.stats.clone(); stats.reliable_packets_expired = self.session.reliable_packets_expired(); + stats.fragment_sets_evicted = self.session.fragment_sets_evicted(); stats } diff --git a/packages/cultnet-rs/src/transport.rs b/packages/cultnet-rs/src/transport.rs index cf28079e..d097aa3c 100644 --- a/packages/cultnet-rs/src/transport.rs +++ b/packages/cultnet-rs/src/transport.rs @@ -22,6 +22,10 @@ pub struct CultNetTransportStats { /// Reliable packets dropped by a send-side expiry deadline instead of being /// retransmitted. Always zero for transports without reliable expiry. pub reliable_packets_expired: u64, + /// Incomplete fragment sets discarded to admit newer ones. Each is one + /// payload that lost a fragment on the wire. Zero for non-fragmenting + /// transports. + pub fragment_sets_evicted: u64, } #[derive(Clone, Debug, PartialEq, Eq)] From a8aeddacab7dd4b9e634b4420867ea3b402cb858 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 11:56:06 +0200 Subject: [PATCH 080/203] Let the media probe fragment on demand A seventh argument sets max_fragment_bytes on the sender. Without it a 3,392-byte payload leaves loopback as one datagram, and a matrix that claims to measure fragmentation measures nothing: the impair proxy's received count equalled the payload count in every cell. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/examples/media_delivery_probe.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cultnet-rs/examples/media_delivery_probe.rs b/packages/cultnet-rs/examples/media_delivery_probe.rs index e1f2ac97..397eb656 100644 --- a/packages/cultnet-rs/examples/media_delivery_probe.rs +++ b/packages/cultnet-rs/examples/media_delivery_probe.rs @@ -45,9 +45,10 @@ fn main() -> Result<()> { args.get(4).ok_or_else(|| anyhow!("payload bytes"))?.parse()?, args.get(5).ok_or_else(|| anyhow!("count"))?.parse()?, args.get(6).map(|v| v.parse()).transpose()?.unwrap_or(12.0), + args.get(7).map(|v| v.parse()).transpose()?, ), _ => Err(anyhow!( - "usage: media_delivery_probe recv | send " + "usage: media_delivery_probe recv | send [mbps] [max_fragment_bytes]" )), } } @@ -102,6 +103,7 @@ fn send( payload_bytes: usize, count: u64, target_mbps: f64, + max_fragment_bytes: Option, ) -> Result<()> { let socket = UdpSocket::bind("0.0.0.0:0")?; socket.set_nonblocking(true)?; @@ -109,6 +111,9 @@ fn send( CultNetRudpSocketTransportOptions::client("probe-send", socket, endpoint, 0x0BE0_0001); options.media_delivery = Some(delivery); options.media_reliable_expire_after_ms = None; + // Fragment above this many bytes so a lost datagram costs a whole payload; + // None sends each payload as one datagram, which loopback carries whole. + options.max_fragment_bytes = max_fragment_bytes; let mut transport = CultNetRudpSocketTransportConnection::new(options)?; transport.connect(b"probe".to_vec())?; From 95493862bdd60a5e5637282ab6df6802dc01220c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:10:47 +0200 Subject: [PATCH 081/203] Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py One distribution named cultcache-py carried three libraries. Rust and TypeScript layer them as cultcache < cultnet < cultmesh; Python now does the same, one package per layer, module names unchanged so every `from cultmesh_py import ...` keeps working. Two imports ran against that layering and are resolved by moving code to where the dependency direction says it lives, not by adding a shim: - cultnet_py.interop_peer imported the Verse/peer catalog, authority lease, stream, and simulation-fact contracts from cultmesh_py. They are wire contracts a bare peer has to speak without owning a node, which is why cultnet-rs and cultnet-ts keep them in the net layer. They now live in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them. - cultcache_py.benchmark imported cultnet_py. It measures framed CultNet parse beside cache paths, so it and compare_csharp move to cultnet_py. verify checks all three packages and moves to cultmesh_py. The single 6,693-line test module is split by what each test touches: a test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in cultnet-py, else cultcache-py. Each suite runs with only its layer and the layers below on the path, so a test that reaches up fails to import rather than quietly binding the packages back together. 11 / 53 / 78 = the same 142. Workflows install all three; the publish job is one matrix over the three packages keyed by tag prefix. The TS test harnesses put all three src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no longer contains cultnet_py or cultmesh_py; the new packages start at 0.1.0. Nothing was ever published, so no PyPI consumer moves. cultcache-py 11 tests, path: cultcache-py/src cultnet-py 53 tests, path: + cultnet-py/src cultmesh-py 78 tests, path: + cultmesh-py/src python -m cultmesh_py.verify -> ok, all five checks cultnet_py.interop_peer imports with cultmesh_py absent Not run here: the cultnet-ts interop ring and cultcache-ts store matrix need dotnet/kotlin peers; their PYTHONPATH change is four lines. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/cultmesh-portability.yml | 4 +- .github/workflows/cultnet-interop.yml | 2 +- .github/workflows/publish-packages.yml | 56 +- .gitignore | 4 + README.md | 6 +- docs/cultnet-transport-parity.md | 2 +- .../python-runtime-parity.md | 20 +- docs/runtime-parity-scope.md | 2 +- packages/cultcache-py/README.md | 394 +- packages/cultcache-py/pyproject.toml | 16 +- .../src/cultmesh_py/simulation.py | 135 - packages/cultcache-py/tests/test_cultcache.py | 6438 +---------------- packages/cultcache-ts/test/cult-cache.test.ts | 6 +- packages/cultmesh-py/README.md | 252 + packages/cultmesh-py/pyproject.toml | 29 + .../src/cultmesh_py/__init__.py | 2 +- .../src/cultmesh_py/client.py | 2 +- .../src/cultmesh_py/daemon.py | 2 +- .../src/cultmesh_py/facade.py | 2 +- .../src/cultmesh_py/node.py | 0 .../src/cultmesh_py/py.typed | 0 .../src/cultmesh_py/server.py | 2 +- .../src/cultmesh_py/session.py | 2 +- .../cultmesh-py/src/cultmesh_py/simulation.py | 47 + .../src/cultmesh_py}/verify.py | 6 +- packages/cultmesh-py/tests/test_cultmesh.py | 4491 ++++++++++++ .../tools/reactive_performance_probe.py | 0 packages/cultnet-py/README.md | 156 + packages/cultnet-py/pyproject.toml | 33 + .../src/cultnet_py/__init__.py | 54 + .../src/cultnet_py}/benchmark.py | 2 +- .../src/cultnet_py/client.py | 0 .../src/cultnet_py}/compare_csharp.py | 2 +- .../src/cultnet_py/cultmesh_contracts.py} | 107 +- .../src/cultnet_py/framing.py | 0 .../src/cultnet_py/interop_peer.py | 2 +- .../src/cultnet_py/messages.py | 0 .../src/cultnet_py/prediction.py | 0 .../src/cultnet_py/py.typed | 0 .../src/cultnet_py/replication.py | 0 .../src/cultnet_py/schema_catalog.py | 0 .../src/cultnet_py/security.py | 0 .../src/cultnet_py/shard_catalog.py | 0 .../src/cultnet_py/shard_log.py | 0 .../src/cultnet_py/shard_replication.py | 0 .../src/cultnet_py/simulation.py | 0 .../src/cultnet_py/snapshot.py | 0 .../src/cultnet_py/subscription.py | 0 .../src/cultnet_py/transport.py | 0 packages/cultnet-py/tests/test_cultnet.py | 2046 ++++++ .../GameCult.Caching.Benchmark.csproj | 0 .../GameCult.Caching.Benchmark/Program.cs | 0 .../test/interop/cultnet-interop.test.ts | 4 +- .../measure-reactive-document-performance.ps1 | 4 +- scripts/verify-reactive-document-scaling.ps1 | 6 +- 55 files changed, 7331 insertions(+), 7007 deletions(-) rename packages/cultcache-py/PARITY.md => docs/python-runtime-parity.md (96%) delete mode 100644 packages/cultcache-py/src/cultmesh_py/simulation.py create mode 100644 packages/cultmesh-py/README.md create mode 100644 packages/cultmesh-py/pyproject.toml rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/__init__.py (98%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/client.py (99%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/daemon.py (99%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/facade.py (99%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/node.py (100%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/py.typed (100%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/server.py (99%) rename packages/{cultcache-py => cultmesh-py}/src/cultmesh_py/session.py (98%) create mode 100644 packages/cultmesh-py/src/cultmesh_py/simulation.py rename packages/{cultcache-py/src/cultcache_py => cultmesh-py/src/cultmesh_py}/verify.py (98%) create mode 100644 packages/cultmesh-py/tests/test_cultmesh.py rename packages/{cultcache-py => cultmesh-py}/tools/reactive_performance_probe.py (100%) create mode 100644 packages/cultnet-py/README.md create mode 100644 packages/cultnet-py/pyproject.toml rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/__init__.py (79%) rename packages/{cultcache-py/src/cultcache_py => cultnet-py/src/cultnet_py}/benchmark.py (98%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/client.py (100%) rename packages/{cultcache-py/src/cultcache_py => cultnet-py/src/cultnet_py}/compare_csharp.py (99%) rename packages/{cultcache-py/src/cultmesh_py/wire.py => cultnet-py/src/cultnet_py/cultmesh_contracts.py} (84%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/framing.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/interop_peer.py (99%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/messages.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/prediction.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/py.typed (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/replication.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/schema_catalog.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/security.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/shard_catalog.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/shard_log.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/shard_replication.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/simulation.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/snapshot.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/subscription.py (100%) rename packages/{cultcache-py => cultnet-py}/src/cultnet_py/transport.py (100%) create mode 100644 packages/cultnet-py/tests/test_cultnet.py rename packages/{cultcache-py => cultnet-py}/tools/GameCult.Caching.Benchmark/GameCult.Caching.Benchmark.csproj (100%) rename packages/{cultcache-py => cultnet-py}/tools/GameCult.Caching.Benchmark/Program.cs (100%) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index 9c7b3005..4d93085a 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -77,8 +77,8 @@ jobs: - name: Install TypeScript workspace dependencies run: npm install --no-package-lock - - name: Install Python CultCache package - run: python -m pip install -e packages/cultcache-py + - name: Install Python CultLib packages + run: python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py - name: Verify explicit reactive scheduling across runtimes shell: pwsh diff --git a/.github/workflows/cultnet-interop.yml b/.github/workflows/cultnet-interop.yml index 3437add3..f7f2eefc 100644 --- a/.github/workflows/cultnet-interop.yml +++ b/.github/workflows/cultnet-interop.yml @@ -69,7 +69,7 @@ jobs: run: npm install --no-package-lock - name: Install Python package dependencies - run: python -m pip install -e packages/cultcache-py + run: python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py - name: Build TypeScript workspace dependencies run: npm run build --workspace packages/cultcache-ts diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index f92868aa..6061ca7a 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -8,15 +8,18 @@ name: Publish packages # directly, which is already how Ghostlight, Epiphany, CodexConnector and Odin # consume the core stack. # -# Tag a release to publish, for example `cultcache-ts-v0.13.6`. The tag's -# version must match the version in the package manifest; the workflow refuses -# the publish otherwise rather than shipping a mislabelled artifact. +# Tag a release to publish, for example `cultcache-ts-v0.13.6` or +# `cultnet-py-v0.1.0`. The tag's version must match the version in the package +# manifest; the workflow refuses the publish otherwise rather than shipping a +# mislabelled artifact. on: push: tags: - "cultcache-ts-v*" - "cultcache-py-v*" + - "cultnet-py-v*" + - "cultmesh-py-v*" workflow_dispatch: inputs: package: @@ -26,6 +29,8 @@ on: options: - cultcache-ts - cultcache-py + - cultnet-py + - cultmesh-py permissions: contents: read @@ -72,9 +77,16 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - cultcache-py: - name: cultcache-py to PyPI - if: startsWith(github.ref, 'refs/tags/cultcache-py-v') || inputs.package == 'cultcache-py' + # The three Python packages layer cultcache-py < cultnet-py < cultmesh-py, the + # same way the Rust crates and npm packages do. One job shape serves all + # three; the matrix entry names the package and the tag prefix that releases it. + python: + name: ${{ matrix.package }} to PyPI + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') || startsWith(github.ref, 'refs/tags/cultnet-py-v') || startsWith(github.ref, 'refs/tags/cultmesh-py-v') || endsWith(inputs.package, '-py') + strategy: + fail-fast: false + matrix: + package: [cultcache-py, cultnet-py, cultmesh-py] runs-on: ubuntu-latest timeout-minutes: 20 environment: pypi @@ -84,31 +96,51 @@ jobs: # long-lived API token is stored in the repository. id-token: write steps: + - name: Skip packages this run does not name + id: selected + run: | + if [ "${GITHUB_REF_NAME#${{ matrix.package }}-v}" != "$GITHUB_REF_NAME" ] || [ "${{ inputs.package }}" = "${{ matrix.package }}" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + - name: Check out CultLib + if: steps.selected.outputs.run == 'true' uses: actions/checkout@v4 - name: Set up Python + if: steps.selected.outputs.run == 'true' uses: actions/setup-python@v5 with: python-version: "3.12" - name: Verify the tag matches the manifest version - if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + if: steps.selected.outputs.run == 'true' && startsWith(github.ref, 'refs/tags/') run: | - manifest="$(python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('packages/cultcache-py/pyproject.toml').read_text())['project']['version'])")" - tagged="${GITHUB_REF_NAME#cultcache-py-v}" + manifest="$(python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('packages/${{ matrix.package }}/pyproject.toml').read_text())['project']['version'])")" + tagged="${GITHUB_REF_NAME#${{ matrix.package }}-v}" if [ "$manifest" != "$tagged" ]; then echo "tag $GITHUB_REF_NAME does not match manifest version $manifest" >&2 exit 1 fi + # Lower layers install from source so a release of one package is tested + # against the sibling revision it was cut with, not whatever PyPI has. + - name: Install and test + if: steps.selected.outputs.run == 'true' + run: | + python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py + python -m unittest discover -s packages/${{ matrix.package }}/tests + - name: Build sdist and wheel + if: steps.selected.outputs.run == 'true' run: | python -m pip install --upgrade build - python -m build packages/cultcache-py + python -m build packages/${{ matrix.package }} - name: Publish - if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + if: steps.selected.outputs.run == 'true' && startsWith(github.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@release/v1 with: - packages-dir: packages/cultcache-py/dist + packages-dir: packages/${{ matrix.package }}/dist diff --git a/.gitignore b/.gitignore index 656fafa0..94ed900d 100644 --- a/.gitignore +++ b/.gitignore @@ -303,6 +303,10 @@ node_modules/ !packages/cultmesh-ts/** !packages/cultcache-py/ !packages/cultcache-py/** +!packages/cultnet-py/ +!packages/cultnet-py/** +!packages/cultmesh-py/ +!packages/cultmesh-py/** !packages/cultcache-rs/ !packages/cultcache-rs/** !packages/cultnet-rs/ diff --git a/README.md b/README.md index 4775a040..602ccc6c 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,9 @@ The solution includes: - `packages/cultnet-ts`: TypeScript CultNet schema-v0 contracts, framing, discovery, raw document replication, and interop tests - `packages/cultmesh-ts`: TypeScript CultMesh local node and mesh catalog surface for local runtimes such as VoidBot - `packages/cultmesh-browser`: browser-safe, read-only CultMesh document leases and typed operations over an Odin-resolved WebSocket route -- `packages/cultcache-py`: Python CultCache/CultNet/CultMesh package with CultCache v1 wire parity +- `packages/cultcache-py`: Python CultCache with CultCache v1 wire parity +- `packages/cultnet-py`: Python CultNet contracts, framing, RUDP transport, replication, and interop peer +- `packages/cultmesh-py`: Python CultMesh local node, discovery, sessions, local server, and daemon - `packages/cultcache-rs`: Rust CultCache and derive macro - `packages/cultnet-rs`: Rust CultNet contracts, framing, discovery, and interop peer - `packages/cultmesh-kotlin`: Kotlin/JVM CultMesh and CultNet surface for Android-adjacent runtimes @@ -145,6 +147,8 @@ packages/ cultmesh-browser/ cultmesh-kotlin/ cultcache-py/ + cultnet-py/ + cultmesh-py/ cultcache-rs/ cultnet-rs/ ``` diff --git a/docs/cultnet-transport-parity.md b/docs/cultnet-transport-parity.md index aa962c46..d0779196 100644 --- a/docs/cultnet-transport-parity.md +++ b/docs/cultnet-transport-parity.md @@ -31,7 +31,7 @@ CultNet owns cross-runtime transport semantics: | C# interop peer | Schema-v0 MessagePack over TCP-framed or shared RUDP transport | UDP multicast probe/announce with TCP and RUDP transport profiles | Test harness transport parity | | TypeScript `cultnet-ts` | `CultNetPeer` over any Node `Duplex`, TCP-framed transport, or single-peer RUDP socket transport; interop uses TCP | UDP multicast probe/announce in the interop peer | First UDP socket binding for the shared RUDP reliability owner | | Rust `cultnet-rs` | Interop example serves and dials schema-v0 MessagePack over TCP-framed or shared RUDP transport; single-peer RUDP socket transport exists in the library | UDP multicast probe/announce with TCP and RUDP transport profiles | UDP socket binding for the shared RUDP reliability owner | -| Python `cultcache-py` | TCP sockets with 4-byte length-prefixed MessagePack frames for local CultMesh/CultNet server and client; single-peer RUDP socket transport exists in the library | Endpoint lists and CultMesh peer/Verse catalogs | UDP socket binding for the shared RUDP reliability owner | +| Python `cultnet-py` | TCP sockets with 4-byte length-prefixed MessagePack frames for local CultMesh/CultNet server and client; single-peer RUDP socket transport exists in the library | Endpoint lists and CultMesh peer/Verse catalogs | UDP socket binding for the shared RUDP reliability owner | | Kotlin `cultmesh-kotlin` | Channel-aware WebSocket transport connection for reliable ordered `schema` frames; single-peer RUDP socket transport exists in the library; interop CLI serves and dials schema-v0 MessagePack over TCP-framed or shared RUDP transport | CultMesh Verse and peer catalogs, plus endpoint lists carried in schema-v0 catalog messages | WebSocket/TCP adapters plus UDP socket binding for the shared RUDP reliability owner, with build-script and TypeScript harness proof | The live split is therefore: payload language and the native RUDP packet/session diff --git a/packages/cultcache-py/PARITY.md b/docs/python-runtime-parity.md similarity index 96% rename from packages/cultcache-py/PARITY.md rename to docs/python-runtime-parity.md index 18607d87..1cc222b7 100644 --- a/packages/cultcache-py/PARITY.md +++ b/docs/python-runtime-parity.md @@ -1,7 +1,7 @@ # Python Runtime Parity Map -This file is the current audit map for `cultcache-py` as a Python runtime for -CultCache, CultNet, and CultMesh. It is evidence, not confetti. +This file is the current audit map for the Python runtime packages +`cultcache-py`, `cultnet-py`, and `cultmesh-py`. It is evidence, not confetti. ## Proven Surfaces @@ -255,14 +255,14 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti. individual discovery, peer exchange, or simulation surfaces unserved without advertising disabled capabilities. - Python package health: - `cultcache-py-verify --json` checks the public CultCache/CultNet/CultMesh + `cultmesh-py-verify --json` checks the public CultCache/CultNet/CultMesh export surface declared by each package `__all__`, `py.typed` markers, a live local CultMesh framed-wire smoke over hello/schema/snapshot/shard catalog/shard log, typed peer-error export availability, peer-advertised mutation contracts, payload schema wire contracts, simulation capability truth, and benchmark sanity for Python-owned hot paths. - Python/C# public cache baseline: - `cultcache_py.compare_csharp` runs Python and C# `CultCache` upsert/get + `cultnet_py.compare_csharp` runs Python and C# `CultCache` upsert/get benchmarks with the same operation names and reports median Python-to-C# ratios from three samples by default. It also emits per-metric threshold checks and an overall parity status so performance claims have an explicit evidence gate. @@ -275,12 +275,14 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti. ## Local Python Gates ```powershell -$env:PYTHONPATH="$PWD\packages\cultcache-py\src" +$env:PYTHONPATH="$PWD\packages\cultcache-py\src;$PWD\packages\cultnet-py\src;$PWD\packages\cultmesh-py\src" python -m unittest discover -s packages\cultcache-py\tests -python -m cultcache_py.verify --json -python -m cultcache_py.benchmark --records 1000 --json -python -m cultcache_py.compare_csharp --records 1000 --samples 3 --json -python -m pip wheel --no-deps -w $env:TEMP packages\cultcache-py +python -m unittest discover -s packages\cultnet-py\tests +python -m unittest discover -s packages\cultmesh-py\tests +python -m cultmesh_py.verify --json +python -m cultnet_py.benchmark --records 1000 --json +python -m cultnet_py.compare_csharp --records 1000 --samples 3 --json +python -m pip wheel --no-deps -w $env:TEMP packages\cultcache-py packages\cultnet-py packages\cultmesh-py ``` ## Cross-Runtime Gate diff --git a/docs/runtime-parity-scope.md b/docs/runtime-parity-scope.md index 75d980d8..5d4f6b50 100644 --- a/docs/runtime-parity-scope.md +++ b/docs/runtime-parity-scope.md @@ -52,7 +52,7 @@ CultMesh. `reconnectPolicy` profile field from C#, Rust, Python, and Kotlin peers. Runtime-local tests cover the deterministic delay helper and reconnect controller in C#, TypeScript, Rust, Python, and Kotlin. -- `packages/cultcache-py/PARITY.md` is the Python-specific audit ledger. Its +- `docs/python-runtime-parity.md` is the Python-specific audit ledger. Its "Still Not Claimed" section is part of the contract, not an apology. - `docs/cultnet-transport-parity.md` is the transport ownership map. It tracks the remaining work to lower older LiteNetLib/WebSocket bodies behind the diff --git a/packages/cultcache-py/README.md b/packages/cultcache-py/README.md index 48c023ba..f8ac551f 100644 --- a/packages/cultcache-py/README.md +++ b/packages/cultcache-py/README.md @@ -1,12 +1,16 @@ -# cultcache-py +# cultcache-py -`cultcache-py` is the Python runtime package for CultCache, CultNet, and -CultMesh. Callers work with registered domain documents while the cache owns -schema identity, routing, globals, name lookups, indexes, and backing-store -envelopes. +`cultcache-py` is the Python CultCache runtime. Callers work with registered +domain documents while the cache owns schema identity, routing, globals, name +lookups, indexes, and backing-store envelopes. It is intentionally small. It is not an ORM, a database, or distributed consensus wearing rented authority. +It is the bottom of the Python CultLib stack and depends only on `msgpack`. +`cultnet-py` (transport and wire contracts) and `cultmesh-py` (nodes, +discovery, sessions, local server) build on it, mirroring `cultcache-rs` / +`cultnet-rs` / `cultmesh-rs` and the npm packages. + ## Current Shape - document types are registered explicitly @@ -21,10 +25,6 @@ It is intentionally small. It is not an ORM, a database, or distributed consensu - the JSONL store uses only the Python standard library for bootstrap consumers - `define_database_entry_type(...)` emits Rust/C#-style slot-indexed MessagePack array payloads for cross-runtime `DatabaseEntry` contracts -- `cultnet_py` exposes schema-v0 MessagePack helpers and 4-byte big-endian - frame helpers for raw document state, subscriptions, shard catch-up, - simulation observations, and witness artifact bundles -- `cultmesh_py` exposes a cache-backed local node surface for Python tools ## Example @@ -144,382 +144,28 @@ That matches the Rust `#[derive(DatabaseEntry)]` formatter shape and the C# `[Key(n)]` intent: schema evolution comes from stable field slots, not source member order. -## CultNet And CultMesh - -`cultnet_py` provides the Python schema-v0 wire helpers and an interop peer: - -```python -from datetime import UTC, datetime - -from cultnet_py import ( - CultNetSecret, - CultNetServerSecurityOptions, - CultNetRawClient, - CultNetSimulationObservation, - CultNetWitnessArtifactBundle, - apply_raw_snapshot, - apply_shard_log_response, - compute_simulation_claim_hash, - database_subscribe, - decode_frame, - encode_frame, - hello, - parse_message, - shard_catalog_request, - simulation_observation, - witness_artifact_bundle, -) -from cultmesh_py import CultMesh - -payload = hello(runtime_id="python-runtime").to_bytes() -message = parse_message(decode_frame(encode_frame(payload))) - -client = CultNetRawClient("127.0.0.1", 3075) -catalog = client.fetch_schema_catalog(kinds=["wire_message"]) -snapshot = client.fetch_snapshot_response(schema_ids=["cultnet.interop-note"]) -shard_catalog = client.fetch_shard_catalog(schema_ids=["cultnet.interop-note"]) -# With a local CultCache and the matching registered document definitions: -# applied = apply_raw_snapshot(cache, [note_doc], snapshot) - -subscription = database_subscribe(subscription_id="ui", schema_ids=["cultnet.interop-note"]) -shard_request = shard_catalog_request(message_id="shards", schema_ids=["cultnet.interop-note"]) -shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=0) -# log_changes = apply_shard_log_response(cache, [note_doc], shard_log) -with client.subscribe_database(subscription_id="ui", schema_ids=["cultnet.interop-note"]) as live: - initial_snapshot = live.read_next_snapshot_response() - -# The same raw-client request surface can ride RUDP when the peer advertises a -# rudp:// endpoint and shares the connection id for that lane. -rudp_client = CultMesh.create_client( - endpoint="rudp://127.0.0.1:3076", - connection_id=0x43554C54, -) -rudp_catalog = rudp_client.fetch_schema_catalog(kinds=["wire_message"]) - -claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit") -observation = simulation_observation( - message_id="obs-1", - witness_runtime_id="python-runtime", - shard_id="interop", - shard_epoch=1, - frame=42, - subject_id="player-1", - claim_kind="hit", - claim_hash=claim_hash, -) -typed_observation = CultNetSimulationObservation.from_wire(observation.to_wire()) -witness = witness_artifact_bundle( - bundle_id="bundle-1", - witness_kind="interop-proof", - captured_at="2026-06-13T00:00:00Z", - subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"}, - contracts=[{"role": "payload", "schemaId": "cultnet.interop-note"}], - artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], - provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-runtime"}, -) -typed_witness = CultNetWitnessArtifactBundle.from_wire(witness) -witness_payload = typed_witness.to_payload() - -security = CultNetServerSecurityOptions.development() -token = CultNetSecret.create_session_token( - "318fb4b6-ff5e-4c4f-b911-d81807de53a8", - datetime(2035, 1, 1, tzinfo=UTC), - security, - session_version=1, -) -session = CultNetSecret.try_validate_session_token(token, security) -``` - -AES-GCM string/byte encryption helpers are available through the same -`CultNetSecret` surface when the optional `crypto` extra is installed. - -The peer can serve, dial, and probe the same raw-state interop lane used by the -TypeScript, Rust, and C# test peers: - -```powershell -python -m cultnet_py.interop_peer serve --runtime-id python-peer --runtime-kind python --display-name "Python Peer" --agent-id python-agent --advertise-host 127.0.0.1 --tcp-port 3075 --discovery-port 4075 --discovery-group 239.77.44.11 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json -python -m cultnet_py.interop_peer dial --runtime-id python-client --runtime-kind python --display-name "Python Client" --agent-id python-client --target-host 127.0.0.1 --target-port 3075 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json -python -m cultnet_py.interop_peer probe --runtime-id python-prober --discovery-port 4075 --discovery-group 239.77.44.11 -``` - -`cultmesh_py` includes a local cache-backed node, schema-v0 helpers for the -CultMesh Verse catalog and peer exchange wire messages, local and verified -authority lease checks, stream transport negotiation, committed simulation fact -documents, and local prediction reconciliation: - -```python -from datetime import UTC, datetime, timedelta - -from cultcache_py import define_database_entry_type -from cultnet_py import ( - CultNetClientAuthorityScope, - CultNetFileShardReplicaCursorStore, - CultNetRawClient, - CultNetSchemaShardLogFetcher, - CultNetSchemaShardSnapshotFetcher, - CultNetShardDescriptor, - CultNetShardReplicator, - CultNetShardReplicatorOptions, -) -from cultmesh_py import ( - CultMesh, - CultMeshAuthorityLease, - CultMeshGameSessionOptions, - CultMeshHmacAuthorityLeaseVerifier, - CultMeshNodeOptions, - CultMeshSnapshotFanout, - peer_exchange_request, -) - -note_doc = define_database_entry_type( - "mesh.note", - [("body", 0), ("name", 1), ("kind", 2)], - name="name", - indexes={"kind": "kind"}, -) -node = CultMesh.start_node( - "mesh.cc", - runtime_id="python-runtime", - options=CultMeshNodeOptions(enable_durable_shard_logs=True), -) -node.database.register_document(note_doc) -unsubscribe = node.database.watch_record(note_doc, "note:1", lambda change: print(change.change_kind)) -unsubscribe_named = node.database.watch_by_name(note_doc, "intro", lambda change: print(change.record_key)) -unsubscribe_kind = node.database.watch_by_index(note_doc, "kind", "guide", lambda change: print(change.record_key)) -node.database.put(note_doc, "note:1", {"body": "hello", "name": "intro", "kind": "guide"}) -live_note = node.database.get_required(note_doc, "note:1") -put_message = node.database.put_raw_message( - note_doc, - "note:2", - {"body": "wire me", "name": "wire", "kind": "interop"}, - shard_id="interop", - shard_epoch=1, -) -snapshot_response = node.database.build_snapshot_response(schema_ids=[note_doc.catalog_entry().schema_id]) -snapshot_wire = node.database.create_snapshot_response(schema_ids=[note_doc.catalog_entry().schema_id]) -delete_message = node.database.delete_raw_message(note_doc, "note:2", shard_id="interop", shard_epoch=1) -shard_log_response = node.database.build_shard_log_response(shard_id="interop", shard_epoch=1) -shard_log_wire = node.database.create_shard_log_response(shard_id="interop", shard_epoch=1) - -station_stock_doc = define_database_entry_type( - "aetheria.station_stock", - [("missiles", 0), ("coolant", 1)], - schema_id="gamecult.aetheria.station_stock.v1", -) -station_stock_ui_doc = define_database_entry_type( - "aetheria.station_stock.ui", - [("missiles", 0), ("coolant", 1)], - schema_id="gamecult.aetheria.station_stock.v1", -) -node.database.register_document(station_stock_doc) -node.database.put(station_stock_ui_doc, "station:starbridge:stock", {"missiles": 24, "coolant": 80}) -stock = node.database.get_required(station_stock_ui_doc, "station:starbridge:stock") - -peers = CultMesh.create_peer_catalog() -response = peers.create_response(peer_exchange_request("pex-1", verse_id="local")) -lease_verifier = CultMeshHmacAuthorityLeaseVerifier({"odin": b"shared-lease-key"}) -lease_catalog = CultMesh.create_authority_lease_catalog( - signature_verifier=lease_verifier.verify, - require_verified_signatures=True, -) -lease_valid_from = datetime.now(UTC) -lease = lease_verifier.issue(CultMeshAuthorityLease( - lease_id="lease:python-runtime", - verse_id="local", - peer_id="python-runtime", - roles=("shard-primary",), - valid_from=lease_valid_from, - expires_at=lease_valid_from + timedelta(minutes=5), - shard_ids=("interop",), - issuer_runtime_id="odin", -)) -lease_catalog.upsert(lease) -session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope("python-runtime", schema_ids=(note_doc.catalog_entry().schema_id,), key_prefix="input:python"), - ), - ), -) -server = CultMesh.serve_node( - node, - peer_catalog=peers, - observation_hub=session.observation_hub, - port=3075, - max_snapshot_documents=1000, - max_snapshot_bytes=4 * 1024 * 1024, -) -``` - -The same local server can be launched as a package daemon when another runtime -or operator process needs a CultNet endpoint without embedding Python glue: - -```powershell -python -m cultmesh_py.daemon --runtime-id python-runtime --display-name "Python Runtime" --host 127.0.0.1 --port 3075 --cache-file mesh.cc --enable-durable-shard-logs --seed-interop-note --seed-shard-id interop --verse-id local --role shard-primary --ready-file mesh.ready.json -``` - -The ready file and stdout line are process readiness hints. The served state and -capability truth still live on the framed CultNet/CultMesh endpoint. Use -`--register-interop-note` to expose the package interop-note schema without -seeding a note, or `--seed-interop-note` to register it and publish one local -record for wire probes. Seeded records are committed through the raw mutation -path, so peers can read them through both snapshot and shard-log catch-up. Add -`--verse-id` and one or more `--role` values to publish the launched endpoint in -its own Verse and peer catalogs for discovery clients. When the daemon is -launched with `--cache-file`, `--enable-durable-shard-logs`, and -`--shard-log-file`, a later daemon process can rehydrate and serve the persisted -snapshot and shard log without reseeding. - -`CultMeshPeerHealthMonitor` probes peer-card endpoints with `cultnet.hello.v0` -and preserves the runtime id, display name, document types, message versions, -and mutation contracts reported by the peer. - -```python - -client = CultMesh.create_verse_discovery_client("127.0.0.1", 3075) -verses = client.fetch_verses(transport_version="cultmesh.v0") -mesh_peers = client.fetch_peers(verse_id="python-interop", roles=["read-replica"]) -client.sync_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"]) -client.fanout_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"]) -replica_notes = client.sync_documents( - node.database, - peers, - verse_id="python-interop", - roles=["read-replica"], - documents=[(note_doc, "note:remote")], -) -snapshot_fanout = CultMeshSnapshotFanout( - client, - node.database, - peers, - verse_id="python-interop", - roles=["read-replica"], - documents=[(note_doc, "note:remote")], - on_document=lambda note: print(note), -) -snapshot_fanout.sync_once() - -raw_client = CultNetRawClient("127.0.0.1", 3075) -synced_note = node.database.sync_document(raw_client, note_doc, "note:remote") -synced_stock = CultMesh.sync_document( - node, - raw_client, - station_stock_ui_doc, - "station:starbridge:stock", -) -with CultMesh.subscribe_document( - node, - raw_client, - station_stock_ui_doc, - "station:starbridge:stock", -) as stock_subscription: - stock = stock_subscription.sync_initial() - next_change = stock_subscription.read_next_change() -node.database.sync_shard_log(raw_client, shard_id="interop", shard_epoch=1) -replicator = CultNetShardReplicator( - node.database, - CultNetShardReplicatorOptions( - fetcher=CultNetSchemaShardLogFetcher(), - snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(), - cursor_store=CultNetFileShardReplicaCursorStore("mesh.replica-cursors.msgpack"), - poll_interval_seconds=1.0, - on_error=lambda error: print(error), - ), -) -replica_shard = CultNetShardDescriptor( - shard_id="interop", - owner_runtime_id="primary-runtime", - epoch=1, - schema_ids=(note_doc.catalog_entry().schema_id,), - primary_endpoints=("cultnet://127.0.0.1:3075",), -) -replicator.pull_once(replica_shard) -replicator.start([replica_shard]) -replicator.stop() -server.stop() - -streams = CultMesh.create_stream_catalog() - -claim_hash = "accepted-claim-hash" -facts = CultMesh.create_simulation_fact_committer(node) -committed = facts.commit({ - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": claim_hash, - "witnessCount": 2, - "supportWeight": 2.0, - "totalWeight": 2.0, - "confidence": 1.0, - "hasQuorum": True, -}) -session_commits = session.submit_and_commit(typed_observation) -prediction = session.predict(note_doc, "input:python:move", {"body": "predicted input"}) -unsubscribe() -``` - ## Wire Parity -See [PARITY.md](PARITY.md) for the current evidence map, gates, and things this -package still does not claim. +See [docs/python-runtime-parity.md](../../docs/python-runtime-parity.md) for +the evidence map across the three Python packages. -The Python interop peer is `cultcache_py.interop`: +The store interop peer is `cultcache_py.interop`: ```powershell python -m cultcache_py.interop write --file cache.cc --runtime-id python python -m cultcache_py.interop read --file cache.cc ``` -Current receipts: +`packages/cultcache-ts/test/cult-cache.test.ts` includes Python in the shared +CultCache v1 parity matrix with TypeScript, Rust, and C#. `cultcache_py` ships +a `py.typed` marker so downstream type checkers can inspect the package surface. -- `packages/cultcache-ts/test/cult-cache.test.ts` includes Python in the shared - CultCache v1 parity matrix with TypeScript, Rust, and C#. -- `packages/cultnet-ts/test/interop/cultnet-interop.test.ts` includes Python in - the live TS/Rust/C#/Python schema-v0 peer ring: discovery, hello, schema - catalog, wire-message catalog discovery, raw snapshot, raw document - put/delete, mutation receipt, fire-command receipt, database subscription - changes, shard catalog, shard log catch-up, simulation consensus candidates, - committed simulation fact snapshots, Python session prediction reconciliation, - and witness artifact bundle round-trips. The same test asks the Python peer - for CultMesh Verse catalog and peer exchange responses over the CultNet pipe, - then verifies the public `CultMeshDiscoveryClient` can fetch typed Python - Verse and peer descriptors from that live endpoint. It also verifies - `CultNetRawClient` can fetch the live Python peer's schema catalog, raw - snapshot, and shard catalog. -- `packages/cultcache-py/tests/test_cultcache.py` covers Python CultMesh - Verse catalog, peer exchange, authority lease, stream negotiation, CultNet - helper shapes, raw snapshot/shard-log application, simulation claim hashing, - consensus aggregation, game-session fact commits, local prediction - reconciliation, committed simulation fact payload slots, the local CultMesh - server's TCP and RUDP schema request paths, and witness artifact bundle - payload slots. -- `cultcache_py`, `cultnet_py`, and `cultmesh_py` ship `py.typed` markers so - downstream type checkers can inspect the package surface instead of treating - the runtime as an untyped xenos swamp. - -## Performance Baseline - -The package ships a lightweight benchmark for Python-owned hot paths. In a -CultLib source checkout, it can also run the C# reference benchmark beside it: +## Tests ```powershell -python -m cultcache_py.benchmark --records 1000 --json -python -m cultcache_py.compare_csharp --records 1000 --json -python -m cultcache_py.verify --json +$env:PYTHONPATH="$PWD\packages\cultcache-py\src" +python -m unittest discover -s packages\cultcache-py\tests ``` -It reports slot-indexed `DatabaseEntry` encode/decode throughput, framed -CultNet MessagePack parse throughput, raw snapshot application into a -registered cache, and public `CultCache` upsert/get throughput. The C# compare -command also runs `packages/cultcache-py/tools/GameCult.Caching.Benchmark` and -reports median Python-to-C# ratios for the shared public cache operations. It -uses three samples by default; pass `--samples` when you need a different -evidence shape. Both runtimes precompute record keys before measured cache -upsert/get loops so the comparison is about public cache access, not string -construction noise. The public in-memory cache owner stores records in -type-partitioned dictionaries so hot `get(...)` calls avoid per-call -composite-key allocation. Treat the benchmark numbers as a local baseline, not -a claim that Python matches the C# reference in every workload. +The suite runs with only this package on the path. If a test here needs +`cultnet_py` or `cultmesh_py`, it belongs in that package. diff --git a/packages/cultcache-py/pyproject.toml b/packages/cultcache-py/pyproject.toml index 9f515ac2..ebd92a26 100644 --- a/packages/cultcache-py/pyproject.toml +++ b/packages/cultcache-py/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "cultcache-py" -version = "0.1.0" -description = "Python CultCache, CultNet, and CultMesh runtime package for GameCult typed state." +version = "0.2.0" +description = "Python CultCache runtime for GameCult typed state." readme = "README.md" requires-python = ">=3.10" license = "MIT" @@ -18,23 +18,11 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] dependencies = ["msgpack>=1.0"] - -[project.optional-dependencies] -msgpack = ["msgpack>=1.0"] -crypto = ["cryptography>=42"] - [project.scripts] cultcache-py-interop = "cultcache_py.interop:main" -cultnet-py-interop = "cultnet_py.interop_peer:main" -cultcache-py-benchmark = "cultcache_py.benchmark:main" -cultcache-py-compare-csharp = "cultcache_py.compare_csharp:main" -cultcache-py-verify = "cultcache_py.verify:main" -cultmesh-py-daemon = "cultmesh_py.daemon:main" [tool.setuptools.packages.find] where = ["src"] [tool.setuptools.package-data] cultcache_py = ["py.typed"] -cultnet_py = ["py.typed"] -cultmesh_py = ["py.typed"] diff --git a/packages/cultcache-py/src/cultmesh_py/simulation.py b/packages/cultcache-py/src/cultmesh_py/simulation.py deleted file mode 100644 index 4b727fd5..00000000 --- a/packages/cultcache-py/src/cultmesh_py/simulation.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from datetime import UTC, datetime -import hashlib -from typing import Any - -from cultcache_py import define_database_entry_type -from cultcache_py.documents import DocumentDefinition - -from .node import CultMeshNode - -SIMULATION_FACT_DOCUMENT_TYPE = "gamecult.mesh.simulation_fact" -SIMULATION_FACT_SCHEMA_VERSION = "gamecult.mesh.simulation_fact.v1" - - -@dataclass(frozen=True) -class CultMeshSimulationFact: - fact_id: str - shard_id: str - shard_epoch: int - frame: int - subject_id: str - claim_kind: str - claim_hash: str - claim_summary: str | None - witness_count: int - support_weight: float - total_weight: float - confidence: float - committed_at: str - - @staticmethod - def compute_fact_id(candidate: dict[str, Any]) -> str: - canonical = "\x1f".join([ - _candidate_string(candidate, "shardId", "shard_id"), - str(_candidate_int(candidate, "shardEpoch", "shard_epoch")), - str(_candidate_int(candidate, "frame", "frame")), - _candidate_string(candidate, "subjectId", "subject_id"), - _candidate_string(candidate, "claimKind", "claim_kind"), - ]) - return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - - @staticmethod - def create_record_key(candidate: dict[str, Any]) -> str: - return f"simulation:{CultMeshSimulationFact.compute_fact_id(candidate)}" - - @staticmethod - def from_candidate(candidate: dict[str, Any], *, committed_at: str | None = None) -> "CultMeshSimulationFact": - return CultMeshSimulationFact( - fact_id=CultMeshSimulationFact.compute_fact_id(candidate), - shard_id=_candidate_string(candidate, "shardId", "shard_id"), - shard_epoch=_candidate_int(candidate, "shardEpoch", "shard_epoch"), - frame=_candidate_int(candidate, "frame", "frame"), - subject_id=_candidate_string(candidate, "subjectId", "subject_id"), - claim_kind=_candidate_string(candidate, "claimKind", "claim_kind"), - claim_hash=_candidate_string(candidate, "claimHash", "claim_hash"), - claim_summary=_candidate_optional_string(candidate, "claimSummary", "claim_summary"), - witness_count=_candidate_int(candidate, "witnessCount", "witness_count"), - support_weight=_candidate_float(candidate, "supportWeight", "support_weight"), - total_weight=_candidate_float(candidate, "totalWeight", "total_weight"), - confidence=_candidate_float(candidate, "confidence", "confidence"), - committed_at=committed_at or datetime.now(UTC).isoformat(), - ) - - -simulation_fact_document: DocumentDefinition[CultMeshSimulationFact] = define_database_entry_type( - SIMULATION_FACT_DOCUMENT_TYPE, - [ - ("fact_id", 0), - ("shard_id", 1), - ("shard_epoch", 2), - ("frame", 3), - ("subject_id", 4), - ("claim_kind", 5), - ("claim_hash", 6), - ("claim_summary", 7, None), - ("witness_count", 8), - ("support_weight", 9), - ("total_weight", 10), - ("confidence", 11), - ("committed_at", 12), - ], - cls=CultMeshSimulationFact, - name="fact_id", - indexes={"shard": "shard_id", "frame": "frame", "subject": "subject_id", "claim_kind": "claim_kind"}, - schema_name=SIMULATION_FACT_DOCUMENT_TYPE, - schema_version=SIMULATION_FACT_SCHEMA_VERSION, -) - - -@dataclass(frozen=True) -class CultMeshSimulationFactCommit: - key: str - fact: CultMeshSimulationFact - - -@dataclass -class CultMeshSimulationFactCommitter: - node: CultMeshNode - document: DocumentDefinition[CultMeshSimulationFact] = simulation_fact_document - - def __post_init__(self) -> None: - if all(document.type != self.document.type for document in self.node.documents): - self.node.register_document(self.document) - - def commit(self, candidate: dict[str, Any], *, committed_at: str | None = None) -> CultMeshSimulationFactCommit: - if not bool(candidate.get("hasQuorum") if "hasQuorum" in candidate else candidate.get("has_quorum")): - raise ValueError("Simulation candidate cannot be committed before quorum") - key = CultMeshSimulationFact.create_record_key(candidate) - fact = CultMeshSimulationFact.from_candidate(candidate, committed_at=committed_at) - self.node.put(self.document, key, fact) - return CultMeshSimulationFactCommit(key=key, fact=fact) - - -def _candidate_string(candidate: dict[str, Any], camel: str, snake: str) -> str: - value = candidate.get(camel, candidate.get(snake, "")) - if value is None: - return "" - return str(value) - - -def _candidate_optional_string(candidate: dict[str, Any], camel: str, snake: str) -> str | None: - value = candidate.get(camel, candidate.get(snake)) - if value is None: - return None - return str(value) - - -def _candidate_int(candidate: dict[str, Any], camel: str, snake: str) -> int: - return int(candidate.get(camel, candidate.get(snake, 0))) - - -def _candidate_float(candidate: dict[str, Any], camel: str, snake: str) -> float: - return float(candidate.get(camel, candidate.get(snake, 0.0))) diff --git a/packages/cultcache-py/tests/test_cultcache.py b/packages/cultcache-py/tests/test_cultcache.py index 639b5508..2c23fbf7 100644 --- a/packages/cultcache-py/tests/test_cultcache.py +++ b/packages/cultcache-py/tests/test_cultcache.py @@ -1,24 +1,9 @@ -from __future__ import annotations +from __future__ import annotations -import json -import os -import socket -import subprocess -import sys import tempfile -import threading -import time import unittest -from unittest.mock import patch -import hashlib -import hmac -import io from dataclasses import asdict, dataclass -from datetime import UTC, datetime, timedelta from pathlib import Path -from typing import Any, Callable -from uuid import uuid4 - from cultcache_py import ( CultCache, JsonLinesBackingStore, @@ -26,155 +11,7 @@ from cultcache_py import ( define_database_entry_type, define_document_type, ) -from cultcache_py.benchmark import run_benchmark -from cultcache_py.compare_csharp import ( - DEFAULT_PARITY_THRESHOLD, - DEFAULT_SAMPLE_COUNT, - _compare_common_metrics, - _median_result, - _parity_status, -) -from cultcache_py.interop import INTEROP_SCHEMA_VERSION, interop_note_document, read_note, write_note -from cultcache_py.verify import verify -from cultnet_py.interop_peer import append_shard_log_put, build_state, raw_snapshot_response -from cultnet_py import ( - compute_simulation_claim_hash, - CultNetDatabaseChange, - CultNetClientAuthorityScope, - CultNetRawClient, - CultNetRawDocumentRecord, - CultNetRawSnapshotResponse, - CultNetSchemaCatalog, - CultNetSchemaDescriptor, - CultNetShardCatalog, - CultNetShardDescriptor, - CultNetShardLogEntry, - CultNetShardLogResponse, - CultNetFileShardReplicaCursorStore, - CultNetFileShardMutationLogStore, - CultNetInMemoryShardReplicaCursorStore, - CultNetSchemaWriteForwarder, - CultNetSchemaShardLogFetcher, - CultNetSchemaShardSnapshotFetcher, - CultNetPeerError, - CultNetClientSecurityOptions, - CultNetSecret, - CultNetServerSecurityOptions, - CultNetShardReplicator, - CultNetShardReplicatorOptions, - CultNetShardMutationLogStore, - CultNetShardWriteForwarder, - CultNetSimulationConsensusOptions, - CultNetSimulationConsensusCandidate, - CultNetSimulationObservation, - CultNetSimulationObservationHub, - CultNetRudpPacket, - CultNetRudpPacketType, - CultNetRudpSendOptions, - CultNetRudpSession, - CultNetRudpSessionOptions, - CultNetRudpSocketMode, - CultNetRudpReconnectLoop, - CultNetRudpSocketTransportConnection, - CultNetRudpSocketTransportOptions, - TcpFramedTransportConnection, - CultNetWitnessArtifactBundle, - apply_raw_document_record, - apply_raw_snapshot, - apply_shard_log_response, - create_rudp_transport_profile, - create_tcp_framed_transport_profile, - create_reconnect_policy, - CultNetReconnectController, - database_subscribe, - database_unsubscribe, - decode_frame, - decode_rudp_packet, - decode_witness_artifact_bundle_payload, - document_delete, - document_put_raw, - encode_frame, - encode_rudp_packet, - encode_witness_artifact_bundle_payload, - hello, - login, - login_success, - parse_message, - register, - schema_catalog_request, - schema_document_map, - shard_catalog_request, - shard_log_request, - simulation_observation, - snapshot_request, - verify_session, - wire_message_schema_catalog, - wire_message_schema_descriptors, - witness_artifact_bundle, - compute_reconnect_delay_ms, -) -from cultmesh_py import create_node -from cultmesh_py import ( - CultMesh, - CultMeshDatabase, - CultMeshDatabaseChange, - CultMeshGameSessionOptions, - CultMeshNodeOptions, - CultMeshReactiveDocumentOptions, - CultMeshPeerCard, - CultMeshPeerCatalog, - CultMeshSimulationFact, - CultMeshAuthorityLease, - CultMeshAuthorityLeaseCatalog, - CultMeshHmacAuthorityLeaseVerifier, - CultMeshDiscoveryClient, - CultMeshPeerExchangeClient, - CultMeshPeerHealthMonitor, - CultMeshSimulationObservationFanout, - CultMeshSnapshotFanout, - CultMeshStreamCatalog, - CultMeshStreamConsumerProfile, - CultMeshStreamDescriptor, - CultMeshStreamFrameHandle, - CultMeshVerseCatalog, - CultMeshVerseCompatibility, - CultMeshVerseDescriptor, - CultMeshVerseDiscoveryClient, - READY_SCHEMA_VERSION, - peer_exchange_request, - simulation_fact_document, - verse_catalog_request, -) - - -def bind_udp_socket() -> socket.socket: - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.bind(("127.0.0.1", 0)) - sock.settimeout(0.02) - return sock - - -def pump_rudp_handshake( - client: CultNetRudpSocketTransportConnection, - server: CultNetRudpSocketTransportConnection, -) -> None: - for _ in range(20): - server.receive_once() - client.receive_once() - server.receive_once() - if client.connected and server.connected: - return - time.sleep(0.005) - raise AssertionError("RUDP socket handshake did not complete") - - -def receive_rudp_frame(transport: CultNetRudpSocketTransportConnection): - for _ in range(20): - frame = transport.receive_once() - if frame is not None: - return frame - time.sleep(0.005) - raise AssertionError("RUDP socket frame was not delivered") +from cultcache_py.interop import read_note, write_note @dataclass @@ -195,14 +32,6 @@ def item_doc(): class CultCacheTests(unittest.TestCase): - def _wait_until(self, predicate: Callable[[], bool], *, timeout_seconds: float = 2.0) -> None: - deadline = time.monotonic() + timeout_seconds - while time.monotonic() < deadline: - if predicate(): - return - time.sleep(0.01) - self.fail("Timed out waiting for condition") - def test_round_trips_registered_documents(self) -> None: with tempfile.TemporaryDirectory() as tmp: document = item_doc() @@ -409,101 +238,6 @@ class CultCacheTests(unittest.TestCase): self.assertEqual(loaded["authorRuntimeId"], "python-test") self.assertIn("interop", loaded["tags"]) - def test_benchmark_reports_core_hot_path_metrics(self) -> None: - result = run_benchmark(8) - metric_names = {metric["name"] for metric in result["metrics"]} - self.assertEqual(result["records"], 8) - self.assertEqual(result["wireMessageCount"], 8) - self.assertEqual(metric_names, { - "database_entry_encode", - "database_entry_decode", - "cultnet_frame_parse", - "raw_snapshot_apply", - "cache_upsert", - "cache_get", - }) - self.assertTrue(all(metric["opsPerSecond"] > 0 for metric in result["metrics"])) - - def test_compare_csharp_median_result_summarizes_samples(self) -> None: - self.assertEqual(DEFAULT_SAMPLE_COUNT, 3) - self.assertEqual(DEFAULT_PARITY_THRESHOLD, 0.90) - samples = [ - { - "runtime": "python", - "records": 3, - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 30.0, "opsPerSecond": 100.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 60.0, "opsPerSecond": 50.0}, - ], - }, - { - "runtime": "python", - "records": 3, - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 10.0, "opsPerSecond": 300.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 150.0}, - ], - }, - { - "runtime": "python", - "records": 3, - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 200.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 40.0, "opsPerSecond": 75.0}, - ], - }, - ] - - summarized = _median_result(samples) - - self.assertEqual(summarized["sampleCount"], 3) - self.assertEqual(summarized["metrics"][0]["name"], "cache_get") - self.assertEqual(summarized["metrics"][0]["elapsedMs"], 20.0) - self.assertEqual(summarized["metrics"][0]["opsPerSecond"], 200.0) - self.assertEqual(summarized["metrics"][1]["opsPerSecond"], 75.0) - - def test_compare_csharp_marks_threshold_status_per_metric(self) -> None: - python_result = { - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 2.0, "opsPerSecond": 50.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0}, - ], - } - csharp_result = { - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, - ], - } - - comparison = _compare_common_metrics(python_result, csharp_result, parity_threshold=0.90) - by_name = {item["name"]: item for item in comparison} - - self.assertFalse(by_name["cache_get"]["meetsParityThreshold"]) - self.assertTrue(by_name["cache_upsert"]["meetsParityThreshold"]) - self.assertEqual(_parity_status(comparison, "ok"), "below-threshold") - self.assertEqual(_parity_status([by_name["cache_upsert"]], "ok"), "meets-threshold") - self.assertEqual(_parity_status(comparison, "failed"), "unknown") - - def test_compare_csharp_marks_meets_threshold_when_all_common_metrics_pass(self) -> None: - comparison = _compare_common_metrics( - { - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 120.0}, - ], - }, - { - "metrics": [ - {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, - {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, - ], - }, - parity_threshold=0.90, - ) - - self.assertEqual(_parity_status(comparison, "ok"), "meets-threshold") - def test_cache_put_without_backing_store_keeps_in_memory_value(self) -> None: document = define_database_entry_type( "bench.memory_item", @@ -520,6174 +254,6 @@ class CultCacheTests(unittest.TestCase): self.assertEqual(cache.get(document, "item:one"), {"name": "one", "value": 1}) self.assertEqual(cache.get_required_envelope(document, "item:one").key, "item:one") - def test_verify_reports_python_runtime_surface_health(self) -> None: - result = verify(records=4) - - self.assertEqual(result["status"], "ok") - self.assertEqual( - {check["name"] for check in result["checks"]}, - { - "public_exports", - "typed_markers", - "local_cultmesh_wire_smoke", - "cultmesh_capability_truth", - "benchmark_sanity", - }, - ) - by_name = {check["name"]: check for check in result["checks"]} - self.assertEqual(by_name["public_exports"]["missing"], {}) - self.assertEqual(by_name["cultmesh_capability_truth"]["failures"], []) - - def test_cultnet_security_options_match_cultlib_defaults_and_environment_rules(self) -> None: - client = CultNetClientSecurityOptions.development() - server = CultNetServerSecurityOptions.development() - - self.assertEqual(client.connection_key, "gamecult-dev-connection-key") - self.assertEqual(server.connection_key, "gamecult-dev-connection-key") - self.assertTrue(server.is_development) - self.assertEqual(server.to_client_options().connection_key, client.connection_key) - self.assertEqual(client.encryption_key(), hashlib.sha256(b"gamecult-dev-connection-key").digest()) - self.assertEqual(server.session_signing_key(), hashlib.sha256(b"gamecult-dev-session-signing-secret").digest()) - self.assertEqual( - CultNetServerSecurityOptions.from_environment({}, allow_development_defaults=True), - server, - ) - with self.assertRaisesRegex(ValueError, "not configured"): - CultNetServerSecurityOptions.from_environment({}) - with self.assertRaisesRegex(ValueError, "GAMECULT_SESSION_SIGNING_SECRET"): - CultNetServerSecurityOptions.from_environment({"GAMECULT_CONNECTION_KEY": "key"}) - configured = CultNetServerSecurityOptions.from_environment({ - "GAMECULT_CONNECTION_KEY": "prod-key", - "GAMECULT_SESSION_SIGNING_SECRET": "prod-secret", - }) - self.assertEqual(configured.connection_key, "prod-key") - self.assertFalse(configured.is_development) - - def test_cultnet_secret_helpers_validate_csharp_versioned_session_tokens(self) -> None: - security = CultNetServerSecurityOptions("connection-key", "session-secret") - user_id = uuid4() - expires = datetime(2035, 1, 2, 3, 4, 5, tzinfo=UTC) - - token = CultNetSecret.create_session_token(user_id, expires, security, session_version=7) - payload_b64, signature_b64 = token.split(".") - self.assertEqual( - CultNetSecret.from_base64url(payload_b64).decode("utf-8"), - f"{user_id.hex}|{int(expires.timestamp())}|7", - ) - self.assertEqual( - CultNetSecret.from_base64url(signature_b64), - hmac.new( - hashlib.sha256(b"session-secret").digest(), - CultNetSecret.from_base64url(payload_b64), - hashlib.sha256, - ).digest(), - ) - - validated = CultNetSecret.try_validate_session_token( - token, - security, - now_utc=datetime(2035, 1, 2, 3, 4, 4, tzinfo=UTC), - ) - self.assertIsNotNone(validated) - assert validated is not None - self.assertEqual(validated.user_id, user_id) - self.assertEqual(validated.expires_at_utc, expires) - self.assertEqual(validated.session_version, 7) - - tampered_payload = CultNetSecret.to_base64url(CultNetSecret.from_base64url(payload_b64) + b"tamper") - tampered = f"{tampered_payload}.{signature_b64}" - self.assertIsNone(CultNetSecret.try_validate_session_token(tampered, security)) - self.assertIsNone(CultNetSecret.try_validate_session_token(token, security, now_utc=expires)) - - def test_cultnet_secret_helpers_validate_legacy_two_field_session_tokens(self) -> None: - security = CultNetServerSecurityOptions.development() - user_id = uuid4() - expires_at_seconds = int(datetime(2036, 1, 1, tzinfo=UTC).timestamp()) - payload = f"{user_id.hex}|{expires_at_seconds}".encode("utf-8") - signature = hmac.new(security.session_signing_key(), payload, hashlib.sha256).digest() - token = f"{CultNetSecret.to_base64url(payload)}.{CultNetSecret.to_base64url(signature)}" - - validated = CultNetSecret.try_validate_session_token( - token, - security, - now_utc=datetime(2035, 12, 31, tzinfo=UTC), - ) - - self.assertIsNotNone(validated) - assert validated is not None - self.assertEqual(validated.user_id, user_id) - self.assertEqual(validated.session_version, 0) - - def test_cultnet_secret_encryption_helpers_are_optional_without_crypto_dependency(self) -> None: - nonce = b"123456789012" - security = CultNetServerSecurityOptions.development() - try: - encrypted = CultNetSecret.encrypt_string("hello", nonce, security.to_client_options()) - except ImportError as exc: - self.assertIn("cryptography", str(exc)) - return - self.assertEqual(CultNetSecret.decrypt_string(encrypted, nonce, security), "hello") - - def test_cultnet_schema_message_frame_round_trip(self) -> None: - message = hello( - runtime_id="python-test", - supported_schema_versions=["cultnet.hello.v0"], - transport_profiles=[ - { - "schemaVersion": "cultnet.transport_profile.v0", - "runtimeId": "python-test", - "transports": [ - { - "transportId": "test", - "protocol": "tcp_framed", - "channels": [{"channelId": "schema", "delivery": "reliable", "ordering": "ordered"}], - } - ], - } - ], - ) - parsed = parse_message(decode_frame(encode_frame(message.to_bytes()))) - self.assertEqual(parsed.schema_version, "cultnet.hello.v0") - self.assertEqual(parsed.body["runtimeId"], "python-test") - self.assertEqual(parsed.body["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") - - def test_cultnet_tcp_framed_transport_carries_schema_payloads_with_stats(self) -> None: - payload = b"cultnet-payload" - output = io.BytesIO() - sender = TcpFramedTransportConnection( - output, - profile=create_tcp_framed_transport_profile("sender", transport_id="test-tcp"), - ) - sender.send("schema", payload) - self.assertEqual(sender.stats.frames_sent, 1) - self.assertEqual(sender.stats.bytes_sent, len(payload) + 4) - with self.assertRaisesRegex(ValueError, "only supports the schema channel"): - sender.send("unreliable", b"") - - receiver = TcpFramedTransportConnection( - io.BytesIO(output.getvalue()), - profile=create_tcp_framed_transport_profile("receiver", transport_id="test-tcp"), - ) - frame = receiver.receive() - self.assertEqual(frame.channel_id, "schema") - self.assertEqual(frame.payload, payload) - self.assertEqual(receiver.stats.frames_received, 1) - self.assertEqual(receiver.stats.bytes_received, len(payload) + 4) - self.assertEqual(receiver.profile["transports"][0]["protocol"], "tcp_framed") - - def test_cultnet_raw_client_uses_schema_transport_factory(self) -> None: - import msgpack # type: ignore - - sent_messages: list[dict[str, object]] = [] - test_case = self - - class FakeSchemaTransport: - profile = create_tcp_framed_transport_profile("fake-client") - - def __enter__(self) -> "FakeSchemaTransport": - return self - - def __exit__(self, exc_type: object, exc: object, traceback: object) -> None: - self.close() - - def send(self, channel_id: str, payload: bytes) -> None: - test_case.assertEqual(channel_id, "schema") - sent_messages.append(msgpack.unpackb(payload, raw=False)) - - def receive(self) -> object: - response = { - "schemaVersion": "cultnet.schema_catalog_response.v0", - "messageId": "transport-factory", - "schemas": [], - } - return type("Frame", (), {"channel_id": "schema", "payload": msgpack.packb(response, use_bin_type=True)})() - - def close(self) -> None: - pass - - client = CultNetRawClient( - "unused.example.test", - 1, - create_transport=FakeSchemaTransport, - ) - - response = client.fetch_schema_catalog(message_id="transport-factory") - - self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") - self.assertEqual(sent_messages[0]["schemaVersion"], "cultnet.schema_catalog_request.v0") - self.assertEqual(sent_messages[0]["messageId"], "transport-factory") - - def test_cultmesh_client_uses_rudp_schema_transport_endpoint(self) -> None: - import msgpack # type: ignore - - server_socket = bind_udp_socket() - server_host, server_port = server_socket.getsockname()[:2] - connection_id = 0x43554C54 - server = CultNetRudpSocketTransportConnection( - CultNetRudpSocketTransportOptions( - runtime_id="python-rudp-schema-server", - socket=server_socket, - mode=CultNetRudpSocketMode.SERVER, - connection_id=connection_id, - initial_sequence=100, - resend_delay_ms=25, - ) - ) - received_messages: list[dict[str, object]] = [] - - def serve_once() -> None: - deadline = time.monotonic() + 2.0 - try: - while time.monotonic() < deadline: - frame = server.receive_once() - server.poll_resends() - if frame is None: - time.sleep(0.005) - continue - received = msgpack.unpackb(frame.payload, raw=False) - received_messages.append(received) - response = { - "schemaVersion": "cultnet.schema_catalog_response.v0", - "messageId": received["messageId"], - "schemas": [], - } - server.send("schema", msgpack.packb(response, use_bin_type=True)) - return - raise AssertionError("RUDP schema server did not receive a request") - finally: - server.close() - - server_thread = threading.Thread(target=serve_once, daemon=True) - server_thread.start() - client = CultMesh.create_client( - endpoint=f"rudp://{server_host}:{server_port}", - timeout_seconds=2.0, - connection_id=connection_id, - runtime_id="python-rudp-schema-client", - ) - - response = client.fetch_schema_catalog(message_id="rudp-transport-factory") - server_thread.join(timeout=2.0) - - self.assertFalse(server_thread.is_alive()) - self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") - self.assertEqual(response["messageId"], "rudp-transport-factory") - self.assertEqual(received_messages[0]["schemaVersion"], "cultnet.schema_catalog_request.v0") - self.assertEqual(received_messages[0]["messageId"], "rudp-transport-factory") - - def test_cultnet_rudp_packet_codec_uses_deterministic_reliable_ordered_fixture(self) -> None: - encoded = encode_rudp_packet( - CultNetRudpPacket( - packet_type=CultNetRudpPacketType.DATA, - connection_id=0x01020304, - sequence=0x0000002A, - ack=0x00000029, - ack_mask=0x80000001, - channel_id="schema", - reliable=True, - ordered=True, - fragment_id=7, - fragment_index=1, - fragment_count=3, - payload=b"hello", - ) - ) - - self.assertEqual( - encoded.hex(), - "434e523000030b2a010203040000002a0000002980000001000700010003000000050600736368656d6168656c6c6f", - ) - - decoded = decode_rudp_packet(encoded) - self.assertEqual(decoded.packet_type, CultNetRudpPacketType.DATA) - self.assertEqual(decoded.connection_id, 0x01020304) - self.assertEqual(decoded.sequence, 0x0000002A) - self.assertEqual(decoded.ack, 0x00000029) - self.assertEqual(decoded.ack_mask, 0x80000001) - self.assertEqual(decoded.channel_id, "schema") - self.assertTrue(decoded.reliable) - self.assertTrue(decoded.ordered) - self.assertFalse(decoded.sequenced) - self.assertEqual(decoded.fragment_id, 7) - self.assertEqual(decoded.fragment_index, 1) - self.assertEqual(decoded.fragment_count, 3) - self.assertEqual(decoded.payload, b"hello") - - def test_cultnet_rudp_transport_profile_advertises_state_and_realtime_channels(self) -> None: - profile = create_rudp_transport_profile( - "python-rudp", - transport_id="public-rudp", - host="127.0.0.1", - port=7777, - max_payload_bytes=1200, - max_fragment_bytes=1000, - ) - - self.assertEqual(profile["transports"][0]["protocol"], "rudp") - self.assertEqual(profile["transports"][0]["reconnectPolicy"]["schemaVersion"], "cultnet.reconnect_policy.v0") - self.assertEqual(profile["transports"][0]["reconnectPolicy"]["baseDelayMs"], 1_000) - self.assertEqual( - [ - (channel["channelId"], channel["delivery"], channel["ordering"]) - for channel in profile["transports"][0]["channels"] - ], - [ - ("schema", "reliable", "ordered"), - ("latest", "unreliable", "sequenced"), - ("realtime", "unreliable", "unordered"), - ], - ) - - def test_cultnet_reconnect_policy_exposes_portable_delay_contract(self) -> None: - policy = create_reconnect_policy(policy_id="rudp-default", max_attempts=8) - - self.assertEqual(policy.schema_version, "cultnet.reconnect_policy.v0") - self.assertEqual(policy.policy_id, "rudp-default") - self.assertEqual(policy.max_attempts, 8) - self.assertEqual(policy.to_wire()["policyId"], "rudp-default") - self.assertEqual(policy.to_wire()["maxAttempts"], 8) - self.assertEqual(compute_reconnect_delay_ms(policy, 1), 1_000) - self.assertEqual(compute_reconnect_delay_ms(policy, 3, 17), 4_017) - self.assertEqual(compute_reconnect_delay_ms(policy, 9, 999), 30_250) - self.assertEqual(compute_reconnect_delay_ms(policy, 0, -5), 1_000) - - def test_cultnet_reconnect_controller_schedules_attempts_and_reset(self) -> None: - controller = CultNetReconnectController(create_reconnect_policy(max_attempts=2)) - - first = controller.record_failure(10_000) - self.assertEqual(first.attempt, 1) - self.assertTrue(first.should_retry) - self.assertEqual(first.delay_ms, 1_000) - self.assertEqual(first.next_attempt_at_ms, 11_000) - self.assertFalse(first.exhausted) - self.assertFalse(controller.can_attempt(10_999)) - self.assertTrue(controller.can_attempt(11_000)) - - second = controller.record_failure(11_000, 17) - self.assertEqual(second.attempt, 2) - self.assertEqual(second.delay_ms, 2_017) - self.assertEqual(second.next_attempt_at_ms, 13_017) - self.assertTrue(second.should_retry) - - exhausted = controller.record_failure(13_017) - self.assertEqual(exhausted.attempt, 2) - self.assertFalse(exhausted.should_retry) - self.assertEqual(exhausted.delay_ms, 0) - self.assertIsNone(exhausted.next_attempt_at_ms) - self.assertTrue(exhausted.exhausted) - self.assertFalse(controller.can_attempt(99_000)) - - controller.reset() - self.assertEqual(controller.attempt, 0) - self.assertIsNone(controller.next_attempt_at_ms) - self.assertFalse(controller.exhausted) - self.assertTrue(controller.can_attempt(99_000)) - - def test_cultnet_rudp_reconnect_loop_consumes_shared_controller(self) -> None: - class FakeRudpReconnectTransport: - def __init__(self) -> None: - self.connect_calls = 0 - self.close_calls = 0 - self.last_payload: bytes | None = None - - def connect(self, payload: bytes = b"") -> None: - self.connect_calls += 1 - self.last_payload = bytes(payload) - - def close(self) -> None: - self.close_calls += 1 - - now_ms = 10_000 - captured_callback: list[Callable[[], None]] = [] - transports: list[FakeRudpReconnectTransport] = [] - - def create_transport() -> FakeRudpReconnectTransport: - transport = FakeRudpReconnectTransport() - transports.append(transport) - return transport - - def scheduler(delay_ms: int, callback: Callable[[], None]) -> Callable[[], None]: - self.assertEqual(delay_ms, 1_017) - captured_callback[:] = [callback] - - def cancel() -> None: - captured_callback.clear() - - return cancel - - loop = CultNetRudpReconnectLoop( - create_transport, - reconnect_policy=create_reconnect_policy(max_attempts=2), - connect_payload=b"join", - now_ms=lambda: now_ms, - jitter_ms=lambda: 17, - scheduler=scheduler, - ) - - first = loop.start() - self.assertEqual(first.connect_calls, 1) - self.assertEqual(first.last_payload, b"join") - self.assertIs(loop.transport, first) - - decision = loop.handle_closed() - self.assertIsNotNone(decision) - self.assertEqual(decision.attempt, 1) - self.assertTrue(decision.should_retry) - self.assertEqual(decision.delay_ms, 1_017) - self.assertEqual(decision.next_attempt_at_ms, 11_017) - self.assertEqual(loop.reconnect_controller.attempt, 1) - self.assertEqual(loop.reconnect_controller.next_attempt_at_ms, 11_017) - self.assertEqual(len(captured_callback), 1) - - now_ms = 11_017 - captured_callback[0]() - self.assertEqual(len(transports), 2) - self.assertEqual(transports[1].connect_calls, 1) - self.assertIs(loop.transport, transports[1]) - - loop.mark_connected() - self.assertEqual(loop.reconnect_controller.attempt, 0) - - loop.stop() - self.assertEqual(transports[1].close_calls, 1) - self.assertIsNone(loop.transport) - - def test_cultnet_rudp_session_handshake_acks_reliable_connect_and_accept_packets(self) -> None: - client = CultNetRudpSession( - CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=1, resend_delay_ms=50) - ) - server = CultNetRudpSession( - CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=100, resend_delay_ms=50) - ) - - connect = client.create_connect(0, b"join") - self.assertEqual(connect.packet_type, CultNetRudpPacketType.CONNECT) - self.assertEqual(connect.sequence, 1) - self.assertEqual(client.pending_reliable_sequences, (1,)) - - accept = server.accept_connect(connect, 10, b"ok") - self.assertEqual(accept.packet_type, CultNetRudpPacketType.ACCEPT) - self.assertEqual(accept.ack, 1) - self.assertTrue(server.connected) - self.assertEqual(server.pending_reliable_sequences, (100,)) - - client.receive(accept, 20) - self.assertTrue(client.connected) - self.assertEqual(client.pending_reliable_sequences, ()) - - ack = client.create_ack() - self.assertEqual(ack.ack, 100) - server.receive(ack, 30) - self.assertEqual(server.pending_reliable_sequences, ()) - - def test_cultnet_rudp_session_computes_ack_masks_and_clears_pending_reliable_packets(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=7, initial_sequence=10, resend_delay_ms=100)) - receiver = CultNetRudpSession( - CultNetRudpSessionOptions(connection_id=7, initial_sequence=200, resend_delay_ms=100) - ) - sender.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 7, 1, 0, 0, "control") - ) - receiver.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 7, 2, 0, 0, "control") - ) - - first = sender.send( - "schema", - b"first", - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), - ) - second = sender.send( - "schema", - b"second", - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), - ) - third = sender.send( - "schema", - b"third", - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), - ) - self.assertEqual(sender.pending_reliable_sequences, (10, 11, 12)) - - receiver.receive(first) - receiver.receive(third) - ack_with_gap = receiver.create_ack() - self.assertEqual(ack_with_gap.ack, 12) - self.assertEqual(ack_with_gap.ack_mask, 0b10 | (1 << 9)) - sender.receive(ack_with_gap) - self.assertEqual(sender.pending_reliable_sequences, (11,)) - - receiver.receive(second) - full_ack = receiver.create_ack() - self.assertEqual(full_ack.ack, 12) - self.assertEqual(full_ack.ack_mask, 0b11 | (1 << 9)) - sender.receive(full_ack) - self.assertEqual(sender.pending_reliable_sequences, ()) - - def test_cultnet_rudp_session_schedules_reliable_resends_until_acked(self) -> None: - session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=99, initial_sequence=1, resend_delay_ms=100)) - session.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 99, 50, 0, 0, "control") - ) - sent = session.send( - "schema", - b"payload", - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10), - ) - - self.assertEqual(session.due_resends(90), ()) - self.assertEqual(tuple(packet.sequence for packet in session.due_resends(110)), (sent.sequence,)) - self.assertEqual(session.due_resends(150), ()) - - session.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACK, 99, 51, sent.sequence, 0, "control") - ) - self.assertEqual(session.due_resends(250), ()) - - def test_cultnet_rudp_session_pings_and_detects_receive_timeout(self) -> None: - client = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=1)) - server = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=100)) - connect = client.create_connect(0, b"join") - accept = server.accept_connect(connect, 10) - client.receive(accept, 20) - - ping = client.create_ping(b"pulse") - ping_result = server.receive(ping, 30) - self.assertIsNotNone(ping_result.reply) - self.assertEqual(ping_result.reply.packet_type, CultNetRudpPacketType.PONG) - self.assertEqual(ping_result.reply.payload, b"pulse") - - pong_result = client.receive(ping_result.reply, 40) - self.assertTrue(pong_result.pong) - self.assertEqual(pong_result.pong_payload, b"pulse") - self.assertFalse(client.check_timeout(90, 50)) - self.assertTrue(client.check_timeout(91, 50)) - self.assertFalse(client.connected) - - def test_cultnet_rudp_session_bounds_pending_reliable_packets_before_enqueue(self) -> None: - session = CultNetRudpSession( - CultNetRudpSessionOptions(connection_id=102, initial_sequence=1, max_pending_reliable_packets=2) - ) - session.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 102, 50, 0, 0, "control")) - session.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) - session.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) - with self.assertRaisesRegex(ValueError, "reliable send queue is full"): - session.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True)) - self.assertEqual(session.pending_reliable_sequences, (1, 2)) - - fragmented = CultNetRudpSession( - CultNetRudpSessionOptions(connection_id=103, initial_sequence=1, max_pending_reliable_packets=3) - ) - fragmented.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 103, 50, 0, 0, "control")) - with self.assertRaisesRegex(ValueError, "reliable send queue is full"): - fragmented.send_many( - "schema", - b"fragment-me", - CultNetRudpSendOptions(reliable=True, ordered=True), - max_fragment_bytes=3, - ) - self.assertEqual(fragmented.pending_reliable_sequences, ()) - - def test_cultnet_rudp_session_suppresses_duplicates_and_delivers_reliable_ordered_payloads(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=100)) - sender.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 123, 90, 0, 0, "control") - ) - receiver.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 123, 91, 0, 0, "control") - ) - - first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) - second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) - third = sender.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True)) - - self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"]) - self.assertEqual(receiver.receive(third).delivered, ()) - self.assertEqual(receiver.receive(first).delivered, ()) - self.assertEqual( - [frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered], - ["second", "third"], - ) - - def test_cultnet_rudp_lossy_packets_cannot_create_reliable_ordered_gaps(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=100)) - sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 90, 0, 0, "control")) - receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 91, 0, 0, "control")) - - realtime = sender.send("realtime", b"discarded realtime", CultNetRudpSendOptions()) - latest = sender.send("latest", b"discarded latest state", CultNetRudpSendOptions(sequenced=True)) - schema = sender.send("schema", b"committed response", CultNetRudpSendOptions(reliable=True, ordered=True)) - - self.assertEqual(realtime.sequence, 0) - self.assertEqual(latest.sequence, 1) - self.assertEqual( - [frame.payload.decode("utf-8") for frame in receiver.receive(schema).delivered], - ["committed response"], - ) - - def test_cultnet_rudp_unreliable_sequenced_delivery_is_scoped_to_its_channel(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=50)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=100)) - sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 90, 0, 0, "control")) - receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 91, 0, 0, "control")) - - older = sender.send("latest", b"older", CultNetRudpSendOptions(sequenced=True)) - newer = sender.send("latest", b"newer", CultNetRudpSendOptions(sequenced=True)) - - self.assertEqual(older.sequence, 1) - self.assertEqual(newer.sequence, 2) - self.assertEqual( - [frame.payload.decode("utf-8") for frame in receiver.receive(newer).delivered], - ["newer"], - ) - self.assertEqual(receiver.receive(older).delivered, ()) - - def test_cultnet_rudp_rejects_unreliable_ordered_delivery(self) -> None: - session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=196, initial_sequence=1)) - session.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 196, 50, 0, 0, "control")) - - with self.assertRaises(ValueError) as raised: - session.send( - "schema", - b"cannot order what will not retransmit", - CultNetRudpSendOptions(ordered=True), - ) - self.assertIn("ordered delivery requires reliability", str(raised.exception)) - - def test_cultnet_rudp_session_skips_control_packets_while_ordering_schema_payloads(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=100)) - sender.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 124, 90, 0, 0, "control") - ) - receiver.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 124, 91, 0, 0, "control") - ) - - first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) - control = sender.create_ack() - second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) - - self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"]) - self.assertEqual(receiver.receive(control).delivered, ()) - self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered], ["second"]) - - def test_cultnet_rudp_session_fragments_and_reassembles_reliable_ordered_payloads(self) -> None: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=100)) - sender.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 456, 90, 0, 0, "control") - ) - receiver.receive( - CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 456, 91, 0, 0, "control") - ) - - packets = sender.send_many( - "schema", - b"fragment-me-please", - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10), - max_fragment_bytes=5, - ) - self.assertEqual(len(packets), 4) - self.assertEqual(tuple(packet.fragment_count for packet in packets), (4, 4, 4, 4)) - self.assertEqual(tuple(packet.fragment_index for packet in packets), (0, 1, 2, 3)) - self.assertTrue(all(packet.fragment_id == packets[0].fragment_id for packet in packets)) - - self.assertEqual(receiver.receive(packets[0]).delivered, ()) - self.assertEqual(receiver.receive(packets[1]).delivered, ()) - self.assertEqual(receiver.receive(packets[2]).delivered, ()) - delivered = receiver.receive(packets[3]).delivered - self.assertEqual(len(delivered), 1) - self.assertEqual(delivered[0].payload, b"fragment-me-please") - self.assertEqual(delivered[0].sequence, packets[0].sequence) - - def test_cultnet_rudp_session_advances_large_fragment_sets_through_bounded_window(self) -> None: - connection_id = 457 - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=100)) - sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, connection_id, 0, 0, 0, "control")) - receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, connection_id, 0, 0, 0, "control")) - - fragment_count = CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS + 17 - payload = bytes(index % 251 for index in range(fragment_count * 8)) - wire = list(sender.send_many( - "schema", - payload, - CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=1), - max_fragment_bytes=8, - )) - self.assertEqual(len(wire), CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS) - oldest_sequence = wire[0].sequence - self.assertEqual(len(sender.pending_reliable_sequences), len(wire)) - self.assertEqual(sender.queued_reliable_packet_count, 17) - - delivered = [] - while wire: - admitted = len(wire) - for _ in range(admitted): - packet = wire.pop(0) - delivered.extend(receiver.receive(packet, 2).delivered) - acknowledged = sender.receive(receiver.create_ack(), 3) - wire.extend(acknowledged.ready_to_send) - - self.assertEqual(sender.outstanding_reliable_packet_count, 0) - self.assertEqual(len(delivered), 1) - self.assertEqual(delivered[0].payload, payload) - old_ack = receiver.create_ack_for_received(oldest_sequence) - self.assertEqual(old_ack.ack, oldest_sequence) - self.assertEqual(old_ack.ack_mask, 0) - - def test_cultnet_rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames(self) -> None: - server_socket = bind_udp_socket() - client_socket = bind_udp_socket() - connection_id = 0x10203040 - server = CultNetRudpSocketTransportConnection( - CultNetRudpSocketTransportOptions( - runtime_id="python-rudp-server", - socket=server_socket, - mode=CultNetRudpSocketMode.SERVER, - connection_id=connection_id, - initial_sequence=100, - resend_delay_ms=25, - ) - ) - client = CultNetRudpSocketTransportConnection( - CultNetRudpSocketTransportOptions( - runtime_id="python-rudp-client", - socket=client_socket, - mode=CultNetRudpSocketMode.CLIENT, - remote_addr=server_socket.getsockname(), - connection_id=connection_id, - initial_sequence=1, - resend_delay_ms=25, - ) - ) - - try: - client.connect(b"join") - pump_rudp_handshake(client, server) - self.assertTrue(client.connected) - self.assertTrue(server.connected) - - client.send("schema", b"client-state") - server_frame = receive_rudp_frame(server) - self.assertEqual(server_frame.channel_id, "schema") - self.assertEqual(server_frame.payload, b"client-state") - - server.send("schema", b"server-state") - client_frame = receive_rudp_frame(client) - self.assertEqual(client_frame.channel_id, "schema") - self.assertEqual(client_frame.payload, b"server-state") - self.assertEqual(server.profile["transports"][0]["protocol"], "rudp") - self.assertEqual(client.stats.frames_sent, 1) - self.assertEqual(server.stats.frames_received, 1) - finally: - client.close() - server.close() - - def test_cultnet_rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames(self) -> None: - server_socket = bind_udp_socket() - client_socket = bind_udp_socket() - connection_id = 0x10203041 - server = CultNetRudpSocketTransportConnection( - CultNetRudpSocketTransportOptions( - runtime_id="python-rudp-fragment-server", - socket=server_socket, - mode=CultNetRudpSocketMode.SERVER, - connection_id=connection_id, - initial_sequence=100, - resend_delay_ms=25, - max_fragment_bytes=8, - ) - ) - client = CultNetRudpSocketTransportConnection( - CultNetRudpSocketTransportOptions( - runtime_id="python-rudp-fragment-client", - socket=client_socket, - mode=CultNetRudpSocketMode.CLIENT, - remote_addr=server_socket.getsockname(), - connection_id=connection_id, - initial_sequence=1, - resend_delay_ms=25, - max_fragment_bytes=8, - ) - ) - - try: - payload = b"this-schema-frame-is-larger-than-one-rudp-fragment" - client.connect(b"join") - pump_rudp_handshake(client, server) - client.send("schema", payload) - server_frame = receive_rudp_frame(server) - self.assertEqual(server_frame.channel_id, "schema") - self.assertEqual(server_frame.payload, payload) - self.assertEqual(client.stats.frames_sent, 1) - self.assertEqual(server.stats.frames_received, 1) - finally: - client.close() - server.close() - - def test_cultmesh_facade_creates_rudp_client_from_peer_endpoint(self) -> None: - connection_id = 0x10203042 - server = CultMesh.create_rudp_server( - "python-cultmesh-rudp-server", - connection_id, - initial_sequence=100, - resend_delay_ms=25, - max_fragment_bytes=1024, - max_pending_reliable_packets=16, - ) - server_host, server_port = server.socket.getsockname()[:2] - endpoint = CultMesh.parse_rudp_endpoint(f"rudp://{server_host}:{server_port}") - self.assertEqual(endpoint.host, server_host) - self.assertEqual(endpoint.port, server_port) - peer = CultMeshPeerCard( - peer_id="python-cultmesh-rudp-server", - verse_id="local", - endpoints=(endpoint.uri,), - roles=("schema",), - authority_lease_id="lease:python-cultmesh-rudp-server", - ) - peers = CultMesh.create_peer_catalog() - leases = CultMesh.create_authority_lease_catalog() - peers.upsert(peer) - with self.assertRaisesRegex(ValueError, "No authorized RUDP peer"): - CultMesh.create_rudp_client_for_authorized_peer( - "python-cultmesh-rudp-client", - connection_id, - peers, - leases, - "local", - "schema", - resend_delay_ms=25, - max_fragment_bytes=1024, - max_pending_reliable_packets=16, - ) - leases.upsert( - CultMeshAuthorityLease( - lease_id="lease:python-cultmesh-rudp-server", - verse_id="local", - peer_id="python-cultmesh-rudp-server", - roles=("schema",), - valid_from=datetime.now(UTC) - timedelta(seconds=1), - expires_at=datetime.now(UTC) + timedelta(seconds=30), - issuer_runtime_id="python-authority", - ) - ) - client = CultMesh.create_rudp_client_for_authorized_peer( - "python-cultmesh-rudp-client", - connection_id, - peers, - leases, - "local", - "schema", - resend_delay_ms=25, - max_fragment_bytes=1024, - max_pending_reliable_packets=16, - ) - - try: - client.connect(b"join") - pump_rudp_handshake(client, server) - self.assertTrue(client.connected) - self.assertTrue(server.connected) - - client.send("schema", b"client-state") - server_frame = receive_rudp_frame(server) - self.assertEqual(server_frame.channel_id, "schema") - self.assertEqual(server_frame.payload, b"client-state") - self.assertEqual(server.profile["transports"][0]["protocol"], "rudp") - self.assertEqual(client.profile["transports"][0]["protocol"], "rudp") - finally: - client.close() - server.close() - - def test_cultnet_database_subscription_helpers_match_schema_v0_shape(self) -> None: - subscribe = database_subscribe( - message_id="sub-message", - subscription_id="sub-1", - schema_ids=["schema-a"], - record_keys=["record-a"], - include_snapshot=False, - ).to_wire() - self.assertEqual(subscribe["schemaVersion"], "cultnet.database_subscribe.v0") - self.assertEqual(subscribe["messageId"], "sub-message") - self.assertEqual(subscribe["subscriptionId"], "sub-1") - self.assertEqual(subscribe["schemaIds"], ["schema-a"]) - self.assertEqual(subscribe["recordKeys"], ["record-a"]) - self.assertFalse(subscribe["includeSnapshot"]) - - unsubscribe = database_unsubscribe(message_id="unsub-message", subscription_id="sub-1").to_wire() - self.assertEqual(unsubscribe["schemaVersion"], "cultnet.database_unsubscribe.v0") - self.assertEqual(unsubscribe["messageId"], "unsub-message") - self.assertEqual(unsubscribe["subscriptionId"], "sub-1") - - def test_cultnet_database_change_parses_put_and_delete_shapes(self) -> None: - put_change = CultNetDatabaseChange.from_wire({ - "schemaVersion": "cultnet.database_change_raw.v0", - "messageId": "change-put", - "subscriptionId": "sub-1", - "changeKind": "added", - "document": { - "schemaId": "schema-note", - "recordKey": "note:1", - "payload": b"payload", - }, - }) - self.assertEqual(put_change.schema_id, "schema-note") - self.assertEqual(put_change.record_key, "note:1") - self.assertIsInstance(put_change.raw_document, CultNetRawDocumentRecord) - self.assertEqual(put_change.raw_document.payload, b"payload") - self.assertEqual(put_change.to_wire()["document"]["recordKey"], "note:1") - - delete_change = CultNetDatabaseChange.from_wire({ - "schemaVersion": "cultnet.database_change_raw.v0", - "messageId": "change-delete", - "subscriptionId": "sub-1", - "changeKind": "removed", - "schemaId": "schema-note", - "recordKey": "note:1", - }) - self.assertIsNone(delete_change.document) - self.assertIsNone(delete_change.raw_document) - self.assertEqual(delete_change.schema_id, "schema-note") - self.assertEqual(delete_change.to_wire()["recordKey"], "note:1") - - def test_cultnet_raw_put_helper_carries_message_id(self) -> None: - put = document_put_raw( - message_id="put-1", - key="record-a", - schema_id="schema-a", - stored_at="2026-06-13T00:00:00Z", - payload=b"payload", - source_runtime_id="python-test", - shard_id="interop", - shard_epoch=1, - ).to_wire() - self.assertEqual(put["schemaVersion"], "cultnet.document_put_raw.v0") - self.assertEqual(put["messageId"], "put-1") - self.assertEqual(put["document"]["recordKey"], "record-a") - self.assertEqual(put["document"]["sourceRuntimeId"], "python-test") - self.assertEqual(put["shardId"], "interop") - self.assertEqual(put["shardEpoch"], 1) - - def test_cultnet_catalog_and_snapshot_helpers_accept_filters(self) -> None: - catalog = schema_catalog_request( - message_id="catalog-1", - include_schema_json=True, - schema_ids=["schema-a"], - kinds=["wire_message"], - ).to_wire() - self.assertEqual(catalog["schemaVersion"], "cultnet.schema_catalog_request.v0") - self.assertEqual(catalog["messageId"], "catalog-1") - self.assertTrue(catalog["includeSchemaJson"]) - self.assertEqual(catalog["schemaIds"], ["schema-a"]) - self.assertEqual(catalog["kinds"], ["wire_message"]) - - snapshot = snapshot_request( - message_id="snapshot-1", - schema_ids=["schema-a"], - record_keys=["record-a"], - shard_id="interop", - shard_epoch=1, - ).to_wire() - self.assertEqual(snapshot["schemaVersion"], "cultnet.snapshot_request.v0") - self.assertEqual(snapshot["messageId"], "snapshot-1") - self.assertEqual(snapshot["schemaIds"], ["schema-a"]) - self.assertEqual(snapshot["recordKeys"], ["record-a"]) - self.assertEqual(snapshot["shardId"], "interop") - self.assertEqual(snapshot["shardEpoch"], 1) - - def test_cultnet_raw_snapshot_response_filters_and_roundtrips_records(self) -> None: - response = CultNetRawSnapshotResponse.from_wire({ - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "snapshot-1", - "shardId": "notes", - "shardEpoch": 3, - "shardLogSequence": 8, - "documents": [ - CultNetRawDocumentRecord( - schema_id="schema-note", - record_key="note:1", - stored_at="2026-06-14T00:00:00Z", - payload=b"note", - source_runtime_id="python-test", - tags=("snapshot",), - ).to_wire(), - { - "schemaId": "schema-fact", - "recordKey": "fact:1", - "payloadEncoding": "messagepack", - "payload": b"fact", - }, - ], - }) - self.assertEqual(response.shard_id, "notes") - self.assertEqual(response.shard_epoch, 3) - self.assertEqual(response.shard_log_sequence, 8) - self.assertEqual(response.documents[0].source_runtime_id, "python-test") - self.assertEqual(response.filter(schema_ids=["schema-note"])[0].record_key, "note:1") - self.assertEqual(response.filter(record_keys=["fact:1"])[0].schema_id, "schema-fact") - self.assertEqual(response.to_wire()["documents"][0]["tags"], ["snapshot"]) - - def test_cultnet_raw_client_fetches_schema_snapshot_and_shard_reads(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - received_versions: list[str] = [] - ready = threading.Event() - server_error: list[BaseException] = [] - port_holder: list[int] = [] - - def serve_requests() -> None: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(4) - ready.set() - for _ in range(4): - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - request = msgpack.unpackb(read_frame(stream), raw=False) - received_versions.append(request["schemaVersion"]) - if request["schemaVersion"] == "cultnet.schema_catalog_request.v0": - self.assertEqual(request["kinds"], ["wire_message"]) - response = {"schemaVersion": "cultnet.schema_catalog_response.v0", "messageId": request["messageId"], "schemas": []} - elif request["schemaVersion"] == "cultnet.snapshot_request.v0": - self.assertEqual(request["shardId"], "interop") - response = {"schemaVersion": "cultnet.snapshot_response_raw.v0", "messageId": request["messageId"], "documents": []} - elif request["schemaVersion"] == "cultnet.shard_catalog_request.v0": - response = { - "schemaVersion": "cultnet.shard_catalog_response.v0", - "messageId": request["messageId"], - "shards": [{ - "shardId": "interop", - "ownerRuntimeId": "socket-test", - "epoch": 1, - "schemaIds": request["schemaIds"], - "keyPrefix": "record-", - "primaryEndpoints": ["cultnet://127.0.0.1:1"], - }], - } - elif request["schemaVersion"] == "cultnet.shard_log_request.v0": - self.assertEqual(request["afterSequence"], 7) - response = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": request["messageId"], - "shardId": request["shardId"], - "shardEpoch": request["shardEpoch"], - "entries": [], - "resyncRequired": False, - } - else: - raise AssertionError(f"unexpected request {request['schemaVersion']}") - write_frame(stream, msgpack.packb(response, use_bin_type=True)) - stream.flush() - except BaseException as error: - server_error.append(error) - ready.set() - - thread = threading.Thread(target=serve_requests, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - self.assertFalse(server_error) - - client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - self.assertEqual(client.fetch_schema_catalog(kinds=["wire_message"])["schemaVersion"], "cultnet.schema_catalog_response.v0") - typed_snapshot = client.fetch_snapshot_response(shard_id="interop", shard_epoch=1) - self.assertEqual(typed_snapshot.shard_id, None) - self.assertEqual(typed_snapshot.documents, ()) - shard_catalog = CultNetShardCatalog() - synced_shards = client.sync_shard_catalog(shard_catalog, schema_ids=["schema-a"]) - self.assertEqual(synced_shards[0].shard_id, "interop") - self.assertEqual(shard_catalog.get("interop"), synced_shards[0]) - shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=7) - self.assertEqual(shard_log.shard_id, "interop") - self.assertFalse(shard_log.resync_required) - self.assertEqual(shard_log.last_sequence, 0) - - thread.join(2.0) - self.assertFalse(server_error) - self.assertEqual(received_versions, [ - "cultnet.schema_catalog_request.v0", - "cultnet.snapshot_request.v0", - "cultnet.shard_catalog_request.v0", - "cultnet.shard_log_request.v0", - ]) - - def test_cultnet_raw_client_raises_typed_peer_error_response(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - ready = threading.Event() - port_holder: list[int] = [] - - def serve_error() -> None: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(1) - ready.set() - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - read_frame(stream) - write_frame(stream, msgpack.packb({ - "schemaVersion": "cultnet.error.v0", - "messageId": "error-1", - "error": "Snapshot document limit exceeded", - }, use_bin_type=True)) - stream.flush() - - thread = threading.Thread(target=serve_error, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - - client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised: - client.fetch_snapshot() - - self.assertEqual(raised.exception.peer_error, "Snapshot document limit exceeded") - self.assertEqual(raised.exception.response["schemaVersion"], "cultnet.error.v0") - thread.join(2.0) - - def test_cultnet_database_subscription_reads_snapshot_and_change(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - document = define_database_entry_type( - "sub.item", - [ - ("name", 0), - ("category", 1), - ("value", 2, 0), - ], - cls=Item, - ) - schema_id = document.catalog_entry().schema_id - ready = threading.Event() - server_error: list[BaseException] = [] - port_holder: list[int] = [] - received_versions: list[str] = [] - - def serve_subscription() -> None: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(1) - ready.set() - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - subscribe = msgpack.unpackb(read_frame(stream), raw=False) - received_versions.append(subscribe["schemaVersion"]) - self.assertEqual(subscribe["subscriptionId"], "sub-1") - snapshot = { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": subscribe["messageId"], - "documents": [], - } - write_frame(stream, msgpack.packb(snapshot, use_bin_type=True)) - stream.flush() - - put = msgpack.unpackb(read_frame(stream), raw=False) - received_versions.append(put["schemaVersion"]) - change = { - "schemaVersion": "cultnet.database_change_raw.v0", - "messageId": "change-1", - "subscriptionId": "sub-1", - "changeKind": "added", - "document": put["document"], - } - write_frame(stream, msgpack.packb(change, use_bin_type=True)) - stream.flush() - - unsubscribe = msgpack.unpackb(read_frame(stream), raw=False) - received_versions.append(unsubscribe["schemaVersion"]) - except BaseException as error: - server_error.append(error) - ready.set() - - thread = threading.Thread(target=serve_subscription, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - self.assertFalse(server_error) - - client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - put = document_put_raw( - message_id="put-sub", - key="item:sub", - schema_id=schema_id, - stored_at="2026-06-14T00:00:00Z", - payload=document.encode_payload(Item("orb", "gear", 8)), - ) - with client.subscribe_database(subscription_id="sub-1", schema_ids=[schema_id]) as subscription: - snapshot = subscription.read_next_snapshot_response() - subscription.send(put) - change = subscription.read_next_change() - - thread.join(2.0) - self.assertFalse(server_error) - self.assertIsInstance(snapshot, CultNetRawSnapshotResponse) - self.assertEqual(snapshot.message_id, "cultnet-python-subscribe") - self.assertEqual(snapshot.to_wire()["schemaVersion"], "cultnet.snapshot_response_raw.v0") - self.assertEqual(change.change_kind, "added") - self.assertEqual(change.record_key, "item:sub") - self.assertIsInstance(change.raw_document, CultNetRawDocumentRecord) - self.assertEqual(change.raw_document.record_key, "item:sub") - self.assertEqual(received_versions, [ - "cultnet.database_subscribe.v0", - "cultnet.document_put_raw.v0", - "cultnet.database_unsubscribe.v0", - ]) - - def test_cultnet_database_subscription_raises_typed_peer_error(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - ready = threading.Event() - port_holder: list[int] = [] - - def serve_subscription_error() -> None: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(1) - ready.set() - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - read_frame(stream) - write_frame(stream, msgpack.packb({ - "schemaVersion": "cultnet.error.v0", - "messageId": "subscription-error", - "error": "subscription rejected", - }, use_bin_type=True)) - stream.flush() - - thread = threading.Thread(target=serve_subscription_error, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - - client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - with client.subscribe_database(subscription_id="sub-error") as subscription: - with self.assertRaisesRegex(CultNetPeerError, "subscription rejected") as raised: - subscription.read_next() - self.assertEqual(raised.exception.response["messageId"], "subscription-error") - thread.join(2.0) - - def test_cultnet_replication_helpers_apply_raw_snapshot_and_shard_log(self) -> None: - document = define_database_entry_type( - "replica.item", - [ - ("name", 0), - ("category", 1), - ("value", 2, 0), - ], - cls=Item, - ) - cache = CultCache() - cache.register_document_type(document) - schema_id = document.catalog_entry().schema_id - - snapshot = { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "snapshot-1", - "documents": [ - { - "schemaId": schema_id, - "recordKey": "item:1", - "storedAt": "2026-06-13T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(Item("sword", "gear", 3)), - } - ], - } - applied_snapshot = apply_raw_snapshot(cache, [document], snapshot) - self.assertEqual(applied_snapshot[0].record_key, "item:1") - self.assertEqual(cache.get_required(document, "item:1").value, 3) - typed_snapshot = CultNetRawSnapshotResponse.from_wire(snapshot) - direct_applied = apply_raw_document_record(cache, schema_document_map([document]), typed_snapshot.documents[0]) - self.assertEqual(direct_applied.schema_id, schema_id) - self.assertEqual(direct_applied.record_key, "item:1") - typed_snapshot_applied = apply_raw_snapshot(cache, [document], typed_snapshot) - self.assertEqual(typed_snapshot_applied[0].record_key, "item:1") - - shard_log = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "log-1", - "shardId": "interop", - "shardEpoch": 1, - "resyncRequired": False, - "entries": [ - { - "sequence": 0, - "changeKind": "updated", - "put": { - "schemaVersion": "cultnet.document_put_raw.v0", - "messageId": "put-unknown", - "document": { - "schemaId": "replica.unknown.v1", - "recordKey": "unknown:1", - "storedAt": "2026-06-13T00:00:01Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(Item("ignored", "unknown", 0)), - }, - "shardId": "interop", - "shardEpoch": 1, - }, - }, - { - "sequence": 1, - "changeKind": "updated", - "put": { - "schemaVersion": "cultnet.document_put_raw.v0", - "messageId": "put-1", - "document": { - "schemaId": schema_id, - "recordKey": "item:1", - "storedAt": "2026-06-13T00:00:01Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(Item("sword", "gear", 12)), - }, - "shardId": "interop", - "shardEpoch": 1, - }, - }, - { - "sequence": 2, - "changeKind": "removed", - "delete": { - "schemaVersion": "cultnet.document_delete.v0", - "messageId": "delete-1", - "schemaId": schema_id, - "recordKey": "item:1", - "shardId": "interop", - "shardEpoch": 1, - }, - }, - ], - } - applied_log = apply_shard_log_response(cache, [document], shard_log) - self.assertEqual([change.change_kind for change in applied_log], ["updated", "removed"]) - self.assertIsNone(cache.get(document, "item:1")) - - cache.put(document, "item:1", Item("sword", "gear", 3)) - typed_shard_log = CultNetShardLogResponse.from_wire({ - **shard_log, - "entries": shard_log["entries"][1:], - }) - typed_applied_log = apply_shard_log_response(cache, [document], typed_shard_log) - self.assertEqual([change.change_kind for change in typed_applied_log], ["updated", "removed"]) - self.assertIsNone(cache.get(document, "item:1")) - - def test_cultnet_replication_helpers_recover_schema_stamped_raw_records(self) -> None: - document = define_database_entry_type( - "replica.runtime-policy", - [ - ("schema_version", 0), - ("name", 1), - ("value", 2), - ], - schema_id="replica.runtime-policy.current", - schema_name="replica.runtime_policy", - schema_version="replica.runtime_policy.v1", - ) - cache = CultCache() - cache.register_document_type(document) - stale_schema_id = "sha256:stale-replica-runtime-policy" - payload = document.encode_payload({ - "schema_version": "replica.runtime_policy.v1", - "name": "policy", - "value": "still synced", - }) - record = { - "schemaId": stale_schema_id, - "recordKey": "policy:1", - "storedAt": "2026-06-25T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": payload, - } - snapshot = { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "snapshot-stale", - "documents": [record], - } - - applied_snapshot = apply_raw_snapshot(cache, [document], snapshot) - local_schema_id = document.catalog_entry().schema_id - self.assertEqual(applied_snapshot[0].schema_id, local_schema_id) - self.assertEqual(cache.get_required(document, "policy:1")["value"], "still synced") - self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) - direct_applied = apply_raw_document_record(cache, schema_document_map([document]), record) - self.assertEqual(direct_applied.schema_id, local_schema_id) - self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) - - shard_log = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "log-stale", - "shardId": "interop", - "shardEpoch": 1, - "resyncRequired": False, - "entries": [ - { - "sequence": 0, - "changeKind": "updated", - "put": { - "schemaVersion": "cultnet.document_put_raw.v0", - "messageId": "put-stale", - "document": { - **record, - "payload": document.encode_payload({ - "schema_version": "replica.runtime_policy.v1", - "name": "policy", - "value": "updated through log", - }), - }, - "shardId": "interop", - "shardEpoch": 1, - }, - } - ], - } - - applied_log = apply_shard_log_response(cache, [document], shard_log) - self.assertEqual(applied_log[0].schema_id, local_schema_id) - self.assertEqual(cache.get_required(document, "policy:1")["value"], "updated through log") - self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) - - delete_log = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "log-delete-alias", - "shardId": "interop", - "shardEpoch": 1, - "resyncRequired": False, - "entries": [ - { - "sequence": 1, - "changeKind": "removed", - "delete": { - "schemaVersion": "cultnet.document_delete.v0", - "messageId": "delete-version-alias", - "schemaId": "replica.runtime_policy.v1", - "recordKey": "policy:1", - "shardId": "interop", - "shardEpoch": 1, - }, - } - ], - } - applied_delete = apply_shard_log_response(cache, [document], delete_log) - self.assertEqual(applied_delete[0].schema_id, local_schema_id) - self.assertEqual(applied_delete[0].change_kind, "removed") - self.assertIsNone(cache.get(document, "policy:1")) - - def test_cultmesh_node_applies_foreign_schema_snapshot_as_local_update(self) -> None: - document = define_database_entry_type( - "mesh.runtime-policy", - [ - ("schema_version", 0), - ("name", 1), - ("value", 2), - ], - schema_id="mesh.runtime-policy.current", - schema_name="mesh.runtime_policy", - schema_version="mesh.runtime_policy.v1", - ) - node = create_node(runtime_id="python-node") - node.register_document(document) - node.put(document, "policy:1", { - "schema_version": "mesh.runtime_policy.v1", - "name": "policy", - "value": "old", - }) - observed: list[CultMeshDatabaseChange] = [] - unsubscribe = node.database.watch_record(document, "policy:1", observed.append) - try: - applied = node.database.apply_snapshot_response({ - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "foreign-schema-snapshot", - "documents": [ - { - "schemaId": "sha256:foreign-mesh-runtime-policy", - "recordKey": "policy:1", - "storedAt": "2026-06-25T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload({ - "schema_version": "mesh.runtime_policy.v1", - "name": "policy", - "value": "new", - }), - }, - ], - }) - finally: - unsubscribe() - - local_schema_id = document.catalog_entry().schema_id - self.assertEqual(applied[0].schema_id, local_schema_id) - self.assertEqual(observed[0].schema_id, local_schema_id) - self.assertEqual(observed[0].change_kind, "updated") - self.assertEqual(observed[0].previous_value["value"], "old") - self.assertEqual(observed[0].value["value"], "new") - self.assertEqual(node.cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) - - def test_cultmesh_node_syncs_snapshot_and_shard_log_through_cultnet_client(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - document = define_database_entry_type( - "mesh.sync_item", - [ - ("name", 0), - ("category", 1), - ("value", 2, 0), - ], - cls=Item, - ) - node = create_node(runtime_id="python-node") - node.register_document(document) - schema_id = document.catalog_entry().schema_id - ready = threading.Event() - server_error: list[BaseException] = [] - port_holder: list[int] = [] - - def serve_requests() -> None: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(2) - ready.set() - for _ in range(2): - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - request = msgpack.unpackb(read_frame(stream), raw=False) - if request["schemaVersion"] == "cultnet.snapshot_request.v0": - response = { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": request["messageId"], - "documents": [ - { - "schemaId": schema_id, - "recordKey": "item:node", - "storedAt": "2026-06-13T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(Item("node", "mesh", 1)), - } - ], - } - elif request["schemaVersion"] == "cultnet.shard_log_request.v0": - response = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": request["messageId"], - "shardId": request["shardId"], - "shardEpoch": request["shardEpoch"], - "resyncRequired": False, - "entries": [ - { - "sequence": 1, - "changeKind": "updated", - "put": { - "schemaVersion": "cultnet.document_put_raw.v0", - "messageId": "put-node", - "document": { - "schemaId": schema_id, - "recordKey": "item:node", - "storedAt": "2026-06-13T00:00:01Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(Item("node", "mesh", 9)), - }, - "shardId": request["shardId"], - "shardEpoch": request["shardEpoch"], - }, - } - ], - } - else: - raise AssertionError(f"unexpected request {request['schemaVersion']}") - write_frame(stream, msgpack.packb(response, use_bin_type=True)) - stream.flush() - except BaseException as error: - server_error.append(error) - ready.set() - - thread = threading.Thread(target=serve_requests, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - self.assertFalse(server_error) - - client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - snapshot_changes = node.sync_snapshot(client, schema_ids=[schema_id]) - log_changes = node.sync_shard_log(client, shard_id="mesh", shard_epoch=1) - - thread.join(2.0) - self.assertFalse(server_error) - self.assertEqual(snapshot_changes[0].record_key, "item:node") - self.assertEqual(log_changes[0].change_kind, "updated") - self.assertEqual(node.get_required(document, "item:node").value, 9) - - def test_cultmesh_node_sync_document_returns_requested_alias_type(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.sync_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.sync_alias_note.v1", - schema_name="mesh.sync_alias_note", - schema_version="mesh.sync_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.sync_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.sync_alias_note.v1", - schema_name="mesh.sync_alias_note", - schema_version="mesh.sync_alias_note.v1", - ) - node = create_node(runtime_id="python-node") - node.register_document(document) - requests: list[dict[str, Any]] = [] - - class SnapshotClient: - def fetch_snapshot_response( - self, - *, - schema_ids: list[str] | None = None, - record_keys: list[str] | None = None, - shard_id: str | None = None, - shard_epoch: int | None = None, - ) -> dict[str, Any]: - requests.append({ - "schema_ids": schema_ids, - "record_keys": record_keys, - "shard_id": shard_id, - "shard_epoch": shard_epoch, - }) - return { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "sync-document", - "documents": [ - { - "schemaId": document.catalog_entry().schema_id, - "recordKey": "note:remote", - "storedAt": "2026-06-27T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(CanonicalNote("synced-alias")), - } - ], - } - - synced = node.sync_document(SnapshotClient(), alias, "note:remote") - facade_synced = CultMesh.sync_document(node, SnapshotClient(), alias, "note:remote") - - self.assertIsInstance(synced, UiNote) - self.assertEqual(synced.body, "synced-alias") - self.assertIsInstance(facade_synced, UiNote) - self.assertEqual(facade_synced.body, "synced-alias") - self.assertEqual(node.get_required(document, "note:remote").body, "synced-alias") - self.assertIsInstance(node.get_required(alias, "note:remote"), UiNote) - self.assertEqual(requests, [ - { - "schema_ids": ["mesh.sync_alias_note.v1"], - "record_keys": ["note:remote"], - "shard_id": None, - "shard_epoch": None, - }, - { - "schema_ids": ["mesh.sync_alias_note.v1"], - "record_keys": ["note:remote"], - "shard_id": None, - "shard_epoch": None, - }, - ]) - - def test_cultmesh_node_sync_document_from_publication_returns_requested_alias_type(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.publication_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.publication_alias_note.v1", - schema_name="mesh.publication_alias_note", - schema_version="mesh.publication_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.publication_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.publication_alias_note.v1", - schema_name="mesh.publication_alias_note", - schema_version="mesh.publication_alias_note.v1", - ) - node = create_node(runtime_id="python-publication-node") - node.register_document(document) - requests: list[dict[str, Any]] = [] - - class SnapshotClient: - def fetch_snapshot_response( - self, - *, - schema_ids: list[str] | None = None, - record_keys: list[str] | None = None, - shard_id: str | None = None, - shard_epoch: int | None = None, - ) -> dict[str, Any]: - requests.append({ - "schema_ids": schema_ids, - "record_keys": record_keys, - "shard_id": shard_id, - "shard_epoch": shard_epoch, - }) - return { - "schemaVersion": "cultnet.snapshot_response_raw.v0", - "messageId": "publication-sync-document", - "documents": [ - { - "schemaId": document.catalog_entry().schema_id, - "recordKey": "note:published-peer", - "storedAt": "2026-06-27T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": document.encode_payload(CanonicalNote("peer-publication-alias")), - } - ], - } - - source = CultMesh.publication_source_from_peer_snapshot( - SnapshotClient(), - shard_id="notes", - shard_epoch=3, - ) - - synced = node.sync_document_from_publication(source, alias, "note:published-peer") - facade_synced = CultMesh.sync_document_from_publication(node, source, alias, "note:published-peer") - - self.assertIsInstance(synced, UiNote) - self.assertEqual(synced.body, "peer-publication-alias") - self.assertIsInstance(facade_synced, UiNote) - self.assertEqual(facade_synced.body, "peer-publication-alias") - self.assertEqual(node.get_required(document, "note:published-peer").body, "peer-publication-alias") - self.assertEqual(requests, [ - { - "schema_ids": ["mesh.publication_alias_note.v1"], - "record_keys": ["note:published-peer"], - "shard_id": "notes", - "shard_epoch": 3, - }, - { - "schema_ids": ["mesh.publication_alias_note.v1"], - "record_keys": ["note:published-peer"], - "shard_id": "notes", - "shard_epoch": 3, - }, - ]) - - def test_cultmesh_node_sync_document_from_single_file_publication_uses_node_aliases(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.file_publication_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.file_publication_alias_note.v1", - schema_name="mesh.file_publication_alias_note", - schema_version="mesh.file_publication_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.file_publication_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.file_publication_alias_note.v1", - schema_name="mesh.file_publication_alias_note", - schema_version="mesh.file_publication_alias_note.v1", - ) - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / "publication.ccmp" - writer = CultCache() - writer.register_document_type(document) - writer.add_generic_store(SingleFileMessagePackBackingStore(path)) - writer.put(document, "note:published-file", CanonicalNote("file-publication-alias")) - - node = create_node(runtime_id="python-file-publication-node") - node.register_document(document) - source = CultMesh.publication_source_from_single_file(path) - - synced = CultMesh.sync_document_from_publication(node, source, alias, "note:published-file") - - self.assertIsInstance(synced, UiNote) - self.assertEqual(synced.body, "file-publication-alias") - self.assertEqual(node.get_required(document, "note:published-file").body, "file-publication-alias") - self.assertIsInstance(node.get_required(alias, "note:published-file"), UiNote) - - def test_cultmesh_node_sync_documents_from_publication_uses_binding_sources_and_aliases(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.catalog_publication_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.catalog_publication_alias_note.v1", - schema_name="mesh.catalog_publication_alias_note", - schema_version="mesh.catalog_publication_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.catalog_publication_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.catalog_publication_alias_note.v1", - schema_name="mesh.catalog_publication_alias_note", - schema_version="mesh.catalog_publication_alias_note.v1", - ) - with tempfile.TemporaryDirectory() as tmp: - first_path = Path(tmp) / "first.ccmp" - second_path = Path(tmp) / "second.ccmp" - first = CultCache() - first.register_document_type(document) - first.add_generic_store(SingleFileMessagePackBackingStore(first_path)) - first.put(document, "note:first", CanonicalNote("first catalog publication")) - second = CultCache() - second.register_document_type(document) - second.add_generic_store(SingleFileMessagePackBackingStore(second_path)) - second.put(document, "note:second", CanonicalNote("second catalog publication")) - - node = create_node(runtime_id="python-catalog-publication-node") - node.register_document(document) - source = CultMesh.publication_source_from_single_file(first_path) - bindings = [ - CultMesh.publication_document(alias, "note:first"), - CultMesh.publication_document( - alias, - "note:second", - source=CultMesh.publication_source_from_single_file(second_path), - ), - ] - - synced = node.sync_documents_from_publication(source, bindings) - facade_synced = CultMesh.sync_documents_from_publication(node, source, bindings) - - self.assertEqual([value.body for value in synced], [ - "first catalog publication", - "second catalog publication", - ]) - self.assertTrue(all(isinstance(value, UiNote) for value in synced)) - self.assertEqual([value.body for value in facade_synced], [ - "first catalog publication", - "second catalog publication", - ]) - self.assertEqual(node.get_required(document, "note:first").body, "first catalog publication") - self.assertEqual(node.get_required(document, "note:second").body, "second catalog publication") - self.assertIsInstance(node.get_required(alias, "note:second"), UiNote) - - def test_cultmesh_node_emits_raw_put_and_delete_messages_for_local_writes(self) -> None: - document = define_database_entry_type( - "mesh.emit_item", - [ - ("name", 0), - ("category", 1), - ("value", 2, 0), - ], - cls=Item, - ) - node = create_node(runtime_id="python-node") - node.register_document(document) - - put = node.put_raw_message( - document, - "item:emit", - Item("wand", "gear", 4), - message_id="put-emit", - shard_id="mesh", - shard_epoch=1, - ).to_wire() - self.assertEqual(node.get_required(document, "item:emit").name, "wand") - self.assertEqual(put["schemaVersion"], "cultnet.document_put_raw.v0") - self.assertEqual(put["messageId"], "put-emit") - self.assertEqual(put["document"]["schemaId"], document.catalog_entry().schema_id) - self.assertEqual(put["document"]["recordKey"], "item:emit") - self.assertEqual(put["document"]["sourceRuntimeId"], "python-node") - self.assertEqual(put["shardId"], "mesh") - self.assertEqual(put["shardEpoch"], 1) - - delete = node.delete_raw_message( - document, - "item:emit", - message_id="delete-emit", - shard_id="mesh", - shard_epoch=1, - ).to_wire() - self.assertIsNone(node.get(document, "item:emit")) - self.assertEqual(delete["schemaVersion"], "cultnet.document_delete.v0") - self.assertEqual(delete["messageId"], "delete-emit") - self.assertEqual(delete["schemaId"], document.catalog_entry().schema_id) - self.assertEqual(delete["recordKey"], "item:emit") - self.assertEqual(delete["shardId"], "mesh") - self.assertEqual(delete["shardEpoch"], 1) - - def test_cultmesh_node_resolves_same_schema_aliases_for_local_state(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.alias_note.v1", - schema_name="mesh.alias_note", - schema_version="mesh.alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.alias_note.v1", - schema_name="mesh.alias_note", - schema_version="mesh.alias_note.v1", - ) - node = create_node(runtime_id="python-node") - node.register_document(document) - seen: list[CultMeshDatabaseChange] = [] - all_seen: list[CultMeshDatabaseChange] = [] - node.database.watch(all_seen.append, document=alias) - node.database.watch_record(alias, "note:alias", seen.append) - - node.put(alias, "note:alias", UiNote("from-alias-put")) - self.assertEqual(node.get_required(document, "note:alias").body, "from-alias-put") - alias_value = node.get_required(alias, "note:alias") - self.assertIsInstance(alias_value, UiNote) - self.assertEqual(alias_value.body, "from-alias-put") - alias_collection = node.get_all(alias) - self.assertEqual(len(alias_collection), 1) - self.assertIsInstance(alias_collection[0], UiNote) - self.assertEqual(alias_collection[0].body, "from-alias-put") - self.assertEqual(seen[0].document.type, alias.type) - self.assertEqual(seen[0].schema_id, "mesh.alias_note.v1") - self.assertIsInstance(seen[0].value, UiNote) - self.assertEqual(seen[0].value.body, "from-alias-put") - self.assertEqual(all_seen[0].document.type, alias.type) - self.assertIsInstance(all_seen[0].value, UiNote) - self.assertEqual(all_seen[0].value.body, "from-alias-put") - - node.put(document, "note:alias", CanonicalNote("from-canonical-put")) - self.assertEqual(seen[1].document.type, alias.type) - self.assertIsInstance(seen[1].value, UiNote) - self.assertIsInstance(seen[1].previous_value, UiNote) - self.assertEqual(seen[1].value.body, "from-canonical-put") - self.assertEqual(seen[1].previous_value.body, "from-alias-put") - - put = node.put_raw_message( - alias, - "note:alias", - UiNote("from-alias-raw-put"), - message_id="alias-put", - ).to_wire() - self.assertEqual(put["document"]["schemaId"], "mesh.alias_note.v1") - self.assertEqual(put["document"]["recordKey"], "note:alias") - self.assertEqual(node.get_required(alias, "note:alias").body, "from-alias-raw-put") - - delete = node.delete_raw_message(alias, "note:alias", message_id="alias-delete").to_wire() - self.assertEqual(delete["schemaId"], "mesh.alias_note.v1") - self.assertIsNone(node.get(alias, "note:alias")) - self.assertEqual(node.get_all(alias), []) - - def test_cultmesh_reactive_document_coalesces_explicit_alias_updates(self) -> None: - @dataclass - class CanonicalNote: - body: str - revision: int - - @dataclass - class UiNote: - body: str - revision: int - - document = define_database_entry_type( - "mesh.reactive_alias_note", - [("body", 0), ("revision", 1)], - cls=CanonicalNote, - schema_id="mesh.reactive_alias_note.v1", - schema_name="mesh.reactive_alias_note", - schema_version="mesh.reactive_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.reactive_alias_note.ui", - [("body", 0), ("revision", 1)], - cls=UiNote, - schema_id="mesh.reactive_alias_note.v1", - schema_name="mesh.reactive_alias_note", - schema_version="mesh.reactive_alias_note.v1", - ) - node = create_node(runtime_id="python-reactive") - node.register_document(document) - node.put(document, "note:reactive", CanonicalNote("initial", 1)) - seen: list[CultMeshDatabaseChange] = [] - node.database.watch_record(alias, "note:reactive", seen.append) - - reactive = node.authoritative_writer(alias, "note:reactive").reactive( - CultMeshReactiveDocumentOptions(flush_delay_seconds=0.02) - ) - try: - self.assertIsInstance(reactive.snapshot, UiNote) - reactive.update(lambda note: setattr(note, "body", "first-local-edit")) - reactive.update(lambda note: setattr(note, "body", "second-local-edit")) - reactive.update(lambda note: setattr(note, "revision", 2)) - - self._wait_until(lambda: node.get_required(alias, "note:reactive").body == "second-local-edit") - - self.assertFalse(reactive.is_dirty) - self.assertEqual(node.get_required(document, "note:reactive").revision, 2) - self.assertEqual(seen[-1].value.body, "second-local-edit") - finally: - reactive.dispose() - - def test_cultmesh_reactive_document_tracks_canonical_reconciliation_delta(self) -> None: - @dataclass - class Note: - body: str - revision: int - - document = define_database_entry_type( - "mesh.reactive_reconcile_note", - [("body", 0), ("revision", 1)], - cls=Note, - schema_id="mesh.reactive_reconcile_note.v1", - schema_name="mesh.reactive_reconcile_note", - schema_version="mesh.reactive_reconcile_note.v1", - ) - node = create_node(runtime_id="python-reactive-reconcile") - node.register_document(document) - node.put(document, "note:reconcile", Note("initial", 1)) - - reactive = CultMesh.authoritative_writer( - node, - document, - "note:reconcile", - ).reactive(CultMeshReactiveDocumentOptions(flush_delay_seconds=60)) - try: - reactive.update(lambda note: setattr(note, "body", "local-prediction")) - reactive.update(lambda note: setattr(note, "revision", 2)) - - node.put(document, "note:reconcile", Note("authoritative", 7)) - - self.assertEqual(reactive.snapshot.body, "local-prediction") - self.assertIsNotNone(reactive.reconciliation) - self.assertEqual(reactive.reconciliation.canonical.body, "authoritative") - self.assertEqual(reactive.reconciliation.predicted.body, "local-prediction") - self.assertEqual(reactive.reconciliation.delta["body"], "local-prediction") - self.assertEqual(reactive.reconciliation.delta["revision"], -5) - - reactive.flush() - self.assertEqual(node.get_required(document, "note:reconcile").body, "local-prediction") - self.assertIsNone(reactive.reconciliation) - finally: - reactive.dispose() - - def test_cultmesh_reactive_document_is_idle_until_explicit_nested_update(self) -> None: - document = define_document_type( - "mesh.nested_loadout", - encode=lambda value: value, - decode=lambda value: value, - ) - node = create_node(runtime_id="python-reactive-nested") - node.register_document(document) - node.put( - document, - "ship:nested", - {"loadout": {"weapon": {"tuning": {"damage": 10}}}}, - ) - - with patch("cultmesh_py.node.threading.Timer") as timer: - reactive = node.authoritative_writer(document, "ship:nested").reactive() - timer.assert_not_called() - - try: - borrowed = reactive.snapshot - borrowed["loadout"]["weapon"]["tuning"]["damage"] = 99 - self.assertEqual( - node.get_required(document, "ship:nested")["loadout"]["weapon"]["tuning"]["damage"], - 10, - ) - - reactive.update( - lambda draft: draft["loadout"]["weapon"]["tuning"].__setitem__("damage", 12) - ) - reactive.flush() - - self.assertEqual(reactive.snapshot["loadout"]["weapon"]["tuning"]["damage"], 12) - self.assertEqual( - node.get_required(document, "ship:nested")["loadout"]["weapon"]["tuning"]["damage"], - 12, - ) - finally: - reactive.dispose() - - def test_cultmesh_reactive_scheduling_scales_with_changed_documents_only(self) -> None: - document = define_document_type( - "mesh.reactive_scale_note", - encode=lambda value: value, - decode=lambda value: value, - ) - - for document_count in (1, 100, 1000): - node = create_node(runtime_id=f"python-reactive-scale-{document_count}") - node.register_document(document) - for index in range(document_count): - node.put(document, f"note:{index}", {"body": "initial", "revision": 1}) - - scheduled_timers: list[Any] = [] - - class FakeScheduledCall: - def __init__(self, delay_seconds: float, callback: Callable[[], None]) -> None: - self.delay_seconds = delay_seconds - self.callback = callback - self.cancelled = False - - def cancel(self) -> None: - self.cancelled = True - - class FakeScheduler: - def schedule( - self, - delay_seconds: float, - callback: Callable[[], None], - ) -> FakeScheduledCall: - call = FakeScheduledCall(delay_seconds, callback) - scheduled_timers.append(call) - return call - - reactive_documents: list[Any] = [] - with patch("cultmesh_py.node._REACTIVE_SCHEDULER", FakeScheduler()): - try: - for index in range(document_count): - reactive_documents.append( - node.authoritative_writer(document, f"note:{index}").reactive() - ) - - self.assertEqual( - len(scheduled_timers), - 0, - f"{document_count} idle documents must schedule no timers", - ) - - changed_document_count = max(1, document_count // 100) - for index in range(changed_document_count): - reactive_documents[index].update( - lambda draft, changed=index: draft.update( - body=f"changed-{changed}", - revision=2, - ) - ) - - self.assertEqual(len(scheduled_timers), changed_document_count) - self.assertEqual( - sum(reactive.is_dirty for reactive in reactive_documents), - changed_document_count, - ) - finally: - for reactive in reactive_documents: - reactive.dispose() - - self.assertTrue(all(timer.cancelled for timer in scheduled_timers)) - - def test_cultmesh_reactive_documents_share_one_lazy_scheduler_thread(self) -> None: - document = define_document_type( - "mesh.reactive_shared_scheduler_note", - encode=lambda value: value, - decode=lambda value: value, - ) - node = create_node(runtime_id="python-reactive-shared-scheduler") - node.register_document(document) - reactive_documents: list[Any] = [] - try: - for index in range(100): - node.put(document, f"note:{index}", {"body": "initial", "revision": 1}) - reactive_documents.append( - node.authoritative_writer(document, f"note:{index}").reactive( - CultMeshReactiveDocumentOptions(flush_delay_seconds=60.0) - ) - ) - - before = sum( - thread.name == "cultmesh-reactive-scheduler" - for thread in threading.enumerate() - ) - for index, reactive in enumerate(reactive_documents): - reactive.update( - lambda draft, changed=index: draft.update( - body=f"changed-{changed}", - revision=2, - ) - ) - after = sum( - thread.name == "cultmesh-reactive-scheduler" - for thread in threading.enumerate() - ) - - self.assertLessEqual(before, 1) - self.assertEqual(after, 1) - finally: - for reactive in reactive_documents: - reactive.dispose() - - def test_cultmesh_observed_document_cannot_publish_borrowed_mutation(self) -> None: - document = define_document_type( - "mesh.observed_note", - encode=lambda value: value, - decode=lambda value: value, - ) - node = create_node(runtime_id="python-observed") - node.register_document(document) - node.put(document, "note:observed", {"body": "initial"}) - - observed = node.observe_document(document, "note:observed") - try: - observed.current["body"] = "borrowed-local-edit" - self.assertEqual(node.get_required(document, "note:observed")["body"], "initial") - - node.put(document, "note:observed", {"body": "canonical"}) - self.assertEqual(observed.current["body"], "canonical") - finally: - observed.dispose() - - def test_cultnet_document_delete_helper_matches_schema_v0_shape(self) -> None: - message = document_delete( - message_id="delete-1", - schema_id="schema-a", - record_key="record-a", - shard_id="interop", - shard_epoch=1, - ).to_wire() - self.assertEqual(message["schemaVersion"], "cultnet.document_delete.v0") - self.assertEqual(message["messageId"], "delete-1") - self.assertEqual(message["schemaId"], "schema-a") - self.assertEqual(message["recordKey"], "record-a") - self.assertEqual(message["shardId"], "interop") - self.assertEqual(message["shardEpoch"], 1) - - def test_cultnet_auth_session_helpers_match_schema_v0_shape(self) -> None: - self.assertEqual( - login(nonce="nonce", auth="encrypted-auth", password="encrypted-password").to_wire(), - { - "schemaVersion": "cultnet.login.v0", - "nonce": "nonce", - "auth": "encrypted-auth", - "password": "encrypted-password", - }, - ) - self.assertEqual( - register( - nonce="nonce", - email="encrypted-email", - password="encrypted-password", - name="encrypted-name", - ).to_wire(), - { - "schemaVersion": "cultnet.register.v0", - "nonce": "nonce", - "email": "encrypted-email", - "password": "encrypted-password", - "name": "encrypted-name", - }, - ) - self.assertEqual( - verify_session(nonce="nonce", session="encrypted-session").to_wire(), - { - "schemaVersion": "cultnet.verify.v0", - "nonce": "nonce", - "session": "encrypted-session", - }, - ) - self.assertEqual( - login_success(nonce="nonce", session="encrypted-session").to_wire(), - { - "schemaVersion": "cultnet.login_success.v0", - "nonce": "nonce", - "session": "encrypted-session", - }, - ) - - def test_cultnet_wire_catalog_describes_python_handled_messages(self) -> None: - descriptors = wire_message_schema_descriptors(include_schema_json=True) - by_version = {descriptor["schemaVersion"]: descriptor for descriptor in descriptors} - for schema_version in [ - "cultnet.error.v0", - "cultnet.login.v0", - "cultnet.register.v0", - "cultnet.verify.v0", - "cultnet.login_success.v0", - "cultnet.transport_profile.v0", - "cultnet.document_delete.v0", - "cultnet.database_change_raw.v0", - "cultnet.shard_log_response.v0", - "cultnet.simulation_consensus_candidate.v0", - "cultmesh.peer_exchange_response.v0", - ]: - self.assertIn(schema_version, by_version) - expected_kind = "shared_contract" if schema_version == "cultnet.transport_profile.v0" else "wire_message" - self.assertEqual(by_version[schema_version]["kind"], expected_kind) - self.assertIn("cultnet.schema.v0", by_version[schema_version]["wireContracts"]) - self.assertIn(schema_version, by_version[schema_version]["schemaJson"]) - self.assertEqual(len(by_version[schema_version]["contentHash"]), 64) - - delete_schema = json.loads(by_version["cultnet.document_delete.v0"]["schemaJson"]) - self.assertEqual( - delete_schema["required"], - ["schemaVersion", "messageId", "schemaId", "recordKey"], - ) - self.assertEqual(delete_schema["properties"]["recordKey"]["type"], "string") - - error_schema = json.loads(by_version["cultnet.error.v0"]["schemaJson"]) - self.assertEqual(error_schema["required"], ["schemaVersion", "error"]) - self.assertEqual(error_schema["properties"]["error"]["type"], "string") - self.assertIn("details", error_schema["properties"]) - - login_schema = json.loads(by_version["cultnet.login.v0"]["schemaJson"]) - self.assertEqual(login_schema["required"], ["schemaVersion", "nonce", "auth", "password"]) - self.assertEqual(login_schema["properties"]["auth"]["type"], "string") - - register_schema = json.loads(by_version["cultnet.register.v0"]["schemaJson"]) - self.assertEqual(register_schema["required"], ["schemaVersion", "nonce", "email", "password", "name"]) - self.assertEqual(register_schema["properties"]["email"]["type"], "string") - - verify_schema = json.loads(by_version["cultnet.verify.v0"]["schemaJson"]) - self.assertEqual(verify_schema["required"], ["schemaVersion", "nonce", "session"]) - self.assertEqual(verify_schema["properties"]["session"]["type"], "string") - - success_schema = json.loads(by_version["cultnet.login_success.v0"]["schemaJson"]) - self.assertEqual(success_schema["required"], ["schemaVersion", "nonce", "session"]) - - transport_profile_schema = json.loads(by_version["cultnet.transport_profile.v0"]["schemaJson"]) - self.assertEqual(transport_profile_schema["required"], ["schemaVersion", "runtimeId", "transports"]) - transport = transport_profile_schema["properties"]["transports"]["items"] - self.assertEqual(transport["properties"]["protocol"]["enum"], ["tcp_framed", "litenetlib", "websocket", "rudp"]) - self.assertEqual( - transport["properties"]["reconnectPolicy"]["properties"]["schemaVersion"]["const"], - "cultnet.reconnect_policy.v0", - ) - channel = transport["properties"]["channels"]["items"] - self.assertEqual(channel["properties"]["ordering"]["enum"], ["ordered", "unordered", "sequenced"]) - - change_schema = json.loads(by_version["cultnet.database_change_raw.v0"]["schemaJson"]) - self.assertEqual(change_schema["properties"]["changeKind"]["enum"], ["added", "updated", "removed"]) - self.assertIn("document", change_schema["properties"]) - self.assertIn("schemaId", change_schema["properties"]) - self.assertIn("recordKey", change_schema["properties"]) - - log_schema = json.loads(by_version["cultnet.shard_log_response.v0"]["schemaJson"]) - self.assertIn("entries", log_schema["required"]) - self.assertIn("resyncRequired", log_schema["required"]) - self.assertIn("compactedThrough", log_schema["properties"]) - - hello_schema = json.loads(by_version["cultnet.hello.v0"]["schemaJson"]) - mutation_contract = hello_schema["properties"]["supportedMutationContracts"]["items"] - self.assertEqual(mutation_contract["type"], "object") - self.assertIn("operations", mutation_contract["properties"]) - self.assertEqual( - hello_schema["properties"]["transportProfiles"]["items"]["$ref"], - "https://github.com/GameCult/cultnet-ts/contracts/cultnet.transport-profile.schema.json", - ) - - consensus_schema = json.loads(by_version["cultnet.simulation_consensus_candidate.v0"]["schemaJson"]) - for required_field in ["witnessCount", "supportWeight", "totalWeight", "hasQuorum", "confidence"]: - self.assertIn(required_field, consensus_schema["required"]) - - def test_cultnet_schema_catalog_applies_filters_and_responses(self) -> None: - catalog = wire_message_schema_catalog(include_schema_json=True) - descriptor = catalog.get("https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-put-raw.schema.json") - self.assertIsNotNone(descriptor) - assert descriptor is not None - self.assertEqual(descriptor.kind, "wire_message") - self.assertEqual(descriptor.schema_version, "cultnet.document_put_raw.v0") - self.assertIsNotNone(descriptor.schema_json) - - filtered = catalog.list(kinds=["wire_message"], schema_ids=[descriptor.schema_id]) - self.assertEqual(filtered, [descriptor]) - - response = catalog.create_response( - message_id="catalog-response", - include_schema_json=False, - schema_ids=[descriptor.schema_id], - kinds=["wire_message"], - ) - self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") - self.assertNotIn("schemaJson", response["schemas"][0]) - - remote = CultNetSchemaCatalog() - applied = remote.apply_response({ - "schemaVersion": "cultnet.schema_catalog_response.v0", - "messageId": "remote", - "schemas": [ - CultNetSchemaDescriptor( - schema_id="schema:custom", - kind="shared_contract", - schema_version="custom.v0", - wire_contracts=("cultnet.schema.v0",), - content_hash="hash", - schema_json="{}", - ).to_wire() - ], - }) - self.assertEqual(applied[0].schema_id, "schema:custom") - self.assertEqual(remote.get("schema:custom"), applied[0]) - - def test_cultnet_shard_helpers_match_schema_v0_shape(self) -> None: - catalog = shard_catalog_request( - message_id="catalog-1", - schema_ids=["schema-a"], - record_keys=["record-a"], - ).to_wire() - self.assertEqual(catalog["schemaVersion"], "cultnet.shard_catalog_request.v0") - self.assertEqual(catalog["messageId"], "catalog-1") - self.assertEqual(catalog["schemaIds"], ["schema-a"]) - self.assertEqual(catalog["recordKeys"], ["record-a"]) - - log = shard_log_request( - message_id="log-1", - shard_id="interop", - shard_epoch=7, - after_sequence=3, - limit=2, - ).to_wire() - self.assertEqual(log["schemaVersion"], "cultnet.shard_log_request.v0") - self.assertEqual(log["messageId"], "log-1") - self.assertEqual(log["shardId"], "interop") - self.assertEqual(log["shardEpoch"], 7) - self.assertEqual(log["afterSequence"], 3) - self.assertEqual(log["limit"], 2) - - def test_cultnet_shard_catalog_applies_filters_and_responses(self) -> None: - catalog = CultNetShardCatalog() - primary = catalog.upsert(CultNetShardDescriptor( - shard_id="notes", - owner_runtime_id="python-runtime", - epoch=2, - is_primary=True, - schema_ids=("schema-note.v1",), - key_prefix="note:", - primary_endpoints=("cultnet://127.0.0.1:3075",), - read_replica_endpoints=("cultnet://127.0.0.1:3075",), - region="local", - )) - catalog.upsert({ - "shardId": "facts", - "ownerRuntimeId": "python-runtime", - "epoch": 1, - "schemaIds": ["schema-fact"], - "keyPrefix": "fact:", - "primaryEndpoints": ["cultnet://127.0.0.1:3076"], - }) - - self.assertTrue(primary.serves(schema_id="schema-note.v1", record_key="note:1")) - self.assertTrue(primary.serves(schema_id="schema-note", record_key="note:1")) - self.assertFalse(primary.serves(schema_id="schema-note", record_key="fact:1")) - self.assertEqual(catalog.list(schema_ids=["schema-note.v1"]), [primary]) - self.assertEqual(catalog.list(schema_ids=["schema-note"]), [primary]) - self.assertEqual(catalog.list(record_keys=["note:1"]), [primary]) - - response = catalog.create_response( - message_id="shards", - schema_ids=["schema-note"], - record_keys=["note:1"], - ) - self.assertEqual(response["schemaVersion"], "cultnet.shard_catalog_response.v0") - self.assertEqual(response["shards"][0]["shardId"], "notes") - self.assertEqual(response["shards"][0]["ownerRuntimeId"], "python-runtime") - - remote = CultNetShardCatalog() - applied = remote.apply_response(response) - self.assertEqual(applied[0].shard_id, "notes") - self.assertEqual(remote.get("notes"), applied[0]) - - def test_cultnet_shard_log_response_tracks_cursor_and_resync_state(self) -> None: - response = CultNetShardLogResponse.from_wire({ - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "log", - "shardId": "notes", - "shardEpoch": 2, - "entries": [ - CultNetShardLogEntry( - sequence=2, - change_kind="updated", - put={"schemaVersion": "cultnet.document_put_raw.v0", "messageId": "put", "document": {"schemaId": "schema-note", "recordKey": "note:1", "payload": b"p"}}, - committed_at="2026-06-14T00:00:00Z", - ).to_wire(), - {"sequence": 3, "changeKind": "removed", "delete": {"schemaId": "schema-note", "recordKey": "note:1"}}, - ], - "resyncRequired": False, - }) - self.assertEqual(response.last_sequence, 3) - self.assertFalse(response.resync_required) - self.assertEqual(response.entries[0].put["document"]["recordKey"], "note:1") - self.assertIsInstance(response.entries[0].raw_document, CultNetRawDocumentRecord) - self.assertEqual(response.entries[0].raw_document.record_key, "note:1") - self.assertEqual(response.entries[1].delete_schema_id, "schema-note") - self.assertEqual(response.entries[1].delete_record_key, "note:1") - self.assertEqual(response.to_wire()["entries"][1]["delete"]["recordKey"], "note:1") - self.assertIs(response.require_usable(), response) - - resync = CultNetShardLogResponse.from_wire({ - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "log-resync", - "shardId": "notes", - "shardEpoch": 2, - "entries": [], - "resyncRequired": True, - "reason": "compacted", - "compactedThrough": 12, - }) - self.assertEqual(resync.last_sequence, 12) - with self.assertRaisesRegex(ValueError, "compacted"): - resync.require_usable() - - def test_cultnet_simulation_observation_helper_matches_schema_v0_shape(self) -> None: - claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit") - self.assertEqual(len(claim_hash), 64) - message = simulation_observation( - message_id="obs-1", - witness_runtime_id="python-test", - shard_id="interop", - shard_epoch=1, - frame=42, - subject_id="player-1", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="player-1 hit target-a", - observed_at="2026-06-13T00:00:02Z", - ).to_wire() - self.assertEqual(message["schemaVersion"], "cultnet.simulation_observation.v0") - self.assertEqual(message["messageId"], "obs-1") - self.assertEqual(message["observation"]["witnessRuntimeId"], "python-test") - self.assertEqual(message["observation"]["claimHash"], claim_hash) - self.assertEqual(message["observation"]["weight"], 1.0) - observation = CultNetSimulationObservation.from_wire(message) - self.assertEqual(observation.witness_runtime_id, "python-test") - self.assertEqual(observation.claim_summary, "player-1 hit target-a") - self.assertEqual(observation.to_message_wire(message_id="obs-2")["observation"], message["observation"]) - - def test_cultnet_witness_artifact_bundle_uses_csharp_slot_order(self) -> None: - import msgpack # type: ignore - - bundle = witness_artifact_bundle( - bundle_id="bundle-1", - witness_kind="interop-proof", - captured_at="2026-06-13T00:00:03Z", - subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"}, - contracts=[{"role": "payload", "schemaId": "schema-a"}], - artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], - timing_witnesses=[{"stage": "roundtrip", "startedAt": "2026-06-13T00:00:03Z", "completedAt": "2026-06-13T00:00:04Z", "latencyMs": 1.0}], - provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-test"}, - ) - payload = encode_witness_artifact_bundle_payload(bundle) - decoded = decode_witness_artifact_bundle_payload(payload) - typed = CultNetWitnessArtifactBundle.from_wire(bundle) - typed_payload = typed.to_payload() - typed_decoded = CultNetWitnessArtifactBundle.from_payload(typed_payload) - slots = msgpack.unpackb(payload, raw=False) - - self.assertEqual(payload, typed_payload) - self.assertEqual(slots[0], "bundle-1") - self.assertEqual(slots[1], "interop-proof") - self.assertEqual(slots[2], "2026-06-13T00:00:03Z") - self.assertEqual(slots[3]["subjectId"], "note:python") - self.assertEqual(slots[4][0]["schemaId"], "schema-a") - self.assertEqual(slots[5][0]["uri"], "cultcache://bundle-1/log") - self.assertEqual(slots[6][0]["latencyMs"], 1.0) - self.assertEqual(slots[7]["runtimeId"], "python-test") - self.assertEqual(decoded["bundleId"], "bundle-1") - self.assertEqual(decoded["witnessKind"], "interop-proof") - self.assertEqual(decoded["subject"]["subjectId"], "note:python") - self.assertEqual(decoded["contracts"][0]["schemaId"], "schema-a") - self.assertEqual(decoded["artifacts"][0]["mediaType"], "text/plain") - self.assertEqual(decoded["provenance"]["runtimeId"], "python-test") - self.assertEqual(typed.bundle_id, "bundle-1") - self.assertEqual(typed_decoded.to_wire(), bundle) - - def test_cultmesh_node_uses_cultcache_store(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - document = define_database_entry_type("mesh.note", [("body", 0)]) - store_path = Path(tmp) / "mesh.cc" - node = create_node(store_path, runtime_id="mesh-test") - self.assertIsInstance(node.database, CultMeshDatabase) - node.database.register_document(document) - node.database.put(document, "note:1", {"body": "hello"}) - self.assertEqual(node.get_required(document, "note:1")["body"], "hello") - - reopened = create_node(store_path, runtime_id="mesh-test") - reopened.database.register_document(document) - reopened.database.pull() - self.assertEqual(reopened.database.get(document, "note:1")["body"], "hello") - - def test_cultmesh_node_options_attach_default_durable_shard_log_store(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - document = define_database_entry_type( - "mesh.durable_option_note", - [("body", 0)], - schema_id="mesh.durable_option_note.v1", - ) - cache_path = Path(tmp) / "world.cc" - node = CultMesh.create_node( - cache_path, - runtime_id="durable-option-primary", - options=CultMeshNodeOptions(enable_durable_shard_logs=True), - ) - node.database.register_document(document) - node.database.put_raw_message(document, "note:1", {"body": "stored"}, shard_id="notes", shard_epoch=5) - - log_path = Path(tmp) / "world.cultmesh" / "shard-logs" - self.assertTrue(log_path.exists()) - - reopened = CultMesh.start_node( - cache_path, - runtime_id="durable-option-primary", - enable_durable_shard_logs=True, - ) - reopened.database.register_document(document) - response = reopened.database.build_shard_log_response(shard_id="notes", shard_epoch=5) - - self.assertEqual(reopened.database.shard_ids(), ["notes"]) - self.assertEqual([entry.sequence for entry in response.entries], [1]) - self.assertEqual(response.entries[0].raw_document.record_key, "note:1") - - def test_cultmesh_database_watchers_observe_local_changes_and_unsubscribe(self) -> None: - document = define_database_entry_type("mesh.watch", [("body", 0)]) - node = CultMesh.create_node(runtime_id="mesh-watch") - node.database.register_document(document) - changes: list[CultMeshDatabaseChange] = [] - all_changes: list[CultMeshDatabaseChange] = [] - - unsubscribe_record = node.database.watch_record(document, "note:1", changes.append) - unsubscribe_all = node.database.watch(all_changes.append, document=document) - - node.database.put(document, "note:1", {"body": "hello"}) - node.database.put(document, "note:2", {"body": "skip"}) - node.database.put(document, "note:1", {"body": "updated"}) - node.database.delete(document, "note:1") - unsubscribe_record() - node.database.put(document, "note:1", {"body": "after-unsubscribe"}) - unsubscribe_all() - - self.assertEqual([change.change_kind for change in changes], ["added", "updated", "removed"]) - self.assertEqual([change.record_key for change in changes], ["note:1", "note:1", "note:1"]) - self.assertEqual(changes[1].previous_value, {"body": "hello"}) - self.assertIsNone(changes[2].value) - self.assertEqual([change.record_key for change in all_changes], ["note:1", "note:2", "note:1", "note:1", "note:1"]) - - def test_cultmesh_database_register_document_is_idempotent(self) -> None: - document = define_database_entry_type("mesh.idempotent", [("body", 0)]) - conflicting = define_database_entry_type("mesh.idempotent", [("title", 0)]) - node = CultMesh.create_node(runtime_id="mesh-idempotent") - - node.database.register_document(document) - node.database.register_document(document) - node.database.put(document, "note:1", {"body": "still registered once"}) - - self.assertEqual(node.database.get_required(document, "note:1")["body"], "still registered once") - self.assertEqual([registered.type for registered in node.documents], ["mesh.idempotent"]) - with self.assertRaises(ValueError): - node.database.register_document(conflicting) - - def test_cultmesh_database_global_document_facade_uses_singleton_key_and_watchers(self) -> None: - document = define_database_entry_type( - "mesh.global_settings", - [("theme", 0)], - global_document=True, - ) - node = CultMesh.create_node(runtime_id="mesh-global") - node.database.register_document(document) - changes: list[CultMeshDatabaseChange] = [] - - node.database.watch_global(document, changes.append) - node.database.put_global(document, {"theme": "ash"}) - node.put_global(document, {"theme": "ember"}) - node.delete_global(document) - - self.assertIsNone(node.database.get_global(document)) - self.assertEqual([change.record_key for change in changes], [node.cache.GLOBAL_KEY] * 3) - self.assertEqual([change.change_kind for change in changes], ["added", "updated", "removed"]) - self.assertEqual(changes[1].previous_value, {"theme": "ash"}) - self.assertEqual(changes[2].previous_value, {"theme": "ember"}) - with self.assertRaises(Exception): - node.database.put(document, "settings:wrong", {"theme": "bad"}) - - def test_cultmesh_database_creates_raw_snapshot_response_from_envelopes(self) -> None: - document = define_database_entry_type( - "mesh.snapshot_note", - [("body", 0)], - schema_id="mesh.snapshot_note.v1", - ) - source = CultMesh.create_node(runtime_id="mesh-snapshot-source") - source.database.register_document(document) - source.database.put(document, "note:1", {"body": "include"}) - source.database.put(document, "note:2", {"body": "skip"}) - - typed_response = source.database.build_snapshot_response( - message_id="snapshot-1", - schema_ids=["mesh.snapshot_note.v1"], - record_keys=["note:1"], - shard_id="notes", - shard_epoch=4, - shard_log_sequence=7, - ) - response = source.database.create_snapshot_response( - message_id="snapshot-1", - schema_ids=["mesh.snapshot_note.v1"], - record_keys=["note:1"], - shard_id="notes", - shard_epoch=4, - shard_log_sequence=7, - ) - - self.assertIsInstance(typed_response, CultNetRawSnapshotResponse) - self.assertEqual(typed_response.message_id, "snapshot-1") - self.assertEqual(typed_response.documents[0].record_key, "note:1") - self.assertEqual(typed_response.to_wire(), response) - self.assertEqual(source.build_snapshot_response(schema_ids=["mesh.snapshot_note.v1"]).documents[0].record_key, "note:1") - self.assertEqual(response["schemaVersion"], "cultnet.snapshot_response_raw.v0") - self.assertEqual(response["messageId"], "snapshot-1") - self.assertEqual(response["shardId"], "notes") - self.assertEqual(response["shardEpoch"], 4) - self.assertEqual(response["shardLogSequence"], 7) - self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:1"]) - self.assertEqual(response["documents"][0]["schemaId"], "mesh.snapshot_note.v1") - self.assertEqual(response["documents"][0]["payloadEncoding"], "messagepack") - - target = CultMesh.create_node(runtime_id="mesh-snapshot-target") - target.database.register_document(document) - applied = target.database.apply_snapshot_response(response) - self.assertEqual([(record.schema_id, record.record_key) for record in applied], [("mesh.snapshot_note.v1", "note:1")]) - self.assertEqual(target.database.get_required(document, "note:1"), {"body": "include"}) - self.assertIsNone(target.database.get(document, "note:2")) - - typed_target = CultMesh.create_node(runtime_id="mesh-snapshot-typed-target") - typed_target.database.register_document(document) - typed_applied = typed_target.database.apply_snapshot_response(typed_response) - self.assertEqual([(record.schema_id, record.record_key) for record in typed_applied], [("mesh.snapshot_note.v1", "note:1")]) - self.assertEqual(typed_target.database.get_required(document, "note:1"), {"body": "include"}) - - def test_cultmesh_database_filters_snapshot_by_logged_shard_membership(self) -> None: - document = define_database_entry_type( - "mesh.sharded_snapshot_note", - [("body", 0)], - schema_id="mesh.sharded_snapshot_note.v1", - ) - source = CultMesh.create_node(runtime_id="mesh-sharded-snapshot-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:notes", {"body": "include"}, shard_id="notes", shard_epoch=2) - source.database.put_raw_message(document, "note:other", {"body": "skip"}, shard_id="other", shard_epoch=1) - - response = source.database.build_snapshot_response( - schema_ids=["mesh.sharded_snapshot_note.v1"], - shard_id="notes", - shard_epoch=2, - ) - - self.assertEqual(response.shard_id, "notes") - self.assertEqual(response.shard_epoch, 2) - self.assertEqual([record.record_key for record in response.documents], ["note:notes"]) - - def test_python_interop_peer_filters_shard_snapshot_by_logged_membership(self) -> None: - runtime_id = f"python-interop-test-{uuid4().hex}" - state = build_state( - runtime_id=runtime_id, - runtime_kind="python", - display_name="Python Interop Test", - agent_id="python-interop-test-agent", - schema_path=str(Path("packages/cultnet-ts/integration/contracts/cultnet.interop-note.schema.json")), - ) - binding = state.bindings["note"] - logged_value = { - "schemaVersion": "cultnet.interop_note.v0", - "documentId": "note:logged", - "authorRuntimeId": runtime_id, - "title": "Logged", - "body": "This record belongs to the shard log.", - "tags": ["interop", "logged"], - } - unlogged_value = { - **logged_value, - "documentId": "note:unlogged", - "title": "Unlogged", - "body": "This record is in cache but not in the shard log.", - } - state.cache.put(binding.document, "note:logged", logged_value) - state.cache.put(binding.document, "note:unlogged", unlogged_value) - logged_record = { - "schemaId": state.note_schema_id, - "recordKey": "note:logged", - "storedAt": "2026-06-14T00:00:00Z", - "payloadEncoding": "messagepack", - "payload": binding.document.encode_payload(logged_value), - } - append_shard_log_put( - state, - {"messageId": "logged-put"}, - logged_record, - ) - - response = raw_snapshot_response( - state, - { - "schemaVersion": "cultnet.snapshot_request.v0", - "messageId": "interop-shard-snapshot", - "schemaIds": [state.note_schema_id], - "shardId": state.shard_id, - }, - ) - - self.assertEqual(response["shardId"], state.shard_id) - self.assertEqual(response["shardLogSequence"], 1) - self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:logged"]) - - def test_cultmesh_database_creates_shard_log_response_from_raw_mutations(self) -> None: - document = define_database_entry_type( - "mesh.log_note", - [("body", 0)], - schema_id="mesh.log_note.v1", - ) - source = CultMesh.create_node(runtime_id="mesh-log-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:1", {"body": "first"}, shard_id="notes", shard_epoch=3) - source.database.put_raw_message(document, "note:1", {"body": "second"}, shard_id="notes", shard_epoch=3) - source.database.put_raw_message(document, "note:2", {"body": "other"}, shard_id="other", shard_epoch=1) - source.database.delete_raw_message(document, "note:1", shard_id="notes", shard_epoch=3) - - self.assertEqual(source.database.shard_ids(), ["notes", "other"]) - self.assertEqual(source.database.shard_schema_ids("notes"), ["mesh.log_note.v1"]) - - typed_response = source.database.build_shard_log_response( - message_id="log-1", - shard_id="notes", - shard_epoch=3, - after_sequence=1, - limit=2, - ) - response = source.database.create_shard_log_response( - message_id="log-1", - shard_id="notes", - shard_epoch=3, - after_sequence=1, - limit=2, - ) - - self.assertIsInstance(typed_response, CultNetShardLogResponse) - self.assertEqual(typed_response.message_id, "log-1") - self.assertEqual([entry.sequence for entry in typed_response.entries], [2, 3]) - self.assertEqual(typed_response.to_wire(), response) - self.assertEqual([entry.sequence for entry in source.build_shard_log_response(shard_id="notes").entries], [1, 2, 3]) - self.assertEqual(response["schemaVersion"], "cultnet.shard_log_response.v0") - self.assertEqual(response["messageId"], "log-1") - self.assertEqual(response["shardId"], "notes") - self.assertEqual(response["shardEpoch"], 3) - self.assertFalse(response["resyncRequired"]) - self.assertEqual([entry["sequence"] for entry in response["entries"]], [2, 3]) - self.assertEqual([entry["changeKind"] for entry in response["entries"]], ["updated", "removed"]) - self.assertEqual(response["entries"][0]["put"]["document"]["recordKey"], "note:1") - self.assertEqual(response["entries"][1]["delete"]["recordKey"], "note:1") - empty = source.database.create_shard_log_response(shard_id="missing") - self.assertEqual(empty["shardEpoch"], 0) - self.assertEqual(empty["entries"], []) - self.assertTrue(empty["resyncRequired"]) - self.assertEqual(empty["reason"], "unknown_shard") - stale = source.database.create_shard_log_response(shard_id="notes", shard_epoch=2) - self.assertEqual(stale["shardEpoch"], 3) - self.assertEqual(stale["entries"], []) - self.assertTrue(stale["resyncRequired"]) - self.assertEqual(stale["reason"], "stale_epoch") - with self.assertRaises(ValueError): - source.database.create_shard_log_response(shard_id="notes", after_sequence=-1) - with self.assertRaises(ValueError): - source.database.create_shard_log_response(shard_id="notes", limit=-1) - - target = CultMesh.create_node(runtime_id="mesh-log-target") - target.database.register_document(document) - target.database.put(document, "note:1", {"body": "first"}) - applied = target.database.apply_shard_log_response(response) - self.assertEqual([record.change_kind for record in applied], ["updated", "removed"]) - self.assertIsNone(target.database.get(document, "note:1")) - self.assertIsNone(target.database.get(document, "note:2")) - - typed_target = CultMesh.create_node(runtime_id="mesh-log-typed-target") - typed_target.database.register_document(document) - typed_target.database.put(document, "note:1", {"body": "first"}) - typed_applied = typed_target.database.apply_shard_log_response(typed_response) - self.assertEqual([record.change_kind for record in typed_applied], ["updated", "removed"]) - self.assertIsNone(typed_target.database.get(document, "note:1")) - - def test_cultnet_file_shard_mutation_log_store_persists_authoritative_entries(self) -> None: - document = define_database_entry_type( - "mesh.durable_log_note", - [("body", 0)], - schema_id="mesh.durable_log_note.v1", - ) - with tempfile.TemporaryDirectory() as tmp: - store: CultNetShardMutationLogStore = CultNetFileShardMutationLogStore(Path(tmp) / "logs") - source = CultMesh.create_node(runtime_id="durable-primary") - source.database.register_document(document) - source.database.use_shard_mutation_log_store(store) - source.database.put_raw_message(document, "note:1", {"body": "first"}, shard_id="durable", shard_epoch=4) - source.database.put_raw_message(document, "note:2", {"body": "second"}, shard_id="durable", shard_epoch=4) - - reopened = CultMesh.create_node(runtime_id="durable-primary") - reopened.database.register_document(document) - reopened.database.use_shard_mutation_log_store(CultNetFileShardMutationLogStore(Path(tmp) / "logs")) - response = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4) - self.assertEqual(reopened.database.shard_ids(), ["durable"]) - self.assertEqual(reopened.database.shard_schema_ids("durable"), ["mesh.durable_log_note.v1"]) - self.assertEqual([entry.sequence for entry in response.entries], [1, 2]) - self.assertEqual(response.entries[0].raw_document.record_key, "note:1") - - store.compact_through("durable", 1) - compacted = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4, after_sequence=0) - retained = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4, after_sequence=1) - self.assertTrue(compacted.resync_required) - self.assertEqual(compacted.reason, "compacted_history") - self.assertEqual(compacted.compacted_through, 1) - self.assertEqual([entry.sequence for entry in retained.entries], [2]) - - def test_cultnet_shard_replicator_pulls_logs_and_resyncs_from_snapshot(self) -> None: - document = define_database_entry_type( - "mesh.replica_note", - [("body", 0)], - schema_id="mesh.replica_note.v1", - ) - source = CultMesh.create_node(runtime_id="primary") - source.database.register_document(document) - source.database.put_raw_message(document, "note:1", {"body": "one"}, shard_id="notes", shard_epoch=3) - source.database.put_raw_message(document, "note:2", {"body": "two"}, shard_id="notes", shard_epoch=3) - with tempfile.TemporaryDirectory() as tmp: - cursor_path = Path(tmp) / "replica-cursors.msgpack" - server = CultMesh.serve_node(source) - try: - shard = CultNetShardDescriptor( - shard_id="notes", - owner_runtime_id="primary", - epoch=3, - is_primary=False, - schema_ids=("mesh.replica_note.v1",), - primary_endpoints=(f"cultnet://127.0.0.1:{server.port}",), - ) - target = CultMesh.create_node(runtime_id="replica") - target.database.register_document(document) - cursor_store = CultNetFileShardReplicaCursorStore(cursor_path) - replicator = CultNetShardReplicator( - target.database, - CultNetShardReplicatorOptions( - fetcher=CultNetSchemaShardLogFetcher(timeout_seconds=2.0), - snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(timeout_seconds=2.0), - cursor_store=cursor_store, - batch_size=16, - ), - ) - - first_sequence = replicator.pull_once(shard) - restarted_replicator = CultNetShardReplicator( - target.database, - CultNetShardReplicatorOptions( - fetcher=CultNetSchemaShardLogFetcher(timeout_seconds=2.0), - snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(timeout_seconds=2.0), - cursor_store=CultNetFileShardReplicaCursorStore(cursor_path), - batch_size=16, - ), - ) - second_sequence = restarted_replicator.pull_once(shard) - - self.assertEqual(first_sequence, 2) - self.assertEqual(second_sequence, 2) - self.assertEqual(target.get_required(document, "note:1")["body"], "one") - self.assertEqual(target.get_required(document, "note:2")["body"], "two") - cursor = CultNetFileShardReplicaCursorStore(cursor_path).read("notes") - self.assertIsNotNone(cursor) - assert cursor is not None - self.assertEqual(cursor.last_applied_sequence, 2) - self.assertEqual(cursor.shard_epoch, 3) - finally: - server.stop() - - resync_target = CultMesh.create_node(runtime_id="resync-replica") - resync_target.database.register_document(document) - snapshot = source.database.build_snapshot_response(shard_id="notes", shard_epoch=3, shard_log_sequence=2) - - class CompactingFetcher: - def fetch(self, shard: CultNetShardDescriptor, *, after_sequence: int, limit: int | None = None) -> CultNetShardLogResponse: - return CultNetShardLogResponse( - message_id="compact", - shard_id=shard.shard_id, - shard_epoch=shard.epoch, - entries=(), - resync_required=True, - reason="compacted_history", - compacted_through=after_sequence, - ) - - class SnapshotFetcher: - def fetch(self, shard: CultNetShardDescriptor) -> CultNetRawSnapshotResponse: - return snapshot - - resync_replicator = CultNetShardReplicator( - resync_target.database, - CultNetShardReplicatorOptions( - fetcher=CompactingFetcher(), - snapshot_fetcher=SnapshotFetcher(), - cursor_store=CultNetInMemoryShardReplicaCursorStore(), - ), - ) - resync_sequence = resync_replicator.pull_once(CultNetShardDescriptor( - shard_id="notes", - owner_runtime_id="primary", - epoch=3, - is_primary=False, - schema_ids=("mesh.replica_note.v1",), - primary_endpoints=("cultnet://primary:3075",), - )) - - self.assertEqual(resync_sequence, 2) - self.assertEqual(resync_target.get_required(document, "note:1")["body"], "one") - - def test_cultnet_shard_replicator_background_loop_polls_non_primary_shards(self) -> None: - document = define_database_entry_type( - "mesh.background_replica_note", - [("body", 0)], - schema_id="mesh.background_replica_note.v1", - ) - source = CultMesh.create_node(runtime_id="background-primary") - source.database.register_document(document) - target = CultMesh.create_node(runtime_id="background-replica") - target.database.register_document(document) - cursor_store = CultNetInMemoryShardReplicaCursorStore() - errors: list[Exception] = [] - - class FlakySourceFetcher: - def __init__(self) -> None: - self.calls = 0 - - def fetch(self, shard: CultNetShardDescriptor, *, after_sequence: int, limit: int | None = None) -> CultNetShardLogResponse: - self.calls += 1 - if self.calls == 1: - raise RuntimeError("transient fetch failure") - return source.database.build_shard_log_response( - shard_id=shard.shard_id, - shard_epoch=shard.epoch, - after_sequence=after_sequence, - limit=limit, - ) - - fetcher = FlakySourceFetcher() - shard = CultNetShardDescriptor( - shard_id="background", - owner_runtime_id="background-primary", - epoch=7, - is_primary=False, - schema_ids=("mesh.background_replica_note.v1",), - primary_endpoints=("cultnet://127.0.0.1:3075",), - ) - replicator = CultNetShardReplicator( - target.database, - CultNetShardReplicatorOptions( - fetcher=fetcher, - cursor_store=cursor_store, - batch_size=1, - poll_interval_seconds=0.01, - on_error=errors.append, - ), - ) - try: - source.database.put_raw_message(document, "note:1", {"body": "one"}, shard_id="background", shard_epoch=7) - replicator.start([shard]) - self._wait_until(lambda: target.database.get(document, "note:1") is not None) - source.database.put_raw_message(document, "note:2", {"body": "two"}, shard_id="background", shard_epoch=7) - self._wait_until(lambda: target.database.get(document, "note:2") is not None) - finally: - replicator.stop() - - cursor = cursor_store.read("background") - self.assertEqual(target.get_required(document, "note:1")["body"], "one") - self.assertEqual(target.get_required(document, "note:2")["body"], "two") - self.assertIsNotNone(cursor) - assert cursor is not None - self.assertEqual(cursor.last_applied_sequence, 2) - self.assertEqual(cursor.shard_epoch, 7) - self.assertEqual(len(errors), 1) - self.assertEqual(str(errors[0]), "transient fetch failure") - - def test_cultnet_schema_write_forwarder_sends_raw_writes_to_primary(self) -> None: - document = define_database_entry_type( - "mesh.forward_note", - [("body", 0)], - schema_id="mesh.forward_note.v1", - ) - primary = CultMesh.create_node(runtime_id="forward-primary") - primary.database.register_document(document) - server = CultMesh.serve_node(primary) - try: - shard = CultNetShardDescriptor( - shard_id="forward", - owner_runtime_id="forward-primary", - epoch=9, - is_primary=False, - schema_ids=("mesh.forward_note.v1",), - primary_endpoints=(f"cultnet://127.0.0.1:{server.port}",), - ) - forwarder: CultNetShardWriteForwarder = CultNetSchemaWriteForwarder(timeout_seconds=2.0) - put = document_put_raw( - message_id="forward-put", - key="note:forward", - schema_id=document.catalog_entry().schema_id, - stored_at="2026-06-14T00:00:00Z", - payload=document.encode_payload({"body": "forwarded"}), - ).to_wire() - delete = document_delete( - message_id="forward-delete", - schema_id=document.catalog_entry().schema_id, - record_key="note:forward", - ).to_wire() - - forwarder.forward_put(shard, put) - self._eventually(lambda: primary.database.get(document, "note:forward") is not None) - self.assertEqual(primary.database.get_required(document, "note:forward")["body"], "forwarded") - forwarder.forward_delete(shard, delete) - self._eventually(lambda: primary.database.get(document, "note:forward") is None) - - log = primary.database.build_shard_log_response(shard_id="forward", shard_epoch=9) - self.assertIsNone(primary.database.get(document, "note:forward")) - self.assertEqual([entry.change_kind for entry in log.entries], ["added", "removed"]) - self.assertEqual(log.entries[0].put["shardId"], "forward") - self.assertEqual(log.entries[1].delete["shardEpoch"], 9) - finally: - server.stop() - - def _eventually(self, predicate: Callable[[], bool], *, timeout_seconds: float = 2.0) -> None: - deadline = time.monotonic() + timeout_seconds - while time.monotonic() < deadline: - if predicate(): - return - time.sleep(0.01) - self.assertTrue(predicate()) - - def test_cultmesh_database_watchers_observe_name_and_index_changes(self) -> None: - document = define_database_entry_type( - "mesh.named_watch", - [("name", 0), ("kind", 1)], - name="name", - indexes={"kind": "kind"}, - ) - node = CultMesh.create_node(runtime_id="mesh-named-watch") - node.database.register_document(document) - name_changes: list[CultMeshDatabaseChange] = [] - index_changes: list[CultMeshDatabaseChange] = [] - - node.database.watch_by_name(document, "Potion", name_changes.append) - node.database.watch_by_index(document, "kind", "consumable", index_changes.append) - - node.database.put(document, "item:1", {"name": "Potion", "kind": "consumable"}) - node.database.put(document, "item:2", {"name": "Sword", "kind": "weapon"}) - node.database.put(document, "item:1", {"name": "Elixir", "kind": "rare"}) - node.database.delete(document, "item:1") - - self.assertEqual([change.change_kind for change in name_changes], ["added", "updated"]) - self.assertEqual([change.record_key for change in name_changes], ["item:1", "item:1"]) - self.assertEqual(name_changes[1].previous_value, {"name": "Potion", "kind": "consumable"}) - self.assertEqual([change.change_kind for change in index_changes], ["added", "updated"]) - self.assertEqual([change.record_key for change in index_changes], ["item:1", "item:1"]) - self.assertEqual(index_changes[1].value, {"name": "Elixir", "kind": "rare"}) - - def test_cultmesh_database_collection_reads_resolve_same_schema_aliases(self) -> None: - @dataclass - class CanonicalItem: - name: str - kind: str - - @dataclass - class UiItem: - name: str - kind: str - - document = define_database_entry_type( - "mesh.alias_collection_item", - [("name", 0), ("kind", 1)], - cls=CanonicalItem, - name="name", - indexes={"kind": "kind"}, - schema_id="mesh.alias_collection_item.v1", - schema_name="mesh.alias_collection_item", - schema_version="mesh.alias_collection_item.v1", - ) - alias = define_database_entry_type( - "mesh.alias_collection_item.ui", - [("name", 0), ("kind", 1)], - cls=UiItem, - name="name", - indexes={"kind": "kind"}, - schema_id="mesh.alias_collection_item.v1", - schema_name="mesh.alias_collection_item", - schema_version="mesh.alias_collection_item.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-collection") - node.database.register_document(document) - - node.database.put(alias, "item:potion", UiItem("Potion", "consumable")) - node.database.put(document, "item:sword", CanonicalItem("Sword", "weapon")) - - all_items = node.database.get_all(alias) - potion = node.database.get_by_name(alias, "Potion") - sword = node.get_by_index(alias, "kind", "weapon") - - self.assertEqual([item.name for item in all_items], ["Potion", "Sword"]) - self.assertTrue(all(isinstance(item, UiItem) for item in all_items)) - self.assertIsInstance(potion, UiItem) - self.assertEqual(potion.kind, "consumable") - self.assertIsInstance(sword, UiItem) - self.assertEqual(sword.name, "Sword") - self.assertIsNone(node.database.get_by_name(alias, "Missing")) - - def test_cultmesh_database_alias_collection_watches_emit_alias_values(self) -> None: - @dataclass - class CanonicalItem: - name: str - kind: str - - @dataclass - class UiItem: - name: str - kind: str - - document = define_database_entry_type( - "mesh.alias_collection_watch_item", - [("name", 0), ("kind", 1)], - cls=CanonicalItem, - name="name", - indexes={"kind": "kind"}, - schema_id="mesh.alias_collection_watch_item.v1", - schema_name="mesh.alias_collection_watch_item", - schema_version="mesh.alias_collection_watch_item.v1", - ) - alias = define_database_entry_type( - "mesh.alias_collection_watch_item.ui", - [("name", 0), ("kind", 1)], - cls=UiItem, - name="name", - indexes={"kind": "kind"}, - schema_id="mesh.alias_collection_watch_item.v1", - schema_name="mesh.alias_collection_watch_item", - schema_version="mesh.alias_collection_watch_item.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-collection-watch") - node.database.register_document(document) - name_changes: list[CultMeshDatabaseChange] = [] - index_changes: list[CultMeshDatabaseChange] = [] - - node.database.watch_by_name(alias, "Potion", name_changes.append) - node.database.watch_by_index(alias, "kind", "consumable", index_changes.append) - - node.database.put(document, "item:potion", CanonicalItem("Potion", "consumable")) - node.database.put(document, "item:potion", CanonicalItem("Elixir", "rare")) - - self.assertEqual([change.change_kind for change in name_changes], ["added", "updated"]) - self.assertTrue(all(change.document.type == alias.type for change in name_changes)) - self.assertIsInstance(name_changes[0].value, UiItem) - self.assertIsInstance(name_changes[1].value, UiItem) - self.assertIsInstance(name_changes[1].previous_value, UiItem) - self.assertEqual(name_changes[1].previous_value.name, "Potion") - self.assertEqual(name_changes[1].value.name, "Elixir") - self.assertEqual([change.change_kind for change in index_changes], ["added", "updated"]) - self.assertTrue(all(change.document.type == alias.type for change in index_changes)) - self.assertIsInstance(index_changes[0].value, UiItem) - self.assertIsInstance(index_changes[1].previous_value, UiItem) - - def test_cultmesh_database_watch_by_name_and_index_validate_lookup_shape(self) -> None: - document = define_database_entry_type("mesh.unnamed_watch", [("body", 0)]) - node = CultMesh.create_node(runtime_id="mesh-watch-validation") - node.database.register_document(document) - - with self.assertRaises(ValueError): - node.database.watch_by_name(document, "missing", lambda _: None) - with self.assertRaises(ValueError): - node.database.watch_by_index(document, "missing", "value", lambda _: None) - with self.assertRaises(ValueError): - node.database.watch_by_index(document, "", "value", lambda _: None) - with self.assertRaises(ValueError): - node.database.watch_by_index(document, "missing", "", lambda _: None) - - def test_cultmesh_facade_matches_peer_runtime_entrypoints(self) -> None: - with tempfile.TemporaryDirectory() as tmp: - store_path = Path(tmp) / "mesh.cc" - node = CultMesh.start_node(store_path, runtime_id="mesh-facade") - - self.assertEqual(node.runtime_id, "mesh-facade") - self.assertIsInstance(CultMesh.create_node(), type(node)) - self.assertIsInstance(CultMesh.create_verse_catalog(), CultMeshVerseCatalog) - self.assertIsInstance(CultMesh.create_peer_catalog(), CultMeshPeerCatalog) - self.assertIsInstance(CultMesh.create_authority_lease_catalog(), CultMeshAuthorityLeaseCatalog) - self.assertIsInstance(CultMesh.create_stream_catalog(), CultMeshStreamCatalog) - self.assertIsInstance(CultMesh.create_schema_catalog(), CultNetSchemaCatalog) - self.assertIsInstance(CultMesh.create_builtin_schema_catalog(), CultNetSchemaCatalog) - self.assertIsInstance(CultMesh.create_shard_catalog(), CultNetShardCatalog) - builtins = CultMesh.create_builtin_schema_catalog( - include_schema_json=False, - kinds=["shared_contract"], - ) - self.assertEqual( - builtins.list()[0].schema_id, - "https://github.com/GameCult/cultnet-ts/contracts/cultnet.transport-profile.schema.json", - ) - self.assertIsNone(builtins.list()[0].schema_json) - peer_exchange_schema_id = ( - "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.peer-exchange-request.schema.json" - ) - filtered_builtin = CultMesh.create_builtin_schema_catalog( - include_schema_json=True, - schema_ids=[peer_exchange_schema_id], - ) - self.assertEqual( - filtered_builtin.list()[0].schema_version, - "cultmesh.peer_exchange_request.v0", - ) - self.assertIn( - "cultmesh.peer_exchange_request.v0", - filtered_builtin.list()[0].schema_json or "", - ) - shard_catalog = CultMesh.create_shard_catalog() - shard_catalog.upsert( - CultNetShardDescriptor( - shard_id="python-notes-a", - owner_runtime_id="mesh-facade", - epoch=7, - is_primary=True, - schema_ids=("python.note.v1",), - key_prefix="note:", - ) - ) - self.assertEqual( - [ - shard.shard_id - for shard in shard_catalog.list(schema_ids=["python.note.v1"], record_keys=["note:1"]) - ], - ["python-notes-a"], - ) - verse_client = CultMesh.create_verse_discovery_client("127.0.0.1", 4010, timeout_seconds=1.5) - peer_client = CultMesh.create_peer_exchange_client("127.0.0.1", 4011, timeout_seconds=1.5) - raw_client = CultMesh.create_client("127.0.0.1", 4012, timeout_seconds=1.5) - connected_client = CultMesh.connect_client("127.0.0.1", 4013, timeout_seconds=1.5) - self.assertIsInstance(verse_client, CultMeshVerseDiscoveryClient) - self.assertIsInstance(peer_client, CultMeshPeerExchangeClient) - self.assertIsInstance(verse_client, CultMeshDiscoveryClient) - self.assertIsInstance(peer_client, CultMeshDiscoveryClient) - self.assertIsInstance(raw_client, CultNetRawClient) - self.assertIsInstance(connected_client, CultNetRawClient) - self.assertEqual((verse_client.host, verse_client.port, verse_client.timeout_seconds), ("127.0.0.1", 4010, 1.5)) - self.assertEqual((peer_client.host, peer_client.port, peer_client.timeout_seconds), ("127.0.0.1", 4011, 1.5)) - self.assertEqual((raw_client.host, raw_client.port, raw_client.timeout_seconds), ("127.0.0.1", 4012, 1.5)) - self.assertEqual((connected_client.host, connected_client.port, connected_client.timeout_seconds), ("127.0.0.1", 4013, 1.5)) - - def test_cultmesh_simulation_fact_uses_csharp_slot_contract(self) -> None: - import msgpack # type: ignore - - candidate = { - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), - "claimSummary": "alice shot bob first", - "witnessCount": 2, - "supportWeight": 2.0, - "totalWeight": 2.0, - "confidence": 1.0, - "hasQuorum": True, - } - - fact = CultMeshSimulationFact.from_candidate(candidate, committed_at="2026-06-13T00:00:00Z") - payload = simulation_fact_document.encode_payload(fact) - slots = msgpack.unpackb(payload, raw=False) - - self.assertEqual(CultMeshSimulationFact.create_record_key(candidate), f"simulation:{fact.fact_id}") - self.assertEqual(slots[0], fact.fact_id) - self.assertEqual(slots[1], "arena") - self.assertEqual(slots[2], 4) - self.assertEqual(slots[3], 100) - self.assertEqual(slots[4], "bob") - self.assertEqual(slots[5], "hit") - self.assertEqual(slots[6], candidate["claimHash"]) - self.assertEqual(slots[7], "alice shot bob first") - self.assertEqual(slots[8], 2) - self.assertEqual(slots[9], 2.0) - self.assertEqual(slots[10], 2.0) - self.assertEqual(slots[11], 1.0) - self.assertEqual(slots[12], "2026-06-13T00:00:00Z") - - def test_cultmesh_simulation_fact_committer_rejects_without_quorum_and_stores_fact(self) -> None: - candidate = { - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), - "claimSummary": "alice shot bob first", - "witnessCount": 2, - "supportWeight": 2.0, - "totalWeight": 2.0, - "confidence": 1.0, - "hasQuorum": True, - } - - with tempfile.TemporaryDirectory() as tmp: - node = CultMesh.create_node(Path(tmp) / "facts.cc", runtime_id="mesh-facts") - committer = CultMesh.create_simulation_fact_committer(node) - - rejected = dict(candidate) - rejected["hasQuorum"] = False - with self.assertRaisesRegex(ValueError, "before quorum"): - committer.commit(rejected) - - committed = committer.commit(candidate, committed_at="2026-06-13T00:00:00Z") - stored = node.get_required(simulation_fact_document, committed.key) - - self.assertEqual(stored.claim_hash, candidate["claimHash"]) - self.assertEqual(stored.committed_at, "2026-06-13T00:00:00Z") - self.assertEqual(committed.fact.fact_id, stored.fact_id) - - def test_cultnet_simulation_consensus_dedupes_witnesses_and_requires_quorum(self) -> None: - from cultnet_py import CultNetSimulationConsensus - - consensus = CultNetSimulationConsensus( - CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) - ) - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") - observations = [ - { - "witnessRuntimeId": "watcher-1", - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": claim_hash, - "claimSummary": "alice shot bob first", - "weight": 1.0, - }, - { - "witnessRuntimeId": "watcher-1", - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": "stale-duplicate", - "weight": 0.5, - }, - { - "witnessRuntimeId": "watcher-2", - "shardId": "arena", - "shardEpoch": 4, - "frame": 100, - "subjectId": "bob", - "claimKind": "hit", - "claimHash": claim_hash, - "weight": 1.0, - }, - ] - - candidates = consensus.build_candidates(observations) - typed_candidates = consensus.build_candidate_objects([ - CultNetSimulationObservation.from_wire(observation) - for observation in observations - ]) - - self.assertEqual(len(candidates), 1) - self.assertEqual(candidates[0]["claimHash"], claim_hash) - self.assertEqual(candidates[0]["witnessCount"], 2) - self.assertEqual(candidates[0]["supportWeight"], 2.0) - self.assertTrue(candidates[0]["hasQuorum"]) - self.assertEqual(len(typed_candidates), 1) - self.assertIsInstance(typed_candidates[0], CultNetSimulationConsensusCandidate) - self.assertEqual(typed_candidates[0].claim_hash, claim_hash) - self.assertEqual(typed_candidates[0].to_wire(), candidates[0]) - self.assertEqual(CultNetSimulationConsensusCandidate.from_wire(candidates[0]), typed_candidates[0]) - - def test_cultmesh_game_session_submits_observations_and_commits_quorum_once(self) -> None: - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") - first = simulation_observation( - message_id="obs-1", - witness_runtime_id="watcher-1", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ).to_wire() - second = simulation_observation( - message_id="obs-2", - witness_runtime_id="watcher-2", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ).to_wire() - - with tempfile.TemporaryDirectory() as tmp: - node = CultMesh.create_node(Path(tmp) / "session.cc", runtime_id="mesh-session") - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - consensus_options=CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) - ), - ) - seen_candidates: list[CultNetSimulationConsensusCandidate] = [] - seen_facts: list[CultMeshDatabaseChange] = [] - unsubscribe_candidates = session.watch_candidates(seen_candidates.append) - unsubscribe_facts = session.watch_simulation_facts(seen_facts.append) - - self.assertEqual(session.submit_and_commit(CultNetSimulationObservation.from_wire(first)), []) - typed_candidates = session.submit_observation_candidates(CultNetSimulationObservation.from_wire(second)) - commits = session.commit_quorum_candidate_objects(typed_candidates) - replay = session.commit_quorum_candidates(session.submit_observation(second)) - unsubscribe_candidates() - unsubscribe_facts() - - self.assertEqual(len(typed_candidates), 1) - self.assertTrue(typed_candidates[0].has_quorum) - self.assertEqual(len(commits), 1) - self.assertEqual(replay, []) - self.assertEqual([candidate.claim_hash for candidate in seen_candidates], [claim_hash, claim_hash, claim_hash]) - self.assertEqual(len(seen_facts), 1) - self.assertEqual(seen_facts[0].change_kind, "added") - self.assertEqual(seen_facts[0].value.claim_hash, claim_hash) - stored = node.get_required(simulation_fact_document, commits[0].key) - self.assertEqual(stored.claim_hash, claim_hash) - self.assertEqual(stored.witness_count, 2) - - def test_cultmesh_game_session_serves_catalogs_and_observations_over_wire(self) -> None: - verse_catalog = CultMeshVerseCatalog() - peer_catalog = CultMeshPeerCatalog() - verse_catalog.upsert(CultMeshVerseDescriptor( - verse_id="session-verse", - display_name="Session Verse", - authority_model="local", - compatibility=CultMeshVerseCompatibility("cultmesh.v0", "session-rules"), - )) - peer_catalog.upsert(CultMeshPeerCard( - peer_id="session-peer", - verse_id="session-verse", - endpoints=("cultnet://127.0.0.1:0",), - roles=("simulation-observer", "read-replica"), - shard_ids=("arena",), - )) - session = CultMesh.create_game_session( - CultMesh.create_node(runtime_id="session-peer"), - CultMeshGameSessionOptions( - verse_catalog=verse_catalog, - peer_catalog=peer_catalog, - consensus_options=CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0), - ), - ) - server = session.serve(display_name="Session Peer") - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - hello_response = raw_client.request( - hello(runtime_id="session-prober"), - expected_schema_version="cultnet.hello.v0", - ) - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) - verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") - peers = discovery_client.fetch_peers(verse_id="session-verse", roles=["simulation-observer"]) - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") - first = raw_client.request( - simulation_observation( - message_id="session-wire-1", - witness_runtime_id="watcher-1", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - second = raw_client.request( - simulation_observation( - message_id="session-wire-2", - witness_runtime_id="watcher-2", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - finally: - server.stop() - - self.assertIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) - self.assertEqual(verses[0].verse_id, "session-verse") - self.assertEqual(peers[0].peer_id, "session-peer") - self.assertIn("simulation-observer", peers[0].roles) - self.assertFalse(first["hasQuorum"]) - self.assertEqual(first["witnessCount"], 1) - self.assertTrue(second["hasQuorum"]) - self.assertEqual(second["witnessCount"], 2) - - def test_cultmesh_game_session_can_disable_served_simulation_observations(self) -> None: - verse_catalog = CultMeshVerseCatalog() - verse_catalog.upsert(CultMeshVerseDescriptor( - verse_id="session-quiet", - display_name="Quiet Session", - authority_model="local", - compatibility=CultMeshVerseCompatibility("cultmesh.v0", "quiet-rules"), - )) - session = CultMesh.create_game_session( - CultMesh.create_node(runtime_id="quiet-session-peer"), - CultMeshGameSessionOptions( - verse_catalog=verse_catalog, - serve_simulation_observations=False, - ), - ) - server = session.serve(display_name="Quiet Session Peer") - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - hello_response = raw_client.request( - hello(runtime_id="quiet-session-prober"), - expected_schema_version="cultnet.hello.v0", - ) - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) - verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") - with self.assertRaisesRegex(CultNetPeerError, "Simulation observations are not enabled") as raised: - raw_client.request( - simulation_observation( - message_id="quiet-session-sim", - witness_runtime_id="watcher-1", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - finally: - server.stop() - - self.assertNotIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) - self.assertEqual(verses[0].verse_id, "session-quiet") - self.assertEqual(raised.exception.response["code"], "simulation_observations_disabled") - - def test_cultmesh_game_session_prediction_requires_scope_and_reconciles_shard_log(self) -> None: - note_doc = define_database_entry_type( - "mesh.input", - [("body", 0)], - schema_id="mesh.input.v1", - ) - with tempfile.TemporaryDirectory() as tmp: - node = CultMesh.create_node(Path(tmp) / "prediction.cc", runtime_id="client-a") - node.register_document(note_doc) - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope( - "client-a", - schema_ids=("mesh.input.v1",), - key_prefix="input:client-a", - ), - ) - ), - ) - - with self.assertRaisesRegex(ValueError, "does not have client prediction authority"): - session.predict(note_doc, "input:other", {"body": "nope"}) - - prediction = session.predict(note_doc, "input:client-a:move", {"body": "predicted"}) - put = document_put_raw( - message_id="authoritative", - key=prediction.key, - schema_id=prediction.schema_id, - stored_at="2026-06-13T00:00:00Z", - payload=note_doc.encode_payload({"body": "authoritative"}), - shard_id="inputs", - shard_epoch=1, - ) - authoritative_response = { - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "inputs-log", - "shardId": "inputs", - "shardEpoch": 1, - "entries": [ - { - "sequence": 1, - "committedAt": "2026-06-13T00:00:00Z", - "changeKind": "updated", - "put": put.to_wire(), - } - ], - "resyncRequired": False, - } - changes = session.apply_shard_log_response(CultNetShardLogResponse.from_wire(authoritative_response)) - - self.assertEqual(changes[0].change_kind, "reconciled") - self.assertEqual(node.get_required(note_doc, prediction.key)["body"], "authoritative") - self.assertEqual(session.pending_predictions(), ()) - self.assertEqual(session.resimulation_inputs(), ()) - - def test_cultmesh_game_session_partial_reconciliation_keeps_unmatched_predictions(self) -> None: - note_doc = define_database_entry_type( - "mesh.partial_input", - [("body", 0)], - schema_id="mesh.partial_input.v1", - ) - node = CultMesh.create_node(runtime_id="client-partial") - node.register_document(note_doc) - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope( - "client-partial", - schema_ids=("mesh.partial_input.v1",), - key_prefix="input:client-partial", - ), - ) - ), - ) - pending = session.predict(note_doc, "input:client-partial:pending", {"body": "pending-local"}) - accepted = session.predict(note_doc, "input:client-partial:accepted", {"body": "accepted-local"}) - put = document_put_raw( - message_id="authoritative-partial", - key=accepted.key, - schema_id=accepted.schema_id, - stored_at="2026-06-13T00:00:00Z", - payload=note_doc.encode_payload({"body": "accepted-authority"}), - shard_id="inputs", - shard_epoch=1, - ) - - changes = session.apply_shard_log_response({ - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "inputs-partial-log", - "shardId": "inputs", - "shardEpoch": 1, - "entries": [ - { - "sequence": 1, - "committedAt": "2026-06-13T00:00:00Z", - "changeKind": "updated", - "put": put.to_wire(), - } - ], - "resyncRequired": False, - }) - - self.assertEqual([(change.change_kind, change.record_key) for change in changes], [ - ("reconciled", accepted.key), - ]) - self.assertEqual(node.get_required(note_doc, accepted.key)["body"], "accepted-authority") - self.assertEqual(node.get_required(note_doc, pending.key)["body"], "pending-local") - self.assertEqual([prediction.key for prediction in session.pending_predictions()], [pending.key]) - self.assertEqual([prediction.key for prediction in session.resimulation_inputs()], [pending.key]) - - def test_cultmesh_game_session_rolls_back_pending_predictions(self) -> None: - note_doc = define_database_entry_type( - "mesh.rollback_input", - [("body", 0)], - schema_id="mesh.rollback_input.v1", - ) - node = CultMesh.create_node(runtime_id="client-rollback") - node.register_document(note_doc) - node.put(note_doc, "input:client-rollback:existing", {"body": "previous"}) - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope( - "client-rollback", - schema_ids=("mesh.rollback_input.v1",), - key_prefix="input:client-rollback", - ), - ) - ), - ) - - existing = session.predict(note_doc, "input:client-rollback:existing", {"body": "predicted-existing"}) - created = session.predict(note_doc, "input:client-rollback:new", {"body": "predicted-new"}) - self.assertEqual( - [prediction.key for prediction in session.pending_predictions()], - ["input:client-rollback:existing", "input:client-rollback:new"], - ) - self.assertEqual([prediction.key for prediction in session.resimulation_inputs()], [ - "input:client-rollback:existing", - "input:client-rollback:new", - ]) - - restored = session.rollback_prediction(existing) - self.assertIsNotNone(restored) - assert restored is not None - self.assertEqual(restored.change_kind, "rolled_back") - self.assertEqual(restored.value, {"body": "previous"}) - self.assertEqual(node.get_required(note_doc, "input:client-rollback:existing")["body"], "previous") - - rolled_back = session.rollback_predictions() - self.assertEqual([(change.change_kind, change.record_key, change.value) for change in rolled_back], [ - ("rolled_back", created.key, None), - ]) - self.assertIsNone(node.get(note_doc, created.key)) - self.assertEqual(session.pending_predictions(), ()) - - def test_cultmesh_game_session_authoritative_delete_rolls_back_prediction(self) -> None: - note_doc = define_database_entry_type( - "mesh.deleted_input", - [("body", 0)], - schema_id="mesh.deleted_input.v1", - ) - node = CultMesh.create_node(runtime_id="client-delete") - node.register_document(note_doc) - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope( - "client-delete", - schema_ids=("mesh.deleted_input.v1",), - key_prefix="input:client-delete", - ), - ) - ), - ) - - prediction = session.predict(note_doc, "input:client-delete:move", {"body": "predicted"}) - delete = document_delete( - message_id="authoritative-delete", - schema_id=prediction.schema_id, - record_key=prediction.key, - shard_id="inputs", - shard_epoch=1, - ) - changes = session.apply_shard_log_response({ - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "inputs-delete-log", - "shardId": "inputs", - "shardEpoch": 1, - "entries": [ - { - "sequence": 1, - "committedAt": "2026-06-13T00:00:00Z", - "changeKind": "removed", - "delete": delete.to_wire(), - } - ], - "resyncRequired": False, - }) - - self.assertEqual(changes[0].change_kind, "rolled_back") - self.assertIsNone(node.get(note_doc, prediction.key)) - self.assertEqual(session.pending_predictions(), ()) - - def test_cultmesh_database_watchers_observe_authoritative_shard_log_reconciliation(self) -> None: - note_doc = define_database_entry_type( - "mesh.watched_input", - [("body", 0)], - schema_id="mesh.watched_input.v1", - ) - node = CultMesh.create_node(runtime_id="client-watch") - node.database.register_document(note_doc) - seen: list[CultMeshDatabaseChange] = [] - node.database.watch_record(note_doc, "input:client-watch:move", seen.append) - session = CultMesh.create_game_session( - node, - CultMeshGameSessionOptions( - client_authority_scopes=( - CultNetClientAuthorityScope( - "client-watch", - schema_ids=("mesh.watched_input.v1",), - key_prefix="input:client-watch", - ), - ) - ), - ) - - prediction = session.predict(note_doc, "input:client-watch:move", {"body": "predicted"}) - put = document_put_raw( - message_id="authoritative-watch", - key=prediction.key, - schema_id=prediction.schema_id, - stored_at="2026-06-13T00:00:00Z", - payload=note_doc.encode_payload({"body": "authoritative"}), - shard_id="inputs", - shard_epoch=1, - ) - session_changes = session.apply_shard_log_response({ - "schemaVersion": "cultnet.shard_log_response.v0", - "messageId": "inputs-watch-log", - "shardId": "inputs", - "shardEpoch": 1, - "entries": [ - { - "sequence": 1, - "committedAt": "2026-06-13T00:00:00Z", - "changeKind": "updated", - "put": put.to_wire(), - } - ], - "resyncRequired": False, - }) - - self.assertEqual([change.change_kind for change in seen], ["added", "updated"]) - self.assertEqual(seen[1].value, {"body": "authoritative"}) - self.assertEqual(seen[1].previous_value, {"body": "predicted"}) - self.assertEqual(session_changes[0].change_kind, "reconciled") - - def test_cultmesh_verse_catalog_response_matches_schema_v0_wire_shape(self) -> None: - import msgpack # type: ignore - - catalog = CultMeshVerseCatalog() - catalog.upsert( - CultMeshVerseDescriptor( - verse_id="aetheria-main", - display_name="Aetheria", - authority_model="OperatorCluster", - compatibility=CultMeshVerseCompatibility( - transport_version="cultmesh.v0", - rules_hash="rules", - compatible_verse_ids=("aetheria-modded",), - required_plugin_ids=("core",), - optional_plugin_ids=("skylands",), - ), - discovery_endpoints=("cultmesh://aetheria.example.test:3075",), - authority_runtime_ids=("runtime-a",), - description="main branch", - ) - ) - - response = catalog.create_response(verse_catalog_request("verses-1")) - decoded = msgpack.unpackb(msgpack.packb(response, use_bin_type=True), raw=False) - self.assertEqual(decoded["schemaVersion"], "cultmesh.verse_catalog_response.v0") - self.assertEqual(decoded["messageId"], "verses-1") - self.assertEqual(decoded["verses"][0]["verseId"], "aetheria-main") - self.assertEqual(decoded["verses"][0]["compatibility"]["requiredPluginIds"], ["core"]) - - def test_cultmesh_verse_catalog_watches_updates_and_finds_transfer_targets(self) -> None: - source = CultMeshVerseDescriptor( - verse_id="aetheria-main", - display_name="Aetheria", - authority_model="federated", - compatibility=CultMeshVerseCompatibility( - transport_version="cultmesh.v0", - rules_hash="rules-main", - ), - ) - compatible = CultMeshVerseDescriptor( - verse_id="aetheria-modded", - display_name="Aetheria Modded", - authority_model="federated", - compatibility=CultMeshVerseCompatibility( - transport_version="cultmesh.v0", - rules_hash="rules-modded", - compatible_verse_ids=("aetheria-main",), - ), - ) - incompatible = CultMeshVerseDescriptor( - verse_id="old-world", - display_name="Old World", - authority_model="solo", - compatibility=CultMeshVerseCompatibility( - transport_version="cultmesh.v0", - rules_hash="rules-old", - ), - ) - catalog = CultMeshVerseCatalog() - seen: list[str] = [] - unsubscribe = catalog.watch(lambda verse: seen.append(verse.verse_id)) - - catalog.upsert(source) - catalog.apply_response({ - "schemaVersion": "cultmesh.verse_catalog_response.v0", - "messageId": "verses-watch", - "verses": [compatible.to_wire(), incompatible.to_wire()], - }) - unsubscribe() - catalog.upsert(CultMeshVerseDescriptor( - verse_id="after-unsubscribe", - display_name="After", - authority_model="none", - compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules-after"), - )) - - self.assertEqual(seen, ["aetheria-main", "aetheria-modded", "old-world"]) - self.assertEqual([verse.verse_id for verse in catalog.verses], ["aetheria-main", "aetheria-modded", "after-unsubscribe", "old-world"]) - self.assertEqual(catalog.get("aetheria-modded"), compatible) - self.assertEqual([verse.verse_id for verse in catalog.find_transfer_targets(source)], ["aetheria-modded"]) - - def test_cultmesh_peer_exchange_response_matches_schema_v0_wire_shape(self) -> None: - import msgpack # type: ignore - - catalog = CultMeshPeerCatalog() - catalog.upsert( - CultMeshPeerCard( - peer_id="peer-a", - verse_id="aetheria-main", - endpoints=("cultnet://peer-a.example.test:3075",), - roles=("discovery", "read-replica"), - shard_ids=("players",), - region="eu-west", - authority_lease_id="lease-1", - expires_at="2026-05-20T12:00:00.0000000Z", - signature="sig", - ) - ) - - response = catalog.create_response( - peer_exchange_request("pex-1", verse_id="aetheria-main", roles=["read-replica"]) - ) - decoded = msgpack.unpackb(msgpack.packb(response, use_bin_type=True), raw=False) - self.assertEqual(decoded["schemaVersion"], "cultmesh.peer_exchange_response.v0") - self.assertEqual(decoded["messageId"], "pex-1") - self.assertEqual(decoded["peers"][0]["peerId"], "peer-a") - self.assertIn("read-replica", decoded["peers"][0]["roles"]) - self.assertEqual(decoded["peers"][0]["authorityLeaseId"], "lease-1") - - def test_cultmesh_peer_catalog_watches_updates_and_gets_peers(self) -> None: - first = CultMeshPeerCard( - peer_id="peer-a", - verse_id="aetheria-main", - endpoints=("cultnet://peer-a.example.test:3075",), - roles=("discovery", "read-replica"), - ) - second = CultMeshPeerCard( - peer_id="peer-b", - verse_id="aetheria-main", - endpoints=("cultnet://peer-b.example.test:3075",), - roles=("shard-primary",), - ) - catalog = CultMeshPeerCatalog() - seen: list[str] = [] - unsubscribe = catalog.watch(lambda peer: seen.append(peer.peer_id)) - - catalog.upsert(first) - catalog.apply_response({ - "schemaVersion": "cultmesh.peer_exchange_response.v0", - "messageId": "peers-watch", - "peers": [second.to_wire()], - }) - unsubscribe() - catalog.upsert(CultMeshPeerCard( - peer_id="peer-c", - verse_id="aetheria-main", - endpoints=("cultnet://peer-c.example.test:3075",), - roles=("read-replica",), - )) - - self.assertEqual(seen, ["peer-a", "peer-b"]) - self.assertTrue(first.has_role("read-replica")) - self.assertEqual([peer.peer_id for peer in catalog.peers], ["peer-a", "peer-b", "peer-c"]) - self.assertEqual(catalog.get("peer-b"), second) - self.assertEqual([peer.peer_id for peer in catalog.find("aetheria-main", role="read-replica")], ["peer-a", "peer-c"]) - - def test_cultmesh_discovery_client_fetches_typed_catalogs_over_cultnet_frames(self) -> None: - import msgpack # type: ignore - from cultnet_py import read_frame, write_frame - - verses = CultMeshVerseCatalog() - verses.upsert( - CultMeshVerseDescriptor( - verse_id="aetheria-main", - display_name="Aetheria", - authority_model="federated", - compatibility=CultMeshVerseCompatibility( - transport_version="cultmesh.v0", - rules_hash="rules-1", - required_plugin_ids=("core",), - ), - discovery_endpoints=("cultmesh://aetheria.example.test:3075",), - authority_runtime_ids=("runtime-a",), - ) - ) - peers = CultMeshPeerCatalog() - peers.upsert( - CultMeshPeerCard( - peer_id="peer-a", - verse_id="aetheria-main", - endpoints=("cultnet://peer-a.example.test:3075",), - roles=("read-replica",), - shard_ids=("players",), - ) - ) - - ready = threading.Event() - server_error: list[BaseException] = [] - - def serve_requests() -> None: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: - server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server.bind(("127.0.0.1", 0)) - port_holder.append(server.getsockname()[1]) - server.listen(8) - ready.set() - for _ in range(8): - connection, _ = server.accept() - with connection: - stream = connection.makefile("rwb") - request = msgpack.unpackb(read_frame(stream), raw=False) - if request["schemaVersion"] == "cultmesh.verse_catalog_request.v0": - response = verses.create_response(request) - elif request["schemaVersion"] == "cultmesh.peer_exchange_request.v0": - response = peers.create_response(request) - else: - raise AssertionError(f"unexpected request {request['schemaVersion']}") - write_frame(stream, msgpack.packb(response, use_bin_type=True)) - stream.flush() - except BaseException as error: - server_error.append(error) - ready.set() - - port_holder: list[int] = [] - thread = threading.Thread(target=serve_requests, daemon=True) - thread.start() - self.assertTrue(ready.wait(2.0)) - self.assertFalse(server_error) - - client = CultMeshDiscoveryClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) - fetched_verses = client.fetch_verses(transport_version="cultmesh.v0") - fetched_peers = client.fetch_peers(verse_id="aetheria-main", roles=["read-replica"]) - local_verses = CultMeshVerseCatalog() - local_peers = CultMeshPeerCatalog() - synced_verses = client.sync_verse_catalog(local_verses, transport_version="cultmesh.v0") - synced_peers = client.sync_peer_catalog(local_peers, verse_id="aetheria-main", roles=["read-replica"]) - discovered_verses = CultMeshVerseCatalog() - discovered_peers = CultMeshPeerCatalog() - endpoint = f"cultnet://127.0.0.1:{port_holder[0]}" - verse_discovery = CultMeshVerseDiscoveryClient.from_endpoint(endpoint, timeout_seconds=2.0) - peer_discovery = CultMeshPeerExchangeClient.from_endpoint(endpoint, timeout_seconds=2.0) - discovered_verse_count = verse_discovery.discover( - discovered_verses, - endpoints=[endpoint, endpoint, ""], - transport_version="cultmesh.v0", - ) - discovered_peer_count = peer_discovery.discover( - discovered_peers, - verse_id="aetheria-main", - endpoints=[endpoint, endpoint, ""], - roles=["read-replica"], - ) - fanout_verses = CultMeshVerseCatalog() - fanout_verses.upsert( - CultMeshVerseDescriptor( - verse_id="aetheria-main", - display_name="Known Aetheria", - authority_model="federated", - compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules-1"), - discovery_endpoints=(endpoint, endpoint, ""), - ) - ) - fanout_peers = CultMeshPeerCatalog() - fanout_peers.upsert( - CultMeshPeerCard( - peer_id="known-peer", - verse_id="aetheria-main", - endpoints=(endpoint, endpoint, ""), - roles=("read-replica",), - ) - ) - fanout_verse_count = verse_discovery.fanout(fanout_verses, transport_version="cultmesh.v0") - fanout_peer_count = peer_discovery.fanout( - fanout_peers, - verse_id="aetheria-main", - roles=["read-replica"], - ) - - thread.join(2.0) - self.assertFalse(server_error) - self.assertEqual(fetched_verses[0].verse_id, "aetheria-main") - self.assertEqual(fetched_verses[0].compatibility.required_plugin_ids, ("core",)) - self.assertEqual(fetched_peers[0].peer_id, "peer-a") - self.assertEqual(fetched_peers[0].shard_ids, ("players",)) - self.assertEqual(synced_verses[0].verse_id, "aetheria-main") - self.assertEqual(local_peers.find("aetheria-main", role="read-replica")[0].peer_id, "peer-a") - self.assertEqual(synced_peers[0].roles, ("read-replica",)) - self.assertEqual(discovered_verse_count, 1) - self.assertEqual(discovered_peer_count, 1) - self.assertEqual(discovered_verses.get("aetheria-main").display_name, "Aetheria") - self.assertEqual(discovered_peers.find("aetheria-main", role="read-replica")[0].peer_id, "peer-a") - self.assertEqual(fanout_verse_count, 1) - self.assertEqual(fanout_peer_count, 1) - self.assertEqual(fanout_verses.get("aetheria-main").display_name, "Aetheria") - self.assertIn( - "peer-a", - {peer.peer_id for peer in fanout_peers.find("aetheria-main", role="read-replica")}, - ) - - def test_cultmesh_discovery_client_fans_out_simulation_observations_to_peers(self) -> None: - first_hub = CultNetSimulationObservationHub( - CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) - ) - second_hub = CultNetSimulationObservationHub( - CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) - ) - first_server = CultMesh.serve_node( - CultMesh.create_node(runtime_id="sim-peer-a"), - observation_hub=first_hub, - ) - second_server = CultMesh.serve_node( - CultMesh.create_node(runtime_id="sim-peer-b"), - observation_hub=second_hub, - ) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="sim-peer-a", - verse_id="arena", - endpoints=(f"cultnet://127.0.0.1:{first_server.port}",), - roles=("simulation-witness",), - )) - peers.upsert(CultMeshPeerCard( - peer_id="sim-peer-b", - verse_id="arena", - endpoints=(f"cultnet://127.0.0.1:{second_server.port}",), - roles=("simulation-witness",), - )) - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:200") - message = simulation_observation( - message_id="fanout-observation", - witness_runtime_id="python-fanout", - shard_id="arena", - shard_epoch=1, - frame=200, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice hit bob", - ).to_wire() - - client = CultMeshDiscoveryClient("127.0.0.1", first_server.port, timeout_seconds=2.0) - candidates = client.fanout_simulation_observation( - peers, - message, - verse_id="arena", - roles=["simulation-witness"], - ) - finally: - first_server.stop() - second_server.stop() - - self.assertEqual([candidate["schemaVersion"] for candidate in candidates], [ - "cultnet.simulation_consensus_candidate.v0", - "cultnet.simulation_consensus_candidate.v0", - ]) - self.assertEqual({candidate["claimHash"] for candidate in candidates}, {claim_hash}) - self.assertEqual({candidate["messageId"] for candidate in candidates}, {"fanout-observation"}) - self.assertEqual({candidate["hasQuorum"] for candidate in candidates}, {True}) - - def test_cultmesh_simulation_observation_fanout_flushes_queued_messages(self) -> None: - hub = CultNetSimulationObservationHub( - CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) - ) - server = CultMesh.serve_node(CultMesh.create_node(runtime_id="sim-flush-peer"), observation_hub=hub) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="sim-flush-peer", - verse_id="arena", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("simulation-witness",), - )) - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:210") - seen_candidates: list[dict[str, object]] = [] - fanout = CultMeshSimulationObservationFanout( - CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), - peers, - verse_id="arena", - roles=["simulation-witness"], - on_candidate=seen_candidates.append, - ) - fanout.enqueue(simulation_observation( - message_id="queued-observation", - witness_runtime_id="python-fanout", - shard_id="arena", - shard_epoch=1, - frame=210, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice hit bob", - ).to_wire()) - - candidates = fanout.flush() - finally: - server.stop() - - self.assertEqual(fanout.pending_count(), 0) - self.assertEqual([candidate["claimHash"] for candidate in candidates], [claim_hash]) - self.assertEqual(seen_candidates[0]["messageId"], "queued-observation") - - def test_cultmesh_simulation_observation_fanout_keeps_failed_messages_pending(self) -> None: - closed_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - closed_socket.bind(("127.0.0.1", 0)) - closed_port = closed_socket.getsockname()[1] - closed_socket.close() - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="closed-simulation-peer", - verse_id="arena", - endpoints=(f"cultnet://127.0.0.1:{closed_port}",), - roles=("simulation-witness",), - )) - errors: list[tuple[str, str]] = [] - fanout = CultMeshSimulationObservationFanout( - CultMeshDiscoveryClient("127.0.0.1", closed_port, timeout_seconds=0.2), - peers, - verse_id="arena", - roles=["simulation-witness"], - on_error=lambda endpoint, error: errors.append((endpoint, str(error))), - ) - fanout.enqueue(simulation_observation( - message_id="retry-observation", - witness_runtime_id="python-fanout", - shard_id="arena", - shard_epoch=1, - frame=212, - subject_id="bob", - claim_kind="miss", - claim_hash=compute_simulation_claim_hash("miss", "alice", "bob", "frame:212"), - claim_summary="alice missed bob", - ).to_wire()) - - candidates = fanout.flush() - - self.assertEqual(candidates, []) - self.assertEqual(fanout.pending_count(), 1) - self.assertEqual(len(errors), 1) - self.assertIn(f"127.0.0.1:{closed_port}", errors[0][0]) - - def test_cultmesh_simulation_observation_fanout_runs_background_loop(self) -> None: - hub = CultNetSimulationObservationHub( - CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) - ) - server = CultMesh.serve_node(CultMesh.create_node(runtime_id="sim-loop-peer"), observation_hub=hub) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="sim-loop-peer", - verse_id="arena", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("simulation-witness",), - )) - claim_hash = compute_simulation_claim_hash("block", "dana", "eve", "frame:211") - seen_candidates: list[dict[str, object]] = [] - fanout = CultMeshSimulationObservationFanout( - CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), - peers, - verse_id="arena", - roles=["simulation-witness"], - interval_seconds=0.05, - on_candidate=seen_candidates.append, - ) - fanout.start() - fanout.enqueue(simulation_observation( - message_id="loop-observation", - witness_runtime_id="python-fanout", - shard_id="arena", - shard_epoch=1, - frame=211, - subject_id="eve", - claim_kind="block", - claim_hash=claim_hash, - claim_summary="dana blocked eve", - ).to_wire()) - deadline = time.monotonic() + 2.0 - while not seen_candidates and time.monotonic() < deadline: - time.sleep(0.01) - fanout.stop() - finally: - server.stop() - - self.assertEqual(fanout.pending_count(), 0) - self.assertTrue(seen_candidates) - self.assertEqual(seen_candidates[0]["claimHash"], claim_hash) - self.assertEqual(seen_candidates[0]["messageId"], "loop-observation") - - def test_cultmesh_discovery_client_fans_out_snapshots_to_peers(self) -> None: - document = define_database_entry_type( - "mesh.snapshot_fanout_note", - [("body", 0)], - schema_id="mesh.snapshot_fanout_note.v1", - ) - first = CultMesh.create_node(runtime_id="snapshot-peer-a") - first.database.register_document(document) - first.database.put_raw_message(document, "note:a", {"body": "from-a"}, shard_id="notes", shard_epoch=1) - second = CultMesh.create_node(runtime_id="snapshot-peer-b") - second.database.register_document(document) - second.database.put_raw_message(document, "note:b", {"body": "from-b"}, shard_id="notes", shard_epoch=1) - target = CultMesh.create_node(runtime_id="snapshot-target") - target.database.register_document(document) - first_server = CultMesh.serve_node(first) - second_server = CultMesh.serve_node(second) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-peer-a", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{first_server.port}", f"cultnet://127.0.0.1:{first_server.port}"), - roles=("read-replica",), - )) - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-peer-b", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{second_server.port}",), - roles=("read-replica",), - )) - - client = CultMeshDiscoveryClient("127.0.0.1", first_server.port, timeout_seconds=2.0) - responses = client.fanout_snapshot_responses( - peers, - verse_id="mesh", - roles=["read-replica"], - schema_ids=["mesh.snapshot_fanout_note.v1"], - ) - applied = client.sync_snapshots( - target.database, - peers, - verse_id="mesh", - roles=["read-replica"], - schema_ids=["mesh.snapshot_fanout_note.v1"], - ) - finally: - first_server.stop() - second_server.stop() - - self.assertEqual(len(responses), 2) - self.assertTrue(all(isinstance(response, CultNetRawSnapshotResponse) for response in responses)) - self.assertEqual( - sorted(record.record_key for response in responses for record in response.documents), - ["note:a", "note:b"], - ) - self.assertEqual(sorted(record.record_key for record in applied), ["note:a", "note:b"]) - self.assertEqual(target.database.get_required(document, "note:a")["body"], "from-a") - self.assertEqual(target.database.get_required(document, "note:b")["body"], "from-b") - - def test_cultmesh_discovery_client_sync_documents_returns_requested_aliases(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.snapshot_fanout_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.snapshot_fanout_alias_note.v1", - schema_name="mesh.snapshot_fanout_alias_note", - schema_version="mesh.snapshot_fanout_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.snapshot_fanout_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.snapshot_fanout_alias_note.v1", - schema_name="mesh.snapshot_fanout_alias_note", - schema_version="mesh.snapshot_fanout_alias_note.v1", - ) - source = CultMesh.create_node(runtime_id="snapshot-alias-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:alias", CanonicalNote("from-source")) - target = CultMesh.create_node(runtime_id="snapshot-alias-target") - target.database.register_document(document) - server = CultMesh.serve_node(source) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-alias-source", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - - values = CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0).sync_documents( - target.database, - peers, - verse_id="mesh", - roles=["read-replica"], - documents=[(alias, "note:alias")], - ) - finally: - server.stop() - - self.assertEqual(len(values), 1) - self.assertIsInstance(values[0], UiNote) - self.assertEqual(values[0].body, "from-source") - self.assertEqual(target.database.get_required(document, "note:alias").body, "from-source") - self.assertIsInstance(target.database.get_required(alias, "note:alias"), UiNote) - - def test_cultmesh_document_subscription_syncs_alias_snapshot_and_changes(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.subscription_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.subscription_alias_note.v1", - schema_name="mesh.subscription_alias_note", - schema_version="mesh.subscription_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.subscription_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.subscription_alias_note.v1", - schema_name="mesh.subscription_alias_note", - schema_version="mesh.subscription_alias_note.v1", - ) - source = CultMesh.create_node(runtime_id="subscription-alias-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:live", CanonicalNote("initial")) - target = CultMesh.create_node(runtime_id="subscription-alias-target") - target.database.register_document(document) - server = CultMesh.serve_node(source) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - with CultMesh.subscribe_document(target, raw_client, alias, "note:live") as subscription: - initial = subscription.sync_initial() - subscription.send(document_put_raw( - message_id="subscription-alias-put", - key="note:live", - schema_id="mesh.subscription_alias_note.v1", - stored_at="2026-06-14T00:00:00Z", - payload=document.encode_payload(CanonicalNote("updated")), - )) - updated = subscription.read_next_change() - subscription.send(document_delete( - message_id="subscription-alias-delete", - schema_id="mesh.subscription_alias_note.v1", - record_key="note:live", - )) - removed = subscription.read_next_change() - finally: - server.stop() - - self.assertIsInstance(initial, UiNote) - self.assertEqual(initial.body, "initial") - self.assertEqual(updated.change_kind, "updated") - self.assertIsInstance(updated.value, UiNote) - self.assertEqual(updated.value.body, "updated") - self.assertIsInstance(updated.previous_value, UiNote) - self.assertEqual(updated.previous_value.body, "initial") - self.assertEqual(removed.change_kind, "removed") - self.assertIsNone(removed.value) - self.assertIsInstance(removed.previous_value, UiNote) - self.assertEqual(removed.previous_value.body, "updated") - self.assertIsNone(target.database.get(alias, "note:live")) - - def test_cultmesh_snapshot_fanout_syncs_once_and_reports_applied_records(self) -> None: - document = define_database_entry_type( - "mesh.snapshot_loop_note", - [("body", 0)], - schema_id="mesh.snapshot_loop_note.v1", - ) - source = CultMesh.create_node(runtime_id="snapshot-loop-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:sync-once", {"body": "from-source"}) - target = CultMesh.create_node(runtime_id="snapshot-loop-target") - target.database.register_document(document) - server = CultMesh.serve_node(source) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-loop-source", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - seen: list[str] = [] - fanout = CultMeshSnapshotFanout( - CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), - target.database, - peers, - verse_id="mesh", - roles=["read-replica"], - schema_ids=["mesh.snapshot_loop_note.v1"], - on_applied=lambda record: seen.append(record.record_key), - ) - - applied = fanout.sync_once() - finally: - server.stop() - - self.assertEqual([record.record_key for record in applied], ["note:sync-once"]) - self.assertEqual(seen, ["note:sync-once"]) - self.assertEqual(target.database.get_required(document, "note:sync-once")["body"], "from-source") - - def test_cultmesh_snapshot_fanout_syncs_typed_documents_and_reports_alias_values(self) -> None: - @dataclass - class CanonicalNote: - body: str - - @dataclass - class UiNote: - body: str - - document = define_database_entry_type( - "mesh.snapshot_loop_alias_note", - [("body", 0)], - cls=CanonicalNote, - schema_id="mesh.snapshot_loop_alias_note.v1", - schema_name="mesh.snapshot_loop_alias_note", - schema_version="mesh.snapshot_loop_alias_note.v1", - ) - alias = define_database_entry_type( - "mesh.snapshot_loop_alias_note.ui", - [("body", 0)], - cls=UiNote, - schema_id="mesh.snapshot_loop_alias_note.v1", - schema_name="mesh.snapshot_loop_alias_note", - schema_version="mesh.snapshot_loop_alias_note.v1", - ) - source = CultMesh.create_node(runtime_id="snapshot-loop-alias-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:typed-loop", CanonicalNote("from-source")) - target = CultMesh.create_node(runtime_id="snapshot-loop-alias-target") - target.database.register_document(document) - server = CultMesh.serve_node(source) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-loop-alias-source", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - seen: list[UiNote] = [] - fanout = CultMeshSnapshotFanout( - CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), - target.database, - peers, - verse_id="mesh", - roles=["read-replica"], - documents=[(alias, "note:typed-loop")], - on_document=seen.append, - ) - - values = fanout.sync_once() - finally: - server.stop() - - self.assertEqual(len(values), 1) - self.assertIsInstance(values[0], UiNote) - self.assertEqual(values[0].body, "from-source") - self.assertEqual(seen, [values[0]]) - self.assertEqual(target.database.get_required(document, "note:typed-loop").body, "from-source") - self.assertIsInstance(target.database.get_required(alias, "note:typed-loop"), UiNote) - - def test_cultmesh_snapshot_fanout_runs_background_loop(self) -> None: - document = define_database_entry_type( - "mesh.snapshot_background_note", - [("body", 0)], - schema_id="mesh.snapshot_background_note.v1", - ) - source = CultMesh.create_node(runtime_id="snapshot-background-source") - source.database.register_document(document) - source.database.put_raw_message(document, "note:background", {"body": "from-background"}) - target = CultMesh.create_node(runtime_id="snapshot-background-target") - target.database.register_document(document) - server = CultMesh.serve_node(source) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="snapshot-background-source", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - seen: list[str] = [] - fanout = CultMeshSnapshotFanout( - CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), - target.database, - peers, - verse_id="mesh", - roles=["read-replica"], - schema_ids=["mesh.snapshot_background_note.v1"], - interval_seconds=0.05, - on_applied=lambda record: seen.append(record.record_key), - ) - fanout.start() - deadline = time.monotonic() + 2.0 - while not seen and time.monotonic() < deadline: - time.sleep(0.01) - fanout.stop() - finally: - server.stop() - - self.assertTrue(seen) - self.assertEqual(target.database.get_required(document, "note:background")["body"], "from-background") - - def test_cultmesh_peer_health_monitor_probes_reachable_and_failed_endpoints(self) -> None: - document = define_database_entry_type( - "mesh.health_note", - [("body", 0)], - schema_id="mesh.health_note.v1", - ) - node = CultMesh.create_node(runtime_id="health-peer") - node.database.register_document(document) - server = CultMesh.serve_node(node) - closed_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - closed_socket.bind(("127.0.0.1", 0)) - closed_port = closed_socket.getsockname()[1] - closed_socket.close() - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="health-peer", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - peers.upsert(CultMeshPeerCard( - peer_id="closed-peer", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{closed_port}",), - roles=("read-replica",), - )) - seen: list[tuple[str, bool]] = [] - monitor = CultMeshPeerHealthMonitor( - runtime_id="health-prober", - timeout_seconds=0.2, - on_update=lambda health: seen.append((health.peer_id, health.is_reachable)), - ) - - results = monitor.probe_catalog(peers, verse_id="mesh", roles=["read-replica"]) - finally: - server.stop() - - by_peer = {result.peer_id: result for result in results} - self.assertTrue(by_peer["health-peer"].is_reachable) - self.assertEqual(by_peer["health-peer"].runtime_id, "health-peer") - self.assertEqual(by_peer["health-peer"].runtime_kind, "python") - self.assertEqual(by_peer["health-peer"].display_name, "health-peer") - self.assertEqual(by_peer["health-peer"].supported_document_types, ("mesh.health_note",)) - self.assertIn("cultnet.hello.v0", by_peer["health-peer"].supported_message_versions) - self.assertIn("cultnet.error.v0", by_peer["health-peer"].supported_message_versions) - self.assertEqual(by_peer["health-peer"].supported_mutation_contracts[0]["documentType"], "mesh.health_note") - self.assertIn("shardLog", by_peer["health-peer"].supported_mutation_contracts[0]["operations"]) - self.assertFalse(by_peer["closed-peer"].is_reachable) - self.assertIsNone(by_peer["closed-peer"].runtime_kind) - self.assertEqual(by_peer["closed-peer"].supported_document_types, ()) - self.assertEqual(by_peer["closed-peer"].supported_message_versions, ()) - self.assertEqual(by_peer["closed-peer"].supported_mutation_contracts, ()) - self.assertTrue(by_peer["closed-peer"].error) - self.assertEqual({peer_id for peer_id, _ in seen}, {"health-peer", "closed-peer"}) - self.assertEqual(len(monitor.latest()), 2) - - def test_cultmesh_peer_health_monitor_runs_background_probe_loop(self) -> None: - server = CultMesh.serve_node(CultMesh.create_node(runtime_id="health-loop-peer")) - try: - peers = CultMeshPeerCatalog() - peers.upsert(CultMeshPeerCard( - peer_id="health-loop-peer", - verse_id="mesh", - endpoints=(f"cultnet://127.0.0.1:{server.port}",), - roles=("read-replica",), - )) - seen: list[str] = [] - monitor = CultMeshPeerHealthMonitor( - runtime_id="health-loop-prober", - timeout_seconds=0.2, - interval_seconds=0.05, - on_update=lambda health: seen.append(health.peer_id), - ) - monitor.start(peers, verse_id="mesh", roles=["read-replica"]) - deadline = time.monotonic() + 2.0 - while time.monotonic() < deadline: - latest = monitor.latest() - if latest and latest[0].is_reachable: - break - time.sleep(0.01) - monitor.stop() - finally: - server.stop() - - self.assertTrue(seen) - latest = monitor.latest() - self.assertEqual(latest[0].peer_id, "health-loop-peer") - self.assertTrue(latest[0].is_reachable) - - def test_cultmesh_daemon_entrypoint_serves_framed_cultnet_hello(self) -> None: - with tempfile.TemporaryDirectory() as temp: - ready_path = Path(temp) / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "daemon-test-peer", - "--display-name", - "Daemon Test Peer", - "--port", - "0", - "--ready-file", - str(ready_path), - "--seed-interop-note", - "--seed-shard-id", - "daemon-interop", - "--verse-id", - "daemon-verse", - "--verse-display-name", - "Daemon Verse", - "--role", - "shard-primary", - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - deadline = time.monotonic() + 5.0 - while not ready_path.exists() and time.monotonic() < deadline: - if process.poll() is not None: - break - time.sleep(0.05) - if not ready_path.exists(): - _, stderr = process.communicate(timeout=2.0) - self.fail(f"cultmesh daemon did not publish readiness: {stderr}") - ready = json.loads(ready_path.read_text(encoding="utf-8")) - self.assertEqual(ready["schemaVersion"], READY_SCHEMA_VERSION) - self.assertEqual(ready["runtimeId"], "daemon-test-peer") - self.assertEqual(ready["runtimeKind"], "python") - self.assertEqual(ready["displayName"], "Daemon Test Peer") - self.assertEqual(ready["endpoint"], f"cultnet://127.0.0.1:{ready['port']}") - self.assertEqual(ready["shardIds"], ["daemon-interop"]) - self.assertEqual(ready["supportedDocumentTypes"], ["cultcache.interop-note"]) - self.assertIn("cultnet.hello.v0", ready["supportedMessageVersions"]) - self.assertIn("cultnet.document_put_raw.v0", ready["supportedMessageVersions"]) - self.assertNotIn("cultnet.simulation_observation.v0", ready["supportedMessageVersions"]) - self.assertEqual(ready["supportedMutationContracts"][0]["documentType"], "cultcache.interop-note") - self.assertIn("shardLog", ready["supportedMutationContracts"][0]["operations"]) - self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": None}) - self.assertEqual(ready["verses"][0]["verseId"], "daemon-verse") - self.assertEqual(ready["verses"][0]["displayName"], "Daemon Verse") - self.assertEqual(ready["verses"][0]["discoveryEndpoints"], [ready["endpoint"]]) - self.assertEqual(ready["verses"][0]["authorityRuntimeIds"], ["daemon-test-peer"]) - self.assertEqual(ready["peers"][0]["peerId"], "daemon-test-peer") - self.assertEqual(ready["peers"][0]["endpoints"], [ready["endpoint"]]) - self.assertEqual(ready["peers"][0]["roles"], ["shard-primary"]) - self.assertEqual(ready["peers"][0]["shardIds"], ["daemon-interop"]) - - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - hello_response = client.request( - hello(runtime_id="daemon-test-prober"), - expected_schema_version="cultnet.hello.v0", - ) - - self.assertEqual(hello_response["runtimeId"], "daemon-test-peer") - self.assertEqual(hello_response["displayName"], "Daemon Test Peer") - self.assertIn("cultnet.hello.v0", hello_response["supportedMessageVersions"]) - self.assertIn("cultnet.schema_catalog_request.v0", hello_response["supportedMessageVersions"]) - self.assertNotIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) - self.assertEqual(hello_response["supportedDocumentTypes"], ["cultcache.interop-note"]) - self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") - self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["port"], int(ready["port"])) - self.assertEqual(ready["supportedMessageVersions"], hello_response["supportedMessageVersions"]) - self.assertEqual(ready["supportedMutationContracts"], hello_response["supportedMutationContracts"]) - self.assertEqual(ready["transportProfiles"], hello_response["transportProfiles"]) - - schema_response = client.fetch_schema_catalog(schema_ids=["cultcache.interop-note"], include_schema_json=True) - self.assertEqual(schema_response["schemas"][0]["schemaVersion"], INTEROP_SCHEMA_VERSION) - self.assertEqual(schema_response["schemas"][0]["documentType"], "cultcache.interop-note") - - snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) - self.assertEqual(snapshot_response.documents[0].record_key, "note:daemon-test-peer") - note = interop_note_document.decode_payload(snapshot_response.documents[0].payload) - self.assertEqual(note["authorRuntimeId"], "daemon-test-peer") - self.assertIn("daemon", note["tags"]) - - shard_catalog = client.fetch_shard_catalog(schema_ids=["cultcache.interop-note"]) - self.assertEqual(shard_catalog["shards"][0]["shardId"], "daemon-interop") - self.assertEqual(shard_catalog["shards"][0]["schemaIds"], ["cultcache.interop-note"]) - - shard_log = client.fetch_shard_log_response(shard_id="daemon-interop", shard_epoch=1) - self.assertFalse(shard_log.resync_required) - self.assertEqual(shard_log.entries[0].sequence, 1) - self.assertEqual(shard_log.entries[0].change_kind, "added") - self.assertIsNotNone(shard_log.entries[0].raw_document) - assert shard_log.entries[0].raw_document is not None - self.assertEqual(shard_log.entries[0].raw_document.record_key, "note:daemon-test-peer") - - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") - self.assertEqual(verses[0].verse_id, "daemon-verse") - self.assertEqual(verses[0].display_name, "Daemon Verse") - self.assertEqual(verses[0].discovery_endpoints, (ready["endpoint"],)) - self.assertEqual(verses[0].authority_runtime_ids, ("daemon-test-peer",)) - self.assertEqual([verse.to_wire() for verse in verses], ready["verses"]) - - peers = discovery_client.fetch_peers(verse_id="daemon-verse", roles=["shard-primary"]) - self.assertEqual(peers[0].peer_id, "daemon-test-peer") - self.assertEqual(peers[0].endpoints, (ready["endpoint"],)) - self.assertEqual(peers[0].roles, ("shard-primary",)) - self.assertEqual(peers[0].shard_ids, ("daemon-interop",)) - self.assertEqual([peer.to_wire() for peer in peers], ready["peers"]) - - monitor = CultMeshPeerHealthMonitor(runtime_id="daemon-health-prober", timeout_seconds=2.0) - health = monitor.probe_peer(peers[0])[0] - self.assertTrue(health.is_reachable) - self.assertEqual(health.runtime_id, "daemon-test-peer") - self.assertEqual(health.supported_document_types, ("cultcache.interop-note",)) - self.assertEqual(health.supported_mutation_contracts[0]["documentType"], "cultcache.interop-note") - finally: - process.terminate() - try: - process.communicate(timeout=5.0) - except subprocess.TimeoutExpired: - process.kill() - process.communicate(timeout=5.0) - - def test_cultmesh_daemon_restarts_with_persisted_cache_and_shard_log(self) -> None: - with tempfile.TemporaryDirectory() as temp: - root = Path(temp) - cache_path = root / "mesh.cc" - shard_log_path = root / "shard-log" - env = dict(os.environ) - package_src = Path(__file__).resolve().parents[1] / "src" - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - - first_ready = root / "first-ready.json" - first = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "durable-daemon-peer", - "--port", - "0", - "--cache-file", - str(cache_path), - "--enable-durable-shard-logs", - "--shard-log-file", - str(shard_log_path), - "--seed-interop-note", - "--seed-shard-id", - "durable-interop", - "--ready-file", - str(first_ready), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - self._wait_for_ready_file(first, first_ready) - finally: - self._terminate_process(first) - - second_ready = root / "second-ready.json" - second = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "durable-daemon-peer", - "--port", - "0", - "--cache-file", - str(cache_path), - "--enable-durable-shard-logs", - "--shard-log-file", - str(shard_log_path), - "--register-interop-note", - "--verse-id", - "durable-verse", - "--role", - "shard-primary", - "--ready-file", - str(second_ready), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(second, second_ready) - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - - snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) - self.assertEqual(snapshot_response.documents[0].record_key, "note:durable-daemon-peer") - note = interop_note_document.decode_payload(snapshot_response.documents[0].payload) - self.assertEqual(note["authorRuntimeId"], "durable-daemon-peer") - - shard_log = client.fetch_shard_log_response(shard_id="durable-interop", shard_epoch=1) - self.assertFalse(shard_log.resync_required) - self.assertEqual(shard_log.entries[0].sequence, 1) - self.assertIsNotNone(shard_log.entries[0].raw_document) - assert shard_log.entries[0].raw_document is not None - self.assertEqual(shard_log.entries[0].raw_document.record_key, "note:durable-daemon-peer") - - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - peers = discovery_client.fetch_peers(verse_id="durable-verse", roles=["shard-primary"]) - self.assertEqual(peers[0].shard_ids, ("durable-interop",)) - finally: - self._terminate_process(second) - - def test_cultmesh_daemon_serves_live_database_subscription_mutations(self) -> None: - with tempfile.TemporaryDirectory() as temp: - ready_path = Path(temp) / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "live-daemon-peer", - "--port", - "0", - "--seed-interop-note", - "--seed-shard-id", - "live-interop", - "--ready-file", - str(ready_path), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(process, ready_path) - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - live_note = { - "schemaVersion": INTEROP_SCHEMA_VERSION, - "documentId": "note:daemon-live", - "authorRuntimeId": "daemon-live-client", - "title": "daemon live subscription note", - "body": "live subscription put from Python daemon smoke", - "tags": ["daemon", "subscription", "interop"], - } - - with client.subscribe_database( - subscription_id="daemon-live-sub", - schema_ids=["cultcache.interop-note"], - ) as subscription: - snapshot = subscription.read_next_snapshot_response() - subscription.send(document_put_raw( - message_id="daemon-live-put", - key="note:daemon-live", - schema_id="cultcache.interop-note", - stored_at="2026-06-14T00:00:00Z", - payload=interop_note_document.encode_payload(live_note), - source_runtime_id="daemon-live-client", - shard_id="live-interop", - shard_epoch=1, - )) - change = subscription.read_next_change() - - self.assertEqual(snapshot.documents[0].record_key, "note:live-daemon-peer") - self.assertEqual(change.change_kind, "added") - self.assertEqual(change.record_key, "note:daemon-live") - self.assertIsNotNone(change.raw_document) - assert change.raw_document is not None - self.assertEqual( - interop_note_document.decode_payload(change.raw_document.payload), - live_note, - ) - - shard_log = client.fetch_shard_log_response(shard_id="live-interop", shard_epoch=1) - self.assertFalse(shard_log.resync_required) - self.assertEqual([entry.sequence for entry in shard_log.entries], [1, 2]) - self.assertEqual(shard_log.entries[1].change_kind, "added") - self.assertIsNotNone(shard_log.entries[1].raw_document) - assert shard_log.entries[1].raw_document is not None - self.assertEqual(shard_log.entries[1].raw_document.record_key, "note:daemon-live") - finally: - self._terminate_process(process) - - def test_cultmesh_daemon_serves_compacted_shard_log_resync_boundary(self) -> None: - with tempfile.TemporaryDirectory() as temp: - root = Path(temp) - cache_path = root / "mesh.cc" - shard_log_path = root / "shard-log" - store = CultNetFileShardMutationLogStore(shard_log_path) - node = CultMesh.create_node( - cache_path, - runtime_id="compacted-daemon-peer", - enable_durable_shard_logs=True, - shard_log_path=shard_log_path, - ) - node.database.register_document(interop_note_document) - node.database.put_raw_message( - interop_note_document, - "note:compacted-one", - { - "schemaVersion": INTEROP_SCHEMA_VERSION, - "documentId": "note:compacted-one", - "authorRuntimeId": "compacted-daemon-peer", - "title": "compacted daemon note one", - "body": "first retained cache value", - "tags": ["daemon", "compaction"], - }, - message_id="compacted-put-1", - shard_id="compacted-interop", - shard_epoch=7, - ) - node.database.put_raw_message( - interop_note_document, - "note:compacted-two", - { - "schemaVersion": INTEROP_SCHEMA_VERSION, - "documentId": "note:compacted-two", - "authorRuntimeId": "compacted-daemon-peer", - "title": "compacted daemon note two", - "body": "second retained cache value", - "tags": ["daemon", "compaction"], - }, - message_id="compacted-put-2", - shard_id="compacted-interop", - shard_epoch=7, - ) - store.compact_through("compacted-interop", 1) - - ready_path = root / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "compacted-daemon-peer", - "--port", - "0", - "--cache-file", - str(cache_path), - "--enable-durable-shard-logs", - "--shard-log-file", - str(shard_log_path), - "--register-interop-note", - "--ready-file", - str(ready_path), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(process, ready_path) - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - - snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) - self.assertEqual( - {document.record_key for document in snapshot_response.documents}, - {"note:compacted-one", "note:compacted-two"}, - ) - - compacted = client.fetch_shard_log_response( - shard_id="compacted-interop", - shard_epoch=7, - after_sequence=0, - ) - retained = client.fetch_shard_log_response( - shard_id="compacted-interop", - shard_epoch=7, - after_sequence=1, - ) - - self.assertTrue(compacted.resync_required) - self.assertEqual(compacted.reason, "compacted_history") - self.assertEqual(compacted.compacted_through, 1) - self.assertEqual(compacted.entries, ()) - self.assertFalse(retained.resync_required) - self.assertEqual([entry.sequence for entry in retained.entries], [2]) - self.assertIsNotNone(retained.entries[0].raw_document) - assert retained.entries[0].raw_document is not None - self.assertEqual(retained.entries[0].raw_document.record_key, "note:compacted-two") - finally: - self._terminate_process(process) - - def test_cultmesh_daemon_accepts_schema_write_forwarder_mutations(self) -> None: - with tempfile.TemporaryDirectory() as temp: - ready_path = Path(temp) / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "forwarder-daemon-peer", - "--port", - "0", - "--register-interop-note", - "--ready-file", - str(ready_path), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(process, ready_path) - endpoint = f"cultnet://127.0.0.1:{ready['port']}" - shard = CultNetShardDescriptor( - shard_id="forwarded-interop", - owner_runtime_id="forwarder-daemon-peer", - epoch=3, - is_primary=False, - schema_ids=("cultcache.interop-note",), - primary_endpoints=(endpoint,), - ) - forwarder: CultNetShardWriteForwarder = CultNetSchemaWriteForwarder(timeout_seconds=2.0) - note = { - "schemaVersion": INTEROP_SCHEMA_VERSION, - "documentId": "note:forwarded-daemon", - "authorRuntimeId": "forwarding-client", - "title": "forwarded daemon note", - "body": "forwarded through the schema write-forwarder", - "tags": ["daemon", "forwarded"], - } - - forwarder.forward_put(shard, document_put_raw( - message_id="forwarded-daemon-put", - key="note:forwarded-daemon", - schema_id="cultcache.interop-note", - stored_at="2026-06-14T00:00:00Z", - payload=interop_note_document.encode_payload(note), - source_runtime_id="forwarding-client", - ).to_wire()) - - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - self._eventually(lambda: bool(client.fetch_snapshot_response( - schema_ids=["cultcache.interop-note"], - record_keys=["note:forwarded-daemon"], - ).documents)) - snapshot = client.fetch_snapshot_response( - schema_ids=["cultcache.interop-note"], - record_keys=["note:forwarded-daemon"], - ) - self.assertEqual( - interop_note_document.decode_payload(snapshot.documents[0].payload), - note, - ) - - forwarder.forward_delete(shard, document_delete( - message_id="forwarded-daemon-delete", - schema_id="cultcache.interop-note", - record_key="note:forwarded-daemon", - ).to_wire()) - self._eventually(lambda: not client.fetch_snapshot_response( - schema_ids=["cultcache.interop-note"], - record_keys=["note:forwarded-daemon"], - ).documents) - - shard_log = client.fetch_shard_log_response(shard_id="forwarded-interop", shard_epoch=3) - self.assertEqual([entry.change_kind for entry in shard_log.entries], ["added", "removed"]) - self.assertEqual(shard_log.entries[0].put["shardId"], "forwarded-interop") - self.assertEqual(shard_log.entries[0].put["shardEpoch"], 3) - self.assertEqual(shard_log.entries[1].delete["shardId"], "forwarded-interop") - self.assertEqual(shard_log.entries[1].delete["shardEpoch"], 3) - finally: - self._terminate_process(process) - - def test_cultmesh_daemon_enforces_snapshot_document_limit_as_peer_error(self) -> None: - with tempfile.TemporaryDirectory() as temp: - ready_path = Path(temp) / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "limited-daemon-peer", - "--port", - "0", - "--seed-interop-note", - "--max-snapshot-documents", - "0", - "--ready-file", - str(ready_path), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(process, ready_path) - self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": 0}) - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - - with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised: - client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) - - self.assertEqual(raised.exception.response["schemaVersion"], "cultnet.error.v0") - self.assertEqual(raised.exception.response["code"], "snapshot_document_limit_exceeded") - self.assertEqual(raised.exception.response["details"]["documentCount"], 1) - self.assertEqual(raised.exception.response["details"]["maxSnapshotDocuments"], 0) - finally: - self._terminate_process(process) - - def test_cultmesh_daemon_serves_opt_in_simulation_observations(self) -> None: - with tempfile.TemporaryDirectory() as temp: - ready_path = Path(temp) / "ready.json" - package_src = Path(__file__).resolve().parents[1] / "src" - env = dict(os.environ) - existing_pythonpath = env.get("PYTHONPATH") - env["PYTHONPATH"] = ( - str(package_src) - if not existing_pythonpath - else f"{package_src}{os.pathsep}{existing_pythonpath}" - ) - process = subprocess.Popen( - [ - sys.executable, - "-m", - "cultmesh_py.daemon", - "--runtime-id", - "simulation-daemon-peer", - "--port", - "0", - "--enable-simulation-observations", - "--simulation-minimum-witnesses", - "2", - "--simulation-quorum-ratio", - "1.0", - "--verse-id", - "simulation-verse", - "--ready-file", - str(ready_path), - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - try: - ready = self._wait_for_ready_file(process, ready_path) - self.assertIn("cultnet.simulation_observation.v0", ready["supportedMessageVersions"]) - self.assertIn("cultnet.simulation_consensus_candidate.v0", ready["supportedMessageVersions"]) - client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - hello_response = client.request( - hello(runtime_id="simulation-daemon-prober"), - expected_schema_version="cultnet.hello.v0", - ) - self.assertEqual(ready["supportedMessageVersions"], hello_response["supportedMessageVersions"]) - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) - peers = discovery_client.fetch_peers(verse_id="simulation-verse", roles=["simulation-observer"]) - self.assertEqual(peers[0].peer_id, "simulation-daemon-peer") - self.assertIn("simulation-observer", peers[0].roles) - self.assertIn("read-replica", peers[0].roles) - - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") - first = client.request( - simulation_observation( - message_id="daemon-sim-1", - witness_runtime_id="watcher-1", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - second = client.request( - simulation_observation( - message_id="daemon-sim-2", - witness_runtime_id="watcher-2", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - - self.assertEqual(first["messageId"], "daemon-sim-1") - self.assertFalse(first["hasQuorum"]) - self.assertEqual(first["witnessCount"], 1) - self.assertEqual(second["messageId"], "daemon-sim-2") - self.assertTrue(second["hasQuorum"]) - self.assertEqual(second["witnessCount"], 2) - self.assertEqual(second["claimHash"], claim_hash) - finally: - self._terminate_process(process) - - def _wait_for_ready_file(self, process: subprocess.Popen[str], ready_path: Path) -> dict[str, object]: - deadline = time.monotonic() + 5.0 - while not ready_path.exists() and time.monotonic() < deadline: - if process.poll() is not None: - break - time.sleep(0.05) - if not ready_path.exists(): - _, stderr = process.communicate(timeout=2.0) - self.fail(f"cultmesh daemon did not publish readiness: {stderr}") - return json.loads(ready_path.read_text(encoding="utf-8")) - - def _terminate_process(self, process: subprocess.Popen[str]) -> None: - process.terminate() - try: - process.communicate(timeout=5.0) - except subprocess.TimeoutExpired: - process.kill() - process.communicate(timeout=5.0) - - def test_cultmesh_local_server_serves_node_and_catalogs_over_clients(self) -> None: - document = define_database_entry_type( - "mesh.server_note", - [("body", 0)], - schema_id="mesh.server_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-server") - node.database.register_document(document) - node.database.put_raw_message(document, "note:1", {"body": "served"}, shard_id="notes", shard_epoch=1) - observation_hub = CultNetSimulationObservationHub( - CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) - ) - seen_candidates: list[CultNetSimulationConsensusCandidate] = [] - unsubscribe_candidates = observation_hub.watch_candidates(seen_candidates.append) - claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") - verses = CultMeshVerseCatalog() - verses.upsert( - CultMeshVerseDescriptor( - verse_id="server-verse", - display_name="Server Verse", - authority_model="local", - compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules"), - ) - ) - peers = CultMeshPeerCatalog() - peers.upsert( - CultMeshPeerCard( - peer_id="mesh-server", - verse_id="server-verse", - endpoints=("cultnet://127.0.0.1:0",), - roles=("read-replica",), - ) - ) - - server = CultMesh.serve_node( - node, - verse_catalog=verses, - peer_catalog=peers, - observation_hub=observation_hub, - display_name="Mesh Server", - ) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - hello_response = raw_client.request(hello(runtime_id="probe"), expected_schema_version="cultnet.hello.v0") - schema_response = raw_client.fetch_schema_catalog(schema_ids=["mesh.server_note.v1"], include_schema_json=True) - rudp_client = CultMesh.create_client( - endpoint=f"rudp://127.0.0.1:{server.port}", - timeout_seconds=2.0, - connection_id=server.rudp_connection_id, - runtime_id="rudp-probe", - ) - rudp_hello_response = rudp_client.request( - hello(runtime_id="rudp-probe"), - expected_schema_version="cultnet.hello.v0", - ) - rudp_schema_response = rudp_client.fetch_schema_catalog( - schema_ids=["mesh.server_note.v1"], - include_schema_json=True, - ) - wire_schema_response = raw_client.fetch_schema_catalog(kinds=["wire_message"], include_schema_json=True) - synced_schema_catalog = CultNetSchemaCatalog() - synced_wire_descriptors = raw_client.sync_schema_catalog( - synced_schema_catalog, - kinds=["wire_message"], - include_schema_json=True, - ) - snapshot_response = raw_client.fetch_snapshot(schema_ids=["mesh.server_note.v1"]) - typed_snapshot_response = raw_client.fetch_snapshot_response(schema_ids=["mesh.server_note.v1"]) - shard_catalog = raw_client.fetch_shard_catalog(schema_ids=["mesh.server_note.v1"]) - synced_shard_catalog = CultNetShardCatalog() - synced_shards = raw_client.sync_shard_catalog(synced_shard_catalog, schema_ids=["mesh.server_note.v1"]) - shard_log = raw_client.fetch_shard_log(shard_id="notes", shard_epoch=1) - typed_shard_log = raw_client.fetch_shard_log_response(shard_id="notes", shard_epoch=1) - self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") - self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["port"], server.port) - stale_shard_log = raw_client.fetch_shard_log(shard_id="notes", shard_epoch=0) - discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) - fetched_verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") - fetched_peers = discovery_client.fetch_peers(verse_id="server-verse", roles=["read-replica"]) - first_candidate = raw_client.request( - simulation_observation( - message_id="obs-1", - witness_runtime_id="watcher-1", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - quorum_candidate = raw_client.request( - simulation_observation( - message_id="obs-2", - witness_runtime_id="watcher-2", - shard_id="arena", - shard_epoch=4, - frame=100, - subject_id="bob", - claim_kind="hit", - claim_hash=claim_hash, - claim_summary="alice shot bob first", - ), - expected_schema_version="cultnet.simulation_consensus_candidate.v0", - ) - with raw_client.subscribe_database(subscription_id="sub-server", schema_ids=["mesh.server_note.v1"]) as subscription: - subscription_snapshot = subscription.read_next_snapshot_response() - subscription.send(document_put_raw( - message_id="server-put", - key="note:2", - schema_id="mesh.server_note.v1", - stored_at="2026-06-14T00:00:00Z", - payload=document.encode_payload({"body": "subscribed"}), - shard_id="primary", - shard_epoch=1, - )) - subscription_change = subscription.read_next_change() - subscription.send(document_delete( - message_id="server-delete", - schema_id="mesh.server_note.v1", - record_key="note:2", - shard_id="primary", - shard_epoch=1, - )) - subscription_delete = subscription.read_next_change() - finally: - unsubscribe_candidates() - server.stop() - - self.assertEqual(hello_response["runtimeId"], "mesh-server") - self.assertEqual(hello_response["displayName"], "Mesh Server") - self.assertIn("cultnet.database_subscribe.v0", hello_response["supportedMessageVersions"]) - self.assertIn("cultnet.error.v0", hello_response["supportedMessageVersions"]) - self.assertIn("cultnet.document_put_raw.v0", hello_response["supportedMessageVersions"]) - self.assertIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) - self.assertEqual(hello_response["supportedMutationContracts"][0]["documentType"], "mesh.server_note") - self.assertIn("documentDelete", hello_response["supportedMutationContracts"][0]["operations"]) - self.assertIn("shardLog", hello_response["supportedMutationContracts"][0]["operations"]) - advertised_transports = { - transport["protocol"]: transport - for profile in hello_response["transportProfiles"] - for transport in profile["transports"] - } - self.assertEqual(advertised_transports["tcp_framed"]["port"], server.port) - self.assertEqual(advertised_transports["rudp"]["port"], server.port) - self.assertEqual(rudp_hello_response["runtimeId"], "mesh-server") - self.assertEqual(rudp_schema_response["schemas"][0]["schemaId"], "mesh.server_note.v1") - self.assertEqual(schema_response["schemas"][0]["schemaId"], "mesh.server_note.v1") - self.assertIn("schemaJson", schema_response["schemas"][0]) - self.assertIn("cultnet.document_delete.v0", schema_response["schemas"][0]["wireContracts"]) - self.assertIn("cultnet.shard_log_response.v0", schema_response["schemas"][0]["wireContracts"]) - wire_descriptors = {schema["schemaVersion"]: schema for schema in wire_schema_response["schemas"]} - self.assertEqual(wire_descriptors["cultnet.error.v0"]["kind"], "wire_message") - self.assertEqual(wire_descriptors["cultnet.document_put_raw.v0"]["kind"], "wire_message") - self.assertIn("schemaJson", wire_descriptors["cultnet.document_put_raw.v0"]) - self.assertIn("cultmesh.peer_exchange_response.v0", wire_descriptors) - self.assertIn("cultnet.error.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) - self.assertIn("cultnet.document_put_raw.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) - self.assertIsNotNone(synced_schema_catalog.get("https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-put-raw.schema.json")) - self.assertEqual(snapshot_response["documents"][0]["recordKey"], "note:1") - self.assertEqual(typed_snapshot_response.documents[0].record_key, "note:1") - self.assertEqual(typed_snapshot_response.documents[0].schema_id, "mesh.server_note.v1") - self.assertEqual(shard_catalog["shards"][0]["schemaIds"], ["mesh.server_note.v1"]) - self.assertEqual(shard_catalog["shards"][0]["epoch"], 1) - self.assertEqual(shard_catalog["shards"][0]["shardId"], "notes") - self.assertEqual(synced_shards[0].shard_id, "notes") - self.assertEqual(synced_shards[0].epoch, 1) - self.assertEqual(synced_shard_catalog.get("notes"), synced_shards[0]) - self.assertEqual(shard_log["entries"][0]["changeKind"], "added") - self.assertTrue(stale_shard_log["resyncRequired"]) - self.assertEqual(stale_shard_log["reason"], "stale_epoch") - self.assertEqual(stale_shard_log["shardEpoch"], 1) - self.assertEqual(stale_shard_log["entries"], []) - self.assertEqual(typed_shard_log.last_sequence, 1) - self.assertEqual(typed_shard_log.entries[0].put["document"]["recordKey"], "note:1") - self.assertIsInstance(typed_shard_log.entries[0].raw_document, CultNetRawDocumentRecord) - self.assertEqual(typed_shard_log.entries[0].raw_document.record_key, "note:1") - self.assertEqual(fetched_verses[0].verse_id, "server-verse") - self.assertEqual(fetched_peers[0].peer_id, "mesh-server") - self.assertEqual(first_candidate["messageId"], "obs-1") - self.assertFalse(first_candidate["hasQuorum"]) - self.assertEqual(quorum_candidate["messageId"], "obs-2") - self.assertEqual(quorum_candidate["claimHash"], claim_hash) - self.assertTrue(quorum_candidate["hasQuorum"]) - self.assertEqual(quorum_candidate["witnessCount"], 2) - self.assertEqual([candidate.claim_hash for candidate in seen_candidates], [claim_hash, claim_hash]) - self.assertIsInstance(subscription_snapshot, CultNetRawSnapshotResponse) - self.assertEqual(subscription_snapshot.to_wire()["schemaVersion"], "cultnet.snapshot_response_raw.v0") - self.assertEqual(subscription_change.change_kind, "added") - self.assertEqual(subscription_change.record_key, "note:2") - self.assertEqual(subscription_change.document["recordKey"], "note:2") - self.assertIsInstance(subscription_change.raw_document, CultNetRawDocumentRecord) - self.assertEqual(subscription_change.raw_document.schema_id, "mesh.server_note.v1") - self.assertEqual(subscription_delete.change_kind, "removed") - self.assertEqual(subscription_delete.record_key, "note:2") - self.assertIsNone(subscription_delete.raw_document) - self.assertIsNone(node.database.get(document, "note:2")) - - def test_cultmesh_local_server_resolves_snapshot_and_shard_catalog_schema_aliases(self) -> None: - document = define_database_entry_type( - "mesh.alias_server_note", - [("body", 0)], - schema_id="sha256:mesh-alias-server-note", - schema_name="mesh.alias_server_note", - schema_version="mesh.alias_server_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-server") - node.database.register_document(document) - node.database.put_raw_message( - document, - "note:alias", - {"body": "served through alias"}, - shard_id="notes", - shard_epoch=1, - ) - - server = CultMesh.serve_node(node) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - snapshot = raw_client.fetch_snapshot(schema_ids=["mesh.alias_server_note.v1"]) - typed_snapshot = raw_client.fetch_snapshot_response(schema_ids=["mesh.alias_server_note.v1"]) - shard_catalog = raw_client.fetch_shard_catalog(schema_ids=["mesh.alias_server_note.v1"]) - finally: - server.stop() - - local_schema_id = document.catalog_entry().schema_id - self.assertEqual(snapshot["documents"][0]["schemaId"], local_schema_id) - self.assertEqual(snapshot["documents"][0]["recordKey"], "note:alias") - self.assertEqual(typed_snapshot.documents[0].schema_id, local_schema_id) - self.assertEqual(shard_catalog["shards"][0]["schemaIds"], [local_schema_id]) - self.assertEqual(shard_catalog["shards"][0]["shardId"], "notes") - - def test_cultmesh_shard_snapshot_keeps_foreign_schema_alias_membership(self) -> None: - document = define_database_entry_type( - "mesh.alias_shard_note", - [ - ("schema_version", 0), - ("body", 1), - ], - schema_id="sha256:mesh-alias-shard-note", - schema_name="mesh.alias_shard_note", - schema_version="mesh.alias_shard_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-shard-server") - node.database.register_document(document) - - node.database.apply_raw_put_message(document_put_raw( - message_id="put-foreign-shard-alias", - key="note:foreign-shard", - schema_id="runtime.generated.mesh.alias_shard_note.ui.99", - stored_at="2026-06-27T00:00:00Z", - payload=document.encode_payload({ - "schema_version": "mesh.alias_shard_note.v1", - "body": "visible through shard snapshot", - }), - shard_id="notes", - shard_epoch=1, - ).to_wire()) - node.database.put_raw_message( - document, - "note:other-shard", - { - "schema_version": "mesh.alias_shard_note.v1", - "body": "hidden by shard filter", - }, - shard_id="other", - shard_epoch=1, - ) - - response = node.database.build_snapshot_response( - schema_ids=["mesh.alias_shard_note.v1"], - shard_id="notes", - shard_epoch=1, - ) - - local_schema_id = document.catalog_entry().schema_id - self.assertEqual(response.shard_id, "notes") - self.assertEqual(response.shard_epoch, 1) - self.assertEqual([(record.schema_id, record.record_key) for record in response.documents], [ - (local_schema_id, "note:foreign-shard"), - ]) - self.assertEqual(node.cache.get_required_envelope(document, "note:foreign-shard").schema_id, local_schema_id) - - node.database.apply_raw_delete_message(document_delete( - message_id="delete-foreign-shard-alias", - schema_id="mesh.alias_shard_note.v1", - record_key="note:foreign-shard", - shard_id="notes", - shard_epoch=1, - ).to_wire()) - deleted_response = node.database.build_snapshot_response( - schema_ids=["mesh.alias_shard_note.v1"], - shard_id="notes", - shard_epoch=1, - ) - self.assertEqual(deleted_response.documents, ()) - - def test_cultmesh_local_server_notifies_schema_alias_subscription_for_raw_put(self) -> None: - document = define_database_entry_type( - "mesh.alias_sub_note", - [ - ("schema_version", 0), - ("body", 1), - ], - schema_id="sha256:mesh-alias-sub-note", - schema_name="mesh.alias_sub_note", - schema_version="mesh.alias_sub_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-sub-server") - node.database.register_document(document) - - server = CultMesh.serve_node(node) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - with raw_client.subscribe_database( - subscription_id="sub-alias", - schema_ids=["mesh.alias_sub_note.v1"], - include_snapshot=False, - ) as subscription: - subscription.send(document_put_raw( - message_id="put-foreign-alias", - key="note:alias-sub", - schema_id="runtime.generated.mesh.alias_sub_note.ui.99", - stored_at="2026-06-27T00:00:00Z", - payload=document.encode_payload({ - "schema_version": "mesh.alias_sub_note.v1", - "body": "notified", - }), - )) - change = subscription.read_next_change() - finally: - server.stop() - - local_schema_id = document.catalog_entry().schema_id - self.assertEqual(change.subscription_id, "sub-alias") - self.assertEqual(change.change_kind, "added") - self.assertEqual(change.record_key, "note:alias-sub") - self.assertIsInstance(change.raw_document, CultNetRawDocumentRecord) - self.assertEqual(change.raw_document.schema_id, "runtime.generated.mesh.alias_sub_note.ui.99") - self.assertEqual(node.database.get_required(document, "note:alias-sub")["body"], "notified") - self.assertEqual(node.cache.get_required_envelope(document, "note:alias-sub").schema_id, local_schema_id) - - def test_cultmesh_local_server_notifies_schema_alias_subscription_for_delete(self) -> None: - document = define_database_entry_type( - "mesh.alias_delete_note", - [("body", 0)], - schema_id="sha256:mesh-alias-delete-note", - schema_name="mesh.alias_delete_note", - schema_version="mesh.alias_delete_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-alias-delete-server") - node.database.register_document(document) - node.database.put_raw_message( - document, - "note:delete-alias", - {"body": "remove me"}, - shard_id="notes", - shard_epoch=1, - ) - - server = CultMesh.serve_node(node) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - with raw_client.subscribe_database( - subscription_id="sub-delete-alias", - schema_ids=[document.catalog_entry().schema_id], - include_snapshot=False, - ) as subscription: - subscription.send(document_delete( - message_id="delete-foreign-alias", - schema_id="mesh.alias_delete_note.v1", - record_key="note:delete-alias", - shard_id="notes", - shard_epoch=1, - )) - change = subscription.read_next_change() - finally: - server.stop() - - self.assertEqual(change.subscription_id, "sub-delete-alias") - self.assertEqual(change.change_kind, "removed") - self.assertEqual(change.schema_id, "mesh.alias_delete_note.v1") - self.assertEqual(change.record_key, "note:delete-alias") - self.assertIsNone(change.raw_document) - self.assertIsNone(node.database.get(document, "note:delete-alias")) - - def test_cultmesh_local_server_rejects_oversized_snapshot_responses(self) -> None: - document = define_database_entry_type( - "mesh.snapshot_limit_note", - [("body", 0)], - schema_id="mesh.snapshot_limit_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-snapshot-limits") - node.database.register_document(document) - node.database.put_raw_message(document, "note:1", {"body": "first"}) - node.database.put_raw_message(document, "note:2", {"body": "second"}) - - server = CultMesh.serve_node(node, max_snapshot_documents=1) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - filtered_snapshot = raw_client.fetch_snapshot(record_keys=["note:1"]) - error = raw_client.request( - snapshot_request(schema_ids=["mesh.snapshot_limit_note.v1"]), - expected_schema_version="cultnet.error.v0", - ) - with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded"): - raw_client.fetch_snapshot(schema_ids=["mesh.snapshot_limit_note.v1"]) - finally: - server.stop() - - self.assertEqual(filtered_snapshot["schemaVersion"], "cultnet.snapshot_response_raw.v0") - self.assertEqual([document["recordKey"] for document in filtered_snapshot["documents"]], ["note:1"]) - self.assertEqual(error["schemaVersion"], "cultnet.error.v0") - self.assertIn("Snapshot document limit exceeded", error["error"]) - self.assertEqual(error["code"], "snapshot_document_limit_exceeded") - self.assertEqual(error["details"]["documentCount"], 2) - self.assertEqual(error["details"]["maxSnapshotDocuments"], 1) - - def test_cultmesh_local_server_returns_errors_for_bad_requests(self) -> None: - document = define_database_entry_type( - "mesh.error_note", - [("body", 0)], - schema_id="mesh.error_note.v1", - ) - node = CultMesh.create_node(runtime_id="mesh-error-server") - node.database.register_document(document) - - server = CultMesh.serve_node(node) - try: - raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) - unsupported = raw_client.request( - {"schemaVersion": "cultnet.nope.v0", "messageId": "bad-schema"}, - expected_schema_version="cultnet.error.v0", - ) - malformed_put = raw_client.request( - {"schemaVersion": "cultnet.document_put_raw.v0", "messageId": "bad-put"}, - expected_schema_version="cultnet.error.v0", - ) - malformed_delete = raw_client.request( - {"schemaVersion": "cultnet.document_delete.v0", "messageId": "bad-delete", "schemaId": "mesh.error_note.v1"}, - expected_schema_version="cultnet.error.v0", - ) - finally: - server.stop() - - self.assertEqual(unsupported["messageId"], "bad-schema") - self.assertEqual(unsupported["code"], "unsupported_schema_version") - self.assertEqual(unsupported["details"]["schemaVersion"], "cultnet.nope.v0") - self.assertIn("Unsupported CultNet message schema", unsupported["error"]) - self.assertEqual(malformed_put["messageId"], "bad-put") - self.assertEqual(malformed_put["code"], "malformed_document_put") - self.assertEqual(malformed_delete["messageId"], "bad-delete") - self.assertEqual(malformed_delete["code"], "malformed_document_delete") - - def test_cultmesh_authority_lease_requires_live_matching_lease(self) -> None: - peer = CultMeshPeerCard( - peer_id="voidbot-local", - verse_id="local", - endpoints=("cultmesh://localhost",), - roles=("shard-primary",), - authority_lease_id="lease:voidbot-local", - ) - leases = CultMeshAuthorityLeaseCatalog() - peers = CultMeshPeerCatalog() - peers.upsert(peer) - now = datetime.now(UTC) - self.assertFalse(leases.is_authorized(peer, "shard-primary", at=now)) - self.assertEqual(peers.find_authorized("local", "shard-primary", leases, at=now), []) - - leases.upsert( - CultMeshAuthorityLease( - lease_id="lease:voidbot-local", - verse_id="local", - peer_id="voidbot-local", - roles=("shard-primary", "shard-primary", ""), - valid_from=now - timedelta(seconds=1), - expires_at=now + timedelta(seconds=1), - shard_ids=("shard-a",), - issuer_runtime_id="odin", - signature="signed", - ) - ) - - lease = leases.get("lease:voidbot-local") - self.assertIsNotNone(lease) - assert lease is not None - self.assertEqual(leases.leases, (lease,)) - self.assertEqual(lease.roles, ("shard-primary",)) - self.assertEqual(lease.issuer_runtime_id, "odin") - self.assertEqual(lease.signature, "signed") - self.assertTrue(lease.is_valid_at(now)) - self.assertTrue(lease.covers(peer, "shard-primary", shard_id="shard-a", at=now)) - self.assertFalse(lease.covers(peer, "shard-primary", shard_id="shard-b", at=now)) - self.assertTrue(leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) - self.assertEqual( - peers.find_authorized("local", "shard-primary", leases, shard_id="shard-a", at=now), - [peer], - ) - self.assertEqual( - peers.first_authorized("local", "shard-primary", leases, shard_id="shard-a", at=now), - peer, - ) - self.assertIsNone(peers.first_authorized("local", "read-replica", leases, at=now)) - self.assertFalse(leases.is_authorized(peer, "shard-primary", shard_id="shard-b", at=now)) - self.assertFalse(leases.is_authorized(peer, "read-replica", at=now)) - self.assertIsNone(leases.get("missing")) - - def test_cultmesh_authority_lease_catalog_can_require_verified_signatures(self) -> None: - peer = CultMeshPeerCard( - peer_id="voidbot-local", - verse_id="local", - endpoints=("cultmesh://localhost",), - roles=("shard-primary",), - shard_ids=("shard-a",), - authority_lease_id="lease:voidbot-local", - ) - now = datetime.now(UTC) - unsigned = CultMeshAuthorityLease( - lease_id="lease:voidbot-local", - verse_id="local", - peer_id="voidbot-local", - roles=("shard-primary",), - valid_from=now - timedelta(seconds=1), - expires_at=now + timedelta(seconds=30), - shard_ids=("shard-a",), - issuer_runtime_id="odin", - ) - verifier = CultMeshHmacAuthorityLeaseVerifier({"odin": b"lease-secret"}) - signed = verifier.issue(unsigned) - strict_leases = CultMesh.create_authority_lease_catalog( - signature_verifier=verifier.verify, - require_verified_signatures=True, - ) - strict_leases.upsert(unsigned) - self.assertFalse(strict_leases.is_verified(unsigned.lease_id)) - self.assertFalse(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) - - strict_leases.upsert(signed) - self.assertTrue(strict_leases.is_verified(signed.lease_id)) - self.assertTrue(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) - - wrong_key = CultMesh.create_authority_lease_catalog( - signature_verifier=CultMeshHmacAuthorityLeaseVerifier({"odin": b"wrong-secret"}).verify, - require_verified_signatures=True, - ) - wrong_key.upsert(signed) - self.assertFalse(wrong_key.is_verified(signed.lease_id)) - self.assertFalse(wrong_key.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) - - tampered = CultMeshAuthorityLease( - lease_id=signed.lease_id, - verse_id=signed.verse_id, - peer_id=signed.peer_id, - roles=("shard-primary", "read-replica"), - valid_from=signed.valid_from, - expires_at=signed.expires_at, - shard_ids=signed.shard_ids, - issuer_runtime_id=signed.issuer_runtime_id, - signature=signed.signature, - ) - strict_leases.upsert(tampered) - self.assertFalse(strict_leases.is_verified(tampered.lease_id)) - self.assertFalse(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) - - def test_cultmesh_stream_catalog_negotiates_transport_and_latest_frame(self) -> None: - streams = CultMeshStreamCatalog() - streams.declare( - CultMeshStreamDescriptor( - stream_id="mimir:kiyo-pro", - verse_id="studio", - owner_peer_id="starfire", - kind="video", - label="Kiyo Pro", - clock={"clockDomainId": "starfire-qpc", "confidence": 0.25}, - video={"width": 1920, "height": 1080, "pixelFormat": "YUY2", "framesPerSecond": 30}, - preferred_transports=("shared-d3d12-texture", "shared-memory", "cultcache-page"), - max_in_flight_frames=3, - ) - ) - - negotiation = streams.negotiate( - "mimir:kiyo-pro", - CultMeshStreamConsumerProfile( - peer_id="fensalir", - verse_id="studio", - supported_transports=("shared-d3d12-texture", "cultcache-page"), - accepted_kinds=("video",), - can_import_gpu_handles=True, - max_in_flight_frames=2, - ), - ) - - self.assertEqual(negotiation.transport, "shared-d3d12-texture") - self.assertEqual(negotiation.max_in_flight_frames, 2) - self.assertEqual(negotiation.copy_budget, "zero-copy-target") - - streams.publish_frame( - CultMeshStreamFrameHandle( - stream_id="mimir:kiyo-pro", - sequence=42, - timestamp_ns=1_000_000_000, - duration_ns=33_333_334, - transport="shared-d3d12-texture", - native_handle="0xfeed", - fence_handle="0xbeef", - fence_value=7, - unavoidable_copy_count=0, - ) - ) - self.assertEqual(streams.latest_frame("mimir:kiyo-pro").sequence, 42) - - -class CultNetRudpFragmentBoundTests(unittest.TestCase): - def _pair(self) -> tuple[CultNetRudpSession, CultNetRudpSession]: - sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=1)) - receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=100)) - sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 90, 0, 0, "control")) - receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 91, 0, 0, "control")) - return sender, receiver - - def test_stranded_fragment_sets_are_bounded_and_evicted_oldest_first(self) -> None: - sender, receiver = self._pair() - receiver._max_pending_fragment_sets = 4 - - # Eight payloads each lose their last fragment; the map must not grow past four. - stranded = [] - for fill in range(1, 9): - packets = sender.send_many("media", bytes([fill]) * 2500, max_fragment_bytes=1000) - self.assertEqual(len(packets), 3) - for packet in packets[:2]: - self.assertEqual(receiver.receive(packet).delivered, ()) - stranded.append(packets[2]) - self.assertEqual(len(receiver._fragment_buffers), 4) - self.assertEqual(receiver.fragment_sets_evicted, 4) - - # A complete payload still lands. - delivered = [] - for packet in sender.send_many("media", bytes([9]) * 2500, max_fragment_bytes=1000): - delivered.extend(receiver.receive(packet).delivered) - self.assertEqual([frame.payload for frame in delivered], [bytes([9]) * 2500]) - self.assertEqual(receiver.fragment_sets_evicted, 5) - - # Oldest-first: set 8 still pends and completes late; set 1 was evicted. - self.assertEqual(receiver.receive(stranded[7]).delivered[0].payload, bytes([8]) * 2500) - self.assertEqual(receiver.receive(stranded[0]).delivered, ()) - if __name__ == "__main__": unittest.main() diff --git a/packages/cultcache-ts/test/cult-cache.test.ts b/packages/cultcache-ts/test/cult-cache.test.ts index fb206eb6..5463191f 100644 --- a/packages/cultcache-ts/test/cult-cache.test.ts +++ b/packages/cultcache-ts/test/cult-cache.test.ts @@ -3,7 +3,7 @@ import { exec, execFile } from "node:child_process"; import { existsSync } from "node:fs"; import { access, mkdtemp, readFile, writeFile } from "node:fs/promises"; import { homedir, tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; +import { dirname, join, resolve, delimiter } from "node:path"; import { test } from "node:test"; import { promisify } from "node:util"; @@ -24,7 +24,9 @@ const pythonCommand = process.env.PYTHON ?? "python"; const cultCacheTsRoot = resolve(__dirname, "../.."); const cultLibRoot = findAncestor(cultCacheTsRoot, "CultLib.sln") ?? resolve(cultCacheTsRoot, "..", "CultLib"); const cultcachePyRoot = resolve(cultLibRoot, "packages", "cultcache-py"); -const cultcachePySrc = resolve(cultcachePyRoot, "src"); +const cultcachePySrc = ["cultcache-py", "cultnet-py", "cultmesh-py"] + .map((name) => resolve(cultLibRoot, "packages", name, "src")) + .join(delimiter); const cultcacheRsRoot = existsSync(resolve(cultLibRoot, "packages", "cultcache-rs")) ? resolve(cultLibRoot, "packages", "cultcache-rs") : resolve(cultCacheTsRoot, "..", "cultcache-rs"); diff --git a/packages/cultmesh-py/README.md b/packages/cultmesh-py/README.md new file mode 100644 index 00000000..63799e52 --- /dev/null +++ b/packages/cultmesh-py/README.md @@ -0,0 +1,252 @@ +# cultmesh-py + +`cultmesh-py` is the Python CultMesh runtime: a cache-backed local node, +reactive and observed documents, the branded `CultMesh` facade, Verse and peer +discovery clients, snapshot and observation fan-out, peer health monitoring, +game sessions with prediction reconciliation, committed simulation facts, the +local TCP/RUDP server, and a package daemon. + +It depends on `cultcache-py` and `cultnet-py`, mirroring `cultmesh-rs` and +`cultmesh-ts`. The Verse catalog, peer exchange, authority lease, stream +negotiation, and simulation fact contracts are defined in +`cultnet_py.cultmesh_contracts` and re-exported here. + +This is the package a Python process embeds to serve a Verse — the Brokkr +Blender add-on serves its editor mirror through `CultMesh.serve_node(...)` +from inside Blender's own interpreter. + +## Example + +```python +from datetime import UTC, datetime, timedelta + +from cultcache_py import define_database_entry_type +from cultnet_py import ( + CultNetClientAuthorityScope, + CultNetFileShardReplicaCursorStore, + CultNetRawClient, + CultNetSchemaShardLogFetcher, + CultNetSchemaShardSnapshotFetcher, + CultNetShardDescriptor, + CultNetShardReplicator, + CultNetShardReplicatorOptions, +) +from cultmesh_py import ( + CultMesh, + CultMeshAuthorityLease, + CultMeshGameSessionOptions, + CultMeshHmacAuthorityLeaseVerifier, + CultMeshNodeOptions, + CultMeshSnapshotFanout, + peer_exchange_request, +) + +note_doc = define_database_entry_type( + "mesh.note", + [("body", 0), ("name", 1), ("kind", 2)], + name="name", + indexes={"kind": "kind"}, +) +node = CultMesh.start_node( + "mesh.cc", + runtime_id="python-runtime", + options=CultMeshNodeOptions(enable_durable_shard_logs=True), +) +node.database.register_document(note_doc) +unsubscribe = node.database.watch_record(note_doc, "note:1", lambda change: print(change.change_kind)) +unsubscribe_named = node.database.watch_by_name(note_doc, "intro", lambda change: print(change.record_key)) +unsubscribe_kind = node.database.watch_by_index(note_doc, "kind", "guide", lambda change: print(change.record_key)) +node.database.put(note_doc, "note:1", {"body": "hello", "name": "intro", "kind": "guide"}) +live_note = node.database.get_required(note_doc, "note:1") +put_message = node.database.put_raw_message( + note_doc, + "note:2", + {"body": "wire me", "name": "wire", "kind": "interop"}, + shard_id="interop", + shard_epoch=1, +) +snapshot_response = node.database.build_snapshot_response(schema_ids=[note_doc.catalog_entry().schema_id]) +snapshot_wire = node.database.create_snapshot_response(schema_ids=[note_doc.catalog_entry().schema_id]) +delete_message = node.database.delete_raw_message(note_doc, "note:2", shard_id="interop", shard_epoch=1) +shard_log_response = node.database.build_shard_log_response(shard_id="interop", shard_epoch=1) +shard_log_wire = node.database.create_shard_log_response(shard_id="interop", shard_epoch=1) + +station_stock_doc = define_database_entry_type( + "aetheria.station_stock", + [("missiles", 0), ("coolant", 1)], + schema_id="gamecult.aetheria.station_stock.v1", +) +station_stock_ui_doc = define_database_entry_type( + "aetheria.station_stock.ui", + [("missiles", 0), ("coolant", 1)], + schema_id="gamecult.aetheria.station_stock.v1", +) +node.database.register_document(station_stock_doc) +node.database.put(station_stock_ui_doc, "station:starbridge:stock", {"missiles": 24, "coolant": 80}) +stock = node.database.get_required(station_stock_ui_doc, "station:starbridge:stock") + +peers = CultMesh.create_peer_catalog() +response = peers.create_response(peer_exchange_request("pex-1", verse_id="local")) +lease_verifier = CultMeshHmacAuthorityLeaseVerifier({"odin": b"shared-lease-key"}) +lease_catalog = CultMesh.create_authority_lease_catalog( + signature_verifier=lease_verifier.verify, + require_verified_signatures=True, +) +lease_valid_from = datetime.now(UTC) +lease = lease_verifier.issue(CultMeshAuthorityLease( + lease_id="lease:python-runtime", + verse_id="local", + peer_id="python-runtime", + roles=("shard-primary",), + valid_from=lease_valid_from, + expires_at=lease_valid_from + timedelta(minutes=5), + shard_ids=("interop",), + issuer_runtime_id="odin", +)) +lease_catalog.upsert(lease) +session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope("python-runtime", schema_ids=(note_doc.catalog_entry().schema_id,), key_prefix="input:python"), + ), + ), +) +server = CultMesh.serve_node( + node, + peer_catalog=peers, + observation_hub=session.observation_hub, + port=3075, + max_snapshot_documents=1000, + max_snapshot_bytes=4 * 1024 * 1024, +) +``` + +The same local server can be launched as a package daemon when another runtime +or operator process needs a CultNet endpoint without embedding Python glue: + +```powershell +python -m cultmesh_py.daemon --runtime-id python-runtime --display-name "Python Runtime" --host 127.0.0.1 --port 3075 --cache-file mesh.cc --enable-durable-shard-logs --seed-interop-note --seed-shard-id interop --verse-id local --role shard-primary --ready-file mesh.ready.json +``` + +The ready file and stdout line are process readiness hints. The served state and +capability truth still live on the framed CultNet/CultMesh endpoint. Use +`--register-interop-note` to expose the package interop-note schema without +seeding a note, or `--seed-interop-note` to register it and publish one local +record for wire probes. Seeded records are committed through the raw mutation +path, so peers can read them through both snapshot and shard-log catch-up. Add +`--verse-id` and one or more `--role` values to publish the launched endpoint in +its own Verse and peer catalogs for discovery clients. When the daemon is +launched with `--cache-file`, `--enable-durable-shard-logs`, and +`--shard-log-file`, a later daemon process can rehydrate and serve the persisted +snapshot and shard log without reseeding. + +`CultMeshPeerHealthMonitor` probes peer-card endpoints with `cultnet.hello.v0` +and preserves the runtime id, display name, document types, message versions, +and mutation contracts reported by the peer. + +```python +client = CultMesh.create_verse_discovery_client("127.0.0.1", 3075) +verses = client.fetch_verses(transport_version="cultmesh.v0") +mesh_peers = client.fetch_peers(verse_id="python-interop", roles=["read-replica"]) +client.sync_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"]) +client.fanout_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"]) +replica_notes = client.sync_documents( + node.database, + peers, + verse_id="python-interop", + roles=["read-replica"], + documents=[(note_doc, "note:remote")], +) +snapshot_fanout = CultMeshSnapshotFanout( + client, + node.database, + peers, + verse_id="python-interop", + roles=["read-replica"], + documents=[(note_doc, "note:remote")], + on_document=lambda note: print(note), +) +snapshot_fanout.sync_once() + +raw_client = CultNetRawClient("127.0.0.1", 3075) +synced_note = node.database.sync_document(raw_client, note_doc, "note:remote") +synced_stock = CultMesh.sync_document( + node, + raw_client, + station_stock_ui_doc, + "station:starbridge:stock", +) +with CultMesh.subscribe_document( + node, + raw_client, + station_stock_ui_doc, + "station:starbridge:stock", +) as stock_subscription: + stock = stock_subscription.sync_initial() + next_change = stock_subscription.read_next_change() +node.database.sync_shard_log(raw_client, shard_id="interop", shard_epoch=1) +replicator = CultNetShardReplicator( + node.database, + CultNetShardReplicatorOptions( + fetcher=CultNetSchemaShardLogFetcher(), + snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(), + cursor_store=CultNetFileShardReplicaCursorStore("mesh.replica-cursors.msgpack"), + poll_interval_seconds=1.0, + on_error=lambda error: print(error), + ), +) +replica_shard = CultNetShardDescriptor( + shard_id="interop", + owner_runtime_id="primary-runtime", + epoch=1, + schema_ids=(note_doc.catalog_entry().schema_id,), + primary_endpoints=("cultnet://127.0.0.1:3075",), +) +replicator.pull_once(replica_shard) +replicator.start([replica_shard]) +replicator.stop() +server.stop() + +streams = CultMesh.create_stream_catalog() + +facts = CultMesh.create_simulation_fact_committer(node) +committed = facts.commit({ + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": "accepted-claim-hash", + "witnessCount": 2, + "supportWeight": 2.0, + "totalWeight": 2.0, + "confidence": 1.0, + "hasQuorum": True, +}) +prediction = session.predict(note_doc, "input:python:move", {"body": "predicted input"}) +unsubscribe() +``` + +## Verify + +`cultmesh_py.verify` checks the public exports and `py.typed` markers of all +three Python packages, smokes a local node over the wire, checks the served +capability truth, and runs the benchmark for sanity: + +```powershell +python -m cultmesh_py.verify --json +``` + +## Tests + +```powershell +$env:PYTHONPATH="$PWD\packages\cultcache-py\src;$PWD\packages\cultnet-py\src;$PWD\packages\cultmesh-py\src" +python -m unittest discover -s packages\cultmesh-py\tests +``` + +`packages\cultnet-ts\test\interop\cultnet-interop.test.ts` verifies the public +`CultMeshDiscoveryClient` can fetch typed Verse and peer descriptors from the +live Python peer. See +[docs/python-runtime-parity.md](../../docs/python-runtime-parity.md) for the +evidence map across the three Python packages. diff --git a/packages/cultmesh-py/pyproject.toml b/packages/cultmesh-py/pyproject.toml new file mode 100644 index 00000000..847b18e4 --- /dev/null +++ b/packages/cultmesh-py/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "cultmesh-py" +version = "0.1.0" +description = "Python CultMesh node, discovery, sessions, and local server for GameCult typed state." +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +authors = [{ name = "GameCult" }] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = ["cultcache-py>=0.2.0", "cultnet-py>=0.1.0"] +[project.scripts] +cultmesh-py-daemon = "cultmesh_py.daemon:main" +cultmesh-py-verify = "cultmesh_py.verify:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +cultmesh_py = ["py.typed"] diff --git a/packages/cultcache-py/src/cultmesh_py/__init__.py b/packages/cultmesh-py/src/cultmesh_py/__init__.py similarity index 98% rename from packages/cultcache-py/src/cultmesh_py/__init__.py rename to packages/cultmesh-py/src/cultmesh_py/__init__.py index 54b68343..af4da272 100644 --- a/packages/cultcache-py/src/cultmesh_py/__init__.py +++ b/packages/cultmesh-py/src/cultmesh_py/__init__.py @@ -39,7 +39,7 @@ from .simulation import ( CultMeshSimulationFactCommitter, simulation_fact_document, ) -from .wire import ( +from cultnet_py.cultmesh_contracts import ( PEER_EXCHANGE_REQUEST, PEER_EXCHANGE_RESPONSE, VERSE_CATALOG_REQUEST, diff --git a/packages/cultcache-py/src/cultmesh_py/client.py b/packages/cultmesh-py/src/cultmesh_py/client.py similarity index 99% rename from packages/cultcache-py/src/cultmesh_py/client.py rename to packages/cultmesh-py/src/cultmesh_py/client.py index e69e0403..2e1c3ab1 100644 --- a/packages/cultcache-py/src/cultmesh_py/client.py +++ b/packages/cultmesh-py/src/cultmesh_py/client.py @@ -9,7 +9,7 @@ from urllib.parse import urlparse from cultcache_py.documents import DocumentDefinition from cultnet_py import CultNetDatabaseSubscription, CultNetRawClient, CultNetRawSnapshotResponse, hello -from .wire import ( +from cultnet_py.cultmesh_contracts import ( PEER_EXCHANGE_RESPONSE, VERSE_CATALOG_RESPONSE, CultMeshPeerCard, diff --git a/packages/cultcache-py/src/cultmesh_py/daemon.py b/packages/cultmesh-py/src/cultmesh_py/daemon.py similarity index 99% rename from packages/cultcache-py/src/cultmesh_py/daemon.py rename to packages/cultmesh-py/src/cultmesh_py/daemon.py index 087dc218..d21d42d8 100644 --- a/packages/cultcache-py/src/cultmesh_py/daemon.py +++ b/packages/cultmesh-py/src/cultmesh_py/daemon.py @@ -13,7 +13,7 @@ from cultnet_py import CultNetSimulationConsensusOptions, CultNetSimulationObser from .facade import CultMesh from .server import CultMeshLocalServer -from .wire import ( +from cultnet_py.cultmesh_contracts import ( CultMeshPeerCard, CultMeshPeerCatalog, CultMeshVerseCatalog, diff --git a/packages/cultcache-py/src/cultmesh_py/facade.py b/packages/cultmesh-py/src/cultmesh_py/facade.py similarity index 99% rename from packages/cultcache-py/src/cultmesh_py/facade.py rename to packages/cultmesh-py/src/cultmesh_py/facade.py index 4c9c34a0..53328aab 100644 --- a/packages/cultcache-py/src/cultmesh_py/facade.py +++ b/packages/cultmesh-py/src/cultmesh_py/facade.py @@ -34,7 +34,7 @@ from .node import ( from .server import CultMeshLocalServer from .session import CultMeshGameSession, CultMeshGameSessionOptions from .simulation import CultMeshSimulationFactCommitter -from .wire import ( +from cultnet_py.cultmesh_contracts import ( AuthorityLeaseVerifier, CultMeshAuthorityLeaseCatalog, CultMeshPeerCard, diff --git a/packages/cultcache-py/src/cultmesh_py/node.py b/packages/cultmesh-py/src/cultmesh_py/node.py similarity index 100% rename from packages/cultcache-py/src/cultmesh_py/node.py rename to packages/cultmesh-py/src/cultmesh_py/node.py diff --git a/packages/cultcache-py/src/cultmesh_py/py.typed b/packages/cultmesh-py/src/cultmesh_py/py.typed similarity index 100% rename from packages/cultcache-py/src/cultmesh_py/py.typed rename to packages/cultmesh-py/src/cultmesh_py/py.typed diff --git a/packages/cultcache-py/src/cultmesh_py/server.py b/packages/cultmesh-py/src/cultmesh_py/server.py similarity index 99% rename from packages/cultcache-py/src/cultmesh_py/server.py rename to packages/cultmesh-py/src/cultmesh_py/server.py index 6f124480..e0c7fdd0 100644 --- a/packages/cultcache-py/src/cultmesh_py/server.py +++ b/packages/cultmesh-py/src/cultmesh_py/server.py @@ -24,7 +24,7 @@ from cultnet_py import ( ) from .node import CultMeshNode -from .wire import ( +from cultnet_py.cultmesh_contracts import ( PEER_EXCHANGE_REQUEST, VERSE_CATALOG_REQUEST, CultMeshPeerCatalog, diff --git a/packages/cultcache-py/src/cultmesh_py/session.py b/packages/cultmesh-py/src/cultmesh_py/session.py similarity index 98% rename from packages/cultcache-py/src/cultmesh_py/session.py rename to packages/cultmesh-py/src/cultmesh_py/session.py index 7e4b1bc1..c02696f0 100644 --- a/packages/cultcache-py/src/cultmesh_py/session.py +++ b/packages/cultmesh-py/src/cultmesh_py/session.py @@ -23,7 +23,7 @@ from .simulation import ( CultMeshSimulationFactCommitter, simulation_fact_document, ) -from .wire import CultMeshAuthorityLeaseCatalog, CultMeshPeerCatalog, CultMeshVerseCatalog +from cultnet_py.cultmesh_contracts import CultMeshAuthorityLeaseCatalog, CultMeshPeerCatalog, CultMeshVerseCatalog if TYPE_CHECKING: from .server import CultMeshLocalServer diff --git a/packages/cultmesh-py/src/cultmesh_py/simulation.py b/packages/cultmesh-py/src/cultmesh_py/simulation.py new file mode 100644 index 00000000..a89ba4a2 --- /dev/null +++ b/packages/cultmesh-py/src/cultmesh_py/simulation.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from cultcache_py.documents import DocumentDefinition +from cultnet_py.cultmesh_contracts import ( + SIMULATION_FACT_DOCUMENT_TYPE, + SIMULATION_FACT_SCHEMA_VERSION, + CultMeshSimulationFact, + simulation_fact_document, +) + +from .node import CultMeshNode + +__all__ = [ + "SIMULATION_FACT_DOCUMENT_TYPE", + "SIMULATION_FACT_SCHEMA_VERSION", + "CultMeshSimulationFact", + "CultMeshSimulationFactCommit", + "CultMeshSimulationFactCommitter", + "simulation_fact_document", +] + + +@dataclass(frozen=True) +class CultMeshSimulationFactCommit: + key: str + fact: CultMeshSimulationFact + + +@dataclass +class CultMeshSimulationFactCommitter: + node: CultMeshNode + document: DocumentDefinition[CultMeshSimulationFact] = simulation_fact_document + + def __post_init__(self) -> None: + if all(document.type != self.document.type for document in self.node.documents): + self.node.register_document(self.document) + + def commit(self, candidate: dict[str, Any], *, committed_at: str | None = None) -> CultMeshSimulationFactCommit: + if not bool(candidate.get("hasQuorum") if "hasQuorum" in candidate else candidate.get("has_quorum")): + raise ValueError("Simulation candidate cannot be committed before quorum") + key = CultMeshSimulationFact.create_record_key(candidate) + fact = CultMeshSimulationFact.from_candidate(candidate, committed_at=committed_at) + self.node.put(self.document, key, fact) + return CultMeshSimulationFactCommit(key=key, fact=fact) diff --git a/packages/cultcache-py/src/cultcache_py/verify.py b/packages/cultmesh-py/src/cultmesh_py/verify.py similarity index 98% rename from packages/cultcache-py/src/cultcache_py/verify.py rename to packages/cultmesh-py/src/cultmesh_py/verify.py index b6a85430..068f5dc2 100644 --- a/packages/cultcache-py/src/cultcache_py/verify.py +++ b/packages/cultmesh-py/src/cultmesh_py/verify.py @@ -6,7 +6,7 @@ import json from importlib import resources from typing import Any -from .benchmark import run_benchmark +from cultnet_py.benchmark import run_benchmark EXPECTED_EXPORTS: dict[str, tuple[str, ...]] = { @@ -150,7 +150,7 @@ EXPECTED_EXPORTS: dict[str, tuple[str, ...]] = { def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(prog="cultcache-py-verify") + parser = argparse.ArgumentParser(prog="cultmesh-py-verify") parser.add_argument("--records", type=int, default=64) parser.add_argument("--json", action="store_true", dest="emit_json") args = parser.parse_args(argv) @@ -159,7 +159,7 @@ def main(argv: list[str] | None = None) -> int: if args.emit_json: print(json.dumps(result, indent=2, sort_keys=True)) else: - print(f"cultcache-py verify: {result['status']}") + print(f"cultmesh-py verify: {result['status']}") for check in result["checks"]: print(f"- {check['name']}: {check['status']}") return 0 if result["status"] == "ok" else 1 diff --git a/packages/cultmesh-py/tests/test_cultmesh.py b/packages/cultmesh-py/tests/test_cultmesh.py new file mode 100644 index 00000000..b6a21bb8 --- /dev/null +++ b/packages/cultmesh-py/tests/test_cultmesh.py @@ -0,0 +1,4491 @@ +from __future__ import annotations + +import json +import os +import socket +import subprocess +import sys +import tempfile +import threading +import time +import unittest +from unittest.mock import patch +from dataclasses import dataclass +from datetime import UTC, datetime, timedelta +from pathlib import Path +from typing import Any, Callable +from cultcache_py import ( + CultCache, + SingleFileMessagePackBackingStore, + define_database_entry_type, + define_document_type, +) +from cultcache_py.interop import INTEROP_SCHEMA_VERSION, interop_note_document +from cultmesh_py.verify import verify +from cultnet_py import ( + compute_simulation_claim_hash, + CultNetClientAuthorityScope, + CultNetRawClient, + CultNetRawDocumentRecord, + CultNetRawSnapshotResponse, + CultNetSchemaCatalog, + CultNetShardCatalog, + CultNetShardDescriptor, + CultNetShardLogResponse, + CultNetFileShardReplicaCursorStore, + CultNetFileShardMutationLogStore, + CultNetInMemoryShardReplicaCursorStore, + CultNetSchemaWriteForwarder, + CultNetSchemaShardLogFetcher, + CultNetSchemaShardSnapshotFetcher, + CultNetPeerError, + CultNetShardReplicator, + CultNetShardReplicatorOptions, + CultNetShardMutationLogStore, + CultNetShardWriteForwarder, + CultNetSimulationConsensusOptions, + CultNetSimulationConsensusCandidate, + CultNetSimulationObservation, + CultNetSimulationObservationHub, + CultNetRudpSocketMode, + CultNetRudpSocketTransportConnection, + CultNetRudpSocketTransportOptions, + apply_shard_log_response, + document_delete, + document_put_raw, + hello, + simulation_observation, + snapshot_request, +) +from cultmesh_py import create_node +from cultmesh_py import ( + CultMesh, + CultMeshDatabase, + CultMeshDatabaseChange, + CultMeshGameSessionOptions, + CultMeshNodeOptions, + CultMeshReactiveDocumentOptions, + CultMeshPeerCard, + CultMeshPeerCatalog, + CultMeshSimulationFact, + CultMeshAuthorityLease, + CultMeshAuthorityLeaseCatalog, + CultMeshHmacAuthorityLeaseVerifier, + CultMeshDiscoveryClient, + CultMeshPeerExchangeClient, + CultMeshPeerHealthMonitor, + CultMeshSimulationObservationFanout, + CultMeshSnapshotFanout, + CultMeshStreamCatalog, + CultMeshStreamConsumerProfile, + CultMeshStreamDescriptor, + CultMeshStreamFrameHandle, + CultMeshVerseCatalog, + CultMeshVerseCompatibility, + CultMeshVerseDescriptor, + CultMeshVerseDiscoveryClient, + READY_SCHEMA_VERSION, + peer_exchange_request, + simulation_fact_document, + verse_catalog_request, +) + + +def bind_udp_socket() -> socket.socket: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(("127.0.0.1", 0)) + sock.settimeout(0.02) + return sock + + +def pump_rudp_handshake( + client: CultNetRudpSocketTransportConnection, + server: CultNetRudpSocketTransportConnection, +) -> None: + for _ in range(20): + server.receive_once() + client.receive_once() + server.receive_once() + if client.connected and server.connected: + return + time.sleep(0.005) + raise AssertionError("RUDP socket handshake did not complete") + + +def receive_rudp_frame(transport: CultNetRudpSocketTransportConnection): + for _ in range(20): + frame = transport.receive_once() + if frame is not None: + return frame + time.sleep(0.005) + raise AssertionError("RUDP socket frame was not delivered") + + +@dataclass +class Item: + name: str + category: str + value: int + + +class CultMeshTests(unittest.TestCase): + def _wait_until(self, predicate: Callable[[], bool], *, timeout_seconds: float = 2.0) -> None: + deadline = time.monotonic() + timeout_seconds + while time.monotonic() < deadline: + if predicate(): + return + time.sleep(0.01) + self.fail("Timed out waiting for condition") + + def test_verify_reports_python_runtime_surface_health(self) -> None: + result = verify(records=4) + + self.assertEqual(result["status"], "ok") + self.assertEqual( + {check["name"] for check in result["checks"]}, + { + "public_exports", + "typed_markers", + "local_cultmesh_wire_smoke", + "cultmesh_capability_truth", + "benchmark_sanity", + }, + ) + by_name = {check["name"]: check for check in result["checks"]} + self.assertEqual(by_name["public_exports"]["missing"], {}) + self.assertEqual(by_name["cultmesh_capability_truth"]["failures"], []) + + def test_cultmesh_client_uses_rudp_schema_transport_endpoint(self) -> None: + import msgpack # type: ignore + + server_socket = bind_udp_socket() + server_host, server_port = server_socket.getsockname()[:2] + connection_id = 0x43554C54 + server = CultNetRudpSocketTransportConnection( + CultNetRudpSocketTransportOptions( + runtime_id="python-rudp-schema-server", + socket=server_socket, + mode=CultNetRudpSocketMode.SERVER, + connection_id=connection_id, + initial_sequence=100, + resend_delay_ms=25, + ) + ) + received_messages: list[dict[str, object]] = [] + + def serve_once() -> None: + deadline = time.monotonic() + 2.0 + try: + while time.monotonic() < deadline: + frame = server.receive_once() + server.poll_resends() + if frame is None: + time.sleep(0.005) + continue + received = msgpack.unpackb(frame.payload, raw=False) + received_messages.append(received) + response = { + "schemaVersion": "cultnet.schema_catalog_response.v0", + "messageId": received["messageId"], + "schemas": [], + } + server.send("schema", msgpack.packb(response, use_bin_type=True)) + return + raise AssertionError("RUDP schema server did not receive a request") + finally: + server.close() + + server_thread = threading.Thread(target=serve_once, daemon=True) + server_thread.start() + client = CultMesh.create_client( + endpoint=f"rudp://{server_host}:{server_port}", + timeout_seconds=2.0, + connection_id=connection_id, + runtime_id="python-rudp-schema-client", + ) + + response = client.fetch_schema_catalog(message_id="rudp-transport-factory") + server_thread.join(timeout=2.0) + + self.assertFalse(server_thread.is_alive()) + self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") + self.assertEqual(response["messageId"], "rudp-transport-factory") + self.assertEqual(received_messages[0]["schemaVersion"], "cultnet.schema_catalog_request.v0") + self.assertEqual(received_messages[0]["messageId"], "rudp-transport-factory") + + def test_cultmesh_facade_creates_rudp_client_from_peer_endpoint(self) -> None: + connection_id = 0x10203042 + server = CultMesh.create_rudp_server( + "python-cultmesh-rudp-server", + connection_id, + initial_sequence=100, + resend_delay_ms=25, + max_fragment_bytes=1024, + max_pending_reliable_packets=16, + ) + server_host, server_port = server.socket.getsockname()[:2] + endpoint = CultMesh.parse_rudp_endpoint(f"rudp://{server_host}:{server_port}") + self.assertEqual(endpoint.host, server_host) + self.assertEqual(endpoint.port, server_port) + peer = CultMeshPeerCard( + peer_id="python-cultmesh-rudp-server", + verse_id="local", + endpoints=(endpoint.uri,), + roles=("schema",), + authority_lease_id="lease:python-cultmesh-rudp-server", + ) + peers = CultMesh.create_peer_catalog() + leases = CultMesh.create_authority_lease_catalog() + peers.upsert(peer) + with self.assertRaisesRegex(ValueError, "No authorized RUDP peer"): + CultMesh.create_rudp_client_for_authorized_peer( + "python-cultmesh-rudp-client", + connection_id, + peers, + leases, + "local", + "schema", + resend_delay_ms=25, + max_fragment_bytes=1024, + max_pending_reliable_packets=16, + ) + leases.upsert( + CultMeshAuthorityLease( + lease_id="lease:python-cultmesh-rudp-server", + verse_id="local", + peer_id="python-cultmesh-rudp-server", + roles=("schema",), + valid_from=datetime.now(UTC) - timedelta(seconds=1), + expires_at=datetime.now(UTC) + timedelta(seconds=30), + issuer_runtime_id="python-authority", + ) + ) + client = CultMesh.create_rudp_client_for_authorized_peer( + "python-cultmesh-rudp-client", + connection_id, + peers, + leases, + "local", + "schema", + resend_delay_ms=25, + max_fragment_bytes=1024, + max_pending_reliable_packets=16, + ) + + try: + client.connect(b"join") + pump_rudp_handshake(client, server) + self.assertTrue(client.connected) + self.assertTrue(server.connected) + + client.send("schema", b"client-state") + server_frame = receive_rudp_frame(server) + self.assertEqual(server_frame.channel_id, "schema") + self.assertEqual(server_frame.payload, b"client-state") + self.assertEqual(server.profile["transports"][0]["protocol"], "rudp") + self.assertEqual(client.profile["transports"][0]["protocol"], "rudp") + finally: + client.close() + server.close() + + def test_cultmesh_node_applies_foreign_schema_snapshot_as_local_update(self) -> None: + document = define_database_entry_type( + "mesh.runtime-policy", + [ + ("schema_version", 0), + ("name", 1), + ("value", 2), + ], + schema_id="mesh.runtime-policy.current", + schema_name="mesh.runtime_policy", + schema_version="mesh.runtime_policy.v1", + ) + node = create_node(runtime_id="python-node") + node.register_document(document) + node.put(document, "policy:1", { + "schema_version": "mesh.runtime_policy.v1", + "name": "policy", + "value": "old", + }) + observed: list[CultMeshDatabaseChange] = [] + unsubscribe = node.database.watch_record(document, "policy:1", observed.append) + try: + applied = node.database.apply_snapshot_response({ + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "foreign-schema-snapshot", + "documents": [ + { + "schemaId": "sha256:foreign-mesh-runtime-policy", + "recordKey": "policy:1", + "storedAt": "2026-06-25T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload({ + "schema_version": "mesh.runtime_policy.v1", + "name": "policy", + "value": "new", + }), + }, + ], + }) + finally: + unsubscribe() + + local_schema_id = document.catalog_entry().schema_id + self.assertEqual(applied[0].schema_id, local_schema_id) + self.assertEqual(observed[0].schema_id, local_schema_id) + self.assertEqual(observed[0].change_kind, "updated") + self.assertEqual(observed[0].previous_value["value"], "old") + self.assertEqual(observed[0].value["value"], "new") + self.assertEqual(node.cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) + + def test_cultmesh_node_syncs_snapshot_and_shard_log_through_cultnet_client(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + document = define_database_entry_type( + "mesh.sync_item", + [ + ("name", 0), + ("category", 1), + ("value", 2, 0), + ], + cls=Item, + ) + node = create_node(runtime_id="python-node") + node.register_document(document) + schema_id = document.catalog_entry().schema_id + ready = threading.Event() + server_error: list[BaseException] = [] + port_holder: list[int] = [] + + def serve_requests() -> None: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(2) + ready.set() + for _ in range(2): + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + request = msgpack.unpackb(read_frame(stream), raw=False) + if request["schemaVersion"] == "cultnet.snapshot_request.v0": + response = { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": request["messageId"], + "documents": [ + { + "schemaId": schema_id, + "recordKey": "item:node", + "storedAt": "2026-06-13T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(Item("node", "mesh", 1)), + } + ], + } + elif request["schemaVersion"] == "cultnet.shard_log_request.v0": + response = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": request["messageId"], + "shardId": request["shardId"], + "shardEpoch": request["shardEpoch"], + "resyncRequired": False, + "entries": [ + { + "sequence": 1, + "changeKind": "updated", + "put": { + "schemaVersion": "cultnet.document_put_raw.v0", + "messageId": "put-node", + "document": { + "schemaId": schema_id, + "recordKey": "item:node", + "storedAt": "2026-06-13T00:00:01Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(Item("node", "mesh", 9)), + }, + "shardId": request["shardId"], + "shardEpoch": request["shardEpoch"], + }, + } + ], + } + else: + raise AssertionError(f"unexpected request {request['schemaVersion']}") + write_frame(stream, msgpack.packb(response, use_bin_type=True)) + stream.flush() + except BaseException as error: + server_error.append(error) + ready.set() + + thread = threading.Thread(target=serve_requests, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + self.assertFalse(server_error) + + client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + snapshot_changes = node.sync_snapshot(client, schema_ids=[schema_id]) + log_changes = node.sync_shard_log(client, shard_id="mesh", shard_epoch=1) + + thread.join(2.0) + self.assertFalse(server_error) + self.assertEqual(snapshot_changes[0].record_key, "item:node") + self.assertEqual(log_changes[0].change_kind, "updated") + self.assertEqual(node.get_required(document, "item:node").value, 9) + + def test_cultmesh_node_sync_document_returns_requested_alias_type(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.sync_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.sync_alias_note.v1", + schema_name="mesh.sync_alias_note", + schema_version="mesh.sync_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.sync_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.sync_alias_note.v1", + schema_name="mesh.sync_alias_note", + schema_version="mesh.sync_alias_note.v1", + ) + node = create_node(runtime_id="python-node") + node.register_document(document) + requests: list[dict[str, Any]] = [] + + class SnapshotClient: + def fetch_snapshot_response( + self, + *, + schema_ids: list[str] | None = None, + record_keys: list[str] | None = None, + shard_id: str | None = None, + shard_epoch: int | None = None, + ) -> dict[str, Any]: + requests.append({ + "schema_ids": schema_ids, + "record_keys": record_keys, + "shard_id": shard_id, + "shard_epoch": shard_epoch, + }) + return { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "sync-document", + "documents": [ + { + "schemaId": document.catalog_entry().schema_id, + "recordKey": "note:remote", + "storedAt": "2026-06-27T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(CanonicalNote("synced-alias")), + } + ], + } + + synced = node.sync_document(SnapshotClient(), alias, "note:remote") + facade_synced = CultMesh.sync_document(node, SnapshotClient(), alias, "note:remote") + + self.assertIsInstance(synced, UiNote) + self.assertEqual(synced.body, "synced-alias") + self.assertIsInstance(facade_synced, UiNote) + self.assertEqual(facade_synced.body, "synced-alias") + self.assertEqual(node.get_required(document, "note:remote").body, "synced-alias") + self.assertIsInstance(node.get_required(alias, "note:remote"), UiNote) + self.assertEqual(requests, [ + { + "schema_ids": ["mesh.sync_alias_note.v1"], + "record_keys": ["note:remote"], + "shard_id": None, + "shard_epoch": None, + }, + { + "schema_ids": ["mesh.sync_alias_note.v1"], + "record_keys": ["note:remote"], + "shard_id": None, + "shard_epoch": None, + }, + ]) + + def test_cultmesh_node_sync_document_from_publication_returns_requested_alias_type(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.publication_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.publication_alias_note.v1", + schema_name="mesh.publication_alias_note", + schema_version="mesh.publication_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.publication_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.publication_alias_note.v1", + schema_name="mesh.publication_alias_note", + schema_version="mesh.publication_alias_note.v1", + ) + node = create_node(runtime_id="python-publication-node") + node.register_document(document) + requests: list[dict[str, Any]] = [] + + class SnapshotClient: + def fetch_snapshot_response( + self, + *, + schema_ids: list[str] | None = None, + record_keys: list[str] | None = None, + shard_id: str | None = None, + shard_epoch: int | None = None, + ) -> dict[str, Any]: + requests.append({ + "schema_ids": schema_ids, + "record_keys": record_keys, + "shard_id": shard_id, + "shard_epoch": shard_epoch, + }) + return { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "publication-sync-document", + "documents": [ + { + "schemaId": document.catalog_entry().schema_id, + "recordKey": "note:published-peer", + "storedAt": "2026-06-27T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(CanonicalNote("peer-publication-alias")), + } + ], + } + + source = CultMesh.publication_source_from_peer_snapshot( + SnapshotClient(), + shard_id="notes", + shard_epoch=3, + ) + + synced = node.sync_document_from_publication(source, alias, "note:published-peer") + facade_synced = CultMesh.sync_document_from_publication(node, source, alias, "note:published-peer") + + self.assertIsInstance(synced, UiNote) + self.assertEqual(synced.body, "peer-publication-alias") + self.assertIsInstance(facade_synced, UiNote) + self.assertEqual(facade_synced.body, "peer-publication-alias") + self.assertEqual(node.get_required(document, "note:published-peer").body, "peer-publication-alias") + self.assertEqual(requests, [ + { + "schema_ids": ["mesh.publication_alias_note.v1"], + "record_keys": ["note:published-peer"], + "shard_id": "notes", + "shard_epoch": 3, + }, + { + "schema_ids": ["mesh.publication_alias_note.v1"], + "record_keys": ["note:published-peer"], + "shard_id": "notes", + "shard_epoch": 3, + }, + ]) + + def test_cultmesh_node_sync_document_from_single_file_publication_uses_node_aliases(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.file_publication_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.file_publication_alias_note.v1", + schema_name="mesh.file_publication_alias_note", + schema_version="mesh.file_publication_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.file_publication_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.file_publication_alias_note.v1", + schema_name="mesh.file_publication_alias_note", + schema_version="mesh.file_publication_alias_note.v1", + ) + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "publication.ccmp" + writer = CultCache() + writer.register_document_type(document) + writer.add_generic_store(SingleFileMessagePackBackingStore(path)) + writer.put(document, "note:published-file", CanonicalNote("file-publication-alias")) + + node = create_node(runtime_id="python-file-publication-node") + node.register_document(document) + source = CultMesh.publication_source_from_single_file(path) + + synced = CultMesh.sync_document_from_publication(node, source, alias, "note:published-file") + + self.assertIsInstance(synced, UiNote) + self.assertEqual(synced.body, "file-publication-alias") + self.assertEqual(node.get_required(document, "note:published-file").body, "file-publication-alias") + self.assertIsInstance(node.get_required(alias, "note:published-file"), UiNote) + + def test_cultmesh_node_sync_documents_from_publication_uses_binding_sources_and_aliases(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.catalog_publication_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.catalog_publication_alias_note.v1", + schema_name="mesh.catalog_publication_alias_note", + schema_version="mesh.catalog_publication_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.catalog_publication_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.catalog_publication_alias_note.v1", + schema_name="mesh.catalog_publication_alias_note", + schema_version="mesh.catalog_publication_alias_note.v1", + ) + with tempfile.TemporaryDirectory() as tmp: + first_path = Path(tmp) / "first.ccmp" + second_path = Path(tmp) / "second.ccmp" + first = CultCache() + first.register_document_type(document) + first.add_generic_store(SingleFileMessagePackBackingStore(first_path)) + first.put(document, "note:first", CanonicalNote("first catalog publication")) + second = CultCache() + second.register_document_type(document) + second.add_generic_store(SingleFileMessagePackBackingStore(second_path)) + second.put(document, "note:second", CanonicalNote("second catalog publication")) + + node = create_node(runtime_id="python-catalog-publication-node") + node.register_document(document) + source = CultMesh.publication_source_from_single_file(first_path) + bindings = [ + CultMesh.publication_document(alias, "note:first"), + CultMesh.publication_document( + alias, + "note:second", + source=CultMesh.publication_source_from_single_file(second_path), + ), + ] + + synced = node.sync_documents_from_publication(source, bindings) + facade_synced = CultMesh.sync_documents_from_publication(node, source, bindings) + + self.assertEqual([value.body for value in synced], [ + "first catalog publication", + "second catalog publication", + ]) + self.assertTrue(all(isinstance(value, UiNote) for value in synced)) + self.assertEqual([value.body for value in facade_synced], [ + "first catalog publication", + "second catalog publication", + ]) + self.assertEqual(node.get_required(document, "note:first").body, "first catalog publication") + self.assertEqual(node.get_required(document, "note:second").body, "second catalog publication") + self.assertIsInstance(node.get_required(alias, "note:second"), UiNote) + + def test_cultmesh_node_emits_raw_put_and_delete_messages_for_local_writes(self) -> None: + document = define_database_entry_type( + "mesh.emit_item", + [ + ("name", 0), + ("category", 1), + ("value", 2, 0), + ], + cls=Item, + ) + node = create_node(runtime_id="python-node") + node.register_document(document) + + put = node.put_raw_message( + document, + "item:emit", + Item("wand", "gear", 4), + message_id="put-emit", + shard_id="mesh", + shard_epoch=1, + ).to_wire() + self.assertEqual(node.get_required(document, "item:emit").name, "wand") + self.assertEqual(put["schemaVersion"], "cultnet.document_put_raw.v0") + self.assertEqual(put["messageId"], "put-emit") + self.assertEqual(put["document"]["schemaId"], document.catalog_entry().schema_id) + self.assertEqual(put["document"]["recordKey"], "item:emit") + self.assertEqual(put["document"]["sourceRuntimeId"], "python-node") + self.assertEqual(put["shardId"], "mesh") + self.assertEqual(put["shardEpoch"], 1) + + delete = node.delete_raw_message( + document, + "item:emit", + message_id="delete-emit", + shard_id="mesh", + shard_epoch=1, + ).to_wire() + self.assertIsNone(node.get(document, "item:emit")) + self.assertEqual(delete["schemaVersion"], "cultnet.document_delete.v0") + self.assertEqual(delete["messageId"], "delete-emit") + self.assertEqual(delete["schemaId"], document.catalog_entry().schema_id) + self.assertEqual(delete["recordKey"], "item:emit") + self.assertEqual(delete["shardId"], "mesh") + self.assertEqual(delete["shardEpoch"], 1) + + def test_cultmesh_node_resolves_same_schema_aliases_for_local_state(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.alias_note.v1", + schema_name="mesh.alias_note", + schema_version="mesh.alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.alias_note.v1", + schema_name="mesh.alias_note", + schema_version="mesh.alias_note.v1", + ) + node = create_node(runtime_id="python-node") + node.register_document(document) + seen: list[CultMeshDatabaseChange] = [] + all_seen: list[CultMeshDatabaseChange] = [] + node.database.watch(all_seen.append, document=alias) + node.database.watch_record(alias, "note:alias", seen.append) + + node.put(alias, "note:alias", UiNote("from-alias-put")) + self.assertEqual(node.get_required(document, "note:alias").body, "from-alias-put") + alias_value = node.get_required(alias, "note:alias") + self.assertIsInstance(alias_value, UiNote) + self.assertEqual(alias_value.body, "from-alias-put") + alias_collection = node.get_all(alias) + self.assertEqual(len(alias_collection), 1) + self.assertIsInstance(alias_collection[0], UiNote) + self.assertEqual(alias_collection[0].body, "from-alias-put") + self.assertEqual(seen[0].document.type, alias.type) + self.assertEqual(seen[0].schema_id, "mesh.alias_note.v1") + self.assertIsInstance(seen[0].value, UiNote) + self.assertEqual(seen[0].value.body, "from-alias-put") + self.assertEqual(all_seen[0].document.type, alias.type) + self.assertIsInstance(all_seen[0].value, UiNote) + self.assertEqual(all_seen[0].value.body, "from-alias-put") + + node.put(document, "note:alias", CanonicalNote("from-canonical-put")) + self.assertEqual(seen[1].document.type, alias.type) + self.assertIsInstance(seen[1].value, UiNote) + self.assertIsInstance(seen[1].previous_value, UiNote) + self.assertEqual(seen[1].value.body, "from-canonical-put") + self.assertEqual(seen[1].previous_value.body, "from-alias-put") + + put = node.put_raw_message( + alias, + "note:alias", + UiNote("from-alias-raw-put"), + message_id="alias-put", + ).to_wire() + self.assertEqual(put["document"]["schemaId"], "mesh.alias_note.v1") + self.assertEqual(put["document"]["recordKey"], "note:alias") + self.assertEqual(node.get_required(alias, "note:alias").body, "from-alias-raw-put") + + delete = node.delete_raw_message(alias, "note:alias", message_id="alias-delete").to_wire() + self.assertEqual(delete["schemaId"], "mesh.alias_note.v1") + self.assertIsNone(node.get(alias, "note:alias")) + self.assertEqual(node.get_all(alias), []) + + def test_cultmesh_reactive_document_coalesces_explicit_alias_updates(self) -> None: + @dataclass + class CanonicalNote: + body: str + revision: int + + @dataclass + class UiNote: + body: str + revision: int + + document = define_database_entry_type( + "mesh.reactive_alias_note", + [("body", 0), ("revision", 1)], + cls=CanonicalNote, + schema_id="mesh.reactive_alias_note.v1", + schema_name="mesh.reactive_alias_note", + schema_version="mesh.reactive_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.reactive_alias_note.ui", + [("body", 0), ("revision", 1)], + cls=UiNote, + schema_id="mesh.reactive_alias_note.v1", + schema_name="mesh.reactive_alias_note", + schema_version="mesh.reactive_alias_note.v1", + ) + node = create_node(runtime_id="python-reactive") + node.register_document(document) + node.put(document, "note:reactive", CanonicalNote("initial", 1)) + seen: list[CultMeshDatabaseChange] = [] + node.database.watch_record(alias, "note:reactive", seen.append) + + reactive = node.authoritative_writer(alias, "note:reactive").reactive( + CultMeshReactiveDocumentOptions(flush_delay_seconds=0.02) + ) + try: + self.assertIsInstance(reactive.snapshot, UiNote) + reactive.update(lambda note: setattr(note, "body", "first-local-edit")) + reactive.update(lambda note: setattr(note, "body", "second-local-edit")) + reactive.update(lambda note: setattr(note, "revision", 2)) + + self._wait_until(lambda: node.get_required(alias, "note:reactive").body == "second-local-edit") + + self.assertFalse(reactive.is_dirty) + self.assertEqual(node.get_required(document, "note:reactive").revision, 2) + self.assertEqual(seen[-1].value.body, "second-local-edit") + finally: + reactive.dispose() + + def test_cultmesh_reactive_document_tracks_canonical_reconciliation_delta(self) -> None: + @dataclass + class Note: + body: str + revision: int + + document = define_database_entry_type( + "mesh.reactive_reconcile_note", + [("body", 0), ("revision", 1)], + cls=Note, + schema_id="mesh.reactive_reconcile_note.v1", + schema_name="mesh.reactive_reconcile_note", + schema_version="mesh.reactive_reconcile_note.v1", + ) + node = create_node(runtime_id="python-reactive-reconcile") + node.register_document(document) + node.put(document, "note:reconcile", Note("initial", 1)) + + reactive = CultMesh.authoritative_writer( + node, + document, + "note:reconcile", + ).reactive(CultMeshReactiveDocumentOptions(flush_delay_seconds=60)) + try: + reactive.update(lambda note: setattr(note, "body", "local-prediction")) + reactive.update(lambda note: setattr(note, "revision", 2)) + + node.put(document, "note:reconcile", Note("authoritative", 7)) + + self.assertEqual(reactive.snapshot.body, "local-prediction") + self.assertIsNotNone(reactive.reconciliation) + self.assertEqual(reactive.reconciliation.canonical.body, "authoritative") + self.assertEqual(reactive.reconciliation.predicted.body, "local-prediction") + self.assertEqual(reactive.reconciliation.delta["body"], "local-prediction") + self.assertEqual(reactive.reconciliation.delta["revision"], -5) + + reactive.flush() + self.assertEqual(node.get_required(document, "note:reconcile").body, "local-prediction") + self.assertIsNone(reactive.reconciliation) + finally: + reactive.dispose() + + def test_cultmesh_reactive_document_is_idle_until_explicit_nested_update(self) -> None: + document = define_document_type( + "mesh.nested_loadout", + encode=lambda value: value, + decode=lambda value: value, + ) + node = create_node(runtime_id="python-reactive-nested") + node.register_document(document) + node.put( + document, + "ship:nested", + {"loadout": {"weapon": {"tuning": {"damage": 10}}}}, + ) + + with patch("cultmesh_py.node.threading.Timer") as timer: + reactive = node.authoritative_writer(document, "ship:nested").reactive() + timer.assert_not_called() + + try: + borrowed = reactive.snapshot + borrowed["loadout"]["weapon"]["tuning"]["damage"] = 99 + self.assertEqual( + node.get_required(document, "ship:nested")["loadout"]["weapon"]["tuning"]["damage"], + 10, + ) + + reactive.update( + lambda draft: draft["loadout"]["weapon"]["tuning"].__setitem__("damage", 12) + ) + reactive.flush() + + self.assertEqual(reactive.snapshot["loadout"]["weapon"]["tuning"]["damage"], 12) + self.assertEqual( + node.get_required(document, "ship:nested")["loadout"]["weapon"]["tuning"]["damage"], + 12, + ) + finally: + reactive.dispose() + + def test_cultmesh_reactive_scheduling_scales_with_changed_documents_only(self) -> None: + document = define_document_type( + "mesh.reactive_scale_note", + encode=lambda value: value, + decode=lambda value: value, + ) + + for document_count in (1, 100, 1000): + node = create_node(runtime_id=f"python-reactive-scale-{document_count}") + node.register_document(document) + for index in range(document_count): + node.put(document, f"note:{index}", {"body": "initial", "revision": 1}) + + scheduled_timers: list[Any] = [] + + class FakeScheduledCall: + def __init__(self, delay_seconds: float, callback: Callable[[], None]) -> None: + self.delay_seconds = delay_seconds + self.callback = callback + self.cancelled = False + + def cancel(self) -> None: + self.cancelled = True + + class FakeScheduler: + def schedule( + self, + delay_seconds: float, + callback: Callable[[], None], + ) -> FakeScheduledCall: + call = FakeScheduledCall(delay_seconds, callback) + scheduled_timers.append(call) + return call + + reactive_documents: list[Any] = [] + with patch("cultmesh_py.node._REACTIVE_SCHEDULER", FakeScheduler()): + try: + for index in range(document_count): + reactive_documents.append( + node.authoritative_writer(document, f"note:{index}").reactive() + ) + + self.assertEqual( + len(scheduled_timers), + 0, + f"{document_count} idle documents must schedule no timers", + ) + + changed_document_count = max(1, document_count // 100) + for index in range(changed_document_count): + reactive_documents[index].update( + lambda draft, changed=index: draft.update( + body=f"changed-{changed}", + revision=2, + ) + ) + + self.assertEqual(len(scheduled_timers), changed_document_count) + self.assertEqual( + sum(reactive.is_dirty for reactive in reactive_documents), + changed_document_count, + ) + finally: + for reactive in reactive_documents: + reactive.dispose() + + self.assertTrue(all(timer.cancelled for timer in scheduled_timers)) + + def test_cultmesh_reactive_documents_share_one_lazy_scheduler_thread(self) -> None: + document = define_document_type( + "mesh.reactive_shared_scheduler_note", + encode=lambda value: value, + decode=lambda value: value, + ) + node = create_node(runtime_id="python-reactive-shared-scheduler") + node.register_document(document) + reactive_documents: list[Any] = [] + try: + for index in range(100): + node.put(document, f"note:{index}", {"body": "initial", "revision": 1}) + reactive_documents.append( + node.authoritative_writer(document, f"note:{index}").reactive( + CultMeshReactiveDocumentOptions(flush_delay_seconds=60.0) + ) + ) + + before = sum( + thread.name == "cultmesh-reactive-scheduler" + for thread in threading.enumerate() + ) + for index, reactive in enumerate(reactive_documents): + reactive.update( + lambda draft, changed=index: draft.update( + body=f"changed-{changed}", + revision=2, + ) + ) + after = sum( + thread.name == "cultmesh-reactive-scheduler" + for thread in threading.enumerate() + ) + + self.assertLessEqual(before, 1) + self.assertEqual(after, 1) + finally: + for reactive in reactive_documents: + reactive.dispose() + + def test_cultmesh_observed_document_cannot_publish_borrowed_mutation(self) -> None: + document = define_document_type( + "mesh.observed_note", + encode=lambda value: value, + decode=lambda value: value, + ) + node = create_node(runtime_id="python-observed") + node.register_document(document) + node.put(document, "note:observed", {"body": "initial"}) + + observed = node.observe_document(document, "note:observed") + try: + observed.current["body"] = "borrowed-local-edit" + self.assertEqual(node.get_required(document, "note:observed")["body"], "initial") + + node.put(document, "note:observed", {"body": "canonical"}) + self.assertEqual(observed.current["body"], "canonical") + finally: + observed.dispose() + + def test_cultmesh_node_uses_cultcache_store(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + document = define_database_entry_type("mesh.note", [("body", 0)]) + store_path = Path(tmp) / "mesh.cc" + node = create_node(store_path, runtime_id="mesh-test") + self.assertIsInstance(node.database, CultMeshDatabase) + node.database.register_document(document) + node.database.put(document, "note:1", {"body": "hello"}) + self.assertEqual(node.get_required(document, "note:1")["body"], "hello") + + reopened = create_node(store_path, runtime_id="mesh-test") + reopened.database.register_document(document) + reopened.database.pull() + self.assertEqual(reopened.database.get(document, "note:1")["body"], "hello") + + def test_cultmesh_node_options_attach_default_durable_shard_log_store(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + document = define_database_entry_type( + "mesh.durable_option_note", + [("body", 0)], + schema_id="mesh.durable_option_note.v1", + ) + cache_path = Path(tmp) / "world.cc" + node = CultMesh.create_node( + cache_path, + runtime_id="durable-option-primary", + options=CultMeshNodeOptions(enable_durable_shard_logs=True), + ) + node.database.register_document(document) + node.database.put_raw_message(document, "note:1", {"body": "stored"}, shard_id="notes", shard_epoch=5) + + log_path = Path(tmp) / "world.cultmesh" / "shard-logs" + self.assertTrue(log_path.exists()) + + reopened = CultMesh.start_node( + cache_path, + runtime_id="durable-option-primary", + enable_durable_shard_logs=True, + ) + reopened.database.register_document(document) + response = reopened.database.build_shard_log_response(shard_id="notes", shard_epoch=5) + + self.assertEqual(reopened.database.shard_ids(), ["notes"]) + self.assertEqual([entry.sequence for entry in response.entries], [1]) + self.assertEqual(response.entries[0].raw_document.record_key, "note:1") + + def test_cultmesh_database_watchers_observe_local_changes_and_unsubscribe(self) -> None: + document = define_database_entry_type("mesh.watch", [("body", 0)]) + node = CultMesh.create_node(runtime_id="mesh-watch") + node.database.register_document(document) + changes: list[CultMeshDatabaseChange] = [] + all_changes: list[CultMeshDatabaseChange] = [] + + unsubscribe_record = node.database.watch_record(document, "note:1", changes.append) + unsubscribe_all = node.database.watch(all_changes.append, document=document) + + node.database.put(document, "note:1", {"body": "hello"}) + node.database.put(document, "note:2", {"body": "skip"}) + node.database.put(document, "note:1", {"body": "updated"}) + node.database.delete(document, "note:1") + unsubscribe_record() + node.database.put(document, "note:1", {"body": "after-unsubscribe"}) + unsubscribe_all() + + self.assertEqual([change.change_kind for change in changes], ["added", "updated", "removed"]) + self.assertEqual([change.record_key for change in changes], ["note:1", "note:1", "note:1"]) + self.assertEqual(changes[1].previous_value, {"body": "hello"}) + self.assertIsNone(changes[2].value) + self.assertEqual([change.record_key for change in all_changes], ["note:1", "note:2", "note:1", "note:1", "note:1"]) + + def test_cultmesh_database_register_document_is_idempotent(self) -> None: + document = define_database_entry_type("mesh.idempotent", [("body", 0)]) + conflicting = define_database_entry_type("mesh.idempotent", [("title", 0)]) + node = CultMesh.create_node(runtime_id="mesh-idempotent") + + node.database.register_document(document) + node.database.register_document(document) + node.database.put(document, "note:1", {"body": "still registered once"}) + + self.assertEqual(node.database.get_required(document, "note:1")["body"], "still registered once") + self.assertEqual([registered.type for registered in node.documents], ["mesh.idempotent"]) + with self.assertRaises(ValueError): + node.database.register_document(conflicting) + + def test_cultmesh_database_global_document_facade_uses_singleton_key_and_watchers(self) -> None: + document = define_database_entry_type( + "mesh.global_settings", + [("theme", 0)], + global_document=True, + ) + node = CultMesh.create_node(runtime_id="mesh-global") + node.database.register_document(document) + changes: list[CultMeshDatabaseChange] = [] + + node.database.watch_global(document, changes.append) + node.database.put_global(document, {"theme": "ash"}) + node.put_global(document, {"theme": "ember"}) + node.delete_global(document) + + self.assertIsNone(node.database.get_global(document)) + self.assertEqual([change.record_key for change in changes], [node.cache.GLOBAL_KEY] * 3) + self.assertEqual([change.change_kind for change in changes], ["added", "updated", "removed"]) + self.assertEqual(changes[1].previous_value, {"theme": "ash"}) + self.assertEqual(changes[2].previous_value, {"theme": "ember"}) + with self.assertRaises(Exception): + node.database.put(document, "settings:wrong", {"theme": "bad"}) + + def test_cultmesh_database_creates_raw_snapshot_response_from_envelopes(self) -> None: + document = define_database_entry_type( + "mesh.snapshot_note", + [("body", 0)], + schema_id="mesh.snapshot_note.v1", + ) + source = CultMesh.create_node(runtime_id="mesh-snapshot-source") + source.database.register_document(document) + source.database.put(document, "note:1", {"body": "include"}) + source.database.put(document, "note:2", {"body": "skip"}) + + typed_response = source.database.build_snapshot_response( + message_id="snapshot-1", + schema_ids=["mesh.snapshot_note.v1"], + record_keys=["note:1"], + shard_id="notes", + shard_epoch=4, + shard_log_sequence=7, + ) + response = source.database.create_snapshot_response( + message_id="snapshot-1", + schema_ids=["mesh.snapshot_note.v1"], + record_keys=["note:1"], + shard_id="notes", + shard_epoch=4, + shard_log_sequence=7, + ) + + self.assertIsInstance(typed_response, CultNetRawSnapshotResponse) + self.assertEqual(typed_response.message_id, "snapshot-1") + self.assertEqual(typed_response.documents[0].record_key, "note:1") + self.assertEqual(typed_response.to_wire(), response) + self.assertEqual(source.build_snapshot_response(schema_ids=["mesh.snapshot_note.v1"]).documents[0].record_key, "note:1") + self.assertEqual(response["schemaVersion"], "cultnet.snapshot_response_raw.v0") + self.assertEqual(response["messageId"], "snapshot-1") + self.assertEqual(response["shardId"], "notes") + self.assertEqual(response["shardEpoch"], 4) + self.assertEqual(response["shardLogSequence"], 7) + self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:1"]) + self.assertEqual(response["documents"][0]["schemaId"], "mesh.snapshot_note.v1") + self.assertEqual(response["documents"][0]["payloadEncoding"], "messagepack") + + target = CultMesh.create_node(runtime_id="mesh-snapshot-target") + target.database.register_document(document) + applied = target.database.apply_snapshot_response(response) + self.assertEqual([(record.schema_id, record.record_key) for record in applied], [("mesh.snapshot_note.v1", "note:1")]) + self.assertEqual(target.database.get_required(document, "note:1"), {"body": "include"}) + self.assertIsNone(target.database.get(document, "note:2")) + + typed_target = CultMesh.create_node(runtime_id="mesh-snapshot-typed-target") + typed_target.database.register_document(document) + typed_applied = typed_target.database.apply_snapshot_response(typed_response) + self.assertEqual([(record.schema_id, record.record_key) for record in typed_applied], [("mesh.snapshot_note.v1", "note:1")]) + self.assertEqual(typed_target.database.get_required(document, "note:1"), {"body": "include"}) + + def test_cultmesh_database_filters_snapshot_by_logged_shard_membership(self) -> None: + document = define_database_entry_type( + "mesh.sharded_snapshot_note", + [("body", 0)], + schema_id="mesh.sharded_snapshot_note.v1", + ) + source = CultMesh.create_node(runtime_id="mesh-sharded-snapshot-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:notes", {"body": "include"}, shard_id="notes", shard_epoch=2) + source.database.put_raw_message(document, "note:other", {"body": "skip"}, shard_id="other", shard_epoch=1) + + response = source.database.build_snapshot_response( + schema_ids=["mesh.sharded_snapshot_note.v1"], + shard_id="notes", + shard_epoch=2, + ) + + self.assertEqual(response.shard_id, "notes") + self.assertEqual(response.shard_epoch, 2) + self.assertEqual([record.record_key for record in response.documents], ["note:notes"]) + + def test_cultmesh_database_creates_shard_log_response_from_raw_mutations(self) -> None: + document = define_database_entry_type( + "mesh.log_note", + [("body", 0)], + schema_id="mesh.log_note.v1", + ) + source = CultMesh.create_node(runtime_id="mesh-log-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:1", {"body": "first"}, shard_id="notes", shard_epoch=3) + source.database.put_raw_message(document, "note:1", {"body": "second"}, shard_id="notes", shard_epoch=3) + source.database.put_raw_message(document, "note:2", {"body": "other"}, shard_id="other", shard_epoch=1) + source.database.delete_raw_message(document, "note:1", shard_id="notes", shard_epoch=3) + + self.assertEqual(source.database.shard_ids(), ["notes", "other"]) + self.assertEqual(source.database.shard_schema_ids("notes"), ["mesh.log_note.v1"]) + + typed_response = source.database.build_shard_log_response( + message_id="log-1", + shard_id="notes", + shard_epoch=3, + after_sequence=1, + limit=2, + ) + response = source.database.create_shard_log_response( + message_id="log-1", + shard_id="notes", + shard_epoch=3, + after_sequence=1, + limit=2, + ) + + self.assertIsInstance(typed_response, CultNetShardLogResponse) + self.assertEqual(typed_response.message_id, "log-1") + self.assertEqual([entry.sequence for entry in typed_response.entries], [2, 3]) + self.assertEqual(typed_response.to_wire(), response) + self.assertEqual([entry.sequence for entry in source.build_shard_log_response(shard_id="notes").entries], [1, 2, 3]) + self.assertEqual(response["schemaVersion"], "cultnet.shard_log_response.v0") + self.assertEqual(response["messageId"], "log-1") + self.assertEqual(response["shardId"], "notes") + self.assertEqual(response["shardEpoch"], 3) + self.assertFalse(response["resyncRequired"]) + self.assertEqual([entry["sequence"] for entry in response["entries"]], [2, 3]) + self.assertEqual([entry["changeKind"] for entry in response["entries"]], ["updated", "removed"]) + self.assertEqual(response["entries"][0]["put"]["document"]["recordKey"], "note:1") + self.assertEqual(response["entries"][1]["delete"]["recordKey"], "note:1") + empty = source.database.create_shard_log_response(shard_id="missing") + self.assertEqual(empty["shardEpoch"], 0) + self.assertEqual(empty["entries"], []) + self.assertTrue(empty["resyncRequired"]) + self.assertEqual(empty["reason"], "unknown_shard") + stale = source.database.create_shard_log_response(shard_id="notes", shard_epoch=2) + self.assertEqual(stale["shardEpoch"], 3) + self.assertEqual(stale["entries"], []) + self.assertTrue(stale["resyncRequired"]) + self.assertEqual(stale["reason"], "stale_epoch") + with self.assertRaises(ValueError): + source.database.create_shard_log_response(shard_id="notes", after_sequence=-1) + with self.assertRaises(ValueError): + source.database.create_shard_log_response(shard_id="notes", limit=-1) + + target = CultMesh.create_node(runtime_id="mesh-log-target") + target.database.register_document(document) + target.database.put(document, "note:1", {"body": "first"}) + applied = target.database.apply_shard_log_response(response) + self.assertEqual([record.change_kind for record in applied], ["updated", "removed"]) + self.assertIsNone(target.database.get(document, "note:1")) + self.assertIsNone(target.database.get(document, "note:2")) + + typed_target = CultMesh.create_node(runtime_id="mesh-log-typed-target") + typed_target.database.register_document(document) + typed_target.database.put(document, "note:1", {"body": "first"}) + typed_applied = typed_target.database.apply_shard_log_response(typed_response) + self.assertEqual([record.change_kind for record in typed_applied], ["updated", "removed"]) + self.assertIsNone(typed_target.database.get(document, "note:1")) + + def test_cultnet_file_shard_mutation_log_store_persists_authoritative_entries(self) -> None: + document = define_database_entry_type( + "mesh.durable_log_note", + [("body", 0)], + schema_id="mesh.durable_log_note.v1", + ) + with tempfile.TemporaryDirectory() as tmp: + store: CultNetShardMutationLogStore = CultNetFileShardMutationLogStore(Path(tmp) / "logs") + source = CultMesh.create_node(runtime_id="durable-primary") + source.database.register_document(document) + source.database.use_shard_mutation_log_store(store) + source.database.put_raw_message(document, "note:1", {"body": "first"}, shard_id="durable", shard_epoch=4) + source.database.put_raw_message(document, "note:2", {"body": "second"}, shard_id="durable", shard_epoch=4) + + reopened = CultMesh.create_node(runtime_id="durable-primary") + reopened.database.register_document(document) + reopened.database.use_shard_mutation_log_store(CultNetFileShardMutationLogStore(Path(tmp) / "logs")) + response = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4) + self.assertEqual(reopened.database.shard_ids(), ["durable"]) + self.assertEqual(reopened.database.shard_schema_ids("durable"), ["mesh.durable_log_note.v1"]) + self.assertEqual([entry.sequence for entry in response.entries], [1, 2]) + self.assertEqual(response.entries[0].raw_document.record_key, "note:1") + + store.compact_through("durable", 1) + compacted = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4, after_sequence=0) + retained = reopened.database.build_shard_log_response(shard_id="durable", shard_epoch=4, after_sequence=1) + self.assertTrue(compacted.resync_required) + self.assertEqual(compacted.reason, "compacted_history") + self.assertEqual(compacted.compacted_through, 1) + self.assertEqual([entry.sequence for entry in retained.entries], [2]) + + def test_cultnet_shard_replicator_pulls_logs_and_resyncs_from_snapshot(self) -> None: + document = define_database_entry_type( + "mesh.replica_note", + [("body", 0)], + schema_id="mesh.replica_note.v1", + ) + source = CultMesh.create_node(runtime_id="primary") + source.database.register_document(document) + source.database.put_raw_message(document, "note:1", {"body": "one"}, shard_id="notes", shard_epoch=3) + source.database.put_raw_message(document, "note:2", {"body": "two"}, shard_id="notes", shard_epoch=3) + with tempfile.TemporaryDirectory() as tmp: + cursor_path = Path(tmp) / "replica-cursors.msgpack" + server = CultMesh.serve_node(source) + try: + shard = CultNetShardDescriptor( + shard_id="notes", + owner_runtime_id="primary", + epoch=3, + is_primary=False, + schema_ids=("mesh.replica_note.v1",), + primary_endpoints=(f"cultnet://127.0.0.1:{server.port}",), + ) + target = CultMesh.create_node(runtime_id="replica") + target.database.register_document(document) + cursor_store = CultNetFileShardReplicaCursorStore(cursor_path) + replicator = CultNetShardReplicator( + target.database, + CultNetShardReplicatorOptions( + fetcher=CultNetSchemaShardLogFetcher(timeout_seconds=2.0), + snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(timeout_seconds=2.0), + cursor_store=cursor_store, + batch_size=16, + ), + ) + + first_sequence = replicator.pull_once(shard) + restarted_replicator = CultNetShardReplicator( + target.database, + CultNetShardReplicatorOptions( + fetcher=CultNetSchemaShardLogFetcher(timeout_seconds=2.0), + snapshot_fetcher=CultNetSchemaShardSnapshotFetcher(timeout_seconds=2.0), + cursor_store=CultNetFileShardReplicaCursorStore(cursor_path), + batch_size=16, + ), + ) + second_sequence = restarted_replicator.pull_once(shard) + + self.assertEqual(first_sequence, 2) + self.assertEqual(second_sequence, 2) + self.assertEqual(target.get_required(document, "note:1")["body"], "one") + self.assertEqual(target.get_required(document, "note:2")["body"], "two") + cursor = CultNetFileShardReplicaCursorStore(cursor_path).read("notes") + self.assertIsNotNone(cursor) + assert cursor is not None + self.assertEqual(cursor.last_applied_sequence, 2) + self.assertEqual(cursor.shard_epoch, 3) + finally: + server.stop() + + resync_target = CultMesh.create_node(runtime_id="resync-replica") + resync_target.database.register_document(document) + snapshot = source.database.build_snapshot_response(shard_id="notes", shard_epoch=3, shard_log_sequence=2) + + class CompactingFetcher: + def fetch(self, shard: CultNetShardDescriptor, *, after_sequence: int, limit: int | None = None) -> CultNetShardLogResponse: + return CultNetShardLogResponse( + message_id="compact", + shard_id=shard.shard_id, + shard_epoch=shard.epoch, + entries=(), + resync_required=True, + reason="compacted_history", + compacted_through=after_sequence, + ) + + class SnapshotFetcher: + def fetch(self, shard: CultNetShardDescriptor) -> CultNetRawSnapshotResponse: + return snapshot + + resync_replicator = CultNetShardReplicator( + resync_target.database, + CultNetShardReplicatorOptions( + fetcher=CompactingFetcher(), + snapshot_fetcher=SnapshotFetcher(), + cursor_store=CultNetInMemoryShardReplicaCursorStore(), + ), + ) + resync_sequence = resync_replicator.pull_once(CultNetShardDescriptor( + shard_id="notes", + owner_runtime_id="primary", + epoch=3, + is_primary=False, + schema_ids=("mesh.replica_note.v1",), + primary_endpoints=("cultnet://primary:3075",), + )) + + self.assertEqual(resync_sequence, 2) + self.assertEqual(resync_target.get_required(document, "note:1")["body"], "one") + + def test_cultnet_shard_replicator_background_loop_polls_non_primary_shards(self) -> None: + document = define_database_entry_type( + "mesh.background_replica_note", + [("body", 0)], + schema_id="mesh.background_replica_note.v1", + ) + source = CultMesh.create_node(runtime_id="background-primary") + source.database.register_document(document) + target = CultMesh.create_node(runtime_id="background-replica") + target.database.register_document(document) + cursor_store = CultNetInMemoryShardReplicaCursorStore() + errors: list[Exception] = [] + + class FlakySourceFetcher: + def __init__(self) -> None: + self.calls = 0 + + def fetch(self, shard: CultNetShardDescriptor, *, after_sequence: int, limit: int | None = None) -> CultNetShardLogResponse: + self.calls += 1 + if self.calls == 1: + raise RuntimeError("transient fetch failure") + return source.database.build_shard_log_response( + shard_id=shard.shard_id, + shard_epoch=shard.epoch, + after_sequence=after_sequence, + limit=limit, + ) + + fetcher = FlakySourceFetcher() + shard = CultNetShardDescriptor( + shard_id="background", + owner_runtime_id="background-primary", + epoch=7, + is_primary=False, + schema_ids=("mesh.background_replica_note.v1",), + primary_endpoints=("cultnet://127.0.0.1:3075",), + ) + replicator = CultNetShardReplicator( + target.database, + CultNetShardReplicatorOptions( + fetcher=fetcher, + cursor_store=cursor_store, + batch_size=1, + poll_interval_seconds=0.01, + on_error=errors.append, + ), + ) + try: + source.database.put_raw_message(document, "note:1", {"body": "one"}, shard_id="background", shard_epoch=7) + replicator.start([shard]) + self._wait_until(lambda: target.database.get(document, "note:1") is not None) + source.database.put_raw_message(document, "note:2", {"body": "two"}, shard_id="background", shard_epoch=7) + self._wait_until(lambda: target.database.get(document, "note:2") is not None) + finally: + replicator.stop() + + cursor = cursor_store.read("background") + self.assertEqual(target.get_required(document, "note:1")["body"], "one") + self.assertEqual(target.get_required(document, "note:2")["body"], "two") + self.assertIsNotNone(cursor) + assert cursor is not None + self.assertEqual(cursor.last_applied_sequence, 2) + self.assertEqual(cursor.shard_epoch, 7) + self.assertEqual(len(errors), 1) + self.assertEqual(str(errors[0]), "transient fetch failure") + + def test_cultnet_schema_write_forwarder_sends_raw_writes_to_primary(self) -> None: + document = define_database_entry_type( + "mesh.forward_note", + [("body", 0)], + schema_id="mesh.forward_note.v1", + ) + primary = CultMesh.create_node(runtime_id="forward-primary") + primary.database.register_document(document) + server = CultMesh.serve_node(primary) + try: + shard = CultNetShardDescriptor( + shard_id="forward", + owner_runtime_id="forward-primary", + epoch=9, + is_primary=False, + schema_ids=("mesh.forward_note.v1",), + primary_endpoints=(f"cultnet://127.0.0.1:{server.port}",), + ) + forwarder: CultNetShardWriteForwarder = CultNetSchemaWriteForwarder(timeout_seconds=2.0) + put = document_put_raw( + message_id="forward-put", + key="note:forward", + schema_id=document.catalog_entry().schema_id, + stored_at="2026-06-14T00:00:00Z", + payload=document.encode_payload({"body": "forwarded"}), + ).to_wire() + delete = document_delete( + message_id="forward-delete", + schema_id=document.catalog_entry().schema_id, + record_key="note:forward", + ).to_wire() + + forwarder.forward_put(shard, put) + self._eventually(lambda: primary.database.get(document, "note:forward") is not None) + self.assertEqual(primary.database.get_required(document, "note:forward")["body"], "forwarded") + forwarder.forward_delete(shard, delete) + self._eventually(lambda: primary.database.get(document, "note:forward") is None) + + log = primary.database.build_shard_log_response(shard_id="forward", shard_epoch=9) + self.assertIsNone(primary.database.get(document, "note:forward")) + self.assertEqual([entry.change_kind for entry in log.entries], ["added", "removed"]) + self.assertEqual(log.entries[0].put["shardId"], "forward") + self.assertEqual(log.entries[1].delete["shardEpoch"], 9) + finally: + server.stop() + + def _eventually(self, predicate: Callable[[], bool], *, timeout_seconds: float = 2.0) -> None: + deadline = time.monotonic() + timeout_seconds + while time.monotonic() < deadline: + if predicate(): + return + time.sleep(0.01) + self.assertTrue(predicate()) + + def test_cultmesh_database_watchers_observe_name_and_index_changes(self) -> None: + document = define_database_entry_type( + "mesh.named_watch", + [("name", 0), ("kind", 1)], + name="name", + indexes={"kind": "kind"}, + ) + node = CultMesh.create_node(runtime_id="mesh-named-watch") + node.database.register_document(document) + name_changes: list[CultMeshDatabaseChange] = [] + index_changes: list[CultMeshDatabaseChange] = [] + + node.database.watch_by_name(document, "Potion", name_changes.append) + node.database.watch_by_index(document, "kind", "consumable", index_changes.append) + + node.database.put(document, "item:1", {"name": "Potion", "kind": "consumable"}) + node.database.put(document, "item:2", {"name": "Sword", "kind": "weapon"}) + node.database.put(document, "item:1", {"name": "Elixir", "kind": "rare"}) + node.database.delete(document, "item:1") + + self.assertEqual([change.change_kind for change in name_changes], ["added", "updated"]) + self.assertEqual([change.record_key for change in name_changes], ["item:1", "item:1"]) + self.assertEqual(name_changes[1].previous_value, {"name": "Potion", "kind": "consumable"}) + self.assertEqual([change.change_kind for change in index_changes], ["added", "updated"]) + self.assertEqual([change.record_key for change in index_changes], ["item:1", "item:1"]) + self.assertEqual(index_changes[1].value, {"name": "Elixir", "kind": "rare"}) + + def test_cultmesh_database_collection_reads_resolve_same_schema_aliases(self) -> None: + @dataclass + class CanonicalItem: + name: str + kind: str + + @dataclass + class UiItem: + name: str + kind: str + + document = define_database_entry_type( + "mesh.alias_collection_item", + [("name", 0), ("kind", 1)], + cls=CanonicalItem, + name="name", + indexes={"kind": "kind"}, + schema_id="mesh.alias_collection_item.v1", + schema_name="mesh.alias_collection_item", + schema_version="mesh.alias_collection_item.v1", + ) + alias = define_database_entry_type( + "mesh.alias_collection_item.ui", + [("name", 0), ("kind", 1)], + cls=UiItem, + name="name", + indexes={"kind": "kind"}, + schema_id="mesh.alias_collection_item.v1", + schema_name="mesh.alias_collection_item", + schema_version="mesh.alias_collection_item.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-collection") + node.database.register_document(document) + + node.database.put(alias, "item:potion", UiItem("Potion", "consumable")) + node.database.put(document, "item:sword", CanonicalItem("Sword", "weapon")) + + all_items = node.database.get_all(alias) + potion = node.database.get_by_name(alias, "Potion") + sword = node.get_by_index(alias, "kind", "weapon") + + self.assertEqual([item.name for item in all_items], ["Potion", "Sword"]) + self.assertTrue(all(isinstance(item, UiItem) for item in all_items)) + self.assertIsInstance(potion, UiItem) + self.assertEqual(potion.kind, "consumable") + self.assertIsInstance(sword, UiItem) + self.assertEqual(sword.name, "Sword") + self.assertIsNone(node.database.get_by_name(alias, "Missing")) + + def test_cultmesh_database_alias_collection_watches_emit_alias_values(self) -> None: + @dataclass + class CanonicalItem: + name: str + kind: str + + @dataclass + class UiItem: + name: str + kind: str + + document = define_database_entry_type( + "mesh.alias_collection_watch_item", + [("name", 0), ("kind", 1)], + cls=CanonicalItem, + name="name", + indexes={"kind": "kind"}, + schema_id="mesh.alias_collection_watch_item.v1", + schema_name="mesh.alias_collection_watch_item", + schema_version="mesh.alias_collection_watch_item.v1", + ) + alias = define_database_entry_type( + "mesh.alias_collection_watch_item.ui", + [("name", 0), ("kind", 1)], + cls=UiItem, + name="name", + indexes={"kind": "kind"}, + schema_id="mesh.alias_collection_watch_item.v1", + schema_name="mesh.alias_collection_watch_item", + schema_version="mesh.alias_collection_watch_item.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-collection-watch") + node.database.register_document(document) + name_changes: list[CultMeshDatabaseChange] = [] + index_changes: list[CultMeshDatabaseChange] = [] + + node.database.watch_by_name(alias, "Potion", name_changes.append) + node.database.watch_by_index(alias, "kind", "consumable", index_changes.append) + + node.database.put(document, "item:potion", CanonicalItem("Potion", "consumable")) + node.database.put(document, "item:potion", CanonicalItem("Elixir", "rare")) + + self.assertEqual([change.change_kind for change in name_changes], ["added", "updated"]) + self.assertTrue(all(change.document.type == alias.type for change in name_changes)) + self.assertIsInstance(name_changes[0].value, UiItem) + self.assertIsInstance(name_changes[1].value, UiItem) + self.assertIsInstance(name_changes[1].previous_value, UiItem) + self.assertEqual(name_changes[1].previous_value.name, "Potion") + self.assertEqual(name_changes[1].value.name, "Elixir") + self.assertEqual([change.change_kind for change in index_changes], ["added", "updated"]) + self.assertTrue(all(change.document.type == alias.type for change in index_changes)) + self.assertIsInstance(index_changes[0].value, UiItem) + self.assertIsInstance(index_changes[1].previous_value, UiItem) + + def test_cultmesh_database_watch_by_name_and_index_validate_lookup_shape(self) -> None: + document = define_database_entry_type("mesh.unnamed_watch", [("body", 0)]) + node = CultMesh.create_node(runtime_id="mesh-watch-validation") + node.database.register_document(document) + + with self.assertRaises(ValueError): + node.database.watch_by_name(document, "missing", lambda _: None) + with self.assertRaises(ValueError): + node.database.watch_by_index(document, "missing", "value", lambda _: None) + with self.assertRaises(ValueError): + node.database.watch_by_index(document, "", "value", lambda _: None) + with self.assertRaises(ValueError): + node.database.watch_by_index(document, "missing", "", lambda _: None) + + def test_cultmesh_facade_matches_peer_runtime_entrypoints(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + store_path = Path(tmp) / "mesh.cc" + node = CultMesh.start_node(store_path, runtime_id="mesh-facade") + + self.assertEqual(node.runtime_id, "mesh-facade") + self.assertIsInstance(CultMesh.create_node(), type(node)) + self.assertIsInstance(CultMesh.create_verse_catalog(), CultMeshVerseCatalog) + self.assertIsInstance(CultMesh.create_peer_catalog(), CultMeshPeerCatalog) + self.assertIsInstance(CultMesh.create_authority_lease_catalog(), CultMeshAuthorityLeaseCatalog) + self.assertIsInstance(CultMesh.create_stream_catalog(), CultMeshStreamCatalog) + self.assertIsInstance(CultMesh.create_schema_catalog(), CultNetSchemaCatalog) + self.assertIsInstance(CultMesh.create_builtin_schema_catalog(), CultNetSchemaCatalog) + self.assertIsInstance(CultMesh.create_shard_catalog(), CultNetShardCatalog) + builtins = CultMesh.create_builtin_schema_catalog( + include_schema_json=False, + kinds=["shared_contract"], + ) + self.assertEqual( + builtins.list()[0].schema_id, + "https://github.com/GameCult/cultnet-ts/contracts/cultnet.transport-profile.schema.json", + ) + self.assertIsNone(builtins.list()[0].schema_json) + peer_exchange_schema_id = ( + "https://github.com/GameCult/cultnet-ts/contracts/cultmesh.peer-exchange-request.schema.json" + ) + filtered_builtin = CultMesh.create_builtin_schema_catalog( + include_schema_json=True, + schema_ids=[peer_exchange_schema_id], + ) + self.assertEqual( + filtered_builtin.list()[0].schema_version, + "cultmesh.peer_exchange_request.v0", + ) + self.assertIn( + "cultmesh.peer_exchange_request.v0", + filtered_builtin.list()[0].schema_json or "", + ) + shard_catalog = CultMesh.create_shard_catalog() + shard_catalog.upsert( + CultNetShardDescriptor( + shard_id="python-notes-a", + owner_runtime_id="mesh-facade", + epoch=7, + is_primary=True, + schema_ids=("python.note.v1",), + key_prefix="note:", + ) + ) + self.assertEqual( + [ + shard.shard_id + for shard in shard_catalog.list(schema_ids=["python.note.v1"], record_keys=["note:1"]) + ], + ["python-notes-a"], + ) + verse_client = CultMesh.create_verse_discovery_client("127.0.0.1", 4010, timeout_seconds=1.5) + peer_client = CultMesh.create_peer_exchange_client("127.0.0.1", 4011, timeout_seconds=1.5) + raw_client = CultMesh.create_client("127.0.0.1", 4012, timeout_seconds=1.5) + connected_client = CultMesh.connect_client("127.0.0.1", 4013, timeout_seconds=1.5) + self.assertIsInstance(verse_client, CultMeshVerseDiscoveryClient) + self.assertIsInstance(peer_client, CultMeshPeerExchangeClient) + self.assertIsInstance(verse_client, CultMeshDiscoveryClient) + self.assertIsInstance(peer_client, CultMeshDiscoveryClient) + self.assertIsInstance(raw_client, CultNetRawClient) + self.assertIsInstance(connected_client, CultNetRawClient) + self.assertEqual((verse_client.host, verse_client.port, verse_client.timeout_seconds), ("127.0.0.1", 4010, 1.5)) + self.assertEqual((peer_client.host, peer_client.port, peer_client.timeout_seconds), ("127.0.0.1", 4011, 1.5)) + self.assertEqual((raw_client.host, raw_client.port, raw_client.timeout_seconds), ("127.0.0.1", 4012, 1.5)) + self.assertEqual((connected_client.host, connected_client.port, connected_client.timeout_seconds), ("127.0.0.1", 4013, 1.5)) + + def test_cultmesh_simulation_fact_uses_csharp_slot_contract(self) -> None: + import msgpack # type: ignore + + candidate = { + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), + "claimSummary": "alice shot bob first", + "witnessCount": 2, + "supportWeight": 2.0, + "totalWeight": 2.0, + "confidence": 1.0, + "hasQuorum": True, + } + + fact = CultMeshSimulationFact.from_candidate(candidate, committed_at="2026-06-13T00:00:00Z") + payload = simulation_fact_document.encode_payload(fact) + slots = msgpack.unpackb(payload, raw=False) + + self.assertEqual(CultMeshSimulationFact.create_record_key(candidate), f"simulation:{fact.fact_id}") + self.assertEqual(slots[0], fact.fact_id) + self.assertEqual(slots[1], "arena") + self.assertEqual(slots[2], 4) + self.assertEqual(slots[3], 100) + self.assertEqual(slots[4], "bob") + self.assertEqual(slots[5], "hit") + self.assertEqual(slots[6], candidate["claimHash"]) + self.assertEqual(slots[7], "alice shot bob first") + self.assertEqual(slots[8], 2) + self.assertEqual(slots[9], 2.0) + self.assertEqual(slots[10], 2.0) + self.assertEqual(slots[11], 1.0) + self.assertEqual(slots[12], "2026-06-13T00:00:00Z") + + def test_cultmesh_simulation_fact_committer_rejects_without_quorum_and_stores_fact(self) -> None: + candidate = { + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), + "claimSummary": "alice shot bob first", + "witnessCount": 2, + "supportWeight": 2.0, + "totalWeight": 2.0, + "confidence": 1.0, + "hasQuorum": True, + } + + with tempfile.TemporaryDirectory() as tmp: + node = CultMesh.create_node(Path(tmp) / "facts.cc", runtime_id="mesh-facts") + committer = CultMesh.create_simulation_fact_committer(node) + + rejected = dict(candidate) + rejected["hasQuorum"] = False + with self.assertRaisesRegex(ValueError, "before quorum"): + committer.commit(rejected) + + committed = committer.commit(candidate, committed_at="2026-06-13T00:00:00Z") + stored = node.get_required(simulation_fact_document, committed.key) + + self.assertEqual(stored.claim_hash, candidate["claimHash"]) + self.assertEqual(stored.committed_at, "2026-06-13T00:00:00Z") + self.assertEqual(committed.fact.fact_id, stored.fact_id) + + def test_cultmesh_game_session_submits_observations_and_commits_quorum_once(self) -> None: + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") + first = simulation_observation( + message_id="obs-1", + witness_runtime_id="watcher-1", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ).to_wire() + second = simulation_observation( + message_id="obs-2", + witness_runtime_id="watcher-2", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ).to_wire() + + with tempfile.TemporaryDirectory() as tmp: + node = CultMesh.create_node(Path(tmp) / "session.cc", runtime_id="mesh-session") + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + consensus_options=CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) + ), + ) + seen_candidates: list[CultNetSimulationConsensusCandidate] = [] + seen_facts: list[CultMeshDatabaseChange] = [] + unsubscribe_candidates = session.watch_candidates(seen_candidates.append) + unsubscribe_facts = session.watch_simulation_facts(seen_facts.append) + + self.assertEqual(session.submit_and_commit(CultNetSimulationObservation.from_wire(first)), []) + typed_candidates = session.submit_observation_candidates(CultNetSimulationObservation.from_wire(second)) + commits = session.commit_quorum_candidate_objects(typed_candidates) + replay = session.commit_quorum_candidates(session.submit_observation(second)) + unsubscribe_candidates() + unsubscribe_facts() + + self.assertEqual(len(typed_candidates), 1) + self.assertTrue(typed_candidates[0].has_quorum) + self.assertEqual(len(commits), 1) + self.assertEqual(replay, []) + self.assertEqual([candidate.claim_hash for candidate in seen_candidates], [claim_hash, claim_hash, claim_hash]) + self.assertEqual(len(seen_facts), 1) + self.assertEqual(seen_facts[0].change_kind, "added") + self.assertEqual(seen_facts[0].value.claim_hash, claim_hash) + stored = node.get_required(simulation_fact_document, commits[0].key) + self.assertEqual(stored.claim_hash, claim_hash) + self.assertEqual(stored.witness_count, 2) + + def test_cultmesh_game_session_serves_catalogs_and_observations_over_wire(self) -> None: + verse_catalog = CultMeshVerseCatalog() + peer_catalog = CultMeshPeerCatalog() + verse_catalog.upsert(CultMeshVerseDescriptor( + verse_id="session-verse", + display_name="Session Verse", + authority_model="local", + compatibility=CultMeshVerseCompatibility("cultmesh.v0", "session-rules"), + )) + peer_catalog.upsert(CultMeshPeerCard( + peer_id="session-peer", + verse_id="session-verse", + endpoints=("cultnet://127.0.0.1:0",), + roles=("simulation-observer", "read-replica"), + shard_ids=("arena",), + )) + session = CultMesh.create_game_session( + CultMesh.create_node(runtime_id="session-peer"), + CultMeshGameSessionOptions( + verse_catalog=verse_catalog, + peer_catalog=peer_catalog, + consensus_options=CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0), + ), + ) + server = session.serve(display_name="Session Peer") + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + hello_response = raw_client.request( + hello(runtime_id="session-prober"), + expected_schema_version="cultnet.hello.v0", + ) + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) + verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") + peers = discovery_client.fetch_peers(verse_id="session-verse", roles=["simulation-observer"]) + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") + first = raw_client.request( + simulation_observation( + message_id="session-wire-1", + witness_runtime_id="watcher-1", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + second = raw_client.request( + simulation_observation( + message_id="session-wire-2", + witness_runtime_id="watcher-2", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + finally: + server.stop() + + self.assertIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) + self.assertEqual(verses[0].verse_id, "session-verse") + self.assertEqual(peers[0].peer_id, "session-peer") + self.assertIn("simulation-observer", peers[0].roles) + self.assertFalse(first["hasQuorum"]) + self.assertEqual(first["witnessCount"], 1) + self.assertTrue(second["hasQuorum"]) + self.assertEqual(second["witnessCount"], 2) + + def test_cultmesh_game_session_can_disable_served_simulation_observations(self) -> None: + verse_catalog = CultMeshVerseCatalog() + verse_catalog.upsert(CultMeshVerseDescriptor( + verse_id="session-quiet", + display_name="Quiet Session", + authority_model="local", + compatibility=CultMeshVerseCompatibility("cultmesh.v0", "quiet-rules"), + )) + session = CultMesh.create_game_session( + CultMesh.create_node(runtime_id="quiet-session-peer"), + CultMeshGameSessionOptions( + verse_catalog=verse_catalog, + serve_simulation_observations=False, + ), + ) + server = session.serve(display_name="Quiet Session Peer") + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + hello_response = raw_client.request( + hello(runtime_id="quiet-session-prober"), + expected_schema_version="cultnet.hello.v0", + ) + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) + verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") + with self.assertRaisesRegex(CultNetPeerError, "Simulation observations are not enabled") as raised: + raw_client.request( + simulation_observation( + message_id="quiet-session-sim", + witness_runtime_id="watcher-1", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=compute_simulation_claim_hash("hit", "alice", "bob", "frame:100"), + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + finally: + server.stop() + + self.assertNotIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) + self.assertEqual(verses[0].verse_id, "session-quiet") + self.assertEqual(raised.exception.response["code"], "simulation_observations_disabled") + + def test_cultmesh_game_session_prediction_requires_scope_and_reconciles_shard_log(self) -> None: + note_doc = define_database_entry_type( + "mesh.input", + [("body", 0)], + schema_id="mesh.input.v1", + ) + with tempfile.TemporaryDirectory() as tmp: + node = CultMesh.create_node(Path(tmp) / "prediction.cc", runtime_id="client-a") + node.register_document(note_doc) + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope( + "client-a", + schema_ids=("mesh.input.v1",), + key_prefix="input:client-a", + ), + ) + ), + ) + + with self.assertRaisesRegex(ValueError, "does not have client prediction authority"): + session.predict(note_doc, "input:other", {"body": "nope"}) + + prediction = session.predict(note_doc, "input:client-a:move", {"body": "predicted"}) + put = document_put_raw( + message_id="authoritative", + key=prediction.key, + schema_id=prediction.schema_id, + stored_at="2026-06-13T00:00:00Z", + payload=note_doc.encode_payload({"body": "authoritative"}), + shard_id="inputs", + shard_epoch=1, + ) + authoritative_response = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "inputs-log", + "shardId": "inputs", + "shardEpoch": 1, + "entries": [ + { + "sequence": 1, + "committedAt": "2026-06-13T00:00:00Z", + "changeKind": "updated", + "put": put.to_wire(), + } + ], + "resyncRequired": False, + } + changes = session.apply_shard_log_response(CultNetShardLogResponse.from_wire(authoritative_response)) + + self.assertEqual(changes[0].change_kind, "reconciled") + self.assertEqual(node.get_required(note_doc, prediction.key)["body"], "authoritative") + self.assertEqual(session.pending_predictions(), ()) + self.assertEqual(session.resimulation_inputs(), ()) + + def test_cultmesh_game_session_partial_reconciliation_keeps_unmatched_predictions(self) -> None: + note_doc = define_database_entry_type( + "mesh.partial_input", + [("body", 0)], + schema_id="mesh.partial_input.v1", + ) + node = CultMesh.create_node(runtime_id="client-partial") + node.register_document(note_doc) + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope( + "client-partial", + schema_ids=("mesh.partial_input.v1",), + key_prefix="input:client-partial", + ), + ) + ), + ) + pending = session.predict(note_doc, "input:client-partial:pending", {"body": "pending-local"}) + accepted = session.predict(note_doc, "input:client-partial:accepted", {"body": "accepted-local"}) + put = document_put_raw( + message_id="authoritative-partial", + key=accepted.key, + schema_id=accepted.schema_id, + stored_at="2026-06-13T00:00:00Z", + payload=note_doc.encode_payload({"body": "accepted-authority"}), + shard_id="inputs", + shard_epoch=1, + ) + + changes = session.apply_shard_log_response({ + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "inputs-partial-log", + "shardId": "inputs", + "shardEpoch": 1, + "entries": [ + { + "sequence": 1, + "committedAt": "2026-06-13T00:00:00Z", + "changeKind": "updated", + "put": put.to_wire(), + } + ], + "resyncRequired": False, + }) + + self.assertEqual([(change.change_kind, change.record_key) for change in changes], [ + ("reconciled", accepted.key), + ]) + self.assertEqual(node.get_required(note_doc, accepted.key)["body"], "accepted-authority") + self.assertEqual(node.get_required(note_doc, pending.key)["body"], "pending-local") + self.assertEqual([prediction.key for prediction in session.pending_predictions()], [pending.key]) + self.assertEqual([prediction.key for prediction in session.resimulation_inputs()], [pending.key]) + + def test_cultmesh_game_session_rolls_back_pending_predictions(self) -> None: + note_doc = define_database_entry_type( + "mesh.rollback_input", + [("body", 0)], + schema_id="mesh.rollback_input.v1", + ) + node = CultMesh.create_node(runtime_id="client-rollback") + node.register_document(note_doc) + node.put(note_doc, "input:client-rollback:existing", {"body": "previous"}) + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope( + "client-rollback", + schema_ids=("mesh.rollback_input.v1",), + key_prefix="input:client-rollback", + ), + ) + ), + ) + + existing = session.predict(note_doc, "input:client-rollback:existing", {"body": "predicted-existing"}) + created = session.predict(note_doc, "input:client-rollback:new", {"body": "predicted-new"}) + self.assertEqual( + [prediction.key for prediction in session.pending_predictions()], + ["input:client-rollback:existing", "input:client-rollback:new"], + ) + self.assertEqual([prediction.key for prediction in session.resimulation_inputs()], [ + "input:client-rollback:existing", + "input:client-rollback:new", + ]) + + restored = session.rollback_prediction(existing) + self.assertIsNotNone(restored) + assert restored is not None + self.assertEqual(restored.change_kind, "rolled_back") + self.assertEqual(restored.value, {"body": "previous"}) + self.assertEqual(node.get_required(note_doc, "input:client-rollback:existing")["body"], "previous") + + rolled_back = session.rollback_predictions() + self.assertEqual([(change.change_kind, change.record_key, change.value) for change in rolled_back], [ + ("rolled_back", created.key, None), + ]) + self.assertIsNone(node.get(note_doc, created.key)) + self.assertEqual(session.pending_predictions(), ()) + + def test_cultmesh_game_session_authoritative_delete_rolls_back_prediction(self) -> None: + note_doc = define_database_entry_type( + "mesh.deleted_input", + [("body", 0)], + schema_id="mesh.deleted_input.v1", + ) + node = CultMesh.create_node(runtime_id="client-delete") + node.register_document(note_doc) + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope( + "client-delete", + schema_ids=("mesh.deleted_input.v1",), + key_prefix="input:client-delete", + ), + ) + ), + ) + + prediction = session.predict(note_doc, "input:client-delete:move", {"body": "predicted"}) + delete = document_delete( + message_id="authoritative-delete", + schema_id=prediction.schema_id, + record_key=prediction.key, + shard_id="inputs", + shard_epoch=1, + ) + changes = session.apply_shard_log_response({ + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "inputs-delete-log", + "shardId": "inputs", + "shardEpoch": 1, + "entries": [ + { + "sequence": 1, + "committedAt": "2026-06-13T00:00:00Z", + "changeKind": "removed", + "delete": delete.to_wire(), + } + ], + "resyncRequired": False, + }) + + self.assertEqual(changes[0].change_kind, "rolled_back") + self.assertIsNone(node.get(note_doc, prediction.key)) + self.assertEqual(session.pending_predictions(), ()) + + def test_cultmesh_database_watchers_observe_authoritative_shard_log_reconciliation(self) -> None: + note_doc = define_database_entry_type( + "mesh.watched_input", + [("body", 0)], + schema_id="mesh.watched_input.v1", + ) + node = CultMesh.create_node(runtime_id="client-watch") + node.database.register_document(note_doc) + seen: list[CultMeshDatabaseChange] = [] + node.database.watch_record(note_doc, "input:client-watch:move", seen.append) + session = CultMesh.create_game_session( + node, + CultMeshGameSessionOptions( + client_authority_scopes=( + CultNetClientAuthorityScope( + "client-watch", + schema_ids=("mesh.watched_input.v1",), + key_prefix="input:client-watch", + ), + ) + ), + ) + + prediction = session.predict(note_doc, "input:client-watch:move", {"body": "predicted"}) + put = document_put_raw( + message_id="authoritative-watch", + key=prediction.key, + schema_id=prediction.schema_id, + stored_at="2026-06-13T00:00:00Z", + payload=note_doc.encode_payload({"body": "authoritative"}), + shard_id="inputs", + shard_epoch=1, + ) + session_changes = session.apply_shard_log_response({ + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "inputs-watch-log", + "shardId": "inputs", + "shardEpoch": 1, + "entries": [ + { + "sequence": 1, + "committedAt": "2026-06-13T00:00:00Z", + "changeKind": "updated", + "put": put.to_wire(), + } + ], + "resyncRequired": False, + }) + + self.assertEqual([change.change_kind for change in seen], ["added", "updated"]) + self.assertEqual(seen[1].value, {"body": "authoritative"}) + self.assertEqual(seen[1].previous_value, {"body": "predicted"}) + self.assertEqual(session_changes[0].change_kind, "reconciled") + + def test_cultmesh_verse_catalog_response_matches_schema_v0_wire_shape(self) -> None: + import msgpack # type: ignore + + catalog = CultMeshVerseCatalog() + catalog.upsert( + CultMeshVerseDescriptor( + verse_id="aetheria-main", + display_name="Aetheria", + authority_model="OperatorCluster", + compatibility=CultMeshVerseCompatibility( + transport_version="cultmesh.v0", + rules_hash="rules", + compatible_verse_ids=("aetheria-modded",), + required_plugin_ids=("core",), + optional_plugin_ids=("skylands",), + ), + discovery_endpoints=("cultmesh://aetheria.example.test:3075",), + authority_runtime_ids=("runtime-a",), + description="main branch", + ) + ) + + response = catalog.create_response(verse_catalog_request("verses-1")) + decoded = msgpack.unpackb(msgpack.packb(response, use_bin_type=True), raw=False) + self.assertEqual(decoded["schemaVersion"], "cultmesh.verse_catalog_response.v0") + self.assertEqual(decoded["messageId"], "verses-1") + self.assertEqual(decoded["verses"][0]["verseId"], "aetheria-main") + self.assertEqual(decoded["verses"][0]["compatibility"]["requiredPluginIds"], ["core"]) + + def test_cultmesh_verse_catalog_watches_updates_and_finds_transfer_targets(self) -> None: + source = CultMeshVerseDescriptor( + verse_id="aetheria-main", + display_name="Aetheria", + authority_model="federated", + compatibility=CultMeshVerseCompatibility( + transport_version="cultmesh.v0", + rules_hash="rules-main", + ), + ) + compatible = CultMeshVerseDescriptor( + verse_id="aetheria-modded", + display_name="Aetheria Modded", + authority_model="federated", + compatibility=CultMeshVerseCompatibility( + transport_version="cultmesh.v0", + rules_hash="rules-modded", + compatible_verse_ids=("aetheria-main",), + ), + ) + incompatible = CultMeshVerseDescriptor( + verse_id="old-world", + display_name="Old World", + authority_model="solo", + compatibility=CultMeshVerseCompatibility( + transport_version="cultmesh.v0", + rules_hash="rules-old", + ), + ) + catalog = CultMeshVerseCatalog() + seen: list[str] = [] + unsubscribe = catalog.watch(lambda verse: seen.append(verse.verse_id)) + + catalog.upsert(source) + catalog.apply_response({ + "schemaVersion": "cultmesh.verse_catalog_response.v0", + "messageId": "verses-watch", + "verses": [compatible.to_wire(), incompatible.to_wire()], + }) + unsubscribe() + catalog.upsert(CultMeshVerseDescriptor( + verse_id="after-unsubscribe", + display_name="After", + authority_model="none", + compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules-after"), + )) + + self.assertEqual(seen, ["aetheria-main", "aetheria-modded", "old-world"]) + self.assertEqual([verse.verse_id for verse in catalog.verses], ["aetheria-main", "aetheria-modded", "after-unsubscribe", "old-world"]) + self.assertEqual(catalog.get("aetheria-modded"), compatible) + self.assertEqual([verse.verse_id for verse in catalog.find_transfer_targets(source)], ["aetheria-modded"]) + + def test_cultmesh_peer_exchange_response_matches_schema_v0_wire_shape(self) -> None: + import msgpack # type: ignore + + catalog = CultMeshPeerCatalog() + catalog.upsert( + CultMeshPeerCard( + peer_id="peer-a", + verse_id="aetheria-main", + endpoints=("cultnet://peer-a.example.test:3075",), + roles=("discovery", "read-replica"), + shard_ids=("players",), + region="eu-west", + authority_lease_id="lease-1", + expires_at="2026-05-20T12:00:00.0000000Z", + signature="sig", + ) + ) + + response = catalog.create_response( + peer_exchange_request("pex-1", verse_id="aetheria-main", roles=["read-replica"]) + ) + decoded = msgpack.unpackb(msgpack.packb(response, use_bin_type=True), raw=False) + self.assertEqual(decoded["schemaVersion"], "cultmesh.peer_exchange_response.v0") + self.assertEqual(decoded["messageId"], "pex-1") + self.assertEqual(decoded["peers"][0]["peerId"], "peer-a") + self.assertIn("read-replica", decoded["peers"][0]["roles"]) + self.assertEqual(decoded["peers"][0]["authorityLeaseId"], "lease-1") + + def test_cultmesh_peer_catalog_watches_updates_and_gets_peers(self) -> None: + first = CultMeshPeerCard( + peer_id="peer-a", + verse_id="aetheria-main", + endpoints=("cultnet://peer-a.example.test:3075",), + roles=("discovery", "read-replica"), + ) + second = CultMeshPeerCard( + peer_id="peer-b", + verse_id="aetheria-main", + endpoints=("cultnet://peer-b.example.test:3075",), + roles=("shard-primary",), + ) + catalog = CultMeshPeerCatalog() + seen: list[str] = [] + unsubscribe = catalog.watch(lambda peer: seen.append(peer.peer_id)) + + catalog.upsert(first) + catalog.apply_response({ + "schemaVersion": "cultmesh.peer_exchange_response.v0", + "messageId": "peers-watch", + "peers": [second.to_wire()], + }) + unsubscribe() + catalog.upsert(CultMeshPeerCard( + peer_id="peer-c", + verse_id="aetheria-main", + endpoints=("cultnet://peer-c.example.test:3075",), + roles=("read-replica",), + )) + + self.assertEqual(seen, ["peer-a", "peer-b"]) + self.assertTrue(first.has_role("read-replica")) + self.assertEqual([peer.peer_id for peer in catalog.peers], ["peer-a", "peer-b", "peer-c"]) + self.assertEqual(catalog.get("peer-b"), second) + self.assertEqual([peer.peer_id for peer in catalog.find("aetheria-main", role="read-replica")], ["peer-a", "peer-c"]) + + def test_cultmesh_discovery_client_fetches_typed_catalogs_over_cultnet_frames(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + verses = CultMeshVerseCatalog() + verses.upsert( + CultMeshVerseDescriptor( + verse_id="aetheria-main", + display_name="Aetheria", + authority_model="federated", + compatibility=CultMeshVerseCompatibility( + transport_version="cultmesh.v0", + rules_hash="rules-1", + required_plugin_ids=("core",), + ), + discovery_endpoints=("cultmesh://aetheria.example.test:3075",), + authority_runtime_ids=("runtime-a",), + ) + ) + peers = CultMeshPeerCatalog() + peers.upsert( + CultMeshPeerCard( + peer_id="peer-a", + verse_id="aetheria-main", + endpoints=("cultnet://peer-a.example.test:3075",), + roles=("read-replica",), + shard_ids=("players",), + ) + ) + + ready = threading.Event() + server_error: list[BaseException] = [] + + def serve_requests() -> None: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(8) + ready.set() + for _ in range(8): + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + request = msgpack.unpackb(read_frame(stream), raw=False) + if request["schemaVersion"] == "cultmesh.verse_catalog_request.v0": + response = verses.create_response(request) + elif request["schemaVersion"] == "cultmesh.peer_exchange_request.v0": + response = peers.create_response(request) + else: + raise AssertionError(f"unexpected request {request['schemaVersion']}") + write_frame(stream, msgpack.packb(response, use_bin_type=True)) + stream.flush() + except BaseException as error: + server_error.append(error) + ready.set() + + port_holder: list[int] = [] + thread = threading.Thread(target=serve_requests, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + self.assertFalse(server_error) + + client = CultMeshDiscoveryClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + fetched_verses = client.fetch_verses(transport_version="cultmesh.v0") + fetched_peers = client.fetch_peers(verse_id="aetheria-main", roles=["read-replica"]) + local_verses = CultMeshVerseCatalog() + local_peers = CultMeshPeerCatalog() + synced_verses = client.sync_verse_catalog(local_verses, transport_version="cultmesh.v0") + synced_peers = client.sync_peer_catalog(local_peers, verse_id="aetheria-main", roles=["read-replica"]) + discovered_verses = CultMeshVerseCatalog() + discovered_peers = CultMeshPeerCatalog() + endpoint = f"cultnet://127.0.0.1:{port_holder[0]}" + verse_discovery = CultMeshVerseDiscoveryClient.from_endpoint(endpoint, timeout_seconds=2.0) + peer_discovery = CultMeshPeerExchangeClient.from_endpoint(endpoint, timeout_seconds=2.0) + discovered_verse_count = verse_discovery.discover( + discovered_verses, + endpoints=[endpoint, endpoint, ""], + transport_version="cultmesh.v0", + ) + discovered_peer_count = peer_discovery.discover( + discovered_peers, + verse_id="aetheria-main", + endpoints=[endpoint, endpoint, ""], + roles=["read-replica"], + ) + fanout_verses = CultMeshVerseCatalog() + fanout_verses.upsert( + CultMeshVerseDescriptor( + verse_id="aetheria-main", + display_name="Known Aetheria", + authority_model="federated", + compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules-1"), + discovery_endpoints=(endpoint, endpoint, ""), + ) + ) + fanout_peers = CultMeshPeerCatalog() + fanout_peers.upsert( + CultMeshPeerCard( + peer_id="known-peer", + verse_id="aetheria-main", + endpoints=(endpoint, endpoint, ""), + roles=("read-replica",), + ) + ) + fanout_verse_count = verse_discovery.fanout(fanout_verses, transport_version="cultmesh.v0") + fanout_peer_count = peer_discovery.fanout( + fanout_peers, + verse_id="aetheria-main", + roles=["read-replica"], + ) + + thread.join(2.0) + self.assertFalse(server_error) + self.assertEqual(fetched_verses[0].verse_id, "aetheria-main") + self.assertEqual(fetched_verses[0].compatibility.required_plugin_ids, ("core",)) + self.assertEqual(fetched_peers[0].peer_id, "peer-a") + self.assertEqual(fetched_peers[0].shard_ids, ("players",)) + self.assertEqual(synced_verses[0].verse_id, "aetheria-main") + self.assertEqual(local_peers.find("aetheria-main", role="read-replica")[0].peer_id, "peer-a") + self.assertEqual(synced_peers[0].roles, ("read-replica",)) + self.assertEqual(discovered_verse_count, 1) + self.assertEqual(discovered_peer_count, 1) + self.assertEqual(discovered_verses.get("aetheria-main").display_name, "Aetheria") + self.assertEqual(discovered_peers.find("aetheria-main", role="read-replica")[0].peer_id, "peer-a") + self.assertEqual(fanout_verse_count, 1) + self.assertEqual(fanout_peer_count, 1) + self.assertEqual(fanout_verses.get("aetheria-main").display_name, "Aetheria") + self.assertIn( + "peer-a", + {peer.peer_id for peer in fanout_peers.find("aetheria-main", role="read-replica")}, + ) + + def test_cultmesh_discovery_client_fans_out_simulation_observations_to_peers(self) -> None: + first_hub = CultNetSimulationObservationHub( + CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) + ) + second_hub = CultNetSimulationObservationHub( + CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) + ) + first_server = CultMesh.serve_node( + CultMesh.create_node(runtime_id="sim-peer-a"), + observation_hub=first_hub, + ) + second_server = CultMesh.serve_node( + CultMesh.create_node(runtime_id="sim-peer-b"), + observation_hub=second_hub, + ) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="sim-peer-a", + verse_id="arena", + endpoints=(f"cultnet://127.0.0.1:{first_server.port}",), + roles=("simulation-witness",), + )) + peers.upsert(CultMeshPeerCard( + peer_id="sim-peer-b", + verse_id="arena", + endpoints=(f"cultnet://127.0.0.1:{second_server.port}",), + roles=("simulation-witness",), + )) + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:200") + message = simulation_observation( + message_id="fanout-observation", + witness_runtime_id="python-fanout", + shard_id="arena", + shard_epoch=1, + frame=200, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice hit bob", + ).to_wire() + + client = CultMeshDiscoveryClient("127.0.0.1", first_server.port, timeout_seconds=2.0) + candidates = client.fanout_simulation_observation( + peers, + message, + verse_id="arena", + roles=["simulation-witness"], + ) + finally: + first_server.stop() + second_server.stop() + + self.assertEqual([candidate["schemaVersion"] for candidate in candidates], [ + "cultnet.simulation_consensus_candidate.v0", + "cultnet.simulation_consensus_candidate.v0", + ]) + self.assertEqual({candidate["claimHash"] for candidate in candidates}, {claim_hash}) + self.assertEqual({candidate["messageId"] for candidate in candidates}, {"fanout-observation"}) + self.assertEqual({candidate["hasQuorum"] for candidate in candidates}, {True}) + + def test_cultmesh_simulation_observation_fanout_flushes_queued_messages(self) -> None: + hub = CultNetSimulationObservationHub( + CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) + ) + server = CultMesh.serve_node(CultMesh.create_node(runtime_id="sim-flush-peer"), observation_hub=hub) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="sim-flush-peer", + verse_id="arena", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("simulation-witness",), + )) + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:210") + seen_candidates: list[dict[str, object]] = [] + fanout = CultMeshSimulationObservationFanout( + CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), + peers, + verse_id="arena", + roles=["simulation-witness"], + on_candidate=seen_candidates.append, + ) + fanout.enqueue(simulation_observation( + message_id="queued-observation", + witness_runtime_id="python-fanout", + shard_id="arena", + shard_epoch=1, + frame=210, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice hit bob", + ).to_wire()) + + candidates = fanout.flush() + finally: + server.stop() + + self.assertEqual(fanout.pending_count(), 0) + self.assertEqual([candidate["claimHash"] for candidate in candidates], [claim_hash]) + self.assertEqual(seen_candidates[0]["messageId"], "queued-observation") + + def test_cultmesh_simulation_observation_fanout_keeps_failed_messages_pending(self) -> None: + closed_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + closed_socket.bind(("127.0.0.1", 0)) + closed_port = closed_socket.getsockname()[1] + closed_socket.close() + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="closed-simulation-peer", + verse_id="arena", + endpoints=(f"cultnet://127.0.0.1:{closed_port}",), + roles=("simulation-witness",), + )) + errors: list[tuple[str, str]] = [] + fanout = CultMeshSimulationObservationFanout( + CultMeshDiscoveryClient("127.0.0.1", closed_port, timeout_seconds=0.2), + peers, + verse_id="arena", + roles=["simulation-witness"], + on_error=lambda endpoint, error: errors.append((endpoint, str(error))), + ) + fanout.enqueue(simulation_observation( + message_id="retry-observation", + witness_runtime_id="python-fanout", + shard_id="arena", + shard_epoch=1, + frame=212, + subject_id="bob", + claim_kind="miss", + claim_hash=compute_simulation_claim_hash("miss", "alice", "bob", "frame:212"), + claim_summary="alice missed bob", + ).to_wire()) + + candidates = fanout.flush() + + self.assertEqual(candidates, []) + self.assertEqual(fanout.pending_count(), 1) + self.assertEqual(len(errors), 1) + self.assertIn(f"127.0.0.1:{closed_port}", errors[0][0]) + + def test_cultmesh_simulation_observation_fanout_runs_background_loop(self) -> None: + hub = CultNetSimulationObservationHub( + CultNetSimulationConsensusOptions(minimum_witnesses=1, quorum_ratio=1.0) + ) + server = CultMesh.serve_node(CultMesh.create_node(runtime_id="sim-loop-peer"), observation_hub=hub) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="sim-loop-peer", + verse_id="arena", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("simulation-witness",), + )) + claim_hash = compute_simulation_claim_hash("block", "dana", "eve", "frame:211") + seen_candidates: list[dict[str, object]] = [] + fanout = CultMeshSimulationObservationFanout( + CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), + peers, + verse_id="arena", + roles=["simulation-witness"], + interval_seconds=0.05, + on_candidate=seen_candidates.append, + ) + fanout.start() + fanout.enqueue(simulation_observation( + message_id="loop-observation", + witness_runtime_id="python-fanout", + shard_id="arena", + shard_epoch=1, + frame=211, + subject_id="eve", + claim_kind="block", + claim_hash=claim_hash, + claim_summary="dana blocked eve", + ).to_wire()) + deadline = time.monotonic() + 2.0 + while not seen_candidates and time.monotonic() < deadline: + time.sleep(0.01) + fanout.stop() + finally: + server.stop() + + self.assertEqual(fanout.pending_count(), 0) + self.assertTrue(seen_candidates) + self.assertEqual(seen_candidates[0]["claimHash"], claim_hash) + self.assertEqual(seen_candidates[0]["messageId"], "loop-observation") + + def test_cultmesh_discovery_client_fans_out_snapshots_to_peers(self) -> None: + document = define_database_entry_type( + "mesh.snapshot_fanout_note", + [("body", 0)], + schema_id="mesh.snapshot_fanout_note.v1", + ) + first = CultMesh.create_node(runtime_id="snapshot-peer-a") + first.database.register_document(document) + first.database.put_raw_message(document, "note:a", {"body": "from-a"}, shard_id="notes", shard_epoch=1) + second = CultMesh.create_node(runtime_id="snapshot-peer-b") + second.database.register_document(document) + second.database.put_raw_message(document, "note:b", {"body": "from-b"}, shard_id="notes", shard_epoch=1) + target = CultMesh.create_node(runtime_id="snapshot-target") + target.database.register_document(document) + first_server = CultMesh.serve_node(first) + second_server = CultMesh.serve_node(second) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-peer-a", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{first_server.port}", f"cultnet://127.0.0.1:{first_server.port}"), + roles=("read-replica",), + )) + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-peer-b", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{second_server.port}",), + roles=("read-replica",), + )) + + client = CultMeshDiscoveryClient("127.0.0.1", first_server.port, timeout_seconds=2.0) + responses = client.fanout_snapshot_responses( + peers, + verse_id="mesh", + roles=["read-replica"], + schema_ids=["mesh.snapshot_fanout_note.v1"], + ) + applied = client.sync_snapshots( + target.database, + peers, + verse_id="mesh", + roles=["read-replica"], + schema_ids=["mesh.snapshot_fanout_note.v1"], + ) + finally: + first_server.stop() + second_server.stop() + + self.assertEqual(len(responses), 2) + self.assertTrue(all(isinstance(response, CultNetRawSnapshotResponse) for response in responses)) + self.assertEqual( + sorted(record.record_key for response in responses for record in response.documents), + ["note:a", "note:b"], + ) + self.assertEqual(sorted(record.record_key for record in applied), ["note:a", "note:b"]) + self.assertEqual(target.database.get_required(document, "note:a")["body"], "from-a") + self.assertEqual(target.database.get_required(document, "note:b")["body"], "from-b") + + def test_cultmesh_discovery_client_sync_documents_returns_requested_aliases(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.snapshot_fanout_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.snapshot_fanout_alias_note.v1", + schema_name="mesh.snapshot_fanout_alias_note", + schema_version="mesh.snapshot_fanout_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.snapshot_fanout_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.snapshot_fanout_alias_note.v1", + schema_name="mesh.snapshot_fanout_alias_note", + schema_version="mesh.snapshot_fanout_alias_note.v1", + ) + source = CultMesh.create_node(runtime_id="snapshot-alias-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:alias", CanonicalNote("from-source")) + target = CultMesh.create_node(runtime_id="snapshot-alias-target") + target.database.register_document(document) + server = CultMesh.serve_node(source) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-alias-source", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + + values = CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0).sync_documents( + target.database, + peers, + verse_id="mesh", + roles=["read-replica"], + documents=[(alias, "note:alias")], + ) + finally: + server.stop() + + self.assertEqual(len(values), 1) + self.assertIsInstance(values[0], UiNote) + self.assertEqual(values[0].body, "from-source") + self.assertEqual(target.database.get_required(document, "note:alias").body, "from-source") + self.assertIsInstance(target.database.get_required(alias, "note:alias"), UiNote) + + def test_cultmesh_document_subscription_syncs_alias_snapshot_and_changes(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.subscription_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.subscription_alias_note.v1", + schema_name="mesh.subscription_alias_note", + schema_version="mesh.subscription_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.subscription_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.subscription_alias_note.v1", + schema_name="mesh.subscription_alias_note", + schema_version="mesh.subscription_alias_note.v1", + ) + source = CultMesh.create_node(runtime_id="subscription-alias-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:live", CanonicalNote("initial")) + target = CultMesh.create_node(runtime_id="subscription-alias-target") + target.database.register_document(document) + server = CultMesh.serve_node(source) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + with CultMesh.subscribe_document(target, raw_client, alias, "note:live") as subscription: + initial = subscription.sync_initial() + subscription.send(document_put_raw( + message_id="subscription-alias-put", + key="note:live", + schema_id="mesh.subscription_alias_note.v1", + stored_at="2026-06-14T00:00:00Z", + payload=document.encode_payload(CanonicalNote("updated")), + )) + updated = subscription.read_next_change() + subscription.send(document_delete( + message_id="subscription-alias-delete", + schema_id="mesh.subscription_alias_note.v1", + record_key="note:live", + )) + removed = subscription.read_next_change() + finally: + server.stop() + + self.assertIsInstance(initial, UiNote) + self.assertEqual(initial.body, "initial") + self.assertEqual(updated.change_kind, "updated") + self.assertIsInstance(updated.value, UiNote) + self.assertEqual(updated.value.body, "updated") + self.assertIsInstance(updated.previous_value, UiNote) + self.assertEqual(updated.previous_value.body, "initial") + self.assertEqual(removed.change_kind, "removed") + self.assertIsNone(removed.value) + self.assertIsInstance(removed.previous_value, UiNote) + self.assertEqual(removed.previous_value.body, "updated") + self.assertIsNone(target.database.get(alias, "note:live")) + + def test_cultmesh_snapshot_fanout_syncs_once_and_reports_applied_records(self) -> None: + document = define_database_entry_type( + "mesh.snapshot_loop_note", + [("body", 0)], + schema_id="mesh.snapshot_loop_note.v1", + ) + source = CultMesh.create_node(runtime_id="snapshot-loop-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:sync-once", {"body": "from-source"}) + target = CultMesh.create_node(runtime_id="snapshot-loop-target") + target.database.register_document(document) + server = CultMesh.serve_node(source) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-loop-source", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + seen: list[str] = [] + fanout = CultMeshSnapshotFanout( + CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), + target.database, + peers, + verse_id="mesh", + roles=["read-replica"], + schema_ids=["mesh.snapshot_loop_note.v1"], + on_applied=lambda record: seen.append(record.record_key), + ) + + applied = fanout.sync_once() + finally: + server.stop() + + self.assertEqual([record.record_key for record in applied], ["note:sync-once"]) + self.assertEqual(seen, ["note:sync-once"]) + self.assertEqual(target.database.get_required(document, "note:sync-once")["body"], "from-source") + + def test_cultmesh_snapshot_fanout_syncs_typed_documents_and_reports_alias_values(self) -> None: + @dataclass + class CanonicalNote: + body: str + + @dataclass + class UiNote: + body: str + + document = define_database_entry_type( + "mesh.snapshot_loop_alias_note", + [("body", 0)], + cls=CanonicalNote, + schema_id="mesh.snapshot_loop_alias_note.v1", + schema_name="mesh.snapshot_loop_alias_note", + schema_version="mesh.snapshot_loop_alias_note.v1", + ) + alias = define_database_entry_type( + "mesh.snapshot_loop_alias_note.ui", + [("body", 0)], + cls=UiNote, + schema_id="mesh.snapshot_loop_alias_note.v1", + schema_name="mesh.snapshot_loop_alias_note", + schema_version="mesh.snapshot_loop_alias_note.v1", + ) + source = CultMesh.create_node(runtime_id="snapshot-loop-alias-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:typed-loop", CanonicalNote("from-source")) + target = CultMesh.create_node(runtime_id="snapshot-loop-alias-target") + target.database.register_document(document) + server = CultMesh.serve_node(source) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-loop-alias-source", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + seen: list[UiNote] = [] + fanout = CultMeshSnapshotFanout( + CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), + target.database, + peers, + verse_id="mesh", + roles=["read-replica"], + documents=[(alias, "note:typed-loop")], + on_document=seen.append, + ) + + values = fanout.sync_once() + finally: + server.stop() + + self.assertEqual(len(values), 1) + self.assertIsInstance(values[0], UiNote) + self.assertEqual(values[0].body, "from-source") + self.assertEqual(seen, [values[0]]) + self.assertEqual(target.database.get_required(document, "note:typed-loop").body, "from-source") + self.assertIsInstance(target.database.get_required(alias, "note:typed-loop"), UiNote) + + def test_cultmesh_snapshot_fanout_runs_background_loop(self) -> None: + document = define_database_entry_type( + "mesh.snapshot_background_note", + [("body", 0)], + schema_id="mesh.snapshot_background_note.v1", + ) + source = CultMesh.create_node(runtime_id="snapshot-background-source") + source.database.register_document(document) + source.database.put_raw_message(document, "note:background", {"body": "from-background"}) + target = CultMesh.create_node(runtime_id="snapshot-background-target") + target.database.register_document(document) + server = CultMesh.serve_node(source) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="snapshot-background-source", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + seen: list[str] = [] + fanout = CultMeshSnapshotFanout( + CultMeshDiscoveryClient("127.0.0.1", server.port, timeout_seconds=2.0), + target.database, + peers, + verse_id="mesh", + roles=["read-replica"], + schema_ids=["mesh.snapshot_background_note.v1"], + interval_seconds=0.05, + on_applied=lambda record: seen.append(record.record_key), + ) + fanout.start() + deadline = time.monotonic() + 2.0 + while not seen and time.monotonic() < deadline: + time.sleep(0.01) + fanout.stop() + finally: + server.stop() + + self.assertTrue(seen) + self.assertEqual(target.database.get_required(document, "note:background")["body"], "from-background") + + def test_cultmesh_peer_health_monitor_probes_reachable_and_failed_endpoints(self) -> None: + document = define_database_entry_type( + "mesh.health_note", + [("body", 0)], + schema_id="mesh.health_note.v1", + ) + node = CultMesh.create_node(runtime_id="health-peer") + node.database.register_document(document) + server = CultMesh.serve_node(node) + closed_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + closed_socket.bind(("127.0.0.1", 0)) + closed_port = closed_socket.getsockname()[1] + closed_socket.close() + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="health-peer", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + peers.upsert(CultMeshPeerCard( + peer_id="closed-peer", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{closed_port}",), + roles=("read-replica",), + )) + seen: list[tuple[str, bool]] = [] + monitor = CultMeshPeerHealthMonitor( + runtime_id="health-prober", + timeout_seconds=0.2, + on_update=lambda health: seen.append((health.peer_id, health.is_reachable)), + ) + + results = monitor.probe_catalog(peers, verse_id="mesh", roles=["read-replica"]) + finally: + server.stop() + + by_peer = {result.peer_id: result for result in results} + self.assertTrue(by_peer["health-peer"].is_reachable) + self.assertEqual(by_peer["health-peer"].runtime_id, "health-peer") + self.assertEqual(by_peer["health-peer"].runtime_kind, "python") + self.assertEqual(by_peer["health-peer"].display_name, "health-peer") + self.assertEqual(by_peer["health-peer"].supported_document_types, ("mesh.health_note",)) + self.assertIn("cultnet.hello.v0", by_peer["health-peer"].supported_message_versions) + self.assertIn("cultnet.error.v0", by_peer["health-peer"].supported_message_versions) + self.assertEqual(by_peer["health-peer"].supported_mutation_contracts[0]["documentType"], "mesh.health_note") + self.assertIn("shardLog", by_peer["health-peer"].supported_mutation_contracts[0]["operations"]) + self.assertFalse(by_peer["closed-peer"].is_reachable) + self.assertIsNone(by_peer["closed-peer"].runtime_kind) + self.assertEqual(by_peer["closed-peer"].supported_document_types, ()) + self.assertEqual(by_peer["closed-peer"].supported_message_versions, ()) + self.assertEqual(by_peer["closed-peer"].supported_mutation_contracts, ()) + self.assertTrue(by_peer["closed-peer"].error) + self.assertEqual({peer_id for peer_id, _ in seen}, {"health-peer", "closed-peer"}) + self.assertEqual(len(monitor.latest()), 2) + + def test_cultmesh_peer_health_monitor_runs_background_probe_loop(self) -> None: + server = CultMesh.serve_node(CultMesh.create_node(runtime_id="health-loop-peer")) + try: + peers = CultMeshPeerCatalog() + peers.upsert(CultMeshPeerCard( + peer_id="health-loop-peer", + verse_id="mesh", + endpoints=(f"cultnet://127.0.0.1:{server.port}",), + roles=("read-replica",), + )) + seen: list[str] = [] + monitor = CultMeshPeerHealthMonitor( + runtime_id="health-loop-prober", + timeout_seconds=0.2, + interval_seconds=0.05, + on_update=lambda health: seen.append(health.peer_id), + ) + monitor.start(peers, verse_id="mesh", roles=["read-replica"]) + deadline = time.monotonic() + 2.0 + while time.monotonic() < deadline: + latest = monitor.latest() + if latest and latest[0].is_reachable: + break + time.sleep(0.01) + monitor.stop() + finally: + server.stop() + + self.assertTrue(seen) + latest = monitor.latest() + self.assertEqual(latest[0].peer_id, "health-loop-peer") + self.assertTrue(latest[0].is_reachable) + + def test_cultmesh_daemon_entrypoint_serves_framed_cultnet_hello(self) -> None: + with tempfile.TemporaryDirectory() as temp: + ready_path = Path(temp) / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "daemon-test-peer", + "--display-name", + "Daemon Test Peer", + "--port", + "0", + "--ready-file", + str(ready_path), + "--seed-interop-note", + "--seed-shard-id", + "daemon-interop", + "--verse-id", + "daemon-verse", + "--verse-display-name", + "Daemon Verse", + "--role", + "shard-primary", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + deadline = time.monotonic() + 5.0 + while not ready_path.exists() and time.monotonic() < deadline: + if process.poll() is not None: + break + time.sleep(0.05) + if not ready_path.exists(): + _, stderr = process.communicate(timeout=2.0) + self.fail(f"cultmesh daemon did not publish readiness: {stderr}") + ready = json.loads(ready_path.read_text(encoding="utf-8")) + self.assertEqual(ready["schemaVersion"], READY_SCHEMA_VERSION) + self.assertEqual(ready["runtimeId"], "daemon-test-peer") + self.assertEqual(ready["runtimeKind"], "python") + self.assertEqual(ready["displayName"], "Daemon Test Peer") + self.assertEqual(ready["endpoint"], f"cultnet://127.0.0.1:{ready['port']}") + self.assertEqual(ready["shardIds"], ["daemon-interop"]) + self.assertEqual(ready["supportedDocumentTypes"], ["cultcache.interop-note"]) + self.assertIn("cultnet.hello.v0", ready["supportedMessageVersions"]) + self.assertIn("cultnet.document_put_raw.v0", ready["supportedMessageVersions"]) + self.assertNotIn("cultnet.simulation_observation.v0", ready["supportedMessageVersions"]) + self.assertEqual(ready["supportedMutationContracts"][0]["documentType"], "cultcache.interop-note") + self.assertIn("shardLog", ready["supportedMutationContracts"][0]["operations"]) + self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": None}) + self.assertEqual(ready["verses"][0]["verseId"], "daemon-verse") + self.assertEqual(ready["verses"][0]["displayName"], "Daemon Verse") + self.assertEqual(ready["verses"][0]["discoveryEndpoints"], [ready["endpoint"]]) + self.assertEqual(ready["verses"][0]["authorityRuntimeIds"], ["daemon-test-peer"]) + self.assertEqual(ready["peers"][0]["peerId"], "daemon-test-peer") + self.assertEqual(ready["peers"][0]["endpoints"], [ready["endpoint"]]) + self.assertEqual(ready["peers"][0]["roles"], ["shard-primary"]) + self.assertEqual(ready["peers"][0]["shardIds"], ["daemon-interop"]) + + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + hello_response = client.request( + hello(runtime_id="daemon-test-prober"), + expected_schema_version="cultnet.hello.v0", + ) + + self.assertEqual(hello_response["runtimeId"], "daemon-test-peer") + self.assertEqual(hello_response["displayName"], "Daemon Test Peer") + self.assertIn("cultnet.hello.v0", hello_response["supportedMessageVersions"]) + self.assertIn("cultnet.schema_catalog_request.v0", hello_response["supportedMessageVersions"]) + self.assertNotIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) + self.assertEqual(hello_response["supportedDocumentTypes"], ["cultcache.interop-note"]) + self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") + self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["port"], int(ready["port"])) + self.assertEqual(ready["supportedMessageVersions"], hello_response["supportedMessageVersions"]) + self.assertEqual(ready["supportedMutationContracts"], hello_response["supportedMutationContracts"]) + self.assertEqual(ready["transportProfiles"], hello_response["transportProfiles"]) + + schema_response = client.fetch_schema_catalog(schema_ids=["cultcache.interop-note"], include_schema_json=True) + self.assertEqual(schema_response["schemas"][0]["schemaVersion"], INTEROP_SCHEMA_VERSION) + self.assertEqual(schema_response["schemas"][0]["documentType"], "cultcache.interop-note") + + snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) + self.assertEqual(snapshot_response.documents[0].record_key, "note:daemon-test-peer") + note = interop_note_document.decode_payload(snapshot_response.documents[0].payload) + self.assertEqual(note["authorRuntimeId"], "daemon-test-peer") + self.assertIn("daemon", note["tags"]) + + shard_catalog = client.fetch_shard_catalog(schema_ids=["cultcache.interop-note"]) + self.assertEqual(shard_catalog["shards"][0]["shardId"], "daemon-interop") + self.assertEqual(shard_catalog["shards"][0]["schemaIds"], ["cultcache.interop-note"]) + + shard_log = client.fetch_shard_log_response(shard_id="daemon-interop", shard_epoch=1) + self.assertFalse(shard_log.resync_required) + self.assertEqual(shard_log.entries[0].sequence, 1) + self.assertEqual(shard_log.entries[0].change_kind, "added") + self.assertIsNotNone(shard_log.entries[0].raw_document) + assert shard_log.entries[0].raw_document is not None + self.assertEqual(shard_log.entries[0].raw_document.record_key, "note:daemon-test-peer") + + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") + self.assertEqual(verses[0].verse_id, "daemon-verse") + self.assertEqual(verses[0].display_name, "Daemon Verse") + self.assertEqual(verses[0].discovery_endpoints, (ready["endpoint"],)) + self.assertEqual(verses[0].authority_runtime_ids, ("daemon-test-peer",)) + self.assertEqual([verse.to_wire() for verse in verses], ready["verses"]) + + peers = discovery_client.fetch_peers(verse_id="daemon-verse", roles=["shard-primary"]) + self.assertEqual(peers[0].peer_id, "daemon-test-peer") + self.assertEqual(peers[0].endpoints, (ready["endpoint"],)) + self.assertEqual(peers[0].roles, ("shard-primary",)) + self.assertEqual(peers[0].shard_ids, ("daemon-interop",)) + self.assertEqual([peer.to_wire() for peer in peers], ready["peers"]) + + monitor = CultMeshPeerHealthMonitor(runtime_id="daemon-health-prober", timeout_seconds=2.0) + health = monitor.probe_peer(peers[0])[0] + self.assertTrue(health.is_reachable) + self.assertEqual(health.runtime_id, "daemon-test-peer") + self.assertEqual(health.supported_document_types, ("cultcache.interop-note",)) + self.assertEqual(health.supported_mutation_contracts[0]["documentType"], "cultcache.interop-note") + finally: + process.terminate() + try: + process.communicate(timeout=5.0) + except subprocess.TimeoutExpired: + process.kill() + process.communicate(timeout=5.0) + + def test_cultmesh_daemon_restarts_with_persisted_cache_and_shard_log(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + cache_path = root / "mesh.cc" + shard_log_path = root / "shard-log" + env = dict(os.environ) + package_src = Path(__file__).resolve().parents[1] / "src" + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + + first_ready = root / "first-ready.json" + first = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "durable-daemon-peer", + "--port", + "0", + "--cache-file", + str(cache_path), + "--enable-durable-shard-logs", + "--shard-log-file", + str(shard_log_path), + "--seed-interop-note", + "--seed-shard-id", + "durable-interop", + "--ready-file", + str(first_ready), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + self._wait_for_ready_file(first, first_ready) + finally: + self._terminate_process(first) + + second_ready = root / "second-ready.json" + second = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "durable-daemon-peer", + "--port", + "0", + "--cache-file", + str(cache_path), + "--enable-durable-shard-logs", + "--shard-log-file", + str(shard_log_path), + "--register-interop-note", + "--verse-id", + "durable-verse", + "--role", + "shard-primary", + "--ready-file", + str(second_ready), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(second, second_ready) + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + + snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) + self.assertEqual(snapshot_response.documents[0].record_key, "note:durable-daemon-peer") + note = interop_note_document.decode_payload(snapshot_response.documents[0].payload) + self.assertEqual(note["authorRuntimeId"], "durable-daemon-peer") + + shard_log = client.fetch_shard_log_response(shard_id="durable-interop", shard_epoch=1) + self.assertFalse(shard_log.resync_required) + self.assertEqual(shard_log.entries[0].sequence, 1) + self.assertIsNotNone(shard_log.entries[0].raw_document) + assert shard_log.entries[0].raw_document is not None + self.assertEqual(shard_log.entries[0].raw_document.record_key, "note:durable-daemon-peer") + + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + peers = discovery_client.fetch_peers(verse_id="durable-verse", roles=["shard-primary"]) + self.assertEqual(peers[0].shard_ids, ("durable-interop",)) + finally: + self._terminate_process(second) + + def test_cultmesh_daemon_serves_live_database_subscription_mutations(self) -> None: + with tempfile.TemporaryDirectory() as temp: + ready_path = Path(temp) / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "live-daemon-peer", + "--port", + "0", + "--seed-interop-note", + "--seed-shard-id", + "live-interop", + "--ready-file", + str(ready_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(process, ready_path) + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + live_note = { + "schemaVersion": INTEROP_SCHEMA_VERSION, + "documentId": "note:daemon-live", + "authorRuntimeId": "daemon-live-client", + "title": "daemon live subscription note", + "body": "live subscription put from Python daemon smoke", + "tags": ["daemon", "subscription", "interop"], + } + + with client.subscribe_database( + subscription_id="daemon-live-sub", + schema_ids=["cultcache.interop-note"], + ) as subscription: + snapshot = subscription.read_next_snapshot_response() + subscription.send(document_put_raw( + message_id="daemon-live-put", + key="note:daemon-live", + schema_id="cultcache.interop-note", + stored_at="2026-06-14T00:00:00Z", + payload=interop_note_document.encode_payload(live_note), + source_runtime_id="daemon-live-client", + shard_id="live-interop", + shard_epoch=1, + )) + change = subscription.read_next_change() + + self.assertEqual(snapshot.documents[0].record_key, "note:live-daemon-peer") + self.assertEqual(change.change_kind, "added") + self.assertEqual(change.record_key, "note:daemon-live") + self.assertIsNotNone(change.raw_document) + assert change.raw_document is not None + self.assertEqual( + interop_note_document.decode_payload(change.raw_document.payload), + live_note, + ) + + shard_log = client.fetch_shard_log_response(shard_id="live-interop", shard_epoch=1) + self.assertFalse(shard_log.resync_required) + self.assertEqual([entry.sequence for entry in shard_log.entries], [1, 2]) + self.assertEqual(shard_log.entries[1].change_kind, "added") + self.assertIsNotNone(shard_log.entries[1].raw_document) + assert shard_log.entries[1].raw_document is not None + self.assertEqual(shard_log.entries[1].raw_document.record_key, "note:daemon-live") + finally: + self._terminate_process(process) + + def test_cultmesh_daemon_serves_compacted_shard_log_resync_boundary(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + cache_path = root / "mesh.cc" + shard_log_path = root / "shard-log" + store = CultNetFileShardMutationLogStore(shard_log_path) + node = CultMesh.create_node( + cache_path, + runtime_id="compacted-daemon-peer", + enable_durable_shard_logs=True, + shard_log_path=shard_log_path, + ) + node.database.register_document(interop_note_document) + node.database.put_raw_message( + interop_note_document, + "note:compacted-one", + { + "schemaVersion": INTEROP_SCHEMA_VERSION, + "documentId": "note:compacted-one", + "authorRuntimeId": "compacted-daemon-peer", + "title": "compacted daemon note one", + "body": "first retained cache value", + "tags": ["daemon", "compaction"], + }, + message_id="compacted-put-1", + shard_id="compacted-interop", + shard_epoch=7, + ) + node.database.put_raw_message( + interop_note_document, + "note:compacted-two", + { + "schemaVersion": INTEROP_SCHEMA_VERSION, + "documentId": "note:compacted-two", + "authorRuntimeId": "compacted-daemon-peer", + "title": "compacted daemon note two", + "body": "second retained cache value", + "tags": ["daemon", "compaction"], + }, + message_id="compacted-put-2", + shard_id="compacted-interop", + shard_epoch=7, + ) + store.compact_through("compacted-interop", 1) + + ready_path = root / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "compacted-daemon-peer", + "--port", + "0", + "--cache-file", + str(cache_path), + "--enable-durable-shard-logs", + "--shard-log-file", + str(shard_log_path), + "--register-interop-note", + "--ready-file", + str(ready_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(process, ready_path) + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + + snapshot_response = client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) + self.assertEqual( + {document.record_key for document in snapshot_response.documents}, + {"note:compacted-one", "note:compacted-two"}, + ) + + compacted = client.fetch_shard_log_response( + shard_id="compacted-interop", + shard_epoch=7, + after_sequence=0, + ) + retained = client.fetch_shard_log_response( + shard_id="compacted-interop", + shard_epoch=7, + after_sequence=1, + ) + + self.assertTrue(compacted.resync_required) + self.assertEqual(compacted.reason, "compacted_history") + self.assertEqual(compacted.compacted_through, 1) + self.assertEqual(compacted.entries, ()) + self.assertFalse(retained.resync_required) + self.assertEqual([entry.sequence for entry in retained.entries], [2]) + self.assertIsNotNone(retained.entries[0].raw_document) + assert retained.entries[0].raw_document is not None + self.assertEqual(retained.entries[0].raw_document.record_key, "note:compacted-two") + finally: + self._terminate_process(process) + + def test_cultmesh_daemon_accepts_schema_write_forwarder_mutations(self) -> None: + with tempfile.TemporaryDirectory() as temp: + ready_path = Path(temp) / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "forwarder-daemon-peer", + "--port", + "0", + "--register-interop-note", + "--ready-file", + str(ready_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(process, ready_path) + endpoint = f"cultnet://127.0.0.1:{ready['port']}" + shard = CultNetShardDescriptor( + shard_id="forwarded-interop", + owner_runtime_id="forwarder-daemon-peer", + epoch=3, + is_primary=False, + schema_ids=("cultcache.interop-note",), + primary_endpoints=(endpoint,), + ) + forwarder: CultNetShardWriteForwarder = CultNetSchemaWriteForwarder(timeout_seconds=2.0) + note = { + "schemaVersion": INTEROP_SCHEMA_VERSION, + "documentId": "note:forwarded-daemon", + "authorRuntimeId": "forwarding-client", + "title": "forwarded daemon note", + "body": "forwarded through the schema write-forwarder", + "tags": ["daemon", "forwarded"], + } + + forwarder.forward_put(shard, document_put_raw( + message_id="forwarded-daemon-put", + key="note:forwarded-daemon", + schema_id="cultcache.interop-note", + stored_at="2026-06-14T00:00:00Z", + payload=interop_note_document.encode_payload(note), + source_runtime_id="forwarding-client", + ).to_wire()) + + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + self._eventually(lambda: bool(client.fetch_snapshot_response( + schema_ids=["cultcache.interop-note"], + record_keys=["note:forwarded-daemon"], + ).documents)) + snapshot = client.fetch_snapshot_response( + schema_ids=["cultcache.interop-note"], + record_keys=["note:forwarded-daemon"], + ) + self.assertEqual( + interop_note_document.decode_payload(snapshot.documents[0].payload), + note, + ) + + forwarder.forward_delete(shard, document_delete( + message_id="forwarded-daemon-delete", + schema_id="cultcache.interop-note", + record_key="note:forwarded-daemon", + ).to_wire()) + self._eventually(lambda: not client.fetch_snapshot_response( + schema_ids=["cultcache.interop-note"], + record_keys=["note:forwarded-daemon"], + ).documents) + + shard_log = client.fetch_shard_log_response(shard_id="forwarded-interop", shard_epoch=3) + self.assertEqual([entry.change_kind for entry in shard_log.entries], ["added", "removed"]) + self.assertEqual(shard_log.entries[0].put["shardId"], "forwarded-interop") + self.assertEqual(shard_log.entries[0].put["shardEpoch"], 3) + self.assertEqual(shard_log.entries[1].delete["shardId"], "forwarded-interop") + self.assertEqual(shard_log.entries[1].delete["shardEpoch"], 3) + finally: + self._terminate_process(process) + + def test_cultmesh_daemon_enforces_snapshot_document_limit_as_peer_error(self) -> None: + with tempfile.TemporaryDirectory() as temp: + ready_path = Path(temp) / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "limited-daemon-peer", + "--port", + "0", + "--seed-interop-note", + "--max-snapshot-documents", + "0", + "--ready-file", + str(ready_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(process, ready_path) + self.assertEqual(ready["snapshotLimits"], {"maxSnapshotBytes": None, "maxSnapshotDocuments": 0}) + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + + with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised: + client.fetch_snapshot_response(schema_ids=["cultcache.interop-note"]) + + self.assertEqual(raised.exception.response["schemaVersion"], "cultnet.error.v0") + self.assertEqual(raised.exception.response["code"], "snapshot_document_limit_exceeded") + self.assertEqual(raised.exception.response["details"]["documentCount"], 1) + self.assertEqual(raised.exception.response["details"]["maxSnapshotDocuments"], 0) + finally: + self._terminate_process(process) + + def test_cultmesh_daemon_serves_opt_in_simulation_observations(self) -> None: + with tempfile.TemporaryDirectory() as temp: + ready_path = Path(temp) / "ready.json" + package_src = Path(__file__).resolve().parents[1] / "src" + env = dict(os.environ) + existing_pythonpath = env.get("PYTHONPATH") + env["PYTHONPATH"] = ( + str(package_src) + if not existing_pythonpath + else f"{package_src}{os.pathsep}{existing_pythonpath}" + ) + process = subprocess.Popen( + [ + sys.executable, + "-m", + "cultmesh_py.daemon", + "--runtime-id", + "simulation-daemon-peer", + "--port", + "0", + "--enable-simulation-observations", + "--simulation-minimum-witnesses", + "2", + "--simulation-quorum-ratio", + "1.0", + "--verse-id", + "simulation-verse", + "--ready-file", + str(ready_path), + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + ) + try: + ready = self._wait_for_ready_file(process, ready_path) + self.assertIn("cultnet.simulation_observation.v0", ready["supportedMessageVersions"]) + self.assertIn("cultnet.simulation_consensus_candidate.v0", ready["supportedMessageVersions"]) + client = CultMesh.create_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + hello_response = client.request( + hello(runtime_id="simulation-daemon-prober"), + expected_schema_version="cultnet.hello.v0", + ) + self.assertEqual(ready["supportedMessageVersions"], hello_response["supportedMessageVersions"]) + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", int(ready["port"]), timeout_seconds=2.0) + peers = discovery_client.fetch_peers(verse_id="simulation-verse", roles=["simulation-observer"]) + self.assertEqual(peers[0].peer_id, "simulation-daemon-peer") + self.assertIn("simulation-observer", peers[0].roles) + self.assertIn("read-replica", peers[0].roles) + + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") + first = client.request( + simulation_observation( + message_id="daemon-sim-1", + witness_runtime_id="watcher-1", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + second = client.request( + simulation_observation( + message_id="daemon-sim-2", + witness_runtime_id="watcher-2", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + + self.assertEqual(first["messageId"], "daemon-sim-1") + self.assertFalse(first["hasQuorum"]) + self.assertEqual(first["witnessCount"], 1) + self.assertEqual(second["messageId"], "daemon-sim-2") + self.assertTrue(second["hasQuorum"]) + self.assertEqual(second["witnessCount"], 2) + self.assertEqual(second["claimHash"], claim_hash) + finally: + self._terminate_process(process) + + def _wait_for_ready_file(self, process: subprocess.Popen[str], ready_path: Path) -> dict[str, object]: + deadline = time.monotonic() + 5.0 + while not ready_path.exists() and time.monotonic() < deadline: + if process.poll() is not None: + break + time.sleep(0.05) + if not ready_path.exists(): + _, stderr = process.communicate(timeout=2.0) + self.fail(f"cultmesh daemon did not publish readiness: {stderr}") + return json.loads(ready_path.read_text(encoding="utf-8")) + + def _terminate_process(self, process: subprocess.Popen[str]) -> None: + process.terminate() + try: + process.communicate(timeout=5.0) + except subprocess.TimeoutExpired: + process.kill() + process.communicate(timeout=5.0) + + def test_cultmesh_local_server_serves_node_and_catalogs_over_clients(self) -> None: + document = define_database_entry_type( + "mesh.server_note", + [("body", 0)], + schema_id="mesh.server_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-server") + node.database.register_document(document) + node.database.put_raw_message(document, "note:1", {"body": "served"}, shard_id="notes", shard_epoch=1) + observation_hub = CultNetSimulationObservationHub( + CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) + ) + seen_candidates: list[CultNetSimulationConsensusCandidate] = [] + unsubscribe_candidates = observation_hub.watch_candidates(seen_candidates.append) + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") + verses = CultMeshVerseCatalog() + verses.upsert( + CultMeshVerseDescriptor( + verse_id="server-verse", + display_name="Server Verse", + authority_model="local", + compatibility=CultMeshVerseCompatibility("cultmesh.v0", "rules"), + ) + ) + peers = CultMeshPeerCatalog() + peers.upsert( + CultMeshPeerCard( + peer_id="mesh-server", + verse_id="server-verse", + endpoints=("cultnet://127.0.0.1:0",), + roles=("read-replica",), + ) + ) + + server = CultMesh.serve_node( + node, + verse_catalog=verses, + peer_catalog=peers, + observation_hub=observation_hub, + display_name="Mesh Server", + ) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + hello_response = raw_client.request(hello(runtime_id="probe"), expected_schema_version="cultnet.hello.v0") + schema_response = raw_client.fetch_schema_catalog(schema_ids=["mesh.server_note.v1"], include_schema_json=True) + rudp_client = CultMesh.create_client( + endpoint=f"rudp://127.0.0.1:{server.port}", + timeout_seconds=2.0, + connection_id=server.rudp_connection_id, + runtime_id="rudp-probe", + ) + rudp_hello_response = rudp_client.request( + hello(runtime_id="rudp-probe"), + expected_schema_version="cultnet.hello.v0", + ) + rudp_schema_response = rudp_client.fetch_schema_catalog( + schema_ids=["mesh.server_note.v1"], + include_schema_json=True, + ) + wire_schema_response = raw_client.fetch_schema_catalog(kinds=["wire_message"], include_schema_json=True) + synced_schema_catalog = CultNetSchemaCatalog() + synced_wire_descriptors = raw_client.sync_schema_catalog( + synced_schema_catalog, + kinds=["wire_message"], + include_schema_json=True, + ) + snapshot_response = raw_client.fetch_snapshot(schema_ids=["mesh.server_note.v1"]) + typed_snapshot_response = raw_client.fetch_snapshot_response(schema_ids=["mesh.server_note.v1"]) + shard_catalog = raw_client.fetch_shard_catalog(schema_ids=["mesh.server_note.v1"]) + synced_shard_catalog = CultNetShardCatalog() + synced_shards = raw_client.sync_shard_catalog(synced_shard_catalog, schema_ids=["mesh.server_note.v1"]) + shard_log = raw_client.fetch_shard_log(shard_id="notes", shard_epoch=1) + typed_shard_log = raw_client.fetch_shard_log_response(shard_id="notes", shard_epoch=1) + self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") + self.assertEqual(hello_response["transportProfiles"][0]["transports"][0]["port"], server.port) + stale_shard_log = raw_client.fetch_shard_log(shard_id="notes", shard_epoch=0) + discovery_client = CultMesh.create_verse_discovery_client("127.0.0.1", server.port, timeout_seconds=2.0) + fetched_verses = discovery_client.fetch_verses(transport_version="cultmesh.v0") + fetched_peers = discovery_client.fetch_peers(verse_id="server-verse", roles=["read-replica"]) + first_candidate = raw_client.request( + simulation_observation( + message_id="obs-1", + witness_runtime_id="watcher-1", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + quorum_candidate = raw_client.request( + simulation_observation( + message_id="obs-2", + witness_runtime_id="watcher-2", + shard_id="arena", + shard_epoch=4, + frame=100, + subject_id="bob", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="alice shot bob first", + ), + expected_schema_version="cultnet.simulation_consensus_candidate.v0", + ) + with raw_client.subscribe_database(subscription_id="sub-server", schema_ids=["mesh.server_note.v1"]) as subscription: + subscription_snapshot = subscription.read_next_snapshot_response() + subscription.send(document_put_raw( + message_id="server-put", + key="note:2", + schema_id="mesh.server_note.v1", + stored_at="2026-06-14T00:00:00Z", + payload=document.encode_payload({"body": "subscribed"}), + shard_id="primary", + shard_epoch=1, + )) + subscription_change = subscription.read_next_change() + subscription.send(document_delete( + message_id="server-delete", + schema_id="mesh.server_note.v1", + record_key="note:2", + shard_id="primary", + shard_epoch=1, + )) + subscription_delete = subscription.read_next_change() + finally: + unsubscribe_candidates() + server.stop() + + self.assertEqual(hello_response["runtimeId"], "mesh-server") + self.assertEqual(hello_response["displayName"], "Mesh Server") + self.assertIn("cultnet.database_subscribe.v0", hello_response["supportedMessageVersions"]) + self.assertIn("cultnet.error.v0", hello_response["supportedMessageVersions"]) + self.assertIn("cultnet.document_put_raw.v0", hello_response["supportedMessageVersions"]) + self.assertIn("cultnet.simulation_observation.v0", hello_response["supportedMessageVersions"]) + self.assertEqual(hello_response["supportedMutationContracts"][0]["documentType"], "mesh.server_note") + self.assertIn("documentDelete", hello_response["supportedMutationContracts"][0]["operations"]) + self.assertIn("shardLog", hello_response["supportedMutationContracts"][0]["operations"]) + advertised_transports = { + transport["protocol"]: transport + for profile in hello_response["transportProfiles"] + for transport in profile["transports"] + } + self.assertEqual(advertised_transports["tcp_framed"]["port"], server.port) + self.assertEqual(advertised_transports["rudp"]["port"], server.port) + self.assertEqual(rudp_hello_response["runtimeId"], "mesh-server") + self.assertEqual(rudp_schema_response["schemas"][0]["schemaId"], "mesh.server_note.v1") + self.assertEqual(schema_response["schemas"][0]["schemaId"], "mesh.server_note.v1") + self.assertIn("schemaJson", schema_response["schemas"][0]) + self.assertIn("cultnet.document_delete.v0", schema_response["schemas"][0]["wireContracts"]) + self.assertIn("cultnet.shard_log_response.v0", schema_response["schemas"][0]["wireContracts"]) + wire_descriptors = {schema["schemaVersion"]: schema for schema in wire_schema_response["schemas"]} + self.assertEqual(wire_descriptors["cultnet.error.v0"]["kind"], "wire_message") + self.assertEqual(wire_descriptors["cultnet.document_put_raw.v0"]["kind"], "wire_message") + self.assertIn("schemaJson", wire_descriptors["cultnet.document_put_raw.v0"]) + self.assertIn("cultmesh.peer_exchange_response.v0", wire_descriptors) + self.assertIn("cultnet.error.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) + self.assertIn("cultnet.document_put_raw.v0", {descriptor.schema_version for descriptor in synced_wire_descriptors}) + self.assertIsNotNone(synced_schema_catalog.get("https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-put-raw.schema.json")) + self.assertEqual(snapshot_response["documents"][0]["recordKey"], "note:1") + self.assertEqual(typed_snapshot_response.documents[0].record_key, "note:1") + self.assertEqual(typed_snapshot_response.documents[0].schema_id, "mesh.server_note.v1") + self.assertEqual(shard_catalog["shards"][0]["schemaIds"], ["mesh.server_note.v1"]) + self.assertEqual(shard_catalog["shards"][0]["epoch"], 1) + self.assertEqual(shard_catalog["shards"][0]["shardId"], "notes") + self.assertEqual(synced_shards[0].shard_id, "notes") + self.assertEqual(synced_shards[0].epoch, 1) + self.assertEqual(synced_shard_catalog.get("notes"), synced_shards[0]) + self.assertEqual(shard_log["entries"][0]["changeKind"], "added") + self.assertTrue(stale_shard_log["resyncRequired"]) + self.assertEqual(stale_shard_log["reason"], "stale_epoch") + self.assertEqual(stale_shard_log["shardEpoch"], 1) + self.assertEqual(stale_shard_log["entries"], []) + self.assertEqual(typed_shard_log.last_sequence, 1) + self.assertEqual(typed_shard_log.entries[0].put["document"]["recordKey"], "note:1") + self.assertIsInstance(typed_shard_log.entries[0].raw_document, CultNetRawDocumentRecord) + self.assertEqual(typed_shard_log.entries[0].raw_document.record_key, "note:1") + self.assertEqual(fetched_verses[0].verse_id, "server-verse") + self.assertEqual(fetched_peers[0].peer_id, "mesh-server") + self.assertEqual(first_candidate["messageId"], "obs-1") + self.assertFalse(first_candidate["hasQuorum"]) + self.assertEqual(quorum_candidate["messageId"], "obs-2") + self.assertEqual(quorum_candidate["claimHash"], claim_hash) + self.assertTrue(quorum_candidate["hasQuorum"]) + self.assertEqual(quorum_candidate["witnessCount"], 2) + self.assertEqual([candidate.claim_hash for candidate in seen_candidates], [claim_hash, claim_hash]) + self.assertIsInstance(subscription_snapshot, CultNetRawSnapshotResponse) + self.assertEqual(subscription_snapshot.to_wire()["schemaVersion"], "cultnet.snapshot_response_raw.v0") + self.assertEqual(subscription_change.change_kind, "added") + self.assertEqual(subscription_change.record_key, "note:2") + self.assertEqual(subscription_change.document["recordKey"], "note:2") + self.assertIsInstance(subscription_change.raw_document, CultNetRawDocumentRecord) + self.assertEqual(subscription_change.raw_document.schema_id, "mesh.server_note.v1") + self.assertEqual(subscription_delete.change_kind, "removed") + self.assertEqual(subscription_delete.record_key, "note:2") + self.assertIsNone(subscription_delete.raw_document) + self.assertIsNone(node.database.get(document, "note:2")) + + def test_cultmesh_local_server_resolves_snapshot_and_shard_catalog_schema_aliases(self) -> None: + document = define_database_entry_type( + "mesh.alias_server_note", + [("body", 0)], + schema_id="sha256:mesh-alias-server-note", + schema_name="mesh.alias_server_note", + schema_version="mesh.alias_server_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-server") + node.database.register_document(document) + node.database.put_raw_message( + document, + "note:alias", + {"body": "served through alias"}, + shard_id="notes", + shard_epoch=1, + ) + + server = CultMesh.serve_node(node) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + snapshot = raw_client.fetch_snapshot(schema_ids=["mesh.alias_server_note.v1"]) + typed_snapshot = raw_client.fetch_snapshot_response(schema_ids=["mesh.alias_server_note.v1"]) + shard_catalog = raw_client.fetch_shard_catalog(schema_ids=["mesh.alias_server_note.v1"]) + finally: + server.stop() + + local_schema_id = document.catalog_entry().schema_id + self.assertEqual(snapshot["documents"][0]["schemaId"], local_schema_id) + self.assertEqual(snapshot["documents"][0]["recordKey"], "note:alias") + self.assertEqual(typed_snapshot.documents[0].schema_id, local_schema_id) + self.assertEqual(shard_catalog["shards"][0]["schemaIds"], [local_schema_id]) + self.assertEqual(shard_catalog["shards"][0]["shardId"], "notes") + + def test_cultmesh_shard_snapshot_keeps_foreign_schema_alias_membership(self) -> None: + document = define_database_entry_type( + "mesh.alias_shard_note", + [ + ("schema_version", 0), + ("body", 1), + ], + schema_id="sha256:mesh-alias-shard-note", + schema_name="mesh.alias_shard_note", + schema_version="mesh.alias_shard_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-shard-server") + node.database.register_document(document) + + node.database.apply_raw_put_message(document_put_raw( + message_id="put-foreign-shard-alias", + key="note:foreign-shard", + schema_id="runtime.generated.mesh.alias_shard_note.ui.99", + stored_at="2026-06-27T00:00:00Z", + payload=document.encode_payload({ + "schema_version": "mesh.alias_shard_note.v1", + "body": "visible through shard snapshot", + }), + shard_id="notes", + shard_epoch=1, + ).to_wire()) + node.database.put_raw_message( + document, + "note:other-shard", + { + "schema_version": "mesh.alias_shard_note.v1", + "body": "hidden by shard filter", + }, + shard_id="other", + shard_epoch=1, + ) + + response = node.database.build_snapshot_response( + schema_ids=["mesh.alias_shard_note.v1"], + shard_id="notes", + shard_epoch=1, + ) + + local_schema_id = document.catalog_entry().schema_id + self.assertEqual(response.shard_id, "notes") + self.assertEqual(response.shard_epoch, 1) + self.assertEqual([(record.schema_id, record.record_key) for record in response.documents], [ + (local_schema_id, "note:foreign-shard"), + ]) + self.assertEqual(node.cache.get_required_envelope(document, "note:foreign-shard").schema_id, local_schema_id) + + node.database.apply_raw_delete_message(document_delete( + message_id="delete-foreign-shard-alias", + schema_id="mesh.alias_shard_note.v1", + record_key="note:foreign-shard", + shard_id="notes", + shard_epoch=1, + ).to_wire()) + deleted_response = node.database.build_snapshot_response( + schema_ids=["mesh.alias_shard_note.v1"], + shard_id="notes", + shard_epoch=1, + ) + self.assertEqual(deleted_response.documents, ()) + + def test_cultmesh_local_server_notifies_schema_alias_subscription_for_raw_put(self) -> None: + document = define_database_entry_type( + "mesh.alias_sub_note", + [ + ("schema_version", 0), + ("body", 1), + ], + schema_id="sha256:mesh-alias-sub-note", + schema_name="mesh.alias_sub_note", + schema_version="mesh.alias_sub_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-sub-server") + node.database.register_document(document) + + server = CultMesh.serve_node(node) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + with raw_client.subscribe_database( + subscription_id="sub-alias", + schema_ids=["mesh.alias_sub_note.v1"], + include_snapshot=False, + ) as subscription: + subscription.send(document_put_raw( + message_id="put-foreign-alias", + key="note:alias-sub", + schema_id="runtime.generated.mesh.alias_sub_note.ui.99", + stored_at="2026-06-27T00:00:00Z", + payload=document.encode_payload({ + "schema_version": "mesh.alias_sub_note.v1", + "body": "notified", + }), + )) + change = subscription.read_next_change() + finally: + server.stop() + + local_schema_id = document.catalog_entry().schema_id + self.assertEqual(change.subscription_id, "sub-alias") + self.assertEqual(change.change_kind, "added") + self.assertEqual(change.record_key, "note:alias-sub") + self.assertIsInstance(change.raw_document, CultNetRawDocumentRecord) + self.assertEqual(change.raw_document.schema_id, "runtime.generated.mesh.alias_sub_note.ui.99") + self.assertEqual(node.database.get_required(document, "note:alias-sub")["body"], "notified") + self.assertEqual(node.cache.get_required_envelope(document, "note:alias-sub").schema_id, local_schema_id) + + def test_cultmesh_local_server_notifies_schema_alias_subscription_for_delete(self) -> None: + document = define_database_entry_type( + "mesh.alias_delete_note", + [("body", 0)], + schema_id="sha256:mesh-alias-delete-note", + schema_name="mesh.alias_delete_note", + schema_version="mesh.alias_delete_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-alias-delete-server") + node.database.register_document(document) + node.database.put_raw_message( + document, + "note:delete-alias", + {"body": "remove me"}, + shard_id="notes", + shard_epoch=1, + ) + + server = CultMesh.serve_node(node) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + with raw_client.subscribe_database( + subscription_id="sub-delete-alias", + schema_ids=[document.catalog_entry().schema_id], + include_snapshot=False, + ) as subscription: + subscription.send(document_delete( + message_id="delete-foreign-alias", + schema_id="mesh.alias_delete_note.v1", + record_key="note:delete-alias", + shard_id="notes", + shard_epoch=1, + )) + change = subscription.read_next_change() + finally: + server.stop() + + self.assertEqual(change.subscription_id, "sub-delete-alias") + self.assertEqual(change.change_kind, "removed") + self.assertEqual(change.schema_id, "mesh.alias_delete_note.v1") + self.assertEqual(change.record_key, "note:delete-alias") + self.assertIsNone(change.raw_document) + self.assertIsNone(node.database.get(document, "note:delete-alias")) + + def test_cultmesh_local_server_rejects_oversized_snapshot_responses(self) -> None: + document = define_database_entry_type( + "mesh.snapshot_limit_note", + [("body", 0)], + schema_id="mesh.snapshot_limit_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-snapshot-limits") + node.database.register_document(document) + node.database.put_raw_message(document, "note:1", {"body": "first"}) + node.database.put_raw_message(document, "note:2", {"body": "second"}) + + server = CultMesh.serve_node(node, max_snapshot_documents=1) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + filtered_snapshot = raw_client.fetch_snapshot(record_keys=["note:1"]) + error = raw_client.request( + snapshot_request(schema_ids=["mesh.snapshot_limit_note.v1"]), + expected_schema_version="cultnet.error.v0", + ) + with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded"): + raw_client.fetch_snapshot(schema_ids=["mesh.snapshot_limit_note.v1"]) + finally: + server.stop() + + self.assertEqual(filtered_snapshot["schemaVersion"], "cultnet.snapshot_response_raw.v0") + self.assertEqual([document["recordKey"] for document in filtered_snapshot["documents"]], ["note:1"]) + self.assertEqual(error["schemaVersion"], "cultnet.error.v0") + self.assertIn("Snapshot document limit exceeded", error["error"]) + self.assertEqual(error["code"], "snapshot_document_limit_exceeded") + self.assertEqual(error["details"]["documentCount"], 2) + self.assertEqual(error["details"]["maxSnapshotDocuments"], 1) + + def test_cultmesh_local_server_returns_errors_for_bad_requests(self) -> None: + document = define_database_entry_type( + "mesh.error_note", + [("body", 0)], + schema_id="mesh.error_note.v1", + ) + node = CultMesh.create_node(runtime_id="mesh-error-server") + node.database.register_document(document) + + server = CultMesh.serve_node(node) + try: + raw_client = CultMesh.create_client("127.0.0.1", server.port, timeout_seconds=2.0) + unsupported = raw_client.request( + {"schemaVersion": "cultnet.nope.v0", "messageId": "bad-schema"}, + expected_schema_version="cultnet.error.v0", + ) + malformed_put = raw_client.request( + {"schemaVersion": "cultnet.document_put_raw.v0", "messageId": "bad-put"}, + expected_schema_version="cultnet.error.v0", + ) + malformed_delete = raw_client.request( + {"schemaVersion": "cultnet.document_delete.v0", "messageId": "bad-delete", "schemaId": "mesh.error_note.v1"}, + expected_schema_version="cultnet.error.v0", + ) + finally: + server.stop() + + self.assertEqual(unsupported["messageId"], "bad-schema") + self.assertEqual(unsupported["code"], "unsupported_schema_version") + self.assertEqual(unsupported["details"]["schemaVersion"], "cultnet.nope.v0") + self.assertIn("Unsupported CultNet message schema", unsupported["error"]) + self.assertEqual(malformed_put["messageId"], "bad-put") + self.assertEqual(malformed_put["code"], "malformed_document_put") + self.assertEqual(malformed_delete["messageId"], "bad-delete") + self.assertEqual(malformed_delete["code"], "malformed_document_delete") + + def test_cultmesh_authority_lease_requires_live_matching_lease(self) -> None: + peer = CultMeshPeerCard( + peer_id="voidbot-local", + verse_id="local", + endpoints=("cultmesh://localhost",), + roles=("shard-primary",), + authority_lease_id="lease:voidbot-local", + ) + leases = CultMeshAuthorityLeaseCatalog() + peers = CultMeshPeerCatalog() + peers.upsert(peer) + now = datetime.now(UTC) + self.assertFalse(leases.is_authorized(peer, "shard-primary", at=now)) + self.assertEqual(peers.find_authorized("local", "shard-primary", leases, at=now), []) + + leases.upsert( + CultMeshAuthorityLease( + lease_id="lease:voidbot-local", + verse_id="local", + peer_id="voidbot-local", + roles=("shard-primary", "shard-primary", ""), + valid_from=now - timedelta(seconds=1), + expires_at=now + timedelta(seconds=1), + shard_ids=("shard-a",), + issuer_runtime_id="odin", + signature="signed", + ) + ) + + lease = leases.get("lease:voidbot-local") + self.assertIsNotNone(lease) + assert lease is not None + self.assertEqual(leases.leases, (lease,)) + self.assertEqual(lease.roles, ("shard-primary",)) + self.assertEqual(lease.issuer_runtime_id, "odin") + self.assertEqual(lease.signature, "signed") + self.assertTrue(lease.is_valid_at(now)) + self.assertTrue(lease.covers(peer, "shard-primary", shard_id="shard-a", at=now)) + self.assertFalse(lease.covers(peer, "shard-primary", shard_id="shard-b", at=now)) + self.assertTrue(leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) + self.assertEqual( + peers.find_authorized("local", "shard-primary", leases, shard_id="shard-a", at=now), + [peer], + ) + self.assertEqual( + peers.first_authorized("local", "shard-primary", leases, shard_id="shard-a", at=now), + peer, + ) + self.assertIsNone(peers.first_authorized("local", "read-replica", leases, at=now)) + self.assertFalse(leases.is_authorized(peer, "shard-primary", shard_id="shard-b", at=now)) + self.assertFalse(leases.is_authorized(peer, "read-replica", at=now)) + self.assertIsNone(leases.get("missing")) + + def test_cultmesh_authority_lease_catalog_can_require_verified_signatures(self) -> None: + peer = CultMeshPeerCard( + peer_id="voidbot-local", + verse_id="local", + endpoints=("cultmesh://localhost",), + roles=("shard-primary",), + shard_ids=("shard-a",), + authority_lease_id="lease:voidbot-local", + ) + now = datetime.now(UTC) + unsigned = CultMeshAuthorityLease( + lease_id="lease:voidbot-local", + verse_id="local", + peer_id="voidbot-local", + roles=("shard-primary",), + valid_from=now - timedelta(seconds=1), + expires_at=now + timedelta(seconds=30), + shard_ids=("shard-a",), + issuer_runtime_id="odin", + ) + verifier = CultMeshHmacAuthorityLeaseVerifier({"odin": b"lease-secret"}) + signed = verifier.issue(unsigned) + strict_leases = CultMesh.create_authority_lease_catalog( + signature_verifier=verifier.verify, + require_verified_signatures=True, + ) + strict_leases.upsert(unsigned) + self.assertFalse(strict_leases.is_verified(unsigned.lease_id)) + self.assertFalse(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) + + strict_leases.upsert(signed) + self.assertTrue(strict_leases.is_verified(signed.lease_id)) + self.assertTrue(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) + + wrong_key = CultMesh.create_authority_lease_catalog( + signature_verifier=CultMeshHmacAuthorityLeaseVerifier({"odin": b"wrong-secret"}).verify, + require_verified_signatures=True, + ) + wrong_key.upsert(signed) + self.assertFalse(wrong_key.is_verified(signed.lease_id)) + self.assertFalse(wrong_key.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) + + tampered = CultMeshAuthorityLease( + lease_id=signed.lease_id, + verse_id=signed.verse_id, + peer_id=signed.peer_id, + roles=("shard-primary", "read-replica"), + valid_from=signed.valid_from, + expires_at=signed.expires_at, + shard_ids=signed.shard_ids, + issuer_runtime_id=signed.issuer_runtime_id, + signature=signed.signature, + ) + strict_leases.upsert(tampered) + self.assertFalse(strict_leases.is_verified(tampered.lease_id)) + self.assertFalse(strict_leases.is_authorized(peer, "shard-primary", shard_id="shard-a", at=now)) + + def test_cultmesh_stream_catalog_negotiates_transport_and_latest_frame(self) -> None: + streams = CultMeshStreamCatalog() + streams.declare( + CultMeshStreamDescriptor( + stream_id="mimir:kiyo-pro", + verse_id="studio", + owner_peer_id="starfire", + kind="video", + label="Kiyo Pro", + clock={"clockDomainId": "starfire-qpc", "confidence": 0.25}, + video={"width": 1920, "height": 1080, "pixelFormat": "YUY2", "framesPerSecond": 30}, + preferred_transports=("shared-d3d12-texture", "shared-memory", "cultcache-page"), + max_in_flight_frames=3, + ) + ) + + negotiation = streams.negotiate( + "mimir:kiyo-pro", + CultMeshStreamConsumerProfile( + peer_id="fensalir", + verse_id="studio", + supported_transports=("shared-d3d12-texture", "cultcache-page"), + accepted_kinds=("video",), + can_import_gpu_handles=True, + max_in_flight_frames=2, + ), + ) + + self.assertEqual(negotiation.transport, "shared-d3d12-texture") + self.assertEqual(negotiation.max_in_flight_frames, 2) + self.assertEqual(negotiation.copy_budget, "zero-copy-target") + + streams.publish_frame( + CultMeshStreamFrameHandle( + stream_id="mimir:kiyo-pro", + sequence=42, + timestamp_ns=1_000_000_000, + duration_ns=33_333_334, + transport="shared-d3d12-texture", + native_handle="0xfeed", + fence_handle="0xbeef", + fence_value=7, + unavoidable_copy_count=0, + ) + ) + self.assertEqual(streams.latest_frame("mimir:kiyo-pro").sequence, 42) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/cultcache-py/tools/reactive_performance_probe.py b/packages/cultmesh-py/tools/reactive_performance_probe.py similarity index 100% rename from packages/cultcache-py/tools/reactive_performance_probe.py rename to packages/cultmesh-py/tools/reactive_performance_probe.py diff --git a/packages/cultnet-py/README.md b/packages/cultnet-py/README.md new file mode 100644 index 00000000..cca39ba7 --- /dev/null +++ b/packages/cultnet-py/README.md @@ -0,0 +1,156 @@ +# cultnet-py + +`cultnet-py` is the Python CultNet runtime: schema-v0 MessagePack wire helpers, +4-byte big-endian framing, the raw client, database subscriptions, shard +catch-up and replication, simulation observations and consensus, witness +artifact bundles, the RUDP codec/session/socket transport, and the CultMesh +wire contracts that both a node and a bare peer have to agree on. + +It depends on `cultcache-py` and `msgpack`; AES-GCM helpers on `CultNetSecret` +light up with the optional `crypto` extra. `cultmesh-py` builds on it. The +layering mirrors `cultnet-rs` and `cultnet-ts`. + +## Example + +```python +from datetime import UTC, datetime + +from cultnet_py import ( + CultNetSecret, + CultNetServerSecurityOptions, + CultNetRawClient, + CultNetSimulationObservation, + CultNetWitnessArtifactBundle, + apply_raw_snapshot, + apply_shard_log_response, + compute_simulation_claim_hash, + database_subscribe, + decode_frame, + encode_frame, + hello, + parse_message, + shard_catalog_request, + simulation_observation, + witness_artifact_bundle, +) + +payload = hello(runtime_id="python-runtime").to_bytes() +message = parse_message(decode_frame(encode_frame(payload))) + +client = CultNetRawClient("127.0.0.1", 3075) +catalog = client.fetch_schema_catalog(kinds=["wire_message"]) +snapshot = client.fetch_snapshot_response(schema_ids=["cultnet.interop-note"]) +shard_catalog = client.fetch_shard_catalog(schema_ids=["cultnet.interop-note"]) +# With a local CultCache and the matching registered document definitions: +# applied = apply_raw_snapshot(cache, [note_doc], snapshot) + +subscription = database_subscribe(subscription_id="ui", schema_ids=["cultnet.interop-note"]) +shard_request = shard_catalog_request(message_id="shards", schema_ids=["cultnet.interop-note"]) +shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=0) +# log_changes = apply_shard_log_response(cache, [note_doc], shard_log) +with client.subscribe_database(subscription_id="ui", schema_ids=["cultnet.interop-note"]) as live: + initial_snapshot = live.read_next_snapshot_response() + +claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit") +observation = simulation_observation( + message_id="obs-1", + witness_runtime_id="python-runtime", + shard_id="interop", + shard_epoch=1, + frame=42, + subject_id="player-1", + claim_kind="hit", + claim_hash=claim_hash, +) +typed_observation = CultNetSimulationObservation.from_wire(observation.to_wire()) +witness = witness_artifact_bundle( + bundle_id="bundle-1", + witness_kind="interop-proof", + captured_at="2026-06-13T00:00:00Z", + subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"}, + contracts=[{"role": "payload", "schemaId": "cultnet.interop-note"}], + artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], + provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-runtime"}, +) +typed_witness = CultNetWitnessArtifactBundle.from_wire(witness) +witness_payload = typed_witness.to_payload() + +security = CultNetServerSecurityOptions.development() +token = CultNetSecret.create_session_token( + "318fb4b6-ff5e-4c4f-b911-d81807de53a8", + datetime(2035, 1, 1, tzinfo=UTC), + security, + session_version=1, +) +session = CultNetSecret.try_validate_session_token(token, security) +``` + +The raw-client request surface can ride RUDP when the peer advertises a +`rudp://` endpoint and shares the connection id for that lane; +`create_rudp_schema_transport(...)` builds that transport, and +`cultmesh_py.CultMesh.create_client(endpoint="rudp://...")` selects it from an +endpoint string. + +## CultMesh wire contracts + +`cultnet_py.cultmesh_contracts` carries the Verse catalog, peer exchange, +authority lease, stream negotiation, and committed simulation fact contracts. +They live here rather than in `cultmesh-py` for the same reason +`CultMeshPeerCard` lives in `cultnet-rs`: the interop peer has to speak them +without owning a node. `cultmesh_py` re-exports every one of them, so callers +that already import from there keep working. + +## RUDP + +`CultNetRudpSession` is the shared reliable/unreliable/sequenced/ordered +session over the RUDP packet codec, with fragmentation above +`max_fragment_bytes`, a bounded reliable window, reliable expiry, and a +bounded fragment reassembly map that evicts the oldest stranded set and counts +it in `fragment_sets_evicted` rather than refusing new payloads. The +socket-backed transport, reconnect policy/controller, and reconnect loop follow +the Rust and TypeScript entrypoint pattern. + +## Interop peer + +The peer can serve, dial, and probe the same raw-state interop lane used by the +TypeScript, Rust, and C# test peers, and answers Verse catalog and peer exchange +requests without a CultMesh node: + +```powershell +python -m cultnet_py.interop_peer serve --runtime-id python-peer --runtime-kind python --display-name "Python Peer" --agent-id python-agent --advertise-host 127.0.0.1 --tcp-port 3075 --discovery-port 4075 --discovery-group 239.77.44.11 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json +python -m cultnet_py.interop_peer dial --runtime-id python-client --runtime-kind python --display-name "Python Client" --agent-id python-client --target-host 127.0.0.1 --target-port 3075 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json +python -m cultnet_py.interop_peer probe --runtime-id python-prober --discovery-port 4075 --discovery-group 239.77.44.11 +``` + +`packages/cultnet-ts/test/interop/cultnet-interop.test.ts` drives it inside the +live TS/Rust/C#/Python peer ring. + +## Performance baseline + +The package ships a lightweight benchmark for Python-owned hot paths. In a +CultLib source checkout, it can also run the C# reference benchmark beside it: + +```powershell +python -m cultnet_py.benchmark --records 1000 --json +python -m cultnet_py.compare_csharp --records 1000 --json +``` + +It reports slot-indexed `DatabaseEntry` encode/decode throughput, framed +CultNet MessagePack parse throughput, raw snapshot application into a +registered cache, and public `CultCache` upsert/get throughput. The C# compare +command also runs `packages/cultnet-py/tools/GameCult.Caching.Benchmark` and +reports median Python-to-C# ratios for the shared public cache operations. It +uses three samples by default; pass `--samples` when you need a different +evidence shape. Treat the numbers as a local baseline, not a claim that Python +matches the C# reference in every workload. + +## Tests + +```powershell +$env:PYTHONPATH="$PWD\packages\cultcache-py\src;$PWD\packages\cultnet-py\src" +python -m unittest discover -s packages\cultnet-py\tests +``` + +The suite runs without `cultmesh_py` on the path. See +[docs/python-runtime-parity.md](../../docs/python-runtime-parity.md) for the +evidence map across the three Python packages. diff --git a/packages/cultnet-py/pyproject.toml b/packages/cultnet-py/pyproject.toml new file mode 100644 index 00000000..f102b58b --- /dev/null +++ b/packages/cultnet-py/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "cultnet-py" +version = "0.1.0" +description = "Python CultNet transport, wire contracts, and replication for GameCult typed state." +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +authors = [{ name = "GameCult" }] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = ["cultcache-py>=0.2.0", "msgpack>=1.0"] +[project.optional-dependencies] +crypto = ["cryptography>=42"] + +[project.scripts] +cultnet-py-interop = "cultnet_py.interop_peer:main" +cultnet-py-benchmark = "cultnet_py.benchmark:main" +cultnet-py-compare-csharp = "cultnet_py.compare_csharp:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +cultnet_py = ["py.typed"] diff --git a/packages/cultcache-py/src/cultnet_py/__init__.py b/packages/cultnet-py/src/cultnet_py/__init__.py similarity index 79% rename from packages/cultcache-py/src/cultnet_py/__init__.py rename to packages/cultnet-py/src/cultnet_py/__init__.py index ec54059d..b1734f25 100644 --- a/packages/cultcache-py/src/cultnet_py/__init__.py +++ b/packages/cultnet-py/src/cultnet_py/__init__.py @@ -7,6 +7,34 @@ from .client import ( create_rudp_schema_transport, create_tcp_framed_schema_transport, ) +from .cultmesh_contracts import ( + PEER_EXCHANGE_REQUEST, + PEER_EXCHANGE_RESPONSE, + SIMULATION_FACT_DOCUMENT_TYPE, + SIMULATION_FACT_SCHEMA_VERSION, + VERSE_CATALOG_REQUEST, + VERSE_CATALOG_RESPONSE, + AuthorityLeaseVerifier, + CultMeshAuthorityLease, + CultMeshAuthorityLeaseCatalog, + CultMeshHmacAuthorityLeaseVerifier, + CultMeshPeerCard, + CultMeshPeerCatalog, + CultMeshSimulationFact, + CultMeshStreamCatalog, + CultMeshStreamConsumerProfile, + CultMeshStreamDescriptor, + CultMeshStreamFrameHandle, + CultMeshStreamNegotiation, + CultMeshVerseCatalog, + CultMeshVerseCompatibility, + CultMeshVerseDescriptor, + peer_exchange_request, + peer_from_wire, + simulation_fact_document, + verse_catalog_request, + verse_from_wire, +) from .framing import decode_frame, encode_frame, read_frame, write_frame from .messages import ( CultNetMessage, @@ -108,6 +136,32 @@ from .simulation import ( ) __all__ = [ + "PEER_EXCHANGE_REQUEST", + "PEER_EXCHANGE_RESPONSE", + "SIMULATION_FACT_DOCUMENT_TYPE", + "SIMULATION_FACT_SCHEMA_VERSION", + "VERSE_CATALOG_REQUEST", + "VERSE_CATALOG_RESPONSE", + "AuthorityLeaseVerifier", + "CultMeshAuthorityLease", + "CultMeshAuthorityLeaseCatalog", + "CultMeshHmacAuthorityLeaseVerifier", + "CultMeshPeerCard", + "CultMeshPeerCatalog", + "CultMeshSimulationFact", + "CultMeshStreamCatalog", + "CultMeshStreamConsumerProfile", + "CultMeshStreamDescriptor", + "CultMeshStreamFrameHandle", + "CultMeshStreamNegotiation", + "CultMeshVerseCatalog", + "CultMeshVerseCompatibility", + "CultMeshVerseDescriptor", + "peer_exchange_request", + "peer_from_wire", + "simulation_fact_document", + "verse_catalog_request", + "verse_from_wire", "CultNetMessage", "CultNetWitnessArtifactBundle", "CultNetDatabaseSubscription", diff --git a/packages/cultcache-py/src/cultcache_py/benchmark.py b/packages/cultnet-py/src/cultnet_py/benchmark.py similarity index 98% rename from packages/cultcache-py/src/cultcache_py/benchmark.py rename to packages/cultnet-py/src/cultnet_py/benchmark.py index a444acd9..83834635 100644 --- a/packages/cultcache-py/src/cultcache_py/benchmark.py +++ b/packages/cultnet-py/src/cultnet_py/benchmark.py @@ -19,7 +19,7 @@ class BenchItem: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(prog="cultcache-py-benchmark") + parser = argparse.ArgumentParser(prog="cultnet-py-benchmark") parser.add_argument("--records", type=int, default=5000) parser.add_argument("--json", action="store_true", dest="as_json") args = parser.parse_args(argv) diff --git a/packages/cultcache-py/src/cultnet_py/client.py b/packages/cultnet-py/src/cultnet_py/client.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/client.py rename to packages/cultnet-py/src/cultnet_py/client.py diff --git a/packages/cultcache-py/src/cultcache_py/compare_csharp.py b/packages/cultnet-py/src/cultnet_py/compare_csharp.py similarity index 99% rename from packages/cultcache-py/src/cultcache_py/compare_csharp.py rename to packages/cultnet-py/src/cultnet_py/compare_csharp.py index 8a9e4c5b..a2654a33 100644 --- a/packages/cultcache-py/src/cultcache_py/compare_csharp.py +++ b/packages/cultnet-py/src/cultnet_py/compare_csharp.py @@ -16,7 +16,7 @@ DEFAULT_PARITY_THRESHOLD = 0.90 def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(prog="cultcache-py-compare-csharp") + parser = argparse.ArgumentParser(prog="cultnet-py-compare-csharp") parser.add_argument("--records", type=int, default=5000) parser.add_argument("--samples", type=int, default=DEFAULT_SAMPLE_COUNT) parser.add_argument("--parity-threshold", type=float, default=DEFAULT_PARITY_THRESHOLD) diff --git a/packages/cultcache-py/src/cultmesh_py/wire.py b/packages/cultnet-py/src/cultnet_py/cultmesh_contracts.py similarity index 84% rename from packages/cultcache-py/src/cultmesh_py/wire.py rename to packages/cultnet-py/src/cultnet_py/cultmesh_contracts.py index 9a666a1e..91d0805b 100644 --- a/packages/cultcache-py/src/cultmesh_py/wire.py +++ b/packages/cultnet-py/src/cultnet_py/cultmesh_contracts.py @@ -1,14 +1,18 @@ from __future__ import annotations import base64 +import hashlib import hmac from dataclasses import dataclass, field, replace -from datetime import datetime +from datetime import UTC, datetime from hashlib import sha256 from typing import Any, Callable, Mapping import msgpack +from cultcache_py import define_database_entry_type +from cultcache_py.documents import DocumentDefinition + VERSE_CATALOG_REQUEST = "cultmesh.verse_catalog_request.v0" VERSE_CATALOG_RESPONSE = "cultmesh.verse_catalog_response.v0" @@ -613,3 +617,104 @@ def copy_budget_for(transport: str) -> str: if transport == "inline-bytes": return "opaque-runtime" return "opaque-runtime" + + +SIMULATION_FACT_DOCUMENT_TYPE = "gamecult.mesh.simulation_fact" +SIMULATION_FACT_SCHEMA_VERSION = "gamecult.mesh.simulation_fact.v1" + + +@dataclass(frozen=True) +class CultMeshSimulationFact: + fact_id: str + shard_id: str + shard_epoch: int + frame: int + subject_id: str + claim_kind: str + claim_hash: str + claim_summary: str | None + witness_count: int + support_weight: float + total_weight: float + confidence: float + committed_at: str + + @staticmethod + def compute_fact_id(candidate: dict[str, Any]) -> str: + canonical = "\x1f".join([ + _candidate_string(candidate, "shardId", "shard_id"), + str(_candidate_int(candidate, "shardEpoch", "shard_epoch")), + str(_candidate_int(candidate, "frame", "frame")), + _candidate_string(candidate, "subjectId", "subject_id"), + _candidate_string(candidate, "claimKind", "claim_kind"), + ]) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + @staticmethod + def create_record_key(candidate: dict[str, Any]) -> str: + return f"simulation:{CultMeshSimulationFact.compute_fact_id(candidate)}" + + @staticmethod + def from_candidate(candidate: dict[str, Any], *, committed_at: str | None = None) -> "CultMeshSimulationFact": + return CultMeshSimulationFact( + fact_id=CultMeshSimulationFact.compute_fact_id(candidate), + shard_id=_candidate_string(candidate, "shardId", "shard_id"), + shard_epoch=_candidate_int(candidate, "shardEpoch", "shard_epoch"), + frame=_candidate_int(candidate, "frame", "frame"), + subject_id=_candidate_string(candidate, "subjectId", "subject_id"), + claim_kind=_candidate_string(candidate, "claimKind", "claim_kind"), + claim_hash=_candidate_string(candidate, "claimHash", "claim_hash"), + claim_summary=_candidate_optional_string(candidate, "claimSummary", "claim_summary"), + witness_count=_candidate_int(candidate, "witnessCount", "witness_count"), + support_weight=_candidate_float(candidate, "supportWeight", "support_weight"), + total_weight=_candidate_float(candidate, "totalWeight", "total_weight"), + confidence=_candidate_float(candidate, "confidence", "confidence"), + committed_at=committed_at or datetime.now(UTC).isoformat(), + ) + + +simulation_fact_document: DocumentDefinition[CultMeshSimulationFact] = define_database_entry_type( + SIMULATION_FACT_DOCUMENT_TYPE, + [ + ("fact_id", 0), + ("shard_id", 1), + ("shard_epoch", 2), + ("frame", 3), + ("subject_id", 4), + ("claim_kind", 5), + ("claim_hash", 6), + ("claim_summary", 7, None), + ("witness_count", 8), + ("support_weight", 9), + ("total_weight", 10), + ("confidence", 11), + ("committed_at", 12), + ], + cls=CultMeshSimulationFact, + name="fact_id", + indexes={"shard": "shard_id", "frame": "frame", "subject": "subject_id", "claim_kind": "claim_kind"}, + schema_name=SIMULATION_FACT_DOCUMENT_TYPE, + schema_version=SIMULATION_FACT_SCHEMA_VERSION, +) + + +def _candidate_string(candidate: dict[str, Any], camel: str, snake: str) -> str: + value = candidate.get(camel, candidate.get(snake, "")) + if value is None: + return "" + return str(value) + + +def _candidate_optional_string(candidate: dict[str, Any], camel: str, snake: str) -> str | None: + value = candidate.get(camel, candidate.get(snake)) + if value is None: + return None + return str(value) + + +def _candidate_int(candidate: dict[str, Any], camel: str, snake: str) -> int: + return int(candidate.get(camel, candidate.get(snake, 0))) + + +def _candidate_float(candidate: dict[str, Any], camel: str, snake: str) -> float: + return float(candidate.get(camel, candidate.get(snake, 0.0))) diff --git a/packages/cultcache-py/src/cultnet_py/framing.py b/packages/cultnet-py/src/cultnet_py/framing.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/framing.py rename to packages/cultnet-py/src/cultnet_py/framing.py diff --git a/packages/cultcache-py/src/cultnet_py/interop_peer.py b/packages/cultnet-py/src/cultnet_py/interop_peer.py similarity index 99% rename from packages/cultcache-py/src/cultnet_py/interop_peer.py rename to packages/cultnet-py/src/cultnet_py/interop_peer.py index 7ee9995e..ca3c692d 100644 --- a/packages/cultcache-py/src/cultnet_py/interop_peer.py +++ b/packages/cultnet-py/src/cultnet_py/interop_peer.py @@ -24,7 +24,7 @@ from cultcache_py import ( define_document_type, ) from cultcache_py.documents import DocumentDefinition -from cultmesh_py import ( +from .cultmesh_contracts import ( PEER_EXCHANGE_REQUEST, SIMULATION_FACT_DOCUMENT_TYPE, VERSE_CATALOG_REQUEST, diff --git a/packages/cultcache-py/src/cultnet_py/messages.py b/packages/cultnet-py/src/cultnet_py/messages.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/messages.py rename to packages/cultnet-py/src/cultnet_py/messages.py diff --git a/packages/cultcache-py/src/cultnet_py/prediction.py b/packages/cultnet-py/src/cultnet_py/prediction.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/prediction.py rename to packages/cultnet-py/src/cultnet_py/prediction.py diff --git a/packages/cultcache-py/src/cultnet_py/py.typed b/packages/cultnet-py/src/cultnet_py/py.typed similarity index 100% rename from packages/cultcache-py/src/cultnet_py/py.typed rename to packages/cultnet-py/src/cultnet_py/py.typed diff --git a/packages/cultcache-py/src/cultnet_py/replication.py b/packages/cultnet-py/src/cultnet_py/replication.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/replication.py rename to packages/cultnet-py/src/cultnet_py/replication.py diff --git a/packages/cultcache-py/src/cultnet_py/schema_catalog.py b/packages/cultnet-py/src/cultnet_py/schema_catalog.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/schema_catalog.py rename to packages/cultnet-py/src/cultnet_py/schema_catalog.py diff --git a/packages/cultcache-py/src/cultnet_py/security.py b/packages/cultnet-py/src/cultnet_py/security.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/security.py rename to packages/cultnet-py/src/cultnet_py/security.py diff --git a/packages/cultcache-py/src/cultnet_py/shard_catalog.py b/packages/cultnet-py/src/cultnet_py/shard_catalog.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/shard_catalog.py rename to packages/cultnet-py/src/cultnet_py/shard_catalog.py diff --git a/packages/cultcache-py/src/cultnet_py/shard_log.py b/packages/cultnet-py/src/cultnet_py/shard_log.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/shard_log.py rename to packages/cultnet-py/src/cultnet_py/shard_log.py diff --git a/packages/cultcache-py/src/cultnet_py/shard_replication.py b/packages/cultnet-py/src/cultnet_py/shard_replication.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/shard_replication.py rename to packages/cultnet-py/src/cultnet_py/shard_replication.py diff --git a/packages/cultcache-py/src/cultnet_py/simulation.py b/packages/cultnet-py/src/cultnet_py/simulation.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/simulation.py rename to packages/cultnet-py/src/cultnet_py/simulation.py diff --git a/packages/cultcache-py/src/cultnet_py/snapshot.py b/packages/cultnet-py/src/cultnet_py/snapshot.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/snapshot.py rename to packages/cultnet-py/src/cultnet_py/snapshot.py diff --git a/packages/cultcache-py/src/cultnet_py/subscription.py b/packages/cultnet-py/src/cultnet_py/subscription.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/subscription.py rename to packages/cultnet-py/src/cultnet_py/subscription.py diff --git a/packages/cultcache-py/src/cultnet_py/transport.py b/packages/cultnet-py/src/cultnet_py/transport.py similarity index 100% rename from packages/cultcache-py/src/cultnet_py/transport.py rename to packages/cultnet-py/src/cultnet_py/transport.py diff --git a/packages/cultnet-py/tests/test_cultnet.py b/packages/cultnet-py/tests/test_cultnet.py new file mode 100644 index 00000000..171a34b9 --- /dev/null +++ b/packages/cultnet-py/tests/test_cultnet.py @@ -0,0 +1,2046 @@ +from __future__ import annotations + +import json +import socket +import threading +import time +import unittest +import hashlib +import hmac +import io +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Callable +from uuid import uuid4 +from cultcache_py import CultCache, define_database_entry_type +from cultnet_py.benchmark import run_benchmark +from cultnet_py.compare_csharp import ( + DEFAULT_PARITY_THRESHOLD, + DEFAULT_SAMPLE_COUNT, + _compare_common_metrics, + _median_result, + _parity_status, +) +from cultnet_py.interop_peer import append_shard_log_put, build_state, raw_snapshot_response +from cultnet_py import ( + compute_simulation_claim_hash, + CultNetDatabaseChange, + CultNetRawClient, + CultNetRawDocumentRecord, + CultNetRawSnapshotResponse, + CultNetSchemaCatalog, + CultNetSchemaDescriptor, + CultNetShardCatalog, + CultNetShardDescriptor, + CultNetShardLogEntry, + CultNetShardLogResponse, + CultNetPeerError, + CultNetClientSecurityOptions, + CultNetSecret, + CultNetServerSecurityOptions, + CultNetSimulationConsensusOptions, + CultNetSimulationConsensusCandidate, + CultNetSimulationObservation, + CultNetRudpPacket, + CultNetRudpPacketType, + CultNetRudpSendOptions, + CultNetRudpSession, + CultNetRudpSessionOptions, + CultNetRudpSocketMode, + CultNetRudpReconnectLoop, + CultNetRudpSocketTransportConnection, + CultNetRudpSocketTransportOptions, + TcpFramedTransportConnection, + CultNetWitnessArtifactBundle, + apply_raw_document_record, + apply_raw_snapshot, + apply_shard_log_response, + create_rudp_transport_profile, + create_tcp_framed_transport_profile, + create_reconnect_policy, + CultNetReconnectController, + database_subscribe, + database_unsubscribe, + decode_frame, + decode_rudp_packet, + decode_witness_artifact_bundle_payload, + document_delete, + document_put_raw, + encode_frame, + encode_rudp_packet, + encode_witness_artifact_bundle_payload, + hello, + login, + login_success, + parse_message, + register, + schema_catalog_request, + schema_document_map, + shard_catalog_request, + shard_log_request, + simulation_observation, + snapshot_request, + verify_session, + wire_message_schema_catalog, + wire_message_schema_descriptors, + witness_artifact_bundle, + compute_reconnect_delay_ms, +) + + +def bind_udp_socket() -> socket.socket: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(("127.0.0.1", 0)) + sock.settimeout(0.02) + return sock + + +def pump_rudp_handshake( + client: CultNetRudpSocketTransportConnection, + server: CultNetRudpSocketTransportConnection, +) -> None: + for _ in range(20): + server.receive_once() + client.receive_once() + server.receive_once() + if client.connected and server.connected: + return + time.sleep(0.005) + raise AssertionError("RUDP socket handshake did not complete") + + +def receive_rudp_frame(transport: CultNetRudpSocketTransportConnection): + for _ in range(20): + frame = transport.receive_once() + if frame is not None: + return frame + time.sleep(0.005) + raise AssertionError("RUDP socket frame was not delivered") + + +@dataclass +class Item: + name: str + category: str + value: int + + +class CultNetTests(unittest.TestCase): + def test_benchmark_reports_core_hot_path_metrics(self) -> None: + result = run_benchmark(8) + metric_names = {metric["name"] for metric in result["metrics"]} + self.assertEqual(result["records"], 8) + self.assertEqual(result["wireMessageCount"], 8) + self.assertEqual(metric_names, { + "database_entry_encode", + "database_entry_decode", + "cultnet_frame_parse", + "raw_snapshot_apply", + "cache_upsert", + "cache_get", + }) + self.assertTrue(all(metric["opsPerSecond"] > 0 for metric in result["metrics"])) + + def test_compare_csharp_median_result_summarizes_samples(self) -> None: + self.assertEqual(DEFAULT_SAMPLE_COUNT, 3) + self.assertEqual(DEFAULT_PARITY_THRESHOLD, 0.90) + samples = [ + { + "runtime": "python", + "records": 3, + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 30.0, "opsPerSecond": 100.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 60.0, "opsPerSecond": 50.0}, + ], + }, + { + "runtime": "python", + "records": 3, + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 10.0, "opsPerSecond": 300.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 150.0}, + ], + }, + { + "runtime": "python", + "records": 3, + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 200.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 40.0, "opsPerSecond": 75.0}, + ], + }, + ] + + summarized = _median_result(samples) + + self.assertEqual(summarized["sampleCount"], 3) + self.assertEqual(summarized["metrics"][0]["name"], "cache_get") + self.assertEqual(summarized["metrics"][0]["elapsedMs"], 20.0) + self.assertEqual(summarized["metrics"][0]["opsPerSecond"], 200.0) + self.assertEqual(summarized["metrics"][1]["opsPerSecond"], 75.0) + + def test_compare_csharp_marks_threshold_status_per_metric(self) -> None: + python_result = { + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 2.0, "opsPerSecond": 50.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0}, + ], + } + csharp_result = { + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, + ], + } + + comparison = _compare_common_metrics(python_result, csharp_result, parity_threshold=0.90) + by_name = {item["name"]: item for item in comparison} + + self.assertFalse(by_name["cache_get"]["meetsParityThreshold"]) + self.assertTrue(by_name["cache_upsert"]["meetsParityThreshold"]) + self.assertEqual(_parity_status(comparison, "ok"), "below-threshold") + self.assertEqual(_parity_status([by_name["cache_upsert"]], "ok"), "meets-threshold") + self.assertEqual(_parity_status(comparison, "failed"), "unknown") + + def test_compare_csharp_marks_meets_threshold_when_all_common_metrics_pass(self) -> None: + comparison = _compare_common_metrics( + { + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 120.0}, + ], + }, + { + "metrics": [ + {"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, + {"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0}, + ], + }, + parity_threshold=0.90, + ) + + self.assertEqual(_parity_status(comparison, "ok"), "meets-threshold") + + def test_cultnet_security_options_match_cultlib_defaults_and_environment_rules(self) -> None: + client = CultNetClientSecurityOptions.development() + server = CultNetServerSecurityOptions.development() + + self.assertEqual(client.connection_key, "gamecult-dev-connection-key") + self.assertEqual(server.connection_key, "gamecult-dev-connection-key") + self.assertTrue(server.is_development) + self.assertEqual(server.to_client_options().connection_key, client.connection_key) + self.assertEqual(client.encryption_key(), hashlib.sha256(b"gamecult-dev-connection-key").digest()) + self.assertEqual(server.session_signing_key(), hashlib.sha256(b"gamecult-dev-session-signing-secret").digest()) + self.assertEqual( + CultNetServerSecurityOptions.from_environment({}, allow_development_defaults=True), + server, + ) + with self.assertRaisesRegex(ValueError, "not configured"): + CultNetServerSecurityOptions.from_environment({}) + with self.assertRaisesRegex(ValueError, "GAMECULT_SESSION_SIGNING_SECRET"): + CultNetServerSecurityOptions.from_environment({"GAMECULT_CONNECTION_KEY": "key"}) + configured = CultNetServerSecurityOptions.from_environment({ + "GAMECULT_CONNECTION_KEY": "prod-key", + "GAMECULT_SESSION_SIGNING_SECRET": "prod-secret", + }) + self.assertEqual(configured.connection_key, "prod-key") + self.assertFalse(configured.is_development) + + def test_cultnet_secret_helpers_validate_csharp_versioned_session_tokens(self) -> None: + security = CultNetServerSecurityOptions("connection-key", "session-secret") + user_id = uuid4() + expires = datetime(2035, 1, 2, 3, 4, 5, tzinfo=UTC) + + token = CultNetSecret.create_session_token(user_id, expires, security, session_version=7) + payload_b64, signature_b64 = token.split(".") + self.assertEqual( + CultNetSecret.from_base64url(payload_b64).decode("utf-8"), + f"{user_id.hex}|{int(expires.timestamp())}|7", + ) + self.assertEqual( + CultNetSecret.from_base64url(signature_b64), + hmac.new( + hashlib.sha256(b"session-secret").digest(), + CultNetSecret.from_base64url(payload_b64), + hashlib.sha256, + ).digest(), + ) + + validated = CultNetSecret.try_validate_session_token( + token, + security, + now_utc=datetime(2035, 1, 2, 3, 4, 4, tzinfo=UTC), + ) + self.assertIsNotNone(validated) + assert validated is not None + self.assertEqual(validated.user_id, user_id) + self.assertEqual(validated.expires_at_utc, expires) + self.assertEqual(validated.session_version, 7) + + tampered_payload = CultNetSecret.to_base64url(CultNetSecret.from_base64url(payload_b64) + b"tamper") + tampered = f"{tampered_payload}.{signature_b64}" + self.assertIsNone(CultNetSecret.try_validate_session_token(tampered, security)) + self.assertIsNone(CultNetSecret.try_validate_session_token(token, security, now_utc=expires)) + + def test_cultnet_secret_helpers_validate_legacy_two_field_session_tokens(self) -> None: + security = CultNetServerSecurityOptions.development() + user_id = uuid4() + expires_at_seconds = int(datetime(2036, 1, 1, tzinfo=UTC).timestamp()) + payload = f"{user_id.hex}|{expires_at_seconds}".encode("utf-8") + signature = hmac.new(security.session_signing_key(), payload, hashlib.sha256).digest() + token = f"{CultNetSecret.to_base64url(payload)}.{CultNetSecret.to_base64url(signature)}" + + validated = CultNetSecret.try_validate_session_token( + token, + security, + now_utc=datetime(2035, 12, 31, tzinfo=UTC), + ) + + self.assertIsNotNone(validated) + assert validated is not None + self.assertEqual(validated.user_id, user_id) + self.assertEqual(validated.session_version, 0) + + def test_cultnet_secret_encryption_helpers_are_optional_without_crypto_dependency(self) -> None: + nonce = b"123456789012" + security = CultNetServerSecurityOptions.development() + try: + encrypted = CultNetSecret.encrypt_string("hello", nonce, security.to_client_options()) + except ImportError as exc: + self.assertIn("cryptography", str(exc)) + return + self.assertEqual(CultNetSecret.decrypt_string(encrypted, nonce, security), "hello") + + def test_cultnet_schema_message_frame_round_trip(self) -> None: + message = hello( + runtime_id="python-test", + supported_schema_versions=["cultnet.hello.v0"], + transport_profiles=[ + { + "schemaVersion": "cultnet.transport_profile.v0", + "runtimeId": "python-test", + "transports": [ + { + "transportId": "test", + "protocol": "tcp_framed", + "channels": [{"channelId": "schema", "delivery": "reliable", "ordering": "ordered"}], + } + ], + } + ], + ) + parsed = parse_message(decode_frame(encode_frame(message.to_bytes()))) + self.assertEqual(parsed.schema_version, "cultnet.hello.v0") + self.assertEqual(parsed.body["runtimeId"], "python-test") + self.assertEqual(parsed.body["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed") + + def test_cultnet_tcp_framed_transport_carries_schema_payloads_with_stats(self) -> None: + payload = b"cultnet-payload" + output = io.BytesIO() + sender = TcpFramedTransportConnection( + output, + profile=create_tcp_framed_transport_profile("sender", transport_id="test-tcp"), + ) + sender.send("schema", payload) + self.assertEqual(sender.stats.frames_sent, 1) + self.assertEqual(sender.stats.bytes_sent, len(payload) + 4) + with self.assertRaisesRegex(ValueError, "only supports the schema channel"): + sender.send("unreliable", b"") + + receiver = TcpFramedTransportConnection( + io.BytesIO(output.getvalue()), + profile=create_tcp_framed_transport_profile("receiver", transport_id="test-tcp"), + ) + frame = receiver.receive() + self.assertEqual(frame.channel_id, "schema") + self.assertEqual(frame.payload, payload) + self.assertEqual(receiver.stats.frames_received, 1) + self.assertEqual(receiver.stats.bytes_received, len(payload) + 4) + self.assertEqual(receiver.profile["transports"][0]["protocol"], "tcp_framed") + + def test_cultnet_raw_client_uses_schema_transport_factory(self) -> None: + import msgpack # type: ignore + + sent_messages: list[dict[str, object]] = [] + test_case = self + + class FakeSchemaTransport: + profile = create_tcp_framed_transport_profile("fake-client") + + def __enter__(self) -> "FakeSchemaTransport": + return self + + def __exit__(self, exc_type: object, exc: object, traceback: object) -> None: + self.close() + + def send(self, channel_id: str, payload: bytes) -> None: + test_case.assertEqual(channel_id, "schema") + sent_messages.append(msgpack.unpackb(payload, raw=False)) + + def receive(self) -> object: + response = { + "schemaVersion": "cultnet.schema_catalog_response.v0", + "messageId": "transport-factory", + "schemas": [], + } + return type("Frame", (), {"channel_id": "schema", "payload": msgpack.packb(response, use_bin_type=True)})() + + def close(self) -> None: + pass + + client = CultNetRawClient( + "unused.example.test", + 1, + create_transport=FakeSchemaTransport, + ) + + response = client.fetch_schema_catalog(message_id="transport-factory") + + self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") + self.assertEqual(sent_messages[0]["schemaVersion"], "cultnet.schema_catalog_request.v0") + self.assertEqual(sent_messages[0]["messageId"], "transport-factory") + + def test_cultnet_rudp_packet_codec_uses_deterministic_reliable_ordered_fixture(self) -> None: + encoded = encode_rudp_packet( + CultNetRudpPacket( + packet_type=CultNetRudpPacketType.DATA, + connection_id=0x01020304, + sequence=0x0000002A, + ack=0x00000029, + ack_mask=0x80000001, + channel_id="schema", + reliable=True, + ordered=True, + fragment_id=7, + fragment_index=1, + fragment_count=3, + payload=b"hello", + ) + ) + + self.assertEqual( + encoded.hex(), + "434e523000030b2a010203040000002a0000002980000001000700010003000000050600736368656d6168656c6c6f", + ) + + decoded = decode_rudp_packet(encoded) + self.assertEqual(decoded.packet_type, CultNetRudpPacketType.DATA) + self.assertEqual(decoded.connection_id, 0x01020304) + self.assertEqual(decoded.sequence, 0x0000002A) + self.assertEqual(decoded.ack, 0x00000029) + self.assertEqual(decoded.ack_mask, 0x80000001) + self.assertEqual(decoded.channel_id, "schema") + self.assertTrue(decoded.reliable) + self.assertTrue(decoded.ordered) + self.assertFalse(decoded.sequenced) + self.assertEqual(decoded.fragment_id, 7) + self.assertEqual(decoded.fragment_index, 1) + self.assertEqual(decoded.fragment_count, 3) + self.assertEqual(decoded.payload, b"hello") + + def test_cultnet_rudp_transport_profile_advertises_state_and_realtime_channels(self) -> None: + profile = create_rudp_transport_profile( + "python-rudp", + transport_id="public-rudp", + host="127.0.0.1", + port=7777, + max_payload_bytes=1200, + max_fragment_bytes=1000, + ) + + self.assertEqual(profile["transports"][0]["protocol"], "rudp") + self.assertEqual(profile["transports"][0]["reconnectPolicy"]["schemaVersion"], "cultnet.reconnect_policy.v0") + self.assertEqual(profile["transports"][0]["reconnectPolicy"]["baseDelayMs"], 1_000) + self.assertEqual( + [ + (channel["channelId"], channel["delivery"], channel["ordering"]) + for channel in profile["transports"][0]["channels"] + ], + [ + ("schema", "reliable", "ordered"), + ("latest", "unreliable", "sequenced"), + ("realtime", "unreliable", "unordered"), + ], + ) + + def test_cultnet_reconnect_policy_exposes_portable_delay_contract(self) -> None: + policy = create_reconnect_policy(policy_id="rudp-default", max_attempts=8) + + self.assertEqual(policy.schema_version, "cultnet.reconnect_policy.v0") + self.assertEqual(policy.policy_id, "rudp-default") + self.assertEqual(policy.max_attempts, 8) + self.assertEqual(policy.to_wire()["policyId"], "rudp-default") + self.assertEqual(policy.to_wire()["maxAttempts"], 8) + self.assertEqual(compute_reconnect_delay_ms(policy, 1), 1_000) + self.assertEqual(compute_reconnect_delay_ms(policy, 3, 17), 4_017) + self.assertEqual(compute_reconnect_delay_ms(policy, 9, 999), 30_250) + self.assertEqual(compute_reconnect_delay_ms(policy, 0, -5), 1_000) + + def test_cultnet_reconnect_controller_schedules_attempts_and_reset(self) -> None: + controller = CultNetReconnectController(create_reconnect_policy(max_attempts=2)) + + first = controller.record_failure(10_000) + self.assertEqual(first.attempt, 1) + self.assertTrue(first.should_retry) + self.assertEqual(first.delay_ms, 1_000) + self.assertEqual(first.next_attempt_at_ms, 11_000) + self.assertFalse(first.exhausted) + self.assertFalse(controller.can_attempt(10_999)) + self.assertTrue(controller.can_attempt(11_000)) + + second = controller.record_failure(11_000, 17) + self.assertEqual(second.attempt, 2) + self.assertEqual(second.delay_ms, 2_017) + self.assertEqual(second.next_attempt_at_ms, 13_017) + self.assertTrue(second.should_retry) + + exhausted = controller.record_failure(13_017) + self.assertEqual(exhausted.attempt, 2) + self.assertFalse(exhausted.should_retry) + self.assertEqual(exhausted.delay_ms, 0) + self.assertIsNone(exhausted.next_attempt_at_ms) + self.assertTrue(exhausted.exhausted) + self.assertFalse(controller.can_attempt(99_000)) + + controller.reset() + self.assertEqual(controller.attempt, 0) + self.assertIsNone(controller.next_attempt_at_ms) + self.assertFalse(controller.exhausted) + self.assertTrue(controller.can_attempt(99_000)) + + def test_cultnet_rudp_reconnect_loop_consumes_shared_controller(self) -> None: + class FakeRudpReconnectTransport: + def __init__(self) -> None: + self.connect_calls = 0 + self.close_calls = 0 + self.last_payload: bytes | None = None + + def connect(self, payload: bytes = b"") -> None: + self.connect_calls += 1 + self.last_payload = bytes(payload) + + def close(self) -> None: + self.close_calls += 1 + + now_ms = 10_000 + captured_callback: list[Callable[[], None]] = [] + transports: list[FakeRudpReconnectTransport] = [] + + def create_transport() -> FakeRudpReconnectTransport: + transport = FakeRudpReconnectTransport() + transports.append(transport) + return transport + + def scheduler(delay_ms: int, callback: Callable[[], None]) -> Callable[[], None]: + self.assertEqual(delay_ms, 1_017) + captured_callback[:] = [callback] + + def cancel() -> None: + captured_callback.clear() + + return cancel + + loop = CultNetRudpReconnectLoop( + create_transport, + reconnect_policy=create_reconnect_policy(max_attempts=2), + connect_payload=b"join", + now_ms=lambda: now_ms, + jitter_ms=lambda: 17, + scheduler=scheduler, + ) + + first = loop.start() + self.assertEqual(first.connect_calls, 1) + self.assertEqual(first.last_payload, b"join") + self.assertIs(loop.transport, first) + + decision = loop.handle_closed() + self.assertIsNotNone(decision) + self.assertEqual(decision.attempt, 1) + self.assertTrue(decision.should_retry) + self.assertEqual(decision.delay_ms, 1_017) + self.assertEqual(decision.next_attempt_at_ms, 11_017) + self.assertEqual(loop.reconnect_controller.attempt, 1) + self.assertEqual(loop.reconnect_controller.next_attempt_at_ms, 11_017) + self.assertEqual(len(captured_callback), 1) + + now_ms = 11_017 + captured_callback[0]() + self.assertEqual(len(transports), 2) + self.assertEqual(transports[1].connect_calls, 1) + self.assertIs(loop.transport, transports[1]) + + loop.mark_connected() + self.assertEqual(loop.reconnect_controller.attempt, 0) + + loop.stop() + self.assertEqual(transports[1].close_calls, 1) + self.assertIsNone(loop.transport) + + def test_cultnet_rudp_session_handshake_acks_reliable_connect_and_accept_packets(self) -> None: + client = CultNetRudpSession( + CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=1, resend_delay_ms=50) + ) + server = CultNetRudpSession( + CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=100, resend_delay_ms=50) + ) + + connect = client.create_connect(0, b"join") + self.assertEqual(connect.packet_type, CultNetRudpPacketType.CONNECT) + self.assertEqual(connect.sequence, 1) + self.assertEqual(client.pending_reliable_sequences, (1,)) + + accept = server.accept_connect(connect, 10, b"ok") + self.assertEqual(accept.packet_type, CultNetRudpPacketType.ACCEPT) + self.assertEqual(accept.ack, 1) + self.assertTrue(server.connected) + self.assertEqual(server.pending_reliable_sequences, (100,)) + + client.receive(accept, 20) + self.assertTrue(client.connected) + self.assertEqual(client.pending_reliable_sequences, ()) + + ack = client.create_ack() + self.assertEqual(ack.ack, 100) + server.receive(ack, 30) + self.assertEqual(server.pending_reliable_sequences, ()) + + def test_cultnet_rudp_session_computes_ack_masks_and_clears_pending_reliable_packets(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=7, initial_sequence=10, resend_delay_ms=100)) + receiver = CultNetRudpSession( + CultNetRudpSessionOptions(connection_id=7, initial_sequence=200, resend_delay_ms=100) + ) + sender.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 7, 1, 0, 0, "control") + ) + receiver.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 7, 2, 0, 0, "control") + ) + + first = sender.send( + "schema", + b"first", + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), + ) + second = sender.send( + "schema", + b"second", + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), + ) + third = sender.send( + "schema", + b"third", + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0), + ) + self.assertEqual(sender.pending_reliable_sequences, (10, 11, 12)) + + receiver.receive(first) + receiver.receive(third) + ack_with_gap = receiver.create_ack() + self.assertEqual(ack_with_gap.ack, 12) + self.assertEqual(ack_with_gap.ack_mask, 0b10 | (1 << 9)) + sender.receive(ack_with_gap) + self.assertEqual(sender.pending_reliable_sequences, (11,)) + + receiver.receive(second) + full_ack = receiver.create_ack() + self.assertEqual(full_ack.ack, 12) + self.assertEqual(full_ack.ack_mask, 0b11 | (1 << 9)) + sender.receive(full_ack) + self.assertEqual(sender.pending_reliable_sequences, ()) + + def test_cultnet_rudp_session_schedules_reliable_resends_until_acked(self) -> None: + session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=99, initial_sequence=1, resend_delay_ms=100)) + session.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 99, 50, 0, 0, "control") + ) + sent = session.send( + "schema", + b"payload", + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10), + ) + + self.assertEqual(session.due_resends(90), ()) + self.assertEqual(tuple(packet.sequence for packet in session.due_resends(110)), (sent.sequence,)) + self.assertEqual(session.due_resends(150), ()) + + session.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACK, 99, 51, sent.sequence, 0, "control") + ) + self.assertEqual(session.due_resends(250), ()) + + def test_cultnet_rudp_session_pings_and_detects_receive_timeout(self) -> None: + client = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=1)) + server = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=100)) + connect = client.create_connect(0, b"join") + accept = server.accept_connect(connect, 10) + client.receive(accept, 20) + + ping = client.create_ping(b"pulse") + ping_result = server.receive(ping, 30) + self.assertIsNotNone(ping_result.reply) + self.assertEqual(ping_result.reply.packet_type, CultNetRudpPacketType.PONG) + self.assertEqual(ping_result.reply.payload, b"pulse") + + pong_result = client.receive(ping_result.reply, 40) + self.assertTrue(pong_result.pong) + self.assertEqual(pong_result.pong_payload, b"pulse") + self.assertFalse(client.check_timeout(90, 50)) + self.assertTrue(client.check_timeout(91, 50)) + self.assertFalse(client.connected) + + def test_cultnet_rudp_session_bounds_pending_reliable_packets_before_enqueue(self) -> None: + session = CultNetRudpSession( + CultNetRudpSessionOptions(connection_id=102, initial_sequence=1, max_pending_reliable_packets=2) + ) + session.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 102, 50, 0, 0, "control")) + session.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) + session.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) + with self.assertRaisesRegex(ValueError, "reliable send queue is full"): + session.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True)) + self.assertEqual(session.pending_reliable_sequences, (1, 2)) + + fragmented = CultNetRudpSession( + CultNetRudpSessionOptions(connection_id=103, initial_sequence=1, max_pending_reliable_packets=3) + ) + fragmented.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 103, 50, 0, 0, "control")) + with self.assertRaisesRegex(ValueError, "reliable send queue is full"): + fragmented.send_many( + "schema", + b"fragment-me", + CultNetRudpSendOptions(reliable=True, ordered=True), + max_fragment_bytes=3, + ) + self.assertEqual(fragmented.pending_reliable_sequences, ()) + + def test_cultnet_rudp_session_suppresses_duplicates_and_delivers_reliable_ordered_payloads(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=100)) + sender.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 123, 90, 0, 0, "control") + ) + receiver.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 123, 91, 0, 0, "control") + ) + + first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) + second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) + third = sender.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True)) + + self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"]) + self.assertEqual(receiver.receive(third).delivered, ()) + self.assertEqual(receiver.receive(first).delivered, ()) + self.assertEqual( + [frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered], + ["second", "third"], + ) + + def test_cultnet_rudp_lossy_packets_cannot_create_reliable_ordered_gaps(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 198, 91, 0, 0, "control")) + + realtime = sender.send("realtime", b"discarded realtime", CultNetRudpSendOptions()) + latest = sender.send("latest", b"discarded latest state", CultNetRudpSendOptions(sequenced=True)) + schema = sender.send("schema", b"committed response", CultNetRudpSendOptions(reliable=True, ordered=True)) + + self.assertEqual(realtime.sequence, 0) + self.assertEqual(latest.sequence, 1) + self.assertEqual( + [frame.payload.decode("utf-8") for frame in receiver.receive(schema).delivered], + ["committed response"], + ) + + def test_cultnet_rudp_unreliable_sequenced_delivery_is_scoped_to_its_channel(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=50)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 197, 91, 0, 0, "control")) + + older = sender.send("latest", b"older", CultNetRudpSendOptions(sequenced=True)) + newer = sender.send("latest", b"newer", CultNetRudpSendOptions(sequenced=True)) + + self.assertEqual(older.sequence, 1) + self.assertEqual(newer.sequence, 2) + self.assertEqual( + [frame.payload.decode("utf-8") for frame in receiver.receive(newer).delivered], + ["newer"], + ) + self.assertEqual(receiver.receive(older).delivered, ()) + + def test_cultnet_rudp_rejects_unreliable_ordered_delivery(self) -> None: + session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=196, initial_sequence=1)) + session.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 196, 50, 0, 0, "control")) + + with self.assertRaises(ValueError) as raised: + session.send( + "schema", + b"cannot order what will not retransmit", + CultNetRudpSendOptions(ordered=True), + ) + self.assertIn("ordered delivery requires reliability", str(raised.exception)) + + def test_cultnet_rudp_session_skips_control_packets_while_ordering_schema_payloads(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=100)) + sender.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 124, 90, 0, 0, "control") + ) + receiver.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 124, 91, 0, 0, "control") + ) + + first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True)) + control = sender.create_ack() + second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True)) + + self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"]) + self.assertEqual(receiver.receive(control).delivered, ()) + self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered], ["second"]) + + def test_cultnet_rudp_session_fragments_and_reassembles_reliable_ordered_payloads(self) -> None: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=100)) + sender.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 456, 90, 0, 0, "control") + ) + receiver.receive( + CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 456, 91, 0, 0, "control") + ) + + packets = sender.send_many( + "schema", + b"fragment-me-please", + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10), + max_fragment_bytes=5, + ) + self.assertEqual(len(packets), 4) + self.assertEqual(tuple(packet.fragment_count for packet in packets), (4, 4, 4, 4)) + self.assertEqual(tuple(packet.fragment_index for packet in packets), (0, 1, 2, 3)) + self.assertTrue(all(packet.fragment_id == packets[0].fragment_id for packet in packets)) + + self.assertEqual(receiver.receive(packets[0]).delivered, ()) + self.assertEqual(receiver.receive(packets[1]).delivered, ()) + self.assertEqual(receiver.receive(packets[2]).delivered, ()) + delivered = receiver.receive(packets[3]).delivered + self.assertEqual(len(delivered), 1) + self.assertEqual(delivered[0].payload, b"fragment-me-please") + self.assertEqual(delivered[0].sequence, packets[0].sequence) + + def test_cultnet_rudp_session_advances_large_fragment_sets_through_bounded_window(self) -> None: + connection_id = 457 + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, connection_id, 0, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, connection_id, 0, 0, 0, "control")) + + fragment_count = CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS + 17 + payload = bytes(index % 251 for index in range(fragment_count * 8)) + wire = list(sender.send_many( + "schema", + payload, + CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=1), + max_fragment_bytes=8, + )) + self.assertEqual(len(wire), CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS) + oldest_sequence = wire[0].sequence + self.assertEqual(len(sender.pending_reliable_sequences), len(wire)) + self.assertEqual(sender.queued_reliable_packet_count, 17) + + delivered = [] + while wire: + admitted = len(wire) + for _ in range(admitted): + packet = wire.pop(0) + delivered.extend(receiver.receive(packet, 2).delivered) + acknowledged = sender.receive(receiver.create_ack(), 3) + wire.extend(acknowledged.ready_to_send) + + self.assertEqual(sender.outstanding_reliable_packet_count, 0) + self.assertEqual(len(delivered), 1) + self.assertEqual(delivered[0].payload, payload) + old_ack = receiver.create_ack_for_received(oldest_sequence) + self.assertEqual(old_ack.ack, oldest_sequence) + self.assertEqual(old_ack.ack_mask, 0) + + def test_cultnet_rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames(self) -> None: + server_socket = bind_udp_socket() + client_socket = bind_udp_socket() + connection_id = 0x10203040 + server = CultNetRudpSocketTransportConnection( + CultNetRudpSocketTransportOptions( + runtime_id="python-rudp-server", + socket=server_socket, + mode=CultNetRudpSocketMode.SERVER, + connection_id=connection_id, + initial_sequence=100, + resend_delay_ms=25, + ) + ) + client = CultNetRudpSocketTransportConnection( + CultNetRudpSocketTransportOptions( + runtime_id="python-rudp-client", + socket=client_socket, + mode=CultNetRudpSocketMode.CLIENT, + remote_addr=server_socket.getsockname(), + connection_id=connection_id, + initial_sequence=1, + resend_delay_ms=25, + ) + ) + + try: + client.connect(b"join") + pump_rudp_handshake(client, server) + self.assertTrue(client.connected) + self.assertTrue(server.connected) + + client.send("schema", b"client-state") + server_frame = receive_rudp_frame(server) + self.assertEqual(server_frame.channel_id, "schema") + self.assertEqual(server_frame.payload, b"client-state") + + server.send("schema", b"server-state") + client_frame = receive_rudp_frame(client) + self.assertEqual(client_frame.channel_id, "schema") + self.assertEqual(client_frame.payload, b"server-state") + self.assertEqual(server.profile["transports"][0]["protocol"], "rudp") + self.assertEqual(client.stats.frames_sent, 1) + self.assertEqual(server.stats.frames_received, 1) + finally: + client.close() + server.close() + + def test_cultnet_rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames(self) -> None: + server_socket = bind_udp_socket() + client_socket = bind_udp_socket() + connection_id = 0x10203041 + server = CultNetRudpSocketTransportConnection( + CultNetRudpSocketTransportOptions( + runtime_id="python-rudp-fragment-server", + socket=server_socket, + mode=CultNetRudpSocketMode.SERVER, + connection_id=connection_id, + initial_sequence=100, + resend_delay_ms=25, + max_fragment_bytes=8, + ) + ) + client = CultNetRudpSocketTransportConnection( + CultNetRudpSocketTransportOptions( + runtime_id="python-rudp-fragment-client", + socket=client_socket, + mode=CultNetRudpSocketMode.CLIENT, + remote_addr=server_socket.getsockname(), + connection_id=connection_id, + initial_sequence=1, + resend_delay_ms=25, + max_fragment_bytes=8, + ) + ) + + try: + payload = b"this-schema-frame-is-larger-than-one-rudp-fragment" + client.connect(b"join") + pump_rudp_handshake(client, server) + client.send("schema", payload) + server_frame = receive_rudp_frame(server) + self.assertEqual(server_frame.channel_id, "schema") + self.assertEqual(server_frame.payload, payload) + self.assertEqual(client.stats.frames_sent, 1) + self.assertEqual(server.stats.frames_received, 1) + finally: + client.close() + server.close() + + def test_cultnet_database_subscription_helpers_match_schema_v0_shape(self) -> None: + subscribe = database_subscribe( + message_id="sub-message", + subscription_id="sub-1", + schema_ids=["schema-a"], + record_keys=["record-a"], + include_snapshot=False, + ).to_wire() + self.assertEqual(subscribe["schemaVersion"], "cultnet.database_subscribe.v0") + self.assertEqual(subscribe["messageId"], "sub-message") + self.assertEqual(subscribe["subscriptionId"], "sub-1") + self.assertEqual(subscribe["schemaIds"], ["schema-a"]) + self.assertEqual(subscribe["recordKeys"], ["record-a"]) + self.assertFalse(subscribe["includeSnapshot"]) + + unsubscribe = database_unsubscribe(message_id="unsub-message", subscription_id="sub-1").to_wire() + self.assertEqual(unsubscribe["schemaVersion"], "cultnet.database_unsubscribe.v0") + self.assertEqual(unsubscribe["messageId"], "unsub-message") + self.assertEqual(unsubscribe["subscriptionId"], "sub-1") + + def test_cultnet_database_change_parses_put_and_delete_shapes(self) -> None: + put_change = CultNetDatabaseChange.from_wire({ + "schemaVersion": "cultnet.database_change_raw.v0", + "messageId": "change-put", + "subscriptionId": "sub-1", + "changeKind": "added", + "document": { + "schemaId": "schema-note", + "recordKey": "note:1", + "payload": b"payload", + }, + }) + self.assertEqual(put_change.schema_id, "schema-note") + self.assertEqual(put_change.record_key, "note:1") + self.assertIsInstance(put_change.raw_document, CultNetRawDocumentRecord) + self.assertEqual(put_change.raw_document.payload, b"payload") + self.assertEqual(put_change.to_wire()["document"]["recordKey"], "note:1") + + delete_change = CultNetDatabaseChange.from_wire({ + "schemaVersion": "cultnet.database_change_raw.v0", + "messageId": "change-delete", + "subscriptionId": "sub-1", + "changeKind": "removed", + "schemaId": "schema-note", + "recordKey": "note:1", + }) + self.assertIsNone(delete_change.document) + self.assertIsNone(delete_change.raw_document) + self.assertEqual(delete_change.schema_id, "schema-note") + self.assertEqual(delete_change.to_wire()["recordKey"], "note:1") + + def test_cultnet_raw_put_helper_carries_message_id(self) -> None: + put = document_put_raw( + message_id="put-1", + key="record-a", + schema_id="schema-a", + stored_at="2026-06-13T00:00:00Z", + payload=b"payload", + source_runtime_id="python-test", + shard_id="interop", + shard_epoch=1, + ).to_wire() + self.assertEqual(put["schemaVersion"], "cultnet.document_put_raw.v0") + self.assertEqual(put["messageId"], "put-1") + self.assertEqual(put["document"]["recordKey"], "record-a") + self.assertEqual(put["document"]["sourceRuntimeId"], "python-test") + self.assertEqual(put["shardId"], "interop") + self.assertEqual(put["shardEpoch"], 1) + + def test_cultnet_catalog_and_snapshot_helpers_accept_filters(self) -> None: + catalog = schema_catalog_request( + message_id="catalog-1", + include_schema_json=True, + schema_ids=["schema-a"], + kinds=["wire_message"], + ).to_wire() + self.assertEqual(catalog["schemaVersion"], "cultnet.schema_catalog_request.v0") + self.assertEqual(catalog["messageId"], "catalog-1") + self.assertTrue(catalog["includeSchemaJson"]) + self.assertEqual(catalog["schemaIds"], ["schema-a"]) + self.assertEqual(catalog["kinds"], ["wire_message"]) + + snapshot = snapshot_request( + message_id="snapshot-1", + schema_ids=["schema-a"], + record_keys=["record-a"], + shard_id="interop", + shard_epoch=1, + ).to_wire() + self.assertEqual(snapshot["schemaVersion"], "cultnet.snapshot_request.v0") + self.assertEqual(snapshot["messageId"], "snapshot-1") + self.assertEqual(snapshot["schemaIds"], ["schema-a"]) + self.assertEqual(snapshot["recordKeys"], ["record-a"]) + self.assertEqual(snapshot["shardId"], "interop") + self.assertEqual(snapshot["shardEpoch"], 1) + + def test_cultnet_raw_snapshot_response_filters_and_roundtrips_records(self) -> None: + response = CultNetRawSnapshotResponse.from_wire({ + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "snapshot-1", + "shardId": "notes", + "shardEpoch": 3, + "shardLogSequence": 8, + "documents": [ + CultNetRawDocumentRecord( + schema_id="schema-note", + record_key="note:1", + stored_at="2026-06-14T00:00:00Z", + payload=b"note", + source_runtime_id="python-test", + tags=("snapshot",), + ).to_wire(), + { + "schemaId": "schema-fact", + "recordKey": "fact:1", + "payloadEncoding": "messagepack", + "payload": b"fact", + }, + ], + }) + self.assertEqual(response.shard_id, "notes") + self.assertEqual(response.shard_epoch, 3) + self.assertEqual(response.shard_log_sequence, 8) + self.assertEqual(response.documents[0].source_runtime_id, "python-test") + self.assertEqual(response.filter(schema_ids=["schema-note"])[0].record_key, "note:1") + self.assertEqual(response.filter(record_keys=["fact:1"])[0].schema_id, "schema-fact") + self.assertEqual(response.to_wire()["documents"][0]["tags"], ["snapshot"]) + + def test_cultnet_raw_client_fetches_schema_snapshot_and_shard_reads(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + received_versions: list[str] = [] + ready = threading.Event() + server_error: list[BaseException] = [] + port_holder: list[int] = [] + + def serve_requests() -> None: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(4) + ready.set() + for _ in range(4): + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + request = msgpack.unpackb(read_frame(stream), raw=False) + received_versions.append(request["schemaVersion"]) + if request["schemaVersion"] == "cultnet.schema_catalog_request.v0": + self.assertEqual(request["kinds"], ["wire_message"]) + response = {"schemaVersion": "cultnet.schema_catalog_response.v0", "messageId": request["messageId"], "schemas": []} + elif request["schemaVersion"] == "cultnet.snapshot_request.v0": + self.assertEqual(request["shardId"], "interop") + response = {"schemaVersion": "cultnet.snapshot_response_raw.v0", "messageId": request["messageId"], "documents": []} + elif request["schemaVersion"] == "cultnet.shard_catalog_request.v0": + response = { + "schemaVersion": "cultnet.shard_catalog_response.v0", + "messageId": request["messageId"], + "shards": [{ + "shardId": "interop", + "ownerRuntimeId": "socket-test", + "epoch": 1, + "schemaIds": request["schemaIds"], + "keyPrefix": "record-", + "primaryEndpoints": ["cultnet://127.0.0.1:1"], + }], + } + elif request["schemaVersion"] == "cultnet.shard_log_request.v0": + self.assertEqual(request["afterSequence"], 7) + response = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": request["messageId"], + "shardId": request["shardId"], + "shardEpoch": request["shardEpoch"], + "entries": [], + "resyncRequired": False, + } + else: + raise AssertionError(f"unexpected request {request['schemaVersion']}") + write_frame(stream, msgpack.packb(response, use_bin_type=True)) + stream.flush() + except BaseException as error: + server_error.append(error) + ready.set() + + thread = threading.Thread(target=serve_requests, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + self.assertFalse(server_error) + + client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + self.assertEqual(client.fetch_schema_catalog(kinds=["wire_message"])["schemaVersion"], "cultnet.schema_catalog_response.v0") + typed_snapshot = client.fetch_snapshot_response(shard_id="interop", shard_epoch=1) + self.assertEqual(typed_snapshot.shard_id, None) + self.assertEqual(typed_snapshot.documents, ()) + shard_catalog = CultNetShardCatalog() + synced_shards = client.sync_shard_catalog(shard_catalog, schema_ids=["schema-a"]) + self.assertEqual(synced_shards[0].shard_id, "interop") + self.assertEqual(shard_catalog.get("interop"), synced_shards[0]) + shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=7) + self.assertEqual(shard_log.shard_id, "interop") + self.assertFalse(shard_log.resync_required) + self.assertEqual(shard_log.last_sequence, 0) + + thread.join(2.0) + self.assertFalse(server_error) + self.assertEqual(received_versions, [ + "cultnet.schema_catalog_request.v0", + "cultnet.snapshot_request.v0", + "cultnet.shard_catalog_request.v0", + "cultnet.shard_log_request.v0", + ]) + + def test_cultnet_raw_client_raises_typed_peer_error_response(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + ready = threading.Event() + port_holder: list[int] = [] + + def serve_error() -> None: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(1) + ready.set() + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + read_frame(stream) + write_frame(stream, msgpack.packb({ + "schemaVersion": "cultnet.error.v0", + "messageId": "error-1", + "error": "Snapshot document limit exceeded", + }, use_bin_type=True)) + stream.flush() + + thread = threading.Thread(target=serve_error, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + + client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised: + client.fetch_snapshot() + + self.assertEqual(raised.exception.peer_error, "Snapshot document limit exceeded") + self.assertEqual(raised.exception.response["schemaVersion"], "cultnet.error.v0") + thread.join(2.0) + + def test_cultnet_database_subscription_reads_snapshot_and_change(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + document = define_database_entry_type( + "sub.item", + [ + ("name", 0), + ("category", 1), + ("value", 2, 0), + ], + cls=Item, + ) + schema_id = document.catalog_entry().schema_id + ready = threading.Event() + server_error: list[BaseException] = [] + port_holder: list[int] = [] + received_versions: list[str] = [] + + def serve_subscription() -> None: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(1) + ready.set() + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + subscribe = msgpack.unpackb(read_frame(stream), raw=False) + received_versions.append(subscribe["schemaVersion"]) + self.assertEqual(subscribe["subscriptionId"], "sub-1") + snapshot = { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": subscribe["messageId"], + "documents": [], + } + write_frame(stream, msgpack.packb(snapshot, use_bin_type=True)) + stream.flush() + + put = msgpack.unpackb(read_frame(stream), raw=False) + received_versions.append(put["schemaVersion"]) + change = { + "schemaVersion": "cultnet.database_change_raw.v0", + "messageId": "change-1", + "subscriptionId": "sub-1", + "changeKind": "added", + "document": put["document"], + } + write_frame(stream, msgpack.packb(change, use_bin_type=True)) + stream.flush() + + unsubscribe = msgpack.unpackb(read_frame(stream), raw=False) + received_versions.append(unsubscribe["schemaVersion"]) + except BaseException as error: + server_error.append(error) + ready.set() + + thread = threading.Thread(target=serve_subscription, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + self.assertFalse(server_error) + + client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + put = document_put_raw( + message_id="put-sub", + key="item:sub", + schema_id=schema_id, + stored_at="2026-06-14T00:00:00Z", + payload=document.encode_payload(Item("orb", "gear", 8)), + ) + with client.subscribe_database(subscription_id="sub-1", schema_ids=[schema_id]) as subscription: + snapshot = subscription.read_next_snapshot_response() + subscription.send(put) + change = subscription.read_next_change() + + thread.join(2.0) + self.assertFalse(server_error) + self.assertIsInstance(snapshot, CultNetRawSnapshotResponse) + self.assertEqual(snapshot.message_id, "cultnet-python-subscribe") + self.assertEqual(snapshot.to_wire()["schemaVersion"], "cultnet.snapshot_response_raw.v0") + self.assertEqual(change.change_kind, "added") + self.assertEqual(change.record_key, "item:sub") + self.assertIsInstance(change.raw_document, CultNetRawDocumentRecord) + self.assertEqual(change.raw_document.record_key, "item:sub") + self.assertEqual(received_versions, [ + "cultnet.database_subscribe.v0", + "cultnet.document_put_raw.v0", + "cultnet.database_unsubscribe.v0", + ]) + + def test_cultnet_database_subscription_raises_typed_peer_error(self) -> None: + import msgpack # type: ignore + from cultnet_py import read_frame, write_frame + + ready = threading.Event() + port_holder: list[int] = [] + + def serve_subscription_error() -> None: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port_holder.append(server.getsockname()[1]) + server.listen(1) + ready.set() + connection, _ = server.accept() + with connection: + stream = connection.makefile("rwb") + read_frame(stream) + write_frame(stream, msgpack.packb({ + "schemaVersion": "cultnet.error.v0", + "messageId": "subscription-error", + "error": "subscription rejected", + }, use_bin_type=True)) + stream.flush() + + thread = threading.Thread(target=serve_subscription_error, daemon=True) + thread.start() + self.assertTrue(ready.wait(2.0)) + + client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0) + with client.subscribe_database(subscription_id="sub-error") as subscription: + with self.assertRaisesRegex(CultNetPeerError, "subscription rejected") as raised: + subscription.read_next() + self.assertEqual(raised.exception.response["messageId"], "subscription-error") + thread.join(2.0) + + def test_cultnet_replication_helpers_apply_raw_snapshot_and_shard_log(self) -> None: + document = define_database_entry_type( + "replica.item", + [ + ("name", 0), + ("category", 1), + ("value", 2, 0), + ], + cls=Item, + ) + cache = CultCache() + cache.register_document_type(document) + schema_id = document.catalog_entry().schema_id + + snapshot = { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "snapshot-1", + "documents": [ + { + "schemaId": schema_id, + "recordKey": "item:1", + "storedAt": "2026-06-13T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(Item("sword", "gear", 3)), + } + ], + } + applied_snapshot = apply_raw_snapshot(cache, [document], snapshot) + self.assertEqual(applied_snapshot[0].record_key, "item:1") + self.assertEqual(cache.get_required(document, "item:1").value, 3) + typed_snapshot = CultNetRawSnapshotResponse.from_wire(snapshot) + direct_applied = apply_raw_document_record(cache, schema_document_map([document]), typed_snapshot.documents[0]) + self.assertEqual(direct_applied.schema_id, schema_id) + self.assertEqual(direct_applied.record_key, "item:1") + typed_snapshot_applied = apply_raw_snapshot(cache, [document], typed_snapshot) + self.assertEqual(typed_snapshot_applied[0].record_key, "item:1") + + shard_log = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "log-1", + "shardId": "interop", + "shardEpoch": 1, + "resyncRequired": False, + "entries": [ + { + "sequence": 0, + "changeKind": "updated", + "put": { + "schemaVersion": "cultnet.document_put_raw.v0", + "messageId": "put-unknown", + "document": { + "schemaId": "replica.unknown.v1", + "recordKey": "unknown:1", + "storedAt": "2026-06-13T00:00:01Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(Item("ignored", "unknown", 0)), + }, + "shardId": "interop", + "shardEpoch": 1, + }, + }, + { + "sequence": 1, + "changeKind": "updated", + "put": { + "schemaVersion": "cultnet.document_put_raw.v0", + "messageId": "put-1", + "document": { + "schemaId": schema_id, + "recordKey": "item:1", + "storedAt": "2026-06-13T00:00:01Z", + "payloadEncoding": "messagepack", + "payload": document.encode_payload(Item("sword", "gear", 12)), + }, + "shardId": "interop", + "shardEpoch": 1, + }, + }, + { + "sequence": 2, + "changeKind": "removed", + "delete": { + "schemaVersion": "cultnet.document_delete.v0", + "messageId": "delete-1", + "schemaId": schema_id, + "recordKey": "item:1", + "shardId": "interop", + "shardEpoch": 1, + }, + }, + ], + } + applied_log = apply_shard_log_response(cache, [document], shard_log) + self.assertEqual([change.change_kind for change in applied_log], ["updated", "removed"]) + self.assertIsNone(cache.get(document, "item:1")) + + cache.put(document, "item:1", Item("sword", "gear", 3)) + typed_shard_log = CultNetShardLogResponse.from_wire({ + **shard_log, + "entries": shard_log["entries"][1:], + }) + typed_applied_log = apply_shard_log_response(cache, [document], typed_shard_log) + self.assertEqual([change.change_kind for change in typed_applied_log], ["updated", "removed"]) + self.assertIsNone(cache.get(document, "item:1")) + + def test_cultnet_replication_helpers_recover_schema_stamped_raw_records(self) -> None: + document = define_database_entry_type( + "replica.runtime-policy", + [ + ("schema_version", 0), + ("name", 1), + ("value", 2), + ], + schema_id="replica.runtime-policy.current", + schema_name="replica.runtime_policy", + schema_version="replica.runtime_policy.v1", + ) + cache = CultCache() + cache.register_document_type(document) + stale_schema_id = "sha256:stale-replica-runtime-policy" + payload = document.encode_payload({ + "schema_version": "replica.runtime_policy.v1", + "name": "policy", + "value": "still synced", + }) + record = { + "schemaId": stale_schema_id, + "recordKey": "policy:1", + "storedAt": "2026-06-25T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": payload, + } + snapshot = { + "schemaVersion": "cultnet.snapshot_response_raw.v0", + "messageId": "snapshot-stale", + "documents": [record], + } + + applied_snapshot = apply_raw_snapshot(cache, [document], snapshot) + local_schema_id = document.catalog_entry().schema_id + self.assertEqual(applied_snapshot[0].schema_id, local_schema_id) + self.assertEqual(cache.get_required(document, "policy:1")["value"], "still synced") + self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) + direct_applied = apply_raw_document_record(cache, schema_document_map([document]), record) + self.assertEqual(direct_applied.schema_id, local_schema_id) + self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) + + shard_log = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "log-stale", + "shardId": "interop", + "shardEpoch": 1, + "resyncRequired": False, + "entries": [ + { + "sequence": 0, + "changeKind": "updated", + "put": { + "schemaVersion": "cultnet.document_put_raw.v0", + "messageId": "put-stale", + "document": { + **record, + "payload": document.encode_payload({ + "schema_version": "replica.runtime_policy.v1", + "name": "policy", + "value": "updated through log", + }), + }, + "shardId": "interop", + "shardEpoch": 1, + }, + } + ], + } + + applied_log = apply_shard_log_response(cache, [document], shard_log) + self.assertEqual(applied_log[0].schema_id, local_schema_id) + self.assertEqual(cache.get_required(document, "policy:1")["value"], "updated through log") + self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id) + + delete_log = { + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "log-delete-alias", + "shardId": "interop", + "shardEpoch": 1, + "resyncRequired": False, + "entries": [ + { + "sequence": 1, + "changeKind": "removed", + "delete": { + "schemaVersion": "cultnet.document_delete.v0", + "messageId": "delete-version-alias", + "schemaId": "replica.runtime_policy.v1", + "recordKey": "policy:1", + "shardId": "interop", + "shardEpoch": 1, + }, + } + ], + } + applied_delete = apply_shard_log_response(cache, [document], delete_log) + self.assertEqual(applied_delete[0].schema_id, local_schema_id) + self.assertEqual(applied_delete[0].change_kind, "removed") + self.assertIsNone(cache.get(document, "policy:1")) + + def test_cultnet_document_delete_helper_matches_schema_v0_shape(self) -> None: + message = document_delete( + message_id="delete-1", + schema_id="schema-a", + record_key="record-a", + shard_id="interop", + shard_epoch=1, + ).to_wire() + self.assertEqual(message["schemaVersion"], "cultnet.document_delete.v0") + self.assertEqual(message["messageId"], "delete-1") + self.assertEqual(message["schemaId"], "schema-a") + self.assertEqual(message["recordKey"], "record-a") + self.assertEqual(message["shardId"], "interop") + self.assertEqual(message["shardEpoch"], 1) + + def test_cultnet_auth_session_helpers_match_schema_v0_shape(self) -> None: + self.assertEqual( + login(nonce="nonce", auth="encrypted-auth", password="encrypted-password").to_wire(), + { + "schemaVersion": "cultnet.login.v0", + "nonce": "nonce", + "auth": "encrypted-auth", + "password": "encrypted-password", + }, + ) + self.assertEqual( + register( + nonce="nonce", + email="encrypted-email", + password="encrypted-password", + name="encrypted-name", + ).to_wire(), + { + "schemaVersion": "cultnet.register.v0", + "nonce": "nonce", + "email": "encrypted-email", + "password": "encrypted-password", + "name": "encrypted-name", + }, + ) + self.assertEqual( + verify_session(nonce="nonce", session="encrypted-session").to_wire(), + { + "schemaVersion": "cultnet.verify.v0", + "nonce": "nonce", + "session": "encrypted-session", + }, + ) + self.assertEqual( + login_success(nonce="nonce", session="encrypted-session").to_wire(), + { + "schemaVersion": "cultnet.login_success.v0", + "nonce": "nonce", + "session": "encrypted-session", + }, + ) + + def test_cultnet_wire_catalog_describes_python_handled_messages(self) -> None: + descriptors = wire_message_schema_descriptors(include_schema_json=True) + by_version = {descriptor["schemaVersion"]: descriptor for descriptor in descriptors} + for schema_version in [ + "cultnet.error.v0", + "cultnet.login.v0", + "cultnet.register.v0", + "cultnet.verify.v0", + "cultnet.login_success.v0", + "cultnet.transport_profile.v0", + "cultnet.document_delete.v0", + "cultnet.database_change_raw.v0", + "cultnet.shard_log_response.v0", + "cultnet.simulation_consensus_candidate.v0", + "cultmesh.peer_exchange_response.v0", + ]: + self.assertIn(schema_version, by_version) + expected_kind = "shared_contract" if schema_version == "cultnet.transport_profile.v0" else "wire_message" + self.assertEqual(by_version[schema_version]["kind"], expected_kind) + self.assertIn("cultnet.schema.v0", by_version[schema_version]["wireContracts"]) + self.assertIn(schema_version, by_version[schema_version]["schemaJson"]) + self.assertEqual(len(by_version[schema_version]["contentHash"]), 64) + + delete_schema = json.loads(by_version["cultnet.document_delete.v0"]["schemaJson"]) + self.assertEqual( + delete_schema["required"], + ["schemaVersion", "messageId", "schemaId", "recordKey"], + ) + self.assertEqual(delete_schema["properties"]["recordKey"]["type"], "string") + + error_schema = json.loads(by_version["cultnet.error.v0"]["schemaJson"]) + self.assertEqual(error_schema["required"], ["schemaVersion", "error"]) + self.assertEqual(error_schema["properties"]["error"]["type"], "string") + self.assertIn("details", error_schema["properties"]) + + login_schema = json.loads(by_version["cultnet.login.v0"]["schemaJson"]) + self.assertEqual(login_schema["required"], ["schemaVersion", "nonce", "auth", "password"]) + self.assertEqual(login_schema["properties"]["auth"]["type"], "string") + + register_schema = json.loads(by_version["cultnet.register.v0"]["schemaJson"]) + self.assertEqual(register_schema["required"], ["schemaVersion", "nonce", "email", "password", "name"]) + self.assertEqual(register_schema["properties"]["email"]["type"], "string") + + verify_schema = json.loads(by_version["cultnet.verify.v0"]["schemaJson"]) + self.assertEqual(verify_schema["required"], ["schemaVersion", "nonce", "session"]) + self.assertEqual(verify_schema["properties"]["session"]["type"], "string") + + success_schema = json.loads(by_version["cultnet.login_success.v0"]["schemaJson"]) + self.assertEqual(success_schema["required"], ["schemaVersion", "nonce", "session"]) + + transport_profile_schema = json.loads(by_version["cultnet.transport_profile.v0"]["schemaJson"]) + self.assertEqual(transport_profile_schema["required"], ["schemaVersion", "runtimeId", "transports"]) + transport = transport_profile_schema["properties"]["transports"]["items"] + self.assertEqual(transport["properties"]["protocol"]["enum"], ["tcp_framed", "litenetlib", "websocket", "rudp"]) + self.assertEqual( + transport["properties"]["reconnectPolicy"]["properties"]["schemaVersion"]["const"], + "cultnet.reconnect_policy.v0", + ) + channel = transport["properties"]["channels"]["items"] + self.assertEqual(channel["properties"]["ordering"]["enum"], ["ordered", "unordered", "sequenced"]) + + change_schema = json.loads(by_version["cultnet.database_change_raw.v0"]["schemaJson"]) + self.assertEqual(change_schema["properties"]["changeKind"]["enum"], ["added", "updated", "removed"]) + self.assertIn("document", change_schema["properties"]) + self.assertIn("schemaId", change_schema["properties"]) + self.assertIn("recordKey", change_schema["properties"]) + + log_schema = json.loads(by_version["cultnet.shard_log_response.v0"]["schemaJson"]) + self.assertIn("entries", log_schema["required"]) + self.assertIn("resyncRequired", log_schema["required"]) + self.assertIn("compactedThrough", log_schema["properties"]) + + hello_schema = json.loads(by_version["cultnet.hello.v0"]["schemaJson"]) + mutation_contract = hello_schema["properties"]["supportedMutationContracts"]["items"] + self.assertEqual(mutation_contract["type"], "object") + self.assertIn("operations", mutation_contract["properties"]) + self.assertEqual( + hello_schema["properties"]["transportProfiles"]["items"]["$ref"], + "https://github.com/GameCult/cultnet-ts/contracts/cultnet.transport-profile.schema.json", + ) + + consensus_schema = json.loads(by_version["cultnet.simulation_consensus_candidate.v0"]["schemaJson"]) + for required_field in ["witnessCount", "supportWeight", "totalWeight", "hasQuorum", "confidence"]: + self.assertIn(required_field, consensus_schema["required"]) + + def test_cultnet_schema_catalog_applies_filters_and_responses(self) -> None: + catalog = wire_message_schema_catalog(include_schema_json=True) + descriptor = catalog.get("https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-put-raw.schema.json") + self.assertIsNotNone(descriptor) + assert descriptor is not None + self.assertEqual(descriptor.kind, "wire_message") + self.assertEqual(descriptor.schema_version, "cultnet.document_put_raw.v0") + self.assertIsNotNone(descriptor.schema_json) + + filtered = catalog.list(kinds=["wire_message"], schema_ids=[descriptor.schema_id]) + self.assertEqual(filtered, [descriptor]) + + response = catalog.create_response( + message_id="catalog-response", + include_schema_json=False, + schema_ids=[descriptor.schema_id], + kinds=["wire_message"], + ) + self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0") + self.assertNotIn("schemaJson", response["schemas"][0]) + + remote = CultNetSchemaCatalog() + applied = remote.apply_response({ + "schemaVersion": "cultnet.schema_catalog_response.v0", + "messageId": "remote", + "schemas": [ + CultNetSchemaDescriptor( + schema_id="schema:custom", + kind="shared_contract", + schema_version="custom.v0", + wire_contracts=("cultnet.schema.v0",), + content_hash="hash", + schema_json="{}", + ).to_wire() + ], + }) + self.assertEqual(applied[0].schema_id, "schema:custom") + self.assertEqual(remote.get("schema:custom"), applied[0]) + + def test_cultnet_shard_helpers_match_schema_v0_shape(self) -> None: + catalog = shard_catalog_request( + message_id="catalog-1", + schema_ids=["schema-a"], + record_keys=["record-a"], + ).to_wire() + self.assertEqual(catalog["schemaVersion"], "cultnet.shard_catalog_request.v0") + self.assertEqual(catalog["messageId"], "catalog-1") + self.assertEqual(catalog["schemaIds"], ["schema-a"]) + self.assertEqual(catalog["recordKeys"], ["record-a"]) + + log = shard_log_request( + message_id="log-1", + shard_id="interop", + shard_epoch=7, + after_sequence=3, + limit=2, + ).to_wire() + self.assertEqual(log["schemaVersion"], "cultnet.shard_log_request.v0") + self.assertEqual(log["messageId"], "log-1") + self.assertEqual(log["shardId"], "interop") + self.assertEqual(log["shardEpoch"], 7) + self.assertEqual(log["afterSequence"], 3) + self.assertEqual(log["limit"], 2) + + def test_cultnet_shard_catalog_applies_filters_and_responses(self) -> None: + catalog = CultNetShardCatalog() + primary = catalog.upsert(CultNetShardDescriptor( + shard_id="notes", + owner_runtime_id="python-runtime", + epoch=2, + is_primary=True, + schema_ids=("schema-note.v1",), + key_prefix="note:", + primary_endpoints=("cultnet://127.0.0.1:3075",), + read_replica_endpoints=("cultnet://127.0.0.1:3075",), + region="local", + )) + catalog.upsert({ + "shardId": "facts", + "ownerRuntimeId": "python-runtime", + "epoch": 1, + "schemaIds": ["schema-fact"], + "keyPrefix": "fact:", + "primaryEndpoints": ["cultnet://127.0.0.1:3076"], + }) + + self.assertTrue(primary.serves(schema_id="schema-note.v1", record_key="note:1")) + self.assertTrue(primary.serves(schema_id="schema-note", record_key="note:1")) + self.assertFalse(primary.serves(schema_id="schema-note", record_key="fact:1")) + self.assertEqual(catalog.list(schema_ids=["schema-note.v1"]), [primary]) + self.assertEqual(catalog.list(schema_ids=["schema-note"]), [primary]) + self.assertEqual(catalog.list(record_keys=["note:1"]), [primary]) + + response = catalog.create_response( + message_id="shards", + schema_ids=["schema-note"], + record_keys=["note:1"], + ) + self.assertEqual(response["schemaVersion"], "cultnet.shard_catalog_response.v0") + self.assertEqual(response["shards"][0]["shardId"], "notes") + self.assertEqual(response["shards"][0]["ownerRuntimeId"], "python-runtime") + + remote = CultNetShardCatalog() + applied = remote.apply_response(response) + self.assertEqual(applied[0].shard_id, "notes") + self.assertEqual(remote.get("notes"), applied[0]) + + def test_cultnet_shard_log_response_tracks_cursor_and_resync_state(self) -> None: + response = CultNetShardLogResponse.from_wire({ + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "log", + "shardId": "notes", + "shardEpoch": 2, + "entries": [ + CultNetShardLogEntry( + sequence=2, + change_kind="updated", + put={"schemaVersion": "cultnet.document_put_raw.v0", "messageId": "put", "document": {"schemaId": "schema-note", "recordKey": "note:1", "payload": b"p"}}, + committed_at="2026-06-14T00:00:00Z", + ).to_wire(), + {"sequence": 3, "changeKind": "removed", "delete": {"schemaId": "schema-note", "recordKey": "note:1"}}, + ], + "resyncRequired": False, + }) + self.assertEqual(response.last_sequence, 3) + self.assertFalse(response.resync_required) + self.assertEqual(response.entries[0].put["document"]["recordKey"], "note:1") + self.assertIsInstance(response.entries[0].raw_document, CultNetRawDocumentRecord) + self.assertEqual(response.entries[0].raw_document.record_key, "note:1") + self.assertEqual(response.entries[1].delete_schema_id, "schema-note") + self.assertEqual(response.entries[1].delete_record_key, "note:1") + self.assertEqual(response.to_wire()["entries"][1]["delete"]["recordKey"], "note:1") + self.assertIs(response.require_usable(), response) + + resync = CultNetShardLogResponse.from_wire({ + "schemaVersion": "cultnet.shard_log_response.v0", + "messageId": "log-resync", + "shardId": "notes", + "shardEpoch": 2, + "entries": [], + "resyncRequired": True, + "reason": "compacted", + "compactedThrough": 12, + }) + self.assertEqual(resync.last_sequence, 12) + with self.assertRaisesRegex(ValueError, "compacted"): + resync.require_usable() + + def test_cultnet_simulation_observation_helper_matches_schema_v0_shape(self) -> None: + claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit") + self.assertEqual(len(claim_hash), 64) + message = simulation_observation( + message_id="obs-1", + witness_runtime_id="python-test", + shard_id="interop", + shard_epoch=1, + frame=42, + subject_id="player-1", + claim_kind="hit", + claim_hash=claim_hash, + claim_summary="player-1 hit target-a", + observed_at="2026-06-13T00:00:02Z", + ).to_wire() + self.assertEqual(message["schemaVersion"], "cultnet.simulation_observation.v0") + self.assertEqual(message["messageId"], "obs-1") + self.assertEqual(message["observation"]["witnessRuntimeId"], "python-test") + self.assertEqual(message["observation"]["claimHash"], claim_hash) + self.assertEqual(message["observation"]["weight"], 1.0) + observation = CultNetSimulationObservation.from_wire(message) + self.assertEqual(observation.witness_runtime_id, "python-test") + self.assertEqual(observation.claim_summary, "player-1 hit target-a") + self.assertEqual(observation.to_message_wire(message_id="obs-2")["observation"], message["observation"]) + + def test_cultnet_witness_artifact_bundle_uses_csharp_slot_order(self) -> None: + import msgpack # type: ignore + + bundle = witness_artifact_bundle( + bundle_id="bundle-1", + witness_kind="interop-proof", + captured_at="2026-06-13T00:00:03Z", + subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"}, + contracts=[{"role": "payload", "schemaId": "schema-a"}], + artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], + timing_witnesses=[{"stage": "roundtrip", "startedAt": "2026-06-13T00:00:03Z", "completedAt": "2026-06-13T00:00:04Z", "latencyMs": 1.0}], + provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-test"}, + ) + payload = encode_witness_artifact_bundle_payload(bundle) + decoded = decode_witness_artifact_bundle_payload(payload) + typed = CultNetWitnessArtifactBundle.from_wire(bundle) + typed_payload = typed.to_payload() + typed_decoded = CultNetWitnessArtifactBundle.from_payload(typed_payload) + slots = msgpack.unpackb(payload, raw=False) + + self.assertEqual(payload, typed_payload) + self.assertEqual(slots[0], "bundle-1") + self.assertEqual(slots[1], "interop-proof") + self.assertEqual(slots[2], "2026-06-13T00:00:03Z") + self.assertEqual(slots[3]["subjectId"], "note:python") + self.assertEqual(slots[4][0]["schemaId"], "schema-a") + self.assertEqual(slots[5][0]["uri"], "cultcache://bundle-1/log") + self.assertEqual(slots[6][0]["latencyMs"], 1.0) + self.assertEqual(slots[7]["runtimeId"], "python-test") + self.assertEqual(decoded["bundleId"], "bundle-1") + self.assertEqual(decoded["witnessKind"], "interop-proof") + self.assertEqual(decoded["subject"]["subjectId"], "note:python") + self.assertEqual(decoded["contracts"][0]["schemaId"], "schema-a") + self.assertEqual(decoded["artifacts"][0]["mediaType"], "text/plain") + self.assertEqual(decoded["provenance"]["runtimeId"], "python-test") + self.assertEqual(typed.bundle_id, "bundle-1") + self.assertEqual(typed_decoded.to_wire(), bundle) + + def test_python_interop_peer_filters_shard_snapshot_by_logged_membership(self) -> None: + runtime_id = f"python-interop-test-{uuid4().hex}" + state = build_state( + runtime_id=runtime_id, + runtime_kind="python", + display_name="Python Interop Test", + agent_id="python-interop-test-agent", + schema_path=str(Path("packages/cultnet-ts/integration/contracts/cultnet.interop-note.schema.json")), + ) + binding = state.bindings["note"] + logged_value = { + "schemaVersion": "cultnet.interop_note.v0", + "documentId": "note:logged", + "authorRuntimeId": runtime_id, + "title": "Logged", + "body": "This record belongs to the shard log.", + "tags": ["interop", "logged"], + } + unlogged_value = { + **logged_value, + "documentId": "note:unlogged", + "title": "Unlogged", + "body": "This record is in cache but not in the shard log.", + } + state.cache.put(binding.document, "note:logged", logged_value) + state.cache.put(binding.document, "note:unlogged", unlogged_value) + logged_record = { + "schemaId": state.note_schema_id, + "recordKey": "note:logged", + "storedAt": "2026-06-14T00:00:00Z", + "payloadEncoding": "messagepack", + "payload": binding.document.encode_payload(logged_value), + } + append_shard_log_put( + state, + {"messageId": "logged-put"}, + logged_record, + ) + + response = raw_snapshot_response( + state, + { + "schemaVersion": "cultnet.snapshot_request.v0", + "messageId": "interop-shard-snapshot", + "schemaIds": [state.note_schema_id], + "shardId": state.shard_id, + }, + ) + + self.assertEqual(response["shardId"], state.shard_id) + self.assertEqual(response["shardLogSequence"], 1) + self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:logged"]) + + def test_cultnet_simulation_consensus_dedupes_witnesses_and_requires_quorum(self) -> None: + from cultnet_py import CultNetSimulationConsensus + + consensus = CultNetSimulationConsensus( + CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0) + ) + claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100") + observations = [ + { + "witnessRuntimeId": "watcher-1", + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": claim_hash, + "claimSummary": "alice shot bob first", + "weight": 1.0, + }, + { + "witnessRuntimeId": "watcher-1", + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": "stale-duplicate", + "weight": 0.5, + }, + { + "witnessRuntimeId": "watcher-2", + "shardId": "arena", + "shardEpoch": 4, + "frame": 100, + "subjectId": "bob", + "claimKind": "hit", + "claimHash": claim_hash, + "weight": 1.0, + }, + ] + + candidates = consensus.build_candidates(observations) + typed_candidates = consensus.build_candidate_objects([ + CultNetSimulationObservation.from_wire(observation) + for observation in observations + ]) + + self.assertEqual(len(candidates), 1) + self.assertEqual(candidates[0]["claimHash"], claim_hash) + self.assertEqual(candidates[0]["witnessCount"], 2) + self.assertEqual(candidates[0]["supportWeight"], 2.0) + self.assertTrue(candidates[0]["hasQuorum"]) + self.assertEqual(len(typed_candidates), 1) + self.assertIsInstance(typed_candidates[0], CultNetSimulationConsensusCandidate) + self.assertEqual(typed_candidates[0].claim_hash, claim_hash) + self.assertEqual(typed_candidates[0].to_wire(), candidates[0]) + self.assertEqual(CultNetSimulationConsensusCandidate.from_wire(candidates[0]), typed_candidates[0]) + + +class CultNetRudpFragmentBoundTests(unittest.TestCase): + def _pair(self) -> tuple[CultNetRudpSession, CultNetRudpSession]: + sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=1)) + receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=100)) + sender.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 90, 0, 0, "control")) + receiver.receive(CultNetRudpPacket(CultNetRudpPacketType.ACCEPT, 311, 91, 0, 0, "control")) + return sender, receiver + + def test_stranded_fragment_sets_are_bounded_and_evicted_oldest_first(self) -> None: + sender, receiver = self._pair() + receiver._max_pending_fragment_sets = 4 + + # Eight payloads each lose their last fragment; the map must not grow past four. + stranded = [] + for fill in range(1, 9): + packets = sender.send_many("media", bytes([fill]) * 2500, max_fragment_bytes=1000) + self.assertEqual(len(packets), 3) + for packet in packets[:2]: + self.assertEqual(receiver.receive(packet).delivered, ()) + stranded.append(packets[2]) + self.assertEqual(len(receiver._fragment_buffers), 4) + self.assertEqual(receiver.fragment_sets_evicted, 4) + + # A complete payload still lands. + delivered = [] + for packet in sender.send_many("media", bytes([9]) * 2500, max_fragment_bytes=1000): + delivered.extend(receiver.receive(packet).delivered) + self.assertEqual([frame.payload for frame in delivered], [bytes([9]) * 2500]) + self.assertEqual(receiver.fragment_sets_evicted, 5) + + # Oldest-first: set 8 still pends and completes late; set 1 was evicted. + self.assertEqual(receiver.receive(stranded[7]).delivered[0].payload, bytes([8]) * 2500) + self.assertEqual(receiver.receive(stranded[0]).delivered, ()) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/cultcache-py/tools/GameCult.Caching.Benchmark/GameCult.Caching.Benchmark.csproj b/packages/cultnet-py/tools/GameCult.Caching.Benchmark/GameCult.Caching.Benchmark.csproj similarity index 100% rename from packages/cultcache-py/tools/GameCult.Caching.Benchmark/GameCult.Caching.Benchmark.csproj rename to packages/cultnet-py/tools/GameCult.Caching.Benchmark/GameCult.Caching.Benchmark.csproj diff --git a/packages/cultcache-py/tools/GameCult.Caching.Benchmark/Program.cs b/packages/cultnet-py/tools/GameCult.Caching.Benchmark/Program.cs similarity index 100% rename from packages/cultcache-py/tools/GameCult.Caching.Benchmark/Program.cs rename to packages/cultnet-py/tools/GameCult.Caching.Benchmark/Program.cs diff --git a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts index 4353e132..00fff83d 100644 --- a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts +++ b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts @@ -24,7 +24,9 @@ const pythonCommand = process.env.PYTHON ?? resolvePythonCommand(); const cultNetTsRoot = resolve(__dirname, "../../.."); const cultLibRoot = findAncestor(cultNetTsRoot, "CultLib.sln") ?? resolve(cultNetTsRoot, "..", "CultLib"); const cultcachePyRoot = resolve(cultLibRoot, "packages", "cultcache-py"); -const cultcachePySrc = resolve(cultcachePyRoot, "src"); +const cultcachePySrc = ["cultcache-py", "cultnet-py", "cultmesh-py"] + .map((name) => resolve(cultLibRoot, "packages", name, "src")) + .join(delimiter); const cultmeshKotlinRoot = resolve(cultLibRoot, "packages", "cultmesh-kotlin"); const kotlinHome = process.env.KOTLIN_HOME ?? "C:\\Program Files\\Android\\Android Studio\\plugins\\Kotlin\\kotlinc"; const kotlinJavaHome = process.env.KOTLIN_JAVA_HOME ?? "C:\\Program Files\\Android\\Android Studio\\jbr"; diff --git a/scripts/measure-reactive-document-performance.ps1 b/scripts/measure-reactive-document-performance.ps1 index 7b410e41..c0f44eaf 100644 --- a/scripts/measure-reactive-document-performance.ps1 +++ b/scripts/measure-reactive-document-performance.ps1 @@ -69,7 +69,7 @@ if ($LASTEXITCODE -ne 0) { throw "CultMesh TypeScript reactive document performance probe failed." } -$pythonSource = Join-Path $root "packages\cultcache-py\src" +$pythonSource = (@("cultcache-py", "cultnet-py", "cultmesh-py") | ForEach-Object { Join-Path $root "packages\$_\src" }) -join [IO.Path]::PathSeparator $previousPythonPath = $env:PYTHONPATH $env:PYTHONPATH = if ([string]::IsNullOrWhiteSpace($previousPythonPath)) { $pythonSource @@ -78,7 +78,7 @@ else { $pythonSource + [IO.Path]::PathSeparator + $previousPythonPath } try { - & $pythonExecutable (Join-Path $root "packages\cultcache-py\tools\reactive_performance_probe.py") @runtimeArguments + & $pythonExecutable (Join-Path $root "packages\cultmesh-py\tools\reactive_performance_probe.py") @runtimeArguments if ($LASTEXITCODE -ne 0) { throw "CultMesh Python reactive document performance probe failed." } diff --git a/scripts/verify-reactive-document-scaling.ps1 b/scripts/verify-reactive-document-scaling.ps1 index a60c9dee..523b761f 100644 --- a/scripts/verify-reactive-document-scaling.ps1 +++ b/scripts/verify-reactive-document-scaling.ps1 @@ -81,7 +81,7 @@ finally { Pop-Location } -$pythonPath = Join-Path $root "packages\cultcache-py\src" +$pythonPath = (@("cultcache-py", "cultnet-py", "cultmesh-py") | ForEach-Object { Join-Path $root "packages\$_\src" }) -join [IO.Path]::PathSeparator $previousPythonPath = $env:PYTHONPATH $env:PYTHONPATH = if ([string]::IsNullOrWhiteSpace($previousPythonPath)) { $pythonPath @@ -91,8 +91,8 @@ else { } try { & $pythonExecutable -m unittest discover ` - -s (Join-Path $root "packages\cultcache-py\tests") ` - -p "test_cultcache.py" ` + -s (Join-Path $root "packages\cultmesh-py\tests") ` + -p "test_cultmesh.py" ` -k "reactive_scheduling_scales_with_changed_documents_only" if ($LASTEXITCODE -ne 0) { throw "CultMesh Python reactive scaling verification failed." From 6d98b03471c5b47c2b6fa3a11d936bad48d0e43b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:22:44 +0200 Subject: [PATCH 082/203] Drop the license classifier setuptools now refuses setuptools >= 77 rejects "License :: OSI Approved :: MIT License" once a PEP 639 license expression is set, and `pip install -e` builds with the newest setuptools. That has failed both CI workflows at the install step since 2026-09-04. `license = "MIT"` is the expression; the classifier said the same thing and is gone from all three Python manifests. Co-Authored-By: Claude Fable 5.1 --- packages/cultcache-py/pyproject.toml | 1 - packages/cultmesh-py/pyproject.toml | 1 - packages/cultnet-py/pyproject.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/cultcache-py/pyproject.toml b/packages/cultcache-py/pyproject.toml index ebd92a26..72573cf2 100644 --- a/packages/cultcache-py/pyproject.toml +++ b/packages/cultcache-py/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10" license = "MIT" authors = [{ name = "GameCult" }] classifiers = [ - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/packages/cultmesh-py/pyproject.toml b/packages/cultmesh-py/pyproject.toml index 847b18e4..a1df5ee3 100644 --- a/packages/cultmesh-py/pyproject.toml +++ b/packages/cultmesh-py/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10" license = "MIT" authors = [{ name = "GameCult" }] classifiers = [ - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/packages/cultnet-py/pyproject.toml b/packages/cultnet-py/pyproject.toml index f102b58b..41c4e8f6 100644 --- a/packages/cultnet-py/pyproject.toml +++ b/packages/cultnet-py/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10" license = "MIT" authors = [{ name = "GameCult" }] classifiers = [ - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From 830a5d718f1216dbb220c6d494cc51f2f87bcd41 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:29:34 +0200 Subject: [PATCH 083/203] Make the Eve onboarding checkpoint run against the contract that exists The portability workflow has been red since 2026-08-22. Three things, each hidden behind the one before it: - Eve started refusing to build a command intent for a surface that has not advertised a command boundary and receipt schema (Eve 4338a42, 2026-08-22). The two-runtime sample never advertised one. It now passes a provider advertisement with worldInteraction for its surface, which is the declaration the lowering was right to demand. - The sample and the package-closure script still imported "cultcache-ts"; the package became @gamecult/cultcache-ts on 2026-09-04 (8cb3b72) and CI never got far enough to say so. Two READMEs carried the same import. - The job pinned Eve to codex/eve-browser-host-isolation, a branch that was never merged and stopped moving on 2026-08-25. The sample is written against Eve main and passes there; the pin now says so. The interop workflow's Python step ran one test file by path; it now discovers the three split suites. scripts/verify-eve-two-runtime-sample.ps1 -EveRoot F:\Projects\Eve (Eve main ccc3ffe) -> browserCount 1, headlessCount 1, restartCount 1 Co-Authored-By: Claude Fable 5.1 --- .github/workflows/cultmesh-portability.yml | 4 +++- .github/workflows/cultnet-interop.yml | 5 ++++- packages/cultcache-ts/README.md | 2 +- packages/cultnet-ts/README.md | 2 +- samples/eve-two-runtime/sample.mjs | 15 ++++++++++++++- scripts/test-typescript-package-closure.mjs | 4 ++-- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index 4d93085a..ce361fc8 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -28,7 +28,9 @@ jobs: uses: actions/checkout@v4 with: repository: GameCult/Eve - ref: ${{ vars.EVE_INTEGRATION_REF || 'codex/eve-browser-host-isolation' }} + # Eve main is the contract this sample is written against; the codex + # branch this used to pin was never merged and stopped on 2026-08-25. + ref: ${{ vars.EVE_INTEGRATION_REF || 'main' }} path: Eve - name: Set up .NET diff --git a/.github/workflows/cultnet-interop.yml b/.github/workflows/cultnet-interop.yml index f7f2eefc..6876a76a 100644 --- a/.github/workflows/cultnet-interop.yml +++ b/.github/workflows/cultnet-interop.yml @@ -87,7 +87,10 @@ jobs: run: npm test --workspace packages/cultnet-ts - name: Verify Python RUDP reliability - run: python -m unittest packages/cultcache-py/tests/test_cultcache.py + run: | + python -m unittest discover -s packages/cultcache-py/tests + python -m unittest discover -s packages/cultnet-py/tests + python -m unittest discover -s packages/cultmesh-py/tests - name: Verify Rust RUDP reliability run: cargo test --manifest-path packages/cultnet-rs/Cargo.toml rudp diff --git a/packages/cultcache-ts/README.md b/packages/cultcache-ts/README.md index 064cc91b..4a696103 100644 --- a/packages/cultcache-ts/README.md +++ b/packages/cultcache-ts/README.md @@ -53,7 +53,7 @@ import { SingleFileMessagePackBackingStore, defineDocumentRegistry, defineDocumentType, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; const itemDocument = defineDocumentType({ type: "item", diff --git a/packages/cultnet-ts/README.md b/packages/cultnet-ts/README.md index 5ff66e7e..75e77b88 100644 --- a/packages/cultnet-ts/README.md +++ b/packages/cultnet-ts/README.md @@ -195,7 +195,7 @@ the helper path. ## Quick Example ```ts -import { CultCache, defineDocumentType } from "cultcache-ts"; +import { CultCache, defineDocumentType } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, createTcpFramedCultNetPeer, diff --git a/samples/eve-two-runtime/sample.mjs b/samples/eve-two-runtime/sample.mjs index e3269618..93ed1dc8 100644 --- a/samples/eve-two-runtime/sample.mjs +++ b/samples/eve-two-runtime/sample.mjs @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { JSDOM } from "jsdom"; import { z } from "zod"; -import { defineDocumentType } from "cultcache-ts"; +import { defineDocumentType } from "@gamecult/cultcache-ts"; import { CultMesh } from "cultmesh-ts"; import { parseEveCommandReceipt, @@ -172,6 +172,19 @@ let browserReceipt; renderEveSurface(await node.document(surfaceDocument, surfaceId).latest(), host, { activeSurfaceId: surfaceId, clientId: "browser-client", + // Eve refuses to build a command intent for a surface that has not + // advertised where its commands go and what comes back. The provider + // advertisement is that declaration; a lowering cannot infer it. + provider: { + providerId, + surfaces: [{ + surfaceId, + worldInteraction: { + commandBoundary: "sample.counter.commands", + receiptSchema: "gamecult.eve.command_receipt.v1", + }, + }], + }, commandSink: async intent => { browserReceipt = await increment.bind(browserVerse).invoke(intent.payload, { idempotencyKey: "browser-click-1", diff --git a/scripts/test-typescript-package-closure.mjs b/scripts/test-typescript-package-closure.mjs index e5648c9c..0ed79e59 100644 --- a/scripts/test-typescript-package-closure.mjs +++ b/scripts/test-typescript-package-closure.mjs @@ -58,7 +58,7 @@ try { writeFileSync(join(consumerRoot, "runtime-smoke.cjs"), ` const assert = require("node:assert/strict"); -const cache = require("cultcache-ts"); +const cache = require("@gamecult/cultcache-ts"); const net = require("cultnet-ts"); const mesh = require("cultmesh-ts"); assert.equal(typeof cache.defineDocumentType, "function"); @@ -81,7 +81,7 @@ assert.equal("putDocument" in CultMeshBrowserClient.prototype, false); execFileSync(process.execPath, ["browser-runtime-smoke.mjs"], { cwd: consumerRoot, stdio: "inherit" }); writeFileSync(join(consumerRoot, "types-smoke.ts"), ` -import { defineDocumentType } from "cultcache-ts"; +import { defineDocumentType } from "@gamecult/cultcache-ts"; import { CultNetPeer } from "cultnet-ts"; import { CultMesh, From 672235e8f3af6c6f9de08df989c7e3e18f925e95 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:30:31 +0200 Subject: [PATCH 084/203] Ignore Python package build output Co-Authored-By: Claude Fable 5.1 --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 94ed900d..e4661501 100644 --- a/.gitignore +++ b/.gitignore @@ -516,3 +516,7 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp + +# Python package build output +packages/*-py/build/ +*.egg-info/ From f94f5581b078f79a532338f642008d66c6a3f1ff Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:35:15 +0200 Subject: [PATCH 085/203] Let the verse catalog schema say what the reference emits The C# reference publishes authorityRoutes with certificate: null for loopback routes, and its own verifier treats null as "no certificate" in local development. The TypeScript JSON schema only allowed the object, so the browser client rejected every catalog the getting-started provider served: "certificate: must be object", Odin could not resolve the Verse, and the real browser/C# checkpoint has failed since f622ee1 (2026-08-18), behind the sample failure that hid it. TypeScript consumers already guard route.certificate; the schema now accepts null. The package-closure smoke also still spelled the browser identity with providerId; it is authorityRuntimeId since c194bd4. npm test --workspace packages/cultnet-ts -> 54 pass npm run test:pack:ts -> passed Co-Authored-By: Claude Fable 5.1 --- .../contracts/cultmesh.verse-catalog-response.schema.json | 4 +++- scripts/test-typescript-package-closure.mjs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json index f23ef5f7..464908ad 100644 --- a/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json +++ b/packages/cultnet-ts/contracts/cultmesh.verse-catalog-response.schema.json @@ -28,7 +28,9 @@ "protocolIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }, "priority": { "type": "integer", "minimum": 0 }, "generation": { "type": "string", "minLength": 1 }, - "certificate": { "$ref": "#/$defs/routeCertificate" } + "certificate": { + "anyOf": [{ "$ref": "#/$defs/routeCertificate" }, { "type": "null" }] + } } }, "routeCertificate": { diff --git a/scripts/test-typescript-package-closure.mjs b/scripts/test-typescript-package-closure.mjs index 0ed79e59..5f00aa47 100644 --- a/scripts/test-typescript-package-closure.mjs +++ b/scripts/test-typescript-package-closure.mjs @@ -131,7 +131,7 @@ import { const identity: CultMeshBrowserIdentity = { verseId: "smoke", - providerId: "smoke.provider", + authorityRuntimeId: "smoke.authority", }; const lease: CultMeshRawDocumentLeaseOptions = { schemaId: "smoke.document.v1", From 89687fceb4d0af32e672738a18c352f1b3b744df Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 13:45:07 +0200 Subject: [PATCH 086/203] Let the browser sample read the provider's advertisement Eve refuses to build a command intent for a surface that has not advertised a command boundary and receipt schema, and the intent it builds now carries the operation under operation.operationId. The browser sample did neither: the click was refused inside the page and the checkpoint saw only a 30 s timeout on the receipt. The C# provider now publishes its gamecult.eve.provider_advertisement.v1 document with the surface's worldInteraction, and the page leases that and hands it to the lowering as `provider`. The provider owns the declaration; the page reads it rather than restating it. The command sink reads intent.operation.operationId. The checkpoint now forwards the page's console and pageerror output. "CultMesh browser operation is required." took one run to find with it and had been a bare timeout without it. scripts/verify-eve-getting-started.ps1 -EveRoot ..\Eve (Eve main) -> Eve getting-started verification passed: clean .NET and TypeScript artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, authority-bound routes, receipts, persistence, and route replacement. Co-Authored-By: Claude Fable 5.1 --- samples/eve-browser-network/Program.cs | 45 ++++++++++++++++++++- samples/eve-browser-network/browser/main.ts | 13 +++++- scripts/verify-eve-browser-network.mjs | 4 ++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/samples/eve-browser-network/Program.cs b/samples/eve-browser-network/Program.cs index bfadd7db..68f72e05 100644 --- a/samples/eve-browser-network/Program.cs +++ b/samples/eve-browser-network/Program.cs @@ -14,6 +14,8 @@ using Microsoft.Extensions.DependencyInjection; const string counterKey = "counter:main"; const string surfaceKey = "sample.counter"; +const string providerKey = "sample.counter-ui"; +const string commandBoundary = "sample.counter.commands"; var arguments = Args.Parse(args); if (arguments.Mode == "provider") { @@ -89,7 +91,7 @@ static async Task RunOdinAsync(Args arguments) static async Task RunProviderAsync(Args arguments) { - var documentTypes = new[] { typeof(CounterState), typeof(EveSurfaceDocument) }; + var documentTypes = new[] { typeof(CounterState), typeof(EveSurfaceDocument), typeof(EveProviderAdvertisementDocument) }; var registry = CultMesh.CreateCultCacheDocumentRegistry(documentTypes); var documents = CultMesh.CreateCultNetDocumentRegistry(documentTypes, registry); using var node = await CultMesh.CreateNodeAsync(arguments.StatePath, new CultMeshNodeOptions @@ -111,6 +113,10 @@ static async Task RunProviderAsync(Args arguments) await node.Database.PutAsync(new CultRecordKey(counterKey), CounterState.Initial(counterKey)); if (node.Cache.Get(new CultRecordKey(surfaceKey)) == null) await node.Database.PutAsync(new CultRecordKey(surfaceKey), CreateCounterSurface()); + // The advertisement is how a lowering learns where this surface's commands + // go and what comes back. Eve refuses to build a command intent without it, + // and the browser must not have to restate it: the provider is the owner. + await node.Database.PutAsync(new CultRecordKey(providerKey), CreateProviderAdvertisement(arguments)); await node.FlushAsync(); await using var schemaServer = new CultNetWebSocketSchemaServer(); @@ -341,6 +347,43 @@ static void ForceCollection() GC.Collect(); } +static EveProviderAdvertisementDocument CreateProviderAdvertisement(Args arguments) +{ + var worldInteraction = new EveWorldInteractionAdvertisement( + projectionKind: "surface-state", + stateSchemas: ["sample.counter_state.v1"], + commandBoundary: commandBoundary, + commandRecordRef: "", + receiptSchema: EveCommandReceiptDocument.SchemaId, + receiptRecordRef: "", + assetManifestRecordRef: "", + loweringTargets: ["browser"], + ownership: arguments.AuthorityRuntimeId); + return new EveProviderAdvertisementDocument( + providerId: providerKey, + serviceId: arguments.VerseId, + verseId: arguments.VerseId, + title: "CultMesh browser counter", + kind: "sample.daemon", + cultMeshAddress: arguments.AuthorityRuntimeId, + updatedAtUtc: DateTime.UtcNow.ToString("O"), + freshness: new EveProviderFreshness("live", DateTime.UtcNow.ToString("O"), 0), + schemas: ["sample.counter_state.v1", EveSurfaceDocument.SchemaId, EveCommandReceiptDocument.SchemaId], + witnesses: [], + surfaces: + [ + new EveAdvertisedSurface( + surfaceKey, + EveSurfaceDocument.SchemaId, + surfaceKey, + "cultmesh", + "live", + "surface", + worldInteraction) + ], + commands: [new EveAdvertisedCommand("sample.counter.increment", surfaceKey, "cultmesh", "Increment")]); +} + static EveSurfaceDocument CreateCounterSurface() { var networkRoute = new CultMeshRouteHint(CultMeshLocalityKind.Network, "cultmesh"); diff --git a/samples/eve-browser-network/browser/main.ts b/samples/eve-browser-network/browser/main.ts index 8251a0a3..b04ee5c8 100644 --- a/samples/eve-browser-network/browser/main.ts +++ b/samples/eve-browser-network/browser/main.ts @@ -1,4 +1,4 @@ -import { parseEveCommandReceipt, parseEveSurfaceDocument } from "@gamecult/eve-contracts"; +import { parseEveCommandReceipt, parseEveProviderAdvertisement, parseEveSurfaceDocument } from "@gamecult/eve-contracts"; import { renderEveSurface } from "@gamecult/eve-browser-lowering"; import { CultMeshBrowserClient, @@ -52,14 +52,23 @@ try { recordKey: "sample.counter", subscriptionId: "browser-surface", }); + // The provider owns the declaration of where this surface's commands go and + // what comes back; the page reads it rather than restating it. + const advertisement = await mesh.leaseRawDocument({ + schemaId: "gamecult.eve.provider_advertisement.v1", + recordKey: "sample.counter-ui", + subscriptionId: "browser-provider", + }); + const provider = parseEveProviderAdvertisement(decodeCultNetPayload(advertisement.current!)); const currentSurface = parseEveSurfaceDocument(decodeCultNetPayload(surface.current!)); renderEveSurface(currentSurface, host, { activeSurfaceId: "sample.counter", clientId: "sample.chromium", + provider, commandSink: async intent => { const response = await mesh.invoke({ serviceId: "sample.counter", - operation: intent.command, + operation: intent.operation.operationId, payloadSchema: "sample.increment.v1", payload: { amount: 1 }, idempotencyKey: window.__sampleCommandId, diff --git a/scripts/verify-eve-browser-network.mjs b/scripts/verify-eve-browser-network.mjs index 8c865b1d..c1388712 100644 --- a/scripts/verify-eve-browser-network.mjs +++ b/scripts/verify-eve-browser-network.mjs @@ -107,6 +107,10 @@ try { browser = await chromium.launch({ executablePath: resolveChromiumExecutable(), headless: true }); let page = await browser.newPage(); + // A command the lowering refuses is caught inside the page; without this + // the only symptom is a 30 s timeout on the receipt. + page.on("console", message => console.log(`[browser:${message.type()}] ${message.text()}`)); + page.on("pageerror", error => console.log(`[browser:pageerror] ${error.message}`)); const url = `http://127.0.0.1:${httpPort}/?odin=${encodeURIComponent(odinEndpoint)}&token=${token}`; await page.goto(url); await page.waitForFunction(() => window.__sampleReady || window.__sampleError); From 34c8ea79b9d5ff0d3ad11b82216f746474b57ec0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 21:33:06 +0200 Subject: [PATCH 087/203] Build receiver feedback where both ends can see the rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_receiver_feedback normalises the record a receiver sends back — damage lists sorted and unique, chunk keys well-formed, a keyframe requested whenever something is missing — and until now lived in Muninn, the one end that never sends it. Ratatoskr sends it; Muninn's sender tests construct it to exercise the repair cache. A builder both ends call belongs beside the record it builds. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/src/media_stream_wire.rs | 118 +++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs index 7083d927..6bc30171 100644 --- a/packages/cultnet-rs/src/media_stream_wire.rs +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -445,3 +445,121 @@ pub fn validate_feedback_record(record: &GameCultMediaReceiverFeedbackRecord) -> normalize_video_chunk_feedback_keys(record.missing_video_chunk_keys.clone())?; Ok(()) } + +/// What a receiver has to say about a stream. The record it builds is the +/// contract's; this normalises it so both ends agree on its shape: damage +/// lists sorted and unique, chunk keys well-formed, and a keyframe requested +/// whenever something is missing, since a producer cannot repair a reference +/// the receiver never had. +#[derive(Clone, Debug)] +pub struct ReceiverFeedbackOptions<'a> { + pub stream_id: &'a str, + pub session_id: &'a str, + pub receiver_id: &'a str, + pub highest_decodable_frame_id: Option, + pub missing_frame_ids: Vec, + pub missing_video_chunk_keys: Vec, + pub late_frame_ids: Vec, + pub requested_keyframe: bool, + pub jitter_us: i64, + pub decode_queue_us: i64, + pub observed_at: &'a str, +} + +pub fn build_receiver_feedback( + options: ReceiverFeedbackOptions<'_>, +) -> Result { + let mut missing_frame_ids = options.missing_frame_ids; + missing_frame_ids.sort_unstable(); + missing_frame_ids.dedup(); + let missing_video_chunk_keys = + normalize_video_chunk_feedback_keys(options.missing_video_chunk_keys)?; + let mut late_frame_ids = options.late_frame_ids; + late_frame_ids.sort_unstable(); + late_frame_ids.dedup(); + let requested_keyframe = options.requested_keyframe + || !missing_frame_ids.is_empty() + || !missing_video_chunk_keys.is_empty(); + + let record = GameCultMediaReceiverFeedbackRecord { + stream_id: options.stream_id.to_string(), + session_id: options.session_id.to_string(), + receiver_id: options.receiver_id.to_string(), + highest_decodable_frame_id: options.highest_decodable_frame_id, + missing_frame_ids, + late_frame_ids, + requested_keyframe, + jitter_us: options.jitter_us, + decode_queue_us: options.decode_queue_us, + observed_at: options.observed_at.to_string(), + missing_video_chunk_keys, + }; + validate_feedback_record(&record)?; + Ok(record) +} + +#[cfg(test)] +mod receiver_feedback_tests { + use super::*; + + fn options() -> ReceiverFeedbackOptions<'static> { + ReceiverFeedbackOptions { + stream_id: "muninn.raven.av.rudp", + session_id: "session-1", + receiver_id: "starfire.obs", + highest_decodable_frame_id: Some(40), + missing_frame_ids: Vec::new(), + missing_video_chunk_keys: Vec::new(), + late_frame_ids: Vec::new(), + requested_keyframe: false, + jitter_us: 0, + decode_queue_us: 2_000, + observed_at: "2026-06-18T00:00:00Z", + } + } + + #[test] + fn builds_receiver_feedback_with_sorted_unique_damage_lists() -> Result<()> { + let feedback = build_receiver_feedback(ReceiverFeedbackOptions { + missing_frame_ids: vec![43, 42, 43], + missing_video_chunk_keys: vec![ + video_chunk_feedback_key(43, 2), + video_chunk_feedback_key(42, 1), + video_chunk_feedback_key(42, 1), + ], + late_frame_ids: vec![39, 39, 38], + jitter_us: 700, + ..options() + })?; + + assert_eq!(feedback.missing_frame_ids, vec![42, 43]); + assert_eq!(feedback.missing_video_chunk_keys, vec!["42:1", "43:2"]); + assert_eq!(feedback.late_frame_ids, vec![38, 39]); + assert!(feedback.requested_keyframe, "missing chunks imply a lost reference"); + Ok(()) + } + + #[test] + fn rejects_negative_receiver_feedback_timing() { + let error = build_receiver_feedback(ReceiverFeedbackOptions { jitter_us: -1, ..options() }) + .unwrap_err(); + assert!(error.to_string().contains("jitter_us")); + } + + #[test] + fn rejects_malformed_receiver_feedback_chunk_keys() { + let error = build_receiver_feedback(ReceiverFeedbackOptions { + missing_video_chunk_keys: vec!["frame:chunk".to_string()], + ..options() + }) + .unwrap_err(); + assert!(error.to_string().contains("frame_id"), "{error}"); + } + + #[test] + fn rejects_an_empty_receiver_id() { + let error = build_receiver_feedback(ReceiverFeedbackOptions { receiver_id: "", ..options() }) + .unwrap_err(); + assert!(error.to_string().contains("receiver_id")); + } +} From 4160ab560b15150d8054c5c7f557711ad244b8e0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 21:35:04 +0200 Subject: [PATCH 088/203] A repair request is not a keyframe request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The builder forced requested_keyframe whenever chunk keys were missing. A producer answers each new keyframe request with an IDR, so every repair request would have cost a keyframe — the thing a repair request exists to avoid. The old C++ receiver sent repairs with the flag off; this builder had never been driven by a real receiver. A whole missing frame still implies one, since the repair cache holds chunks. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/src/media_stream_wire.rs | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs index 6bc30171..fb1b1f8d 100644 --- a/packages/cultnet-rs/src/media_stream_wire.rs +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -449,8 +449,9 @@ pub fn validate_feedback_record(record: &GameCultMediaReceiverFeedbackRecord) -> /// What a receiver has to say about a stream. The record it builds is the /// contract's; this normalises it so both ends agree on its shape: damage /// lists sorted and unique, chunk keys well-formed, and a keyframe requested -/// whenever something is missing, since a producer cannot repair a reference -/// the receiver never had. +/// whenever a whole frame is missing, since a producer's repair cache holds +/// chunks, not references. Missing chunks alone are a repair request, which +/// exists precisely so that a keyframe is not needed. #[derive(Clone, Debug)] pub struct ReceiverFeedbackOptions<'a> { pub stream_id: &'a str, @@ -477,9 +478,7 @@ pub fn build_receiver_feedback( let mut late_frame_ids = options.late_frame_ids; late_frame_ids.sort_unstable(); late_frame_ids.dedup(); - let requested_keyframe = options.requested_keyframe - || !missing_frame_ids.is_empty() - || !missing_video_chunk_keys.is_empty(); + let requested_keyframe = options.requested_keyframe || !missing_frame_ids.is_empty(); let record = GameCultMediaReceiverFeedbackRecord { stream_id: options.stream_id.to_string(), @@ -535,7 +534,19 @@ mod receiver_feedback_tests { assert_eq!(feedback.missing_frame_ids, vec![42, 43]); assert_eq!(feedback.missing_video_chunk_keys, vec!["42:1", "43:2"]); assert_eq!(feedback.late_frame_ids, vec![38, 39]); - assert!(feedback.requested_keyframe, "missing chunks imply a lost reference"); + assert!(feedback.requested_keyframe, "a whole missing frame is a lost reference"); + Ok(()) + } + + /// A producer answers a keyframe request with an IDR. A repair request is + /// the cheaper alternative and must not carry one by accident. + #[test] + fn a_repair_request_does_not_ask_for_a_keyframe() -> Result<()> { + let feedback = build_receiver_feedback(ReceiverFeedbackOptions { + missing_video_chunk_keys: vec![video_chunk_feedback_key(42, 1)], + ..options() + })?; + assert!(!feedback.requested_keyframe); Ok(()) } From 780e1a48cdf6ded6d3a6e3e72916817fb3768f05 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 22:07:39 +0200 Subject: [PATCH 089/203] Advertise and request media streams without naming a producer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two state records beside the media wire records, travelling as CultMesh documents through Odin rather than on the media channel: - gamecult.media_stream_advertisement.v1: a stream a producer can serve on request — its video and audio sources, the codecs it can encode, audio sample rate and channels, defaults, and the connection id it dials a receiver with. Keyed by stream_id. A consumer's picker is built from these. - gamecult.media_stream_request.v1: a consumer asking a producer to start or stop serving a stream to a receiver endpoint, with sources, codecs, bitrate and latency budget. Keyed by stream_id:receiver_id so a newer request supersedes; the producer answers on the same key by rewriting state and detail. Until now the only way to pick a stream was Muninn's own muninn.obs_stream_catalog and muninn.capture_stream_command, which live in odin-core and bind every consumer to one producer. Odin persists any well-formed document, so nothing there has to change to carry these. Co-Authored-By: Claude Fable 5.1 --- .../cultnet-rs/src/media_stream_contracts.rs | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) diff --git a/packages/cultnet-rs/src/media_stream_contracts.rs b/packages/cultnet-rs/src/media_stream_contracts.rs index 815e3392..59761561 100644 --- a/packages/cultnet-rs/src/media_stream_contracts.rs +++ b/packages/cultnet-rs/src/media_stream_contracts.rs @@ -46,6 +46,349 @@ pub const GAMECULT_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA: &str = "gamecult.media_video_ pub const GAMECULT_MEDIA_VIDEO_PARITY_SHARD_SCHEMA: &str = "gamecult.media_video_parity_shard.v2"; pub const GAMECULT_MEDIA_AUDIO_PACKET_SCHEMA: &str = "gamecult.media_audio_packet.v1"; pub const GAMECULT_MEDIA_RECEIVER_FEEDBACK_SCHEMA: &str = "gamecult.media_receiver_feedback.v1"; +pub const GAMECULT_MEDIA_STREAM_ADVERTISEMENT_SCHEMA: &str = + "gamecult.media_stream_advertisement.v1"; +pub const GAMECULT_MEDIA_STREAM_REQUEST_SCHEMA: &str = "gamecult.media_stream_request.v1"; + +/// A media stream a producer can serve on request, as it appears in Odin. +/// +/// This is the record a consumer's picker is built from: which streams exist, +/// which video and audio sources each can capture, which codecs it can encode, +/// and the defaults it would use. It is state, not media — it travels as a +/// CultMesh document through Odin, never on the media channel — and it names no +/// consumer. `producer_id` is the runtime that answers requests for it. +/// +/// Keyed by `stream_id`. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_stream_advertisement", + schema = "gamecult.media_stream_advertisement.v1" +)] +pub struct GameCultMediaStreamAdvertisementRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub producer_id: String, + #[cultcache(key = 2)] + pub label: String, + /// `available`, `streaming`, or `unavailable`. + #[cultcache(key = 3)] + pub state: String, + #[cultcache(key = 4)] + pub video_source_ids: Vec, + #[cultcache(key = 5)] + pub video_source_labels: Vec, + #[cultcache(key = 6)] + pub audio_source_ids: Vec, + #[cultcache(key = 7)] + pub audio_source_labels: Vec, + /// Codecs the producer can encode video as, e.g. `h264`. + #[cultcache(key = 8)] + pub video_codecs: Vec, + /// Codecs the producer can emit audio as, e.g. `pcm-f32le-interleaved`. + #[cultcache(key = 9)] + pub audio_codecs: Vec, + #[cultcache(key = 10)] + pub audio_sample_rate: u32, + #[cultcache(key = 11)] + pub audio_channels: u32, + #[cultcache(key = 12)] + pub default_video_bitrate_kbps: u32, + #[cultcache(key = 13)] + pub default_latency_budget_ms: u32, + #[cultcache(key = 14)] + pub media_packet_bytes: u32, + /// The CultNet RUDP connection id the producer dials the receiver with. + #[cultcache(key = 15)] + pub media_connection_id: u32, + #[cultcache(key = 16)] + pub updated_at: String, +} + +/// A consumer asking a producer to start or stop serving a stream to it. +/// +/// The producer answers by rewriting `state` and `detail` on the same record +/// key, so a consumer watches one document for the outcome. A newer request for +/// the same `stream_id` and `receiver_id` supersedes an older one. +/// +/// Keyed by `media_stream_request_key(stream_id, receiver_id)`. +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "gamecult.media_stream_request", + schema = "gamecult.media_stream_request.v1" +)] +pub struct GameCultMediaStreamRequestRecord { + #[cultcache(key = 0)] + pub request_id: String, + #[cultcache(key = 1)] + pub stream_id: String, + #[cultcache(key = 2)] + pub producer_id: String, + #[cultcache(key = 3)] + pub receiver_id: String, + /// `host:port` the producer should dial with the advertised connection id. + #[cultcache(key = 4)] + pub receiver_endpoint: String, + /// `start` or `stop`. + #[cultcache(key = 5)] + pub action: String, + /// `pending`, `running`, `stopped`, or `failed`; the producer owns it after + /// `pending`. + #[cultcache(key = 6)] + pub state: String, + /// Empty means no video. + #[cultcache(key = 7)] + pub video_source_id: String, + /// Empty means no audio. + #[cultcache(key = 8)] + pub audio_source_id: String, + #[cultcache(key = 9)] + pub video_codec: String, + #[cultcache(key = 10)] + pub audio_codec: String, + #[cultcache(key = 11)] + pub video_bitrate_kbps: u32, + #[cultcache(key = 12)] + pub latency_budget_ms: u32, + #[cultcache(key = 13)] + pub media_packet_bytes: u32, + #[cultcache(key = 14)] + pub detail: String, + #[cultcache(key = 15)] + pub updated_at: String, +} + +pub const MEDIA_STREAM_REQUEST_ACTIONS: [&str; 2] = ["start", "stop"]; +pub const MEDIA_STREAM_REQUEST_STATES: [&str; 4] = ["pending", "running", "stopped", "failed"]; +pub const MEDIA_STREAM_ADVERTISEMENT_STATES: [&str; 3] = ["available", "streaming", "unavailable"]; + +/// One live request per stream per receiver: a newer one replaces the older. +pub fn media_stream_request_key(stream_id: &str, receiver_id: &str) -> String { + format!("{stream_id}:{receiver_id}") +} + +pub fn validate_media_stream_advertisement( + record: &GameCultMediaStreamAdvertisementRecord, +) -> anyhow::Result<()> { + use anyhow::anyhow; + for (name, value) in [ + ("stream_id", &record.stream_id), + ("producer_id", &record.producer_id), + ("label", &record.label), + ("updated_at", &record.updated_at), + ] { + if value.is_empty() { + return Err(anyhow!("media stream advertisement {name} must be non-empty")); + } + } + if !MEDIA_STREAM_ADVERTISEMENT_STATES.contains(&record.state.as_str()) { + return Err(anyhow!( + "media stream advertisement state {:?} is not one of {:?}", + record.state, + MEDIA_STREAM_ADVERTISEMENT_STATES + )); + } + if record.video_source_ids.len() != record.video_source_labels.len() { + return Err(anyhow!( + "media stream advertisement has {} video source ids and {} labels", + record.video_source_ids.len(), + record.video_source_labels.len() + )); + } + if record.audio_source_ids.len() != record.audio_source_labels.len() { + return Err(anyhow!( + "media stream advertisement has {} audio source ids and {} labels", + record.audio_source_ids.len(), + record.audio_source_labels.len() + )); + } + if !record.audio_source_ids.is_empty() && (record.audio_sample_rate == 0 || record.audio_channels == 0) { + return Err(anyhow!( + "media stream advertisement offers audio without a sample rate and channel count" + )); + } + if record.media_connection_id == 0 { + return Err(anyhow!("media stream advertisement media_connection_id must be non-zero")); + } + Ok(()) +} + +pub fn validate_media_stream_request( + record: &GameCultMediaStreamRequestRecord, +) -> anyhow::Result<()> { + use anyhow::anyhow; + for (name, value) in [ + ("request_id", &record.request_id), + ("stream_id", &record.stream_id), + ("producer_id", &record.producer_id), + ("receiver_id", &record.receiver_id), + ("updated_at", &record.updated_at), + ] { + if value.is_empty() { + return Err(anyhow!("media stream request {name} must be non-empty")); + } + } + if !MEDIA_STREAM_REQUEST_ACTIONS.contains(&record.action.as_str()) { + return Err(anyhow!( + "media stream request action {:?} is not one of {:?}", + record.action, + MEDIA_STREAM_REQUEST_ACTIONS + )); + } + if !MEDIA_STREAM_REQUEST_STATES.contains(&record.state.as_str()) { + return Err(anyhow!( + "media stream request state {:?} is not one of {:?}", + record.state, + MEDIA_STREAM_REQUEST_STATES + )); + } + if record.action == "start" { + if record.receiver_endpoint.parse::().is_err() { + return Err(anyhow!( + "media stream request receiver_endpoint {:?} must be host:port", + record.receiver_endpoint + )); + } + if record.video_source_id.is_empty() && record.audio_source_id.is_empty() { + return Err(anyhow!("media stream request asks for neither video nor audio")); + } + if !record.video_source_id.is_empty() && record.video_codec.is_empty() { + return Err(anyhow!("media stream request names a video source without a codec")); + } + if !record.audio_source_id.is_empty() && record.audio_codec.is_empty() { + return Err(anyhow!("media stream request names an audio source without a codec")); + } + } + Ok(()) +} + +#[cfg(test)] +mod media_stream_catalog_tests { + use super::*; + + fn advertisement() -> GameCultMediaStreamAdvertisementRecord { + GameCultMediaStreamAdvertisementRecord { + stream_id: "muninn.raven.av.rudp".into(), + producer_id: "raven".into(), + label: "Raven desktop".into(), + state: "available".into(), + video_source_ids: vec!["display:0".into()], + video_source_labels: vec!["Display 1".into()], + audio_source_ids: vec!["wasapi-loopback:Realtek".into()], + audio_source_labels: vec!["Realtek loopback".into()], + video_codecs: vec!["h264".into()], + audio_codecs: vec!["pcm-f32le-interleaved".into()], + audio_sample_rate: 48_000, + audio_channels: 2, + default_video_bitrate_kbps: 12_000, + default_latency_budget_ms: 250, + media_packet_bytes: 848, + media_connection_id: 0x6d75_0001, + updated_at: "2026-09-10T00:00:00Z".into(), + } + } + + fn request() -> GameCultMediaStreamRequestRecord { + GameCultMediaStreamRequestRecord { + request_id: "req-1".into(), + stream_id: "muninn.raven.av.rudp".into(), + producer_id: "raven".into(), + receiver_id: "starfire.obs".into(), + receiver_endpoint: "192.168.178.146:5204".into(), + action: "start".into(), + state: "pending".into(), + video_source_id: "display:0".into(), + audio_source_id: "wasapi-loopback:Realtek".into(), + video_codec: "h264".into(), + audio_codec: "pcm-f32le-interleaved".into(), + video_bitrate_kbps: 12_000, + latency_budget_ms: 250, + media_packet_bytes: 848, + detail: String::new(), + updated_at: "2026-09-10T00:00:00Z".into(), + } + } + + #[test] + fn a_complete_advertisement_and_request_validate() -> anyhow::Result<()> { + validate_media_stream_advertisement(&advertisement())?; + validate_media_stream_request(&request())?; + Ok(()) + } + + #[test] + fn source_ids_and_labels_must_pair_up() { + let mut record = advertisement(); + record.video_source_labels.clear(); + let error = validate_media_stream_advertisement(&record).unwrap_err(); + assert!(error.to_string().contains("video source ids"), "{error}"); + } + + #[test] + fn audio_on_offer_needs_a_sample_rate_and_channels() { + let mut record = advertisement(); + record.audio_channels = 0; + assert!(validate_media_stream_advertisement(&record).is_err()); + record.audio_source_ids.clear(); + record.audio_source_labels.clear(); + assert!(validate_media_stream_advertisement(&record).is_ok(), "no audio, no requirement"); + } + + #[test] + fn a_start_needs_somewhere_to_send_and_something_to_send() { + let mut record = request(); + record.receiver_endpoint = "starfire".into(); + assert!(validate_media_stream_request(&record).unwrap_err().to_string().contains("host:port")); + let mut record = request(); + record.video_source_id.clear(); + record.audio_source_id.clear(); + assert!(validate_media_stream_request(&record).unwrap_err().to_string().contains("neither")); + let mut record = request(); + record.video_codec.clear(); + assert!(validate_media_stream_request(&record).unwrap_err().to_string().contains("codec")); + } + + #[test] + fn a_stop_needs_no_endpoint_or_sources() -> anyhow::Result<()> { + let mut record = request(); + record.action = "stop".into(); + record.receiver_endpoint.clear(); + record.video_source_id.clear(); + record.audio_source_id.clear(); + validate_media_stream_request(&record) + } + + #[test] + fn unknown_actions_and_states_are_refused() { + let mut record = request(); + record.action = "pause".into(); + assert!(validate_media_stream_request(&record).is_err()); + let mut record = request(); + record.state = "done".into(); + assert!(validate_media_stream_request(&record).is_err()); + } + + #[test] + fn one_request_per_stream_per_receiver() { + assert_eq!(media_stream_request_key("s", "starfire.obs"), "s:starfire.obs"); + } + + /// The records round-trip through the same positional encoding every + /// other runtime reads, so a Python or TypeScript consumer sees the same + /// slots. + #[test] + fn records_round_trip_positionally() -> anyhow::Result<()> { + let record = advertisement(); + let encoded = rmp_serde::to_vec(&record)?; + let decoded: GameCultMediaStreamAdvertisementRecord = rmp_serde::from_slice(&encoded)?; + assert_eq!(decoded, record); + let record = request(); + let encoded = rmp_serde::to_vec(&record)?; + let decoded: GameCultMediaStreamRequestRecord = rmp_serde::from_slice(&encoded)?; + assert_eq!(decoded, record); + Ok(()) + } +} /// One coded video access unit, possibly split across `chunk_count` records /// sharing a `frame_id`. A consumer reassembles by `(stream_id, session_id, From 61b7bf0e241f0d4cd768e1bc4e589e6eb1531f56 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 22:24:04 +0200 Subject: [PATCH 090/203] Say how Odin admits catalog records, and what it refuses Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/src/media_stream_contracts.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cultnet-rs/src/media_stream_contracts.rs b/packages/cultnet-rs/src/media_stream_contracts.rs index 59761561..2d16f57a 100644 --- a/packages/cultnet-rs/src/media_stream_contracts.rs +++ b/packages/cultnet-rs/src/media_stream_contracts.rs @@ -52,6 +52,14 @@ pub const GAMECULT_MEDIA_STREAM_REQUEST_SCHEMA: &str = "gamecult.media_stream_re /// A media stream a producer can serve on request, as it appears in Odin. /// +/// Odin admits these without any registration: it derives the document type +/// from the schema id (`prefix.vN` -> `prefix`) and refuses only a short list +/// of authority-owned types (`gamecult.runtime_presence_health`, +/// `odin.runtime_topology_correlation`, `idunn.expected_incarnation`, +/// `idunn.runtime_activation`, `idunn.process_write_lease`, +/// `gamecult.service_trust_anchor`). A new record whose type collided with one +/// of those would be rejected at the catalog with no mention of the collision. +/// /// This is the record a consumer's picker is built from: which streams exist, /// which video and audio sources each can capture, which codecs it can encode, /// and the defaults it would use. It is state, not media — it travels as a From c84cb2e862de6e19614c8bb1be72f08bb6308dac Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 10 Sep 2026 22:50:20 +0200 Subject: [PATCH 091/203] The consumer dials the producer A media stream advertisement now carries `media_endpoint`, where the producer serves it from; the request no longer carries a consumer endpoint. The producer listens and the consumer connects, the same way as every other client/server pair on the mesh, so a viewer's host admits nothing inbound and needs no firewall rule or port forward. Unshipped contract, keys renumbered in place. Audio gets its own channel ("audio": reliable, unordered, media expiry) so one session can carry lossy video under parity and reliable audio together, which is what lets the producer serve both to one dialled-in consumer. Co-Authored-By: Claude Fable 5.1 --- .../cultnet-rs/src/media_stream_contracts.rs | 87 ++++++++++++------- packages/cultnet-rs/src/media_stream_wire.rs | 7 +- packages/cultnet-rs/src/rudp.rs | 13 ++- packages/cultnet-rs/tests/cultnet.rs | 20 +++-- 4 files changed, 89 insertions(+), 38 deletions(-) diff --git a/packages/cultnet-rs/src/media_stream_contracts.rs b/packages/cultnet-rs/src/media_stream_contracts.rs index 2d16f57a..5704912e 100644 --- a/packages/cultnet-rs/src/media_stream_contracts.rs +++ b/packages/cultnet-rs/src/media_stream_contracts.rs @@ -62,7 +62,7 @@ pub const GAMECULT_MEDIA_STREAM_REQUEST_SCHEMA: &str = "gamecult.media_stream_re /// /// This is the record a consumer's picker is built from: which streams exist, /// which video and audio sources each can capture, which codecs it can encode, -/// and the defaults it would use. It is state, not media — it travels as a +/// the defaults it would use, and where it serves the stream from. It is state, not media — it travels as a /// CultMesh document through Odin, never on the media channel — and it names no /// consumer. `producer_id` is the runtime that answers requests for it. /// @@ -106,10 +106,16 @@ pub struct GameCultMediaStreamAdvertisementRecord { pub default_latency_budget_ms: u32, #[cultcache(key = 14)] pub media_packet_bytes: u32, - /// The CultNet RUDP connection id the producer dials the receiver with. + /// `host:port` the producer serves this stream from. A consumer dials it + /// with `media_connection_id` once its request is answered `running`; + /// the producer listens. The consumer's host admits nothing inbound, + /// which is what lets a viewer install a plugin without opening a port. #[cultcache(key = 15)] - pub media_connection_id: u32, + pub media_endpoint: String, + /// The CultNet RUDP connection id a consumer dials `media_endpoint` with. #[cultcache(key = 16)] + pub media_connection_id: u32, + #[cultcache(key = 17)] pub updated_at: String, } @@ -119,6 +125,9 @@ pub struct GameCultMediaStreamAdvertisementRecord { /// key, so a consumer watches one document for the outcome. A newer request for /// the same `stream_id` and `receiver_id` supersedes an older one. /// +/// It carries no consumer endpoint. The consumer dials the advertised +/// `media_endpoint` and identifies itself on the wire by `receiver_id`. +/// /// Keyed by `media_stream_request_key(stream_id, receiver_id)`. #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( @@ -134,35 +143,32 @@ pub struct GameCultMediaStreamRequestRecord { pub producer_id: String, #[cultcache(key = 3)] pub receiver_id: String, - /// `host:port` the producer should dial with the advertised connection id. - #[cultcache(key = 4)] - pub receiver_endpoint: String, /// `start` or `stop`. - #[cultcache(key = 5)] + #[cultcache(key = 4)] pub action: String, /// `pending`, `running`, `stopped`, or `failed`; the producer owns it after /// `pending`. - #[cultcache(key = 6)] + #[cultcache(key = 5)] pub state: String, /// Empty means no video. - #[cultcache(key = 7)] + #[cultcache(key = 6)] pub video_source_id: String, /// Empty means no audio. - #[cultcache(key = 8)] + #[cultcache(key = 7)] pub audio_source_id: String, - #[cultcache(key = 9)] + #[cultcache(key = 8)] pub video_codec: String, - #[cultcache(key = 10)] + #[cultcache(key = 9)] pub audio_codec: String, - #[cultcache(key = 11)] + #[cultcache(key = 10)] pub video_bitrate_kbps: u32, - #[cultcache(key = 12)] + #[cultcache(key = 11)] pub latency_budget_ms: u32, - #[cultcache(key = 13)] + #[cultcache(key = 12)] pub media_packet_bytes: u32, - #[cultcache(key = 14)] + #[cultcache(key = 13)] pub detail: String, - #[cultcache(key = 15)] + #[cultcache(key = 14)] pub updated_at: String, } @@ -218,6 +224,12 @@ pub fn validate_media_stream_advertisement( if record.media_connection_id == 0 { return Err(anyhow!("media stream advertisement media_connection_id must be non-zero")); } + if !is_host_port(&record.media_endpoint) { + return Err(anyhow!( + "media stream advertisement media_endpoint {:?} must be host:port", + record.media_endpoint + )); + } Ok(()) } @@ -251,12 +263,6 @@ pub fn validate_media_stream_request( )); } if record.action == "start" { - if record.receiver_endpoint.parse::().is_err() { - return Err(anyhow!( - "media stream request receiver_endpoint {:?} must be host:port", - record.receiver_endpoint - )); - } if record.video_source_id.is_empty() && record.audio_source_id.is_empty() { return Err(anyhow!("media stream request asks for neither video nor audio")); } @@ -270,6 +276,16 @@ pub fn validate_media_stream_request( Ok(()) } +/// A hostname or IP and a non-zero port. Not `SocketAddr`: a producer may +/// advertise a name the consumer resolves. +fn is_host_port(value: &str) -> bool { + let Some((host, port)) = value.rsplit_once(':') else { + return false; + }; + let host = host.trim_start_matches('[').trim_end_matches(']'); + !host.is_empty() && port.parse::().is_ok_and(|port| port != 0) +} + #[cfg(test)] mod media_stream_catalog_tests { use super::*; @@ -291,6 +307,7 @@ mod media_stream_catalog_tests { default_video_bitrate_kbps: 12_000, default_latency_budget_ms: 250, media_packet_bytes: 848, + media_endpoint: "192.168.178.20:5220".into(), media_connection_id: 0x6d75_0001, updated_at: "2026-09-10T00:00:00Z".into(), } @@ -302,7 +319,6 @@ mod media_stream_catalog_tests { stream_id: "muninn.raven.av.rudp".into(), producer_id: "raven".into(), receiver_id: "starfire.obs".into(), - receiver_endpoint: "192.168.178.146:5204".into(), action: "start".into(), state: "pending".into(), video_source_id: "display:0".into(), @@ -342,11 +358,23 @@ mod media_stream_catalog_tests { assert!(validate_media_stream_advertisement(&record).is_ok(), "no audio, no requirement"); } + /// The producer is the listener, so it must say where. #[test] - fn a_start_needs_somewhere_to_send_and_something_to_send() { - let mut record = request(); - record.receiver_endpoint = "starfire".into(); - assert!(validate_media_stream_request(&record).unwrap_err().to_string().contains("host:port")); + fn an_advertisement_says_where_the_stream_is_served() { + for bad in ["", "raven", "raven:", "raven:0", ":5220"] { + let mut record = advertisement(); + record.media_endpoint = bad.into(); + assert!(validate_media_stream_advertisement(&record).unwrap_err().to_string().contains("host:port"), "{bad:?}"); + } + for good in ["raven:5220", "10.77.0.2:5220", "[::1]:5220"] { + let mut record = advertisement(); + record.media_endpoint = good.into(); + assert!(validate_media_stream_advertisement(&record).is_ok(), "{good:?}"); + } + } + + #[test] + fn a_start_needs_something_to_send() { let mut record = request(); record.video_source_id.clear(); record.audio_source_id.clear(); @@ -357,10 +385,9 @@ mod media_stream_catalog_tests { } #[test] - fn a_stop_needs_no_endpoint_or_sources() -> anyhow::Result<()> { + fn a_stop_needs_no_sources() -> anyhow::Result<()> { let mut record = request(); record.action = "stop".into(); - record.receiver_endpoint.clear(); record.video_source_id.clear(); record.audio_source_id.clear(); validate_media_stream_request(&record) diff --git a/packages/cultnet-rs/src/media_stream_wire.rs b/packages/cultnet-rs/src/media_stream_wire.rs index fb1b1f8d..9b7fbc1b 100644 --- a/packages/cultnet-rs/src/media_stream_wire.rs +++ b/packages/cultnet-rs/src/media_stream_wire.rs @@ -35,9 +35,14 @@ use crate::media_stream_contracts::{ GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, }; -/// The CultNet channel media rides. +/// The CultNet channel video and receiver feedback ride. pub const GAMECULT_MEDIA_CHANNEL: &str = "media"; +/// The CultNet channel audio rides. Audio has its own channel so one session +/// can carry it reliably with a short expiry while video stays lossy under +/// parity; the RUDP transport profile owns each channel's delivery. +pub const GAMECULT_MEDIA_AUDIO_CHANNEL: &str = "audio"; + /// One media record on its way somewhere. #[derive(Clone, Debug, PartialEq, Eq)] pub enum GameCultMediaWireRecord { diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index b6a8fe95..f5a31e76 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -2187,6 +2187,17 @@ pub fn create_rudp_transport_profile( max_pending_reliable_packets: options.max_pending_reliable_packets, reliable_expire_after_ms: options.media_reliable_expire_after_ms, }, + // Audio beside lossy video on one session: reliable, but a packet + // older than the media expiry is not worth a retransmit. + CultNetTransportChannel { + channel_id: "audio".to_string(), + delivery: CultNetTransportDelivery::Reliable, + ordering: CultNetTransportOrdering::Unordered, + max_payload_bytes: options.max_payload_bytes, + max_fragment_bytes: options.max_fragment_bytes, + max_pending_reliable_packets: options.max_pending_reliable_packets, + reliable_expire_after_ms: options.media_reliable_expire_after_ms, + }, ], }], } @@ -2352,7 +2363,7 @@ fn channel_send_options( // The one channel whose delivery a caller chooses. Media that has gone // stale is worth dropping, not retransmitting: a reliable media channel // under loss adds load exactly when the link has least to give. - "media" => CultNetRudpSendOptions { + "media" | "audio" => CultNetRudpSendOptions { reliable: matches!(delivery, CultNetTransportDelivery::Reliable), ordered: false, sequenced: false, diff --git a/packages/cultnet-rs/tests/cultnet.rs b/packages/cultnet-rs/tests/cultnet.rs index 92401305..b3adc7f4 100644 --- a/packages/cultnet-rs/tests/cultnet.rs +++ b/packages/cultnet-rs/tests/cultnet.rs @@ -428,6 +428,12 @@ fn rudp_transport_profile_advertises_state_and_realtime_channels() { CultNetTransportOrdering::Unordered, Some(64) ), + ( + "audio", + CultNetTransportDelivery::Reliable, + CultNetTransportOrdering::Unordered, + Some(64) + ), ] ); } @@ -2828,7 +2834,7 @@ fn accept_rudp_session(session: &mut CultNetRudpSession, connection_id: u32) -> } #[test] -fn rudp_profile_expires_only_the_media_channel() -> Result<()> { +fn rudp_profile_expires_only_the_media_channels() -> Result<()> { let socket = std::net::UdpSocket::bind("127.0.0.1:0")?; let peer = socket.local_addr()?; let options = CultNetRudpSocketTransportOptions::client("expiry-profile", socket, peer, 11); @@ -2842,12 +2848,14 @@ fn rudp_profile_expires_only_the_media_channel() -> Result<()> { .map(|channel| (channel.channel_id.as_str(), channel.reliable_expire_after_ms)) .collect::>(); - assert!( - channels.contains(&("media", Some(75))), - "the media channel advertises its expiry so a peer can see it: {channels:?}" - ); + for media_channel in ["media", "audio"] { + assert!( + channels.contains(&(media_channel, Some(75))), + "the {media_channel} channel advertises its expiry so a peer can see it: {channels:?}" + ); + } for (channel_id, expiry) in &channels { - if *channel_id != "media" { + if !matches!(*channel_id, "media" | "audio") { assert_eq!( *expiry, None, "state channel {channel_id} must never expire a reliable send" From 47b9fc4ee3a4824327623d7efc9d7614d96b5912 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 11 Sep 2026 01:21:37 +0200 Subject: [PATCH 092/203] A reader returns "nothing" only when nothing is waiting Both RUDP socket readers returned None after consuming a packet that carried no frame or event for the caller: an acknowledgement, a ping, a fragment that completed nothing yet. A caller that polls once per send therefore read one incoming datagram per outgoing one, and fell behind its peer's acknowledgements as soon as a reliable channel shared the session; every unacknowledged packet was then resent, and the resends starved the sends. Measured on the media path 2026-09-10: 7 video frames in 9 s with reliable audio on, 266 without. Readers now consume no-event packets and keep reading; None means the socket is empty. The client no longer acknowledges unreliable frames. They carry no sequence, so the ACK told the sender nothing and cost it one datagram to read for every one it sent. The fragment-set bound test now asserts eviction, which is what the bound has done since the fuse was cut earlier today; the old assertion was hiding behind a truncated test run. Co-Authored-By: Claude Fable 5.1 --- packages/cultnet-rs/src/rudp.rs | 66 ++++++++++++++------ packages/cultnet-rs/tests/rudp_server_hub.rs | 63 ++++++++++++++++++- 2 files changed, 109 insertions(+), 20 deletions(-) diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index f5a31e76..174739be 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -1538,11 +1538,26 @@ impl CultNetRudpServerHub { Ok(true) } + /// The next event, or `None` only when nothing is waiting on the socket. + /// + /// Packets that carry no event for the caller — acknowledgements, pings, + /// a repeated Connect, a fragment that completes nothing yet — are + /// consumed here and reading continues. Returning `None` after one of + /// those made a caller that polls once per send fall behind its own + /// peers' acknowledgements, and a reliable channel then resent everything. pub fn receive_event_once(&mut self) -> Result> { - if let Some(event) = self.pending_events.pop_front() { - return Ok(Some(event)); + loop { + if let Some(event) = self.pending_events.pop_front() { + return Ok(Some(event)); + } + if !self.receive_packet_once()? { + return Ok(None); + } } + } + /// Reads one datagram and applies it. `false` when the socket is empty. + fn receive_packet_once(&mut self) -> Result { let mut wire = vec![0_u8; 65_535]; let (received, remote_addr) = match self.socket.recv_from(&mut wire) { Ok(value) => value, @@ -1551,7 +1566,7 @@ impl CultNetRudpServerHub { || error.kind() == ErrorKind::TimedOut || error.kind() == ErrorKind::ConnectionReset => { - return Ok(None); + return Ok(false); } Err(error) => return Err(error.into()), }; @@ -1559,7 +1574,7 @@ impl CultNetRudpServerHub { self.stats.bytes_received += received as u64; let packet = decode_rudp_packet(&wire)?; if packet.connection_id != self.connection_id { - return Ok(None); + return Ok(true); } if packet.packet_type == CultNetRudpPacketType::Connect { @@ -1572,7 +1587,7 @@ impl CultNetRudpServerHub { .pending_accept_for_resend(now_ms()) .unwrap_or_else(|| peer.session.create_ack()); self.send_packet(remote_addr, &reply)?; - return Ok(None); + return Ok(true); } if !self.peers.contains_key(&remote_addr) && self.peers.len() >= self.max_peers { return Err(anyhow!("RUDP server hub peer limit reached")); @@ -1613,11 +1628,11 @@ impl CultNetRudpServerHub { } self.pending_events .push_back(CultNetRudpServerEvent::Connected { session: context }); - return Ok(self.pending_events.pop_front()); + return Ok(true); } let Some(peer) = self.peers.get_mut(&remote_addr) else { - return Ok(None); + return Ok(true); }; let result = peer.session.receive(&packet, now_ms())?; let context = peer.context.clone(); @@ -1660,7 +1675,7 @@ impl CultNetRudpServerHub { }); self.peers.remove(&remote_addr); } - Ok(self.pending_events.pop_front()) + Ok(true) } pub fn poll_resends(&mut self) -> Result<()> { @@ -1923,11 +1938,23 @@ impl CultNetRudpSocketTransportConnection { } } + /// The next delivered frame, or `None` only when nothing is waiting on + /// the socket. Acknowledgements, pongs and incomplete fragments are + /// consumed and reading continues; see the hub's `receive_event_once`. pub fn receive_once(&mut self) -> Result> { - if let Some(frame) = self.delivered_frames.pop_front() { - return Ok(Some(frame)); + loop { + if let Some(frame) = self.delivered_frames.pop_front() { + return Ok(Some(frame)); + } + if !self.receive_packet_once()? { + return Ok(None); + } } + } + /// Reads one datagram and applies it. `false` when the socket is empty + /// or the session has been disconnected. + fn receive_packet_once(&mut self) -> Result { let mut wire = vec![0_u8; 65_535]; let (received, remote_addr) = match self.socket.recv_from(&mut wire) { Ok(value) => value, @@ -1936,7 +1963,7 @@ impl CultNetRudpSocketTransportConnection { || error.kind() == ErrorKind::TimedOut || error.kind() == ErrorKind::ConnectionReset => { - return Ok(None); + return Ok(false); } Err(error) => return Err(error.into()), }; @@ -1951,14 +1978,14 @@ impl CultNetRudpSocketTransportConnection { { self.remote_addr = Some(remote_addr); } else { - return Ok(None); + return Ok(true); } } } else { if self.mode == CultNetRudpSocketMode::Server && packet.packet_type != CultNetRudpPacketType::Connect { - return Ok(None); + return Ok(true); } self.remote_addr = Some(remote_addr); } @@ -1968,7 +1995,7 @@ impl CultNetRudpSocketTransportConnection { self.session.reset_peer_state(); let accept = self.session.accept_connect(&packet, now_ms(), Vec::new())?; self.send_packet(&accept)?; - return Ok(None); + return Ok(true); } let result = self.session.receive(&packet, now_ms())?; @@ -1983,7 +2010,7 @@ impl CultNetRudpSocketTransportConnection { } if result.disconnected { self.disconnect_reason = Some(result.disconnect_reason); - return Ok(None); + return Ok(false); } for frame in result.delivered { @@ -1993,13 +2020,14 @@ impl CultNetRudpSocketTransportConnection { }); self.stats.frames_received += 1; } - let frame = self.delivered_frames.pop_front(); - if packet.reliable || packet.packet_type == CultNetRudpPacketType::Accept || frame.is_some() - { + // Only what the sender is waiting on is acknowledged. An unreliable + // frame carries no sequence, so an ACK for it tells the sender + // nothing and costs it one datagram to read for every one it sent. + if packet.reliable || packet.packet_type == CultNetRudpPacketType::Accept { let ack = self.session.create_ack_for_received(packet.sequence); self.send_packet(&ack)?; } - Ok(frame) + Ok(true) } pub fn poll_resends(&mut self) -> Result<()> { diff --git a/packages/cultnet-rs/tests/rudp_server_hub.rs b/packages/cultnet-rs/tests/rudp_server_hub.rs index cb765a33..201cf3b4 100644 --- a/packages/cultnet-rs/tests/rudp_server_hub.rs +++ b/packages/cultnet-rs/tests/rudp_server_hub.rs @@ -399,7 +399,12 @@ fn session_and_hub_reject_configured_memory_bounds() -> Result<()> { )?; let second_set = sender.send_many("schema", vec![5, 6, 7, 8], fragment_options, Some(2))?; receiver.receive(&first_set[0], 2)?; - assert!(receiver.receive(&second_set[0], 2).is_err()); + // The fragment-set bound evicts the stalest incomplete set rather than + // refusing the packet: a receiver that dies at its bound under loss is a + // fuse, not a limit (measured 2026-09-10, see tests/cultnet.rs). + assert_eq!(receiver.fragment_sets_evicted(), 0); + receiver.receive(&second_set[0], 2)?; + assert_eq!(receiver.fragment_sets_evicted(), 1, "the first set was evicted for the second"); let server_socket = socket()?; let server_addr = server_socket.local_addr()?; @@ -511,3 +516,59 @@ fn replay_history_is_bounded_and_idle_hub_sessions_expire() -> Result<()> { assert!(hub.sessions().is_empty()); Ok(()) } + +/// A packet that carries no event — an acknowledgement, a ping — must not +/// make the reader report "nothing waiting" while frames sit behind it. +/// A caller that polls once per send fell behind its peer's acknowledgements +/// this way and a reliable channel resent everything. +#[test] +fn a_reader_does_not_stop_at_a_packet_that_carries_no_event() -> Result<()> { + let mut hub = CultNetRudpServerHub::new(CultNetRudpServerHubOptions::new( + "hub", + socket()?, + CONNECTION_ID, + ))?; + let mut peer = client(hub.local_addr()?)?; + let session = connect(&mut hub, &mut peer, b"evidence")?; + + // Hub side: a ping (reply, no event) ahead of a reliable frame. + peer.ping(b"keepalive".to_vec())?; + peer.send("schema", b"after the ping".to_vec())?; + std::thread::sleep(Duration::from_millis(20)); + match hub.receive_event_once()? { + Some(CultNetRudpServerEvent::Frame { frame, .. }) => { + assert_eq!(frame.payload, b"after the ping"); + } + other => panic!("the first read must reach the frame behind the ping, got {other:?}"), + } + + // Client side: the hub's acknowledgement of that frame is queued ahead of + // two unreliable frames; the client must reach both without a "nothing" + // in between, and must not acknowledge unreliable frames back. + let mut unreliable_hub_options = + CultNetRudpServerHubOptions::new("hub-unreliable", socket()?, CONNECTION_ID); + unreliable_hub_options.media_delivery = Some(CultNetTransportDelivery::Unreliable); + let mut media_hub = CultNetRudpServerHub::new(unreliable_hub_options)?; + let mut media_client = client(media_hub.local_addr()?)?; + let media_session = connect(&mut media_hub, &mut media_client, b"media")?; + media_client.send("schema", b"reliable, will be acked".to_vec())?; + std::thread::sleep(Duration::from_millis(20)); + while media_hub.receive_event_once()?.is_some() {} + media_hub.send(&media_session, "media", b"one".to_vec())?; + media_hub.send(&media_session, "media", b"two".to_vec())?; + std::thread::sleep(Duration::from_millis(20)); + let before = media_hub.stats().bytes_received; + let first = media_client.receive_once()?.expect("the first read reaches the frame behind the ack"); + let second = media_client.receive_once()?.expect("the second frame follows"); + assert_eq!((first.payload, second.payload), (b"one".to_vec(), b"two".to_vec())); + assert!(media_client.receive_once()?.is_none()); + std::thread::sleep(Duration::from_millis(20)); + while media_hub.receive_event_once()?.is_some() {} + assert_eq!( + media_hub.stats().bytes_received, + before, + "unreliable frames are not acknowledged; the hub read nothing back" + ); + let _ = session; + Ok(()) +} From c2a9a6e526390808ef195759056fda0b9a55d8a3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 11 Sep 2026 02:44:17 +0200 Subject: [PATCH 093/203] Windows atomic replace waits out a transient reader A concurrent reader of the store's destination file (another process opening it, an indexer, an antivirus scan) makes MoveFileExW fail with a sharing or access error for as long as it holds the handle. That is transient by construction; treating the first refusal as the outcome killed Muninn serve on 2026-09-11 the moment a status CLI overlapped a write. The replace retries such errors for up to half a second. The test holds the destination open from another thread and expects the replace to outlast it. Co-Authored-By: Claude Fable 5.1 --- packages/cultcache-rs/src/lib.rs | 84 ++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index ba7088e5..36953620 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -2662,23 +2662,77 @@ fn replace_file_atomically(staged: &Path, destination: &Path) -> Result<()> { .encode_wide() .chain(std::iter::once(0)) .collect::>(); - let success = unsafe { - MoveFileExW( - staged_wide.as_ptr(), - destination_wide.as_ptr(), - MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH, - ) - }; - if success == 0 { - return Err(std::io::Error::last_os_error()).with_context(|| { - format!( - "failed to atomically replace {} with {}", - destination.display(), - staged.display() + // A concurrent reader of the destination (another process opening the + // store, an indexer, an antivirus scan) makes the replace fail with a + // sharing or access error for as long as it holds the handle. That is + // transient by construction; a daemon that treats it as fatal dies on + // the first overlap (Muninn serve did, 2026-09-11). Retry briefly. + const ERROR_ACCESS_DENIED: i32 = 5; + const ERROR_SHARING_VIOLATION: i32 = 32; + const ERROR_LOCK_VIOLATION: i32 = 33; + const ATTEMPTS: u32 = 25; + const BACKOFF: std::time::Duration = std::time::Duration::from_millis(20); + let mut attempt = 0; + loop { + let success = unsafe { + MoveFileExW( + staged_wide.as_ptr(), + destination_wide.as_ptr(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH, ) - }); + }; + if success != 0 { + return Ok(()); + } + let error = std::io::Error::last_os_error(); + attempt += 1; + let transient = matches!( + error.raw_os_error(), + Some(ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION) + ); + if !transient || attempt >= ATTEMPTS { + return Err(error).with_context(|| { + format!( + "failed to atomically replace {} with {} after {attempt} attempt(s)", + destination.display(), + staged.display() + ) + }); + } + std::thread::sleep(BACKOFF); + } +} + +#[cfg(all(test, windows))] +mod windows_replace_tests { + use super::replace_file_atomically; + use std::os::windows::fs::OpenOptionsExt; + + /// A reader holding the destination open with no sharing makes the + /// replace fail for as long as it holds on; it must succeed once the + /// reader lets go, not report the first refusal as the outcome. + #[test] + fn a_replace_waits_out_a_reader_that_holds_the_destination() { + let dir = std::env::temp_dir().join(format!("cultcache-replace-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let destination = dir.join("store.cc"); + let staged = dir.join("store.cc.tmp"); + std::fs::write(&destination, b"old").unwrap(); + std::fs::write(&staged, b"new").unwrap(); + let held = std::fs::OpenOptions::new() + .read(true) + .share_mode(0) + .open(&destination) + .unwrap(); + let releaser = std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(120)); + drop(held); + }); + replace_file_atomically(&staged, &destination).expect("the replace outlasts the reader"); + releaser.join().unwrap(); + assert_eq!(std::fs::read(&destination).unwrap(), b"new"); + let _ = std::fs::remove_dir_all(&dir); } - Ok(()) } #[cfg(unix)] From e9e9c60b0f537783d6c109713cf66fabd38af453 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 13 Sep 2026 14:10:10 +0200 Subject: [PATCH 094/203] REC-102: cut stale generated mirrors and a dead maintenance receipt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deletes four confirmed-dead tracked files: - CultLib.txt, src/GameCult.Caching/GameCult.Caching.txt, src/GameCult.Unity/Assets/UI/UI.txt: concatenated snapshot copies of live source, carrying stale E: drive provenance and obsolete dependency versions. Live source under src/ remains the owner. - tests/GameCult.Networking.Tests/lcov.info: tracked coverage output. .vscode/tasks.json:14 still generates it fresh via "/p:CoverletOutput=./lcov.info" on every coverage run, so tracking the stale committed copy in git is the liability, not the generation step. - .voidbot/status/void-memory-maintenance.json: a one-time "starting" receipt (added whole in 4813570, never touched again) naming a dead E:\Projects\CultLib path and an unimplemented repo_face_heartbeat launcher. VoidBot's actual heartbeat state lives at .voidbot/status/repo-face-heartbeats.{cc,json} (see packages/config/src/index.ts REPO_FACE_HEARTBEAT_STATE_PATH) — a different file entirely; nothing reads this one. Kill attempts: - git grep -n for each of "CultLib\.txt", "GameCult\.Caching\.txt", "UI\.txt", "lcov\.info" across origin/main, excluding each file's own path: only .voidbot birth/distiller snapshots (auto-generated onboarding file-listing catalogs, not functional readers) and the one real lcov.info producer in .vscode/tasks.json. - rg across all of F:/Projects (excluding target/node_modules/.git): gamecult-site's docs/research/gamecult-org-repo-ledger.json lists "CultLib.txt" as a filename in a static historical repo-file inventory (not a functional consumer); Aetheria/docs/cultcache-migration-cut.md cites GameCult.Caching.txt:157 as an evidence line number in a migration-history note (stale citation after this cut, not a functional break — flagged for the record, not fixed here since that doc is out of scope). - voidbot MCP search for void-memory-maintenance.json / repo_face_heartbeat found only the unrelated repo-face-heartbeats state path, confirming no reader. Collision check: git diff --stat origin/main against each of the other 4 CultLib worktrees (codex/recover-aetheria-authority, codex/delvehold-isosurface, claude/media-stream-catalog, and the two detached-HEAD worktrees, both confirmed ancestors of origin/main) over these five paths: all empty, no in-flight work touches them. REC-102 Co-Authored-By: Claude Sonnet 5 --- .voidbot/status/void-memory-maintenance.json | 7 - CultLib.txt | 132 - src/GameCult.Caching/GameCult.Caching.txt | 1213 -------- src/GameCult.Unity/Assets/UI/UI.txt | 2917 ------------------ tests/GameCult.Networking.Tests/lcov.info | 2537 --------------- 5 files changed, 6806 deletions(-) delete mode 100644 .voidbot/status/void-memory-maintenance.json delete mode 100644 CultLib.txt delete mode 100644 src/GameCult.Caching/GameCult.Caching.txt delete mode 100644 src/GameCult.Unity/Assets/UI/UI.txt delete mode 100644 tests/GameCult.Networking.Tests/lcov.info diff --git a/.voidbot/status/void-memory-maintenance.json b/.voidbot/status/void-memory-maintenance.json deleted file mode 100644 index 0d0b876a..00000000 --- a/.voidbot/status/void-memory-maintenance.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "status": "starting", - "startedAt": "2026-06-14T19:46:12.715Z", - "stateFile": "E:\\Projects\\CultLib\\.voidbot\\state\\libby.cc", - "launcher": "repo_face_heartbeat", - "reason": "repo_face_stale_short_term_memory" -} diff --git a/CultLib.txt b/CultLib.txt deleted file mode 100644 index 4755ac07..00000000 --- a/CultLib.txt +++ /dev/null @@ -1,132 +0,0 @@ - - -// E:\Projects\CultLib\src\GameCult.Caching\GameCult.Caching.csproj - - - - CultCache: Concurrent polymorphic cache with modular backing stores for .NET - - - - - - - - - - -// E:\Projects\CultLib\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj - - - - File-based MessagePack backing store for CultCache - - - - - - - - - -// E:\Projects\CultLib\src\GameCult.Caching.MessagePack.Generator\GameCult.Caching.MessagePack.Generator.csproj - - - - - - true - enable - true - false - - - Source generator that emits MessagePack formatters for DatabaseEntry subclasses. - MessagePack;SourceGenerator;DatabaseEntry - - - - - - - - - - - - - - - - -// E:\Projects\CultLib\src\GameCult.Logging\GameCult.Logging.csproj - - - - Simple pluggable logging interface for CultLib - - - -// E:\Projects\CultLib\src\GameCult.Logging.Unity\GameCult.Logging.Unity.csproj - - - - Unity integration for GameCult.Logging - - - - - - - - -// E:\Projects\CultLib\src\GameCult.Networking\GameCult.Networking.csproj - - - - CultNet: Secure, high-performance messaging for games/servers - - - - - - - - - - - - - -// E:\Projects\CultLib\tests\GameCult.Networking.Tests\GameCult.Networking.Tests.csproj - - - - - net8.0 - false - GameCult.Networking.Tests - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - all - - - - diff --git a/src/GameCult.Caching/GameCult.Caching.txt b/src/GameCult.Caching/GameCult.Caching.txt deleted file mode 100644 index ab959478..00000000 --- a/src/GameCult.Caching/GameCult.Caching.txt +++ /dev/null @@ -1,1213 +0,0 @@ - - -// E:\Projects\CultLib\src\GameCult.Caching\CultCache.cs - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using ConcurrentCollections; -using GameCult.Logging; -using MessagePack; -using R3; - -namespace GameCult.Caching -{ - ///

    - /// Specifies that the decorated class contains global settings. - /// Use this attribute to mark classes intended to hold application-wide configuration or settings. - /// - [AttributeUsage(AttributeTargets.Class)] - public class GlobalSettingsAttribute : Attribute { } - - /// - /// Represents an in-memory and persistent cache for objects, supporting global entries, - /// named entries, generic field indexes, and multiple backing stores. Provides thread-safe operations for adding, - /// retrieving, indexing, and removing entries, with support for event notifications and logging. - /// - /// - /// - /// Features: - /// - /// Supports multiple s for persistence and synchronization. - /// Handles global settings entries via . - /// Provides fast lookup by ID, name (for ), and indexed fields. - /// Allows registering generic indexes for any public field of a type. - /// Thread-safe via and . - /// Notifies on entry updates via event. - /// Supports both synchronous and asynchronous add operations. - /// Integrates with for error and event logging. - /// - /// - /// - /// Usage: - /// - /// var cache = new CultCache(); - /// cache.RegisterIndex<PlayerData>("Email"); - /// cache.AddAsync(new PlayerData { ... }).GetAwaiter().GetResult(); - /// var player = cache.GetByIndex<PlayerData>("Email", "user@example.com"); - /// - /// - /// - public class CultCache : IDisposable - { - private readonly object addLock = new object(); // Retained for synchronous Add compatibility - - /// - /// Occurs when a is updated. - /// The event provides the old and new instances. - /// - /// - /// The first parameter is the previous entry, and the second parameter is the updated entry. - /// The first parameter will be null if the entry is newly added, - /// and the second parameter will be null if the entry is deleted. - /// - public event Action? OnUpdate; - - private readonly List _backingStores = new(); - private readonly ConcurrentDictionary _typeStores = new(); - private readonly ConcurrentDictionary _storeTypes = new(); - private readonly ConcurrentDictionary _entries = new(); - private readonly ConcurrentDictionary _globals = new(); - private readonly ConcurrentDictionary> _types = new(); - private readonly ConcurrentDictionary> _nameToIdMap = new(); - // Generic indexes: Map (Type, FieldName) -> (Value -> ID). Allows registering indexes for any field - private readonly ConcurrentDictionary<(Type Type, string FieldName), ConcurrentDictionary> _indexes = new(); - // Cache getters per type (list of (field, getter) for that type only). Avoids scanning all indexes. - private readonly ConcurrentDictionary Getter)>> _typeToGetters = new(); - private ILogger _logger = new NullLogger(); - - /// - /// Gets or sets the instance used for logging within the cache. - /// If set to null, a will be used as a fallback. - /// - public ILogger Logger - { - get => _logger; - set => _logger = value ?? new NullLogger(); - } - - /// - /// Gets all objects currently stored in the cache. - /// - public IEnumerable AllEntries => _entries.Values; - - /// - /// Initializes a new instance of the class. - /// Sets up the cache by registering all child classes of . - /// For each child type: - /// - /// Adds a concurrent hash set for caching entries of that type. - /// If the type is marked with , adds it to the global settings and creates an instance. - /// If the type implements , initializes a name-to-ID mapping dictionary. - /// - /// Also sets the static cache reference in . - /// - public CultCache() - { - DatabaseLinkBase.Cache = this; - - foreach (var type in typeof(DatabaseEntry).GetAllChildClasses()) - { - _types.TryAdd(type, new ConcurrentHashSet()); - if (type.GetCustomAttribute() != null) - { - _globals.TryAdd(type, null); - try - { - var global = (DatabaseEntry)Activator.CreateInstance(type)!; - AddAsync(global).GetAwaiter().GetResult(); - _globals[type] = global; // Update after successful add - } - catch (Exception ex) - { - Logger.LogError($"Failed to initialize global {type.Name}: {ex.Message}"); - _globals[type] = null; - } - } - // Initialize name-to-ID map for INamedEntry types - if (typeof(INamedEntry).IsAssignableFrom(type)) - { - _nameToIdMap.TryAdd(type, new ConcurrentDictionary()); - } - } - } - - /// - /// Adds a backing store to the cache system and configures domain type associations and event subscriptions. - /// - /// The instance to add. - /// - /// Optional array of objects representing domain types associated with the backing store. - /// If provided, the store is mapped to these types; otherwise, the store is added to the general backing store list and subscribes to existing stores. - /// - /// - /// - /// If no domain types are specified, the store subscribes to all existing backing stores to receive their updates, - /// ensuring synchronization across stores, with the first store added acting as the primary source of truth. - /// - /// - /// Specifying domain types allows for targeted storage and retrieval of specific types, - /// for example, using a database store for user data and a file-based store for configuration data or game assets. - /// - /// - public void AddBackingStore(CacheBackingStore store, params Type[] domain) - { - if (domain.Length > 0) - { - _storeTypes.TryAdd(store, domain); - foreach (var t in domain) _typeStores.TryAdd(t, store); - } - else - { - foreach (var existingStore in _backingStores) store.SubscribeTo(existingStore); - _backingStores.Add(store); - } - store.EntryAdded.Subscribe(async entry => - { - try // Try-catch logs without swallowing. Added to EntryDeleted for consistency (even though sync) - { - await AddAsync(entry, store); - OnUpdate?.Invoke(null, entry); - } - catch (Exception ex) - { - Logger.LogError($"EntryAdded processing failed for {entry?.ID}: {ex.Message}"); - } - }); - store.EntryUpdated.Subscribe(async entry => - { - try - { - await AddAsync(entry, store); - OnUpdate?.Invoke(entry, entry); - } - catch (Exception ex) - { - Logger.LogError($"EntryUpdated processing failed for {entry?.ID}: {ex.Message}"); - } - }); - store.EntryDeleted.Subscribe(entry => - { - try - { - Remove(entry, store); - OnUpdate?.Invoke(entry, null); - } - catch (Exception ex) - { - Logger.LogError($"EntryDeleted processing failed for {entry?.ID}: {ex.Message}"); - } - }); - store.Logger = Logger; - } - - /// - /// Asynchronously pulls all data from the backing stores. - /// Initiates parallel tasks to execute the PullAll method on each backing store and store type, - /// and waits for all tasks to complete. - /// - /// A task that represents the asynchronous operation. - public async Task PullAllBackingStoresAsync() - { - var tasks = _backingStores.Select(store => Task.Run(store.PullAll)) - .Concat(_storeTypes.Keys.Select(store => Task.Run(store.PullAll))) - .ToArray(); - await Task.WhenAll(tasks); - } - - /// - /// Adds a to the cache asynchronously, updating all relevant maps and indexes. - /// If the entry already exists, cleans up old mappings before updating. - /// Also persists the entry to the backing store if available. - /// - /// The to add or update in the cache. - /// - /// The source from which the entry originated, used internally to avoid redundant writes. - /// Optional; defaults to null. - /// - /// A representing the asynchronous operation. - public async Task AddAsync(DatabaseEntry entry, CacheBackingStore? source = null) - { - if (entry == null) return; - - var type = entry.GetType(); - - // Pre-fetch existing to detect updates (for map/index cleanup) - if (!_entries.TryGetValue(entry.ID, out var existing)) existing = null; - - // Cleanup old maps/indexes BEFORE updating _entries - if (existing != null) - { - // For name map (if updating existing) - if (existing is INamedEntry existingNamed && _nameToIdMap.TryGetValue(type, out var nameMap)) - { - nameMap.TryRemove(existingNamed.EntryName, out _); - } - - // For generic indexes (if registered) - if (_typeToGetters.TryGetValue(type, out var getters)) - { - foreach (var (fieldName, getter) in getters) - { - var oldValue = getter(existing); - if (!string.IsNullOrEmpty(oldValue)) - { - var indexKey = (type, fieldName); - _indexes[indexKey].TryRemove(oldValue, out _); - } - } - } - - // For type sets - _types[existing.GetType()].TryRemove(existing); - foreach (var parentType in existing.GetType().GetParentTypes()) - { - if (_types.TryGetValue(parentType, out var typeSet)) - typeSet.TryRemove(existing); - } - } - - _entries[entry.ID] = entry; - _types[type].Add(entry); // Assumes exists; throws if not (edge case) - foreach (var parentType in type.GetParentTypes()) - { - if (_types.TryGetValue(parentType, out var typeSet)) - typeSet.Add(entry); - } - - if (_globals.ContainsKey(type)) - { - _globals.AddOrUpdate(type, entry, (_, old) => entry); - } - - // Add new values (always, for both new and updates) - // Update name-to-ID map if INamedEntry - if (entry is INamedEntry namedEntry && _nameToIdMap.TryGetValue(type, out var nameMap2)) - { - nameMap2[namedEntry.EntryName] = entry.ID; - } - - // Update generic indexes if registered for this type - if (_typeToGetters.TryGetValue(type, out var getters2)) - { - foreach (var (fieldName, getter) in getters2) - { - var value = getter(entry); - if (!string.IsNullOrEmpty(value)) - { - var indexKey = (type, fieldName); - _indexes[indexKey][value] = entry.ID; - } - } - } - - // Async file write - try - { - if (_typeStores.TryGetValue(type, out var store)) - await Task.Run(() => store.Push(entry)); - else if (_backingStores.Any() && _backingStores.First() != source) - await Task.Run(() => _backingStores.First().Push(entry)); - } - catch (Exception ex) - { - // Log error but don't revert cache update to ensure consistency - Logger.LogError($"Failed to persist entry {entry.ID}: {ex.Message}"); - } - } - - /// - /// Determines whether the specified is a global settings entry. - /// - /// The to check. - /// true if the entry is global; otherwise, false. - public bool IsGlobal(DatabaseEntry entry) => _globals.ContainsKey(entry.GetType()); - - /// - /// Adds all objects from the specified collection to the cache asynchronously. - /// - /// The collection of objects to add. - /// - /// The source from which the entries originated, used to avoid redundant writes. - /// Optional; defaults to null. - /// - /// A representing the asynchronous operation. - public Task AddAllAsync(IEnumerable entries, CacheBackingStore? source = null) - { - var tasks = entries.Select(entry => AddAsync(entry, source)).ToArray(); - return Task.WhenAll(tasks); - } - - /// - /// Retrieves a from the cache by its unique identifier. - /// - /// The unique identifier of the entry to retrieve. - /// The associated with the specified , or null if not found. - public DatabaseEntry Get(Guid guid) - { - _entries.TryGetValue(guid, out var entry); - return entry; - } - - /// - /// Retrieves a of type from the cache by its unique identifier. - /// - /// The type of to retrieve. - /// The unique identifier of the entry to retrieve. - /// The entry of type associated with the specified , or null if not found or not of type . - public T? Get(Guid guid) where T : DatabaseEntry - { - return Get(guid) as T; - } - - /// - /// Retrieves the global instance for the specified type, if it exists. - /// - /// The type of the global entry to retrieve. - /// The global instance associated with the specified type, or null if not found. - public DatabaseEntry? GetGlobal(Type type) - { - _globals.TryGetValue(type, out var entry); - return entry; - } - - /// - /// Retrieves the global instance of type , if it exists. - /// - /// The type of the global entry to retrieve. - /// The global entry of type , or null if not found. - public T? GetGlobal() where T : DatabaseEntry - { - return GetGlobal(typeof(T)) as T; - } - - /// - /// Retrieves all objects of the specified type from the cache. - /// - /// The type of to retrieve. - /// An enumerable collection of objects of the specified type. - public IEnumerable GetAll(Type type) - { - return _types.TryGetValue(type, out var entries) ? entries : Enumerable.Empty(); - } - - /// - /// Retrieves all objects of type from the cache. - /// - /// The type of to retrieve. - /// An enumerable collection of objects from the cache. - public IEnumerable GetAll() where T : DatabaseEntry - { - return _types.TryGetValue(typeof(T), out var entries) ? entries.Cast() : Enumerable.Empty(); - } - - /// - /// Retrieves the unique identifier () of an entry of type by its name. - /// - /// The type of that implements . - /// The name of the entry to look up. - /// The of the entry if found; otherwise, null. - public Guid? GetIdByName(string name) where T : DatabaseEntry, INamedEntry - { - var type = typeof(T); - if (_nameToIdMap.TryGetValue(type, out var nameMap) && nameMap.TryGetValue(name, out var id)) - return id; - return null; - } - - /// - /// Retrieves an entry of type by its name from the cache. - /// - /// The type of that implements . - /// The name of the entry to retrieve. - /// The entry of type if found; otherwise, null. - public T? GetByName(string name) where T : DatabaseEntry, INamedEntry - { - return GetIdByName(name) is { } id ? Get(id) : null; - } - - /// - /// Registers an index for a public member (field or property) of the specified type. - /// This enables fast lookup of entries by the value of the indexed member. - /// Call this method once per member to be indexed. - /// - /// The type of for which to register the index. - /// The name of the public member to index. - /// Thrown if the specified member does not exist on the type. - public void RegisterIndex(string memberName) where T : DatabaseEntry - { - var type = typeof(T); - var key = (type, memberName); - if (_indexes.ContainsKey(key)) - return; // Already registered; ignore - - _indexes[key] = new ConcurrentDictionary(); - - // Cache a getter func for perf. - if (type.GetField(memberName) is { } field) - { - var getter = (Func)(entry => field.GetValue(entry)?.ToString() ?? string.Empty); - _typeToGetters.AddOrUpdate(type, - _ => new List<(string, Func)> { (memberName, getter) }, - (_, list) => { list.Add((memberName, getter)); return list; }); - } - else if (type.GetProperty(memberName) is { } property) - { - var getter = (Func)(entry => property.GetValue(entry)?.ToString() ?? string.Empty); - _typeToGetters.AddOrUpdate(type, - _ => new List<(string, Func)> { (memberName, getter) }, - (_, list) => { list.Add((memberName, getter)); return list; }); - } - else - { - throw new ArgumentException($"No public field or property '{memberName}' on {type.Name}"); - } - } - - /// - /// Retrieves the unique identifier () of an entry of type by the value of an indexed field. - /// - /// The type of for which the index is registered. - /// The name of the indexed field. - /// The value of the indexed field to look up. - /// The of the entry if found; otherwise, null. - public Guid? GetIdByIndex(string fieldName, string value) where T : DatabaseEntry - { - var key = (typeof(T), fieldName); - if (_indexes.TryGetValue(key, out var indexMap) && indexMap.TryGetValue(value, out var id)) - return id; - return null; - } - - /// - /// Retrieves an entry of type by the value of an indexed field from the cache. - /// - /// The type of for which the index is registered. - /// The name of the indexed field. - /// The value of the indexed field to look up. - /// The entry of type if found; otherwise, null. - public T? GetByIndex(string fieldName, string value) where T : DatabaseEntry - { - return GetIdByIndex(fieldName, value) is { } id ? Get(id) : null; - } - - /// - /// Removes the specified from the cache, including all associated indexes, type stores, - /// global references, parent type sets, and name-to-ID mappings. - /// - /// The to remove from the cache. - /// - /// The to exclude from deletion. Used internally to avoid redundant deletions when the entry originates from a specific store. - /// - public void Remove(DatabaseEntry entry, CacheBackingStore? source = null) - { - var type = entry.GetType(); - if (!_entries.TryRemove(entry.ID, out _)) return; - - // Clean up type sets - _types[type].TryRemove(entry); - foreach (var parentType in type.GetParentTypes()) - { - if (_types.TryGetValue(parentType, out var typeSet)) - typeSet.TryRemove(entry); - } - - // Clean up global if applicable - if (_globals.TryGetValue(type, out var global) && global != null && global.Equals(entry)) _globals[type] = null!; - - // Propagate delete to backing stores (except source) - if (_typeStores.TryGetValue(type, out var typeStore)) - typeStore.Delete(entry); - else - { - foreach (var store in _backingStores) - { - if (store != source) - store.Delete(entry); - } - } - - // Clean up name-to-ID map if INamedEntry - if (entry is INamedEntry namedEntry && _nameToIdMap.TryGetValue(type, out var nameMap)) - { - nameMap.TryRemove(namedEntry.EntryName, out _); - } - - // Clean up generic indexes if registered for this type - if (_typeToGetters.TryGetValue(type, out var getters)) - { - foreach (var (fieldName, getter) in getters) - { - var value = getter(entry); - if (!string.IsNullOrEmpty(value)) - { - var indexKey = (type, fieldName); - _indexes[indexKey].TryRemove(value, out _); - } - } - } - } - - /// - public void Dispose() - { - foreach (var store in _backingStores) - if (store is IDisposable d) d.Dispose(); - foreach (var store in _storeTypes.Keys) - if (store is IDisposable d) d.Dispose(); - } - } - - /// - /// Represents a backing store that supports observing real-time changes. - /// - public interface IRealtimeBackingStore - { - /// - /// Begins observing real-time changes in the backing store and triggers appropriate events when changes occur. - /// - public void ObserveChanges(); - } - - /// - /// Represents an abstract backing store for caching objects. - /// Provides mechanisms for pulling, pushing, deleting, and synchronizing entries, as well as event notifications for entry changes. - /// - public abstract class CacheBackingStore : IDisposable - { - private ILogger _logger = new NullLogger(); - - /// - /// Gets or sets the instance used for logging within the cache. - /// If set to null, a will be used as a fallback. - /// - public ILogger Logger - { - get => _logger; - set => _logger = value ?? new NullLogger(); - } - - /// - /// Initializes a new instance of the class. - /// Sets up subjects for entry added, deleted, and updated events. - /// - protected CacheBackingStore() - { - EntryAdded = new Subject(); - EntryDeleted = new Subject(); - EntryUpdated = new Subject(); - } - - /// - /// Pulls all entries from the backing store into the cache. - /// Implementations should load all persisted objects and notify via . - /// - public abstract void PullAll(); - /// - /// Persists the specified to the backing store. - /// Implementations should save or update the entry in the underlying storage. - /// - /// The to persist. - public abstract void Push(DatabaseEntry entry); - /// - /// Deletes the specified from the backing store. - /// Implementations should remove the entry from the underlying storage. - /// - /// The to delete. - public abstract void Delete(DatabaseEntry entry); - /// - /// Persists all objects to the backing store. - /// Implementations should save or update all entries in the underlying storage. - /// - /// - /// If true, performs a soft push (implementation-specific); otherwise, performs a full push. - /// - public abstract void PushAll(bool soft = false); - - /// - /// Gets the subject that notifies when a is added to the backing store. - /// - public Subject EntryAdded { get; } - /// - /// Gets the subject that notifies when a is deleted from the backing store. - /// - public Subject EntryDeleted { get; } - /// - /// Gets the subject that notifies when a is updated in the backing store. - /// - public Subject EntryUpdated { get; } - - /// - /// Stores all objects managed by this backing store, keyed by their unique identifier. - /// - protected ConcurrentDictionary Entries = new(); - - /// - /// Subscribes this backing store to the entry events of another , - /// so that added, deleted, and updated entries in the target store are automatically pushed or deleted in this store. - /// - /// The target to subscribe to. - public void SubscribeTo(CacheBackingStore targetStore) - { - targetStore.EntryAdded.Subscribe(Push); - targetStore.EntryDeleted.Subscribe(Delete); - targetStore.EntryUpdated.Subscribe(Push); - } - - /// - public void Dispose() - { - EntryAdded?.Dispose(); - EntryDeleted?.Dispose(); - EntryUpdated?.Dispose(); - } - } - - /// - /// An abstract cache backing store that persists objects as individual files, - /// organizing them by entry type in subdirectories under a root directory. Supports real-time change observation - /// via and provides serialization/deserialization hooks for custom formats. - /// - /// - /// - /// Each entry type is mapped to its own directory, and entries are stored as files with a configurable extension. - /// The class maintains a mapping between file paths and entry IDs for efficient lookup and change tracking. - /// - /// - /// Real-time changes (creation, modification, deletion) to files are observed and propagated to upstream subscribers - /// using Rx subjects and debounced event handling. - /// - /// - /// Subclasses must implement , , and - /// to define the file format. - /// - /// - public abstract class MultiFileBackingStore : CacheBackingStore, IRealtimeBackingStore, IDisposable - { - /// - /// Gets the root directory where all entry files are stored. - /// - public DirectoryInfo RootDirectory { get; } - /// - /// Maps each type to its corresponding subdirectory. - /// - protected readonly Dictionary EntryTypeDirectories = new(); - /// - /// Maps file paths to their corresponding IDs for quick lookup during change detection. - /// - protected readonly ConcurrentDictionary _filePathToIdMap = new(); - private FileSystemWatcher? watcher; - private Subject? _changedSubject; - - /// - /// Serializes a into a byte array for file storage. - /// - /// The to serialize. - /// A byte array representing the serialized entry. - public abstract byte[] Serialize(DatabaseEntry entry); - /// - /// Deserializes a byte array into a object. - /// - /// The byte array containing the serialized entry. - /// The deserialized object. - public abstract DatabaseEntry Deserialize(byte[] data); - /// - /// Gets the file extension used for storing entries (e.g., ".json", ".msgpack"). - /// - public abstract string Extension { get; } - - /// - /// Initializes a new instance of the class. - /// Creates the root directory if it does not exist and sets up subdirectories for each child type. - /// - /// The path to the root directory for storing entry files. - public MultiFileBackingStore(string path) - { - RootDirectory = new DirectoryInfo(path); - foreach (var type in typeof(DatabaseEntry).GetAllChildClasses()) - { - EntryTypeDirectories[type] = GetDirectoryForType(type); - } - } - - private DirectoryInfo GetDirectoryForType(Type type) - { - var stack = new Stack(); - var t = type; - while (t != null && t != typeof(DatabaseEntry)) - { - stack.Push(t.Name); - t = t.BaseType; - } - stack.Push(RootDirectory.FullName); - // ToArray() copies in LIFO order (top/first: root, then parents, deepest child last) - // Ensures Path.Combine yields: Root\Parent\...\Child - return Directory.CreateDirectory(Path.Combine(stack.ToArray())); - } - - /// - /// Loads all objects from individual files in the root directory and its subdirectories, - /// organized by entry type. Each file is deserialized using , - /// added to the local dictionary, and notifies upstream via . - /// Skips files that cannot be deserialized, logging errors without failing the entire operation. - /// Only processes files matching the . - /// - public override void PullAll() - { - if (!RootDirectory.Exists) return; - - foreach (var directory in EntryTypeDirectories.Values) - { - foreach (var file in directory.EnumerateFiles($"*.{Extension}")) - { - try - { - var entry = Deserialize(File.ReadAllBytes(file.FullName)); - Entries[entry.ID] = entry; - EntryAdded.OnNext(entry); - _filePathToIdMap[file.FullName] = entry.ID; - } - catch (Exception ex) - { - Logger.LogError($"Failed to pull entry from {file.FullName}: {ex.Message}"); - } - } - } - } - - private string GetFileName(DatabaseEntry entry) - { - string name = entry is INamedEntry namedEntry ? namedEntry.EntryName : entry.ID.ToString(); - // Sanitize to prevent invalid chars/path traversal - foreach (char invalid in Path.GetInvalidFileNameChars()) - name = name.Replace(invalid, '_'); - return $"{name}.{Extension}"; - } - - /// - /// Persists the specified to a file in the appropriate type subdirectory. - /// The file name is generated using , which prefers the entry name if available. - /// The entry is serialized via and written to disk. - /// Updates the local dictionary and the for change tracking. - /// Logs errors if the write fails but re-throws to allow caller handling. - /// - /// The to persist to a file. - public override void Push(DatabaseEntry entry) - { - var type = entry.GetType(); - Entries[entry.ID] = entry; - var directory = EntryTypeDirectories[type]; - var filePath = Path.Combine(directory.FullName, GetFileName(entry)); - File.WriteAllBytes(filePath, Serialize(entry)); - _filePathToIdMap[filePath] = entry.ID; - } - - /// - /// Removes the specified from the local dictionary - /// and deletes its corresponding file in the type subdirectory. - /// Updates the and notifies upstream via . - /// File deletion failures are logged but do not prevent local removal or notification. - /// - /// The to delete from storage. - public override void Delete(DatabaseEntry entry) - { - if (Entries.ContainsKey(entry.ID)) - { - var type = entry.GetType(); - Entries.TryRemove(entry.ID, out _); - var directory = EntryTypeDirectories[type]; - var filePath = Path.Combine(directory.FullName, GetFileName(entry)); - _filePathToIdMap.TryRemove(filePath, out _); - var file = new FileInfo(filePath); - if (file.Exists) - file.Delete(); - } - } - - /// - /// Persists all entries in to their respective files by invoking for each. - /// The parameter is ignored in this implementation, as all pushes are full writes. - /// Errors during individual pushes are logged but do not halt the process. - /// Suitable for batch synchronization after bulk changes. - /// - /// If true, performs a soft push (not supported; treated as full push). - public override void PushAll(bool soft = false) - { - foreach (var entry in Entries.Values.ToArray()) Push(entry); - } - - /// - /// Starts monitoring the root directory and its subdirectories for file system changes related to entry files. - /// Initializes a with filters for creation, modification, and deletion events on files matching . - /// Uses debouncing for change events to handle bursty writes (100ms delay via and R3). - /// Disposes any existing watcher before creating a new one to prevent duplicates. - /// Logs a warning if the root directory does not exist and skips observation. - /// Logs info when observation starts successfully. - /// Call this method to enable real-time synchronization with external file modifications. - /// - public void ObserveChanges() - { - if (watcher != null) // Guard against re-creation - Dispose(); - - watcher = new FileSystemWatcher(RootDirectory.FullName, $"*.{Extension}") - { - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, - EnableRaisingEvents = true, - IncludeSubdirectories = true - }; - - // Use R3 to throttle events if file writes are bursty - _changedSubject = new Subject(); // For paths - _changedSubject.Debounce(TimeSpan.FromMilliseconds(100)).Subscribe(HandleFileChange); - - watcher.Changed += (sender, args) => - { - _changedSubject.OnNext(args.FullPath); // Debounced handling - }; - watcher.Created += (sender, args) => - { - HandleFileCreation(args.FullPath); - }; - watcher.Deleted += (sender, args) => - { - HandleFileRemoval(args.FullPath); - }; - } - - private void HandleFileCreation(string fullPath) - { - try - { - var entry = Deserialize(File.ReadAllBytes(fullPath)); - Entries[entry.ID] = entry; - _filePathToIdMap[fullPath] = entry.ID; - EntryAdded.OnNext(entry); // Upstream notify - } - catch (Exception ex) - { - Logger.LogError($"Failed to handle creation of {fullPath}: {ex.Message}"); - } - } - - private void HandleFileChange(string fullPath) - { - try - { - var entry = Deserialize(File.ReadAllBytes(fullPath)); - Entries[entry.ID] = entry; - _filePathToIdMap[fullPath] = entry.ID; - EntryUpdated.OnNext(entry); // Upstream notify - } - catch (Exception ex) - { - Logger.LogError($"Failed to handle change in {fullPath}: {ex.Message}"); - } - } - - private void HandleFileRemoval(string fullPath) - { - try - { - // External deletion detected—handle without deserializing - if (_filePathToIdMap.TryGetValue(fullPath, out var id)) - { - _filePathToIdMap.TryRemove(fullPath, out _); // Clean map immediately - - // Emit delete if we have the entry locally - if (Entries.TryGetValue(id, out var entry)) - { - Entries.TryRemove(id, out _); // Sync local dict - EntryDeleted.OnNext(entry); - } - else - { - // Fallback: Stub if race/removed already (rare) - var type = GetTypeFromPath(fullPath); // See helper below - var stub = CreateStubEntry(id, type); - EntryDeleted.OnNext(stub); - Logger.LogWarning($"External deletion of {fullPath} (ID: {id}) with no local entry; emitted stub for cleanup."); - } - } - } - catch (Exception ex) - { - Logger.LogError($"Failed to handle deletion of {fullPath}: {ex.Message}"); - } - } - - // Helper: Infer type from path (e.g., based on directory name matching EntryTypeDirectories keys) - private Type GetTypeFromPath(string filePath) - { - var currentDir = new DirectoryInfo(Path.GetDirectoryName(filePath) ?? string.Empty); - // Walk up the directory tree to find matching EntryTypeDirectories (handles sub-subdirs) - while (currentDir is { Exists: true }) - { - var matchingType = EntryTypeDirectories.FirstOrDefault(kvp => kvp.Value.FullName == currentDir.FullName).Key; - if (matchingType != null) - return matchingType; - currentDir = currentDir.Parent; // Walk up (e.g., from "PlayerData/backup" to "PlayerData") - } - return typeof(DatabaseEntry); // Fallback if no match (e.g., manual file) - } - - // Stub creator (minimal; only for rare fallback—extend if Deletion events need more data) - private DatabaseEntry CreateStubEntry(Guid id, Type type) - { - var entry = (DatabaseEntry)Activator.CreateInstance(type); - typeof(DatabaseEntry).GetField("ID")?.SetValue(entry, id); // Set ID via reflection - return entry; - } - - /// - public new void Dispose() - { - base.Dispose(); - watcher?.Dispose(); - _changedSubject?.Dispose(); - } - } - - /// - /// An abstract cache backing store that persists all objects in a single file. - /// Supports serialization of the entire collection and provides hooks for custom binary formats. - /// Suitable for smaller datasets or when atomic updates are preferred over individual file management. - /// - /// - /// - /// All entries are loaded and saved as a single array in the specified file. - /// Changes do not trigger a write, must manually call PushAll to persist. - /// - /// - /// Subclasses must implement and - /// to define the storage format (e.g., JSON array, MessagePack array). - /// - /// - public abstract class SingleFileBackingStore : CacheBackingStore - { - /// - /// Gets the file where all entries are stored. - /// - public FileInfo FileInfo { get; } - - /// - /// Serializes an array of objects into a byte array for single-file storage. - /// - /// The array of objects to serialize. - /// A byte array representing the serialized entries. - public abstract byte[] Serialize(DatabaseEntry[] entries); - /// - /// Deserializes a byte array into an array of objects. - /// - /// The byte array containing the serialized entries. - /// An array of deserialized objects. - public abstract DatabaseEntry[] Deserialize(byte[] data); - - /// - /// Initializes a new instance of the class. - /// - /// The path to the single file for storing all entries. - public SingleFileBackingStore(string filePath) - { - FileInfo = new FileInfo(filePath); - } - - /// - /// Loads all objects from the single storage file by reading its contents, - /// deserializing via into an array, and adding each to the local dictionary. - /// Notifies upstream via for each entry. - /// If the file does not exist or deserialization fails, logs an error and returns without adding entries. - /// Logs the number of loaded entries for debugging. - /// - public override void PullAll() - { - if (!FileInfo.Exists) return; - - foreach (var entry in Deserialize(File.ReadAllBytes(FileInfo.FullName))) - { - Entries[entry.ID] = entry; - EntryAdded.OnNext(entry); - } - } - - /// - /// Adds or updates the specified in the local dictionary. - /// Does not immediately persist to the single file; call to save changes. - /// - /// The to persist. - public override void Push(DatabaseEntry entry) - { - var type = entry.GetType(); - Entries[entry.ID] = entry; - } - - /// - /// Removes the specified from the local dictionary. - /// Does not immediately persist to the single file; call to save changes. - /// - /// The to delete. - public override void Delete(DatabaseEntry entry) - { - if (Entries.TryGetValue(entry.ID, out _)) - { - Entries.TryRemove(entry.ID, out _); - } - } - - /// - /// Serializes all entries in to a byte array using - /// and writes it to the single storage file, overwriting any existing content. - /// The parameter is ignored in this implementation, as saves are always full rewrites. - /// Logs the number of saved entries and errors if the write fails, re-throwing for caller handling. - /// - /// If true, performs a soft push (not supported; treated as full push). - public override void PushAll(bool soft = false) - { - var entriesArray = Entries.Values.ToArray(); - File.WriteAllBytes(FileInfo.FullName, Serialize(entriesArray)); - } - } -} - -// E:\Projects\CultLib\src\GameCult.Caching\DatabaseEntry.cs - -using System; -using MessagePack; - -namespace GameCult.Caching -{ - public interface INamedEntry - { - string EntryName { get; set; } - } - - /// - /// Base abstract class for all database entries in the cache. - /// For optional MessagePack integration, define concrete subclasses with [MessagePackObject] and [Key(n)] on fields. - /// The ID field can be serialized via a custom formatter in MessagePack stores. - /// - public abstract class DatabaseEntry - { - /// - /// Unique identifier for the entry. Handled by custom formatters in MessagePack integration. - /// - public Guid ID = Guid.NewGuid(); - - /// - public override int GetHashCode() - { - return ID.GetHashCode(); - } - - /// - public override bool Equals(object obj) - { - if (obj is DatabaseEntry entry) return entry.ID == ID; - return false; - } - } - - /// - /// Generic link to a in the cache. - /// Value is computed from the cache; serializes only LinkID. - /// - public class DatabaseLink : DatabaseLinkBase where T : DatabaseEntry - { - /// - /// Computed value from the cache (not serialized; regenerated post-deserialize). - /// - public T? Value => Cache.Get(LinkID); - } - - /// - /// Base for database links. LinkID is serialized directly; Value is computed in subclasses. - /// - public class DatabaseLinkBase - { - /// - /// ID of the linked entry. - /// - public Guid LinkID; - - /// - /// Static reference to the cache (not serialized). - /// - public static CultCache? Cache; - } - - // Example of a DatabaseEntry implementation in user code - public class PlayerData : DatabaseEntry, INamedEntry - { - [Key(1)] - public string Email; - - [Key(2)] - public string PasswordHash; - - [Key(3)] - public string Username; - - [IgnoreMember] - public string EntryName - { - get => Username; - set => Username = value; - } - } -} - -// E:\Projects\CultLib\src\GameCult.Caching\ReflectionExtensions.cs - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace GameCult.Caching -{ - public static class ReflectionExtensions - { - private static Dictionary ParentTypes = new Dictionary(); - - public static Type[] GetParentTypes(this Type type) - { - if (ParentTypes.ContainsKey(type)) - return ParentTypes[type]; - return ParentTypes[type] = type.GetParents().ToArray(); - } - - private static IEnumerable GetParents(this Type type) - { - // is there any base type? - if (type == null) - { - yield break; - } - - // return all inherited types - var currentBaseType = type.BaseType; - while (currentBaseType != null) - { - yield return currentBaseType; - currentBaseType = currentBaseType.BaseType; - } - } - - private static Dictionary ChildClasses = new Dictionary(); - public static Type[] GetAllChildClasses(this Type type) - { - if (ChildClasses.ContainsKey(type)) - return ChildClasses[type]; - return ChildClasses[type] = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(ass => ass.GetTypes()).Where(type.IsAssignableFrom).ToArray(); - } - - } -} diff --git a/src/GameCult.Unity/Assets/UI/UI.txt b/src/GameCult.Unity/Assets/UI/UI.txt deleted file mode 100644 index 8789f73a..00000000 --- a/src/GameCult.Unity/Assets/UI/UI.txt +++ /dev/null @@ -1,2917 +0,0 @@ - - -// E:\Projects\CultLib\src\GameCult.Unity\Assets\UI\Attributes.cs - -using System; - -namespace GameCult.Unity.UI -{ - [AttributeUsage(AttributeTargets.All, Inherited = false)] - public class InspectableAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public abstract class PreferredInspectorAttribute : InspectableAttribute { } - - public class InspectableReadOnlyLabelAttribute : PreferredInspectorAttribute { } - - public class InspectableRangedFloatAttribute : PreferredInspectorAttribute - { - public readonly float Min, Max; - - public InspectableRangedFloatAttribute(float min, float max) - { - Min = min; - Max = max; - } - } - - public class InspectableRangedIntAttribute : PreferredInspectorAttribute - { - public readonly int Min, Max; - - public InspectableRangedIntAttribute(int min, int max) - { - Min = min; - Max = max; - } - } - - public class InspectableIncrementIntAttribute : PreferredInspectorAttribute - { - public readonly int Min, Max; - - public InspectableIncrementIntAttribute(int min, int max) - { - Min = min; - Max = max; - } - } -} - -// E:\Projects\CultLib\src\GameCult.Unity\Assets\UI\ColorConversion.cs - -using Unity.Mathematics; -using UnityEngine; -using static Unity.Mathematics.math; - -namespace GameCult.Unity.UI -{ - // Color conversion functions from https://www.chilliant.com/rgb2hsv.html - public static class ColorConversion - { - private static float Epsilon = 1e-10f; - - public static float3 HUEtoRGB(in float h) - { - float R = abs(h * 6 - 3) - 1; - float G = 2 - abs(h * 6 - 2); - float B = 2 - abs(h * 6 - 4); - return saturate(float3(R,G,B)); - } - - public static float3 RGBtoHCV(in float3 rgb) - { - // Based on work by Sam Hocevar and Emil Persson - float4 P = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.yz, 0.0f, -1.0f/3.0f); - float4 Q = (rgb.x < P.x) ? float4(P.xyw, rgb.x) : float4(rgb.x, P.yzx); - float C = Q.x - min(Q.w, Q.y); - float H = abs((Q.w - Q.y) / (6 * C + Epsilon) + Q.z); - return float3(H, C, Q.x); - } - - public static float3 HSVtoRGB(in float3 hsv) - { - float3 rgb = HUEtoRGB(hsv.x); - return ((rgb - 1) * hsv.y + 1) * hsv.z; - } - - public static float3 HSLtoRGB(in float3 hsl) - { - float3 RGB = HUEtoRGB(hsl.x); - float C = (1 - abs(2 * hsl.z - 1)) * hsl.y; - return (RGB - 0.5f) * C + hsl.z; - } - - // The weights of RGB contributions to luminance. - // Should sum to unity. - private static float3 HCYwts = float3(0.299f, 0.587f, 0.114f); - - public static float3 HCYtoRGB(float3 hcy) - { - float3 RGB = HUEtoRGB(hcy.x); - float Z = dot(RGB, HCYwts); - if (hcy.z < Z) - { - hcy.y *= hcy.z / Z; - } - else if (Z < 1) - { - hcy.y *= (1 - hcy.z) / (1 - Z); - } - return (RGB - Z) * hcy.y + hcy.z; - } - - public static float3 RGBtoHSV(in float3 rgb) - { - float3 HCV = RGBtoHCV(rgb); - float S = HCV.y / (HCV.z + Epsilon); - return float3(HCV.x, S, HCV.z); - } - - public static float3 RGBtoHSL(in float3 rgb) - { - float3 HCV = RGBtoHCV(rgb); - float L = HCV.z - HCV.y * 0.5f; - float S = HCV.y / (1 - abs(L * 2 - 1) + Epsilon); - return float3(HCV.x, S, L); - } - - public static float3 RGBtoHCY(in float3 rgb) - { - // Corrected by David Schaeffer - float3 HCV = RGBtoHCV(rgb); - float Y = dot(rgb, HCYwts); - float Z = dot(HUEtoRGB(HCV.x), HCYwts); - if (Y < Z) - { - HCV.y *= Z / (Epsilon + Y); - } - else - { - HCV.y *= (1 - Z) / (Epsilon + 1 - Y); - } - return float3(HCV.x, HCV.y, Y); - } - - public static Color ToColor(this float3 color) => new Color(color.x, color.y, color.z); - public static float3 ToFloat3(this Color color) => new float3(color.r, color.g, color.b); - } -} - -// E:\Projects\CultLib\src\GameCult.Unity\Assets\UI\DisplayOptions.cs - -using TMPro; - -namespace GameCult.Unity.UI -{ - public class DisplayOptions - { - public TextAlignmentOptions? TextAlignment { get; } - public VerticalAlignmentOptions? VerticalAlignment { get; } - public float? LayoutPriority { get; } - public float? LayoutHeight { get; } - public float? TextSize { get; } - public bool Interactable { get; } - public bool PlaceInContext { get; } - - public DisplayOptions(bool placeInContext = true, - bool interactable = true, - float? layoutHeight = null, - float? textSize = null, - float? layoutPriority = null, - VerticalAlignmentOptions? verticalAlignment = null, - TextAlignmentOptions? textAlignment = null) - { - PlaceInContext = placeInContext; - LayoutHeight = layoutHeight; - Interactable = interactable; - TextSize = textSize; - LayoutPriority = layoutPriority; - VerticalAlignment = verticalAlignment; - TextAlignment = textAlignment; - } - } -} - -// E:\Projects\CultLib\src\GameCult.Unity\Assets\UI\Generator.cs - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using GameCult.Unity.UI.Components; -using UnityEngine; -using ZLinq; - -namespace GameCult.Unity.UI -{ - public class Generator : ResolverComponent, IUIContext - { - [SerializeField] private float inspectorLabelPriority = 3; - [SerializeField] private float inspectorFieldPriority = 2; - [SerializeField] private ReflectiveResolver? resolver; - [SerializeField] private RectTransform? contentRoot; - protected event Action? OnPropertyAdded; - protected event Action? RefreshPropertyValues; - - protected List Elements { get; } = new(); - public IUIResolver Resolver => resolver ?? throw new NullReferenceException("Generator doesn't have an assigned resolver."); - public Action? Refresh - { - get => RefreshPropertyValues; - set => RefreshPropertyValues = value; - } - - public RectTransform ContentRoot => contentRoot ?? (RectTransform)transform; - public Canvas Canvas => GetComponentInParent(); - public RectTransform CanvasRoot => (RectTransform)Canvas.transform; - - public void Inspect(object obj, bool inspectableOnly = false, bool recursive = true) - { - var members = obj.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance); - foreach (var member in members) - { - var inspectable = member.GetCustomAttribute(); - if (inspectable == null && inspectableOnly) continue; - var preferred = inspectable as PreferredInspectorAttribute; - switch (member) - { - case FieldInfo field: - { - var type = field.FieldType; - var handler = Resolver.ResolveField(type, preferred); - // If there's a handler for this field and attribute type, use that - if (handler != null) - { - var group = Resolver.Resolve()?.Configure(this); - if (group == null) continue; - Resolver.Resolve

    rTjYLhZjODP&pTjt_%A61Qe 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 Ce3kD2 literal 86016 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb index 60ce5583b3d4e71f5690e7db0dff0e866ca40597..6b4ea8d7de9b9e7d070118828e3537324acfbf9a 100644 GIT binary patch delta 15874 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 delta 13408 zcmZXa2YeJo`^RVZj?1NNNFXGE6haCKNeJl)gc1@Uv;ZOW7E)=Ug`xrRa>rfn&{qUj zMG-4R5dlHEC|Px1f1l5HXMWE!ZD)38W_NGvJ*nZU zv?9||+nKSLhZ&oi!q_GQ;-&NJrp=l%kKq@T&X^Hl%*av}ig*LXi$)IUgYaqSsfEx5 zwX-XreLo%e(ERsD^?l=!q30Gndc@DZTd~2+Mz`PP>mA9Mg!pnW1`L2`FQW{J6|mMY z1VIENT!ID+LP&{;j$j=)3)+Egfb2-d5&`*ZSN;*xmP(VrS{Nun05DUirb#0$XJRCF z9$W@Dz-@33m|`WSfFKYCqCqE+3UWYCP!%V!8K4f-g9hLP%fXXiEm#LOf^A?AH~SeatT}me}KP%p`*lnKp+SODIgp41pUAeFbYfpbHIb30l2^l@Em9Yo54=- z4)_2Z0bhb|!H?jVjuupk$UmS(JkA)j0TG}Oi~|*5I@kl=1s{Rq;1oCuegeOO+khoV z%nSlRD2M_HAPe*WeL*o82_}MS3l1~E0P^@Em9Yo54I!9&0fZi0kF3>PQ>1Ho{x7hDIulVAguQXHzl8n6#s0i%;8_7bSCu=sT8zU4+p?D9#n!E;6cy;)_|A6UT_$k0zZOZz&#M0hK7LAXrh_`L2-v_QU=>&cUe3@edjs*qJWiT^ z5<3dpb#TjqgLfuc2ok^mFg{aD_U{pA40~D@x(|#5b>Mw)HA`l{1OIFpx2`*f%-TSA z1f4-47zj$hL{J50fI47Vgo6z{4pxH~z$UN*9L_edqu_cr6>MOCBA%6FVBJ9>7zj$h z7*Gakz)Ub7EC#E<8t^js8hj5ff$QKFxR+zFFt1z#YY9SfwMyC}J~r3LUI*WSAHh}d z8@L17bv3dMAOUm%d7v0f19L$=u!85oRxRfounGJL z((;U~6f6Qyf#<<1dDJ)~+mhFU?E;^2O*V<0$&=Uxa21%lODq;7gG|sJi~*CuR4@lL zfz4nic*lan2jB?!5_}7O1in3RD}gW&1Cl@n$ODC75GV!XK_!?0=7GgvDR>m9;90N^ zybiX5ec(OtDL4r%-{5c_TmioUr6V>-i1cJ7p14!vb4ZyswhduSMpB@g;!%=!T z33+P3gIsHQ8=zfa1$Y{~2sVJNU=Mh=mj&}VBFDigA>&!-p9K9Y^ld@2e5!zc81sBB z4B(*_57li^`M9k?s;+bOFdw%56rYF!Y`QU~Hw@5o^zcI-l8t^?m(tOZjFTd*Jy2BHhKIyx0% z{sCPJ*VX#?NUAzB=!HO@`^X!|Jbo+$P3S49g~12I%Q% z_pw>f3iK917eT)vXm9APf*uOJP0-ZFH=zgW<+q344n0WI7S;-e9se3wIP^|ICqeHL z^io|9MgcUb#)94O7wZ+!iSL0PqU#>ed!b8oP36DUtSyYuNbZB7R8J5A{kEX}q2Cd7 z0`z|95qf%R!~y7$x~3j`SI{G&4+?q!^daa`lpggn>azFT2E_O1dNdN0AWl2Y_u(I- zSKy6&Zru&O-MW7){B$x~!3Rh`UQbU$d>DFyu2&&HX}Z2GWLu;sm;~VkJrDXrBq-A} zU{Atz1@v3+(O#JRmC)}YPABjYbhVzoAN0q9UI6_GbdBz(A@~$P)A=*z1wI4r1f-96 z=%YdbwC6tN;Xf|;DgWn!4un1-X!4)r+JaNVkFNSJfY^X91x^011Wg5eEokze@~Ggn z;HUKe_0Zpt=KYTdB{(A#5CZ+3pxZ!yFKD_Z&I`IL^aVlFMfRhIz6d>47=laC7Plc3 z8GaHH41xYx&{W}N;7(5szApGX%bK2n3g`m9sQYW7_alB**R!Cnpwr_tZDF%vxCsNE zwb-vHa1J#6qQMP8(|vwZ(AZ$H-=OE}>1kddT?b9iS9s22zeCT{{j^W|1A2k3sYm~0 z7O!Rlb=fW8PC#dP+e6>QK)L-iFZ}J{|3~ms1^@H#-}BJSBK=z+U8NGv%3T39KokW*8&TiJ|e>#gCRNziP1>}0@ zuFV;6#lzoC(1)P&pd0iGs7LXX(5$IEY~JtFJ>BVP{HefRZUf@9p~x3>Jalhpt6l-E zeg%T2VJd{S>3-T#^$|2Ru&wIl%Q*%M+=%3 zpD}{&13lKGLE}h^>;HI<1QUb;=!_?N_$PVjG7mjj$WIL@_wZLhTf{D`^hi+Up{qS~ zjfbA%p{IK2X+ndN(V*#c{fP~j;gO-%L(lZkvpn={4?RcFw3y8mG!0=L?LXZOpakj1xdu6}~EH zDqw@4$^V+5X>r;pXcP1%K?gu@`B!W6|JHvEEEEae6f`wxhoI^4dY6aZEoj=7@4-ds zzR0M7`vgDfx0^NkpA7Fb8?@zkzo6-i4|wQz1x@)6dgwzQ`aMDWBK`YrP2*3758MV_ z9~LxK@Uft&L7xbk{6_>$SN%~zlmBx;(|+R%T~qyxy7Vg{0d@J;LKpUeJ}vm^IpG^Y zM?;_S(BBH09#p;;G%e}pG|lVh4fsJw5P-_vnlc{+7vO(NpE&*pzgv_4ZC$U1|2T9M zxQO&m>*?vK>L)?d7XLEW`udL#E9{z(fV$|qps5Rg_0Tsw^i4t25dAJ_y6SIX7wv8k z4bdIa?)9G<$_V_jm?@ast7^TybvkqccAGviyzk*@T|qV0f^zpfcKf2jLX?1)v%DON%j_7A26hfdBkRHYqZmgoe!rO6OUPB?%>&HwJlkrX&B?PBqc9`O zW^d>K`&@FPA1^NN=t4IX0w-l(ewQ$j3Hz=AJ!WM+%FcM6YFIr!{{^0AO} zI}Hs!r}A<%3&rtSK**)#)rOC5T>~!}FWh-+sZ0ZVfr{kM?wAZ|Rx#l6qZ6R_1+>Uh zp+^vtvE!gVAg`Hwy?OQYTAO~f=ftOVUOi>S_Z`G#b_?lS;0C74gBs+;EAwR!!|R7f zA^K5DdYVQ3_yk0``B7qLdHfPTjv_PPjJq2&9oY=*15|*wh)82#{yeTVot_i$$%R{X zQ4+ikr(X@PjE?vM%;Rx9J{VsPo6mj&61HG1xPt~xW>lPkKj0eK*k(IEwZKkA5kKK5 zco`?(#UDw}GFxi4TFyF8~8OhHOTDN-|r8G!_F}XXqifC`25^^@^j$qb|gK zvRfdeU4g{D&!(H+o5w%RrDc%jUA!;MqeYzN&>y5NnCK5-GJh2l_y$bcTQN;0NQyjE zij=>W;&E~}jqxaNZHsse$xW}XP>=rr`A-D0p%PQ@uSSEx$gUY<_|ag*7fa)NI9@j9 zcQ>=LdX{_|J&ApWEfHP5f(UNI>0fH7aLPRJ3K>&--)ajrPBWO*pk8 z%Kp&Ag3Whc+cDQL;l>(E#1G2$=X0uL%az!Z9ZeJS9=K3pKEAMja>e^~Rgd4jQ+mGi z#YfdwHbll)FOEciZ65K>^l$P5mbARk-qdU7j+LtpFYDs%_+nEB(RzZG%hT)NC(r$4t9ocXhERm7Ajub#eft^4dHonEqxZCZY<%zn%@=kJQ#$jAG&&g=fd+Og!=JwT`nP*8Zd(55p&h3RU!Ha% zIdPUUH)-X_f_rIu4$cexV&lxU7wbP5_dws8^&eZFIePL^L`c%XFDLCeeWqmE(lg&3 zxO{5t?VvH!IzF|<&oHWZO}F!@hm-&Khp9?;M?!UVd2V`UZf;Ia*Q(6yu4x%vbIPkz zb8@SzYqF}!tE)0Av#Tm{E33A@)qR5;dg<-TWwdY=9Uu4l$veY)zjnD>-P4YM4%-*? zdL*neG@9-+V`w}N!eh0d6Az*^V^lI%v2j|E$b*E=T9D3z?3mwN~}hTHH@-+@KhqWKaSRhDBgqIAr>TVxY* zda--;`u9%N>)+p5ORzs(3*YOchbdb4Q5QYT)WRc)dZ^n^=;=;mX!fsj^{@x+H2fNN zsN*NaNoQ2+q~O3G^fE4FV5^|aX}GFxm{e?0VtEd0pdPl-nrLkst=S{uw6KHLQfpk4 zraS5OM6E_^N}*;?@2iCwBegKIKfYlE8?AYYA6Atv)=^5394%w+$9P!FVzq&>mTBj0 zogAh)ChN77>#Zo)TUDX;y0s!fOIN9lhP6_+S7{w?tY7A8TGld! zE;i$mD6}TQI(OCNs7?)fN7(hMU0SzU|HE~V^|@?%N1AS3tqzzRCaqShCr3$ds*RH~ zybmffjo3w7KT;1(o++JEQ_Hhb{#F99AQae)N(gIZG|Ozbsh*nN#}=qim~U&V)XqIL48{!#<7_ zZIB!pd1Uu0ZtsFM>bWJ}?D!!cgJ9fmNPI!pGIgXNK45ljwlj7Ku^ zq$!jyq_L}_&_0%o1&swtTl$7-f`6bAz*@;@^Ejh9RvzV3@712NPf&uRD0_|KD-DtH zYQCe%tS~=|i8mv%ae-D`U z-j_Ng5bH`-<59Gvo6N$E;WC@t5#LjsK1u}hGMe$%P3qMo8UGtW*ZMJkV=~Iuhb_qX zfRTkzxpI9lAG_88;|(m5EJzcKeI4s&s`qCBM(ht0jph()cJFxH61|)$YSE1DF$wrs z;~a|S1yLqcW@g=t=3tg%svp}4_s16JB=waUrKUuDk#JhodoxN}IyWkPnPjRDOJc0j zxlJvrZ6`ZFR2SE_vpA1M(BK%IpKCY2^CS;zofr5>8l686A-|XNhIT7BZ}QOWyu-r) z=Ur{SaY_7^@OMetE#WeCpqwXK*3dg9Rd+EY& z<4VBAN>>(M0gPnaSuHt^{_*LyW6ZY0XB-7&8nA6*$o~ zR|(_e1rgw?$LXZWxV=yXm67B;qbAO3ALVNFHnC`<*^eAr3zx??e3pVFxGP*u>akq|hVj1Ch4(%_q5Y~onEhH~V#V5vP zKI;70gH3pFpR=rsdU$q6*>z97HoKj3?u1oTjkEojrZPXbEuDYN#Cr^O=Fwi@$jE=@9c{}8~&=v_~~ z6x;J^;KH!trhdG?ZLbX0!dHipx7uz(PJZh}Qg36H;9bts*cGaU8SO2#RnH!nor#C! z6nn5*zc90RVtdV-tX<3Y6zv+cr)q1dJzckFYI_NLwytwKVeil)-`+#LwlK~zNUx(< z&poueR%(g1^RSm{I~V&VJ>90xnt!vNezV@TExLb;?teqqZ|Hif-rB9&@@wCwwZ>xK zrZ@LZZHr)kQ?GctZr`qLrR+QO=I_w$JN47rsjWu#U3x=zu6*q}cZMBHh-ukGHRt?HzuiifuoIK4rFpd~4!s`;kr@`XblBHI<$Fg+OC`w>lx_Oe8a6i9-e3u^J1^6yKI6$-i zQsR|Q%m1uA3ghjwW>6SmpUpcW$UeJPv(KHt)7vdwXy!<;HwOB$3~kkFXMHk+oRQX( zH5B%?b++`N$T(XS`Uno&Wap24;S|3HtXxe_nLsJmiKYW3p8SR+<`ZIMKG;{VJ6i(ARLt=WyFmhuDkJk^j!br z_Hfr1eAHSu{1jVQq}Dz>LmHr-dbmUyt0r2ayB_6_HIepX>EVO4#u!+!V`4N`ilMxI S*09#yb@V2CIHh%lX>lh2 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll index bc67fc77ad5d50d767e73d7960b8ba01bd556024..b6116f321cfd18d28feea8b2a869946af2972293 100644 GIT binary patch delta 290 zcmZoLX)u}4!Lqb3>e|L0Ck{rl&F49OvIxvw&KEab&DVL>xlK72F5S%8e1o@v(ZWQ} zfPuk;kwM>3&p^-AL_5*c)G*D=)X37@#4IJ*GR@e+(9+1v(ljZ_!qmdl#L_s?IN2i2 z#ANaW{yQuTj~%~EE)WP1&{KDRIWy1GW7?DUuOi9wD<)qMXjFg-T3mw)0#%pDzdj<` zmzd~w)3Qx0cj%! zO9pcW6CgW?3dDcFav*saU}j)eVrJk1VnHC5m|V~J+O-0x zl7WF4h#48KD6#@+HZYrkfdfc$0|Dr?m7G**n9F_i*LKigt%6EJ1I5_EPuD~i^1x-SNSrQy3WeI;eIOn%gL@c*Vg=I z-G>3arxqzWt=~)V3)bHcvPF!rkUjnzyUlo_W&i z`Q_f><{7)Q1~Pz)Cx`dlvas~tlv!jp=kvvB=dMe~U;M-UgKcw8%(+dUkKTT{CZM~%$VguQ zTza?8`R6xnX6vb01b!~El3n-vLF~mw5U?kxr^tq0Kk-1?-wNgrc^5iu3GL1x2Q^PbfQzJ`r6SI_L%QRyPLrWtw tOVgwz3sVbI6HDVn<7A686O+v>91CO^3nwpCiDoHeTzG0Si>eDF0|4P1GJOC5 delta 772 zcmX@(b;4^xfr5+@1B0?tPJR*t1H-|GW+pl%Y#x2SjL#3pX^Aefc+fU+!ZOcvMMj27 zAnpg^rHVj>3``8$6qy)q0PzzbehtK*fcPg^4kQl)%nS@l%na;6%m>7xlj|8@yOsh~ zGB5yT7#JBYDzXA;HZYrkfdfc$0U1`%0>j_+pP z3vYjLZ9Nyj@=-x^bBcFBGCS98j{|-`1w1%KLLQobW4w@Ak(RwsVS^o4!Oo?guF|=E z0vX!-rnRj8qi_2D_N>FrW}A#Ryjc`byL0zhxs_X%wCvsOvoPMkZ58+J*gujJV*l@n z{;|vPdTcj~Uev3@-g|n5DO2z|Whi~4dn9cTkUR7jgUvD|rf8w#r zl7W@WCVp|5`#Ndamx8QKR!YfFF3nAyoWowMk(!!lVPs-qVQy}jVq#`#Xl!Yom}+2d qk(!!jnv$5BVv=l@l4OybvYDM@fed5bEP0Fzk56V-bpZf>YcY8M diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index 96f9481b7cfcfe45c8b580726362f6404eedbaa0..a5468f11001f30778f1fcb3a72e1b93cddc978bb 100644 GIT binary patch delta 316 zcmXv`Jxc>Y6nv{^VIzrxcmzaH3yECrvTrxL;jmLIL=-z4xw~+`B1I~(5d2s{M6eJ= z)2Y@A@v2=FXjE(std?(L(m} zFmkh34K+aW4M@nRz(G-~sgZOA h!~{k9OQeL;2Tof>TLd(DT?`?3;M-z2&MXVR*$)IoTJQh> delta 316 zcmZpuX{edd!Lr6*?ZU<$14%~H$$KSL8BI4omi#Fsuv9qvA?Nke4(*8t{&{YFQ?U7s z&LS2&V?6@~1`|dGeM3D1JyT=t)YL=^BNGb?b92iS6EjOgV@vbIR0DI1)YLT7l*H5& zlVr1$B#Yz}1_n7s2Fc0uOe8lyHGIyB$$20|ch|i#_(4Y~$HD=VDUEeXW+s z1}2jgpn@|GLIpvpHH=pHp6Z-%eG%KVJDVSytY+jiWnf@n1Y*<8)6Jxr7>ze?wph&= zY|Nm|kP3u}3>FMV3?@L*g25aJEg4dPJTo9|$Y2a4&4DUX84Q551z3F=gDFr2NDQPd j8K^RaAqgm#%#bp9qm?PEF@qk1!Dc}lF~-e|Hvbs`H#1>B diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index fc8890e2870214b07e8bc13399fa3d0b21db5262..2d70b0ebda4f9d99be3f2cbf41be1d292957ddd8 100644 GIT binary patch delta 1035 zcmbP}upnVVfr5k<1B0?tPJR*t14E#d^mFFrJ{}V-T?M12%>Md1>CB#q6OM(+s4+4q z1F;qm8v?Nf5T60!k3jqbi2thrH8U_Wu&FaLBmnUQAl?nchk^Js5MNOT$%6nR!(AX> zSc8#4Rb#Rvqp%22#9IR*^A1W+-h7$yG85yr$;(+3ZN*vC8Q2&O0!elTarQ6}L7>+Bhdi6D>XOK#olkFh zYAn&?Y~8hG=MMg*{EM%8NJYGzbvLldZsq=q`r6vtk1f;P@t{_~qx#vL#m0x1ynN|+ zzF@+#g5I?|E_dlvifG;YBLBE@O}%A98S6oAwi^rz?-?BD7;8D1f2i!;wus@8LsII= zwgm_IOtv*PtS!4YS?t8hW9emb7sNgsI_BV)5F_xQ=&+jU+O3ui%X=&IIj_Hrw~@D+ z^>6`ij&{Y1rhwwDzgN9mX|kks?%u8o>m`^>cfQp7&$426e0lNpH5bavmF9g8d!dn8 z?-5Wfa_!{D?Rz70PKHXao^$unE_G*#7xf)sbDyu8raMXgexhU31{LXF|1#3APe0fG zcjf)S3-iKv^Yu^a*ZAYJfn$F3HG}+K9gQmwIC~3(rSo(%3+23?KRmgqWG|<>Y2^kL z#|vUFxIe7z73G}6!(JFOX~Cq&d*9yOv{3cu2I&X&9&=abtU34X|Jm=`O7^{!ZhRKs zqCO*aanYo?=i~a-Tjxy`zkKmcjnU~(HM4v-e|*EF|7D9z|M%;EpTu5#^>*LgL}x>P zQRYy?xUGEE^_SemoZFYi$SFLw=ws!2=kx0ntJw41>iLtxCtOilT`+T*sQ2AxUc1%) zzP0P^=b!Vv>F+1m9k=6B3eW1u-P@MK_2W@w#yhTEUn5LYShpM$2w5{(f~!#@(bUv1 z&CJxu(%i%>CD}5~*uv1#$js6-Dapdr!qmjlIMF!SBF)5P^Fgi^OcLtNr*<+t0H)+7 zmWWfkSsfdueLK=2@8U7Jkylqj#cqB-LjwcD0nxVy%?b=0mRuc;oG)&=ny>S$bDMH5 zT)LSv*?><(eB!~$pBNap8K#y9y7aK{`z_b>vtx4kJvo|BT0(8zwh%$@n&XZ007$> BpKt&G delta 1082 zcmZ3GFehO`fr5w@1B0?tPJR*t1H&|bvBy4>Z9E(2TujQiuhnv<>-y=56OM&RsxdMs z0I@m{>jAMT5T69%cR>6Vi2tYoH8U_WFsm~%!~$_I5bpru13-KNh%c&xQf6df5Moha;b1tUp}@k)a7!Zr$ZpbH03<&HNlpgF&8)0{nd-SUIgS?|`<8$B z_T!m*(-U}#C!D|S_S{@>Z=VBmnfHcQjo+$R85q`v+|Ih~AW+NyL!Ql6bxEXUukdY8 zjU{@Vt-H4D+`+$;fAQUkCL3;-xaAMC(`(>Xa>Wf+4@*d=Ni(p>xg28dmb}h&2A0m6#E#i3O7?gT) z+Jb|8Cfg=9%>AY~K}=)CvGi|@7bGhZvll9Cu;VJ&nKpS=*ew~w3sHNfx4izNKkvKj z*#`@H-)L35SQJpa_3x^8E4M6Z-MicC!g>j1)7H0o|5;YdjxRU9uXC}~T&b};?1e#g zy+;7M$hDgnx6h4?IT<>8)t=iAcBy+yysYn-7W-h;G~P+__Y?h^HYkYy{+IDSY`Wc% zyDRSpUTK_un~#5Uzs4UQgZ}x^*9@}Pp3%G}z_GSqveY}7%#Vy-@()gKEZNJcZ^~Mr z5_mz9ht27AkSp8q=4KPU$dH>C&wb0gd8x_i4&R6R5ZMH~W3;OXthxOmFvd z^mi6~sxq0`b3E91*Nl1de2;q^{@Aj@#@r)qiN25F#kBIf{s(7=<$rt2C+$_aN@RuC z$5o*>c2Bn36md)cX%fHW@0xK3%b#@pF{w*R)V zwJW&D{q6MQyke<|OHGaID{gNx{h+$x!WrpZyb-2PShgIM2wgQP)xg{$H8ssNB{4O{B-tz_$s##r^AWBUOcGlQZ_H#kz{n72ut>Ie z(_*hF%*Q4v`CKfWe3DmI!!a*0r?M)ws8}y0CkL3=#j|}n85$TEM2>tePJgz;abwP| zv+~!Dt^z7nu6H`VY6XJ=Bg2JX0ww$Q1v@u-PwmloeS^V0F*nt@G^a$*CqF$sGcO%# z*7H5dV6)sWM6JI3E68!>E32XztpbX0<-V!K8Bn#yY>aL*FmN**d^zRuwzy#DEy152 zr%PmChpY8VEt#Cn$0y-!G~*A00|x`E)D=Coymj8wzjx{U@JZddc_N>IoUkL~G7rXO XL5#}^8JE>EF6(1lwsi9q9dBj;_1?10 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll index 65f566e9d3683aff583d28438c5dc911cf8f16f2..784767429606c97c26791acd2df016bc093e86ac 100644 GIT binary patch delta 383 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 UD=GJnZ!UfA25xcO)$iW_4=;0X^Z)<= delta 27098 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%0A^SOAM-Ijo9xu_?C3PS^wcCoV4=ZGA$X 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 R-#@;&^tqdIoAW{c{{!&$U!nj2 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index 7cfbbf1329f9fc6a106ab8aff8cb25109f1391ed..a01a7704523e60047b20e2c8498770d89d3b91e8 100644 GIT binary patch delta 306 zcmZoDX(*Y{!4joc-LSDIM}pC8@=pm>MzhWGl8XESF|QWy6srI2JynW*bDgt}>E-|p zVO9$hJp%>?6GjGoLp=jMQxok(Q&Yn9Vw3ZX!6ppa42cY;KxoL2 z#$X1djTkH$%o$98>=cG%AUlo0n85-lYYCJw1Cpi;NkC`;q(RsOC}IpW!5FC40;tx6 b!DRALGgDR*20aFY&4Lz8jGGxP{xbpqks?=P delta 306 zcmZoDX(*Y{!4l0nZNtW%90^9#$v-7j8BI6KODggUY;bMg`A5~tb3%+oQ0D)oYc~gI z2(wxk>lrXGm@qQv8|oS8nHp=SrY2e#nOInun_H%sm{}ScTbd`P8kk$8rly&uB&MdA zB%7rqStO@yo~qo2MIbGcg)(UTkunG1!XH5Lz;%0C{FW+K|B*NSXsxq%s%)X$!FWGzL?k3Xm8`T{2K*3PTc5E}0=^ Z@=-HWR$~S|27}Fl7EFwr87=-Z0s!UmT$}&^ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index 40d4ec74585ecf0077d40ae478ebdbeed3720d96..a68c628de93e648b986589b29f91377245cfbbab 100644 GIT binary patch literal 517120 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%0D{rDa)@q^j0PD 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 Cu>YF? literal 510976 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

    d-m+;lQpG1TVu-TqJ?*7pJ?os7BnZ} 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 Z{||g}4;+$zTlNVWdjH?g|L;8TzW^0#?VbPt diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb index 353d991ff7005e86cfd8296e9cc63d044027a596..b2446e7f105fee9d59a198fdc424fe1fc9835cec 100644 GIT binary patch delta 6274 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb index 978cd4cbe7f222da256c5032c96cc7734ade4931..888adde01b7e91e84e9755e277b93f4fc4872086 100644 GIT binary patch delta 3015 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 diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll index b2d7efa56592ab744697dcb8dfb495694d677237..be0edb36bdd2b942c5c65bce2fe6491e7effc4e5 100644 GIT binary patch delta 289 zcmZoLX)u}4$$aI=tc^WR9E>KL&vX1_5tzcfoFzi$wfnRjg_i55B6n}T!P~%SVWelk zz+l42pl_&Wpl52NZINhXV4Q4dW}232k(iX4XqsqgmY8OeXkngYU}0foY-yC7l$vH_ zF?j<29Tqp)Wt%4#2m}Zi`WwIavGK3_?suxkjP-fV8CF^kj!An zV8&p|kj9V-7EJ=ui9miLgC$Tr5h!i~BrO=s8IpkNEr8GnsLK*aCIdxMf$~NS7Lzr_ MOo7J7i!EdZ0F!W80{{R3 delta 289 zcmZoLX)u}4$*e5iys^iLgVA{Nd5)hf0#1EFw`?( zU@&21&<8T~ObxY@lPxS06H`qR4U8-;(kzS&k`0p6EG<(_EmDlrQW8xK%#)K-lPwJ< zPvF19BBd`}WH3$xidh0xm;yyp7>t3kDL}QRKsj@;8L2?hlEGlIrkE+v_;|5}%m5O| BQ&0c^ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb index 5aaeb6b14f96eadbd27ea9be13628aac9360ab68..84bb0c96d69e239b2f30c219fd9e52dbd04e044c 100644 GIT binary patch delta 760 zcmX@%b;4^xjfkPY@rxfD|GMvf_j>2nmp=>LWS4E8IANJ*x*{V(B@p)m@lr(wAYfwH zrpUx_1Bjmh@oOOd1jIkVav*saU}j)YVrF0mVm=@iom|iO+O-s@l7Rsz!@$UJQIQo$ zvw_(R3>-k36NuFrSQ(62)Pdr6fh0SFG5a*2`dr3sKr)eunURITaPxYmeN6RX+Z)fB1I=GM<$Ts11A$}j8}}UEuzZ@? zx3tT?bL~Yg>2kKRc$wXl^Ws;o)ZsrdY2q>7x+VME=C8@v9=j)(S#M3=q_n#GAI^GI z)@`|7S~1~yLFe2b%FcaETvOj)YtOX3QZM89d(s0=*uMG9TqpWauz(& zyyQ%mu4A)p#v3I@>$lDdx(iku%YVbXK>K0mT2|MDT9F4?-jipU-I7tf5LTnhIsK=7 z*1hef?d-8B=?OR87d}fWpM5tt)swrre4^9-gq|xJx99(toe=wXSM-l%uGix`S;FRC z==Q94Zn9diVcwoUR8yE52`4{+b((G$OTecaqP4)FPSF1ebrxCJ(Q%gSKV&u+L)3~>@bk}MeiQW1* zXV%&DnKe;qFb#gj;v4%yWk%4itp_yq~szqW_YNBbPrCDN{Nuq^$l7WSVk+G#w fa#CuVk;P^vjs-G|d6O5bM6={EE<8S&N!0}aX0bN7 delta 763 zcmX@%b;4^xjmQV>$!9rAo_XvSj=Udf7$zd9+_rn-gk_$oii`~9K->$&ixnAwfQey? zA``F3Z^f#|(+Q(GC?83A4 zl|6~7`HT2Wbk{3vQv1T}*SIg5Gh){Mvu{gGt~8!82b#Zj%I&N>1_EvGnJe29*GJvD z;dg84YZZ}8x}2>nUS@aWro{C;-DtC*dEznNx+VME=C3(%TkoD+0`qEXPviUce~g3X z&6g>Wm49j(lcfL8W%A+%r?0m^8mw)fR zKlkj%K>3@Q5xjvX;@;$KuFm4oOnk2w^g_I6Voqw?=l#r^R>zln=UbN?p6c;nm3Woe z=6aPCOkAsPeqLW49CLV=f8;&ii0>&J%C@XmcpFQ#g-`UqFK}<#peBC&pT>K$>2^o{ zUVN{)>cCxj(RDY<+7=k^y%sU^O3B*E%ipv(HFque$T)59#OQV3!gr@RXLA*Xzcta` z^fNQ;&H0}Xq~zbc(4O_5FWZnP&>!( z$sbDBFU#HG=zX&7P1)lM|JJaJIkPLS<$sVdYpE1#`LQjZCmwBo7;Lb4x@~ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.dll index 2e4576da8dffebd0e8db19c1c84be958510744d0..477716731b9682725ad61241d001cf31b06c6be3 100644 GIT binary patch delta 316 zcmZpuX{edd!SXrO=Ih2D14%}c$$KSL8BI1nmi#Fs;CREjiu3my?~V3vmxv_v-rD>| zXAz5?k)8nqg9#&pzM-Cho~e!g?W;Jg@uu^ zrBQNHYMPM+1A`nRgXH9SCX$<<8a`)aIl?r5)#MAt0Rlgj9wg-4zUHuAH~EyCXT|Kv z1}2jgpn?-%fwACVr8VQBwVNNCtY+jiVPIfj1Y(oT)6Jxr7>zb>wph&= zY{a0=V8M{cV8md+V9b!rV8~#`V9JojkO~$}0@8^{s+O0YWp&*JlNF`o!iM^9Hvx9>W5nRM@xx=}0l`e{d(8?(ef`f}n z7RxPN1XnlzhbS)Mo=G32e0-1h{dlj}?R5ti&PY{mhL_YO%)Gj*gxSI^=WAEh>r&oz zc3#TI_NT3%*`M_XC-yxG5E9tH#f$uglw72#3R4;gu6aNum0AcD@r>y#jc8L!B?TTW z5^&9Z=-My;n>d4&*=O^?c56ldWO}*tw*BPk_&quNH8eC!6SHzWF|ob2ceJ0M$D;$; zPEKtR=A_C1fFPN@-KNBSjR@Tg7^WF^ cBg~rP9}1@cYKB|Uj1L}AJJ(tumbCuJKP2N?#sB~S diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index c53382faa67248dd6046d99d7974d104bc505a4c..31760e9e29a805924895ecabc0be86603ad7e205 100644 GIT binary patch delta 1047 zcmbP{FehO`fr5w@1B0?tPJR*t1H(_H2MIa1uQ{yOO+Mx3Suy(v)BIHvCmaitRAXdN z0Ah6@)&pWwAU+Ai?|}F#5dTpFYGz<$U{+^jhy~(aAl?DQ2Y~nl5MNXW$%6nR!!00R zK!cG%Nn^4jqp%22#6trj^BPJ|*nFAsG85zG$;(+3?L=7A8Q2*10ZDcS5%w?!W(HZt zG9W3%q|C^|AjG1;!ohGxLxF{p;g&`Mklm!Y07!lYlAH{TTASHe|1i}%FgyumQF5Cg z$y>W1%f?eJz1}u$^8enw=Bi(h{armVpz&K3D+9yYklR_e9RzC4f5@}hsxFDN^qZ?C z&b89pX@Op@-aF@)hciqkv4!nUE1$B!|5g5%)2B|Y-I&R(RUyxEX`bct7iSDItM*(s zmRqVBlc@b}hmyBkYuEO_$1CKr>-iSMI(}$kG-Ehn?I7^Hr|SZ5P2A$O8Vwc`A|f{_ zW^7>Y%~eQPJ@@+()}mD(XU=8c!n$XJ;RIm`ah4r-HhB7G=kh(sRKDkZDD;>3`QLqM zHd)NKx#V^!T#0$NZ&kGxZ)Re;zVa9CHV5ClUETJKuYO&>d&g9J%iXOV&-aL0c}Ud@ zU6E@oE&7pb(5<^OD|zbX=$P**9L#_DFRuC=b9IyJr^nVh9s<)+obSuOee>(mPlGwX zY+2t{Zhieh;b+I8^DNyTcBb-vzILL?iFe)w4)cujeIT9z_7@7s8FPqyD>@UOq(a&}pk(I4~oIWKE2n=_r=&(Uu* z$ItKU$(w7I@Dv}CoHsx5r_^TipDL%9Eq@+7H`wxQ+v~bd>oUHU-Ot;&aK`ek##IvQ zquOofe3^LkLY?OH>Jt`y$&F3cllK)h9{u_5iS%Rxx!y?hhz&P2^ z%rq_4A~7j7(KON0EHTX_(ZW2*z{0}F*wQFDDK*W=V)K5k6-*NAZniC8IKarzas8Og zSrc#9+2WJ)&&51qo?OqXD`EL1bv{D_1H*;eGX!0xuJLS7iWfVX9r6e$DJz$flLO2G zr!84;Gbk`J^c_ymQT=|$b;jYm$)~oNB~Sj(E8%?Q$SjcdDa^}RB4l2>Ps>qgxqd2g zw|io4s&i>hiJot2afTkmoLk0mhZz{S8PZ>TkF#A{>bSF|jgNIw>9xsGd{PpYyqR+t z95@(G1bpQy(-(D`{JEz>V0uye<{mx+Iblb}Wgd*nf*6+-GA^rST-L|9Z0Y9nI-bl- G3=9AZbeVnt delta 1031 zcmXZbeK6a190&04S0WyI!XlO|6=I4|-7*g+rk-N;P-jxZ%|lJ(!9?!RyE%%BRF0Om ziAB5JDyC|gG*l(gc^Y9It4`x-8;$O=)@dnGM48&z&u!nk*ZceMzR#cE#_X@NrG*p+ z9{}7Lf`S|Xz%xk&%I%ykw(UJ$*n>~9Oj37@vR*oxO7Mih1>u44MffAe5UYqS#GWTI z3?P7B5M&_g5cd#+h*5;z>##pu5X>QWEENI@RTc>y@yJUw^}lBM|NW|F2F_T(F4^~J zw=f*q3t+$il2!m`l?qUR42zK@S-3+qa6nVg)?l1ULEC^iYBqAN^lnCS14$cj;H|+} z?pp|xK6NG3d&k|d$`g?9uJ^Z)@{NJ+Q@=h7zr0h0=0|Zv=JS>SbR--2h6sm>pj}r? zs7Gs>cV5EUF?`ZjVn*iU%!QiinkoJ8>cX`N1XeZa&K3Fw|IOp|R>e2vp^W+7ufRVHOp6=G4OWWi%Ru8A`JsY))!(XB!Gj4Y3Vs7~O(Mgi& z9!?~;CdAO66BWP73ehY^H}l%l6g_MGA@@(H-JU+3#Rkvel#|X%?l3Z2GTkHJPs?67Q|+A z*}*i;ux*TsNqHjMkD zdpGuqd~_^n)>kj1Ve!Nox5ZWfj(`LelTMDd{^+g>d;f(Sb6A#%B@wym3N47R2FXp` wvqOO^r)!&Wt{Vq^oib>T*R)`rT^z%qA{r`~P*DICWl(V$DsE|}eB)660yxW~DF6Tf diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll index 42fbe73ed72db9364eb1ccccc18dec1ad396955d..6d06333d04a1f77851a053138468757cde03b199 100644 GIT binary patch delta 384 zcmZqJq1CWMYeEOhE#tVujXkYBjIBLPtv$@GJuIy~tgSt4TYK1l)G?Y&Z>?wdmo;HP z0Hgf~HY1Q}GW}ycyELOoJ8uI!5OZwjZQ$I_ERg=qfrW*Uv87RR zQfiu!#r9K4TzYyecWi_AP5(22D?osG|C2m5jYRKlvx;vW=`@%#J!T?TwE|Q$y9_D{ zRBzY$rK!5xe@Srb>s!*>zfa_v&cq4w57_P7r%&M$W@0qjzIqy0GGnk2gEoT&Ln4C_ zg8_pvLo$OQgBgP?wdmo;WU z0Hgf~HY1Q}JpE%myELP5J8uI!5OZwjZQ$I_Ea1C3z(UrI!)3#2z9TOb0}gJ#(#*N< zo`s>F0Rw{xBZEGWp=WBSot$i8nV6Vrl4xLLX_01OWRPr-oMvg6YHE>UoR*SkYG9t6 zoSJNDu>DjLm!2NW*R>Tlr~jG26(GQv_v13-_roqz96Be}Jhkng9y5`vS^+950uu$P z4^p!=%w(@nw(@`cV*B@rT+^92LH+@|ef#t&T*6F@hTB(9<4R@>He}FdNCrX+21|xS zAWUU20g?s`MnJv=LmChp0eQ(_oCXxL1gbCvil#6a17%ZyYE6N1=3p~Yfutpa!Suu# ZT&Aps40;R(+XZKFr7>=2oW=E@5dhH)Z-@W@ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb index 5971d0e3dbcd70ee2dcf381803a9558c4a2f92ac..2b0311d90fe3f0a98b6bc044c7d64f3275652644 100644 GIT binary patch delta 27124 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 DpMz$> diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.WebSockets.dll index e5c84a7fd44384e8ecdad34ebec833ab37d6723a..3b4141647f2625001e3f7284147352ff46905243 100644 GIT binary patch delta 306 zcmXYoF-yZx6ou~#tsn(Wafy_of`U*?UT$8}n@u+nECnasUP3bIR#(kfp@TG_gE;9b zRNgKs6cH@>0d*D~`~kY?Q_AI>%l*!|Stsk9c1y1}GyU7%zQsiTWvwuw7Uq{`&Go6; z_1SZ_*&Du(5AOz_s=Yc_CbT(#VBokmw|#7oMDE3I;HL@6D3QJl1DOg*&4?4B7h)VG zDU!Nas~V-{m~606wIU{L@Hpsc#9P=2CN^ouCPk?vozW bU!nF`lSJnTln(>Hj9>#-RWg`j^uvAu5T;q3 delta 306 zcmZoDX(*Y{!4k&6_j6-Ujs&CeKQOFm@qQv0~vazhT6%=7M6*LsV0dAMwS+77Dfii2FYobmZ_!|DaL6jiKYhT z$;qk7mIj+=>k2ZluunHUG>m*yKE8upxssLoyIrFjz7q z0%0nH36L~kFaq)|7}9{)2*^tY<20a{B~XPaP&9?X7$}RxS7%7KO+E~HCGY< diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index ac97186460e3b8907647c411d48de34a23d4150d..a6163a0a9f8604ad0c972867f36d0aab151753bc 100644 GIT binary patch delta 361 zcmXYoJxIe)5XbLn>Z3}fQVQZyU0e$JcuCq92M5JbD2TJXghX9C_<^mhNQp=WK_ne? z6Tv|c(Z0b=L`o+oq2QzlX3@dAs?So6yC3(v|5fXmYCUtXCs~QrFgaDp&m(d0-KSf2%LA}wGAqYCgVDULQc84-IJWB%3NVTw6>7J!6#sY|*ngCMCNA&7 zQUe!~pC{63rPG-|y0{vAc~axya|8S1JzGxq?0>$fEKRyojrG#(*frnfcqkD!004gg3;bkOy1jsVE}8EHNEn aTyZBr!^I{xV+3SS`A7>F5l1cji~azLj&X+o delta 361 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 M4Oe6%XM;ii4+tSb@Bjb+ delta 22994 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 LYv?rpwj%!z%Uw5k diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index b695c7b6..ad99d1ba 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.51", + "version": "1.0.52", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT", From 81ca92fe403c11d3100d03459e9fd80b4f9951cb Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 19 Aug 2026 00:56:37 +0200 Subject: [PATCH 033/203] Keep CultCache readers on one committed generation --- .../DirectoryMessagePackBackingStore.cs | 133 ++++++++++-------- .../BackingStoreTests.cs | 97 +++++++++++++ .../Plugins/GameCult.Caching.MessagePack.dll | Bin 47104 -> 47616 bytes .../Plugins/GameCult.Caching.MessagePack.pdb | Bin 20852 -> 21032 bytes .../Runtime/Plugins/GameCult.Caching.dll | Bin 86016 -> 86016 bytes .../Runtime/Plugins/GameCult.Caching.pdb | Bin 30208 -> 30208 bytes .../Runtime/Plugins/GameCult.Logging.dll | Bin 6656 -> 6656 bytes .../Runtime/Plugins/GameCult.Logging.pdb | Bin 9544 -> 9544 bytes .../Plugins/GameCult.Mesh.Quic.Native.dll | Bin 15872 -> 15872 bytes .../Plugins/GameCult.Mesh.Quic.Native.pdb | Bin 12316 -> 12316 bytes .../Runtime/Plugins/GameCult.Mesh.dll | Bin 695296 -> 695296 bytes .../Runtime/Plugins/GameCult.Mesh.pdb | Bin 205548 -> 205548 bytes .../GameCult.Networking.WebSockets.dll | Bin 14848 -> 14848 bytes .../Runtime/Plugins/GameCult.Networking.dll | Bin 510976 -> 510976 bytes .../Runtime/Plugins/GameCult.Networking.pdb | Bin 246920 -> 246920 bytes unity/org.gamecult.cultlib/package.json | 2 +- 16 files changed, 174 insertions(+), 58 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs index 0c980c61..4f1a2521 100644 --- a/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs +++ b/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs @@ -21,6 +21,8 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore public const string BeforeRecordPageWriteStage = "before-record-page-write"; ///

    S$B9yIBAighj;p= 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

    OU_g@ zi08g|k$BOJ-C`NEX1N4IGHUTmO?FR+OvaS9u1+$h?zAdEuG+dPINHl41;FZ7otDnDX=;XsP8o!YaFh?#mg$| z65HXhRh-B7ch7?LlaVR2znR`r-37<;yBXYMESir<|J0vY51iIT=VRW^>Pvqn`ylMY z(&Lsc4slV+N-##@!!yjTA3VmQYT?Q1x48%eU*`yII)WrMeplM~Nq z6dqYj-VRzb*zG+zKkOa~o?orJMWkeNMm36CLbA4~mOfE8kjZ%dU`}%fXr7hJcx-~a zxIF7R1hXoi)y3f;?|@ip6Q1kA+KW8>o(((fT+a&A%fobXdo$E0WP)dNDb^OUczqp` zvG+K)rC|0*k=sgE-gS7BV8y*zp!ufv1t*nbE}p11U*Uvb#Wv-*t#L~?^yXV9S4M|0 zzMjZ|+l_JQ46D8tev3$#;&quLV;vStt@T#EZX4k}rqVi>qQ!}R#e8!doW!<|d)R9c zugMAUMq_r_-sEa=Su~>~>L#}>VKu#J5IO5SH2bE4vyn$@I~+N~Y-i-R$4trGL7>;| zC@?G>9ed6M=p_*>{&2j?1s6F5=lDF6+{TqV$t+%*bWpEJoE@8VhV7j&pnPmdML(Gw zk)0+d^1{KRDXX%ueSmMA3`Thxk0~EL`Rr9LY%71_J^2vW z^eLjJS+^atR`B8-*^W(5R#_VF<^37OowW~sfG&HqH`s-$({#T6x4?qF^1fBBulvg4 z^>sfP(bxS2bA3HPR`m64g3TkwDr&Bk#-3N(1Lc#~cmV%DwD(|9Nwrs&M0?*ZJjb8_ z;}o8D)&m->00(sLx?()7FwGwC@>pw}HOFf1JDsabktnNUBE z!6DO6Sy(;go9gGgF^|`9EDcd(w!IQEB(Lnci$chF;|(&WiQ;HAy9HvfwHR!^t!UZuzVnm z4wHr~dTdV1K)=`A#s4VAY+|9X_Lbpmu0eg53y7O@U<6L@PdEW{>qH?qu>+{Kz7I`b z{`?&x+&|mZig6;p?qSB)og{CeJ6WLQngYG9B`_=}7^SO3H7QH!2a}%gFJ-f=GTEd?`PmA<^4>|DeuCs%KL@leHl(E?`?St9S;zR zcPG&6mJ1Ba3Eu0*yDY2neiqpF3VpUU-pzhC_ON}90*vE5OSYv!GPQcO6mO4E!uGi$ z^gcd*k_SbmV|yODmO4>Ao`q8;hqgiRS2;BKXsB`=J&Ie@8XPvSx_{qaRc zf(>7ym_&9lMn4~ViMeJBW(ML?ob({Z#JQxi6f5%CHskH1sn+PbCD9zodkk2^c8zbl z!Y96TYz$kmQUWFokVTjoPGb?hZZtYYW3p(hfiAROl0IhM)YmNsH`3?6$aMK~h!*z8 zGWQ%z<~KNHFdq6Ih9TO1mh-N_o4b-v5Ds4j&~r#y7NN5`%(|E?{z^p+>2>BsyKxF< zUK9=YZd@y%;nsB+?B0z|)FpUssv>F2m7{C|g{`a>6l%7W^?DI#>)ZLS-Ut|kV+F)cO&cenMx9Y+*+njv>zvN_J7{fGxr%eXc{ZI7c^NHyir>SCkv-tq zpT>{e>#e)o3ggFI(WU2q!tG+y^M1+$;5nHGB2g1Y~ zUDve*G*LDagRP8EEYKefki)iISs4*1tWsN0sM#v@d=cP!AAnm+=7uv=Tb6hp^^a9T z?A1sQgvp`vxqD1O#dTVctTN){g2JssIG?7`Dm~?D>lX4dng(lbF79b*T13oQ!iOci zG|(gk%8THEx@kcQv^D8C~%HleT zKa-_4rA;?kzF;KfspW0QGTfOu+cd+B=3nJux*mH;JlvIe(5~_s%}vh3!|{Al4|iuCREy4NULX&T6g=F6 zhvb;#q@G$=jg{#9)fmU9fW^g;nzPukKo?SgS2Btb64D&pXM2ugae;Od%4kl z7Qd7drOG2d=MiGkBkmKTEdhpYQTAB%al+On?EYXYha9cf6K@Jan>$1{-Yl_*WW0G| zk$8+myoF(!hrT^HDIQ8(mGtFCDLgK%C;+El~k_>1zxT7}-x=s&oBb6sqGB|12? zDT*2l{gWkgwiV&YJzydISK@H$2__onb&UghpnMN8>$F9?WEF9b&iUJxA?>j&Y#EYD zhh3fSK|!6Xe+WZ&Cnxi}tp0e8R)-ssKeSNO!;s+k|fcq40U#{#74-iJIA&b~X7Am3QZogGCOq>b`%I zwTpPo^CWhqGwqi5OzWDqsPY`k??o!C2H_`WNo}yuk+Wn}@V9>|U= ze};rxtcO+CMBn41ZCbruy-bpib}bCnN_Ti)PnB$npo&u4$1i%X5Nya&WvP?skC+cG zM^|lPm8*7g*DjE^HJej`g^KDDHa|>?7#TALLA*HxHOe8)uMygf=$j`I*hkzmvuoy-D%2>b zcc%@ob|q(QR@baW^O_G5rszje*^wHbItV1LyF($`MZ3f(;j6SreX7pBkF0``y-!tt z#Ao~lJ+s5E+3p88gl&CKJsr2jGZITD8e%hW{XKKK<|G=?-2OtbB;V-)0VRPev4z=` z)}fea){sHxsam6JjYZpJ(*8K4OsO&>8#c6b&pRg@YtsnF09BDBoWfIm(lA4S~OqgYsq}-EtbZ)KI;dTc+QSwzz z04fy#$q!&%i-xa;h84+ZuUPTTa@XwW8BoE823dyhH9eUvzQCf$>5i% zIg5SgXXuEqku;djMxV6rEdMLPGuIak6!7Y~sPDZlwAiN=oP>QDQ21>+0(&dqZeS1g z7EpiJG5@j&kXFU+<(6KH{ziOWr2E1Bkik0gnEYuRyeYUh@!K2ocK9z&_{?D)fQ?AO zrPgQvDT!nptYh~dS9{ue7KD3_PuTX~5g{~5mm;1Q;%5cK&xLq_4|RZir@efIZ_DEZ zU42i?Ty3$-{Q`d;nU<-rEgXf)UM8kE6_yx&X)d!8m)Snxuy}}mB`2%G+}Rk1xFnTi z{Tictkx$t6Uoq#-;setjBi56NNFmyCY>R~V;O)P`zF_eHrTKe@Jgk%~QFnylCS@Y_Ao!0!0UC@HF&a<+Hy<^{|9@n=Orh08OrI zmrveAW3SHdG1}?aKgghKF8_!j*$$zM*LH|M32N~BXAE|noJ*aVWq65L!nRyBKPNca z%jAdOd0>H|_gB#FZ+xh`1y0!mAMWo$G(5g7OZ>7hCb7}#E10c|*!l;i&XL1er82d7 z-SpdZ@aXLRo{phGPA~eii^fXBYiz@7ktK!wHUETua_)6?zG~6G`72&Gv>HJMJ~o}`z=g*wC2eB8iriczB0!@re>$k_eX*DB7VcJh0|(} zkNa_}afHgv6|F2ax;lIfOU(yO<>V@r6D*t{S70Ic8)BS>J&J8Mw})5%)^H+ zf3ZF37BiQ>fFtFWA`>N>(dWybe@SX}zS!<7PXlS4{Ac)NiZ9@%reJMgu};>U<_-KN zu9LmKTw(gM4m2m$mvzNvdja47wZ4cb`m#_!5?&93>C2i0eUU5rB2ds53!3UnBm!Yu zz^yNH!zC!Q8FOKKKrU^u>-A+&-WM5iv3+G;RbLFd1gF>07vl(h*+8_i)JR{L&!vj; z`XX29i(o-t6gbxxMVaVJfwM$kjGqnhk?D)Y+(=U{cQ4AZ`m#D-|L^pr8nn@+D@|WE zf{UaLzXPR1&o>5>w!+*@GGPNu-}|-NUXcjf3Y=e1r64QW3#5>95p-X?IY#TTZ6Sje zQHL-jT{rQiZPr@~DkHYSVB4(cSuSa}FuS8bYQ;lvw3nM0eAmqaLyzG>-MyI)N+;m9 zmbu|kZCRrEP)n|qTePtGFM1K4@j^fRDJJH_wk7#31o4ATE%sR@U~x4(Wn+@uEG+h| z;+!q^74MnYocz#*H}gr=b`y+cw!rNw%dfY>f#p{?cme|0MhR+f%Qu(wcG!^g_U5z$ z-)QYj-dc^5xV0K*()O!yqDleZQ9$zC34^W1tz*J|z=SPV3b;T)))o|MChMI=fb;wS zw~NdTXQ;L;6UthwuOe%uAQ~%cyP(hFYIw@VV|F*6Ev&BMoXI-9OYSfKoaipOO7LS6 zc(uuQSE5ej`<}496dM;q*mrln$ryCqZK9)1DfR&OTGI7b>xm}P-BUo)-3xCqyey5UqtvMZ32`xAoOVdwZ$D+>KgxL%E&g$nojb)_!O?g58(F(A9j&K*Du90Hs{d0Ehx>kz6h7uy`_-X=fBcboFX z@~qg7cnz%Dx`7G51xbcBe1^v7fz$HRf%wub!C0Cjuo7P&GgnL)xr0RGCNO>R!5FO$ zd%Fy(!w$ia)M1jP>aceRst$W62CKssSg|??sfKO2N-G3MdpXJA>o5xpy+c8}!}w6H z1l-{=Hw=|_m$)IvuqO1pB9IvDMoE7!WV|q|25c?as$F&DlWJEdj6KUWcZ4FzSk|c! zwyo1R-e!6rR}H5H3;OC=uKB9qNKt6(+c~m}->WYkH5)FN&WhRHJQlFwGqmCJtl`sX zB4e1lA3F$=E0|x$bE@*$n+7`9_X*hMeadX^3agEEj{|oyzx-@paox8gpidQCYvtly z@SL=h_xZwb6qvLy9F3X6U|aEvYrV(FkCdG~Lo=GIj8Wf>bGi_V?sqmfs-gC6{4**0 zo-}N-5WLDV?6H{#zYyGUDFtUMKCf4Bjw9g21xGHv;2bYM+^Yd-?hQ?UB!t6T3?z_wD>I)M*`NeS_*sV8Dm@e=1A6AKeZ=$1PfW7w8d!?xs^Y%FQY zm~SUB&9zlbX>MT^QyCPp3!){cO*^Mx=&o@TWqs4!CrxwZD$Nxv^inM_^lTOLR8im- zcYr%h=7uxWu(6V?cE};wYc9s)0Yx&hxgR|mxrQ@2H=K!*3&uSuqiD=Ar1bYtZK%22 zHx_1sJE~~h1Cm&Hmt#q55!+8z+|D+Od=nhkr}%x57fSj03+)n-*6Fb2?$M5nd=oTJ zwQ6_jrv)nR4uNTY*Sxzku{=)|>Ua$nmx|V0mUHBc6P-fn`Pwq?tZu43FlR_OEV%3P zg9N{s@OG~Q*P{&v1GAf#Z5y1VUCfG?H(X1>_MSz;D87i)9Uc+^dI$1SZF(3i4Axzm?&ar+IgX7056e$6ehMfra0fj5Y|6rNME<#a+l-TDH(ZlS=id?5VACVW|bD`-{=VKi?F?%^P2aO5gr}$m}9kYGCjGM zMd8}JS^vQTtN&on3zy?Q>ju0JmaO$Z4}<#H=L3_=*u)XrKpzy)aFgeb{qt+=aic|D zVVwZEvhzY8WDvbQ5s*%iJ)O?*sUUw8$~JG-qC>1brT(6%n5{ zPA^+JcGt#}gGqkOtOv=+k%xfv$%=k})9Z|*jXB2A4~ds7VH!toU}Dzrd4EMUpxHgsIjNYLCv5jWkE2wEE(Z&d3-~4KE%OG=s4V5ng)P zGyxEsNza^X(M592=3xE?&)R#*wK1#Qznh+Oo{#%>tBlbq!CvU#O{RliBAPLMXxmi$ zdEae#7{|1$a8`c!wEc+3(^2Sb&RIX}1`K}%yuU$#%vWGL<8Pgc#avgPVXXBXqyjVy z4^C90Kk(5j(&+r7eB1k(1`82W<{5!sOyW&#FTo(Sy%d)|LZ8v8(p`n!8AShsCGA z6=5@$e(4Y?VeGOHx=*V_9Fxs3cFzLOK*-h1mmM3nCA#=>m`F5Px9Qx*p&{Eq`7sR1 zS$pG`uEUcem!(yknKxEiN7|g6#7X@I!GanqB;5uI5-PUXFV+|}<9@VP&rXnFapfgQ zSJt=r>#5CuO|R)|4aIqaR0s3B{C%5#Bs$5NvaT?qH_N6Q#TQ*c%E=^Nb)T}Zpi=H@ zxR&B8C701j=;@5+?FiiaU;?tftlW3pXxaL*FXPanaz*@Q_Y~oDrJVlz2WGdfg2FOd z?QaO5ST|>f5Z3r=#jQtkePq{Q9ry#hlq=C56kzhs9Lg2lq^6ceEaTefi?78m@3b4) z%5eudBTxEt#^3N&vkdlM~UOZ!~s*^gee5n+3da?Iry`l#uuBLaI(Ex$Z28oMBF{ccJq;UHe+;> zw#!^6QTaqh%yJL*^~Kj)GW(t4@GkKUpq6g}b9QU{#u!GcFO$&L3&?1}3b{nf2p#dwmCS>V z<{Uyq(L4EQ&F__p?F%Z!2(;SSJ;}SWnI6|!iR2|vDSL$VB#mw+F zd-R>s^&v6&VLlYGf%`e8@)MHR{vjWqfUx}x<^=sCjL{0JwAM$I#*EsLrM`{33HvPW z@hD7PcI;?becH^&oDFNib|uc5Te~xYzx4itrR)db{)4*vCCGr{^b25{JLrMn`{|0K+c;e7i#r!#1&UU+0thG+L1 z1xoyF${9ASq28D0K3@2aU!(?C=mK6@a9nF^vfjO9fs=CzTH`um=(~_+F+C#_w&f{o zi>o+BRK7~rG^T2Fw0VnBCI*OY2}dSiLpw#dlC)Orq*<^oe|n{WIL-sp+Z+k zXjz3mCQj90W8<`j(<0r`E@!hyne7=9D@|q+>xZqul%pnlCW^M4MC;>?cadm*i+|*1 zk{qj+?ssx(n}@c{`K~R0+xVj@v@9gnmpxY_Za)PhDXNkXOokZVK{+D_!Ng(A&a1D^hb$5;m;7>Z+yxyoQqSh_s&@#H3w5ygC4O4xe z`^*#jW_Qkn&S_H{Y1n*PyvBi#pu)!t`kBcJv~*f@M2 zdFD_1DJZx>g)%xH^L7*T8z3h}soxlNjorZCH&=40)QD&EoRK4@EOB_dp~LN4*GD%F3( zWbfYd{j;zi%}Rl3zlxr^jaCZ$N`lfrxAA2Gb*27@!Mcr`S=pP5kixcHDVG9;vZtUB zv$B`YY#xDcaqg4mS@*m|%dHkzj|~U;btu}6us#?jU^Sa*%%Hcgh(gAVhA5jGH`;0+ zw&kiJieTY76blT!0q8l#h47$n=g6BVG1CYq>BJvHA`7`r#MXi=NW)8#K zar`91UBq+Hm_Mb??hGbkQ8fA zP`6KpZK30TP{8vuW1Y=^Ks~)iQXG44VSBP@6mAXD_$NxsOHxeKTq@m~I_(zJ)an06 z+k3#tSyli4liBQSP1$626GBOWgojKLn!*rT2u+Igj`SwY2X+hy&pvBVY;+Mpsz_5o z5fB9xX@VjkMS96nl@5ZU0y6yHpL6e1W_JR9zu(_~Upvpe_uPBWJ@=e*dmD|#rcSBn z80)$HAoA4^NM^2>SY*aPR!n+?p%}@CyJRoN9%O_MtC_kj^Z+}rAz2&=SmW%B#~9e< zS~vX$*Gf~yI;eR@a;OY<&gHQqIV$i*Uhy~ui`-X;EtFkk+SUKX@1(-x+%TRd+G1pM zL`JWr>f(n?s+QvMhR^xQUOU!wa#eR$%`(rPBn2wRhNtYJ7!NtQcAaVHY&AQd5V$S) zx#??uyvL%8-4)b)eWI=K>n`4GHr<^F37l!hZ4HWh_*vGPPZ(odh8wyD9J#gSxLm#Oei8F(_iR7sV=IKx>1&6vCrfc=i`oqWRMLLU6WJV%Z#`G^jmT z;lp2#XsuV_!dvIsc?DlVVDWN>Kxl$7Ana~m=Vo#bDH&$hMHltKiB|nt)1n=vR%W!e zmn$>kx7PMnm8sUFjK$XWcqM}}v!YlQ$ye2*%v_nV$l$Xw!vP=|=)TH(&_-vON=3(? z9aJ=SEn%0qr&kA2g`PcH3jbAQaCu?zoMp)tjLLF2A7bJauo-sJgdegR^qkcp- zDdXC1$KbYjHK9!ztd+=hjls90qQywQs+45rx)O?<%*zy#!#E;PU0j*Iiwld5Y)`kw zSc4Ce^<2kj2X%~oL0)DqmQS!AsSp0SG0ct%bAyGEPq02=f(^v_siXxT%`genOS>!Z zjRX!hOoL5Ru)dhPi$+%uXf?%5IW}M_>6V>}k%f#DFjzozFY&!qmNqiC#e2%=n=vbq zx`6f49!mMwB6DI#tRT!rEfftK^ND9V8dk>OAlvSoM9u31Mnfo28sl^^kiE8cs4dhl`^8=K*I1TIb9xS-AH$|%Spd;nDn*v%1NI) z;1cPxiK?0O(`ST?A*RnR^_xiDjn7}9pur+nBvka0!k~g-O6*I3#kK;~7o1>q!)4Og zbUrbCo6Ego?1=9gMtoOFd|O65BhY5V`{%hl!|J_aAxsrJ857BH4##K4L?^Lx6=il@ zw;EEFKwtRVCal&uP|xZqwEm9T?qSq+r_}C_KKg=2XRaBw-M-I0G?7DiJ!9foqHj_x zO&TJqOdV&OG_SR~k2Fg9VR}#HHB}~^Z1g!?HqGI#wHCD@1#_` z%UZ@H)``55+)TWaoOt`+V8$XD#iYc#4qEUvDqZrBYM$@i{C6Zzo-o;wyh(AYuB>tu z-sE8(vA6TYvKs+Y_OlqIUPqq zxB^j4r5q=7OA_67>Xqa7O@d+xglY)9>HD9~L%fiO zOz%OT7NxR`^4=kEin(!<=NaX)Jo7TRQ!yJ}enQ|>;gYu+HbQ8#VAUb~5`9C>3iJ5w z0Bms%4#@g}X97EyU-R*3$@J<9)2nr%lDU*@AI85xQLPb|k>rsSXSRW@ulydUqK#t% zIS%%Y4dwW}cPzj$_)sIacM`XMj}T`rmQS#$y0M$_2@kVa_G}@m0{!QWXJBZHKy&Hs zg-6IY-UN(nkmhSO-2=NrQ~)2?dFH$}!mIWo!VLdF8hznie5Hhc#MDgK-I!h(l~HY- zmZ72EdRT08@8+hAFDk>4e6@8Nqu$FV7^jSi3bK1ZM#Z}jmrVp6lOwI62d5oxHdN9y zr!}$e0YxK$Y0k~SkkO#m8#rOk`tB;60`<~K-e>EOdRNO0pQ z9?^bucjIla(VyH_Mr4iw*@K(hc-cY*XG;Yr_7;uN3aJb%l^*aP(`qZYJ))kOHYoD+W@SBdinSLCD-;Yc`wui8^ z9&zX2-V+w~V&fB0Xvvsa*-VIaXP_ZEiX8sr3+sKDS|Ge1lN}$L@f21T2zQqOr+2`# z1wsxQ(tvzm_~ps)Oh&!;!lcYvOprYQvUoSe=Vd;OcvsoW4iuRq+EJirC;J**XJ-1L zc_k~$QWPu83vJ25@H4Nphhin9N$y)EMOi+oXk_NpLlHq`>Va@=9n6rG9ch-y#&xg} z-l=f&Wm(UbuG1hZo)V<`UA!kLW=qktv)2dhMZZ`<_HGEm;{8={KsQyEW)moXA?kjF zWRJ=IGFsyU6pyVXg31(Frordcz9!rU3YM;fyA1=p%=cCNxCKM6sGNXR=2v9mb(z@j z9_@417VRgvI3-DY30ex07<~zNa<}UR;sbZ~iw}aAf>YUGEL{7y zgaZ~lMdQXuvzufg&35W{oU&rsOZ}iLxl{BfM&A?U{=c|W^htunPw`>;;%SV=J4KfO z`S?3Ul??62kPNjS&tS3kgWZSJT+D4#Gs>5$Lq_gSQHx4NVt0x@D+H0g$(^DiD!GeG z(&kQ45m=1mQ7=8mH+SjP^Ei{q-~64T%7I-GqgbXjBQK!RwAl+y?i4iwS%!Lns9EN; z4R1TkoV!z0zMAosnY)0><<8$J`l2vI?kkG2#WGE*JGe48>`qZ-s^Nwwwl@gWd$G3h zMNOnL{7zAqh2iqiouVcm-6<-mXpOC@bo`y7e<2Lp9r&hqiW=s!sg9F+vf`uJX&WsH^-67Jnp5lPV&EQfW*rw9X2H)6ym%U*V%NDdkxy}LYT`H}1~|L}ECzz>Dh zL&SFHfT1tP;upvar;&%G-%xOOoP24axQ{`BxXWib$;I`vwu7a+@@u|Fpryb(PvL5l z>uIEFa%D?ja`nn?P%;G$y}AS{e}j4O0W6w=w;INSGK9oh?KCl+&@; zfM^Hf@N1C91Ec^M5Xq?b(xN#ATU4<99c=MgLcmSF81Z*x=lxL`MpAeMWbkMr0DC}k zM{t*Gi)jG5hK|j3jaQ|T!Ej)nV&rD9| zK#DJaYXU6RzD=HE4kN-C_dOqb?Amm>;16k|`c_{t2XZXn26gFV9q(W_sGYlc4#hHSCOCcp z4phT&O=;5VSeLVNZcATfEcRQOK43hpt|Qxasb%|YMn~6@TeZ4Yj4T`UR=r&6 zrE^8W2Sf~Zhk{@aKJkw&U;1iBi<0mm{?-Ml%c6eOM_BykH{)VYdwhdpr4neT+|bff z7Jgvsdh%5}D6`&69ph?ZCxZ5X<6E8a&xL{0c^L7PvX>K(T!RPm$qkdfA<9P)Z{}k8 zX!CVt@Q}tZ-&B~Qg^^FNH(`Q(P~rG0oTO!lKi?SXTZ+_Tk>nGwmPp+!m8_S}liz6k zf_-z)^BbVSet5+{A-p&)bMR0;Inqx`!p~eRAHx;4f;GM#XXSH5lO-$(^b2f=QIg{V zN-G&ESmF7o1=noKnhq`de|i8Lz<$pr74M@#UvMuUq|XI_1oo)y4B$D*mIbAzK9r?O zBkm@n5AIrhu;f-NVQJ6O4qkP<`Ykzn%_=U!Rrz&8(1(Z*4fWm~0 zVa`yP4htin;6TCz^r9;9PjNz}jML`Q5mLF+J(lH4YoOw1?KTZ!r9xjaPxqW&kEU<6mdS*50o#hpm<7M#G8Q6-CRYh?0u}I_l^Z}=Ep@X5|2U(c zS_cm_RM^o_?~NHu_MkJS`;lte+BF1=uQmJ@*Y&SmB`k2#fy@xQ-g3*tF+F3}vGo{N zr{sFd4xME}Re1Pg((tpDS}?cSsIe2qy4fhV4a3zTW8)h|Nm_@Dr4Bg=ITx0&hl?U0 z--%iR-LSt7TOU;OU*WR`zkRX8AMV1OSG$H{ zv-nr!D4WPxD>=jRG788v5OYeS3kNICD9%*9tU~Fg=z)GfbTU;gOy?$7`?z7m+|q_$*dV8oaJVhb;oBlcPl-%_rL;keg3d>dYtKE`y%s&6qZye6W?0KZ=M* zp3Eo9$gKe>Cb;ea=PV91;xghI)_s;XS8S;=SNuC-rO9fp_z)#Kv@^#(@lmmrnJbx= zXfjtk#dU8DnNglj5|^de8Qjkf=liT{!GG>m$&Fj))ozsD-9dtx-ZAI-ADF4RPij4z z`(^r?BKS&``%62n-GDPLwte< z>q8wfRa^)0@J|7+bwgrp44?4x2%;|>i`6cP87GTIF5|KIr3ewEjie9B)TF}$SZpKd zp(e$>ijlk|MVa+p)}&k?O)iK4O1GN;|;oZ-TcMp?<=iRxEcaH)|c$a^-`7s9{!8^ed-YG5<%Yuk&&Khzf%O`P6 zWQb$5qK;#aU@?vzo^VWF;+V`F#}qT;n37I8W=IEwsgYwDJmFZvuLmC=$uHHX=fIll z;a8!rv-(xXrYAugJx`re+HIM=x;XVG_Y&Mwr?pMlW4FUX*19_9a#ecfv0COqFNfX; zlWD~YH((48GAUe^IL|iIrewTxrs7ouy^!ylOhXpG^(32OCgu4v|=* zq0iyT%1wT*TW2gUL=S$Ud`+eA^sI956rb==WJi2jV}m^{4`r?7ay4nqBy{RCby}17 zE_9mJuKszH_~fKTBWBit8k1iE^7F96ASqnVxH60=Yh?04J@rZSCCZWG=kS$xU)yPi zhELJhCv>D7Esd1knH<$jL?MAauN0Hqj*#^>9I>+uyvR4$3H~)ar^i}H$=*5srR2{c zL1BJ@Pv2qF3d}Z;Jd>oIoyAh0lVkjnVY6^T*|!EHq^{Bw7iv|u2pRA%1PR>%7ezeG z&K29ynXdjHNe}E8T4#{NhM~Ar|ALf2~n0v_Fq9ND^ zCUl#)+QNAVt=(TSGq;5PrCE^%gv?(e(=63z!D34e6kC6^XU2CEZIi6O>kXSD)2WqK+_v>GL{eU9(4nHiNN! zleCqvX8ntmY0N>sMPxNoqRTEJH?tJKjalfJwA+}D61z?r^Ui-;yK5tb?*pRO%8Ql$ zMBDQ;-fH<#z+8>s3PDa&<98GQ`6?iMW&R-LbbFEuNu<3|>t^ro1O5#^7u#0_^J-74 zQ}=i9W$gofw_dss835wcCw~Y<{S~t3xFq3|^XLa7%75byZdK*Np-b#Z&*3*J9Ikg& z>jP!{9-f+`QJ}$p@Ua;VC0{Qs(QLpZLd{$(pMYAl5>8gE_i?g5oNYPNDc-7zO7(6& z;8Tcj8HN2oVS&gD4rjYa{2y$N#Ax-RtOA5n2#iWm9=MhMM2E%VFuPKUKTP8U*O7F% zh?NtrK3Bvetsk>2(QOH8Ia$n^PUZ)wzJ3<8mZ5lV#-%@i8AZGe`qANJy=ns7K9;kgvcJQ z1XOci1{x5k-g_5O1v8lph@a_IDq_ADq^8Q|&!X%kvt<&mX3I>>>1>%?F8}Fbs%58a zYUa4k65CACF=U%{M|igdv*hd6a#Rl=!|$Jn2jxrmJOQ$`91e8q&cLOvrM^qJUt9TT zA+y$>E>@03(j}a}czDMRCZtxeX=9q8xDA{0;?-1zzL@}h;mQ!FZbpEq0qQE4bKTXW zN`hJV4jJkYkH%uN@JC5*Lm`JmfiMLxquzT@A~TBzs@b55+Z8*~H$9AN{$)pyA!QjX z$H5WaDF?Ih#x*x2=W*xN{-WxolLWHbWJ;|<+L_zxmQ$Gf_>J+do51j{v%)R}|8Uyh zU~Dx&D)6!g?}!sv?i!m1RlzW~t>N2Pe3FVMzrAprgWHfLcmGl(cyd{eKg~Prx*STE zk_+xKX$gODFV=VhHc!dAR-*{ORPlI>kQ@5g>M@tE)erMzQT?zg7GFOoi>e)$qlZ2rd$%uPpFB?c)(gFH?I4UTp>BThdlJ^3g zPh>xvI))d>Y&JD_(n{}@IU>8`DMFAE*$t63Bl22PUHML=M9R#~*(fpxx7_|dzoT4~ zsKS%!dy8&Ixr8PU6P~LHnV@Ah$4DqU)hs8vU>P#)>KW@df3EUh1Wx9I{60|fP|(El zD9UKE4-`;gb6EcnzbQF5V5=+V09%t^w&ppn_L}N~WywHR_H%m+H}Ka~7F*)w6^72Z znNx!0SgvE*^KC_6p&>dwl|NG;HQQun+FQMuhhquJp0ybmoD8+^O7@+}w{{La`S5am zF6NUFv`tI+kd^jXgz{U-j^U&mE_XBASa8av2ck>#3nA=%5ynyCT)tLl35G;iO3+uh z{hknWm=Txh=nYR`Qp@#glR4Y9;2&#d^sy=mKBpyo#K@V0E7=YA5$U-*mbpD2cqzGM zS=VwY8XF`vg*LA?svFAY0?7!;aXe=o!9eXvTp3z8TCTez*QE>;qt(IWctQm|j`FdF zCAJ=(vkK~DI=5Xkd7vhc*OyP0Hu!{h=5V}g=JE3E`KL0p!s?t$$Ev%M*h^SoGdO(v zG?5$UvepKYGzU#;p z;5TRx9|aB2denm?KK-p7(E-6Df;3-HAo2?Mv}W5f$&FFR1= zvLsOW0yz?MB{Qzo7wAjm<+MY0^I|L~QN?F31nYqwmg|8^G^%$^L}?FHE{9pujdoLv z(Pug0bj6ks=_hD2cw4ZQz*(R3D)Re7G=ObXY5fYw>QQ)Ii1;<%KQy|a!`#dpqxTy_ ziyM=*Rv!}wYpuvF{tRHaxhY0=U$trUbnZ;^8XawhH$}vXLjJ{I!NbL{D_ zR=69+fofN)RN!P#ehvxGDoJ5K3!?BCDO96pn>@2iiW5(xHxi*C7uRb6)1~h48V#dO zwHptJYdfMp6RYC~5`r#V`K(NB2HgaUUAS_rskV+-6(jjd?`7sX0*VYezpSc+lTt6v zS?wEgqn;K0R4{2}V34mOs~ld#d&LCi1bZwmKPUP z?qvFo5M3zxI~I`_BNV0Vw5@Ck;&|TE9dPj#h0iu^eL~>2fV&`U$t2xV`zl0p(@){g zP0A!IhlSVpWqq5pnYXbF3#o;htxRqUb+kq5ichi!gr|YBsym;GwRV-xoe2PQw#P_X zsQfMSJ2?1=Vk!717t1u1OCaR(w32JS>2uih<@o+9X!r7EnNuy`j@a5WI((nPW><_Z zcMdg$8ua7iUGU{tj(tUS!0lyBH|&TtoNv<&Os%NmmMe)OIfDO`6fDO`6fDMu>zyq!TOJ*)_)>-leSn_eA2}++_jGbsYKj)?x+~cY#7go!}5^XP`(W#FGm1Jw5D*J zcXBQT1}J1sgVNGoRn_o6ikdI`e!RmBYC+wyR?2aHyz>WO+H<`^4h~bSou*q`SexJa zxEC^1%60vz6X^32?-r{Q_J(TTKmDO%7wtqVC!v75a`z58XFG}F1AtYpP$(Zr09`Dg ztKOF@^q)q-Q$l&QQwRG+Jt$&VOi`85-*M}8uHGUpMk_~n2S>(IA75?h=9fq&t>VM#B-u(}A zA1Z=>OdS>YvK5~BkW-B03kzlB4!BrUc*t5=XMCg(6eAh&5we#ZC^CnU<<7k5V!0LB0iz7K z9WecGnl^l1fN!B9{wZ&Z<9p=ltymMT!xPqoyK}}y0@d8qm;)*Uu~UdoV9+2zQ1L+9AF&nv3LE-h46IJHiuKej>@z5_GQ z<3~5VNm(0R_&t8NnQk0SvZ-$9jOk!?9-dN+MR+;IkRn(CiH`x6sXz^vser)$g$gJ! zRN#0S;$c4)Q-Kpq1=b|tVkBQwKt@glENY?xUlIb&zhUSdQ1Q(`kvSrfm8k%gz~{cF zO5lrthgSkc3;YSPx3rOcq@+^1C3~hJDf-A>UXs1coa`OdUiQZ+dZceEd-E?FSIL7> zZ%k>F{nA1;mwgQ`9?vh6y<3~Q0NCxwyinuHuFlTtDBOkOA~tAov8vWiR9S>p}Lo>cm*|E6j z9#XU^hi;yM-J8J6I)qv@o#zqav(M6S;kPe-AS=1N6kD94ux2{{Ee5lta6kN2HES`` z2eS8Fm{AJ%$8Tb1Wo>%n2gush*3)H~m=bb=p>u9uTezyGhR2$kaRcH3v@DgPw{#Kh zK%&jPb!glK4%{w8y?Qb9nn-1`80zhHId(^sq`x@LPGgILWau8kyxyJf7Qy# z!No{k6mA*0jpb1oX$ zHj1YklJ7gocdefYou#0L5r2^ncj+&Z(Gnjcqc=WQ#(=!IQ2lGr;~c?pKxUsrZG~x^ z3cf-IvL);?rbU8&ozNM(t&cNrVJ668s$iyYZK9 zkmK(+#NQ=Q%=Vez046eAfyNnP_V3osyey_X|fx^GPs6(f1*^jBwm zf`UZ)>P)#t|FU=S@`Js{XZQyc++!%a7z3gafqhkRh5|KZ9~-nSI8h;UGQ9KN!rW&4 zMeaN(u+EC^E(g!O?(PL$%i;_s@JZ|wv4@|?Tw#H^Un6;A?iAdnyx%nD{>PXrUzjT+ z$6Sk=F!$?1P>f_a=6*wf0l8D=io|FfpP17iom|`TEg6wH24qjK?HHaJ4t?Ss_lQ`_ zX(Vgc(1valp|~0>2$R3T!^y`;98g?O#DMaLVA$}DV5Unj&BG+8X_I@IwWD_fer@nS z>HFifeuu6f(g1!I?vXMZU6_j>3*5+TO;YzV+a5Wsjx*SZ&nL@CF_F_rWX0sf_BPYa(7dgqu$;qN7aymr_IK+zKO zkeuvkQLt176e%qV7Gpq^!trk_jw=cu8?-GrQz0n|X?^y%f)u_s`ghaV70%ios=|tf6h`w zO-s+lZRZb9GQsE#XO6Vu%Pw&lIl)*|uvr7}T}#=eFMD`4pJ7)6*vz@5?QTpp1a#3F z6W49$E^&7in|#$vYnpn=rGAc5FT3myDEU+wGbEu zMXiH6Lfhi=geT>a4*H)gE)6XzM)K0!r_9_qQZci+Po>v2G|N4^V@L-C@t@%a$T zC5Bl4TlG>(sMiIGD8|jiZR&N3sh6rA4(;NLddbMCmqks~>p~&mAg_!_j_@Kr@%QB} zJGur0PUI^ZISvaXFJFn(*13n>M`cN&h!+c8N~RR?)I`4Wk`~I$$yYHMbNab$r6>8? z5Ydnh2$aZInP-rYlW+4dOOfv$Fl#@4nS2-WVov-6Qs5$*OE9#!ET>F!rxUp9H_|aH zuwRCH{9p)4_PK#DuL+B)I{`r`2NEXQgim-2eWbkmx8^=bUJK|(+BAbJKb7{mz7(%et$PdIjFv@2JgYtxFD3CX;PgimCQjc(NO`46Csr_#B8Db7*USHqYeV zQn4(u_ScZdRWx5z-LZ@m*R zH14+y8*6?@E?~`%WJKl|kiC&L`;sGvn^BVnVq{+^c*T})${9tJm(XOKF{F;Gn%2AhhJKGtT~8J{xjYhA*$Ndn##Fvlb&c=x3EDzGfs zHw_T%lHr&XE40Jc7YkuR(#F_bXXqzHPh}^HBstsac7?8bz>3xn#KAO0x@^9G^$0+{}uYvlE-6_9> zO#Bk+j9->eBx08@UcrMd<|`(_t=EjDcDDNdqHEr-9saOw5MH({@yLU6o|gh-I{j1D9v za>C#8DF-+6olCj~x&_B+?i>*1yI9$f=;ooxIA^VwZe^yab}J!kxABQ@=fj2LqI*(G zR+8nSrOUn6O5QmfLELM-pRRdz9LnY5cfx6W>WlBdUfCZS%pJ*jUM%J*yiA)Ubk>>m zwmX3gCv5Ua!i@Vx!vKrba(+*wZG^kRFed>ECd&wJqoB)&alx;^9mUV9%uMLa; zWc97`KefK*WbPQiW?Haj7iC=VVrQkTuXYrrmie9NXv|nLV^dr)_&t_vl16yn2@q@ERwEtD^rXz<}JoSLjG|?aJNq?2GS_5t(B^_Oyyq26%`PP^?PdMfHt= z<+L2^1l(skN*jiBra{N!k`h8YjPdD*OJGP`F`9F(!zsdhz^5Ewvw&IaU zW%NEiY{#XVRO_`iraG!sOn(EE;vnt&Wk}i&U@>Wb+oY`uuNcV}Y0IehvY_wIv|3cK ztqZpJ&q5ICJNOWvnR6%3Ds#gSNNSJBy;#uTJGg_h_@uL>hQv~Z= z)3>31d`&V(I*H z_%e{nDtX8&hS@^AYF1GdzQ%`Mr=!Q>^QtDW*o9H>NwLtUeIQY#`av{ieITPaQm$;d zX_-V4zXE~77``qn&^#dgjuCc?_~+7W7$e@m7e>4(BQnQ;>fTQM(gi0FxzqD;p2g6eDOzq`(aQ1 z)W6YKWDdWqf0OF<7pOMKjO{kMG(D;xz6JHO@p~Zgdm;1toBZ+!UP}TsLGXIwHylAf z3`_hteBq}FM~81xLa0{{RK5D|hI)01-A0n7?DjOzA%9=g@QMRT1aBD~oJ|2{@nsB~ z3Fo$yDs->kH;+&rU6n!`=nob84;s+36Z`K3$naB9ylr*ChlCqmQCOY+8FEsg>S=6Rr>E>s!sSjrmyscluEx< zQB>)Fgxe~8?%q$Qq~%McFQeZ3Mp7AARIt4Zws^D<6eAgNi|l0wisT5-gbWCior_(p z@9xI-x&vE;CRh5YXwi$T_sk-=G8l2)4VjTO{3i)<2R36Q+G2=GH^xCkP^v*HqT?YNUlLrv#$;b^n6%}ND;5qzWawXqXCL-R;MAk$m$<@_H5HIWD z<)}%kB74Ghgm+RSx%+ZpYhhD<>S{ETODh31i8LL5CzpA(FRGM{A-$|naXU0KAf<3I zm(_5AwC!%D1<*l~FEIki0uDQ`@E!`n$uDrF=O8H96L7bL@8dUXHdiW7#5v+hW$4j4 zg&RtR!o#K)WkMzUwTGw`i?uI7XFL@!66wQ8)MU(OGc}pw;C5I3S(~JAUV*_Q1~G9OH#tLM5M?D`a>-(l zs!6Br>D6lmfS1V;jMEdlGjURfwWq5nJ5_b4zF5FAE4OZKt@MRJ5}s_tAz^J%P}$=!uJSCzO*cg`{1w>$GyRp)(o^S)!u zH&t-&OZ{w^$C_`d?B17~dYpC}X_4xM;6dsIDiy7X@Yda&h14sLr_#vPD<1JU5bFlS z{=LpeMA{=YLPSG{yZsDOZPf^-;{Gt_IQ3kNV`Xa#;LkLIVOXun%c0I?)lS)!MczYm zK%?69vuB)NR@nOg4ZfrmPR0dKAn-DBdIK}x|KE{4(CQcMMGeZFr~Oer3UBini{D2q zQY7K!slF8VeHAA2)4e7O(M{cP_hq%&bIDcK6n|VZP21X9xQTQhv9@(JB3hrTwR)RU z;Z&d)6s^HG7_Var_TKu|%la0v{{&=Zdp#8{7)yvtW=`%u*PzR|c*zZyYUwVSImJrg zCBcfCue_H$ACFQCeP}IhZ9G~KGm|1+7tUzh=!5_Z5%!pxOrK>*yD*k+|L>$WbTvEu zi3_G-lDchoG9o80P1*NTe8UMK^2_o|S$hySSbM0-(AvZ5SZwX#0xP@UGzQ6+k-m&v z7f?}?wFj4W*HBd*Z;{~|v81Gktv`6dn4SG{0nb!Xy5^-P?T~_y|f>*?7f=O7horKSzGahe*(^{t1sU6`3_5HyUx=NlzMRgX>s(*iB69 z8d_Z{Dr>5u9jXQ`eU6CY;nHTE$LXfs4l=?!QA)_toUQ&2<=2lOpW-)MFS*{J@EX7A zgh}eLQ>2%hkzl5TQ-VHJJ#(iz`IzbCX9(=ai_*!@ir8pNzQL57PKG8dftRL}iWv2p zPNu;^)5&e{Y1GLyK%-7>OTa`Yx05|mRF)3xD@dx7mRet?ljfe3>g4u>O>}Z=?1@ee zkEU=VbuuL{SMf{LT|Fcg1xmaW3nq^m|Set|@#yiOx2+BSgn}fRr z^>RDr;+^rFjgA%z>!8=qxH&FG?(FL-G11GZd-dFR0ircz1vehBd%JsxX^ys%pN(u! z_WCKpj5&oa(3$R6F(;$@U>S~=(^2PAg76XE7O!uG2E2Y>c^+r6ptZCA4rjO-dxY8J zk|%Yx?MW1!(<_e7*>EP%pU47kc%$^u~vTTUe z4}6Jy+x@f?TEfW)AQ`f*QK&70Q-e^?$SF9S~|U*5>)$Q-a~{KDXrywKEca7YnLppGau8iKtB6j`r@pG3_+_!)TWX5^BQbNibYN zsgABpV9&!;Ss}b7{BR5c12PTB+`n)dfKkZ5Au<{BwwOx6a2|(g_q6&e@!&h>e2Rt7 z6s8_h7zdfymoFT!IHUaxU{7Yc(#T{}Le6Xyx&CNUERA$*Y)W)bj%Y_(Li1lo(rgk4 zr;{UTN{-11;g0T&3M=fB(A`<6#g6WYtBtb0u-&EMN(vQxSO0@5WIsd8j%SX$HUs4G zZlc+hL~U8y6{qg?+#PdnFGp1|!)Y`FVGAF3-o&jHuL@s|bY)Y44gUuz{$g-Gr3f`M zX|H%8>DqB#7k4G%dfJ-BwNzWe@2fiG?XDI(_xy3;EfYT0#l#9%6yIDOvN>sSG(=_H zr&X-1gRtZPrl{dc1n;VhZ3|~&>27zzX$x1OewzELmD4t_OG!DMUYN43o9mv;b6;jUuOdWvl5S(haituZ?vo?) zVd8TZ+(y2|!n8ym5IS8{P9dRAtd6csfbep~w-!tYu9fRtkL{W~>dXh!fl%9dpQYhm zMYykS!2Kh+I?;0_^us*v%m>`FKM{9DxG!nIZLbxcqRC${Xv8-A>XyOdVWM5+B5u}8 zcM+p@Ck&XmSU$o2)c(N_&}X~cT5*VLoJ??}0&r_Wv*6>?=z1~wieYwd`u$00=Rgu$ zp9JSkzq!f+9qd7zoJz6pV*8%JH`r7>Xo7o*;r<3elAD1Z5bk*nx8UGjRk-&rMkgA+ zui5wOe1r8Guf6{4i79Q+t$Tbc*OSi*YjFXl1&3rF!Wx z5pWhf3oe0C03H`$HE0M}m8JblLU zeI`MYd_SdBR(JUpoP4k0^L;u_UY4@&xA_JeHKa_w&oaO7@@)w=Sz5lIRlc7i8Rh$2 zi*YvJC~*0HUIcts`M%8M`vn2kRK73w`98-I*tj8e~pvzReapE*I=8 zDw(OqC+{&nZ*pH7l+E|Bg;mWORErGni?u4j;&u2i3Z9SA_#WfBK!zQ<(E3^KyoZuu z(qugu>iMsa#U@RD1TT~K80E{PiHzLFCW{I-JMXc95JdVW?=gy~WR6?>;XOtX$PG+< z>!l6(=BBn6;7qQd$iK&^9N3JNVi})D&S)>~Jw_vtTWU5UYL+=2)A+Gvj{XX5v3xZk zl9`*)cDeKKF)kE_$bChTo5$7$OufhG;L6;v_ZXF_VN=rv@>A1Iq%-_IMwf-*^3i*Y zCLg`WD5+>hzNvKldyE?shO3kKrdNm>=CY}dlX~NOj3c~9?(6#BzsFcAaNR?A2FiGMYE)If^8|<9!m~a_#72i|Ls#WbClw+l|`GvjMbJ^b(XQfJgD$Q zv#G3nSl7^5w6EedbM6dQ7v*%{dg)$OV4EPEnG;9H3c?BJO>xFIV)yGknj?`jyqnsW z{gGIME79V<@J+4dXe!DsqR|~>xr=DTT@CwxBU4&_9Tpq*Uu8w$J&_#ASA}0@?jjnC z3_iPv=GVf&K3RE%4O7ZI&ikkCeLPxk6xnvHjY#4`TXgaGI z_mYtHDGDn#Oq$qPJ%gj(tkE#xWrVUpSY%laIjnt!R@&SSh!Ngx!4KujxG|aUVt%{8 z5^Z5DdN~6#KM$*Oc?*&?41O<~F5d(J=yJ9Kcy#qgL?TzW;1jAp z=dJ4J6{A~$PH3t3?g`e8up8Cu3+cePCpPHA*d_;hG07Gc;b)V>9Wo@Z-zt6)3H`)` z`3;qyNWLsi$*A{oeA(?ww5W{Jzo~tRzY_u;SHXyHm%VHtSO2zFuXi+?Pjnmp(JE+j zxEk~oqnj1pC~wG07VJ|NEOXDs?PqW^;2HqaoCl9ky8N8#$Rk(%$HWTJ5pIPez87n*c9S!_$ZEGgVhygg+U-v& z4yx4dlSL~(_p3a(iFWZsH`D_{DUkagu-HQ?OgGSbakZO#wIxVqy_YF?S0XGj_#RM+ z0J}S2;KE-`|6r5TzhdPG^ywj-Hm>$ojknqA3^Xm)A(vfKtha?26Wn%Kv}K(=&%RKN88K_mxzl#*RJFm2t^7nBcJ73(V7wE zt6usuklG%kP&*Zs(&OSoKHd+Y`P=(ELdQMV(1-QJKJC+mmMd|37v-1 zGR~HJSGw#<#ZL&FmNk>FTesZ``R%|@bCo0I+g>;szn4wE&q0=#ua4XO)$;YCegDEY z*vtgCCmEOEK8Gp6?S<(DCjldT@TPM-db9yv?I7tPJUImrVaPmx_iFA=i1~~On$FH zNkg0b1bL7DW|?%~x3I^j=KyaA@NGT_MnNLko3+V`nY76nMhouRI?THq_xTW z%c3^<04%;u7EjbB|3jwQ9HCAAVbUfmplg#qE~IOd#fHW<*$6E=QCkjalMjYy-wurM zZVL{PZ@TZ}|Jo+MYWnvsbou&2ZSucy+Elf-YNDDqXnFZ?{jnBWe@qB`X; zOkbx6DRs&RGSnvjQ}NX%|H9LbyRl8SRCx?VX&AM%$wxwgm&FM0w%`l${U5i< zuNlw(Lo!*L{Im-Es4h~D+?VB<(eYU##RKZtmqLT6RGi3_@0RI4$GU=B{*EoBRoZ)3TPU(?7mVZY%I$YvFa1Z;7;ho&J&fY@8YZ67hnX zElqadXqfF_za*=TtxLWN2Kd}1e_KwP$1VYLeGw*Acqh*sizJgV1S)dMRi@u>7cRr; zVB1f5zV%}|g-1=P@yxby$72;O5(VtlGpcM{p6frQv3MqiVAhEWS9WczBDmP$@<6#@X$NOJ2)F2cm=sUI#@P^ zH^b-XX)fyiyN_1Cy%DolK5{9#hPY_U_4Z73WmkU6t~{9JsUO_!A~Q4{xU^7yb8j*( z)^ziw{OFey#7FhjrRUnKdnTp%Tz<{_fbM(?$G;e(l_8M&qEoy6{X0%igP_Xd6q7VB zs(o07AZ(zv?_|F5c~s%zIPNU>=$Ch3u@Wq7X`+wRPEQi^nul<(( zYP>vw)wn3F!9|T(x;@L1e6-Q1GI&g5nBOVPmy9*?32r7#c$`|I8N%G~iq<_+UcI7K z{iZaS(@6vgk5}LoJsC2+ZdwC04^9N!`d#taywFnDDu)>0zV*^yMMNYbCbq{4!c6L| zL<(->6R$+b>fcoUIGTVxxE*L;I7oPVDvOQs?!?MsEDSYtql)PpCsyVvl~h-?IQBat z7nU%r0GoWZJ|?r?yJFHwa?08foB+OfmN0Oz0VAF%d)a}E6b>!`3co_YS;;F}TY^~7 zzEGocJ4T}6xjpY?w8|2kD9~(d97Ewdcx7IJfaYG6oVF3>df!aA=sMv~yTt0f0&(aI@2(|!tZd?#V$Ybkf}0#lfjK+G zH@t;p*k3`uInH5VqyL+v!n>_HdNTjL4?p@y-txe3>Sk)Fy;1W&k9Tram!LXYV!pOr z=V@Zd1!I(IaHq6u1p^c#nXc8FCP<|3+=(7ecW;!Mz)@4w3d$>M1>>m}-c?<)e{u;| zTCNns3LSP4hO%o6%1M=I)mz!sK4mNC1Tktk52FZH|MraWYYvs~|=07cES~t+o+e)qT5qL%VQ1@MUl(e`+&V;B3oGep_Kz_}tb8 z*2zkRz3_X}+RRnS*|0YA9U|GyTE*H!C}zB-G9S6`8rb8L(a{93x&WsDK(*v)+j3M1 zb6yl_3bcIx-0v5TN^5Bgn`=q6JfW6UBe`5l zs$8k2_Jk`aGO_Hyh&$`$X1 z**B%u=D7oFLq9Da4&9^y>ZONO>QADu1zfvZ89cPHPWYvKPsVrVV)+DjDa>JwVQ$F6 z$S2^+(n`QRl9hNZOGn?dL8J|J(wK3v8pMpqZ?PXVk&bKR)+U~+GsPb~yfMuivoz%s zaD`N5@QB7Rzfzb}jCT11T;Wy;PsJUtC(Iw;$SFv8ngXxy$&l%_D{JKb1WuoeQ!RBh za>|tZ)=Q6wh|@&`jhuqe$lXVzfXiqq@dkuU8ademu4AE*JA?4n$bDNDJz;w$7T?I3 zD$vMnu2fPr>OhUS8-+%$(;7MXs*#gf?_D)% zr(5`jXA!WWkrULd9atl$#RF^PZ1uo3a*o2T_%lM2QrHEB+|~evpQpay$trx+$jQvD zA2i?5)9&R*1&%yUNDK*sPJ0B{*B4O|TUseFu+hREl3q9_J8d z=3@D{M(%sK)yP@20Yh^xpsbM-sNT1FzL7IfuR!b=!no3!sU3TH6nR7=C#V`Z^GzE$xnPV^b&Xur0KC?R=^DBDf<*eNku!K> zBPXw1Be#x)ceRS_{ns`rwGL6fWiuI;U6ox6Am-x2r9^lh_TZM`ij^U%WtWBge$BXZxZvBzhlnh9Vl0nPz{M#B7By+x zb`k;(eqhAg$X<>;NV(!5mVHxdZNAvrHpi}d=@}L9^C?#W*G*Igzd+t|W5VCa_X2!p zE|yQgvDwOC*cj%fER1{tj!aep&Zt%5Z7p$q^X-|Fz%P-2qrv!{8@U}{bnV%8#8b_s z_=86^ruo|}P5A^Iv8fCm-5BO(g}KmZmruY^mP&XL?s#Wm{`mGxLBj7V@GhPVnO>u^ z_KYJYv}eZN?^)_<&*V(l`>cq#SVYjCDG2Qu=Nc*j8@?;?u7ph5GuZ>SYSW%ELFC%A zOJq@d#+rK4o|*V*&-PU+sp_abW6w3J^B?HN0=p>PkQ zP`+xT;3Nh{yoc=NSaxZJXbi7VHa>^V+A~=K_9gK)88!}P@Ez=L zREk?9j~@|c=3@D{_Kf}G)}C3k0Yk%nhFp6lP`z*ce0ye~UV%6 zboan=9AI0#Ke@}cpszJjj(9(R#MRAiv-+yzC_#4qIB? zG7Vg|_B>q{tv%1c;;-`)U0QoSOr}ciL0D|<`8kzb?Ut68)(d3Tdl>_{sY{CqvK2uV zAFAkjGl%u5>1>)spypq8L>ktfwtzLUWQ}ozcUy3^eAD|GUwX6P_xjAM%@E%Y2Ybfg z+?@~ky#}KT)8RRHN-)oSBoU0~#mX@-Jj9tWyl3rf6SLsFV#YUk-DdA=So2R-ie=g< ztih^n6m}Ok3q1|`F)}n!^#v?;4&ZsK^DZzc%a;aSMsAz1MFpE}6J8_)JUfP=w|G=? z3>29oIvS8xxoY1t!S>^tn8v15iI7i5S%c2gm+tTtuY$qCz9L{FE6x<#M;v1QLknexNOyP%_C&&;pzl6n@$(DB-5XVgU z!b}-CW?IyQnTv&>7|GDHTY8M$59iVAbx%@z&a*V7@XdO1y3Ld%ZU0vK7_XVx7=;fYppn3qB= zVkUW1vW9*&bIZYi?lB7UR|V$p=g_)=<$)#B#!R$QF}h6Dt%E0D72amK@b`0|9!7`= z3%GK+6n~pY#mIe6!>(HfoI5-o!o$ch+yywjF~EQV#1_Cs2_m69Qz$t|#aYEpy=bEv z!BX9wRP|QQXl_vbt_;1)gGgjg&a_*|Vk8{#o`iQ%i;?iedjLr9RaGo?%7nw8R!8Fv zqf1WQu5qeRH?j#|$QBm6H%*zb+y&eMUkW+;mw}Zh2j<9O1|Af6Z%VqZQn2`fQ0hYt zfo8!L0}R;&*;xo5ppc;*qB~^MAaz1cI~+TWQ5mfxH6u37P}QM3)&kWXc5ucj57+m1 zs!`I*J~{o?E0OGiM!#P$9hI-T7cz6zhec+XVAaRj!Vu}(wL~||auwEb>iB1(J4Ye< z^cWEV&7s$S8b!x|A(a|0Kj#Ieuo9SJW&$%jE)grxS1>O%@pY(I zy_*cq0)fD@lQ|Tv$m!mSfFhC1_p6+8u(&V}^9js6{6b*51M4*4gV>Wltq-(PFLm>4 zaxbo(ncj>r?^18o7#-831j}$?ZOkZG@A)zA(TP;ncsom@W0rCCtRG9CJnN?rJnP4f zX`c0yBUpimI%mwXCwjs<7vDSW`+Ir6Ovu?=a$UO|?!mtaWIF4OL5I$Viym&B=&9XB zz=WPZVuzl)WwgYcRTDk;%IJ;%Bx67!poblz&?ASVhXn7#!3Dqqv478KctDP&(lENP zIy5W;4cYx07p0GUaf1}hOd zKrA-6gO^N;yHlm?v7!{cO0L+N50DO!jNAq47L~SVU9iQE3PCZF5kDq-*?}TCg6SZe zX}6Zo-E-FR$sK3!Gr+JDqoO_d0}Rm}XKP$4=rRv*zmdC>B)nT44K6s_#e5hBTkd8q zm%s7sK-~zH4g!p{vLj?bSz-`ym6am9S80TIc4V^QU9QzAQ)}~^4i^?SStmOYKQ0oX zZ}m87_`cOQ2@KDkP@a}9t|#$kXFT6bXo_-4nKJ{5%AS=^5S`LQbW3EY`5VGweOH$5 zQqjp5(aFe(&Y~uwV`5IClMyqEB+(ftlH-#^r+oZRL}z*YIMEqtWk-l*sZn$zyc5yo z_l0XuIG5-wMQ*Kyo$-6m# zO<4;EE=>r$xbq7CYkD8RBmV)N!-Zxp?Mzz@d9DP*PgM!to`M`wV5w3}Z z^r#2T^BwlRlW%apG2MC*8cVAeA*Sa>AW9OZ-z!5*za5J)o!JwZej5q$rU5{hE+f~4 zu&7|OF2vnJV2^W*j_;Da92>}J4Hwa;2v;R_M|;?h+g=v2lbZD6SI5sfIvloi9i26? z_+>>)i1U*jpXQP>x9A(Zc$+j6Jfou}zx|4NdcR&;cA#PExpsj{#rR{?f6Cy)9H2hCbJp2J-uA{s4BZxl_SF&8F7(_3;#)K4ujpN92_BH zZ9r(f{bn~dic#7`J0w%b*0 zzU0aILKAXygGKJCFh@@NFml=%vp8~|w~QM(JQdcA9RF09W8uIfurT-+Y+)>#3Dk2g zqcYTW*tI>NuWM+xS)R4wM&yOJ2HaJ?l8Xi%GW`ZH${|AtBe@)#7&u#}oO8BLhVhcy z{=P+r&d+;%Lg2RGB!#tkkF6zS-hJSA1VY#V-A?YK#9FwH-vz+8!I6wZ0NEbieSsaq zFPkS(zkamscVd;Pr`9=E_uPz*cOJu4I<4_87`bb;o)z(>NS=BiMs|gWpEvt}>|8$6 z1&-V`CePrhORE%FrXtEzBrcj6kbm%RB$2yyNM-hQ#!DYsnSFr}ou$bXgx2cw%1SYM z8Bp{LGUT8^SO5CQ(S7)tu2ZvjS<=${<=iarUx`5}x6-`qDrt8k5bq~mW^K_nx-Ju(k0}t%0V|oMr4lYCG53rsqW&}3~B^F z0l^$8G=RY2dt_Vb>e`g9{+p;Y+&Vy^v?;kSASn}DH)ds=m2P{5wyM4SESqhADzkX zQdk!xeq9+UMlu3s*DCQF0yx<`Zdnx5SJ|j#v&tq{y8CG2B;g_t0lq1dBi;iq zVwup+wd{<3schUOFZWG!ebTm6E&=MHlwD?Q!_M~I$~l%i+B?H*$1a5c5x4a3Sc zSDiRk^_x+{saS{yovD4~k^ode%HlzuYF&yq@*G47##^!?f44m^49+e*ozm{{x8L$f);j zo^&8BCdgJ*h4N3uFGe!rzsp`uKyn465Xz#@iUNd# z6hJMC4A!;Je&Aifil9hpysrKuQWV9=qO^E#uJxR=1~glo?UBFtAC14aausQ3?b969 z3OA)$oK?MTIOivS0<1~XOH~rI(_vP?Z z6m|jfWRqvwmr%*z4$#8tz(Rf}U|)!-Kb1qK!nCHCo70?E+Y>P_{txtIJ#$|5Ov~q% zykCW9i?uu9w`uG$KkL8$jI#TYmEDgB$2>xN+Uec3s>cP8{-|!rpvX5@Of?KuF;#K3 zV(ON5g@q-X4n@(DO#zE7*)Yk3I&BO_j%ER*8kLbNrxq1#R!-Z6fLEC?;?c5~V-J$c zmDA&ra{72$$l_KPPe!;8{T7?QntzSu!+`sy!1FWU-U1#;^9%pPkAU|G;7KKbyv9tQD7(m)bF`Gtv7< za}*w{Lk&RW9e&9>8p#Zg8vzz+;qeB}$O)qn;Y30>;K7!tn3Gm=VeGw>`rJ0F_$m;S zrN1o*l6M{MVG$&YI+%+(herh)<0QzK9BuUQdB0cdJzvhDnA_IB%jJcmw2-q{;J8iv z^4cnUzyujP&P;KSgsP7jmuaQW^p9~_r_iLjtCcz?chKE$m}ge6&itCU&n*QmS>ktQaRg^T zDH0us1I^fjLy#+@M^329XTV@d+rosw{F%I9P#9V(*RZVU(L}F+o+ZI7j$Wh59>Q}K zyzLV)nk|#eBj#R~&){hVuk)T_x5kaS2TA*wS}{N1r$5!>g00D~+1ixGXKS!-HOk~Q zflTG*6BXHySWcuJsIG+2tKa3rh~NZoIBkoB1mySK?04FT&)R0YDF7no+zBwF$1S1{(P4BcvMB( za{K>Hs2mpSN*!;Rt+Bvbf;UF&GuUBNmR!inydW;a4?s)EUK;IP5Tk-Vcc5Inn-o5X zFCNB7ht9a1KolWlb*>ct*##7_1202tumLPF%{3NU>h<3FNPcK*DjznMEk{s~y00-E z`Gk{1m!A3VzhNy+i5XA_`vXo@c7_itJ#?5u!-RJ91k6NGA@%{tkJgs;c96RCJhwZva&x%FI=Wikwx5;sejnsk^t1o-#_wG6vDHDfKz-hO(b_ zs3S5wJsaSrMCRsjRckvUJBAV2!St0Q@?}fRh-4188IkE6u49zA6LP6WGO|t+)>~#a zWZc+}Te^ffFH880JDo|wL%f1&siMfuQteCzvH{X&%xCDz`4Z{)-7`9W+I(rD8tNZ% ztb6MVr@-j<_|fIGbqm2&g*;1*@e;pm{y6O~+IVgpenqQ)Rv>R~eyCUhkhQUWE>RmF~)Z)wi+Oq6eoY#ivMtIjGGy_`EgAm(Yh)U-DAlm%I$q zuO}&W`jUYRwZ~JjSYMLCChB)MRUn5{0n(S0QSYU5=eAf{RGO~K!4|J11d+Z}A$EVp za;iPIa(bAfC>w;@Lk#MhNwqD%5!x1s!Yht^>7_k zt*(c=vM_Ln6(gQ0JMR`ckX){Zduh_cy^oHLABoOVpayO_i1GBWtec~WAe#}=$S*h5 z;d0Ox&la9E2W{klJ_0!?TMp!_A}=#nsudY@Zbk@-J>f>dfKiY%z8yUs^Dp9B!DJ*Q zHftm$(@*BA*_Lx?vt0AMCe5={#Wl~%7c;DL$8%6tv3scCtuf$Y)@&@ucr2N0vG}g{ zpa>JX!U?YXV9{-k+1_vNR>3oVm~QoS=NtD7o)oTRpNi&3mPLb@VHh!X+a1_d{M6=a zk2L=_rqcw)b^H`J`BPbZ0@$m$Q8e^`Ug^| zzAJ&&4ribkuSQtz?p_rmxx1V4a1!y&n%z_N#=N!Nsf_=sA43Kdg%y=%}sQ#4p3)MH2;{tuUkah z?hC~-W{teBTiX9ZvL@+Ypj5kI`C;DUQ$)=&q|H}UePl=*cscHhuO>cZ=I-mZ$jp4$ z3zTaJL*%}q$X!aT`5oQY?cmDXu=~1|sbTkZ8>shU3VkzyOiZVCT?6EDFkB?<(?99> zdIZ>*-*oH?$z0=E?N)p5e6;EEr=d5UgJ`RC{(r2!37pN<|37~2I&<&LU=~C6OLQ}g zCCk`CvSf>7L`KqSFeEBbWJy_DXt6~LMWwU|NtU!Al~O9&OB%oD>vfj*`z|y2 zeE)wBciyk}^K7s4IGdQM?Dv}Emb=GQw=&VK;hsy89wtyGd zRPsr;%)7I0}$sIiq2{p${XczYYa7QniRqpKP-@IjDMDio&o;st&1x)wwKQ zawS!#r>*i*U^B-j9jy4}LLK}PJ%7om%h>}g&ZBV5cx@<5f7VZOo#sur%>GQH>iZ{esA zT_;`QEy&f&a_s`%Or4}%-6OMTQ<-p9kag}x&OtZ+E9vf_gSn+H8U|H0aSp_}tju*nNMG zz#%;m$K~D&$(eE|TYMUYo^=Zsz5G}y1d4 zj6J7IJ#S7F#y(b}%Gm4Uo*Y|R0T&j_5$0X09QVAVoQ;y%_XK_qf&YwO09lspd1hWA ztql%C?yWUH+u6^|yt~PLL~-ueJ~#!d+O_>I9N)c{yp9R;DnEq>Uj?wt^x3wl^d#T- z+^Jd;uN{8I*Ot1D{eBrv#=;-HafPNKq98Wa`t!;`Pw6XilaVstv_LErc^{_XVLx;! zY8H}9@L~FjTmlr2*qE-^(NAVevuP%}y5p23f~G&}N=r{m%gd)gnR$;=Hm3r&B!-Z) zRgC;JF|3si__iGUvma@?>XvdT>_LR1J(HPvt0~^Xuy|2UshI2k6;Jw?R~}idDTNWb zf~qH8NV1^{>V>6p8_nHUR0!oTgoD-Ww~$^ctCtygX|{->tpWrw5fFNbR?x5m*&C{A z^Cw{p%e|p$QSfGh19tdjLU5{>6Xj^P5mLjBY$xPOJMs!4H3^~Rx>`igw+sD()?4Wx zzJv%6Up3^vSM%T6{5OOD*1_L8eO)1XhUyw9lxdOmK;~^gLdsWt%c+4yHnhlVC;)b) z&{;640aaYV6_Auw`jhdIKNK5X^hHlurYhD9By5O@Ffy7T?mGLk~~Ey?D^>J$jYjb?gASpm&g*SBm!z(gL-=01N(ZoCz90 z4_85ddbkU6=ux(yA3dg$51a`~M17Di!O;i^Eqso&@IH#(D6y?ZqUIWybkilSLi*T` zfb&41#>gQ)Gl;M=t1=19t62k+f7f~wLTme9b&$+ z?lFo1Utq+e;8CK}3WyfK=ryzzyoH)UC^kz@?U#!9h56&3N2y-Z%(SH{wvS{}De+n8G>_Q=Hqh7bgq7W2M(0n!e3*s?6^`2(J zmqGlE@%zAr1D5MYz`n(=72dCln$L?9TQLWX));ko6>_TAk0v=|$4G))sbZ2ecBl;i zGM0~nA-$`Kn)^`O7!LQKOB%Y4974%bwPemXx_O8p)DDhPrYetbavrY-%vVIUCxSLj zcK`uPNuv#!$Ap57BpFkl3CJV9uY>aJ$d-~^Y11@4i^^u}?KT`3@!@=szu-BF!I@8w zf~Sd2lMpL8hF>PzO39~dC3ohN_fRK9B2`@i3CC%Uz_xf-U0%eyp5o4@zms6b9nfu> z+{A6WYTu@;RXBMQnc!S*1u0-~Hw42KOeUz$gpiVu4W*~Hp?u>lXEcqi3$n$!#EOr* zl5POoYObs}Uva_CV!Oo=oiU5Z!@mdkL8B;_bkO zHDhFx@tx`+V1GT6mU;67|_Z8?Hu(f#mbA6|f~t-Z#&8TyhIh))f9gU6@`@FF~B zVemsbz7;enXnKYx_z4^n;+j1P2j!9;`V<(AXFtP}W@2%^Q?FzYHyF!rqX&)UH-q4@ zd@AeLovdHvim^OBipru|$#r%Pir9`Lwt|-_0=`AXO&6+=dJ&ZS1efX-A|FV?$qCw@ z!q=?0^G+ZfI%QVoxQ8NhOt}j-LwAa%x$9ICxxQzp7Ze^(-U9T-qGG5)ITxZ?o8T~{ zl@g~qCUK!>AWLOZ%=$QO@UYF~a+WZYyPa}i#6g4mG!n;&vG~#`oM;&+?@ts4Hk-#V_P-m)Sw*UoYF~F$*I?HO8OC#Rkdooo7m$EBMhqwBF5>~3F4kJ zkqcF~?^e8IZPxm4y7$U+^6MrpOFy64nQE?1ri&`3uNGx1i zFum5ec)5tV*7yPPjIKEH>_<8(&)tz{d>6)_kK-Et5KgnGZm*t`HLHnjIb#$OZ5R$x z$&0w+h+`*=b_LGa1l;5ZCp2L6u-R-1XH7<%1XbEM!*dXy(#4fq6;OGQJ5FaR#g5yx zUemTusL%+sT&Y;A{Xy*V7U-gNG= z4<|@}O=j7BD#aF_SqnFr)p>p3;n;co26;uF7-=P%D>D6rqbkvaEd9+ zsriGI5vdUeho4FbhKs|(jW|r%Z+a=VJgDr^eTO`%B0j6^E8#64lY8I|U(33TvZvsX z$88jZdMg$2&hn_D%buJd40_ngp2O*~znv4Lzb3Qn&as7O*1}C@ODOx!I77<@_Ivyd z4MYN%ha7}Q;b5YT>8tJH&nuQIa#$GmXJ|Jfx}lI$drV@ooIF`{tsYAGdLZ&|eshqa z8o0Ay+7QIcGkBChk#{Km<_#mPkX(XQAr%>>$UB_e)*xiC6!vZwbTiQfdw|3~L`opP zDgN-bvh)z`Ri&y&dsWG>@?KSGScy9r))9!3w}#-A4Qn+n1sGOxfnlYGZJ#OyLqxPq z)fiTeYHB+`;fB@1m0{I#KXE#ij`>ABbVkr;h3s(nN9!2mDbIb>6Iqk-B5cJeH)0)O*+oH&N7& z;ZKS7fw+Tujz^TFo;i4B`%qom2XcXK(!*vSC|IO@;HWr;aQk54muVkrLGBm@Kz2is zjB0~W9>zY5<-)P9Q5gbn`91L`(mqT8*Oq;tH|!@M&)MYJZXf1T!7Impa8zU;kZHvk z1Lfxy_D+%i4`zXK2D#@FOOX3uyt3SD*vtYsLiF^onFR`InFY=b6?F+_!4gl2W`S}* zW`T1eR}RQ5*rYZL>1eYsHKthzPpU+-z+NfF*k*wR#BvEY3l^@;g0S0#LnuZ%S4j1| z8J^lM(6;H3_(j@hFk7Z>LC!N2;I=R3Y z67|9jkIJU(gQGL=D_X(uaRTyhJm>;S1>5T5@Cf4KN!nUNI8By=6DBc(?$)!*IHvCo zEvIFss+irXgOjiZ;gv#KBIj9T%sX>n&cI8^fSh#>y#`W_j%h$ZbH>yt@|3ZhPa(e1 zDNLXTbqYNoxKoG|gJ=!qz8G>vzmOib!7&F#Y&>D?rwH<8PKhoc$w5~c56iD54JmFj zk5?cIg50ItZ{sm6y%%}Erqz~-;6GRsj0JJx8olj+9BDBlX;Rpn0rW#B!K?bxa+Gsi za5A~p4&Qr4mnqTWO8~`p9n{WV+H`jXRwM*Q%_!?UR1swZ$DwlKEq@aJC=xMi(g~8c z#yIn_$<-gM1=iC;KZ2yv_z6!cjdOUa;Tgq@(zpZcacPXlJD0{)R2nBRJR>I*1wD$& zj?t5=91cZdkSOxL|dHArswKT(>b+U(qc-fgCUi z069!Ud`b@34Tjw_{(S5$LE21yVUb@li9IU*VDP_0K#@WOcTy%uA+g^^ir{K30&;=~ z=wTB9htndsi$dqqUn2r`=h(tUVBwYsj0_%sCYB65Vh84DFkJu{l!tceyUcb#GN{m% zNrAV-VVa2UMJk+!rAl=#QU$o^od))Mkt#|&@?IpII)y{TMM4GGr-S>Ts?cmVF8Z^r zcvd9`EOzE2+XdL5gZjv*7x_DO!_%|j(z!qWU zQiN(#dCRnMnv2pX;8LwbQ8Y;)yEY8~Ph(Cef>t>TL8xs5R5H%wxPmL`i6zOYSAa-~ z!|g{I6!YeIDrmn9qftw?&<<23r54zD0jvgYk=f zUjp{PBOGSVUAzo`w3N&9}eb z%I_4Id$e*@r4C-LrIaAvcqxTo`w|7UHMEjKZH;Xug*b+xtxpd!v{!-Pwx%wZDSb)- zhwK2t*3!ealEOg|+iApFfPHmTJLpkRm*_MJM@;{;KZv(Bbe@J&9-P<|8s+m!!i;cu`TtgUj*-@~m*q!*l|gGZY! zA!Q>|LUS)_$&_}SP?8vAwV($ES=cNn&qZO;116cKgm3~6Aj}9oY-WT)A}%%~lF|IV zF zQ$41C$RC~Y(}!C81#?i|>isUf2mPo7e9gKue=^=fqw%N2>3fP9b@pD0hpNIvymOt! zdJy`DW1RWqgbGU!+Y~N`Ly7S8{e8@Z4>N$aqRh(CSTp-z_`m7Xr_gh2)k`_0)oJHOR@SIl` zA`apEm>JA(#rjUs1n%W=k5x?G$`ufEGTqQZN>zlVW=pGDZJ8FunTju7?piR9bjO|^`fGB`?i^cq zjxAj0Sl8nPkLY@A=1{N`yEn`xFATav^Dr8-ZPTXtl^Y-n`lsht`oMSaX*5hYXU75M zUxM>zV{J!n8nI8Z^o3LGNfw%8nTGZXbDxlPKlo!=d$RaOw!B2_cBl0$a3}u`08Pu2 zG{=HfIWxyXI46#oSR&_u4?V}iz`eBS)E2@M|2<_wY@oOX(u0cYRuF828&h!|KL}ai zz!X4~7(HxLCL9zc))6Tc+)fcF#)&QYgqMT#TmC!t18~g&utDGy5cNjZ>VZJn(hqjLI*vzK2gw=uxEPcU4Ua;v+DafeT`vLx#&8I*gMQp3rh)!(iAuuQB+*LV!28 zf;`~mQ+ipcUeGPFTu#_szA!Id>IYFmUjp)kR>_32B=$99t5tO0*a9_^9?r0bH8POm z7>B4dcZTC1EDR`D7 zC*=Kvs1nmtJy|qyDv~`kAxl%Y?UP81_*KD=HXBmqyA?mW#AiBww1s#be)!m#pn1qI z@%szEEcz-zykYfmWF~Y3iHNMf;TqxaF*|%g4!CWHynez6?Y;o=Bq5(MLVa)T=QtzJ z+Weekga$Y|)G0=&vxy{CnLI_&p=0Y68RLPfQdvvrX-50$(BJZF7kE-7vyMD{$7th- zJe*~;Y6SWtqdr#H!l1kh@n-74B6`tv0M|uWE|1t< z9yKpUvdaN^LR#Nf+B`Iu`o5Yyz=F#@9_4c`vR>5Wb+DpIUECf7X560|jyMob7?P(Rik}4_zKSOGZYkm;41XbLB znW{+T(-Rr6>mkgfw7VItv=Uwsp@TS

    PXm5RTJu-2zt9}EifsyA!9HR4($#>s=+}XB zBf8;PsWi;ig*Gw94_Je2n5_>DF$Z`QyG7;_1oW%WUCd!?tvzfady3u9!uaOc3;g|% zj{W<@JOs{}(9?h-L8s^lj`~FYBmOUJk zEBPVXskGvwY7byF{S2^yo(IHXS6iu_qJWoBE#N|00JxYQ09;B30KZ8m05cMPhdx&q z;v=q2TZr0k(=L~8GLn;#oD5zEm+O6!zg_Z&XuyZ+kQ5I=yj8zLiuX(LBPA%zJp$TB7jVQ^hfrsfD;QyH!1 zQ!ahmz=mvYQ$QEjbe0#Bb(1G%`f{84*Y#)8oBEdCoNnuwP2cX@bkp)>R}|l@i;w)d zZWUKoq;K6^yrTYw;*0gq7Qa8GZR8JA6l>%}^9C(CZ2kS&%!9dWo;rJA?)xLvcNAC8 zZ1mk5P$bO758}+}EmL%-*TDWby-u%YC=oM)1v`bK$OgxxBq*X9%<+s0r*I#0`|;W> zu^1UChF8uyd}%^#CaA*gV=?@cptzeOCf6f2DLmp7-iW%JO@RV`OC>g)M_|-c6HGBd za>X#zTDTRQh+-vpZKPIl3df6c+a`6MGKsl`$K1lO2qI|)K@<)OpGm>OkHm=fu%J^~ zf}pD!yr#IPE$&;et2u>3X2c>a@#T}Sq zG|32?Zo(}btkr6`DIyy8V2I^`j|dSua)2)kX(H(uCT^1YrD)_zpRvVSo)Q0gaZ?E| z-IOw4AB>%UqvhS+X&EX$*1lo=daiR*FQrb=-BlPhNcl$02~|fNR!NFI1$kJ2XEbpH z2>D=|SV66(VzIpf4WL^EoV%+c2}LB`?RQtX>z$3o;aOEJz3zH!n2Mc>MC^oE6&C1h z3?-OU6*zKST5R#ytR(9#Djn^36E1djtPX|I1D5pN+Ls>aEAH#q(cLk#HHH67VssIZ z){dFYnM_xAI^8!booeguTG!Q<%A_*uy1V;2y4JR@Tbu4kb!IYsneNoc>e+*=c(U_O z@fp6SK|e_B+Uq#*A7j+uXM~zr#cf@2{Ux9;%_{Egni4(zgS=9oJ95<%t?TB!y7a~~ z#n-#89}mXs?r1A1m^YUcV0Ol`H7kC)W8U+tGIN)13oV81($y&N#BhC_S1v6c=$=vh zr|voT&VQ&FxwwG$lT!Z}`QgPAxza&1M@HrxWXplzv6R7I7v7oD`0K-e#HA@kZJ@ho z9Y!0dj2!F0>xM=LxNFg|)&ZtLrSRi~u+fLnO{wDHdF_U<+)AktXF;4<_;nTUgVfay K@?$ofGX4)6ziC$h literal 16896 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 Yud7&KkNd6t-`sMQy8kBqe{X^R3-rqFnE(I) diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.Quic.Native.pdb index a73f4ef6cf4dec36f835d3c95792602d11f3df4f..c53382faa67248dd6046d99d7974d104bc505a4c 100644 GIT binary patch delta 2860 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%0DwZ-XGMgyh47MG(G@(wDrM#$@> zrz2%h4i!)dRZuPDbEV|<8Yuao&Vh9=Ixl^w;CK z8Ppc?+#D_W4>4|7D4ZM7+E54TLOrN2uE2sDk=X4K+~m8*$teYC##)hH|I_70|Shuk$Wi3H6`~s-Xr-?~Pq$ zP!1JPDdeLm`TSK-jaSLN4Vsc`$$gPhs0C$E8_J;$R0w%5N?y4Ol~50=pgvSX1E_(L z`(x!4YC##)hH|I_6;Kx{p&nF0eW->8Py?mE8T-qiHk1qb)~@6;)PV}93zbk0s-Qkp zLj$ORlHZD>rBDXtPytN~h3yKhC$x~P{~LLa2d!_~4~kX``LZebvZcQrt7Jm1<)-N# zujE<*mC&rk>5;!etDy!;9*9*^D1+KU3&{i0UyfEnt^cUUD;*;f@?P3d4t1ac>O!SZ zm@BkdAwM(q(B{$lXiI1VA@4=Wds)God^mcQ+}lD^3f-f%(fUH`>{mAiQ2t=dD24p) zy(i?q12ll{dd)rj=Z)p@ERE!3Hhq>GtIvqUC4>)HUz# zinm1Tnf9V+l~CvkZ{NJ%^)=Zq@hW)-NfoIDwS{~(lsv9$n(iM;WAN|!?p+BgEdQb)Rp&DwSbS~D*gnT)ae2gA6 zKOcMUoQZR#At5 zAr!_y%h1|rIa&dgPz5zm_WQANN66>2K%xJqT(YSea z+h_~ke<^|#4%bz3(22K6(wJ98QwNpj@CgdguK5lT4~x3NH3L;&r2U~ zjn+UbLjJsI>yz5c#+T$uLCAA+sDMf#AGa?QjuvE6@Ao0<0Ax;+zDKo#Cv$a@|L`5ZLFC7+5~D&(W(LY`5eb%kV2|D9an zq4i9Ar)U*gZS?)pRfCrPajcv{ZJ~waLy}vVrWvJ>kJ}Uaw)8KwGFo5AuK;Qx@3euE z$D=nDa&IZ*^=c^lbkuUF6nY|=kUgMAYoP2i(OU?4z0$Ow)O`l6h8igSlbDeSc~=G6 zw2>YeO0-$D3ay6HKJ|p2NOZ3)&`PK$!a0Z1EFx$hc~HXZYt!<+d|9G+Gqt@ z7p)ZX<()UL-cL8grJtp;kawD)wb6340xF?OC~OC4=^~Yd7LxIoq0%K z57k0p3?ZM5bSY9x$Sb#@T*xyzXqAxX_R(szM#wYLbCFs?p=Tk_ZKLIA9kc?i6!Ll% z)F*BLHBj=WakNy(N6VlbZwD%%E>uE2s1gcGj#i^JQ2O6uTuUe{Ia(VnM=PKbs-PNb zgnTxV&&A5AkayKW%b+%tLmj9P^4u<3iPl4_(E4aK+5oLVTM_a*wB$UU3WfCnwejX? z9kc?ii&mn|LKRd)GJH73rb1zcgcg#Ymi;J08yE6U+d_T>$?+CY3C%*4kgwl9R6_%( z5%Lb?k&2L!3WeE#a;ShRsD>IL-wv`TW0f502zgfpS{JQCtDz+!pMwT1eTp%lLdbjW z3i%j4sKPrhb_gF6z@+HdA3aErCsD>IS{l8InJO(llCE$ScnZg?E3^=F$2>o~z^;12iSqk}pN-2ziE*zYkXk z`5a6O`Dk4s&+S1KaecG_)IiDqh`BAOBjn>2LcU#18|l{B#arU-33&$W%9`gm)+OLzx( z8@wxclP|~7G9e$WE#%$jLZN50!Zhu4TFBRGS19ysn%>R!&?>ZfBkLJ&jdvjA<0^T* z25Q}^g}j4t({$~((Q@J@@pkYQcuS}!C0^CtV472g62M zxrH~w+eXWUyq6AI7wSQkkXNpuB_kbUKwKl_^^&jBm5|S53#|=xphC#Um=^M<=(>1I zytC%jYyKYI3h%s;cGbsQ;~fZvV;J5BZ*nnumBKzK6xJ$Qj@CgdgnY@nXeDtyw2HVs z+5k%aR~)wmwV@8wg?dm0^`RvppUDAQBjii8VqTre_3InnBCaI%p+YPss1zl)RTdnv!d^kiR25 zKucbX-WD`26y66xn?&oNb)gN@v>L4u@_i=#n;4ftIaEL;R6z}t{w-C6o=$#P zjvM+75APs{3aEzCuf@1wBi#-%v~jdt$k$r|b%lIRdr&15mK?1{8=y64D?;HnpPybazJyvg6`wq{ouLOusAG$q$E zp)hMg-hBzpnpdA)s?cg^#k~4!IQh%5N-5-3dO|)%h1N%_(FSPg<(S(NT1XxdPuu9C zP$A@aj%`t_KbL2V)LAV({p3aX(-$j4YQO|RyXzmHW?Az!u@S{v#>g^+LKN}kch zTcY*Q`cN(8xk{e9Box*;aVzH4tt9#1^e5!GsZi(?ZyU<-cF+p6X(O#XE9B$$h^vUJ zp$1C75yu@i(kkOZUZpMM>!Tp9gnD>uv<6CEiO0@iA%72`g*GnaW8~(wy9vDWrfDxV z-X$T=XwcICH+Da5WOE=C=0M2L-4$^Sl>9^NPiY}}vuv5EkgtVI$hWI;)3kD1D9i?N zT_GQ%MC+kdXf@P8>8o+HmXKG@pmCwF7SM925b`-y^4!uin@ORtECQ+O(0*S{HALcUH)ktwO7zCG&n&j#2|5AFaWg{A1Ko zAzxd=MmkyxZ-#eVD9j|@9Pgxg^|SI0-oi9{{|avxZz<&C_R!{utMMlPM5jVw??!7w z9jG9#i`EnJ*;Vq{sEF(1RdR2QcYs&Py(>cgZ47x2JajcK6xOeh$K`lCXa!mqtwftO z(zP%zi5@^MQeU4yemx_;-O zTFA#(LTk{{E24K;D1388$mgXE<-|=A*A-eweoBrdCEi)QJ)y8);%%UGD303_T1Y-A zRWh`;kmu%T1yn(Op@rl%$*qO_sdI2 zR-kp!O0*tYh1N%_(FSOVTuq0rguJT^%Ao@42`wbwC)YR?THmyjqSa^v)1DBmL0b{> zy&_qoRkq5NNGcTGk1*|4(J~?LWztC3V8^^46>oue8gJLUFNwFr+cWKYxx(lh{jyMl zHyPCqz9d>I6xN4nJLSqDL(8F#d8fqNHTsB9iMMCk--=eDHBcs>5&9V8M!J>cXp=^^ zltR8fx=;_AC$5h+fD-vFw6HFP!V;lnXyZmYS{rYUcM@+0ul}yFS1yH~NRG*R?4eac zK3X5Gwm5wcet_1PX7BsRuTh3hg+ix7epG4U&G70khPqeDb8{g-s&vo_({zk3T8Y*} zn-}t{mOff-ak^|v=G7S*;B8E^yP^%TgH$MVkCq8Nk^HPw88@%ZGv3@ZtW9UXf@gZtwBq!jJYi+6Y~DXjckVS=6E}31zH!aMC+kd zXnnL=$k*kP(S5Ra4)8X3lTESHR4B|JT87p}%h5V$1zH!aMC+kdXnnL=$U7aNHN+*G z=}*YxT4-%37xL~qXa!mqtrYSqJ)!VU7T!w8tIS)Rp2zycDft#r6Srh>HoJHm)AX3L zVpPf8%RjdYxh<#-{o{o2L8QtZAQ>UMk}HLJP?+$i7vh4NUu#XboDjRpVX} zEfosu*R+2n_k9^!4wXXQL0>2=FWv!KgO+TIaj8%k11&>qqjjJHZx^jZ>!DR>eY9H0 zI~|}kXvy~2)v!>w+eRBl%Z>C%IEhxEl~7N}kNoM5xRe>x7V^332zi{6YlV<^S_=6L zRZxw0Amkaz&Pb_{XJk+=zlSk-cP87 zywibcKPpN%($a6Cx&uHV#O|xFmx=;!Api0Q^f0caMmV|si8lbJ9 zCEpU`QX%i9g_fbU(K=8Ms)*~O)o25>71Q+Sn7kp5+Y$;_ZfI>N7xJz;#3}i*b^O`qBtpf#rHawKDPAoN6{aV@mWG>vPc!DR>eY6^FfYzWTZ;JI= zP$uO4wb43I4{Cp_E|G3S!*AA3bqkw>N+GXOnWk|y)Iiz(xRg265%Rez(7HmtUNe8czcTnIhp&D8e^7k#WZ;MrOAs@FRw2=I+ zoasuuvqn096IooT24V$Gq!1~AT_ImvNHol2nozMxrDn zA(_db5)zVZGmI=*Lbhx(8hesdLNb$)WM5NRW=d&SN!n;8WN8r+lKkK2ocnyw>-YTp z`aRF<_wRYVKl6UR*V*rLopbJUpZg}Qd18fP#bUaaECJ??nnTYK2lOl3KCURBeCV9P^e>WAUvft%=f_B(15K*WWT+o58svTUztP z3dM@u;hgiwe%G8|IM8*RsW{^%VcvXG#WKXQ#qz`o#kOJbZ%{~0-K^uk1){O|8c8il zYN=R!JzR6wLt6913dM>rZ;x;+z9&kns_VBp7T=~=dH)Ka9DDz$W}Wk@YcYS~iDm0F(E3Z%BptvP#p zk<^N1I91PIzC^Jk%v;hFsijIS%{908NNbi@whWhxd9R>j-fL7KHOIVKA?D4kSZu#* z?vdtJe<~V_Z$FH0x1Q$+EVUH3)|y(X)Uw>#ENa;gY15DwZy-*;31uT7g)hv=)o0hWv}A?(gwv%-cdq zSp3=t^X8k5#ji$WxNNaJu>yBE_c}*vMN%u4n!3%Io->@r;&YQ)D&{Rqy3{hHmL;`p zspU#7Pih5HE0kI>=FQD+XQyIdzwy^QM=A#g|!X=~BzUy!{~4ZO!F;lqId% z(wZZ!xzd^^t@+YgD79@;E5f`bP|NozLdty`X0zH83X7szmh zm^Y7Y(poIF{Zdm+WT`N33>)*Vt`ns-Nm?_+vc>Ym3dM>sZ%K8UZ#L%jiX_+Ez95z% zmMOz!OD#{VP;9>pr<*$Sa6V^B!o2lJ70Zy;Y^mj9Yt{8it30XYyXNejMQ-aLT8pK1 zKNkPAw3#z*0j(N~Z#69bohzvesijFR9rMP>aBI#~vSqj&87^0b%ahi8X)Ti0{Zi8{{3Wolcz?s< z%O|a=Vrep5x(t`$)|_kVOli%P);!Ex%R;dtX)TtTzQ-Rf5%ZQH74xo(Gu>8an`XN; zXMf4XymNP++v>bV1(kNikHw!TYw6c)%&R40UjInJym_R`aA`7J zHs-Bco>;!L7GU1Eh0?kW^R{!bYtBBtUuwFQEQ@Q-9+51Tig|NOlUh3F%_GCDIn&F+ zyls#zwH(abpYz6)`|WJznbwB|@_ zp49TCR)od3nzX9>{F=tRdDyNw^GKA|Bxy|*%aYa{x8{tIE42c#Lbug6rH#Wr<~D-VzjH-WaOA zZyNKqP@>dQ#8Ra-6Z76#XGv?Gv=)jLizVLgA3>5ZZyxDV%ad9G<}FpB)QY60I{2*` zi!Z6vlBAZ3#kaGxW=m@><{iz7#PkC)JJE2@#r2T1>dyXDlEgB^ zvaq%4dB(`Yyw@n-ZFQDUcad?$QZR3B*;30BE0ESgsTGUqt};EbRIzl-n{tNKvc+;S zZ$HSBTA^5xv=&QkKjxiDbT@y#iI_K)B&j81-lvDDQcIWNGNhL24(FV4vZa zP^?H=i>0R1Wm&NJvS8k}PnFhmY0Z#YwpgyT=1DCd^VXwKYDF?!vD9>iECCi@0;#29 z@xCgp8PZzlnzL^dOHFr|>0$9+A+=PobZo8qjeRWx^X8E)t$AWa?r`00_F}2&9x@Ls zzRXff6-$@a49uHHrfcr&Cd1`OEl-Bam)bV5V$3_P=}g~}#8SmF#4<5&ugjKNo?COS zJo4R|+xM}x>MNV)=A>4PdCP3~^rw=Fc}tMt)|{ziOD#{V2=jVRu?)B0HD}H3hy3X! zV%~7cn0GWwky@%-bB<#@ykb(X3K zi!ZaZs$PCgW8T(C63fE8S3eu`rkCTIvrY4)Rv^O_N=^0l$JLlOZjxAvv}TEAOKYB3 zfwUG%tr+u`RQ2)ak%W2kNRe8q)H1{}vG_KX;j-PDvrTiPHD9a{i*HG(6=Cr$DXsh6 zR_Fe`>g%^^%p2Fnyt8SNYtGzKU8@w~yA+r=y$sA7BTH)8Qp*!7z`VX+B&HsArsBLZ zj(M#b^QM<1mh74{Tsr2>H%lyAhRc!Ra-}s-S_`CBhNA$JTw;H8kqMg zBuZhBT*~~^IB7+mMXP0u?)=Hce7-;JZUY!y#7`wwQZR9`^CjlQv?0sH0Dh&MQW*- zw=5ZAS<;#y)En8~2n71BzQY*x~x$SptdjdUW zkUx(^u_Vk}`efIftIZT?O~XD=&baAP%W#Ktj-pvo%XWuzkCRf%li>=aRw%V1sTE`1 zlB&V}@@dRlszj+JNi7|VA19@jE44hS*+ZPEI6W`JHD{T##qz`ou@BVa92tsTbJip8 zQGW>v#fq``k`DEkGzp9EiI}%ksZz_pyfLz*HCtK>#q=ye8Eq$)k@};&-tVmk-OHGaNr)OjFeMl@# zEM11nlv<8huC(S$tw3tqq*f%g{Zg|>%6!FAFmDT`NiAJ!Sznp zuce4(i4};YkM@Vl#dyL#L@yiT*HXl?#0tdJSQ!rUUcq#+Txl&5+mCtMS&j3@wK1=j zD7AF4EU{d%0>%zZ=cM-_e(8hfcH6_F+5_A3OrD5I{O2^i! z1GMH#tqAj$FL9nf-!#nYzv)uT70Z{_BB`ak;7=tD^R_{{)H0=(E43obTg$}x{_!eB zEL|)U^QMae-e;7t0k(%#~KLT(Kgt#5@^JtN@E|wU>NL5la^< z5GxW>3;i(?#Ztu5#j?b5#R|lV#Q0LWzbs-YV(DVJVrr3$A(k#yAXX%%UiQbZG4FVq zBDHj}Of235#PY=ou(hgTnEOpq+b?D>_Qy@cyseuowG^qPNi9<>M=W1#n^+O%O>e){ z>{n!Nn722jNiAJ!Ia14&TE5h_i4{rfeyOD_@u!?6Rv@Nc^;?rMZ>yz9Elp~fVmV?( zn72muQh(e;%v+Ciu^eg57c0QxTf?on_ePfa<0fPAWszE%)H21gFmEb3Qp*=Bz`SMI zCbj)yYPsKPW8U17#Zsg-O=?+ED-cUu;V(ft=FKfvtiWw`uKx;F`mJh}Z&{eP^aWD0 zS3Aq%Yz=#jER|TASf*HxSiaacvHfE9T7PEBL4Q`@5TJ>&EHG-+lRlU`P-Mjm-6>v{$9!7ex{UBsvV4n>97En z!7tFzQfdMu*-Cv9Qfe`abUlmH9D2ZU$c!>Icm~$OdvF?}EN*KU4{KouRAte}LKzml z2DF4eFcIE^O03HLuo&Kk8@9kM_z5Dca%GqUufe;p6WXx} zYO(r_peYQ55wIS1z+Z3~o8^9(3kzWhtcQ=`qB2YuYC=PJ3_gLYE@N5XKIjP(VILfV zb5OOcQVn1RybfF8J@_2X!=;xi)gGRN1<$||I1P;|ux`*5o`-yR z9jaWT)XnfDybkB#mWm7uGaw%d;5#@CmnCvf83w_0D1txW;v}W&!rjmf2E$nR5O%{p zI0GdsvEFbETnm%oNq81sg?Hg|uqrc6s1CQmozNNv!<$ecnQaNTLN>ewyI~)cuA)%4(FhH4fX>V0CV9>Xmg`d!(cw_g;-5q zGnfduuo8ZR8aF9b2OfZa@H%XT{g7QtsdBa1hhZEngEygO9ma?C@Fo<(`%wC3)&Zu% zi?AL-b*aO(a3kCTFF}P{SZ=r%I>9pd01iX69{USSfSK?ztby&Y6V$D2b4Y?);11{u z!(bYmg-CslF;Ek9170im1CkrEFTf<21qv_lGXhM}+!-h)rz3-|_pgrDKc zMhp+P!)TZU^B@m)!x;$OuG9^1Ck%v9FbU?uD%b??!52^jKf`I*(^#paa1uf(^bzO_ ztKnVv9QMKy$VgS{1T?;bV+8!(gkw<}uOY;mvfZISJPjLQH;ikh)TfZxoaKT#ck(*l zrPNdK8CZAI2VgRM3|(8WtkC5iUSqIYG7nhVir2O^eFYwb>97XghJ#S@UZt*sJ76Nb z3Dw%LjiAJR%(tylzd^@#tP4zO&vHSZ`;|HX$Kcuy^c6^hj<6c4KEQDtI>Bt%30HPh zsx6Fukp2O;cH)=cFzK_s334`VYp6L)9( z!Qarb2WMLN1nOjRY=dd=7KC~-Zx{^nXJt}p1@T29L~cHLpiR(F~}T78$1P@Au^oT5oW;#2#?_S1w&vKEQ5V;)kyjsJPrCW z`Uk9o+M`%L$bk*;2c(Ur&%itIH#8f=IRajQgt45DVIbtg0SJxbyaPSpUHAn~L+dQ| zKNtj4VJU2YLihnnWz$!oCfotbU>6*R*m$-T)PV5RAY6yYNE=k*6M22PFW`_CDntmda7jAo3Z+;>T0;UL5*YV9Cf3bsA{T7s+O9p>ZmE|W;KZ|8e12s=Ip_2#*!UZ_p9Gk2la>Qs7|w#=UB?WRcCcxbx~S( zQM^R59;dqLEY(wI^Ktoj)lWaJ2I(9%Tu)Hr^h7mLPg0Zh zR5evUp=RkP)iZjgnx~)UCc`Y1tDoU5_p@rTevY?rv(*ayyjr8@sI_{o+MwsFjrv8N z=vtr(bgp_^=c)JfOX_|8LEX=FzS^T-R^RKzs#w3G_Uk3;uzpqjs+X#xdYSq|uTZD- zN_9@JQnp#GB4&+BFl$u_vrb)PHmK6(HC5JZROQU;D$%^5Dw|Cz*=$x-%@%co*{W)q z0(G+~RQ1eTs-byX-EOw2CgvS=mw8vUG~3mE<~?=4d0%xhA8>8|A=mLAan1g*>Th~nPcj0b6jmVC)9i9q}pLlt1rzN^|kp^?J;N7 zUh|hKGUwC}=5Mv%oL4`xYyV`7K4xtFw+U&>is(zNsJ_CA>GD>BzQ!t{D_JFV6|1zq z!Maq}w#w*RtjlymtE_HrU9MYM<#c=N3faUks1N3t1QN6+%tyfu-^%`r6UTaO&>#S*dy|qYhuwKy{t=IHs>kD0A{j3YEqxx+t z!MtOYFz;F=&33DldEdIod}v*4KDI6~JFL>?Gb_dHv>q^DTJ-u&9AUnqqpMSIDplXL zN@qBVxwf^cR!zLt0)Mzt$vQq<*9ZJs;Z=TZ!C8OYN$0r^aL4_%hE}ayd+|r!o49tI z)(2cW_@`gnvY9ilTWg@T>h9WG2e`6#Z9o^l^_ly9>vN@6L)_MLNm`9`?aoTRb$g4e zS+~}jW%j20&aYezxV1g){i%$2gEPNdbCz_bYoC_UYPM@DJNU!Z=)~2WSL^22cCf8p zc573A;o97_e_qmHsoLG3)e3iv*c{Fot~p!$_5X1y!Egx+xN>!eTfT(ziEGE$x*xhW zcq!L4uKmpR+wI!wM1LF9y++5E`P!}idgMuKEot3+u~vKBagRRdulbX*mZqye?q#Pr z6S~9gWNUafG^*oPv5HndyRBF89{jj#YuSFMT)WV#Wq0(a{E*lko&8$Llm07sL|)7D z@*17o#+8ga?O{)-s}5YzTf%? zTP^C2F>V=WUDw8B`ulU*F5bntwP1~cC4J!!-fy_AFUgkdn&waWX|dn0I()ze>!CH=+%N3(LMz;j5Zg6esI;HBl7EG^YSsm|DbOq^C6cz~9@q zuJ`9VnZ4NS0V`z-HJ$CZ{ym4QCU?HieX3Lw*G~TIw;p<0sk_|TuiO1KpUiQ{>#P5~ z1pjqzUjKEL#oPaa`98|^wzo$(uTg7vZoSyYz4^`*d!K&P-fhjGS9nLMYAk6dw-zjE zU~AdB8UJJJKaYE%*47+fyfxn~`*W}jIC1>9?Q))1)mzflS7_DGU6y~I%7T(Qe*6oL zguy(jh5S9DL3Q7fH~RC46#8}p!wq)l7A$En-<}oyts6`^Th{GDd(%JfLl@d}{@p46 zyZdP{kLq%StM@k#Y`AMVm~HPJm4o^I*Zt2sG6cf~`^I3-oH_1Pf^*bn*&3%#`#q|{ zEdSM?N-y*FgXxQWd*V;7^xdg^)r|LXuHAB<-|C#Zy)m|5qT@#?=Pd5^OJ}LP)?m1x z^^_cocgZpKLd)_rR|?)#oN>MN4VEuhmgZ-$FX=^UU2G^=@|0F2NY<=J-=koq>vVquz_Kk`1 zD$M0-#CrvUZT}lrTL1bg^kv`p%bmw%vPT4aSYU(c)84p4O88Tb$Te4RL<-LA&UKwP z#)X!uAJ+%^8|QpkYbEcOT?@ty=KHE#xqW-oKX*IVj^32ZuHek+jvHLph2)ssLhM)0 zwPoDlLR^h_;|60mOaGruC0MF)5k4Dmr`+%qKUC)0gKSCftg(-KH`U$RWwP|E*<-yW z2wp+w)vxOg=UkCBbnW2V{uStj=H|@9n~F0J&zyO9{oGlWRCg-D+?=(%)2#(-?rbY> zTxVN(7Hq38KI86+J4V(H-%=L%HvDCOj5fdfN0n50wSudlCUOJ`_Qas?2S=pc(zm89 z^r!rp)PkeRgYR(f%3Z2PGB@YnKJnK4dHQ}wxAj6tkQQ<^5*#PDvYov(Ps#V~AlDmS zEm-E@D7BjF({y(pPoCh@qyKSkx1Quqv)lR*uZL%$t99Ihv%$Z6RvWa%KPp?Z{iEov z%6@MQuAAD>R|mWEt;m(pDA$7Fg1-8roUJ|*bC%CLZaA+@&cBTN&-1AE33tTYF`OlM zMwVc{Yr)p&a)IqqN!IB9&lXznI@`*fZ}m60hvnL=Ke*%ST3g=Pyyn__TB}X2Iscl0 zcRln2XARHxzUbFh%;k=yJKVoJ{@W- za{A>_x3>5Te+eeC<|o}+&})O%V2wt}k_PX^-{Q=E)*UyAJv;oZbB4Ik7{T!+c$XEt zQwpXWOfQ&6wj6<1KIkuVK@b0xcHYl;_XWPX({Bxi3zo{c0x0P&U(g!NH<;U+uKrY- zaK&r@9v0g%~F9 zW`p;-!SO0sk6>A@c-G&NEjfR9{V14TFpvLy%E7jB)~%Vlq!*f7?Q%MP1?9Z6@vgao znzK)~b;q5{ogMEUPq059Y)u#b0ir((;!f~-Bj+;ygNN-EvLH{+!YF5H)r|0 z_4xL3-zspd^Ntq55(G>2pD#|wFW(^om|mgXt)n$TV1Ht*#@)RZE#Hb z%!P&<)zM$`9mn~c$sHr8Rs7wrZRWh?wT6y~vBkZb^ICf5yb7L0cwZ53<^f zDtS+Q<~u&Oap${->#G&6{qvd!eWCGQKFj==Dj!G0AS<@?F& zcA;f)jyWH>^9Y6umNXb+$(Q~s6D<9O<{OOr?~c*vX@ANy<-8T_bt8F=yuJ{uTQHA* z9`2u~e4*)G=zJ6mccJM8V>ny#b9YOwVh{VuwMu{TNv>iXZe%Takp`omd1@ptDDoI_vD^~b1p`v31#T1wxa##OHOYHjZ1 zTQ_;<5WIT|t|MmiUdTJP2Ukn~?zq8KMuEKZ3f?^ht-(Bk+7wx4=Lmev-BuTB4PO1= z{>$5P3>!MrU&{+EvwKg*o$`KpfAsH;>%15Dw&ct5{&MG~TAgyIxAvgl8Z7hooZrs= z%X$>cxWRD2ng`1gEMJ}+frB*<-XC4)ZkhAzy_%yYp9OFa%6qSIp)2VNU2*^Oy@y_0 zcY5#M7jh*Napw`#f_Ff{eTG8bk(P3YbIv$rTnmm7=Q{gaE!euF=J>VXXck=k2Ul8; z%H2F?S*~=a9Nh18u5K#2HRs-v_qql9cyO1bu!G#>Xb zAg%>hBf*rN`(_Q@)?nM$-@-MkTO0f;&szSAUFg;Sztn;~XD)XQTDo()0c-18a3|}J zkNx-OgA4pSxWPT1GTaUDjy}Qrh4ON@(YgQ9(VgC#o&9YUyvK1qZ}je;9$DyHu!pUX zvs#5s{e@B(Dr|hte+8X=e5~6V+-E4- zJ+z#Ag7s~++}{(Oe{`8yAR<-k|e4#rm&gU-PxDB|zU+#`O<#+#8xP*IJ-ut&;dco5F(#fCl#hra~ zKFiwZPNe~NiT`z3s<73zxUIodoU@B}pYTHW=7Q->knJ2?Urm!^dx;JH5_He??Z7&J z%K!TvK5q#wbQkfaW&FOIyUfA;MCVhA58T>??)nAmwp(8F;6CR6eoydU@0$Me^p>-C ze(Ekuur~$go;Nuvdq=|A4D+o!+=b2t8)Zo^bVczBXUSrBT<1wfug}AX#veTY$z2Sd@?|RC(=5U9IXGrt=&1Y{$FPgtah)FO zeQFt;^MYeeu&ok!PvYHw$(K((*1e)sd3P$#ncZ7A=O~)w)`D#n+?5P!!Btvd&UMqx z?zoOMa4l#J=22UYf5H9nM|t|hd(y(Bd&uC5)zyzBO#y8Ckr z%=zTZyKXAMlN{dlOHgx;G`-!a1hsWXID%ZD_UOOVoN*ufmzp!Kb9amcsx8lrUcz^6jPjlzjjHy>rgA^&)IwnKM5*Dq(ij=z*;2#PrKQM= z`JXd(X|goWl^Ru1l~r0Ll13$|a(E@u@I2|2crs~J6;&R;mNcrWs(@cd8lE++h*u{K zzp0yq-#{9kJFSf0NE)6wt%Bc78lF3?ir+#SRgbYXPoGx9>$|_m+kiAYgIa_7ZKP3+ zcyd+qL~2dEF;B4aiyEX+sZ3S#bZQ;E2~*W7jWj%~S`TkV8r7VsYIP@Rcy_fRem7}& zezg&P4{3OYwK3j`G(5|iir-5bo@Z@>-$xqNmZ@@fBMr~BHpe@VMm@k(xf&%6&$zb0 zJCTOxTwCH@NW+u4t?_Q8QN5U|Q4cdcqxz}#WPhe))BvVo)L_OoY6#;Q^(bQ)HB@Dg z!+5gS@FZ_fGK*;##WSwtc&4P)T$YVzaE+R- z=a5h8dE^Y9!ZmzpdjUB|=aF;uLUNv7M83fDw}vNqmyip1_SW!3?{YGaXK#&qNv|ds z>b2x6dOf*9zecXquam3vo8$()nS4!eB{%9q@^$?-`G$Un+@!aYoAvwTHvJ*_j{cZ@ zSMMM{(4UbX>Yd~ldKYWGi!|y>y&K<68ugX_8vmL!>Kpwn{w-X1H+A0~}DqJP4FB8~c4|APNQ8lFf# zivLC$bxa?_kCR57&?oTUNuy5cllUK`;mPGw_-WFpGx`kvCu!7KeHQDNEKd<;dI3 zmE;|!JlVulAX}P>WGj}Becy-h>1k7-2qHI2!KO)A;nG$99=rsP1=oE&NH(j2!*qeht)_-N9oF{ULx zmZXQ8)_4|44>fJ@@ucDT>$Z3fNiQ(%@rfk8z;wVTlk@`95uZx3|C>(ubdvqwbirqk z^i9(ZpGnd;O$I)Tq;HxY__L%@&l%?zzh{&57t;%$L(*SNAABB3e=!f^^GSM*>5nfU z={05`o<|y$Zw8Tz%@FbxGn8CnhLf+Fk>pAwx0Cb;lTGe0k5l=S zH0m=m0sovdYNwfme?c0x%S^$)B#qi_rr}?ah9}CWq?c zcri&IG0)=rNcxDGjUOP5I%wwLhe)Fin|b&V(x{)zeEet9sNc*2{1|D}ag&FiAdULn zEW}TeM*U$H;ipKWPMgK}8Pcdf%@X`9Y1Ch4DSnPL>Tk0gKTjII;ItCgBH)(QL}lKyC&#CwzUN9z>cm!v;hXYhU`{n0v04zkXXk6P!+p_VpU4I_;jVOiuz zD@0DTBIG11Mo!^reaHJg2YKQ>0Ndtuo~ER#|eMRgQeYa=w!@-zrb$ zS{2AVt0MW5l|(MIDwFwE6>^bPm3-N%MlQ9kCzn|@$mLc|a)nikTxr!IS6g+-HC8=x ztyP~~XEh|(TaCyKR%7xtE0x@6HDTFaCpr6AP4PEL&Ougld^2g(7V9p2D@iZ2THv3O z^fId@{y9l6vs&X{kn}RE4gMubFSFX>Uy<}Ot3Cb=NiVaU-x&Umq?cJ8@$X4`nbip| zCh28X7knQ{FSEMg2T0CDmh+ABgCxDla=uM|jHEYN&Nsu1R{3Jp{K%&NcQ>JVbJ?v4`Ukl0IdR#1lyR zlsyVBNg7qk9)n**at&;c!!IGZ2DY>DOG(a3_T%_vq)}z<3HartQRVDO_!Xp4SK3qX zt4O2D+tcu?NqVR~9j{0lm1xhvlSq1~Jrl1?(nIZ8comW!YCnrtCF!B|Y`hw2RCRj} zemzNVwddhANP4S1AFoN$TkQpSEt1}9=izlo&Y1Q>vWdNjOtTl0&Fm#)b9*Uyr@frK z%U(&|ZLcO<*lWp5dp+65evRyFzfL}Eze)D9Hj3%kbKO3n;d1oLyoq$lVj}n z$#M3FWS0FgnQiYN$J?Kg6YQPjM0*!G$=*#)wZA5(+24{+*n7z7_V?tIb}{*sy^ow} zA0VH$50bO&!{jsePvo=qFJ!KLl=aOc>6P{|d?87%v`^rRNP4Ax5?@Sm{<584xn4qY z{<6>DOG$dGeHLF%(p&9w_)3y~YM&?9+S;O@+8kEYdOJjJup{Jac8uI;mmuG=OObEe z7n9rV(&T$~8S-nN$#@ClV92u$lZ2D@+&)u{MxQeeq&c5i|nf8Z+10{ z9z@cE?CbFpBt6KkfuAHfAK5kWQzYjjyB2RBCiRxAS;GilGle?lQ)FgkTpVW$(us$ z$y%WfWbIH#vQDTI*&x(~Y#8cB-WJLr8-;q1DWRTZ^H4AH&QKrnuF%8eJ)!<&%g{iw zYiJPJEi{Bo4-F-IhlZ1VLL5UAt#6CkyAqR$*G|Qr zU&v7SC>ahPBO~DxWHfw|jD=5;3E?wj$?#dSRQMcuQTRN0aah}2?T0P0bT~v_8jg@x zhht=ga0&97a49k=d@)%mT$-#LE<;`$E=yJmmm{l(uOzPzmnUxwS0HPKE0Q;blgK*Z z%H+-ADrDVoRq~c_HL`yAda^;d2ALABNv4Ktk#~gakWIpM$+U1ivT3+J*(}_UY#wez z-WhI8-W^UQTZEgC_k^30t-{U8`@?sU9l|Zh2f{7M9^uwxX1ER6Gu)PZDBPax74AUx z3wI>@hdYr2!d=L$a5pkLoI#Ec_aGk+_at+|y~t_dKI9YOhso*T{^V2Pf#l5aAoA(( z5OP*{D7hd!oXic6B=f?f$o%jaa#463`Eod$TpWI!d?h@ATo#^0E)P#3SA?gLE5p;t zRpA-r>hMf*eRvkRA^a@)T6i|OF+7Ld6rM+J4$mjIgcp!o!+B&ucpZejQ04iA3=0NsiBv7+!;ybv?8YzR>( zYT_M8`e39M-jU=kT%-=(i8QK9q%Pi-eat4Xqg-;$(usUA(uE-xko5XUH}aK82Kj2F z2e~ZLlUx<)MXrwYA=gA6Cf7##lj|Y_$@P&z8(a22lx5zB=Smasqcw{zt zA~J{kJu;6x8JSO>jw~S0MDoZ#BMZs1kwxTRk;UY>$P)7J$Wqq#JW2nJE+?(%O45$5 zCd1LSWF)$tj7DE0W6{^i645uwlF`j%spwX+bhMDXH2OAqdGsB!Ty#5GKKed+b@W3r zG5Rr?6x~5qihf2`j_zdc$t3+Qx{It4-A(02lAaa)8o!C8XGOooYm@Y>=pOuLlJimY zd;AuX_s-E`{8p0t$oV|9v(e~_a@oHqi68GBzt)DEZ&dgoptma zIW&5n92V7KqlQN-5v@N+K+MfJ0+JW2`?MUvAb|Mc%yO2Ld zyO9T@8RVg85AsN~C;3yf7x{Cv5BW>PW|5)T zvt&3nn~cQfkkQyYG9fmfED>8kmW<_*rD6-oi(-q&i(`w)OJYmNOJhsPGO^|4WwDiH z+1P6G^4MCkTx>mgMeH^5%Gm4VRk1h8^0Ce2)v>K)g;*hZP3&zlG4>9b6x&W#ioH)( zj(tcb$37;njqM<-#y%sji|r(<#deW5#CDT4VqcRt#=a$M#`cgm#l9zN#fr(=v3+En z*a5O`>>znd>@Zm`_7mAS_6vDW>?nItOOmr*>=@pf%~suZAp#^u~Xy& zu`^`H*jch`>>SxGcAk7FrX%$07&pAs@K}f(5sQ!`V=?lvSP61etQ0veb}^Y1D@|s{ z%8=t@Wy!~5<;a}amE@FId2(v30y!;Kk$fVSL{5)YCZCK|A!o#@l265|kuzi0le1$r z$i*?|Uk7}JP4=XRI#1g5=6GRu5lAa<+=q$JdbbtXM;Q9ZAoM zHNrQLoULMw@r@*XEtZPELDJV^P4G=5=d4&$d<#hriZv&9#O@+Li?twk##)lQVy($9 zV{OQ9Vr|Ku3GKd5KIB&k50jUa=ueg|F_5fKVi0*vi6Lag5<|(v62r-)5+j*rC6eoz5~IitCB~2s zlo&^DD3MJbFY!2eqQnI9Y>7$axe`;zCMBnl_mrGY-dA!4*|y|NvVF-}>n3(4*!7mb+7O zRb9QJOiglCnOghFFiFzzP zNoV1c^mzPveF%SEAHnD7pYb{RSA4Gi4WFxz`zf^XJ8<6HEv_!j*ezEvN`x9Z>V0{sVGpikq4 z`cJ%2|AoJ;qvdLn3FT_>Noq3wj=mOuS6_#}tE=NX^nLga-46d$-;aN)AHYA?>G~9zt=19@AWFYNUy<*^g6s)Z@`Q7M*Ii;2L6NIgzwke@csH- z{Gk2>Kd3*&59!bGL;4H+u>KN1tiQsK=x^{N`aAq5y%+yU7vaC?L-;TH2>z@78UI!P ziXYX-%e5qbFV~v1O*a-=3{uBxS47#nY4!uUNVir-@r@Rp_|-qKuzw=$RDt<0r(YjYXi z+FXuzHd%OQGam0^a_}x@BHqwH zm>2Lt=0$vv$;Ahom+-+RA0KK~;X}4>KF^5oQZM!W7^m&0F|Lvkf0*KEOwr zkMPmv6MVG!6dz+g$H$m2@UiAge60BjA7{S7$C>Z&Y_lKFHb3Iy%^`feIf73xf8rC& zU-(4xH$IU|x>|fvo>0CfSrV_y6H|?#DYS(4&;@!ze;5j5U;<2sXJI}pg5|ItHp4sc zG3UIA9x%V!45b8XQ5+=-vEW9P&3T$ zDZ`s^60V5w+n&$~hQk`z0S6%#Wxe2L7!Pydb@&{<0UhJ{KSo-{6vx{9Yu~g&r^xmO^qVeybG5z+N~ClP+TVupLf9 z>5KX8S!ee>ZB}?1-h|z-A1*7SR0=!?8z6R> zQa8a&SOoW#uo(`+Rps~%V(19HVGuk9J0Nle+a0z*#+CfOCai$h zpaAy6?@;b4p4^5_P_jJR9a=#qyZ{^F2e|5LmJKGtT-X5b!KbhXDpcS%PoW76g^4f& z_CoX;wg=n`Jz)&I2(Q4~@F~P9Ds?%eKv#Geo`n_g0sIIik^KyAg%)rhjDp#)1~$Qm z@E4R%;t5A+2=_uyco~+%de{Vqpmrsm>x3@Q3kJeimDp?f!%Njj=`T$ zBAM3`s=?jR33|i$Ds0bd*(R_EzK285pei+Z5Jtf?I02QeqYgXZ8#o15RO9u4?(ih6 zghDt5c6Fuhfk$By%z>}rcQ^+ZU(fWQ6^wuhumiq>!%+JMj%Cmpro;2F8n(keh}2*j za3_p_d{_9kA{~!(SflQbN@4#U=1F<^vQ>YL3z=JRe4#06}elzO}Sx~MnuiGv3 z4;TdFVK%IVqwqH*)?=T8CeQ%}!6=vw8(|-ug7B>zFQF}Dz-U+io1j8{_Cc5mb73KD zg!kbHWHjJ4gh}u$ya=md8|;R^pk_n%Ey#osumlR=Q}_yghf=q3oPfKbKTL!tVI`aa z(}-g(w1vJf1fGVi@E+`gGPiTSfGTh+bb*IpI=l=e8?!!eE!2Zyun^Y4X7~cm!lfzv zN*fG;=ioK?6;8rA2&MA-^iU2Gp&HbNhR_7=gqCn0bb!vV3x0su9qdC;38uj+I0>bi z&`+TPG=Od}0+nci=zzgsSybZ_T4Cp)Ae_#NNhUxGn?1dlUqPsY^ z!2|Fr9D&5UnLeC=>sqk=;5L{Fv!L`nY$vD;y1K|d^6?(vI_!z#2V{rMs?3>UQhQm{^6xPB!Py~nI z9F%UuZyQ4g_!f@9F*ptB_wky;TG$6a!%3*rmVN{|@FFaNW$-3cXve%E3)aIW?K!SO z6{rml!y35we)fH+0yn}F;5-NaF?qs8896= zXTou~;z3?($b(nF`G)Et2zO%oa0T2C<6ttp0?zk~55XUBS!dc|5v+jM!1?B3i7tFI z6&?iVdvVdO90TEgNQaT|I6MO@U?UX52k2an3YNlp_yBl3UX6en{n(B$7ACi`2_GR%bc;4}CJ zN)MpF!W9GAFX1kD2!=uq`~bhf-%#-pj+Jm1Ts(;5C!|0Z=m(=@jm%4ZI%ppnE5rVA^-QkElY>LgSHx)?Ey96CVdgV zwtkVS4RzpVsH-pGm)FaxdT=Y$hX&9PZi7Z}J2ZwANQFC~38X<&Xa>#UPPhy1h8A!S zw1igB8t#QQa38dVcF-Q~hYs)nbc6??6Lf|y&=tBtI%Gh1=mD9~6CQ$I&>Q;b%T-@^ z82af7sy__S*QkN;h_1vh$5&BC#KwWWGox8mC`t+>D3iu=l~xS!mLZ@skRJ1_0{#!EZC@6wKM zyR_rGF75cHOFO>j(oQ`EGvR5N1<$~<@EpvB=V1=ag?aD-%!e0Y0pvm+yaWp&U$<9_ zI7z+?i*+Zy1JhM4fmdNEEQ95+0#?E*SPg4ntd^@H;_ksI!|F=K)efx92w?FrJ`>VI~Bi!SCgm27@7U&A+id+1yE z4)(xa_#TR&7=D0#d`oCQ9DpC;AROXX`w#Q0{YT&@_?hnw{Q|$jQTPpx@hkqv^)l|h zuH>6BEBRi`O1>5I8sCX|jk~aK@O_v~d>dv9--RjUn=plZ59TAj1@jSiWxwad#P@j2 zugaLC$^zT`r9u$qm+>PI<=63Jkihq4NG+YX0;4&x+mqR(Y0({O&>6ZwSLg=m<_4Vs-Ju6$ zLQi-IdO>gK1AXCP=m-7zuFwF!w=>Y(s~>?urkx%PL(F9TDBn&Q3d3NynW{&?NO+8I zs*Hls=1D!qJf+8)MS2`$nOAf+j5i1L<0ir6zyx!PnP}>nNhZZiHV>F7FxB)o)8Gl1 z4o|`icnW61(=ZF3foI`4m<`Xv9GDC9;02fue3QrUJsy(_dGHb}gnU>8FT-MZ1(vW> z&e7jim+$t@Y_>|qoT9CoVa|EPR?{)(zp$zoA14=9sPWBwKfpY~7VoTPd|dnO>nx?`^5QE!*lHS-0(C?~8pX_OaLwvCqVI zitQ5HE%vq8w_n5qfag4@6UC_>dCnEWZe2vt1q>NGQEZ} zy+$%zBN?u-)EdWYwrV0v-&E$?T&%T>(OSl6BWvDXtbXK6I&^^N^FhTTCsIvZ-{M@-m^*i)+X70TcmZ1^sOz@ zx3+fHjv!ty)h}XD_WqHRJTYV(MeI&zuB2)QP>~pa%#J&{!O6(i4 z@5J_s6^Z@mnzMXA%6tz=?U2-tNbN|x#=SAiKeGO2`PX5`Wo!H{cDwDrMt8)`R=sWi z_2?%yKy0wsqhiCvz7yLkRwVX=*nY7?Vn@V&7W-A~H?iYlr^WshvqR3(J6E1|$Uj3w zL;jI9!EJS3>5_5d{++Z=3j1q5MQobb^tkb9ZrJYuGs6DW^=uh#j@Uf0`C<#j7R8Ob zcVYikm=W=pba>3Kr6#-)zm{*2;Lo>3g5UF6Cit&(%LMk`k*cHVW^s&$Em{6d(s{o2ZK?ZrBXbrjoC!r!K! ziR~2ICAM4aYq4*|_K1BiRxGwp?10!wS%ULY(}&63)zcL(eu_#*!`-zWBy*e_y7#g2)c z5NmUZKb5v(^Ww(!OKE=_%n(~5wp47n*h;b0Vr%2ZCs0@TbDJ(ULu|g-0@wEP?*=+g z`Fus5)}?Je1>&3Fe3s*8Ojp25>6&C|eaHW!?cJj!O|JUDtf}gm?qPaVEzR4k&=|1L zj=GhZRasdHLreMWF4Qk|Rcj=?B9)c-RadI3GE13R-CdHg5HgF#25cFFKw=SEORU6m zK}PE}>@LD!&{>2LSm1-v&hi+uvpygl$?Wb}E407gy*J{E?~y(H;qB8E8FBB8#~06g zZ`_E0`<8`mxFc~V=KeQ;+?2St#eJK&-zn~YE$-EmOmlLQasRQnrx#v&H{MEEVBB97 zntvtkZ;1OQak~p2fZF)C#eeB8QvRO!|3KXTSKPu~OnFJ%|4kr&Ebi%r4qf!G2!cPl)?#;vSb2zOwM>z4+3*MsQy$ke7-3H^u$nT^H`dF6CW6 z1osUB`OIA}J;_|?o?bWu_p^6R#s6J!pS$Y^;r`rR{~qqk7Z2~p4u$0Pibau^smyc2GB;c2+< zU;GWYA6%sW^upJlf*$^^SHk_+;x^n93oOZrg}Yyjw`mvIvWrV^ga2nvu{u2FV zwe;(7A75H}33Ge}+z;MWf&0V~5j@r^Y;O;N|G2A~}`s0W2zWmauhgpVAxXoiMPxsh8kJ5c9+_xWNj*Ckl zf&Yo6zkrL&<{qP;kncT4cYo<|K;CnVZg+u@MJ?yT4*_ywfhnI}_!Rt~x$ED-{l#N+ zpIG{-zd`tKzG-B+W2#C`nk8T_xf`}^R&>h6CE_r${Q!#%z5 z9Nf1b`vTn0+(oy$K=-wGU-&QRz5(tt$KCD^uo`>|8sZ$CEUM{Gr-uly8D%1!&W#C_ltMG3-0rG|0vv#Eqq+u-+}x1 z(qD*s@5@-8SHS(uU353@xi0Rz;C^i3BXGZb><{36Mcl{l{yhBeyyw{CEW=m9eR}ah zxZQ=Xhr75$_aEHTg8QC(-U#=-_w0##8}4WBBK;Ff8tc>WKfU-_xThDs0QbH3d_^FS z{7u4N33q?#Nx0pGCfpO^b{B5LzqmyA6Zd=w?#C8>N&LSF_tW=~@)P%*_*&$3&r9K+ zSok`)&BY?zQwwWwpI&UhJ-sl5+ZF8Tg{R>^vG7rGe^%UIfV;o+N8&Dh9cu5MvbbmA z{=>x<-2J63xThD??F;0lKsfFj^KD~JC7Hr;q;>+-Hu{e|q67aJvh~zX3C_z(05V zA^5*|{0VWZaGyW^EpVS++!e@7-0y(9aN_6Tb{9Sg_k_4_Kc;@pncshcv-&Tdc>fcy zIy=E~eoEX=i~GCczDZ{Ok1fnziJ9uePr$vgNcXuDzXduLdxcZ&-{RE$T?R6lnw-R{C40`kNX=ibM@in;%mxW6s#6H7l>Bn{pF zD3HGrw{RaRzyHKjCDgn?PKo>Y(uV=Lv53c?5$hD(pSz3h6H9cTUi>k*Z$HMbe)%HZ z|5MhQhb6V{!s}PD=YAjEpSz3h7w%^+j|;XbGuRjI|0v*(-$%E(r0&lOCEfpB=>J&U zFWmo!fZ!&b3ihJ!dl}sQ``AO?F79`W`~Lf0y~dKzJ+W{D?)NUe9qyNpy$9}B#C`nk zUxNSi0%yXf7k?Z6-@orq;XZTU|0C}GRr();`<#^X`yOGLPb|CwknX|_an;Y+V{wW8 z#U=WmzxyA+|E2qWOx&M>yKwRo;(iM5sgo~Sr~5bHK76t$?vrqzzxx30*Pr|@xTTYS z2KVVjZ1n+h67I(qz8>zYPreiG6HEUDZg+w1`xk!&?&*bp2lts{{}JwcjuFyb_!8Xj zJ$dgp0dkUa_A$*6|A$Y$Q{3-``@y?@81CasABX$%r`YTMdvp2i zpFH_xz&|hR4o>f?Jj&&+BX;zTRuzwp}6ma8{Pj;#Q$UBeq7vN5%;&?zT)mb5dXgy_X}_rrDje@ z&759%=+!J$5$^QWK?lZ@J8t!|J5x&3l>u|dZzXkV+CA#20rqxcH(Ut<(Jk z+?5v*g4e;}o?56hU}r3l8;c)+|3`%W6^q3Fp!lQ3#U|`7#s4aSzfb%N#;=a+y3fzw7D6kHG)*;%DGKBH#7&;$2q= zc`4lKi=TwManA?E{XMwPFLz%@_uX)R_r;%r`~4^WRQz9o`}loK^VCaz=qgM8OK`6& ze-iGgm;7sSk8g6m{c3U9Lw@{jt{u@!eglyAyyTmTGasMZAKMVJLcWWzL zyvCW9{vR>O6p(An|3cjQ>kEa3%pup7H{q{8K+0#Ae*ymI?)!DP?>+hXm+|ZV1N^-Q zHlIQte&BuLegy6pmsy|BE`JXGXP57N11V3#eRlbsa6kIM|19pW!~Gv0_^LOe6&`pE z+>bu+ez?uW|4#fr4foRz{3%?Vfd5wZhm&xhSfcy;572$)INj3=ZMfZqH^cqt10N^+ z7*~X@@Mh00KmNCw@2kYUDDHd2{Q%rQTl!~kzv{tH!u^wDzXSLAXRQ92vh4yn(tryH-i0hagTQy>lEB)m%mZm^Kd_NiX7Lw9{f1`Uo`(G z;eTRj@6D`j@>Cyq@B{FF;KAP)cc;e`{#@LZZTc_4J-tBA>MtMs2>kCoNq%YZp+AHF zx%{K6m6Wr&Qe_Y&uCGPX$KCr{|UnA}g+~=3S1ozVqUAc~L ze~9UvUZC4uU~hkR`Ii7c@$lL%{cnc*-jn1Cp1Y5H!n4aC7x*)9Hy(a`NcYv^_TgSx z=9+j_+&?+S)%FSD0ZuRU-$Fk*fzt~=B7SlUrx!i}|MSboZ_s@d?)#R%2k!5__#eal z$%j8F?(Yk{z6X28htI+NwTJ(a_BJ z!}pHaH(mkvnPX4F{UdR~`NKcGpO9ycy^-#5LQXII0Nk&6UG~{mF;#JD__S zZtamhxWD%B!dsc<%iun9Yz6KgiQ8THM);@q6Y|WlD|C+&a(dx=;C{^`?}PjJBOiwQ zYY%@$-2V&immdDv2~+zmxX&E>Be;JgZg=7TfPZ>FAJzv?&FVa z!u`_2?-KVzaKCtpJ@y@sd>Q`le&oNL(OrjoZTU9b%JMIY`>Sy8x%bcE{*25;_uTu~ zA*_GJ{}FMYU;b7=e!~2>;lH;04BY1)S-DBKF78#hHy(H=+!K%f0cNddm;V|3&n`bF z?q9)u^wC$|B9^`A(MN0WKfC;00{Ohq|Fl5WpSwssy+5u z@qf*C(EloNze(Ik+;_i*@V_Vi9}xHb;{K+%za#FSi~D79A9*L~xjugIF-jb@$Ebs< zJw|!thwuK>cf!teVSn)z_}hie))0Shg@1y-zk|PT$KT(@-#hU49r!EoU$?tC>&=G! z`ef2O+87OITSo`c=J0LNH5e|FpBI?=4Z z8(;>&Aj-9aL2ni*v4NW7(J1QA60oz?Zui!tcfcryzZ&)SX2bn3HzZI7!BdlAkR&0S z(P+@zJsjQei4xk`JDl!%$ciMrzdxM4?r=Og+;8?qgCPqy4N1!F#%iU~Xf!MJda+zu z>8w>6J*iy} zXpZ*}4rdWscNUFi=X=v#kMi}QiKeQ<=Ts`ORxd@@$Fm{Y4IN}~d9-)LWE7Pyq(NDc z5|u^Ri)N9_T%qY$24!_gtj72dz03C&W~YctG-^fD{$zOIIvr~< zos6cX$1aQ&<)6=HWJT-Hy(WXN~dTNJkmlO7ZD5^P#1h zPggv?U=rd`xDfTG*8B55Orxi|RB1&>QP^@>yfVZ#N4mooI?tGrAs44kp8q^iV}^O@{lI_Xa?-9Tb+X<9ujYb4`_SH7-j<3!}`$ zoEg(oi^o@pgto@6R#`NqOy%aIm#VA+E*qE|g7G9^jIoJvGyFz@Wxi3NGCIV2v3DbC z9`4PsZ0^<%XS-wYG)L{x&EaG`;+$&3ptR~EibqF^Fcz(7FS?G=&C(0bf1ZR;D)b1h zc;k_qqXpp>=5RU@L}xPI5359g=?}>TmP_lti-<4oA`;vhH+!?*-uQZZG@IypE%40L zvf|SbkN+YG=@)e{i|UG_w5cY`1(s^AcxCY!D{}R4FPeH@KrwD*R>JhxmufyG@km4X z*+)RGdfl+t-0dAm8wpmw;bQNAt)^+EK0tx!DClrk4!8G))7{8sV8-G6ZCyUgA1gkW zOON9j0~WCA8f{~A6U%}Bm^cE~9%00DP+-4?qeL9bJfuxl64Esq`;KUEr8hG+J@El9 z-#KXejx=@?Vu|a;Q+8>o#J;S-VX1^|XpZ;xAc|mlPN2r)8;1uWq<*V6Brn$Nfj(Yt z;yTI@hkz@GvljaQHuw}*vJfpL$wOEL*@uQvq)HvT5+bm`4R>NzODelJ*u>Pl7jf!x z*#z7OXs%2SM^XFs!Eh1@g8~p{^MTDvZwr9YUPxdalC+JZ*TVHZ)KzQa=u&TAg@VG9 zE_1fZCYVtNzq~z-CO4xiyGK(l+nwHU@6aP1Z!pC?Hgz!+Xp;)MQ;0*i86RjV`_|;B-hb;MYC|MV`=~2>Duh|(pz9p&3-bg(eH-Gz*o9o~>F$@Q z9Rpkz{vOF(#gvXt!Y@$%6R<1jqG-O?9Y&L*r+SdFJ;qf6KsKUWI`T1Bqn#AkPp3#$ zl5)NBs&HO)VGMv{du~m7qv=jG+3HQeIXDalSLAEgLk|xQ#uHD}zS-M5B-<%#KGMT< z3#r?E{e%*Z>$@uD7|PX~1(TVjXk=uAb#n|UvM%TfK>C)wCE{z8^^+7KIXgKx+atFN%ZrQTNNzTJ1L4e5z&#dE` zN)rL@OvoyT*2Q4W@xhU3J+{UvcuK*7_+G&R|5G}dxvJAPu*t?xg~+2^mW)N`gIbbB zAf9eXP%5m~S4#EOV!K&y7MswWwAQPQm13h+s;o8Z)p}*M+Foz3thAcNYH78y+NiEo zI*n$vT5Xk@E30*uj7(9n3!lttsrnEENub04iDxV{NCfl~Awq-cg&XP$%Sx%|=psN9 z4-yvx`T)SKxw7?uHA^Ydc^Z^OloayS1y(GG-C^b$l&XOM49p95OA5-CRx&9GWjL+! zs-qT9jwIy@2Gw77g`h%En-1~~d{#TwiBe0tl4PpKCjkK zOtF`sw9ev2lxJgNREoWfQx9pA>?LUCadvUKVf1|NE2d3zU%=#ZUqFN_0J|+~(>Nya zYb(JU^PMq+1h88G6ATi`WlciOdOpVkb9f-&P^W&Cmc(*+AdtnYrDt@QuxE4r9DhOs8ydFW1D;;*0WujYUO>^m z59PD-n0_pJE5$b>T4v&*(moGC^?4q=v~GyFtYE|J+TxNtg9qUt)8q}Zp zsm(*ofmi3ish&;et~D;%LnWD?{X7Kpa*U9i@sJeOtK5`obKq6zvhs3XgH9(8&b1&H zQJVu_gC-<5p(0cexdSlm~fPQ<;$mCzp{62TwqF&fG}W zvrHVP7(Zyr0%G~AImi{CR@_H@)Y2-kbHW)IS2THP@p^Ot*XYQOIUFInitNv;vQgPhwrK*pX)3oIp z)11?^ij;*ar5nk~g$Z>IM}4N38b4T~I5(Uf?kPWH26HFtYZY=hjXYvV6Xpqf1h;;!;M21=v--VEDGf<`Kr4)^xR7{a^J z9%27%;zE}vkou!R64-t#L}{Y{E3z}Z4%NtjC6{_NjA~FE8Dv+L=+8qdAvtA>ojDS85rR;7O|WT=rboc#h;Aanw@Zu_IqGA#OcaPDM0XfP zd!`X^(7GrQf!>HelOfmZ5>OdgEYrmZivL05aEKj}3CJ&^q9#X}tgw;JFcRB8=uOnm zp1@7J0Oq@aP`FQUMHilq$vA{?VW^W?bY+ZPnnWbQT|AVoHizWS*i@tqvj@0>7Vu*L zWf2>b5Matb`CRMz(8~p-Htti{%gYBGsZ(Gv<-Y}*TllEJ-;C&U-|6>U!7*bjgc#K{ zdfqrdr5he7-+C9I{6W|?rZy7*7kY0yBHb+7$He4SEQRLg*F8yDX>Sa5<`lP(&o{6s zn>dBve90+B_zAfL7IK-Jbd^>V4vDwZlMD{Hk{XT7!3 zu6LTX>PoAO^lNLS^?I?jvQ}&qo9k^<0Yz`9lvo*iv9XOI6h22~1R5^|385raVp7(i zu3i%=4Jd~!b@1=-je!>cE%Z4Fx5c zghVF7XZ1anqR?igSDzTvHF1t6X(y^l37D@?IvuS`DMY06V5fgo7{)Nwt|`< zC=9$6mpzNHt&5$7u*i6;D5c(|fs}f8hNIpdtHlx1(#_*0(;3$R3(J&aerT+*-R}ar z)LF=t9%H>9fg$0vYAE+5X1^mduAz4E&Deg>Qf(jZV1K}2gwvQ>Xh(N&To5VJY;a(+ zw-JqZcBawH0)4S|vcSOXiX0C1SB`FKiVQu63}`+J2?xOm6)OKf<6VmDGTyv|E7KFw!}cqj0`r3aHGWbmVR^57UX6 ziCBBYG_2YZ+9SX*|M&%Oo-znzajMkE0N|%J_;VCEfaYm30d@tkbh3J#fF@6`19V)N zK!F3e)PB4!Wz{#J5ym*A0mlUKFoaOU1Io0(U0R{mAP+$$Kpq?sO1TJVjRn5b?bczA zbhJM{oYHznpkZ&JfVc)y5pT+#hmi`W;JqhDn}-t^h(!VU9I!<3 zY?873aVA*|TKX{BI)tbcBz|QwCJ%u`Bp|16QE@P!d8eXIS+C&ly&V$Y%#EH|XNv3rCqkm@bMA+`O-{ z1lC9sucZj&t)&1@cYB;N;Y+Na2p^a^qhfmp2bjQRF=Z@~q%ERbX)L%6dEh$w$Y|iU z`89>9?7ypj0&BzJ$d*c^XN%*7abH$B=0K@Tq+m

    d-m+;lQpG1TVu-TqJ?*7pJ?os7BnZ} z0%?Sawi=mtBt)B>p3+My^E59*)9NxO#5h<+S-?s@RdN*dR>M3A7^x*kyc}VSqLIC4 zT_mv!(*zY^b*eNoVct1W|F>TH5#&G1W7(r+nn!`&oLth?ld_zJiUX%r1`QsB92U8m~ zwgV&0c6?n|7}6MYdhzTAMy~7z)ro&$u??jq@N8lZShP6;3FJY2O{vRGF|tMKkhj4eBz+}V4PW343|3QQQLKxdO=fI1F^6K4 zBKqW(pcLfwFw$oR8p|7DE=i6I#FInIR(M8^3B;rEAdIhEp*Ev7?L>|#l(Y-E%6#Tx zu%g%N>E8fbO1+)N2bt%|a`dh_A0Gn^qs9Q_G?3d$CmnX2YfGoLoEBP$0r2n#edXw%08 z7Aj@JfoB{x+8m010N*JRhPnWfZ%s*f!fzJ*96!?3CBGBg!!|DS6 z;837X)!{yPa)7d0?i}MEdPFraB#JVDM2Sqyh#X4a|3Q-S(>EOYKwvMqn};5#I`8E42sakLTo;Ua5V& z)B{o=Y%ngX3>~q6p8|)5tbU@{{k?GKVZ{#niFCm+2(Bv$BS#-bA+Vh^sMd&9s^Ac- zvEgH>#brjNfx#nA{7Q9?!v;ksGvPt!1P2+#C0dGj4&%RR06BH=h|Z5;pTkM11B1^x z=?AV*NNPTZ(Hx~P2u%A}haGB^$$!DOgTGen5<2s^0v6MgrGV5y;X_=OmWcw^E)xh% zyN++|0V~g8FZDTnDDM^gr=on)s#iP?2UUNh!O7=YSBJC;Yu_#HTTf%6m@@9eX3h}em^@rF!&F}=Yr&2)rZR2-H3!E z_-GeeZlS{$SPj3Rl`vuq*T%B zZYsSJPC#Dx^-z4kZ!{db(jJstf0`@ZiN*$pWZYQrE>#Q72F%HWa6wR^u)+|GrhSw~ z9mZH;Wg@eX^S6(PV)FJlBv zZ?;sq^zu(eckf|1VqAf0I*S}G8_x9ESZ+)NUshw1^JtP%Fa{=3{5>A5L+^dEXwb{*8nCmE$C1J=ljnU&3cq?0OU)vNMgIj)G54f$)UA-TFoXcDDH_u1S41!1Z_3ScINb&~!^^*PCP=zW93 z(uY9IJ|Md_D(gjIX@dNR-jt?6X1SAMTKW@XHd;w{&=5LY=Fnx_K$ooGwkZdCgd5cd z+lK6vGxM-c8Cs%Yp0kf17p7*<($9!6aDX7o-{Mh=n)og0vA29ylf8wc=Va!da$xcr=pbdrgbjI;R`8CM=ulb5S@D z*EjCe<`-EpGoo77kY`pd&+MwBb!LhL%Nu!tlLld@hpe-l#I2JW~y9CT{_Od#b9^8oHoNao`V`LvuN2J@^0`)toD_n9WQFJiQ%30Tki1797Hc z2EJU0C0<^c!{J7sLR|roo<|To%>e}6)9s^O26t-8z>S_mrO51Pry!kMCbGMfn;Mb^ zqO2yyUo3X#h3%-NwU;TPb^j=(I)C0D{P0NKD{Gt&?mPYFC4brfnDf;Ym7c=?u9|GK zVbf}(&u8151ZWRCHTQhr<4+W7a}#{@fx_~S-+6zs)0d4OpV9x>0THHAs!NWm*g%;hY+$yVSTx?>tXjNzNHP__v<@D8i@+aQ^j9o;c(;K5#K|z! zpvmksA01&0ngdaWxz$4!5lVfXO-E#cT9GbnSk0im0%?K_C5pem2nKjw%S@W^EP`kF zbVf%;rlZ-M&7z6hD#V&oWewy+XXVE;8!6_@EqRtTJSkZDSVL|eZ48>z?!Y=xs{61p z56mNB9*~Xfq+#;E>{Uc#kxmfuQv4l4St?D`IR&aYl)J<-Ce!WGqdys8jMo5E7L;c{!q7DYvJ*{gxXIxfSOD zo%h{x2gImcY0I$kqP%pBrM2f}wC6bm;_o_NAc8NsfadKOlZnEyheT8dEw!;9r)L>< zj@Zb~^xi_=j3)4U_hJCehDg?XK|sk31!F9CP9_< zpxh#~1d_uG)VVUd0xaHO^6+S)FlH8#S)p}OS`-8S4x`rS<&=qBm&^GW+UU+KqZO@* zpjXpA=-Dxd1T$i@NJfEOa&mJ`Yne_S2Gi65~yRf-eT4 zAAAq2B3jkZ23#7RZFHyK*~1$bct>BsU&~QhZ(5@&YP}0pYrXL0hA^0^DR%xfY64Xh%8glS-8$uDF1{{J2%k!>h%{g)33j zI6epphO4<7pXkB6a~-6%0^dON3DhVCKTZvHdrZse@l4-%e3j?Q)s*xe{O2z9IGmgH}DEqwI-n^9rw=Uaq_Eh_i9KTA&t zyndfp+}XJjAB7ZMNxZ-oi}8!$W||KxkvQ^Df)|I0^d#~26K~exZ}F&WIpLF z$M44D`1^_+io_v&2H<+pg3HwPxJF$ItQp@RbvEc`z@@p{M`#EH4}7H8{q|bPjdUlh1ZLsT9lCM9J3q|DrDE za9viTECY7+!md=7xBlgGfVU05 z(?GTJ?>6w}Kw~Dj9q2*Z0HdI$idmcoUDeJ3g}-6WG1Lg()75boeBH^HyB}qT6-(S0 Z{||g}4;+$zTlNVWdjH?g|L;8TzW^0#?VbPt literal 45568 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 jxj^+f;Hh|g?f|8#Q9wor9Dv;IyTyzQy`x%&d5 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 delta 12537 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 G%Krnp`LD|W diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll index 4d90585e932b6b1706e3b6a74b7fc03263176086..5a8974074bd1e8c87e707fa4d73ae141b1f2192d 100644 GIT binary patch literal 86016 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

    OU_g@ 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