From a28a32f46d236c50bc8de3dd0010cd2c72727a0b Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:42:49 -0700 Subject: [PATCH] fix(json): Rectangle3DConverter loses a z-level on write (#2506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `Rectangle3DConverter.Write` corrupts a rectangle's Z range for certain bounds. The omit-z guard was: ```csharp var writeZ = value.Start.Z is > sbyte.MinValue and < sbyte.MaxValue || value.End.Z is > sbyte.MinValue and < sbyte.MaxValue; ``` This drops `z1`/`z2` whenever **both** Z bounds sit at/outside the sbyte extremes — but `Read` reconstructs absent Z as exactly `z1 = -128, z2 = 127` (depth 255). So any rectangle that trips the omit condition without *being* that sentinel round-trips to depth 255 and is corrupted. The concrete case: a **homeRange-style** bound `Start.Z = -128, End.Z = 128` (depth 256, the full vertical range used by spawners) gets `z1`/`z2` omitted on write, then reads back as depth **255** — silently losing the top z-level on every re-serialize. (Spotted while working on #2505; spawners now prefer the `homeRange` form so most square bounds avoid this path, but any non-square `spawnBounds` or other `Rectangle3D` JSON is affected.) ## Fix Omit Z only for the exact sentinel `Read` produces (`z1 == -128 && z2 == 127`); write it for anything else: ```csharp var writeZ = value.Start.Z != sbyte.MinValue || value.End.Z != sbyte.MaxValue; ``` `Read` is unchanged, so existing `regions.json` rectangles that omit Z keep loading identically. ## Tests New `Rectangle3DConverterTests`: round-trips the homeRange depth-256 case, the depth-255 sentinel, and ordinary/edge z values; asserts the sentinel omits Z while homeRange bounds write it. Server.Tests 717/717, UOContent.Tests 487/487. --- .../Tests/Json/Rectangle3DConverterTests.cs | 46 +++++++++++++++++++ .../Json/Converters/Rectangle3DConverter.cs | 2 +- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Projects/Server.Tests/Tests/Json/Rectangle3DConverterTests.cs diff --git a/Projects/Server.Tests/Tests/Json/Rectangle3DConverterTests.cs b/Projects/Server.Tests/Tests/Json/Rectangle3DConverterTests.cs new file mode 100644 index 000000000..b91c521dc --- /dev/null +++ b/Projects/Server.Tests/Tests/Json/Rectangle3DConverterTests.cs @@ -0,0 +1,46 @@ +using System.Text.Json; +using Server.Json; +using Xunit; + +namespace Server.Tests.Json; + +public class Rectangle3DConverterTests +{ + private static Rectangle3D RoundTrip(Rectangle3D value) + { + var json = JsonSerializer.Serialize(value, JsonConfig.DefaultOptions); + return JsonSerializer.Deserialize(json, JsonConfig.DefaultOptions); + } + + [Theory] + // homeRange-style full vertical range (z1=-128, depth 256 -> z2=128): must survive losslessly. + [InlineData(100, 100, -128, 11, 11, 256)] + // The omitted-z sentinel itself (z1=-128, z2=127 -> depth 255). + [InlineData(100, 100, -128, 11, 11, 255)] + // Ordinary bounds with a meaningful z. + [InlineData(100, 100, 0, 11, 11, 5)] + [InlineData(100, 100, 5, 11, 11, 122)] // z2 == 127 but z1 != -128 + [InlineData(100, 100, -128, 11, 11, 200)] // z1 == -128 but z2 != 127 + public void RoundTrips(int x, int y, int z, int w, int h, int d) + { + var rect = new Rectangle3D(x, y, z, w, h, d); + Assert.Equal(rect, RoundTrip(rect)); + } + + [Fact] + public void FullRangeSentinel_OmitsZ() + { + // z1=-128, z2=127 is the only case z is omitted. + var json = JsonSerializer.Serialize(new Rectangle3D(0, 0, -128, 10, 10, 255), JsonConfig.DefaultOptions); + Assert.DoesNotContain("z1", json); + Assert.DoesNotContain("z2", json); + } + + [Fact] + public void HomeRangeBounds_WritesZ() + { + var json = JsonSerializer.Serialize(new Rectangle3D(0, 0, -128, 10, 10, 256), JsonConfig.DefaultOptions); + Assert.Contains("\"z1\":", json); + Assert.Contains("\"z2\":", json); + } +} diff --git a/Projects/Server/Json/Converters/Rectangle3DConverter.cs b/Projects/Server/Json/Converters/Rectangle3DConverter.cs index 74d469f3b..890c5d6f7 100644 --- a/Projects/Server/Json/Converters/Rectangle3DConverter.cs +++ b/Projects/Server/Json/Converters/Rectangle3DConverter.cs @@ -175,7 +175,7 @@ public class Rectangle3DConverter : JsonConverter public override void Write(Utf8JsonWriter writer, Rectangle3D value, JsonSerializerOptions options) { - var writeZ = value.Start.Z is > sbyte.MinValue and < sbyte.MaxValue || value.End.Z is > sbyte.MinValue and < sbyte.MaxValue; + var writeZ = value.Start.Z != sbyte.MinValue || value.End.Z != sbyte.MaxValue; writer.WriteStartObject(); writer.WritePropertyName("x1");