diff --git a/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs b/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
index 3c88585..72bcf1e 100644
--- a/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
+++ b/AyCode.Core.Tests/TestModels/BenchmarkTestDataProvider.cs
@@ -19,8 +19,8 @@ public static class BenchmarkTestDataProvider
public static TestOrder CreateProfilerOrder()
{
TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("SharedTag");
- var sharedUser = TestDataFactory.CreateUser("shareduser");
+ var sharedTag = TestDataFactory.CreateTag("KözösCímke");
+ var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
return TestDataFactory.CreateOrder(
itemCount: 3,
palletsPerItem: 3,
@@ -34,8 +34,8 @@ public static class BenchmarkTestDataProvider
{
if (resetId) TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("SharedTag");
- var sharedUser = TestDataFactory.CreateUser("shareduser");
+ var sharedTag = TestDataFactory.CreateTag("KözösCímke");
+ var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
var order = TestDataFactory.CreateOrder(
itemCount: 2,
@@ -54,16 +54,16 @@ public static class BenchmarkTestDataProvider
{
if (resetId) TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("SharedTag");
- var sharedUser = TestDataFactory.CreateUser("shareduser");
- var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
+ var sharedTag = TestDataFactory.CreateTag("KözösCímke");
+ var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
+ var sharedMeta = TestDataFactory.CreateMetadata("közös", withChild: true);
var sharedPreferences = new UserPreferences
{
- Theme = "dark",
- Language = "en-US",
+ Theme = "sötét",
+ Language = "magyar",
NotificationsEnabled = true,
- EmailDigestFrequency = "weekly"
+ EmailDigestFrequency = "hetenkénti"
};
sharedUser.Preferences = sharedPreferences;
@@ -86,15 +86,15 @@ public static class BenchmarkTestDataProvider
{
if (resetId) TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("SharedTag");
- var sharedUser = TestDataFactory.CreateUser("shareduser");
+ var sharedTag = TestDataFactory.CreateTag("KözösCímke");
+ var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
var sharedPreferences = new UserPreferences
{
- Theme = "light",
- Language = "de-DE",
+ Theme = "világos",
+ Language = "német",
NotificationsEnabled = false,
- EmailDigestFrequency = "daily"
+ EmailDigestFrequency = "naponkénti"
};
sharedUser.Preferences = sharedPreferences;
@@ -116,15 +116,15 @@ public static class BenchmarkTestDataProvider
{
if (resetId) TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("RepeatedTag");
- var sharedUser = TestDataFactory.CreateUser("repeateduser");
+ var sharedTag = TestDataFactory.CreateTag("IsmétlődőCímke");
+ var sharedUser = TestDataFactory.CreateUser("ismétlődőfelhasználó");
var sharedPreferences = new UserPreferences
{
- Theme = "dark",
- Language = "en-US",
+ Theme = "sötét",
+ Language = "magyar",
NotificationsEnabled = true,
- EmailDigestFrequency = "weekly"
+ EmailDigestFrequency = "hetenkénti"
};
sharedUser.Preferences = sharedPreferences;
@@ -162,16 +162,16 @@ public static class BenchmarkTestDataProvider
{
if (resetId) TestDataFactory.ResetIdCounter();
- var sharedTag = TestDataFactory.CreateTag("DeepTag");
- var sharedUser = TestDataFactory.CreateUser("deepuser");
- var sharedCategory = TestDataFactory.CreateCategory("DeepCategory");
+ var sharedTag = TestDataFactory.CreateTag("MélyCímke");
+ var sharedUser = TestDataFactory.CreateUser("mélyfelhasználó");
+ var sharedCategory = TestDataFactory.CreateCategory("MélyKategória");
var sharedPreferences = new UserPreferences
{
- Theme = "light",
- Language = "fr-FR",
+ Theme = "világos",
+ Language = "francia",
NotificationsEnabled = false,
- EmailDigestFrequency = "monthly"
+ EmailDigestFrequency = "havonkénti"
};
sharedUser.Preferences = sharedPreferences;
diff --git a/AyCode.Core.Tests/TestModels/TestDataFactory.cs b/AyCode.Core.Tests/TestModels/TestDataFactory.cs
index 028bf84..31aaa4b 100644
--- a/AyCode.Core.Tests/TestModels/TestDataFactory.cs
+++ b/AyCode.Core.Tests/TestModels/TestDataFactory.cs
@@ -3,6 +3,10 @@ namespace AyCode.Core.Tests.TestModels;
///
/// Factory for creating test data hierarchies.
/// Used by both unit tests and benchmarks.
+///
+/// All placeholder strings use Hungarian (UTF-8 multi-byte) content to exercise the UTF-8
+/// encoder/decoder path rather than the ASCII fast-path. This makes the benchmark reflect
+/// realistic i18n payloads, not just the FixStrAscii / StringAscii marker fast-paths.
///
public static class TestDataFactory
{
@@ -29,12 +33,12 @@ public static class TestDataFactory
return new SharedTag
{
Id = id,
- Name = name ?? $"Tag-{id}",
- Color = color ?? $"#{id:X2}{(id * 10) % 256:X2}{(id * 20) % 256:X2}",
+ Name = name ?? $"Címke-{id}",
+ Color = color ?? $"Szín-#{id:X2}{(id * 10) % 256:X2}{(id * 20) % 256:X2}",
Priority = id % 5,
IsActive = id % 2 == 0,
CreatedAt = DateTime.UtcNow.AddDays(-id),
- Description = $"Description for tag {id}"
+ Description = $"Címke leírása {id}"
};
}
@@ -47,8 +51,8 @@ public static class TestDataFactory
return new SharedCategory
{
Id = id,
- Name = name ?? $"Category-{id}",
- Description = $"Category description {id}",
+ Name = name ?? $"Kategória-{id}",
+ Description = $"Kategória leírása {id}",
SortOrder = id * 100,
IsDefault = id == 1,
ParentCategoryId = parentId,
@@ -66,20 +70,20 @@ public static class TestDataFactory
return new SharedUser
{
Id = id,
- Username = username ?? $"user{id}",
- Email = $"user{id}@test.com",
- FirstName = $"First{id}",
- LastName = $"Last{id}",
+ Username = username ?? $"felhasználó{id}",
+ Email = $"felhasználó{id}@teszt.hu",
+ FirstName = $"Vezetéknév{id}",
+ LastName = $"Keresztnév{id}",
IsActive = true,
Role = role,
LastLoginAt = DateTime.UtcNow.AddHours(-id),
CreatedAt = DateTime.UtcNow.AddYears(-1),
Preferences = new UserPreferences
{
- Theme = id % 2 == 0 ? "dark" : "light",
- Language = "en-US",
+ Theme = id % 2 == 0 ? "sötét" : "világos",
+ Language = "magyar",
NotificationsEnabled = true,
- EmailDigestFrequency = "daily"
+ EmailDigestFrequency = "naponkénti"
}
};
}
@@ -92,10 +96,10 @@ public static class TestDataFactory
var id = _idCounter++;
return new MetadataInfo
{
- Key = key ?? $"Meta-{id}",
- Value = $"MetaValue-{id}",
+ Key = key ?? $"Metaadat-{id}",
+ Value = $"MetaÉrték-{id}",
Timestamp = DateTime.UtcNow.AddMinutes(-id * 10),
- ChildMetadata = withChild ? CreateMetadata($"Child-{id}", false) : null
+ ChildMetadata = withChild ? CreateMetadata($"Gyermek-{id}", false) : null
};
}
@@ -120,11 +124,11 @@ public static class TestDataFactory
{
// If sharedUser is provided but no sharedPreferences, use the user's preferences as shared
sharedPreferences ??= sharedUser?.Preferences;
-
+
var order = new TestOrder
{
Id = _idCounter++,
- OrderNumber = $"ORD-{_idCounter:D4}",
+ OrderNumber = $"Megrendelés-{_idCounter:D4}",
Status = TestStatus.Pending,
CreatedAt = DateTime.UtcNow,
TotalAmount = 1000m + _idCounter * 100,
@@ -144,12 +148,12 @@ public static class TestDataFactory
for (int i = 0; i < itemCount; i++)
{
var item = CreateOrderItem(
- palletsPerItem,
- measurementsPerPallet,
- pointsPerMeasurement,
- sharedTag,
- sharedUser,
- sharedMetadata,
+ palletsPerItem,
+ measurementsPerPallet,
+ pointsPerMeasurement,
+ sharedTag,
+ sharedUser,
+ sharedMetadata,
sharedPreferences,
sharedCategory);
item.ParentOrder = order;
@@ -181,11 +185,11 @@ public static class TestDataFactory
assignee = CreateUser();
assignee.Preferences = sharedPreferences;
}
-
+
var item = new TestOrderItem
{
Id = _idCounter++,
- ProductName = $"Product-{_idCounter}",
+ ProductName = $"Termék-{_idCounter}",
Quantity = 10 + _idCounter,
UnitPrice = 5.5m * _idCounter,
Status = TestStatus.Pending,
@@ -198,8 +202,8 @@ public static class TestDataFactory
{
// Pass shared references to all levels - creates many shared refs!
var pallet = CreatePallet(
- measurementsPerPallet,
- pointsPerMeasurement,
+ measurementsPerPallet,
+ pointsPerMeasurement,
sharedMetadata,
sharedTag, // IId shared ref
sharedUser, // IId shared ref
@@ -228,7 +232,7 @@ public static class TestDataFactory
var pallet = new TestPallet
{
Id = _idCounter++,
- PalletCode = $"PLT-{_idCounter:D4}",
+ PalletCode = $"Raklapkód-{_idCounter:D4}",
TrayCount = 5 + _idCounter % 10,
Status = TestStatus.Pending,
Weight = 100.5 + _idCounter,
@@ -259,7 +263,7 @@ public static class TestDataFactory
var measurement = new TestMeasurement
{
Id = _idCounter++,
- Name = $"Measurement-{_idCounter}",
+ Name = $"Mérés-{_idCounter}",
TotalWeight = 100.5 + _idCounter,
CreatedAt = DateTime.UtcNow,
Tag = sharedTag,
@@ -287,7 +291,7 @@ public static class TestDataFactory
return new TestMeasurementPoint
{
Id = id,
- Label = $"Point-{id}",
+ Label = $"MérőPont-{id}",
Value = 10.5 + (id * 0.1),
MeasuredAt = DateTime.UtcNow,
Tag = sharedTag,
@@ -310,23 +314,23 @@ public static class TestDataFactory
int pointsPerMeasurement = 5)
{
ResetIdCounter();
-
+
// Create shared references that will be used throughout
var sharedTags = Enumerable.Range(1, 10).Select(_ => CreateTag()).ToList();
- var sharedUser = CreateUser("benchuser", TestUserRole.Admin);
- var sharedMetadata = CreateMetadata("benchmark", withChild: true);
+ var sharedUser = CreateUser("mérőfelhasználó", TestUserRole.Admin);
+ var sharedMetadata = CreateMetadata("mérőteszt", withChild: true);
var order = new TestOrder
{
Id = _idCounter++,
- OrderNumber = $"BENCH-{_idCounter:D6}",
+ OrderNumber = $"MÉRŐTESZT-{_idCounter:D6}",
Status = TestStatus.Processing,
CreatedAt = DateTime.UtcNow,
TotalAmount = 999999.99m,
PrimaryTag = sharedTags[0],
SecondaryTag = sharedTags[0],
Owner = sharedUser,
- Category = CreateCategory("Benchmark"),
+ Category = CreateCategory("Mérőteszt"),
OrderMetadata = sharedMetadata,
AuditMetadata = sharedMetadata,
Tags = sharedTags.Take(3).ToList()
@@ -337,7 +341,7 @@ public static class TestDataFactory
var item = new TestOrderItem
{
Id = _idCounter++,
- ProductName = $"BenchProduct-{i}",
+ ProductName = $"MérőTermék-{i}",
Quantity = 100 + i * 10,
UnitPrice = 25.99m + i,
Status = (TestStatus)(i % 5),
@@ -352,7 +356,7 @@ public static class TestDataFactory
var pallet = new TestPallet
{
Id = _idCounter++,
- PalletCode = $"PLT-{i}-{p}",
+ PalletCode = $"Raklapkód-{i}-{p}",
TrayCount = 10 + p,
Status = (TestStatus)(p % 4),
Weight = 500.0 + p * 50,
@@ -365,7 +369,7 @@ public static class TestDataFactory
var measurement = new TestMeasurement
{
Id = _idCounter++,
- Name = $"Meas-{i}-{p}-{m}",
+ Name = $"Mérés-{i}-{p}-{m}",
TotalWeight = 50.0 + m * 10,
CreatedAt = DateTime.UtcNow.AddMinutes(-m)
};
@@ -376,7 +380,7 @@ public static class TestDataFactory
var point = new TestMeasurementPoint
{
Id = _idCounter++,
- Label = $"Pt-{i}-{p}-{m}-{pt}",
+ Label = $"MérőPnt-{i}-{p}-{m}-{pt}",
Value = 1.0 + pt * 0.5,
MeasuredAt = DateTime.UtcNow.AddSeconds(-pt)
};
@@ -409,17 +413,17 @@ public static class TestDataFactory
int pointsPerMeasurement = 4)
{
ResetIdCounter();
-
+
// Create shared references - these will be heavily reused (tests $ref handling)
var sharedTags = Enumerable.Range(1, 50).Select(_ => CreateTag()).ToList();
- var sharedUsers = Enumerable.Range(1, 20).Select(i => CreateUser($"user{i}", (TestUserRole)(i % 4))).ToList();
- var sharedMetadata = CreateMetadata("large-scale", withChild: true);
- var sharedCategories = Enumerable.Range(1, 10).Select(i => CreateCategory($"Cat-{i}")).ToList();
+ var sharedUsers = Enumerable.Range(1, 20).Select(i => CreateUser($"felhasználó{i}", (TestUserRole)(i % 4))).ToList();
+ var sharedMetadata = CreateMetadata("nagy-méretű", withChild: true);
+ var sharedCategories = Enumerable.Range(1, 10).Select(i => CreateCategory($"Kategória-{i}")).ToList();
var order = new TestOrder
{
Id = _idCounter++,
- OrderNumber = $"LARGE-{_idCounter:D8}",
+ OrderNumber = $"NAGYMÉRET-{_idCounter:D8}",
Status = TestStatus.Processing,
CreatedAt = DateTime.UtcNow,
TotalAmount = 9999999.99m,
@@ -437,7 +441,7 @@ public static class TestDataFactory
var item = new TestOrderItem
{
Id = _idCounter++,
- ProductName = $"Product-{i}",
+ ProductName = $"Termék-{i}",
Quantity = 100 + i,
UnitPrice = 10.99m + (i % 100),
Status = (TestStatus)(i % 5),
@@ -452,7 +456,7 @@ public static class TestDataFactory
var pallet = new TestPallet
{
Id = _idCounter++,
- PalletCode = $"P-{i}-{p}",
+ PalletCode = $"Raklapkód-{i}-{p}",
TrayCount = 5 + (p % 10),
Status = (TestStatus)(p % 4),
Weight = 100.0 + p * 10,
@@ -465,7 +469,7 @@ public static class TestDataFactory
var measurement = new TestMeasurement
{
Id = _idCounter++,
- Name = $"M-{i}-{p}-{m}",
+ Name = $"Mérés-{i}-{p}-{m}",
TotalWeight = 10.0 + m,
CreatedAt = DateTime.UtcNow
};
@@ -476,7 +480,7 @@ public static class TestDataFactory
var point = new TestMeasurementPoint
{
Id = _idCounter++,
- Label = $"Pt-{i}-{p}-{m}-{pt}",
+ Label = $"MérőPnt-{i}-{p}-{m}-{pt}",
Value = pt * 0.1,
MeasuredAt = DateTime.UtcNow
};
@@ -518,7 +522,7 @@ public static class TestDataFactory
DecimalValue = 12345.6789m,
FloatValue = 1.5f,
BoolValue = true,
- StringValue = "Test String ?? ????",
+ StringValue = "Teszt Szöveg árvíztűrőtükörfúrógép",
GuidValue = Guid.Parse("12345678-1234-1234-1234-123456789abc"),
DateTimeValue = new DateTime(2024, 12, 25, 12, 30, 45, DateTimeKind.Utc),
EnumValue = TestStatus.Shipped,
diff --git a/AyCode.Core/Serializers/AcSerializerOptions.cs b/AyCode.Core/Serializers/AcSerializerOptions.cs
index eecb9f8..9e0e1d8 100644
--- a/AyCode.Core/Serializers/AcSerializerOptions.cs
+++ b/AyCode.Core/Serializers/AcSerializerOptions.cs
@@ -116,17 +116,24 @@ public enum ReferenceHandlingMode : byte
}
///
-/// Wire encoding mode for binary serialization.
+/// Wire encoding mode. Pick by content + host:
+/// ASCII-heavy → (smaller wire, faster CPU).
+/// UTF-8 multi-byte → (larger wire, faster CPU).
+/// AVX-512BW host → wins everywhere (smaller wire on all content, CPU parity with Fast).
///
public enum WireMode : byte
{
///
- /// Compact encoding: VarInt for integers, UTF-8 for strings. Smaller output.
+ /// VarInt + UTF-8 with ASCII marker-dispatch fast-path. Smallest wire on all content types.
+ /// Best for ASCII-heavy workloads, any workload where wire size matters (network, storage),
+ /// or any workload on AVX-512BW hosts.
///
Compact = 0,
///
- /// Fast encoding: fixed-width integers, UTF-16 for strings. Larger output, faster encode/decode.
+ /// Fixed-width ints + UTF-16 raw memcpy (no UTF-8 transcoding). Larger wire (2 bytes/char fixed).
+ /// Best for UTF-8 multi-byte workloads (i18n / CJK) on non-AVX-512 hosts where wire size
+ /// matters less than CPU throughput (in-memory IPC, fast local transport).
///
Fast = 1
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
index 792b858..2fc1f8d 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
@@ -5,6 +5,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
using System.Text;
namespace AyCode.Core.Serializers.Binaries;
@@ -495,17 +496,21 @@ public static partial class AcBinaryDeserializer
/// Counts UTF-16 chars produced by decoding the given UTF-8 byte span.
///
///
- /// Vectorized via Vector256 (32 bytes/iter) using two bit-pattern checks:
+ /// Layered SIMD: Vector512 (64 byte/iter) on AVX-512BW hosts → Vector256 (32 byte/iter)
+ /// on AVX2 hosts → scalar tail. Both SIMD paths use the same two bit-pattern checks:
/// • Non-continuation bytes (NOT 10xxxxxx, mask 0xC0 ≠ 0x80): each contributes 1 char.
/// • 4-byte start bytes (11110xxx, mask 0xF8 == 0xF0): each contributes an EXTRA char (surrogate pair).
///
- /// SIMD per-block result: (32 - popcount(continuationMask)) + popcount(fourByteStartMask).
- /// Scalar tail handles the remaining <32 bytes.
+ /// SIMD per-block result: (N - popcount(continuationMask)) + popcount(fourByteStartMask)
+ /// where N = 64 (Vector512) or 32 (Vector256). Scalar tail handles the remaining bytes.
///
/// Char-count rules:
/// • Continuation bytes (10xxxxxx, 0x80–0xBF) — produce no char, skip.
/// • All other start bytes (0xxxxxxx, 110xxxxx, 1110xxxx) — produce 1 char each.
/// • 4-byte start bytes (11110xxx, 0xF0–0xF7) — produce 2 chars (UTF-16 surrogate pair).
+ ///
+ /// JIT-time path-selection: Avx512BW.IsSupported and Vector256.IsHardwareAccelerated
+ /// are [Intrinsic] static booleans — the JIT/AOT constant-folds the dead branches per host.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CountUtf8Chars(ReadOnlySpan bytes)
@@ -514,8 +519,34 @@ public static partial class AcBinaryDeserializer
var i = 0;
ref var bytesRef = ref MemoryMarshal.GetReference(bytes);
- // SIMD path: 32 bytes/iter via Vector256
- if (Vector256.IsHardwareAccelerated && bytes.Length >= 32)
+ // SIMD path 1: 64 bytes/iter via Vector512 (AVX-512BW hosts)
+ if (Avx512BW.IsSupported && bytes.Length >= 64)
+ {
+ var contMask512 = Vector512.Create((byte)0xC0);
+ var contValue512 = Vector512.Create((byte)0x80);
+ var fourByteMask512 = Vector512.Create((byte)0xF8);
+ var fourByteValue512 = Vector512.Create((byte)0xF0);
+
+ do
+ {
+ var v = Vector512.LoadUnsafe(ref bytesRef, (uint)i);
+
+ // Non-continuation count: 64 - popcount(continuation byte mask)
+ var contMatches = Vector512.Equals(v & contMask512, contValue512);
+ var contBits = contMatches.ExtractMostSignificantBits(); // ulong
+ count += 64 - System.Numerics.BitOperations.PopCount(contBits);
+
+ // 4-byte start count: popcount(fourByte start byte mask)
+ var fourByteMatches = Vector512.Equals(v & fourByteMask512, fourByteValue512);
+ var fourByteBits = fourByteMatches.ExtractMostSignificantBits();
+ count += System.Numerics.BitOperations.PopCount(fourByteBits);
+
+ i += 64;
+ } while (bytes.Length - i >= 64);
+ }
+
+ // SIMD path 2: 32 bytes/iter via Vector256 (AVX2 hosts, also handles AVX-512 tail < 64)
+ if (Vector256.IsHardwareAccelerated && bytes.Length - i >= 32)
{
var contMask = Vector256.Create((byte)0xC0);
var contValue = Vector256.Create((byte)0x80);
@@ -540,6 +571,34 @@ public static partial class AcBinaryDeserializer
} while (bytes.Length - i >= 32);
}
+ // SIMD path 3: 16 bytes/iter via Vector128 (Apple Silicon NEON, WASM SIMD, legacy SSE2;
+ // also handles tail < 32 from higher tiers). Cross-platform — Vector128.IsHardwareAccelerated
+ // returns true on any host with a 128-bit SIMD ISA (NEON / SSE2 / WASM SIMD).
+ if (Vector128.IsHardwareAccelerated && bytes.Length - i >= 16)
+ {
+ var contMask128 = Vector128.Create((byte)0xC0);
+ var contValue128 = Vector128.Create((byte)0x80);
+ var fourByteMask128 = Vector128.Create((byte)0xF8);
+ var fourByteValue128 = Vector128.Create((byte)0xF0);
+
+ do
+ {
+ var v = Vector128.LoadUnsafe(ref bytesRef, (uint)i);
+
+ // Non-continuation count: 16 - popcount(continuation byte mask)
+ var contMatches = Vector128.Equals(v & contMask128, contValue128);
+ var contBits = contMatches.ExtractMostSignificantBits();
+ count += 16 - System.Numerics.BitOperations.PopCount(contBits);
+
+ // 4-byte start count: popcount(fourByte start byte mask)
+ var fourByteMatches = Vector128.Equals(v & fourByteMask128, fourByteValue128);
+ var fourByteBits = fourByteMatches.ExtractMostSignificantBits();
+ count += System.Numerics.BitOperations.PopCount(fourByteBits);
+
+ i += 16;
+ } while (bytes.Length - i >= 16);
+ }
+
// Scalar tail (and fallback for non-SIMD hardware)
for (; i < bytes.Length; i++)
{
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index f838f7b..45b256a 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -6,6 +6,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
using System.Text;
using System.Threading;
using static AyCode.Core.Helpers.JsonUtilities;
@@ -904,7 +905,28 @@ public static partial class AcBinarySerializer
ref ushort srcRefU16 = ref Unsafe.As(ref srcRefChar);
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
- // Phase 1 — Vector256 ASCII narrow (16 chars/iter, falls out on first non-ASCII)
+ // Phase 1a — Vector512 ASCII narrow (32 chars/iter on AVX-512BW hosts).
+ // JIT-time path-selection via Avx512BW.IsSupported [Intrinsic] static bool — non-AVX-512
+ // hosts get this branch eliminated by constant-folding (zero overhead in the generated asm).
+ if (Avx512BW.IsSupported)
+ {
+ var asciiMask512 = Vector512.Create((ushort)0xFF80);
+ while (src.Length - srcIdx >= Vector512.Count) // 32 chars per Vector512
+ {
+ var v = Vector512.LoadUnsafe(ref srcRefU16, (uint)srcIdx);
+ // ASCII detect: any char's high bits set (>= 0x80)?
+ if ((v & asciiMask512) != Vector512.Zero) break;
+ // Narrow 32 ushorts (Vector512) → 32 bytes (Vector256) via two 256-bit halves.
+ // The JIT lowers this to AVX-512 VPACKUSWB on capable hosts (single-instruction pack).
+ var bytes = Vector256.Narrow(v.GetLower(), v.GetUpper());
+ bytes.StoreUnsafe(ref dstRef, (uint)dstIdx);
+ srcIdx += Vector512.Count;
+ dstIdx += Vector512.Count; // 32 chars → 32 bytes (1:1 for ASCII)
+ }
+ }
+
+ // Phase 1b — Vector256 ASCII narrow (16 chars/iter on AVX2 hosts; also handles tail < 32 chars
+ // after the AVX-512 path on capable hosts).
if (Vector256.IsHardwareAccelerated)
{
var asciiMask = Vector256.Create((ushort)0xFF80);
@@ -921,6 +943,27 @@ public static partial class AcBinarySerializer
}
}
+ // Phase 1c — Vector128 ASCII narrow (16 chars/iter on Apple Silicon NEON, WASM SIMD,
+ // legacy SSE2; also handles tail < 16 chars after higher tiers). Cross-platform —
+ // Vector128.IsHardwareAccelerated is true on any 128-bit-SIMD-capable host.
+ // Two Vector128 loads (8 + 8 = 16 chars) narrow to one Vector128 (16 bytes).
+ if (Vector128.IsHardwareAccelerated)
+ {
+ var asciiMask128 = Vector128.Create((ushort)0xFF80);
+ while (src.Length - srcIdx >= 16) // 16 chars = 2 × Vector128
+ {
+ var lo = Vector128.LoadUnsafe(ref srcRefU16, (uint)srcIdx);
+ var hi = Vector128.LoadUnsafe(ref srcRefU16, (uint)(srcIdx + 8));
+ // ASCII detect: any char's high bits set in either half?
+ if (((lo | hi) & asciiMask128) != Vector128.Zero) break;
+ // Narrow 2× Vector128 (16 chars) → Vector128 (16 bytes)
+ var bytes = Vector128.Narrow(lo, hi);
+ bytes.StoreUnsafe(ref dstRef, (uint)dstIdx);
+ srcIdx += 16;
+ dstIdx += 16;
+ }
+ }
+
// Phase 2/3 — scalar with DWORD ASCII batch
while (srcIdx < src.Length)
{
diff --git a/AyCode.Core/docs/BINARY/BINARY_TODO.md b/AyCode.Core/docs/BINARY/BINARY_TODO.md
index 60c1457..b3358da 100644
--- a/AyCode.Core/docs/BINARY/BINARY_TODO.md
+++ b/AyCode.Core/docs/BINARY/BINARY_TODO.md
@@ -672,35 +672,101 @@ Two `.csproj` files:
- `.Aot` throws a clear `InvalidOperationException` (not `MissingMethodException`) when a non-`[AcBinarySerializable]` type is encountered at deser time
- `BINARY_FEATURES.md` NativeAOT Compatibility section documents both packages and when to choose which
-## ACCORE-BIN-T-V4N2: .NET 11 SIMD-specialized UTF-8 decoder via multi-targeting
-**Priority:** P3 · **Type:** Performance · **Related:** `AcBinaryDeserializer.BinaryDeserializationContext.Read.cs::DecodeUtf8`
+## ACCORE-BIN-T-V4N2: Cross-tier SIMD UTF-8 transcoder paths (AVX-512BW + Vector128 + multi-byte transcoder)
+**Priority:** P2 · **Type:** Performance · **Related:** `EncodeUtf8SinglePass`, `DecodeUtf8SinglePass`, `CountUtf8Chars`
-The custom UTF-8 → UTF-16 decoder in `DecodeUtf8` / `CountUtf8Chars` / `DecodeUtf8ToChars` currently targets .NET 9 — scalar two-pass with optional Vector256 ASCII prefix widen + DWORD ASCII batch (per Phase 1 optimization). .NET 11 (planned ~Nov 2026) exposes additional SIMD intrinsics that can meaningfully accelerate the decoder on AVX-512-capable hosts, particularly the `vpcompressb`-style mask-driven byte compression that simdutf relies on for its 64-byte AVX-512 transcoder.
+**Current SIMD hierarchy (post 2026-05-05 implementation):**
-### Why .NET 11 specifically (and not .NET 10)
+```
+AVX-512BW (64 byte/iter) → Server, Intel 11th gen client, AMD Zen 4+
+Vector256 / AVX2 (32 byte) → AVX2 host (Intel 12-14th gen, AMD Zen 3 and earlier)
+Vector128 (16 byte/iter) → Apple Silicon NEON, WASM SIMD, legacy SSE2
+scalar (1 byte/iter) → no-SIMD fall-back
+```
-- **.NET 10**: incremental SIMD improvements, but the changes that affect us are mostly inside the BCL (`Encoding.UTF8.GetString` internal SIMD widening). Our custom decoder bypasses the BCL — we don't benefit unless we hand-roll the same SIMD ourselves with .NET 9 intrinsics, which already work today. Multi-targeting `net9.0;net10.0` adds CI/test overhead with marginal payoff. **Skip.**
-- **.NET 11**: PR #120628 (Vector512/Vector256 SIMD for UTF-8 utilities) was closed without merge but signals upcoming work in this area. Future iterations are expected to expose `Avx512Vbmi`-style mask-compress intrinsics that today require unsafe / Vector128-emulation paths. Target this once the framework lands.
+JIT/AOT path-selection via `[Intrinsic]` `IsSupported` static booleans — non-supported tiers constant-folded to dead code per host. Cascading tail handlers: a higher tier's tail (< 64 byte AVX-512 → < 32 byte Vector256 → < 16 byte Vector128 → scalar) is processed by the next-lower tier on the same iteration. No regression on any host.
-### Implementation outline (when triggered)
+**Implementation status:**
-- Multi-target `net9.0;net11.0` on `AyCode.Core.csproj`
-- `#if NET11_0_OR_GREATER` block in `DecodeUtf8` selects an AVX-512-aware path: process 64-byte blocks via `Vector512` + `vpcompressb` for byte-stream extraction, fall back to the .NET 9 scalar+Vector256 path on non-AVX-512 hardware (`Avx512Vbmi.IsSupported` runtime check)
-- Reuse the .NET 9 scalar path for short strings (<64 bytes) — SIMD setup cost dominates
-- New benchmark cells comparing .NET 9 vs .NET 11 builds on the same hardware
+| Phase | Method | AVX-512BW | Vector256 | Vector128 | scalar |
+|-------|--------|-----------|-----------|-----------|--------|
+| 1 | `CountUtf8Chars` (decode 1st pass) | ✅ done | ✅ existing | ✅ done | ✅ existing |
+| 2 | `EncodeUtf8SinglePass` Phase 1 (ASCII narrow) | ✅ done | ✅ existing | ✅ done | ✅ existing |
+| 3a | `DecodeUtf8SinglePass` multi-byte transcoder (Vector512) | ⏳ TODO | bail-out only | bail-out only | ✅ existing |
+| 3b | `DecodeUtf8SinglePass` multi-byte transcoder (Vector256) | — | ⏳ TODO | bail-out only | ✅ existing |
+| 3c | `DecodeUtf8SinglePass` multi-byte transcoder (Vector128) | — | — | ⏳ TODO | ✅ existing |
+
+**Phase 3 is the remaining gap — UTF-8 multi-byte decode on every host class**. ASCII path is already fast across all SIMD tiers (Vector256 + Vector128 prefix widen + `Encoding.Latin1.GetString` BCL fast path). The gap is on **multi-byte UTF-8 content** — Hungarian / Cyrillic / Greek (2-byte) and CJK BMP (3-byte) sequences — where the SIMD prefix bails out on the first non-ASCII byte and falls back to scalar bit-extract. The Repeated benchmark cell (Hungarian content) is the canonical witness; with all-Hungarian content (current bench data), Small / Repeated Deser cells trail MemPack by 6-14%.
+
+**Why all 3 SIMD tiers (not just AVX-512BW)** — public NuGet package goal: i18n payloads must be fast on every supported host (cloud server, desktop, mobile, Blazor WASM), not only AVX-512-capable cloud servers. The saját scalar multi-byte branch is the bottleneck on **all** non-ASCII content regardless of host class. The BCL `Encoding.UTF8` falls back to a similar scalar path on multi-byte content (with virtual dispatch + EncoderFallback overhead), so even where the BCL has its own SIMD 2-byte handler (.NET 9 PR #92580), our trust-input scalar wins on net — but a saját SIMD multi-byte path would dominate on every host.
+
+**Phase 3 approach — in-house multi-byte transcoder, three SIMD widths.** Single algorithm template (classify-mask-compress-widen pipeline) ported across Vector512 / Vector256 / Vector128 register widths. Algorithm designed and written in-house — no third-party port, no NuGet dependency:
+
+- **Phase 3a — `DecodeUtf8SinglePass` Vector512 (AVX-512BW)**: 64-byte block fetch → classify each byte's UTF-8 sequence position via mask compares → byte-compression for length-resolution → widen to UTF-16 in two `Vector256` lanes → store. ~3-5× speedup vs current scalar multi-byte branch on Hungarian / CJK content. Activates on AVX-512 hosts (cloud server, Intel 11th gen, AMD Zen 4+).
+- **Phase 3b — `DecodeUtf8SinglePass` Vector256 (AVX2)**: same algorithm at 32-byte block. Smaller register space → fewer codepoints per iter, but ASCII bail-out gone → multi-byte content is now SIMD-handled. ~2-3× speedup. Activates on AVX2 hosts (Intel 12-14th gen, AMD Zen 3 and earlier).
+- **Phase 3c — `DecodeUtf8SinglePass` Vector128 (NEON / SSE / WASM SIMD)**: same algorithm at 16-byte block. ~1.5-2× speedup. Activates on Apple Silicon / WASM / legacy x86 — covering the i18n production case for mobile (MAUI iOS / Android) and Blazor WASM.
+
+The cascading tail-handler hierarchy (existing in Phase 1+2) carries over: AVX-512 → Vector256 → Vector128 → scalar tail. Each tier hands off the < N-byte tail to the next-lower tier.
+
+**No .NET 11 / multi-targeting needed.** `Avx512BW`, `Vector256`, `Vector128` intrinsics all available in .NET 9 (and .NET 8). Implementation lands on the current `net9.0` target.
+
+**Hardware reach (2026).** Per Wikipedia "CPUs with AVX-512":
+
+- ✅ **Intel server**: Skylake-X (2017), Cascade Lake-X, Ice Lake-SP, **Sapphire Rapids (2023+)**, Emerald Rapids, Granite Rapids — near-universal in cloud (Azure, AWS, GCP)
+- ✅ **Intel client 11th gen**: Tiger Lake (mobile, 2020), Rocket Lake (desktop, 2021), Ice Lake (mobile) — pre-Alder Lake era still supports AVX-512
+- ❌ **Intel client 12-14th gen**: Alder Lake / Raptor Lake / Meteor Lake / Core Ultra — AVX-512 disabled at firmware level (E-core blocking) → falls back to Vector256
+- ✅ **AMD Zen 4+**: Ryzen 7000 (2022), Ryzen 9000 (2024), EPYC Genoa (2022), EPYC Turin (2024)
+- ❌ **AMD pre-Zen 4**: Zen 3 and earlier → falls back to Vector256
+- ❌ **Apple Silicon / ARM**: NEON only → uses Vector128 (16 byte/iter)
+- ❌ **Blazor WASM**: only 128-bit SIMD per WASM SIMD spec → uses Vector128 (16 byte/iter)
+
+The Vector128 path is the **WASM and Apple Silicon target** — without it both platforms fell back to scalar (1 byte/iter). With Phase 1+2 landed, WASM and Apple Silicon now run the UTF-8 hot path at 16 byte/iter (16× scalar speedup on the count + ASCII narrow operations).
+
+### Phase 3 implementation outline
+
+- Insert SIMD multi-byte branches at `DecodeUtf8SinglePass` entry, **before** the existing ASCII-prefix bail-out loops:
+ ```
+ if (Avx512BW.IsSupported && byteCount >= 64) { Vector512MultiByteDecode(...) }
+ if (Vector256.IsHardwareAccelerated && len-i >= 32) { Vector256MultiByteDecode(...) }
+ if (Vector128.IsHardwareAccelerated && len-i >= 16) { Vector128MultiByteDecode(...) }
+ // existing scalar tail
+ ```
+- Single algorithm template — classify-mask-compress-widen pipeline:
+ 1. Block load (Vector512 / Vector256 / Vector128)
+ 2. Classify each byte's UTF-8 sequence position via mask compares (start vs continuation, 1/2/3/4-byte sequence width)
+ 3. Compute output char count via popcount on start-byte mask + extra-char mask for 4-byte sequences
+ 4. Byte-compression for leader/continuation extraction (mask-driven `PermuteVar` / `Shuffle`)
+ 5. Combine leader + continuations into codepoints (shift + OR)
+ 6. Widen codepoints to UTF-16 chars (handle surrogate pairs for 4-byte sequences)
+ 7. Store output, advance src/dst pointers
+- Block-boundary edge case: incomplete multi-byte sequence at block end → carry to next iter or hand off to lower tier / scalar tail
+- Trust-input semantics maintained — no validate-pass instructions (reader input is valid UTF-8 by writer contract)
+- `Avx512BW.X64.IsSupported` (64-bit-only intrinsics) checked separately if any code path requires the X64 sub-feature
+
+### Why P2
+
+- "i18n production deploy" perf gap on **every** host class — the public NuGet package contract requires fast multi-byte UTF-8 across cloud server, desktop, mobile, and Blazor WASM
+- No NuGet dependency, no third-party code, no wire-format change, additive — pure CPU optimization
+- Phase 1+2 delivered cross-tier ASCII / count SIMD coverage; Phase 3 closes the multi-byte CPU gap on **all** SIMD-capable hosts (not just AVX-512)
+- Single algorithm template ported across 3 register widths — code volume manageable
### Acceptance
-- `dotnet test` passes on both target frameworks
-- Benchmark on AVX-512 hardware (Sapphire Rapids / Zen 4+) shows ≥1.5x non-ASCII deser speedup vs .NET 9 build for strings ≥256 bytes
-- Short-string perf (≤64 bytes) within ±5% of .NET 9 build (no regression from multi-target setup)
-- `BINARY_FEATURES.md` documents the SIMD path selection logic
+- Repeated Deser ratio ≤ 0.7 vs MemPack on AVX-512 hosts (Phase 3a)
+- Repeated Deser ratio ≤ 0.8 vs MemPack on AVX2 hosts (Phase 3b)
+- Repeated Deser ratio ≤ 0.85 vs MemPack on Apple Silicon / WASM (Phase 3c)
+- Repeated Ser ratio ≤ 0.85 across all host classes
+- Round-trip tests pass on all UTF-8 content classes (ASCII / 2-byte / 3-byte BMP / 4-byte surrogate-pair)
+- `BINARY_FEATURES.md` documents the SIMD path selection across all four tiers
### Trigger
-- Wait for .NET 11 release (or RC)
-- Re-evaluate once `dotnet/runtime` UTF-8 SIMD utilities re-land (post-PR #120628 follow-up)
-- Skip entirely if .NET 11 BCL `Encoding.UTF8.GetString` becomes fast enough that hybrid (≥256 bytes → BCL, <256 → custom) wins without hand-rolled SIMD
+- Each SIMD width validated on a representative host before merge:
+ - Phase 3a: AVX-512 host (developer's local AMD Zen 4+ desktop, Intel 11th gen, or server-class machine)
+ - Phase 3b: AVX2 host (any modern x86 desktop / laptop without AVX-512)
+ - Phase 3c: Apple Silicon (macOS / iOS / Mac Catalyst) AND Blazor WASM browser runtime
+- Local `dotnet test` covers correctness; per-tier benchmarks measure the multi-byte speedup
+- Phase 1+2 (AVX-512BW + Vector128 in `CountUtf8Chars` + `EncodeUtf8SinglePass` Phase 1) **landed 2026-05-05** — covered by existing round-trip tests, no regression on non-AVX-512 hosts (validated on AVX2-host bench)
## ACCORE-BIN-T-S5L8: Sentinel-length encoding for strings (wire-size optimization, both modes)
**Priority:** P3 · **Type:** Wire-format optimization · **Related:** `AcBinarySerializer.WriteString`, `AcBinaryDeserializer.ReadValue` string dispatch
diff --git a/AyCode.Services/AyCode.Services.csproj b/AyCode.Services/AyCode.Services.csproj
index 4473311..58999c4 100644
--- a/AyCode.Services/AyCode.Services.csproj
+++ b/AyCode.Services/AyCode.Services.csproj
@@ -6,7 +6,12 @@
+
diff --git a/AyCode.Services/Mvc/AcBinaryInputFormatter.cs b/AyCode.Services/Mvc/AcBinaryInputFormatter.cs
index 1daf837..661d5f0 100644
--- a/AyCode.Services/Mvc/AcBinaryInputFormatter.cs
+++ b/AyCode.Services/Mvc/AcBinaryInputFormatter.cs
@@ -1,3 +1,10 @@
+// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
+// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
+// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
+// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
+// AyCode.Services.csproj is also disabled in tandem.
+
+/*
using System.IO.Pipelines;
using AyCode.Core.Serializers.Binaries;
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -74,3 +81,4 @@ public class AcBinaryInputFormatter : InputFormatter
protected override bool CanReadType(Type type) => true;
}
+*/
diff --git a/AyCode.Services/Mvc/AcBinaryMvcBuilderExtensions.cs b/AyCode.Services/Mvc/AcBinaryMvcBuilderExtensions.cs
index afed90c..427efe6 100644
--- a/AyCode.Services/Mvc/AcBinaryMvcBuilderExtensions.cs
+++ b/AyCode.Services/Mvc/AcBinaryMvcBuilderExtensions.cs
@@ -1,3 +1,10 @@
+// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
+// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
+// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
+// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
+// AyCode.Services.csproj is also disabled in tandem.
+
+/*
using AyCode.Core.Serializers.Binaries;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@@ -56,3 +63,4 @@ public static class AcBinaryMvcBuilderExtensions
return builder;
}
}
+*/
diff --git a/AyCode.Services/Mvc/AcBinaryOutputFormatter.cs b/AyCode.Services/Mvc/AcBinaryOutputFormatter.cs
index 00652f9..96c56da 100644
--- a/AyCode.Services/Mvc/AcBinaryOutputFormatter.cs
+++ b/AyCode.Services/Mvc/AcBinaryOutputFormatter.cs
@@ -1,3 +1,10 @@
+// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
+// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
+// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
+// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
+// AyCode.Services.csproj is also disabled in tandem.
+
+/*
using System.IO.Pipelines;
using AyCode.Core.Serializers.Binaries;
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -41,3 +48,4 @@ public class AcBinaryOutputFormatter : OutputFormatter
protected override bool CanWriteType(Type? type) => true;
}
+*/
diff --git a/AyCode.Services/Mvc/README.md b/AyCode.Services/Mvc/README.md
index 630a011..fcf8967 100644
--- a/AyCode.Services/Mvc/README.md
+++ b/AyCode.Services/Mvc/README.md
@@ -1,5 +1,7 @@
# Mvc
+> ⚠️ **TEMPORARILY DISABLED** — formatter sources block-commented (`/* ... */`) and `Microsoft.AspNetCore.App` FrameworkReference removed from `AyCode.Services.csproj` (downstream net10.0 Hybrid client conflict on `Microsoft.AspNetCore.Mvc` namespace). Re-enable when split into a separate NuGet package / solution. Description below documents the intended state.
+
ASP.NET Core MVC formatters for AcBinary wire format. Standard MVC pipeline integration — works with both controller-based MVC and Minimal API.
> **Topic docs:** `AyCode.Services/docs/MVC/README.md` — media type, request/response flow, configuration, ProblemDetails error model.
diff --git a/AyCode.Services/README.md b/AyCode.Services/README.md
index 18715de..7d0c14a 100644
--- a/AyCode.Services/README.md
+++ b/AyCode.Services/README.md
@@ -12,7 +12,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
|---|---|
| `SIGNALR/README.md` | Client-side SignalR transport (tags, wire protocol, req/resp flow) |
| `SIGNALR_BINARY_PROTOCOL/README.md` | Binary-over-SignalR wire format, chunked framing |
-| `MVC/README.md` | ASP.NET Core MVC formatters for AcBinary (`application/vnd.acbinary`) |
+| `MVC/README.md` | ASP.NET Core MVC formatters for AcBinary (`application/vnd.acbinary`) — **temporarily disabled** |
| `LOGGING/README.md` | Remote log writers (HTTP, browser console, SignalR) |
## Folder Structure
@@ -21,7 +21,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
|---|---|
| [`Loggers/`](Loggers/README.md) | Remote log writers: HTTP, browser console (JS interop), SignalR |
| [`Logins/`](Logins/README.md) | Base and client-side login service implementations |
-| [`Mvc/`](Mvc/README.md) | ASP.NET Core MVC `InputFormatter` / `OutputFormatter` for AcBinary wire format |
+| [`Mvc/`](Mvc/README.md) | ASP.NET Core MVC `InputFormatter` / `OutputFormatter` for AcBinary wire format — **temporarily disabled** (block-commented, FrameworkReference removed) |
| [`SignalRs/`](SignalRs/README.md) | Custom binary SignalR protocol, client base, message tagging, serialization |
## Dependencies
@@ -32,7 +32,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
| `AyCode.Entities` | Entity base classes |
| `AyCode.Interfaces` | Service contracts |
| `AyCode.Models` | DTOs |
-| `Microsoft.AspNetCore.App` (FrameworkReference) | ASP.NET Core MVC formatter base classes (.NET 9+) |
+| ~~`Microsoft.AspNetCore.App` (FrameworkReference)~~ | _temporarily disabled — see `Mvc/README.md`_ |
| `Microsoft.AspNetCore.SignalR.Client` | SignalR client |
| `Microsoft.AspNetCore.SignalR.Common` | `IHubProtocol` for custom binary protocol |
| `Microsoft.AspNetCore.Authentication.JwtBearer` | JWT authentication |
diff --git a/AyCode.Services/docs/MVC/README.md b/AyCode.Services/docs/MVC/README.md
index 5a879be..1e8afde 100644
--- a/AyCode.Services/docs/MVC/README.md
+++ b/AyCode.Services/docs/MVC/README.md
@@ -1,5 +1,7 @@
# MVC — AcBinary formatters
+> ⚠️ **TEMPORARILY DISABLED** — formatter sources block-commented (`/* ... */`) and `Microsoft.AspNetCore.App` FrameworkReference removed from `AyCode.Services.csproj` (downstream net10.0 Hybrid client conflict on `Microsoft.AspNetCore.Mvc` namespace). Re-enable when split into a separate NuGet package / solution. Description below documents the intended state.
+
ASP.NET Core MVC `InputFormatter` / `OutputFormatter` pair for the AcBinary wire format. Works in controller-based MVC and Minimal API on .NET 9+. The wire payload is the raw `byte[]` produced by `AcBinarySerializer.Serialize(value, opts)` — bit-compatible with the single-shot byte[] API; no MVC-specific envelope.
> **Code:** `AyCode.Services/Mvc/` (`AcBinaryInputFormatter`, `AcBinaryOutputFormatter`, `AcBinaryMvcBuilderExtensions`)
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 5ae915b..c76e109 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -94,7 +94,7 @@ AyCode.Services ← AyCode.Services.Server
- **AyCode.Database** — EF Core with generic DAL pattern. Session for reads, Transaction for writes. DAL pooling via `PooledDal`.
### Service Layer
-- **AyCode.Services** — Client-side: SignalR client, login service, loggers, ASP.NET Core MVC formatters for AcBinary (`Mvc/`).
+- **AyCode.Services** — Client-side: SignalR client, login service, loggers, ASP.NET Core MVC formatters for AcBinary (`Mvc/` — **temporarily disabled**, see `AyCode.Services/Mvc/README.md`).
- **AyCode.Services.Server** — Server-side: SignalR hub with custom binary protocol, email (SendGrid), JWT auth.
- **AyCode.Models.Server/DynamicMethods** — Reflection-based tag→method dispatch used by the SignalR hub.