From fc3bdc70e8b9ce05b7701afc446a18182eef4f93 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 16 Oct 2026 18:16:37 +0200 Subject: [PATCH] Caching: MessagePack and JSON shapes for CultSiteCells, pinned Co-Authored-By: Claude Sonnet 5.5 --- .../CultMathJson.cs | 12 ++++++++++-- .../CultMathResolver.cs | 19 ++++++++++++++++++- src/GameCult.Caching.MessagePack/README.md | 1 + .../CultMathJsonTests.cs | 4 ++++ .../CultMathSerializationTests.cs | 8 ++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/GameCult.Caching.MessagePack/CultMathJson.cs b/src/GameCult.Caching.MessagePack/CultMathJson.cs index dfd15c7f..ba83410d 100644 --- a/src/GameCult.Caching.MessagePack/CultMathJson.cs +++ b/src/GameCult.Caching.MessagePack/CultMathJson.cs @@ -14,7 +14,7 @@ namespace GameCult.Caching.MessagePack; /// The canonical System.Text.Json shape of every public CultMath value type, the JSON twin of /// . Vectors, quaternions and colors are objects keyed by their /// field names ({"x":1,"y":2}), matrices are arrays of row objects, rect is -/// {"min":..,"max":..}, CultCellular is {"nearest":..,"edge":..,"id":..}, CultPhasor is {"cos":..,"sin":..}, SpanNoise is {"field":..,"curvature":..,"fold":..}, SpanPath is {"midpoint":..,"velocity":..,"speed":..,"acceleration":..,"halfLength":..}, and SpanStage is {"frequency":..,"stretch":..,"axis":..,"shift":..,"offset":..,"octaveStep":..}. +/// {"min":..,"max":..}, CultCellular is {"nearest":..,"edge":..,"id":..}, CultPhasor is {"cos":..,"sin":..}, SpanNoise is {"field":..,"curvature":..,"fold":..}, SpanPath is {"midpoint":..,"velocity":..,"speed":..,"acceleration":..,"halfLength":..}, SpanStage is {"frequency":..,"stretch":..,"axis":..,"shift":..,"offset":..,"octaveStep":..}, and CultSiteCells is {"nearest":..,"second":..,"nearestSite":..,"secondSite":..}. /// Property names are fixed: PropertyNamingPolicy does not rename them, and /// PropertyNameCaseInsensitive makes reading case-insensitive. /// @@ -481,7 +481,7 @@ public static class CultMathJson Add(UIntScalar.Instance, new Fields(("state", Val())), a => new Random { state = a[0] }, (v, a) => a[0] = v.state), - // Compositions: matrices are arrays of row objects, rect is {min, max}, CultCellular is {nearest, edge, id}, CultPhasor is {cos, sin}, SpanNoise is {field, curvature, fold}, SpanPath is {midpoint, velocity, speed, acceleration, halfLength}, SpanStage is {frequency, stretch, axis, shift, offset, octaveStep}. + // Compositions: matrices are arrays of row objects, rect is {min, max}, CultCellular is {nearest, edge, id}, CultPhasor is {cos, sin}, SpanNoise is {field, curvature, fold}, SpanPath is {midpoint, velocity, speed, acceleration, halfLength}, SpanStage is {frequency, stretch, axis, shift, offset, octaveStep}, CultSiteCells is {nearest, second, nearestSite, secondSite}. Add(f, new Rows(2, Xy()), a => new float2x2(a[0], a[1], a[2], a[3]), (v, a) => { a[0] = v[0].x; a[1] = v[0].y; a[2] = v[1].x; a[3] = v[1].y; }), @@ -539,6 +539,14 @@ public static class CultMathJson a[8] = v.offset.x; a[9] = v.offset.y; a[10] = v.offset.z; a[11] = v.octaveStep.x; a[12] = v.octaveStep.y; a[13] = v.octaveStep.z; }), + Add(f, new Fields(("nearest", Xyzw()), ("second", Xyzw()), ("nearestSite", Val()), ("secondSite", Val())), + a => new CultSiteCells(new float4(a[0], a[1], a[2], a[3]), new float4(a[4], a[5], a[6], a[7]), a[8], a[9]), + (v, a) => + { + a[0] = v.nearest.x; a[1] = v.nearest.y; a[2] = v.nearest.z; a[3] = v.nearest.w; + a[4] = v.second.x; a[5] = v.second.y; a[6] = v.second.z; a[7] = v.second.w; + a[8] = v.nearestSite; a[9] = v.secondSite; + }), }; var map = new Dictionary(); diff --git a/src/GameCult.Caching.MessagePack/CultMathResolver.cs b/src/GameCult.Caching.MessagePack/CultMathResolver.cs index 8f287a5c..95937282 100644 --- a/src/GameCult.Caching.MessagePack/CultMathResolver.cs +++ b/src/GameCult.Caching.MessagePack/CultMathResolver.cs @@ -180,7 +180,7 @@ public sealed class CultMathResolver : IFormatterResolver return value; }), - // Compositions: matrices are rows, rect is [min, max], CultCellular is [nearest, edge, id], CultPhasor is [cos, sin], SpanNoise is [field, curvature, fold], SpanPath is [midpoint, velocity, speed, acceleration, halfLength], SpanStage is [frequency, stretch, axis, shift, offset, octaveStep]. + // Compositions: matrices are rows, rect is [min, max], CultCellular is [nearest, edge, id], CultPhasor is [cos, sin], SpanNoise is [field, curvature, fold], SpanPath is [midpoint, velocity, speed, acceleration, halfLength], SpanStage is [frequency, stretch, axis, shift, offset, octaveStep], CultSiteCells is [nearest, second, nearestSite, secondSite]. Shape((ref MessagePackWriter w, float2x2 v) => { w.WriteArrayHeader(2); PutFloat2(ref w, v[0]); PutFloat2(ref w, v[1]); }, (ref MessagePackReader r) => { @@ -289,6 +289,23 @@ public sealed class CultMathResolver : IFormatterResolver } return value; }), + Shape((ref MessagePackWriter w, CultSiteCells v) => { w.WriteArrayHeader(4); PutFloat4(ref w, v.nearest); PutFloat4(ref w, v.second); w.Write(v.nearestSite); w.Write(v.secondSite); }, + (ref MessagePackReader r) => + { + var value = default(CultSiteCells); + for (int i = 0, n = r.ReadArrayHeader(); i < n; i++) + { + switch (i) + { + case 0: value.nearest = GetFloat4(ref r); break; + case 1: value.second = GetFloat4(ref r); break; + case 2: value.nearestSite = r.ReadSingle(); break; + case 3: value.secondSite = r.ReadSingle(); break; + default: r.Skip(); break; + } + } + return value; + }), }; var map = new Dictionary(); diff --git a/src/GameCult.Caching.MessagePack/README.md b/src/GameCult.Caching.MessagePack/README.md index acb1142f..a03a67c4 100644 --- a/src/GameCult.Caching.MessagePack/README.md +++ b/src/GameCult.Caching.MessagePack/README.md @@ -102,6 +102,7 @@ broken graph whole (an `ExprGraphException` naming the rule), so a consumer | `rect` | `[min, max]` of `float2` | `{"min":{..},"max":{..}}` | | `CultCellular` | `[nearest, edge, id]` | `{"nearest":{..},"edge":{..},"id":..}` | | `CultPhasor` | `[cos, sin]` of `float4` | `{"cos":{..},"sin":{..}}` | +| `CultSiteCells` | `[nearest, second, nearestSite, secondSite]` | `{"nearest":{..},"second":{..},"nearestSite":..,"secondSite":..}` | Floats are MessagePack float32 (`0xCA`), doubles float64 (`0xCB`), integers the shortest MessagePack integer. diff --git a/tests/GameCult.Caching.Tests/CultMathJsonTests.cs b/tests/GameCult.Caching.Tests/CultMathJsonTests.cs index 2920c2f3..4ebd552b 100644 --- a/tests/GameCult.Caching.Tests/CultMathJsonTests.cs +++ b/tests/GameCult.Caching.Tests/CultMathJsonTests.cs @@ -51,6 +51,8 @@ namespace GameCult.Caching.Tests """{"midpoint":{"x":1,"y":2,"z":3},"velocity":{"x":4,"y":5,"z":6},"speed":7,"acceleration":8,"halfLength":9}"""); Json(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }).Should().Be( """{"frequency":1,"stretch":2,"axis":{"x":3,"y":4,"z":5},"shift":{"x":6,"y":7,"z":8},"offset":{"x":9,"y":10,"z":11},"octaveStep":{"x":12,"y":13,"z":14}}"""); + Json(new CultSiteCells(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 9f, 10f)).Should().Be( + """{"nearest":{"x":1,"y":2,"z":3,"w":4},"second":{"x":5,"y":6,"z":7,"w":8},"nearestSite":9,"secondSite":10}"""); } [Test] @@ -78,6 +80,8 @@ namespace GameCult.Caching.Tests RoundTrip(new SpanNoise(new float4(1, 2, 3, 4), 0.25f, 0.5f)); RoundTrip(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }); RoundTrip(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }); + RoundTrip(new CultSiteCells(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 9f, 10f)); + RoundTrip(new CultSiteCells(new float4(0, 0, 0, 1.0e30f), new float4(0, 0, 0, 1.0e30f), -1f, -1f)); } [Test] diff --git a/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs b/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs index 1882bb80..c55154fb 100644 --- a/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs +++ b/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs @@ -105,6 +105,8 @@ namespace GameCult.Caching.Tests RoundTrip(new SpanNoise(new float4(1, 2, 3, 4), 0.25f, 0.5f)); RoundTrip(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }); RoundTrip(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }); + RoundTrip(new CultSiteCells(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 9f, 10f)); + RoundTrip(new CultSiteCells(new float4(0, 0, 0, 1.0e30f), new float4(0, 0, 0, 1.0e30f), -1f, -1f)); } [Test] @@ -158,6 +160,12 @@ namespace GameCult.Caching.Tests "93CA40C00000CA40E00000CA41000000" + "93CA41100000CA41200000CA41300000" + "93CA41400000CA41500000CA41600000"); + Hex(new CultSiteCells(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 9f, 10f)).Should().Be( + "94" + + "94CA3F800000CA40000000CA40400000CA40800000" + + "94CA40A00000CA40C00000CA40E00000CA41000000" + + "CA41100000" + + "CA41200000"); } [Test]