diff --git a/src/GameCult.Mesh/CultMesh.cs b/src/GameCult.Mesh/CultMesh.cs index e7436db2..0339158f 100644 --- a/src/GameCult.Mesh/CultMesh.cs +++ b/src/GameCult.Mesh/CultMesh.cs @@ -3471,13 +3471,15 @@ namespace GameCult.Mesh CultDocumentRegistry.Shared.GetRequired()); } + // The one schema-alias matcher (docs/cultnet-selection-cut.md, D4): every runtime-side + // schema identity decision, wire or local, goes through CultNetSchemaAliasMatching. private static bool IsSameCultDocumentSchema( Type documentType, CultDocumentDescriptor descriptor, string? schemaId = null) { if (!string.IsNullOrWhiteSpace(schemaId) && - string.Equals(schemaId, descriptor.SchemaId, StringComparison.Ordinal)) + CultNetSchemaAliasMatching.Matches(schemaId, descriptor)) { return true; } @@ -3492,8 +3494,7 @@ namespace GameCult.Mesh return false; } - return string.Equals(storedDescriptor.SchemaName, descriptor.SchemaName, StringComparison.Ordinal) && - string.Equals(storedDescriptor.SchemaVersion, descriptor.SchemaVersion, StringComparison.Ordinal); + return CultNetSchemaAliasMatching.Matches(storedDescriptor.SchemaVersion, descriptor); } private static TDocument ConvertUntypedDocument(object document) diff --git a/src/GameCult.Mesh/CultMeshSnapshots.cs b/src/GameCult.Mesh/CultMeshSnapshots.cs index d40eef03..9db8727f 100644 --- a/src/GameCult.Mesh/CultMeshSnapshots.cs +++ b/src/GameCult.Mesh/CultMeshSnapshots.cs @@ -1002,8 +1002,8 @@ namespace GameCult.Mesh binding != null && typeof(TDocument).IsAssignableFrom(binding.DocumentType); var canDeserializeAsSchemaAlias = - string.Equals(record.SchemaId, descriptor.SchemaId, StringComparison.Ordinal) || - (binding != null && IsSameCultDocumentSchema(binding.DocumentType, descriptor)) || + CultNetSchemaAliasMatching.Matches(record.SchemaId, descriptor) || + (binding != null && CultNetSchemaAliasMatching.Matches(binding.SchemaId, descriptor)) || RawSnapshotPayloadMatchesSchema(record.Payload, descriptor); if (!canDeserializeWithBinding && !canDeserializeAsSchemaAlias) continue; @@ -1030,20 +1030,17 @@ namespace GameCult.Mesh return documents; } + // Decode-time fallback for a wire schemaId this runtime does not recognize by any alias + // (e.g. a foreign/runtime-generated id): trust the payload's own embedded schemaVersion + // instead. This is a decode concern, not a selector engine — the server's answer is not + // re-filtered here, and schema identity itself still goes through the one alias matcher. private static bool RawSnapshotPayloadMatchesSchema( byte[] payload, CultDocumentDescriptor descriptor) { var schemaVersion = TryReadSchemaVersion(payload); - if (string.IsNullOrWhiteSpace(schemaVersion)) - return false; - - if (string.Equals(schemaVersion, descriptor.SchemaVersion, StringComparison.Ordinal)) - return true; - - var schemaName = InferSchemaName(schemaVersion!); - return !string.IsNullOrWhiteSpace(schemaName) && - string.Equals(schemaName, descriptor.SchemaName, StringComparison.Ordinal); + return !string.IsNullOrWhiteSpace(schemaVersion) && + CultNetSchemaAliasMatching.Matches(schemaVersion!, descriptor); } private static string? TryReadSchemaVersion(byte[] payload) @@ -1074,17 +1071,5 @@ namespace GameCult.Mesh return null; } - - private static string? InferSchemaName(string schemaVersion) - { - var marker = schemaVersion.LastIndexOf(".v", StringComparison.Ordinal); - if (marker <= 0 || marker + 2 >= schemaVersion.Length) - return null; - - var version = schemaVersion.Substring(marker + 2); - return version.All(char.IsDigit) - ? schemaVersion.Substring(0, marker) - : null; - } } }