diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..f410d53
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,31 @@
+name: Build and Test
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+
+env:
+ BUILD_CONFIG: Release
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build -c Release --no-restore
+
+ - name: Test
+ run: dotnet test -c Release --no-build --verbosity normal
diff --git a/.gitignore b/.gitignore
index 3ce9469..6ea7526 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@
.DS_Store
/packages/*
+
+BenchmarkDotNet.Artifacts/
diff --git a/ModernUO.Serialization.Generator.Benchmarks/GeneratorBenchmarks.cs b/ModernUO.Serialization.Generator.Benchmarks/GeneratorBenchmarks.cs
new file mode 100644
index 0000000..4cb5ec8
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Benchmarks/GeneratorBenchmarks.cs
@@ -0,0 +1,163 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2026 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: GeneratorBenchmarks.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System.Collections.Immutable;
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using ModernUO.Serialization.Generator;
+using ModernUO.Serialization.Generator.Tests.Helpers;
+
+namespace ModernUO.Serialization.Generator.Benchmarks;
+
+///
+/// Measures the generator over a synthetic corpus: a cold full run, and the incremental
+/// re-run cost after editing a single file - the operation an IDE performs constantly.
+/// A correctly incremental pipeline keeps the re-run near the cost of one class; a broken
+/// one re-runs the whole corpus.
+///
+[MemoryDiagnoser]
+public class GeneratorBenchmarks
+{
+ [Params(150)]
+ public int ClassCount { get; set; }
+
+ private CSharpCompilation _compilation = null!;
+ private ImmutableArray _additionalTexts;
+ private List _references = null!;
+
+ private GeneratorDriver _warmDriver = null!;
+ private CSharpCompilation _warmCompilation = null!;
+ private SyntaxTree _editTarget = null!;
+ private bool _editToggle;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var trustedAssemblies = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
+ .Split(Path.PathSeparator);
+
+ _references = trustedAssemblies
+ .Where(p => !string.IsNullOrEmpty(p))
+ .Select(p => (MetadataReference)MetadataReference.CreateFromFile(p))
+ .Concat([MetadataReference.CreateFromFile(typeof(SerializationGeneratorAttribute).Assembly.Location)])
+ .ToList();
+
+ var trees = new List
+ {
+ CSharpSyntaxTree.ParseText(SourceGeneratorTestHelper.ServerStubs)
+ };
+
+ var additionalTexts = ImmutableArray.CreateBuilder();
+
+ for (var i = 0; i < ClassCount; i++)
+ {
+ trees.Add(CSharpSyntaxTree.ParseText(BuildClassSource(i)));
+ additionalTexts.Add(
+ new InMemoryAdditionalText($"Server.TestContent.BenchItem{i}.v0.json", BuildMigrationJson(i))
+ );
+ }
+
+ _additionalTexts = additionalTexts.ToImmutable();
+ _compilation = CSharpCompilation.Create(
+ "BenchmarkAssembly",
+ trees,
+ _references,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ );
+
+ // Warm state for the incremental benchmark: one full run, then per-invocation edits.
+ _warmDriver = CSharpGeneratorDriver
+ .Create(new EntitySerializationGenerator())
+ .AddAdditionalTexts(_additionalTexts)
+ .RunGenerators(_compilation);
+ _warmCompilation = _compilation;
+ _editTarget = _warmCompilation.SyntaxTrees.Last();
+ }
+
+ [Benchmark]
+ public GeneratorDriver ColdFullRun() =>
+ CSharpGeneratorDriver
+ .Create(new EntitySerializationGenerator())
+ .AddAdditionalTexts(_additionalTexts)
+ .RunGenerators(_compilation);
+
+ [Benchmark]
+ public GeneratorDriver WarmRerunAfterSingleEdit()
+ {
+ // Toggle one file between two whitespace variants so every invocation is a real edit.
+ _editToggle = !_editToggle;
+ var text = _editTarget.GetText().ToString();
+ var edited = _editToggle ? text + "\n// edit\n" : text.Replace("\n// edit\n", "");
+ var newTree = CSharpSyntaxTree.ParseText(edited);
+
+ _warmCompilation = _warmCompilation.ReplaceSyntaxTree(_editTarget, newTree);
+ _editTarget = newTree;
+
+ _warmDriver = _warmDriver.RunGenerators(_warmCompilation);
+ return _warmDriver;
+ }
+
+ private static string BuildClassSource(int index)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("using System;");
+ sb.AppendLine("using ModernUO.Serialization;");
+ sb.AppendLine("using Server;");
+ sb.AppendLine();
+ sb.AppendLine("namespace Server.TestContent");
+ sb.AppendLine("{");
+ sb.AppendLine(" [SerializationGenerator(1)]");
+ sb.AppendLine($" public partial class BenchItem{index} : ISerializable");
+ sb.AppendLine(" {");
+
+ for (var f = 0; f < 6; f++)
+ {
+ sb.AppendLine($" [SerializableField({f})]");
+ sb.AppendLine($" private {(f % 2 == 0 ? "int" : "string")} _field{f};");
+ sb.AppendLine();
+ }
+
+ sb.AppendLine(" public DateTime Created { get; set; }");
+ sb.AppendLine(" public Serial Serial { get; }");
+ sb.AppendLine(" public bool Deleted => false;");
+ sb.AppendLine(" public void Delete() { }");
+ sb.AppendLine();
+ sb.AppendLine(" private void MigrateFrom(V0Content content)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" _field0 = content.Field0;");
+ sb.AppendLine(" }");
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+
+ return sb.ToString();
+ }
+
+ private static string BuildMigrationJson(int index) =>
+ $$"""
+ {
+ "version": 0,
+ "type": "Server.TestContent.BenchItem{{index}}",
+ "properties": [
+ {
+ "name": "Field0",
+ "type": "int",
+ "rule": "PrimitiveTypeMigrationRule"
+ }
+ ]
+ }
+ """;
+}
diff --git a/ModernUO.Serialization.Generator.Benchmarks/ModernUO.Serialization.Generator.Benchmarks.csproj b/ModernUO.Serialization.Generator.Benchmarks/ModernUO.Serialization.Generator.Benchmarks.csproj
new file mode 100644
index 0000000..514db43
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Benchmarks/ModernUO.Serialization.Generator.Benchmarks.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net10.0
+ preview
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ModernUO.Serialization.Generator.Benchmarks/Program.cs b/ModernUO.Serialization.Generator.Benchmarks/Program.cs
new file mode 100644
index 0000000..8ee24d0
--- /dev/null
+++ b/ModernUO.Serialization.Generator.Benchmarks/Program.cs
@@ -0,0 +1,3 @@
+using BenchmarkDotNet.Running;
+
+BenchmarkSwitcher.FromAssembly(typeof(ModernUO.Serialization.Generator.Benchmarks.GeneratorBenchmarks).Assembly).Run(args);
diff --git a/ModernUO.Serialization.Generator.DiffTool/Application.cs b/ModernUO.Serialization.Generator.DiffTool/Application.cs
new file mode 100644
index 0000000..969cc55
--- /dev/null
+++ b/ModernUO.Serialization.Generator.DiffTool/Application.cs
@@ -0,0 +1,114 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2026 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Application.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+using ModernUO.Serialization.Generator;
+using ModernUO.Serialization.SchemaGenerator;
+
+namespace ModernUO.Serialization.DiffTool;
+
+///
+/// Runs the generator against every project in a solution and writes a deterministic manifest
+/// of hint names and content hashes. Running it before and after a generator change and
+/// diffing the manifests proves the change is output-identical across the real corpus.
+///
+public static class Application
+{
+ public static async Task Main(string[] args)
+ {
+ if (args.Length < 2)
+ {
+ Console.WriteLine(
+ "Usage: ModernUO.Serialization.Generator.DiffTool