using System; using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using GameCult.Caching; using GameCult.Caching.MessagePack; using GameCult.Networking; using MessagePack; using NUnit.Framework; using R3; namespace GameCult.Mesh.Tests; public sealed class CultMeshStreamingTests { [Test] public async Task TypedOperationHandle_CarriesAuthorityAndRouteContext() { var handle = new CultMeshOperationHandle( "aetheria.entity.pilot.move", (request, context) => Task.FromResult(new CultMeshOperationReceipt( "aetheria.entity.pilot.move", accepted: request.EntityId == 7 && context.Claims.Count == 1 && context.Claims[0].Role == "pilot-control" && context.RouteHint.Kind == CultMeshLocalityKind.InProcess, context.RouteHint))); var receipt = await handle.InvokeAsync( new MeshMoveRequest(7, 1, -1), CultMeshOperationContext .ForRuntime("unity-raven") .WithClaim(new CultMeshAuthorityClaim("pilot-control", shardId: "zone:local-rts")) .WithRoute(new CultMeshRouteHint(CultMeshLocalityKind.InProcess, "co-located daemon")) .WithIdempotencyKey("move-7-001")); receipt.Accepted.Should().BeTrue(); receipt.Route.Kind.Should().Be(CultMeshLocalityKind.InProcess); var diagnostic = CultMesh.DescribeOperationHandle(handle); diagnostic.OperationId.Should().Be("aetheria.entity.pilot.move"); } [Test] public void ContextBuilders_MakeTypedHandleCallsReadLikeVerseOperations() { var operationContext = CultMesh.OperationContextFor("unity-raven") .Claim("pilot-control", shardId: "zone:local-rts", leaseId: "lease:raven") .Route(CultMeshLocalityKind.SharedMemory, "co-located daemon") .Idempotency("move-7-001") .Build(); operationContext.RuntimeId.Should().Be("unity-raven"); operationContext.Claims.Should().ContainSingle(); operationContext.Claims[0].Role.Should().Be("pilot-control"); operationContext.Claims[0].ShardId.Should().Be("zone:local-rts"); operationContext.Claims[0].LeaseId.Should().Be("lease:raven"); operationContext.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); operationContext.IdempotencyKey.Should().Be("move-7-001"); var queryContext = CultMesh.QueryContextFor("browser-starfire") .Route(CultMeshLocalityKind.Wasm, "browser-local projection") .Build(); queryContext.RuntimeId.Should().Be("browser-starfire"); queryContext.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Wasm); queryContext.RouteHint.Description.Should().Be("browser-local projection"); } [Test] public async Task VerseContext_LetsGeneratedDomainSugarUseSharedTypedContexts() { var verse = await CultMesh.ConnectVerseAsync( "starbridge", "unity-raven", new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located Verse"), new[] { new CultMeshAuthorityClaim("pilot-control", shardId: "zone:raven", leaseId: "lease:raven") }); var commandVerse = verse.WithRoute(new CultMeshRouteHint(CultMeshLocalityKind.Network, "remote command route")); var aetheria = commandVerse.Use(context => new MeshAetheriaDomain(context)); var receipt = await aetheria.Entity(7).Pilot.MoveAsync(new MeshVec2(1, 0), "move:raven:1"); var viewport = await aetheria.Zone("zone:raven").Objects.VisibleWithinAsync(new MeshViewportRequest(-16, 16)); verse.VerseId.Should().Be("starbridge"); verse.RuntimeId.Should().Be("unity-raven"); verse.QueryContext().RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); commandVerse.OperationContext("move:raven:2").RouteHint.Kind.Should().Be(CultMeshLocalityKind.Network); commandVerse.OperationContext("move:raven:2").IdempotencyKey.Should().Be("move:raven:2"); receipt.OperationId.Should().Be("aetheria.entity.pilot.move"); receipt.Accepted.Should().BeTrue(); receipt.Route.Kind.Should().Be(CultMeshLocalityKind.Network); viewport.Should().Equal("unity-raven:zone:raven:-16:16:Network"); } [Test] public async Task QuerySurfaceAndStatePointer_ExposeTypedReactiveState() { var query = new CultMeshQuerySurface( "aetheria.zone.objects.visible", (request, context) => Task.FromResult(new[] { $"{context.RuntimeId}:{request.MinX}:{request.MaxX}" })); var queryResult = await query.ExecuteAsync( new MeshViewportRequest(-8, 8), CultMeshQueryContext.ForRuntime("browser-starfire")); queryResult.Should().Equal("browser-starfire:-8:8"); var queryDiagnostic = CultMesh.DescribeQuerySurface(query); queryDiagnostic.QueryId.Should().Be("aetheria.zone.objects.visible"); queryDiagnostic.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Automatic); queryDiagnostic.Sources.Should().BeEmpty(); var subject = new Subject(); var pointer = CultMesh.StatePointer( "aetheria.selection.current", () => Task.FromResult("entity:station:0"), () => subject, new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located selection cache"), new[] { CultMesh.ProjectionSource( "daemon:aetheria.selection.current.v1", schemaId: "gamecult.aetheria.selection.v1") }); string observed = null!; using var subscription = pointer.Watch().Subscribe(value => observed = value); (await pointer.ResolveAsync()).Should().Be("entity:station:0"); subject.OnNext("entity:pawn:7"); observed.Should().Be("entity:pawn:7"); var pointerDiagnostic = CultMesh.DescribeStatePointer(pointer); pointerDiagnostic.PointerId.Should().Be("aetheria.selection.current"); pointerDiagnostic.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); pointerDiagnostic.Sources.Should().ContainSingle().Which.SchemaId.Should().Be("gamecult.aetheria.selection.v1"); } [Test] public async Task StatePointer_CanBindToVerseContextForUiAndTools() { var subject = new Subject(); var pointer = CultMesh.StatePointer( "aetheria.daemon.frame.latest", context => Task.FromResult($"{context.RuntimeId}:{context.RouteHint.Kind}"), context => subject.Select(value => $"{context.RuntimeId}:{value}:{context.RouteHint.Kind}"), new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located daemon frame"), new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1") }); var verse = CultMesh.Verse( "aetheria.local", "bifrost-tool", new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "tool bridge")); var bound = verse.BindStatePointer(pointer); (await bound.ResolveAsync()).Should().Be("bifrost-tool:Ipc"); string observed = null!; using var subscription = bound.Watch().Subscribe(value => observed = value); subject.OnNext("frame:12"); bound.PointerId.Should().Be("aetheria.daemon.frame.latest"); bound.Sources.Should().ContainSingle().Which.SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); observed.Should().Be("bifrost-tool:frame:12:Ipc"); } [Test] public async Task MutableStatePointer_HoistsReadWatchReplaceDocumentHandles() { var subject = new Subject(); var stored = "frame:0"; var observedContexts = new List(); var pointer = CultMesh.MutableStatePointer( "aetheria.daemon.frame.latest", context => { observedContexts.Add($"read:{context.RuntimeId}:{context.RouteHint.Kind}"); return Task.FromResult(stored); }, context => subject.Select(value => $"{context.RuntimeId}:{value}:{context.RouteHint.Kind}"), (context, value) => { observedContexts.Add($"replace:{context.RuntimeId}:{context.RouteHint.Kind}"); stored = value; subject.OnNext(value); return Task.CompletedTask; }, new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located daemon frame"), new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1") }); var verse = CultMesh.Verse( "aetheria.local", "unity-raven", new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "tool bridge")); var bound = verse.BindMutableStatePointer(pointer); string observed = null!; using var subscription = bound.Watch().Subscribe(value => observed = value); (await bound.ReadAsync()).Should().Be("frame:0"); await bound.ReplaceAsync("frame:1"); stored.Should().Be("frame:1"); observed.Should().Be("unity-raven:frame:1:Ipc"); observedContexts.Should().Equal("read:unity-raven:Ipc", "replace:unity-raven:Ipc"); var diagnostic = CultMesh.DescribeStatePointer(pointer); diagnostic.PointerId.Should().Be("aetheria.daemon.frame.latest"); diagnostic.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); var binding = CultMesh.StateBinding("frame", pointer); binding.PointerId.Should().Be("aetheria.daemon.frame.latest"); binding.SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); } [Test] public void StateBindingDescriptor_BindsUiPropsToTypedStatePointers() { var pointer = CultMesh.StatePointer( "aetheria.selection.current", () => Task.FromResult("entity:station:0"), () => new Subject(), new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located selection cache"), new[] { CultMesh.ProjectionSource( "daemon:aetheria.selection.current.v1", schemaId: "gamecult.aetheria.selection.v1") }); var binding = CultMesh.StateBinding("value", pointer); var explicitBinding = CultMesh.StateBinding( "label", "aetheria.selection.label", sourceId: "daemon:aetheria.selection.label.v1", schemaId: "gamecult.aetheria.selection_label.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "tool bridge")); binding.TargetProp.Should().Be("value"); binding.PointerId.Should().Be("aetheria.selection.current"); binding.SourceId.Should().Be("daemon:aetheria.selection.current.v1"); binding.SchemaId.Should().Be("gamecult.aetheria.selection.v1"); binding.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); binding.RouteHint.Description.Should().Be("co-located selection cache"); explicitBinding.TargetProp.Should().Be("label"); explicitBinding.PointerId.Should().Be("aetheria.selection.label"); explicitBinding.SourceId.Should().Be("daemon:aetheria.selection.label.v1"); explicitBinding.SchemaId.Should().Be("gamecult.aetheria.selection_label.v1"); explicitBinding.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Ipc); } [Test] public void StateBindingRecord_FlattensAndRehydratesPointerBindingFields() { var binding = CultMesh.StateBinding( "status", "aetheria.current.status", sourceId: "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located frame slab")); var record = CultMesh.StateBindingRecord(binding); record.TargetProp.Should().Be("status"); record.PointerId.Should().Be("aetheria.current.status"); record.SourceId.Should().Be("daemon:aetheria.frame.latest.v1"); record.SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); record.RouteKind.Should().Be(nameof(CultMeshLocalityKind.SharedMemory)); record.RouteDescription.Should().Be("co-located frame slab"); var fromTypescript = CultMesh.StateBindingRecord( "value", "aetheria.selection.current", "daemon:aetheria.selection.current.v1", "gamecult.aetheria.selection.v1", "shared-memory", "browser-adjacent cache").ToBinding(); fromTypescript.TargetProp.Should().Be("value"); fromTypescript.PointerId.Should().Be("aetheria.selection.current"); fromTypescript.SourceId.Should().Be("daemon:aetheria.selection.current.v1"); fromTypescript.SchemaId.Should().Be("gamecult.aetheria.selection.v1"); fromTypescript.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); fromTypescript.RouteHint.Description.Should().Be("browser-adjacent cache"); } [Test] public void StateBindingRecord_PreservesEditableFieldIdentityAndAuthority() { var binding = CultMesh.StateBinding( "value", "ghostlight.session-zero.composer", sourceId: "session-zero:draft-7", schemaId: "session_zero.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "Ghostlight provider"), bindingName: "composer.message", documentId: "session-zero:draft-7", fieldPath: "channels.private.messages.pending", valueKind: "string", accessMode: "local-draft", authority: "SessionZeroKernel", writeCommand: "edit.value"); var record = CultMesh.StateBindingRecord(binding); record.BindingName.Should().Be("composer.message"); record.FieldPath.Should().Be("channels.private.messages.pending"); record.AccessMode.Should().Be("local-draft"); var restored = record.ToBinding(); restored.BindingName.Should().Be(binding.BindingName); restored.DocumentId.Should().Be(binding.DocumentId); restored.FieldPath.Should().Be(binding.FieldPath); restored.ValueKind.Should().Be(binding.ValueKind); restored.AccessMode.Should().Be(binding.AccessMode); restored.Authority.Should().Be(binding.Authority); restored.WriteCommand.Should().Be(binding.WriteCommand); } [Test] public void OperationBindingDescriptor_BindsUiCommandsToTypedOperations() { var operation = new CultMeshOperationHandle( "aetheria.entity.pilot.move", (_request, _context) => Task.FromResult(new CultMeshOperationReceipt("aetheria.entity.pilot.move", true))); var binding = CultMesh.OperationBinding( operation, label: "Move", schemaId: "gamecult.aetheria.pilot_move.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "remote Verse peer")); var explicitBinding = CultMesh.OperationBinding( "aetheria.surface.refresh", label: "Refresh"); binding.OperationId.Should().Be("aetheria.entity.pilot.move"); binding.Label.Should().Be("Move"); binding.SchemaId.Should().Be("gamecult.aetheria.pilot_move.v1"); binding.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Network); binding.RouteHint.Description.Should().Be("remote Verse peer"); explicitBinding.OperationId.Should().Be("aetheria.surface.refresh"); explicitBinding.Label.Should().Be("Refresh"); explicitBinding.SchemaId.Should().Be(""); explicitBinding.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Automatic); } [Test] public void OperationBindingRecord_FlattensAndRehydratesSurfaceCommandFields() { var binding = CultMesh.OperationBinding( "aetheria.entity.pilot.move", label: "Move", schemaId: "gamecult.aetheria.pilot_move.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "remote Verse peer")); var record = CultMesh.OperationBindingRecord(binding); record.OperationId.Should().Be("aetheria.entity.pilot.move"); record.Label.Should().Be("Move"); record.SchemaId.Should().Be("gamecult.aetheria.pilot_move.v1"); record.RouteKind.Should().Be(nameof(CultMeshLocalityKind.Network)); record.RouteDescription.Should().Be("remote Verse peer"); var fromTypescript = CultMesh.OperationBindingRecord( "aetheria.surface.refresh", "Refresh", "gamecult.aetheria.refresh.v1", "in-process", "embedded UI runtime").ToBinding(); fromTypescript.OperationId.Should().Be("aetheria.surface.refresh"); fromTypescript.Label.Should().Be("Refresh"); fromTypescript.SchemaId.Should().Be("gamecult.aetheria.refresh.v1"); fromTypescript.RouteHint.Kind.Should().Be(CultMeshLocalityKind.InProcess); fromTypescript.RouteHint.Description.Should().Be("embedded UI runtime"); } [Test] public void OperationInvocationDescriptor_CarriesTypedOperationIdentityThroughUiRequests() { var binding = CultMesh.OperationBinding( "aetheria.entity.pilot.move", label: "Move", schemaId: "gamecult.aetheria.pilot_move.v1", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "local Verse node")); var invocation = CultMesh.OperationInvocation(binding, idempotencyKey: "move:42"); var explicitInvocation = CultMesh.OperationInvocation( "aetheria.surface.refresh", routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "remote Verse peer")); invocation.OperationId.Should().Be("aetheria.entity.pilot.move"); invocation.SchemaId.Should().Be("gamecult.aetheria.pilot_move.v1"); invocation.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Ipc); invocation.RouteHint.Description.Should().Be("local Verse node"); invocation.IdempotencyKey.Should().Be("move:42"); explicitInvocation.OperationId.Should().Be("aetheria.surface.refresh"); explicitInvocation.SchemaId.Should().Be(""); explicitInvocation.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Network); explicitInvocation.IdempotencyKey.Should().BeNull(); } [Test] public void OperationPayload_ReadsCommonSurfaceScalarFields() { var payload = CultMesh.OperationPayload( ("value", "42.5"), ("tierIndex", "3"), ("enabled", "on"), ("name", "Starfire")); var updated = payload.With("enabled", "false"); payload.GetString("name").Should().Be("Starfire"); payload.GetString("missing", "fallback").Should().Be("fallback"); payload.GetInt32("tierIndex", -1).Should().Be(3); payload.GetInt32("missing", -1).Should().Be(-1); payload.GetDouble("value", -1).Should().Be(42.5); payload.GetBoolean("enabled").Should().BeTrue(); updated.GetBoolean("enabled", true).Should().BeFalse(); updated.GetString("name").Should().Be("Starfire"); payload.GetBoolean("missing", true).Should().BeTrue(); payload.Should().ContainKey("value"); payload.Count.Should().Be(4); } [Test] public void OperationInvocationRecord_FlattensAndRehydratesTransportFields() { var invocation = CultMesh.OperationInvocation( "aetheria.entity.pilot.move", "gamecult.aetheria.pilot_move.v1", new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "local Verse node"), "move:42"); var record = CultMesh.OperationInvocationRecord(invocation); var restored = record.ToInvocation( fallbackOperationId: "fallback.operation", fallbackSchemaId: "fallback.schema", fallbackRouteHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "fallback route")); var fallback = CultMesh.OperationInvocationRecord( operationId: "", schemaId: "", routeKind: "not-a-route", routeDescription: "", idempotencyKey: "") .ToInvocation( fallbackOperationId: "fallback.operation", fallbackSchemaId: "fallback.schema", fallbackRouteHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "fallback route"), fallbackIdempotencyKey: "fallback-key"); var payloadFields = CultMesh.OperationPayload(("value", "Raven")).ToDictionary(); record.OperationId.Should().Be("aetheria.entity.pilot.move"); record.SchemaId.Should().Be("gamecult.aetheria.pilot_move.v1"); record.RouteKind.Should().Be("Ipc"); record.RouteDescription.Should().Be("local Verse node"); record.IdempotencyKey.Should().Be("move:42"); restored.OperationId.Should().Be("aetheria.entity.pilot.move"); restored.SchemaId.Should().Be("gamecult.aetheria.pilot_move.v1"); restored.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Ipc); restored.IdempotencyKey.Should().Be("move:42"); fallback.OperationId.Should().Be("fallback.operation"); fallback.SchemaId.Should().Be("fallback.schema"); fallback.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Network); fallback.RouteHint.Description.Should().Be("fallback route"); fallback.IdempotencyKey.Should().Be("fallback-key"); payloadFields["value"].Should().Be("Raven"); } [Test] public void OperationInvocationRecord_ReadsLegacyStringAndWritesStructuredRecord() { var legacyPayload = MessagePackSerializer.Serialize( "aetheria.entity.pilot.move", CultDocumentMessagePackSerialization.Options); var restored = MessagePackSerializer.Deserialize( legacyPayload, CultDocumentMessagePackSerialization.Options); var currentPayload = MessagePackSerializer.Serialize( restored, CultDocumentMessagePackSerialization.Options); var reader = new MessagePackReader(currentPayload); restored.OperationId.Should().Be("aetheria.entity.pilot.move"); restored.SchemaId.Should().BeEmpty(); restored.RouteKind.Should().BeEmpty(); restored.IdempotencyKey.Should().BeEmpty(); reader.ReadArrayHeader().Should().Be(5); reader.ReadString().Should().Be("aetheria.entity.pilot.move"); } [Test] public void RouteRecord_ReadsLegacyStringAndWritesStructuredRecord() { var legacyPayload = MessagePackSerializer.Serialize( "SharedMemory", CultDocumentMessagePackSerialization.Options); var restored = MessagePackSerializer.Deserialize( legacyPayload, CultDocumentMessagePackSerialization.Options); var currentPayload = MessagePackSerializer.Serialize( restored, CultDocumentMessagePackSerialization.Options); var reader = new MessagePackReader(currentPayload); restored.Kind.Should().Be("SharedMemory"); restored.Description.Should().BeEmpty(); reader.ReadArrayHeader().Should().Be(2); reader.ReadString().Should().Be("SharedMemory"); } [Test] public void RouteRecord_FlattensAndParsesCrossRuntimeRouteKinds() { var record = CultMesh.RouteRecord(new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located slab")); var restored = record.ToRoute(); var fromTypescript = CultMesh.RouteRecord("in-process", "").ToRoute( new CultMeshRouteHint(CultMeshLocalityKind.Network, "fallback route")); var fallback = CultMesh.RouteRecord("not-real", "").ToRoute( new CultMeshRouteHint(CultMeshLocalityKind.Ipc, "fallback route")); record.Kind.Should().Be("SharedMemory"); record.Description.Should().Be("co-located slab"); restored.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); restored.Description.Should().Be("co-located slab"); fromTypescript.Kind.Should().Be(CultMeshLocalityKind.InProcess); fromTypescript.Description.Should().Be("fallback route"); fallback.Kind.Should().Be(CultMeshLocalityKind.Ipc); fallback.Description.Should().Be("fallback route"); } [Test] public void StateRefResolver_ComposesNamedSurfaceResolution() { var route = new CultMeshRouteHint(CultMeshLocalityKind.InProcess, "embedded renderer"); var daemon = CultMesh.StateRefResolver( "aetheria.daemon.refs", (stateRef, context) => stateRef == "aetheria.daemon/frame/id" ? $"{context.RuntimeId}:{context.RouteHint.Kind}:42" : "", new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1") }, route); var itemStats = CultMesh.StateRefResolver( "aetheria.item_stats.refs", stateRef => stateRef == "aetheria.item_stats/laser/damage" ? "12.5" : ""); var resolver = daemon.Or(itemStats); resolver.Resolve( "aetheria.daemon/frame/id", CultMesh.QueryContextFor("unity-raven") .Route(CultMeshLocalityKind.Network, "remote peer") .Build()) .Should() .Be("unity-raven:Network:42"); resolver.Resolve("aetheria.item_stats/laser/damage").Should().Be("12.5"); resolver.TryResolve("missing", out var missing).Should().BeFalse(); missing.Should().Be(""); resolver.AsFunc()("aetheria.item_stats/laser/damage").Should().Be("12.5"); var diagnostic = CultMesh.DescribeStateRefResolver(resolver); diagnostic.ResolverId.Should().Be("aetheria.daemon.refs|aetheria.item_stats.refs"); diagnostic.RouteHint.Kind.Should().Be(CultMeshLocalityKind.InProcess); diagnostic.Sources.Should().ContainSingle(); diagnostic.Sources[0].SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); } [Test] public async Task ProjectionRecipe_NamesSourcesAndCanBecomeQuerySurface() { var recipe = CultMesh.ProjectionRecipe( "aetheria.zone.objects.visible", new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1", description: "latest daemon frame"), CultMesh.ProjectionSource( "daemon:aetheria.authority.policy.v1", schemaId: "gamecult.aetheria.authority_policy.v1") }, (request, context) => Task.FromResult(new[] { $"{context.RuntimeId}:{request.MinX}:{request.MaxX}:{context.RouteHint.Kind}" }), new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located frame slab")); recipe.Sources.Should().HaveCount(2); recipe.Sources[0].SourceId.Should().Be("daemon:aetheria.frame.latest.v1"); recipe.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); var recipeDiagnostic = CultMesh.DescribeProjectionRecipe(recipe); recipeDiagnostic.ProjectionId.Should().Be("aetheria.zone.objects.visible"); recipeDiagnostic.RouteHint.Description.Should().Be("co-located frame slab"); recipeDiagnostic.Sources.Should().HaveCount(2); recipeDiagnostic.Sources[0].SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); recipeDiagnostic.Sources.Should().NotBeSameAs(recipe.Sources); var result = await recipe.ProjectAsync( new MeshViewportRequest(-16, 16), CultMesh.QueryContextFor("browser-starfire") .Route(CultMeshLocalityKind.Wasm, "browser-local projection") .Build()); result.Should().Equal("browser-starfire:-16:16:Wasm"); var query = recipe.AsQuerySurface(); var queryResult = await query.ExecuteAsync( new MeshViewportRequest(-4, 4), "unity-raven"); query.QueryId.Should().Be(recipe.ProjectionId); query.Sources.Should().HaveCount(2); query.Sources[0].SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); query.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); queryResult.Should().Equal("unity-raven:-4:4:SharedMemory"); var queryDiagnostic = CultMesh.DescribeQuerySurface(query); queryDiagnostic.QueryId.Should().Be("aetheria.zone.objects.visible"); queryDiagnostic.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); queryDiagnostic.Sources.Should().HaveCount(2); queryDiagnostic.Sources.Should().NotBeSameAs(query.Sources); } [Test] public async Task LiveFeed_DescribesAndWatchesCoherentClientSnapshots() { var subject = new Subject(); var feed = CultMesh.LiveFeed( "aetheria.rts.viewport.feed", (request, context) => Task.FromResult( $"{context.RuntimeId}:{request.MinX}:{request.MaxX}:{context.RouteHint.Kind}"), (_request, context) => subject.Select(value => $"{context.RuntimeId}:{value}:{context.RouteHint.Kind}"), new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1"), CultMesh.ProjectionSource( "daemon:aetheria.health.latest.v1", schemaId: "gamecult.aetheria.daemon_health.v1") }, new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located RTS cache")); feed.FeedId.Should().Be("aetheria.rts.viewport.feed"); feed.Sources.Should().HaveCount(2); feed.Sources[0].SchemaId.Should().Be("gamecult.aetheria.daemon_frame.v1"); feed.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); var diagnostic = CultMesh.DescribeLiveFeed(feed); diagnostic.FeedId.Should().Be("aetheria.rts.viewport.feed"); diagnostic.RouteHint.Description.Should().Be("co-located RTS cache"); diagnostic.Sources.Should().HaveCount(2); diagnostic.Sources[1].SourceId.Should().Be("daemon:aetheria.health.latest.v1"); diagnostic.Sources.Should().NotBeSameAs(feed.Sources); var inherited = await feed.SnapshotAsync( new MeshViewportRequest(-32, 32), "browser-starfire"); inherited.Should().Be("browser-starfire:-32:32:SharedMemory"); var explicitRoute = await feed.SnapshotAsync( new MeshViewportRequest(-32, 32), CultMesh.QueryContextFor("unity-raven") .Route(CultMeshLocalityKind.InProcess, "embedded Verse") .Build()); explicitRoute.Should().Be("unity-raven:-32:32:InProcess"); string observed = null!; using var subscription = feed .Watch(new MeshViewportRequest(-1, 1), CultMeshQueryContext.ForRuntime("browser-starfire")) .Subscribe(value => observed = value); subject.OnNext("frame:42"); observed.Should().Be("browser-starfire:frame:42:SharedMemory"); } [Test] public async Task DocumentHandle_ExposesTypedSnapshotsAndWatches() { var subject = new Subject(); var route = new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located document slab"); var sources = new[] { CultMesh.ProjectionSource( "daemon:mesh.note.current", schemaId: "tests.mesh_note.v1") }; var context = CultMesh.Verse("starbridge", "unity-pilot", route).Context; var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "primary", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.current", context, _ => Task.FromResult(current), _ => subject, sources, route); handle.DocumentId.Should().Be("mesh.note.current"); handle.DocumentType.Should().Be(typeof(MeshNoteDocument)); handle.SchemaName.Should().Be("tests.mesh_note"); handle.SchemaVersion.Should().Be("tests.mesh_note.v1"); handle.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); handle.Sources.Should().ContainSingle().Which.SourceId.Should().Be("daemon:mesh.note.current"); var snapshot = await handle.LatestAsync(); snapshot.Text.Should().Be("primary"); handle.Latest().Text.Should().Be("primary"); MeshNoteDocument observed = null!; using var subscription = handle.Watch(value => observed = value); subject.OnNext(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "reactive", Revision = 2 }); observed.Text.Should().Be("reactive"); observed.Revision.Should().Be(2); } [Test] public async Task DocumentHandle_Field_ProjectsTypedReactiveStateWithoutRepublishingSurfaceTrees() { var subject = new Subject(); var route = new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located document slab"); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.current", CultMesh.Verse("starbridge", "unity-pilot", route), _ => Task.FromResult(current), _ => subject, routeHint: route); var text = handle.Field(value => value.Text); text.PointerId.Should().Be("mesh.note.current#Text"); text.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); (await text.ResolveAsync()).Should().Be("initial"); string observed = null!; using var subscription = text.Watch().Subscribe(value => observed = value); subject.OnNext(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "reactive", Revision = 2 }); observed.Should().Be("reactive"); } [Test] public async Task DocumentCatalog_IndexesHandlesByTypeAndSchema() { var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "catalog-primary", Revision = 3 }; var handle = CultMesh.Document( "mesh.note.current", CultMesh.Verse("starbridge", "unity-pilot"), _ => Task.FromResult(current), _ => new Subject()); var catalog = CultMesh.Documents(handle); catalog.Documents.Should().ContainSingle().Which.Should().BeSameAs(handle); catalog.TryGetDocument(out var exact).Should().BeTrue(); exact.Should().BeSameAs(handle); catalog.TryGetDocumentBySchema("tests.mesh_note.v1", out var byVersion).Should().BeTrue(); byVersion.Should().BeSameAs(handle); catalog.DocumentBySchema("tests.mesh_note").Should().BeSameAs(handle); catalog.DocumentBySchema(CultDocumentRegistry.Shared.GetRequired().SchemaId) .Should() .BeSameAs(handle); catalog.TryGetDocument(out _).Should().BeFalse(); var snapshot = await catalog.LatestAsync(); snapshot.Text.Should().Be("catalog-primary"); snapshot.Revision.Should().Be(3); catalog.Latest().Text.Should().Be("catalog-primary"); } [Test] public async Task DocumentHelpers_ProjectLatestSnapshotsFromMultipleHandles() { var primary = CultMesh.Document( "mesh.note.primary", CultMesh.Verse("starbridge", "unity-pilot"), _ => Task.FromResult(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "alpha", Revision = 3 }), _ => new Subject()); var secondary = CultMesh.Document( "mesh.note.secondary", CultMesh.Verse("starbridge", "unity-pilot"), _ => Task.FromResult(new MeshOtherDocument { Schema = "tests.mesh_other.v1", Text = "beta" }), _ => new Subject()); var projected = await CultMesh.LatestAsync( primary, secondary, (first, second) => $"{first.Text}:{first.Revision}:{second.Text}"); projected.Should().Be("alpha:3:beta"); } [Test] public async Task DocumentHandle_ReadsWatchesAndReplacesCultCacheRecords() { var cache = new CultCache(); var key = new CultRecordKey("mesh-note:cache"); await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cache-initial", Revision = 1 }, new CultRecordHandle(key)); var handle = CultMesh.Document( cache, key, CultMesh.Verse("starbridge", "unity-pilot")); handle.CanReplace.Should().BeTrue(); handle.CanSubmitPrediction.Should().BeFalse(); handle.DocumentId.Should().Be(key.Value); handle.Sources.Should().ContainSingle().Which.SchemaId.Should().Be(handle.SchemaId); var snapshot = await handle.LatestAsync(); snapshot.Text.Should().Be("cache-initial"); MeshNoteDocument observed = null!; using var subscription = handle.Watch(value => observed = value); await handle.ReplaceAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cache-replaced", Revision = 2 }); cache.Get(key)!.Text.Should().Be("cache-replaced"); observed.Text.Should().Be("cache-replaced"); observed.Revision.Should().Be(2); var catalog = CultMesh.Documents(handle); catalog.CanReplace().Should().BeTrue(); await catalog.ReplaceAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cache-catalog-replaced", Revision = 3 }); cache.Get(key)!.Text.Should().Be("cache-catalog-replaced"); Func act = () => handle.SubmitPredictionAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cache-predicted", Revision = 4 }); await act.Should().ThrowAsync(); } // A database serves only what its shards own. A schema has one CLR type (retire-schema-alias), so there is no second type // that could reach a record around the shard that owns its schema. [TestCase("tests.mesh_note.v1", true)] [TestCase("tests.unrelated.v1", false)] public async Task DocumentHandle_ReadsARecordOnlyWhenAShardOwnsItsSchema(string ownedSchemaId, bool served) { var cache = new CultCache(); var key = new CultRecordKey("note:one"); await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cached" }, new CultRecordHandle(key)); var database = new CultNetDatabase(cache, new CultNetDatabaseOptions { RuntimeId = "reader", Shards = new[] { new CultNetShardDescriptor("notes", "reader", epoch: 1, isPrimary: true, schemaIds: new[] { ownedSchemaId }) } }); var handle = CultMesh.Document(database, key, CultMesh.Verse("starbridge", "reader")); if (served) { (await handle.LatestAsync()).Text.Should().Be("cached"); } else { await FluentActions.Awaiting(() => handle.LatestAsync()).Should().ThrowAsync(); } } [Test] public async Task DocumentHandle_SubmitsPredictionsThroughCultNetDatabase() { var cache = new CultCache(); var schemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId; var database = new CultNetDatabase(cache, new CultNetDatabaseOptions { RuntimeId = "pilot-a", Shards = new[] { new CultNetShardDescriptor( "pilot-inputs", "server", epoch: 1, isPrimary: false, schemaIds: new[] { schemaId }, keyPrefix: "input:") }, ClientAuthorityScopes = new[] { new CultNetClientAuthorityScope( "pilot-a", schemaIds: new[] { schemaId }, keyPrefix: "input:pilot-a") } }); var key = new CultRecordKey("input:pilot-a:thermal"); var handle = CultMesh.Document( database, key, CultMesh.Verse("starbridge", "pilot-a")); handle.CanReplace.Should().BeFalse(); handle.CanSubmitPrediction.Should().BeTrue(); var catalog = CultMesh.Documents(handle); catalog.CanSubmitPrediction().Should().BeTrue(); var predictionWriter = catalog.PredictionWriter(); predictionWriter.Kind.Should().Be(CultMeshDocumentWriteKind.Prediction); MeshNoteDocument observed = null!; using var subscription = handle.Watch(value => observed = value); await predictionWriter.WriteAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "predicted-thermal", Revision = 5 }); cache.Get(key)!.Text.Should().Be("predicted-thermal"); observed.Text.Should().Be("predicted-thermal"); observed.Revision.Should().Be(5); (await handle.LatestAsync()).Text.Should().Be("predicted-thermal"); var updated = await handle.PredictionWriter().UpdateAsync(value => new MeshNoteDocument { Schema = value.Schema, Text = "updated-as-prediction", Revision = value.Revision + 1 }); updated.Text.Should().Be("updated-as-prediction"); cache.Get(key)!.Revision.Should().Be(6); } [Test] public async Task ReactiveDocument_CoalescesLocalEditsIntoOnePrediction() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); var catalog = CultMesh.Documents(handle); using var reactive = await catalog.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Snapshot.Text.Should().Be("initial"); reactive.Update(document => { document.Text = "first-local-edit"; document.Revision++; }); reactive.Update(document => { document.Text = "second-local-edit"; document.Revision++; }); predictions.Should().BeEmpty(); await reactive.FlushAsync(); predictions.Should().ContainSingle(); predictions[0].Text.Should().Be("second-local-edit"); predictions[0].Revision.Should().Be(3); current.Text.Should().Be("second-local-edit"); } [Test] public async Task ReactiveDocument_TracksCanonicalSnapshotDuringDirtyPrediction() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.reconciliation", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.AuthoritativeWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "local-prediction"; document.Revision = 2; }); subject.OnNext(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "canonical-correction", Revision = 7 }); reactive.Snapshot.Text.Should().Be("local-prediction"); reactive.Reconciliation.Should().NotBeNull(); reactive.Reconciliation!.Version.Should().Be(1); reactive.Reconciliation!.Canonical.Text.Should().Be("canonical-correction"); reactive.Reconciliation.Predicted.Text.Should().Be("local-prediction"); reactive.Reconciliation.Delta.Should().ContainKey(nameof(MeshNoteDocument.Text)); reactive.Reconciliation.Delta[nameof(MeshNoteDocument.Text)].Should().Be("local-prediction"); reactive.Reconciliation.Delta.Should().ContainKey(nameof(MeshNoteDocument.Revision)); reactive.Reconciliation.Delta[nameof(MeshNoteDocument.Revision)].Should().Be(-5.0); } [Test] public async Task ReactiveDocument_AutoFlushesOnceAfterDebounceWindow() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.debounce", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMilliseconds(25) }); reactive.Update(document => { document.Text = "coalesced-one"; document.Revision++; }); reactive.Update(document => { document.Text = "coalesced-two"; document.Revision++; }); await WaitForAsync(() => predictions.Count == 1); predictions[0].Text.Should().Be("coalesced-two"); predictions[0].Revision.Should().Be(3); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_CoalescesExplicitMemberUpdates() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.direct-members", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMilliseconds(25) }); reactive.Update(document => document.Text = "explicit-member-one"); reactive.Update(document => { document.Text = "explicit-member-two"; document.Revision = 2; }); await WaitForAsync(() => predictions.Count == 1); predictions[0].Text.Should().Be("explicit-member-two"); predictions[0].Revision.Should().Be(2); current.Text.Should().Be("explicit-member-two"); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_QueuesExplicitEditsWhileFlushIsInFlight() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var firstPredictionStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var releaseFirstPrediction = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.in-flight", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, async value => { predictions.Add(value); if (predictions.Count == 1) { firstPredictionStarted.SetResult(); await releaseFirstPrediction.Task.ConfigureAwait(false); } current = value; subject.OnNext(value); }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "first-in-flight"; document.Revision = 2; }); var flushTask = reactive.FlushAsync(); await firstPredictionStarted.Task; reactive.Update(document => { document.Text = "second-after-flight-started"; document.Revision = 3; }); releaseFirstPrediction.SetResult(); await flushTask; predictions.Should().HaveCount(2); predictions.Select(document => document.Text).Should().Equal( "first-in-flight", "second-after-flight-started"); current.Text.Should().Be("second-after-flight-started"); current.Revision.Should().Be(3); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_UsesReplacementWhenPredictionIsUnavailable() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var replacements = new List(); var handle = CultMesh.Document( "mesh.note.reactive.replace", CultMesh.Verse("starbridge", "daemon"), _ => Task.FromResult(current), _ => subject, value => { replacements.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.AuthoritativeWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "authoritative-replace"; document.Revision = 10; }); await reactive.FlushAsync(); replacements.Should().ContainSingle(); replacements[0].Text.Should().Be("authoritative-replace"); current.Revision.Should().Be(10); } [Test] public void ReactiveDocument_ReadOnlyHandleRejectsWriterSelection() { var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.reactive.readonly", CultMesh.Verse("starbridge", "observer"), _ => Task.FromResult(current), _ => new Subject()); Action authoritative = () => handle.AuthoritativeWriter(); Action prediction = () => handle.PredictionWriter(); authoritative.Should().Throw(); prediction.Should().Throw(); } [Test] public async Task ObservedDocument_WatchesWithoutWriteCapabilityOrIdleTimer() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.observed.readonly", CultMesh.Verse("starbridge", "observer"), _ => Task.FromResult(current), _ => subject); using var observed = await handle.ObserveAsync(); observed.Current.Text.Should().Be("initial"); typeof(CultMeshObservedDocument) .GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) .Should() .NotContain(field => field.FieldType == typeof(System.Threading.Timer)); subject.OnNext(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "canonical-update", Revision = 2 }); observed.Current.Text.Should().Be("canonical-update"); observed.Current.Revision.Should().Be(2); } // The older of two racing writes is parked in an earlier observer until the newer one has been applied. [Test] public async Task WatchRecordIgnoresStaleSequence() { var cache = new CultCache(); var key = new CultRecordKey("mesh-note:stale-sequence"); var handle = new CultRecordHandle(key); MeshNoteDocument Note(string text, int revision) => new() { Schema = "tests.mesh_note.v1", Text = text, Revision = revision }; await cache.UpsertAsync(Note("initial", 1), handle); using var release = new System.Threading.ManualResetEventSlim(); using var parked = new System.Threading.ManualResetEventSlim(); using var park = cache.WatchRecord(key).Subscribe(change => { if (change.Document?.Revision != 2) return; parked.Set(); release.Wait(TimeSpan.FromSeconds(30)); }); using var observed = await CultMesh.Document(cache, key).ObserveAsync(); var older = Task.Run(() => cache.UpsertAsync(Note("older", 2), handle)); parked.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue(); var newer = Task.Run(() => cache.UpsertAsync(Note("newer", 3), handle)); var newerFinished = newer.Wait(TimeSpan.FromSeconds(10)); release.Set(); newerFinished.Should().BeTrue("a writer waited on another thread's delivery"); older.Wait(TimeSpan.FromSeconds(10)).Should().BeTrue(); observed.Current.Text.Should().Be("newer"); cache.Dispose(); } // A batch parks in an observer of another key before publishing its (older) target-key change; the mirror is // created after a newer write landed, then the straggler is delivered. [TestCase(false)] [TestCase(true)] public async Task ObserveAfterNewerSnapshotIgnoresStraggler(bool reactive) { var cache = new CultCache(); var key = new CultRecordKey("mesh-note:straggler"); var other = new CultRecordKey("mesh-note:straggler-other"); await cache.UpsertAsync(Note("initial", 1), new CultRecordHandle(key)); using var release = new ManualResetEventSlim(); using var parked = new ManualResetEventSlim(); using var park = cache.WatchRecord(other).Subscribe(_ => { parked.Set(); release.Wait(TimeSpan.FromSeconds(30)); }); var batch = Task.Run(() => cache.Commit(stage => { stage.Upsert(Note("other", 1), new CultRecordHandle(other)); stage.Upsert(Note("older", 2), new CultRecordHandle(key)); })); parked.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue(); await cache.UpsertAsync(Note("newer", 3), new CultRecordHandle(key)); var (mirror, text, _) = await Mirror(cache, key, reactive); release.Set(); batch.Wait(TimeSpan.FromSeconds(10)).Should().BeTrue(); text().Should().Be("newer"); mirror.Dispose(); cache.Dispose(); } // The mirror's own delivery of the newer write is parked too, so only the refresh saw it before the straggler. [TestCase(false)] [TestCase(true)] public async Task RefreshIgnoresStragglerOlderThanSnapshot(bool reactive) { var cache = new CultCache(); var key = new CultRecordKey("mesh-note:refresh-straggler"); var other = new CultRecordKey("mesh-note:refresh-straggler-other"); await cache.UpsertAsync(Note("initial", 1), new CultRecordHandle(key)); using var releaseOlder = new ManualResetEventSlim(); using var olderParked = new ManualResetEventSlim(); using var releaseNewer = new ManualResetEventSlim(); using var newerParked = new ManualResetEventSlim(); using var parkOlder = cache.WatchRecord(other).Subscribe(_ => { olderParked.Set(); releaseOlder.Wait(TimeSpan.FromSeconds(30)); }); using var parkNewer = cache.WatchRecord(key).Subscribe(change => { if (change.Document?.Text != "newer") return; newerParked.Set(); releaseNewer.Wait(TimeSpan.FromSeconds(30)); }); var (mirror, text, refresh) = await Mirror(cache, key, reactive); var batch = Task.Run(() => cache.Commit(stage => { stage.Upsert(Note("other", 1), new CultRecordHandle(other)); stage.Upsert(Note("older", 2), new CultRecordHandle(key)); })); olderParked.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue(); var newer = Task.Run(() => cache.UpsertAsync(Note("newer", 3), new CultRecordHandle(key))); newerParked.Wait(TimeSpan.FromSeconds(5)).Should().BeTrue(); await refresh(); releaseOlder.Set(); batch.Wait(TimeSpan.FromSeconds(10)).Should().BeTrue(); text().Should().Be("newer"); releaseNewer.Set(); newer.Wait(TimeSpan.FromSeconds(10)).Should().BeTrue(); text().Should().Be("newer"); mirror.Dispose(); cache.Dispose(); } private static MeshNoteDocument Note(string text, int revision) => new() { Schema = "tests.mesh_note.v1", Text = text, Revision = revision }; private static async Task<(IDisposable Mirror, Func Text, Func Refresh)> Mirror( CultCache cache, CultRecordKey key, bool reactive) { var handle = CultMesh.Document(cache, key); if (reactive) { var mirror = await handle.AuthoritativeWriter().ReactiveAsync(); return (mirror, () => mirror.Snapshot.Text, () => mirror.RefreshAsync()); } var observed = await handle.ObserveAsync(); return (observed, () => observed.Current.Text, () => observed.RefreshAsync()); } [Test] public async Task ReactiveDocument_DisposeSuppressesScheduledFlush() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.dispose", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMilliseconds(25) }); reactive.Update(document => document.Text = "disposed-before-flush"); reactive.Dispose(); await Task.Delay(100); predictions.Should().BeEmpty(); } [Test] public async Task ReactiveDocument_CanAdoptCanonicalSnapshotWhileDirty() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.reactive.adopt-canonical", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.AuthoritativeWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1), ReplaceDirtyCurrentOnCanonicalSnapshot = true }); reactive.Update(document => document.Text = "local-prediction"); subject.OnNext(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "canonical-wins", Revision = 9 }); reactive.Snapshot.Text.Should().Be("canonical-wins"); reactive.Snapshot.Revision.Should().Be(9); reactive.Reconciliation.Should().BeNull(); } [Test] public async Task ReactiveDocument_RefreshClearsDirtyLocalState() { var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var replacements = new List(); var handle = CultMesh.Document( "mesh.note.reactive.refresh", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => new Subject(), value => { replacements.Add(value); current = value; return Task.CompletedTask; }); using var reactive = await handle.AuthoritativeWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => document.Text = "local-dirty"); current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "fresh-canonical", Revision = 5 }; await reactive.RefreshAsync(); await reactive.FlushAsync(); reactive.Snapshot.Text.Should().Be("fresh-canonical"); reactive.IsDirty.Should().BeFalse(); replacements.Should().BeEmpty(); } [Test] public async Task ReactiveDocument_SyncsAcrossRuntimeHandles() { var cache = new CultCache(); var schemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId; var database = new CultNetDatabase(cache, new CultNetDatabaseOptions { RuntimeId = "pilot-a", Shards = new[] { new CultNetShardDescriptor( "pilot-inputs", "server", epoch: 1, isPrimary: false, schemaIds: new[] { schemaId }, keyPrefix: "input:") }, ClientAuthorityScopes = new[] { new CultNetClientAuthorityScope( "pilot-a", schemaIds: new[] { schemaId }, keyPrefix: "input:pilot-a") } }); var key = new CultRecordKey("input:pilot-a:reactive"); await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }, new CultRecordHandle(key)); var pilotHandle = CultMesh.Document( database, key, CultMesh.Verse("starbridge", "pilot-a")); var commanderHandle = CultMesh.Document( database, key, CultMesh.Verse("starbridge", "commander-rts")); var observedByCommander = new List(); using var commanderSubscription = commanderHandle.Watch(observedByCommander.Add); using var pilotReactive = await CultMesh .Documents(pilotHandle) .PredictionWriter() .ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); pilotReactive.Update(document => { document.Text = "pilot-predicted"; document.Revision = 2; }); await pilotReactive.FlushAsync(); await WaitForAsync(() => observedByCommander.Any(document => document.Text == "pilot-predicted")); commanderHandle.Context.RuntimeId.Should().Be("commander-rts"); observedByCommander.Last().Revision.Should().Be(2); (await commanderHandle.LatestAsync()).Text.Should().Be("pilot-predicted"); } [Test] public async Task ReactiveDocument_TwoRuntimeManagedDocumentsSyncThroughDatabase() { var cache = new CultCache(); var schemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId; var database = new CultNetDatabase(cache, new CultNetDatabaseOptions { RuntimeId = "pilot-a", Shards = new[] { new CultNetShardDescriptor( "pilot-inputs", "server", epoch: 1, isPrimary: false, schemaIds: new[] { schemaId }, keyPrefix: "input:") }, ClientAuthorityScopes = new[] { new CultNetClientAuthorityScope( "pilot-a", schemaIds: new[] { schemaId }, keyPrefix: "input:pilot-a") } }); var key = new CultRecordKey("input:pilot-a:co-op"); await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }, new CultRecordHandle(key)); var pilotHandle = CultMesh.Document( database, key, CultMesh.Verse("starbridge", "pilot-a")); var commanderHandle = CultMesh.Document( database, key, CultMesh.Verse("starbridge", "commander-rts")); MeshNoteDocument commanderSnapshot = null!; using var commanderSubscription = commanderHandle.Watch(value => commanderSnapshot = value); using var pilotReactive = await pilotHandle .PredictionWriter() .ReactiveAsync(new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); pilotReactive.Update(document => { document.Text = "pilot-first-touch"; document.Revision++; }); pilotReactive.Update(document => { document.Text = "pilot-coalesced-order"; document.Revision++; }); await pilotReactive.FlushAsync(); await WaitForAsync(() => commanderSnapshot?.Text == "pilot-coalesced-order"); pilotReactive.IsDirty.Should().BeFalse(); commanderHandle.Context.RuntimeId.Should().Be("commander-rts"); commanderSnapshot.Revision.Should().Be(3); cache.Get(key)!.Text.Should().Be("pilot-coalesced-order"); } [Test] public async Task NodeDocument_OpensExplicitPredictionWriter() { var schemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId; using var temp = new TemporaryDirectory(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(temp.Path, "node-reactive-alias.ccmp"), new CultMeshNodeOptions { StartServer = false, DatabaseOptions = new CultNetDatabaseOptions { RuntimeId = "pilot-a", Shards = new[] { new CultNetShardDescriptor( "pilot-inputs", "server", epoch: 1, isPrimary: false, schemaIds: new[] { schemaId }, keyPrefix: "input:") }, ClientAuthorityScopes = new[] { new CultNetClientAuthorityScope( "pilot-a", schemaIds: new[] { schemaId }, keyPrefix: "input:pilot-a") } } }); var key = new CultRecordKey("input:pilot-a:node-reactive"); await node.Cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "node-initial", Revision = 1 }, new CultRecordHandle(key)); using var pilotReactive = await node .Document(key, CultMesh.Verse("starbridge", "pilot-a")) .PredictionWriter() .ReactiveAsync(new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); var commanderHandle = node.Document( key.Value, CultMesh.Verse("starbridge", "commander-rts")); var observedByCommander = new List(); using var commanderSubscription = commanderHandle.Watch(observedByCommander.Add); pilotReactive.Update(document => { document.Text = "node-one-call-prediction"; document.Revision = 2; }); await pilotReactive.FlushAsync(); await WaitForAsync(() => observedByCommander.Any(document => document.Text == "node-one-call-prediction")); pilotReactive.Document.DocumentId.Should().Be(key.Value); pilotReactive.Document.Context.RuntimeId.Should().Be("pilot-a"); node.Cache.Get(key)!.Text.Should().Be("node-one-call-prediction"); (await commanderHandle.LatestAsync()).Revision.Should().Be(2); } [Test] public async Task SyncedSnapshotEndpointObservesCanonicalCorrectionAcrossRuntimeBoundary() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:remote-correction"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }, new CultRecordHandle(key)); var endpoint = "cultnet://remote-correction.test:3080"; var surface = CultMesh.SnapshotEndpoint( endpoint, new CultMeshSnapshotEndpointOptions { Context = CultMesh.Verse("starbridge", "unity-pilot").Context, DocumentRegistry = registry, PollInterval = TimeSpan.FromMilliseconds(10), Request = new CultMeshSnapshotRequestOptions { CreateClient = () => new MeshSnapshotSchemaClient(request => registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request)) } }); using var temp = new TemporaryDirectory(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(temp.Path, "pilot-runtime.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var handle = surface.SyncTo(node).Document(key.Value); MeshNoteDocument observed = null!; using var subscription = handle.Watch(value => observed = value); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "remote-authority-correction", Revision = 7 }, new CultRecordHandle(key)); await WaitForAsync(() => observed?.Text == "remote-authority-correction"); observed.Revision.Should().Be(7); node.Cache.Get(key)!.Text.Should().Be("remote-authority-correction"); } [Test] public async Task ReactiveDocument_FreshRuntimeReconstructsFromSyncedSnapshotEndpoint() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:runtime-reconnect"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "before-crash", Revision = 1 }, new CultRecordHandle(key)); var endpoint = "cultnet://runtime-reconnect.test:3081"; var surface = CultMesh.SnapshotEndpoint( endpoint, new CultMeshSnapshotEndpointOptions { Context = CultMesh.Verse("starbridge", "unity-reconnected").Context, DocumentRegistry = registry, Request = new CultMeshSnapshotRequestOptions { CreateClient = () => new MeshSnapshotSchemaClient(request => registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request)) } }); using var temp = new TemporaryDirectory(); using (var crashedRuntime = await CultMesh.CreateNodeAsync( Path.Combine(temp.Path, "crashed-runtime.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } })) { var stale = await surface.SyncTo(crashedRuntime).Document(key.Value).LatestAsync(); stale.Text.Should().Be("before-crash"); } await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "after-reboot-authority", Revision = 4 }, new CultRecordHandle(key)); using var freshRuntime = await CultMesh.CreateNodeAsync( Path.Combine(temp.Path, "fresh-runtime.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var reconstructed = await surface .SyncTo(freshRuntime) .Documents(CultMesh.SnapshotDocument(key.Value)) .LatestAsync(); reconstructed.Text.Should().Be("after-reboot-authority"); reconstructed.Revision.Should().Be(4); freshRuntime.Cache.Get(key)!.Text.Should().Be("after-reboot-authority"); } [Test] public async Task ReactiveDocument_ReadOnlySnapshotEndpointRefreshRecoversAfterRejectedLocalEdit() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:readonly-refresh"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }, new CultRecordHandle(key)); var endpoint = "cultnet://readonly-refresh.test:3082"; var surface = CultMesh.SnapshotEndpoint( endpoint, new CultMeshSnapshotEndpointOptions { Context = CultMesh.Verse("starbridge", "observer").Context, DocumentRegistry = registry, Request = new CultMeshSnapshotRequestOptions { CreateClient = () => new MeshSnapshotSchemaClient(request => registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request)) } }); using var temp = new TemporaryDirectory(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(temp.Path, "observer-runtime.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var readOnlyHandle = surface .SyncTo(node) .Document(key.Value); readOnlyHandle.CanReplace.Should().BeFalse(); readOnlyHandle.CanSubmitPrediction.Should().BeFalse(); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "fresh-authority", Revision = 5 }, new CultRecordHandle(key)); var refreshed = await readOnlyHandle.LatestAsync(); refreshed.Text.Should().Be("fresh-authority"); node.Cache.Get(key)!.Revision.Should().Be(5); } [Test] public async Task ReactiveDocument_SnapshotMutationCannotPublish() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.mark-dirty", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); var escapedSnapshot = reactive.Snapshot; escapedSnapshot.Text = "must-not-publish"; escapedSnapshot.Revision = 12; await reactive.FlushAsync(); predictions.Should().BeEmpty(); reactive.Snapshot.Text.Should().Be("initial"); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_IdleMirrorDoesNotPublishUntilExplicitUpdate() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.direct-member-auto", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMilliseconds(10) }); await Task.Delay(75); predictions.Should().BeEmpty(); reactive.Update(document => { document.Text = "explicit-member-edit"; document.Revision = 3; }); await WaitForAsync(() => predictions.Count == 1); predictions[0].Text.Should().Be("explicit-member-edit"); predictions[0].Revision.Should().Be(3); reactive.IsDirty.Should().BeFalse(); } [TestCase(1)] [TestCase(100)] [TestCase(1000)] public async Task ReactiveDocument_SchedulingScalesWithChangedDocumentsOnly(int documentCount) { var clock = new BlockingCountClock(); var reactiveDocuments = new List>(documentCount); try { for (var index = 0; index < documentCount; index++) { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = $"initial-{index}", Revision = 1 }; var handle = CultMesh.Document( $"mesh.note.reactive.scale.{index}", CultMesh.Verse("scale", "test"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }); reactiveDocuments.Add(await handle.AuthoritativeWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { Clock = clock, FlushDelay = TimeSpan.FromSeconds(1) })); } clock.DelayCount.Should().Be(0, "idle reactive documents must not schedule background work"); var changedDocumentCount = Math.Max(1, documentCount / 100); for (var index = 0; index < changedDocumentCount; index++) { reactiveDocuments[index].Update(document => { document.Text = $"changed-{index}"; document.Revision++; }); } clock.DelayCount.Should().Be( changedDocumentCount, "flush scheduling must be proportional to explicitly changed documents"); reactiveDocuments.Count(document => document.IsDirty).Should().Be(changedDocumentCount); } finally { foreach (var reactiveDocument in reactiveDocuments) reactiveDocument.Dispose(); } } [Test] public async Task ReactiveDocument_ReplaceLocalFlushesWholeDocument() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.direct-member-flush", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.ReplaceLocal(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "whole-document-replacement", Revision = 4 }); await reactive.FlushAsync(); predictions.Should().ContainSingle(); predictions[0].Text.Should().Be("whole-document-replacement"); predictions[0].Revision.Should().Be(4); } [Test] public async Task ReactiveDocument_CleanFlushDoesNotWrite() { var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var writes = 0; var handle = CultMesh.Document( "mesh.note.reactive.clean-flush", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => new Subject(), value => { writes++; current = value; return Task.CompletedTask; }); using var reactive = await handle.AuthoritativeWriter().ReactiveAsync(); await reactive.FlushAsync(); writes.Should().Be(0); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_FlushesAgainWhenEditedDuringInFlightFlush() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var allowFirstWrite = new TaskCompletionSource(); var firstWriteStarted = new TaskCompletionSource(); var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.in-flight", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, async value => { predictions.Add(value); if (predictions.Count == 1) { firstWriteStarted.SetResult(true); await allowFirstWrite.Task.ConfigureAwait(false); } current = value; subject.OnNext(value); }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "first"; document.Revision = 2; }); var flush = reactive.FlushAsync(); await firstWriteStarted.Task; reactive.Update(document => { document.Text = "second"; document.Revision = 3; }); allowFirstWrite.SetResult(true); await flush; predictions.Should().HaveCount(2); predictions[0].Text.Should().Be("first"); predictions[1].Text.Should().Be("second"); current.Revision.Should().Be(3); } [Test] public async Task ReactiveDocument_DebouncesBurstUpdatesIntoOnePrediction() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.burst", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { predictions.Add(value); current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMilliseconds(35) }); for (var i = 0; i < 5; i++) { reactive.Update(document => { document.Text = $"burst-{i}"; document.Revision++; }); } await WaitForAsync(() => predictions.Count == 1); predictions[0].Text.Should().Be("burst-4"); predictions[0].Revision.Should().Be(6); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_ExplicitEditsDuringInFlightFlushQueueLatestPrediction() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var allowFirstWrite = new TaskCompletionSource(); var firstWriteStarted = new TaskCompletionSource(); var predictions = new List(); var handle = CultMesh.Document( "mesh.note.reactive.direct-in-flight", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, async value => { predictions.Add(value); if (predictions.Count == 1) { firstWriteStarted.SetResult(true); await allowFirstWrite.Task.ConfigureAwait(false); } current = value; subject.OnNext(value); }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "first-explicit"; document.Revision = 2; }); var flush = reactive.FlushAsync(); await firstWriteStarted.Task; reactive.Update(document => { document.Text = "second-explicit"; document.Revision = 3; }); allowFirstWrite.SetResult(true); await flush; predictions.Should().HaveCount(2); predictions[0].Text.Should().Be("first-explicit"); predictions[1].Text.Should().Be("second-explicit"); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task ReactiveDocument_CanonicalEchoClearsOutstandingPredictionWithoutDelta() { var subject = new Subject(); var current = new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "initial", Revision = 1 }; var handle = CultMesh.Document( "mesh.note.reactive.canonical-echo", CultMesh.Verse("starbridge", "pilot-a"), _ => Task.FromResult(current), _ => subject, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }, value => { current = value; subject.OnNext(value); return Task.CompletedTask; }); using var reactive = await handle.PredictionWriter().ReactiveAsync( new CultMeshReactiveDocumentOptions { FlushDelay = TimeSpan.FromMinutes(1) }); reactive.Update(document => { document.Text = "accepted"; document.Revision = 2; }); await reactive.FlushAsync(); reactive.Snapshot.Text.Should().Be("accepted"); reactive.Reconciliation.Should().BeNull(); reactive.IsDirty.Should().BeFalse(); } [Test] public async Task DocumentHandle_ReadsSchemaPublicationsFromSingleFileStores() { using var directory = new TemporaryDirectory(); var filePath = Path.Combine(directory.Path, "publication.ccmp"); var key = new CultRecordKey("mesh-note:publication"); var writerCache = new CultCache(); writerCache.AddBackingStore(new SingleFileMessagePackBackingStore(filePath)); await writerCache.UpsertAsync(typeof(MeshPublicationNoteDocument), new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "published", Revision = 1 }, key); writerCache.FlushAllBackingStores(); File.Exists(filePath).Should().BeTrue(); var directReadCache = new CultCache(); directReadCache.AddBackingStore(new SingleFileMessagePackBackingStore(filePath)); await directReadCache.PullAllBackingStoresAsync(); directReadCache.Get(key)!.Text.Should().Be("published"); var handle = CultMesh.DocumentFromPublication( CultMeshDocumentPublicationSource.SingleFile(filePath), key, CultMesh.Verse("starbridge", "unity-pilot"), new CultMeshStoreDocumentOptions { DocumentId = "daemon:tests.mesh_note.latest", SourceId = "daemon:tests.mesh_note.latest.v1", PollInterval = TimeSpan.FromMilliseconds(10) }); var observed = new List(); using var subscription = handle.Watch(value => observed.Add(value)); (await handle.LatestAsync()).Text.Should().Be("published"); handle.CanReplace.Should().BeFalse(); handle.CanSubmitPrediction.Should().BeFalse(); handle.DocumentId.Should().Be("daemon:tests.mesh_note.latest"); handle.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); handle.Sources.Should().ContainSingle().Which.SourceId.Should().Be("daemon:tests.mesh_note.latest.v1"); var republisher = new CultCache(); republisher.AddBackingStore(new SingleFileMessagePackBackingStore(filePath)); await republisher.UpsertAsync(typeof(MeshPublicationNoteDocument), new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "republished", Revision = 2 }, key); republisher.FlushAllBackingStores(); await WaitForAsync(() => observed.Any(value => value.Text == "republished")); (await handle.LatestAsync()).Revision.Should().Be(2); } [Test] public async Task SingleFileDocumentHelpers_RoundTripTypedDocuments() { using var directory = new TemporaryDirectory(); var filePath = Path.Combine(directory.Path, "single-document.ccmp"); var key = new CultRecordKey("mesh-note:single-document"); CultMesh.WriteSingleFileDocument( filePath, key, new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "published-directly", Revision = 3 }, storedAt: "2026-06-27T12:00:00.0000000Z"); var document = CultMesh.ReadSingleFileDocument(filePath, key); document.Text.Should().Be("published-directly"); document.Revision.Should().Be(3); var cache = new CultCache(); cache.AddBackingStore(new SingleFileMessagePackBackingStore(filePath)); await cache.PullAllBackingStoresAsync(); cache.Get(key)!.Text.Should().Be("published-directly"); } [Test] public async Task DocumentHandle_ReadsRemotePeerSnapshotsAsTypedDocuments() { var cache = new CultCache(); var key = new CultRecordKey("mesh-note:remote"); await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "remote-snapshot", Revision = 9 }, new CultRecordHandle(key)); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared) .Register(CultNetDocumentBinding.ForDocument(CultDocumentRegistry.Shared)); var requests = new List(); var handle = CultMesh.DocumentFromPublication( CultMeshDocumentPublicationSource.PeerSnapshot( () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(cache, request.MessageId, request); }), "cultnet://snapshot.test:3075"), key, CultMesh.Verse("starbridge", "unity-pilot"), peerOptions: new CultMeshPeerSnapshotDocumentOptions { PollInterval = TimeSpan.FromMilliseconds(10), MessageIdPrefix = "mesh-test-snapshot" }); var catalog = CultMesh.Documents(handle); var snapshot = await handle.LatestAsync(); snapshot.Text.Should().Be("remote-snapshot"); snapshot.Revision.Should().Be(9); handle.DocumentType.Should().Be(typeof(MeshNoteDocument)); handle.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Network); handle.Sources.Should().ContainSingle().Which.SourceId.Should().Be(key.Value); requests.Should().ContainSingle(); requests[0].SchemaIds.Should().ContainSingle().Which .Should().Be(CultDocumentRegistry.Shared.GetRequired().SchemaId); requests[0].RecordKeys.Should().ContainSingle().Which.Should().Be(key.Value); (await catalog.LatestAsync()).Text.Should().Be("remote-snapshot"); var aliasSchemaHandle = CultMesh.DocumentFromPeerSnapshot( _ => Task.FromResult(new CultNetSnapshotResponseRawMessage { MessageId = "alias-schema", Documents = new[] { new CultNetRawDocumentRecord { SchemaId = "remote.generated.mesh_note_alias.v99", RecordKey = key.Value, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "record-key-fallback", Revision = 10 }, typeof(MeshNoteDocument)) } } }), key.Value, CultMesh.Verse("starbridge", "unity-pilot")); (await aliasSchemaHandle.LatestAsync()).Text.Should().Be("record-key-fallback"); var mixedAliasSchemaHandle = CultMesh.DocumentFromPeerSnapshot( _ => Task.FromResult(new CultNetSnapshotResponseRawMessage { MessageId = "alias-schema-with-neighbor", Documents = new[] { new CultNetRawDocumentRecord { SchemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId, RecordKey = "mesh-note:other", StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "wrong-schema-neighbor", Revision = 11 }, typeof(MeshNoteDocument)) }, new CultNetRawDocumentRecord { SchemaId = "remote.generated.mesh_note_alias.v100", RecordKey = key.Value, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "compatible-record-key", Revision = 12 }, typeof(MeshNoteDocument)) } } }), key.Value, CultMesh.Verse("starbridge", "unity-pilot")); (await mixedAliasSchemaHandle.LatestAsync()).Text.Should().Be("compatible-record-key"); // The recordKey match is exact (ordinal), not case-insensitive. A case-variant key must not // win even when it appears first in the response. var caseVariantKey = key.Value.ToUpperInvariant(); var caseVariantKeyHandle = CultMesh.DocumentFromPeerSnapshot( _ => Task.FromResult(new CultNetSnapshotResponseRawMessage { MessageId = "case-variant-key", Documents = new[] { new CultNetRawDocumentRecord { SchemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId, RecordKey = caseVariantKey, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "wrong-case-key", Revision = 13 }, typeof(MeshNoteDocument)) }, new CultNetRawDocumentRecord { SchemaId = CultDocumentRegistry.Shared.GetRequired().SchemaId, RecordKey = key.Value, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "right-case-key", Revision = 14 }, typeof(MeshNoteDocument)) } } }), key.Value, CultMesh.Verse("starbridge", "unity-pilot")); (await caseVariantKeyHandle.LatestAsync()).Text.Should().Be("right-case-key"); } // R-R (docs/cultnet-selection-cut.md, Self's rulings 2026-09-22) corrects the earlier commit-2 // ruling this test used to pin: an explicit empty recordKeys array is a real filter, not "no // filter" that falls back to the type's own schema default. Discrepancy against R-R's prose, // reported rather than silently resolved: at CultMesh's typed convenience layer this selection // never reaches the wire at all. ResolveDefaultSelection now produces Keys=[] instead of Keys=null, // and EnsureV0Compatible - CultMesh's own client-side door, landed under the earlier ruling R-F - // already refuses an empty selection.keys before any request is sent ("Keys=[]/[\"\"] is refused // rather than silently lowered to 'every key'", CultMeshSnapshots.cs:836-841). R-R's "recordKeys: // [] answers empty" therefore only holds for the raw v0 wire protocol server, which any v0 peer // can call directly without CultMesh's typed wrapper - see // CultNetDocumentRegistry_RawSnapshot_ExplicitEmptyRecordKeys_AnswersEmpty in NetworkingTests.cs. [Test] public void FetchDocumentsAsync_WithExplicitEmptyRecordKeys_IsRefusedClientSide() { var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared) .Register(CultNetDocumentBinding.ForDocument(CultDocumentRegistry.Shared)); var requests = new List(); using var session = CultMesh.SnapshotSession( "cultnet://empty-keys-default.test:3075", new CultMeshSnapshotRequestOptions { CreateClient = () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(new CultCache(), request.MessageId, request); }) }, registry); Assert.ThrowsAsync( () => session.FetchDocumentsAsync(recordKeys: Array.Empty())); requests.Should().BeEmpty(); } [Test] public async Task PublicationHelpers_BindConfiguredDocumentsIntoCatalog() { using var directory = new TemporaryDirectory(); var firstPath = Path.Combine(directory.Path, "first.ccmp"); var secondPath = Path.Combine(directory.Path, "second.ccmp"); var firstKey = new CultRecordKey("mesh-note:publication-catalog-first"); var secondKey = new CultRecordKey("mesh-note:publication-catalog-second"); var firstWriter = new CultCache(); firstWriter.AddBackingStore(new SingleFileMessagePackBackingStore(firstPath)); await firstWriter.UpsertAsync(typeof(MeshPublicationNoteDocument), new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "first source", Revision = 21 }, firstKey); firstWriter.FlushAllBackingStores(); var secondWriter = new CultCache(); secondWriter.AddBackingStore(new SingleFileMessagePackBackingStore(secondPath)); await secondWriter.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "second source", Revision = 22 }, new CultRecordHandle(secondKey)); secondWriter.FlushAllBackingStores(); var catalog = CultMesh.DocumentsFromPublication( CultMeshDocumentPublicationSource.SingleFile(firstPath), new ICultMeshPublicationDocumentBinding[] { CultMesh.PublicationDocument( firstKey, documentId: "daemon:first"), CultMesh.PublicationDocument( secondKey, documentId: "daemon:second", source: CultMeshDocumentPublicationSource.SingleFile(secondPath)) }, CultMesh.Verse("starbridge", "unity-catalog"), new CultMeshStoreDocumentOptions { RouteHint = new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "publication catalog"), PollInterval = TimeSpan.FromMilliseconds(10) }); catalog.Documents.Select(document => document.DocumentId).Should().Equal("daemon:first", "daemon:second"); (await catalog.LatestAsync()).Text.Should().Be("first source"); (await catalog.LatestAsync()).Text.Should().Be("second source"); catalog.Document().DocumentId.Should().Be("daemon:second"); catalog.Document().RouteHint.Description.Should().Be("publication catalog"); catalog.Document().Sources.Should().ContainSingle().Which.SourceId.Should().Be("daemon:second"); } [Test] public async Task PublicationHelpers_SyncConfiguredDocumentsIntoLocalCatalog() { using var directory = new TemporaryDirectory(); var firstPath = Path.Combine(directory.Path, "sync-first.ccmp"); var secondPath = Path.Combine(directory.Path, "sync-second.ccmp"); var firstKey = new CultRecordKey("mesh-note:publication-sync-catalog-first"); var secondKey = new CultRecordKey("mesh-note:publication-sync-catalog-second"); var firstWriter = new CultCache(); firstWriter.AddBackingStore(new SingleFileMessagePackBackingStore(firstPath)); await firstWriter.UpsertAsync(typeof(MeshPublicationNoteDocument), new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "first synced source", Revision = 31 }, firstKey); firstWriter.FlushAllBackingStores(); var secondWriter = new CultCache(); secondWriter.AddBackingStore(new SingleFileMessagePackBackingStore(secondPath)); await secondWriter.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "second synced source", Revision = 32 }, new CultRecordHandle(secondKey)); secondWriter.FlushAllBackingStores(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(directory.Path, "target.ccmp"), new CultMeshNodeOptions { StartServer = false }); var catalog = await CultMesh.SyncDocumentsFromPublicationAsync( node, CultMeshDocumentPublicationSource.SingleFile(firstPath), new ICultMeshPublicationDocumentBinding[] { CultMesh.PublicationDocument( firstKey, documentId: "local:first"), CultMesh.PublicationDocument( secondKey, documentId: "local:second", source: CultMeshDocumentPublicationSource.SingleFile(secondPath)) }, CultMesh.Verse("starbridge", "unity-sync-catalog"), new CultMeshStoreDocumentOptions { PollInterval = TimeSpan.FromMilliseconds(10) }); catalog.Documents.Select(document => document.DocumentId).Should().Equal("local:first", "local:second"); (await catalog.LatestAsync()).Text.Should().Be("first synced source"); (await catalog.LatestAsync()).Text.Should().Be("second synced source"); node.Cache.Get(firstKey)!.Revision.Should().Be(31); (await node.Document(secondKey, CultMesh.Verse("starbridge", "unity-sync-catalog")) .LatestAsync()).Text.Should().Be("second synced source"); (await node.Document(secondKey, CultMesh.Verse("starbridge", "unity-sync-catalog")) .LatestAsync()).Revision.Should().Be(32); } [Test] public async Task PublicationHelpers_SyncConfiguredPeerSourceIntoLocalNode() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:publication-sync-alias"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "publication-synced", Revision = 51 }, new CultRecordHandle(key)); var requests = new List(); var verse = CultMesh.Verse("starbridge", "unity-support"); using var node = await CultMesh.CreateNodeAsync( Path.Combine(Path.GetTempPath(), $"cultmesh-publication-sync-alias-{Guid.NewGuid():N}.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var synced = await CultMesh.SyncDocumentFromPublicationAsync( node, CultMeshDocumentPublicationSource.PeerSnapshot( () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request); }), "cultnet://publication-sync.test:3075"), key, verse, peerOptions: new CultMeshPeerSnapshotDocumentOptions { MessageIdPrefix = "mesh-test-publication-sync", PollInterval = TimeSpan.FromMilliseconds(10) }); var local = await node.Document(key, verse).LatestAsync(); synced.Text.Should().Be("publication-synced"); synced.Revision.Should().Be(51); local.Text.Should().Be("publication-synced"); local.Revision.Should().Be(51); requests.Should().ContainSingle().Which.RecordKeys.Should().Equal(key.Value); } [Test] public async Task SnapshotHelpers_FetchApplyAndDecodeScopedSnapshots() { var sourceCache = new CultCache(); var targetCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:snapshot-helper"); await sourceCache.UpsertAsync(new MeshPublicationNoteDocument { Schema = "tests.mesh_publication_note.v1", Text = "snapshot-helper", Revision = 11 }, new CultRecordHandle(key)); var requests = new List(); var endpoint = "cultnet://snapshot-helper.test:3075"; var options = new CultMeshSnapshotRequestOptions { Selection = new CultNetSelection { Keys = new[] { key.Value } }, ShardId = "primary", ShardEpoch = 7, MessageIdPrefix = "mesh-test-scoped-snapshot", CreateClient = () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request); }) }; var snapshot = await CultMesh.FetchSnapshotAsync(endpoint, options); var decoded = await CultMesh.FetchSnapshotDocumentsAsync( endpoint, options, registry); using var node = await CultMesh.CreateNodeAsync( Path.Combine(Path.GetTempPath(), $"cultmesh-snapshot-helper-{Guid.NewGuid():N}.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = targetCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var applied = await CultMesh.ApplySnapshotAsync(node, endpoint, options); snapshot.Documents.Should().ContainSingle().Which.RecordKey.Should().Be(key.Value); decoded.Should().ContainSingle().Which.Text.Should().Be("snapshot-helper"); applied.Should().ContainSingle(); node.Cache.Get(key)!.Revision.Should().Be(11); requests.Should().HaveCount(3); requests.Should().OnlyContain(request => request.RecordKeys!.SequenceEqual(new[] { key.Value }) && request.ShardId == "primary" && request.ShardEpoch == 7 && request.MessageId.StartsWith("mesh-test-scoped-snapshot:", StringComparison.Ordinal)); } [Test] public async Task SnapshotHelpers_SurfaceBackgroundClientFailureBeforeTimeout() { var options = new CultMeshSnapshotRequestOptions { ResponseTimeout = TimeSpan.FromSeconds(30), Selection = new CultNetSelection { Keys = new[] { "eve:surface:test" } }, CreateClient = () => new FailedBackgroundSchemaClient( new FormatException("malformed fragment")) }; Func act = async () => await CultMesh.FetchSnapshotAsync( "rudp://127.0.0.1:3075", options); var failure = await act.Should().ThrowAsync(); failure.Which.InnerException.Should().BeOfType(); failure.Which.Message.Should().Contain("127.0.0.1:3075"); } [Test] public async Task SnapshotHelpers_SurfaceBackgroundClientFailureWhileConnecting() { var options = new CultMeshSnapshotRequestOptions { ConnectTimeout = TimeSpan.FromSeconds(30), CreateClient = () => new FailedBackgroundSchemaClient( new FormatException("malformed handshake"), connected: false) }; Func act = async () => await CultMesh.FetchSnapshotAsync( "rudp://127.0.0.1:3075", options); var failure = await act.Should().ThrowAsync(); failure.Which.InnerException.Should().BeOfType(); failure.Which.Message.Should().Contain("connecting"); } [Test] public async Task SnapshotHelpers_DecodeFromForeignSchemaId() { var key = new CultRecordKey("mesh-note:snapshot-alias-foreign-schema"); var snapshot = new CultNetSnapshotResponseRawMessage { MessageId = "foreign-schema", Documents = new[] { new CultNetRawDocumentRecord { SchemaId = "runtime.generated.mesh-note.ui.42", RecordKey = key.Value, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "foreign-schema-alias", Revision = 44 }, typeof(MeshNoteDocument)) } } }; var documents = await CultMesh.FetchSnapshotDocumentsAsync( "cultnet://foreign-schema.test:3075", new CultMeshSnapshotRequestOptions { Selection = new CultNetSelection { Keys = new[] { key.Value } }, CreateClient = () => new MeshSnapshotSchemaClient(request => { snapshot.MessageId = request.MessageId; return snapshot; }) }, new CultNetDocumentRegistry()); documents.Should().ContainSingle().Which.Text.Should().Be("foreign-schema-alias"); } [Test] public async Task SnapshotEndpoint_SyncsCompatibleForeignSchemaIdIntoLocalCache() { var key = new CultRecordKey("mesh-note:snapshot-sync-foreign-schema"); var snapshot = new CultNetSnapshotResponseRawMessage { MessageId = "foreign-schema-sync", Documents = new[] { new CultNetRawDocumentRecord { SchemaId = "runtime.generated.mesh-note.ui.84", RecordKey = key.Value, StoredAt = DateTimeOffset.UtcNow.ToString("O"), PayloadEncoding = "messagepack", Payload = CultDocumentMessagePackSerialization.SerializeUntyped( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "foreign-schema-synced", Revision = 45 }, typeof(MeshNoteDocument)) } } }; var cacheRegistry = CultMesh.CreateCultCacheDocumentRegistry( typeof(MeshNoteDocument), typeof(MeshNoteDocument)); var registry = CultMesh.CreateCultNetDocumentRegistry( new[] { typeof(MeshNoteDocument), typeof(MeshNoteDocument) }, cacheRegistry); var endpoint = CultMesh.SnapshotEndpoint( "cultnet://foreign-schema.test:3075", new CultMeshSnapshotEndpointOptions { DocumentRegistry = registry, Request = new CultMeshSnapshotRequestOptions { CreateClient = () => new MeshSnapshotSchemaClient(request => { snapshot.MessageId = request.MessageId; return snapshot; }) } }); using var node = await CultMesh.CreateNodeAsync( Path.Combine(Path.GetTempPath(), $"cultmesh-foreign-schema-sync-{Guid.NewGuid():N}.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = cacheRegistry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var synced = await endpoint.SyncDocumentAsync(node, key.Value); synced.Text.Should().Be("foreign-schema-synced"); node.Cache.Get(key)!.Text.Should().Be("foreign-schema-synced"); node.Cache.Get(key)!.Revision.Should().Be(45); } [Test] public async Task SnapshotFacade_SyncsRemoteDocumentIntoLocalNode() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:facade-sync-alias"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "facade-synced", Revision = 46 }, new CultRecordHandle(key)); var requests = new List(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(Path.GetTempPath(), $"cultmesh-facade-sync-alias-{Guid.NewGuid():N}.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var synced = await CultMesh.SyncDocumentFromPeerSnapshotAsync( node, () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request); }), "cultnet://facade-sync.test:3075", key.Value, new CultMeshSnapshotEndpointOptions { DocumentRegistry = registry, Context = CultMesh.Verse("starbridge", "unity-raven").Context }); var local = await node.Document(key.Value, CultMesh.Verse("starbridge", "unity-raven")) .LatestAsync(); synced.Text.Should().Be("facade-synced"); synced.Revision.Should().Be(46); local.Text.Should().Be("facade-synced"); node.Cache.Get(key)!.Revision.Should().Be(46); requests.Should().ContainSingle().Which.RecordKeys.Should().Equal(key.Value); } [Test] public async Task SnapshotSession_ReusesOneConnectedClientAcrossOrderedRequests() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var firstKey = new CultRecordKey("mesh-note:session-first"); var secondKey = new CultRecordKey("mesh-note:session-second"); await sourceCache.UpsertAsync( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "first", Revision = 1 }, new CultRecordHandle(firstKey)); await sourceCache.UpsertAsync( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "second", Revision = 2 }, new CultRecordHandle(secondKey)); var client = new MeshSnapshotSchemaClient(request => registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request)); using var session = CultMesh.SnapshotSession( "cultnet://session.test:3075", new CultMeshSnapshotRequestOptions { CreateClient = () => client }, registry); var first = await session.FetchDocumentsAsync(new[] { firstKey.Value }); var second = await session.FetchDocumentsAsync(new[] { secondKey.Value }); client.ConnectCount.Should().Be(1); first.Should().ContainSingle().Which.Text.Should().Be("first"); second.Should().ContainSingle().Which.Text.Should().Be("second"); } [Test] public async Task DocumentRegistryHelpers_CreateTypedNetworkRegistriesForAliases() { var sourceCache = new CultCache(); var cacheRegistry = CultMesh.CreateCultCacheDocumentRegistry( typeof(MeshNoteDocument), typeof(MeshNoteDocument)); var networkRegistry = CultMesh.CreateCultNetDocumentRegistry( new[] { typeof(MeshNoteDocument), typeof(MeshNoteDocument) }, cacheRegistry); var key = new CultRecordKey("mesh-note:registry-helper"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "registry-helper", Revision = 23 }, new CultRecordHandle(key)); var endpoint = "cultnet://registry-helper.test:3077"; var documents = await CultMesh.FetchSnapshotDocumentsAsync( endpoint, new CultMeshSnapshotRequestOptions { Selection = new CultNetSelection { Keys = new[] { key.Value } }, CreateClient = () => new MeshSnapshotSchemaClient(request => networkRegistry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request)) }, networkRegistry); var schemaId = cacheRegistry.GetRequired().SchemaId; cacheRegistry.GetRequired().SchemaId.Should().Be(schemaId); networkRegistry.GetByDocumentType(typeof(MeshNoteDocument)).Should().NotBeNull(); networkRegistry.GetByDocumentType(typeof(MeshNoteDocument)).Should().NotBeNull(); networkRegistry.GetBySchemaId(schemaId)!.DocumentType.Should().Be(typeof(MeshNoteDocument)); documents.Should().ContainSingle().Which.Text.Should().Be("registry-helper"); } [Test] public async Task SnapshotEndpoint_ProvidesTypedHandles() { var sourceCache = new CultCache(); var registry = new CultNetDocumentRegistry(CultDocumentRegistry.Shared); registry.Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry)); var key = new CultRecordKey("mesh-note:snapshot-endpoint"); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "snapshot-endpoint", Revision = 19 }, new CultRecordHandle(key)); var requests = new List(); var endpoint = "cultnet://snapshot-endpoint.test:3076"; var surface = CultMesh.SnapshotEndpoint( endpoint, new CultMeshSnapshotEndpointOptions { Context = CultMesh.Verse("starbridge", "browser-starfire").Context, DocumentRegistry = registry, Request = new CultMeshSnapshotRequestOptions { ShardId = "primary", ShardEpoch = 9, CreateClient = () => new MeshSnapshotSchemaClient(request => { requests.Add(request); return registry.CreateRawSnapshotResponse(sourceCache, request.MessageId, request); }) } }); var fetched = await surface.FetchDocumentAsync(key.Value); using var node = await CultMesh.CreateNodeAsync( Path.Combine(Path.GetTempPath(), $"cultmesh-snapshot-endpoint-{Guid.NewGuid():N}.ccmp"), new CultMeshNodeOptions { StartServer = false, CacheOptions = new CultCacheOpenOptions { Registry = sourceCache.Registry }, DatabaseOptions = new CultNetDatabaseOptions { DocumentRegistry = registry } }); var syncedDocument = await surface.SyncDocumentAsync(node, key.Value); var endpointHandle = surface.Document(key.Value); var endpointLatest = await endpointHandle.LatestAsync(); var catalog = surface.Documents( CultMesh.SnapshotDocument(key.Value, "daemon:mesh-note:snapshot-endpoint")); var catalogLatest = await catalog.LatestAsync(); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "snapshot-endpoint-synced", Revision = 20 }, new CultRecordHandle(key)); var syncedEndpoint = surface.SyncTo(node); var syncedHandle = syncedEndpoint.Document(key.Value); var syncedLatest = await syncedHandle.LatestAsync(); await sourceCache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "snapshot-endpoint-catalog-synced", Revision = 21 }, new CultRecordHandle(key)); var syncedCatalog = syncedEndpoint.Documents( CultMesh.SnapshotDocument(key.Value)); var syncedCatalogLatest = await syncedCatalog.LatestAsync(); fetched.Text.Should().Be("snapshot-endpoint"); syncedDocument.Text.Should().Be("snapshot-endpoint"); endpointLatest.Revision.Should().Be(19); catalog.Document().DocumentId.Should().Be("daemon:mesh-note:snapshot-endpoint"); catalogLatest.Text.Should().Be("snapshot-endpoint"); syncedLatest.Text.Should().Be("snapshot-endpoint-synced"); node.Cache.Get(key)!.Revision.Should().Be(21); syncedCatalogLatest.Text.Should().Be("snapshot-endpoint-catalog-synced"); requests.Should().HaveCount(6); requests.Should().OnlyContain(request => request.SchemaIds == null && request.RecordKeys!.SequenceEqual(new[] { key.Value }) && request.ShardId == "primary" && request.ShardEpoch == 9 && request.MessageId.StartsWith("cultmesh:browser-starfire:snapshot:", StringComparison.Ordinal)); } [Test] public async Task CollectionHandle_ReadsAndWatchesDatabaseCollectionsByIndex() { var cache = new CultCache(); var database = new CultNetDatabase(cache); var teamId = Guid.NewGuid().ToString("D"); var collection = CultMesh.CollectionByIndex( database, "TeamId", teamId); var changes = new List>(); using var subscription = collection.WatchChanges(change => changes.Add(change)); await database.PutAsync( new CultRecordKey("player:one"), new MeshIndexedPlayer { Name = "One", TeamId = teamId, Score = 10 }); await database.PutAsync( new CultRecordKey("player:two"), new MeshIndexedPlayer { Name = "Two", TeamId = Guid.NewGuid().ToString("D"), Score = 20 }); collection.CollectionId.Should().Contain(teamId); collection.SchemaName.Should().Be("tests.mesh_indexed_player"); collection.RouteHint.Kind.Should().Be(CultMeshLocalityKind.Automatic); collection.Sources.Should().ContainSingle().Which.SchemaId.Should().Be(collection.SchemaId); var snapshot = await collection.LatestAsync(); snapshot.Should().ContainSingle().Which.Name.Should().Be("One"); changes.Should().ContainSingle(); changes[0].Kind.Should().Be(CultMeshCollectionChangeKind.Added); changes[0].Document!.Name.Should().Be("One"); } [Test] public async Task CollectionHandle_IsIndexedInItsCatalogByTypeAndSchema() { var cache = new CultCache(); await cache.UpsertAsync( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "collection-alias", Revision = 4 }, new CultRecordHandle(new CultRecordKey("mesh-note:collection"))); var collection = CultMesh.Collection(cache); var catalog = CultMesh.Collections(collection); catalog.Collections.Should().ContainSingle().Which.Should().BeSameAs(collection); catalog.TryGetCollection(out var byType).Should().BeTrue(); byType.Should().BeSameAs(collection); catalog.TryGetCollection(out _).Should().BeFalse(); catalog.TryGetCollectionBySchema("tests.mesh_note.v1", out var byVersion).Should().BeTrue(); byVersion.Should().BeSameAs(collection); catalog.CollectionBySchema("tests.mesh_note").Should().BeSameAs(collection); catalog.CollectionBySchema(CultDocumentRegistry.Shared.GetRequired().SchemaId) .Should() .BeSameAs(collection); var snapshot = await collection.LatestAsync(); snapshot.Should().ContainSingle().Which.Text.Should().Be("collection-alias"); var catalogSnapshot = await catalog.LatestAsync(); catalogSnapshot.Should().ContainSingle().Which.Revision.Should().Be(4); CultMeshCollectionChange observed = null!; using var subscription = catalog.WatchChanges(change => observed = change); await cache.UpsertAsync( new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "collection-alias-updated", Revision = 5 }, new CultRecordHandle(new CultRecordKey("mesh-note:collection"))); observed.Kind.Should().Be(CultMeshCollectionChangeKind.Updated); observed.Document!.Text.Should().Be("collection-alias-updated"); observed.Document.Revision.Should().Be(5); observed.PreviousDocument!.Text.Should().Be("collection-alias"); } [Test] public async Task DocumentHandle_ReplacesThroughCultMeshNodeDatabase() { using var directory = new TemporaryDirectory(); using var node = await CultMesh.CreateNodeAsync( Path.Combine(directory.Path, "mesh-node.cc"), new CultMeshNodeOptions { DatabaseOptions = new CultNetDatabaseOptions { RuntimeId = "node-runtime", Shards = new[] { new CultNetShardDescriptor( "mesh", "node-runtime", epoch: 1, isPrimary: true, schemaIds: new[] { CultDocumentRegistry.Shared.GetRequired().SchemaId }) } }, StartServer = false }); var key = new CultRecordKey("mesh-note:node"); var handle = CultMesh.Document( node, key, CultMesh.Verse("starbridge", "unity-pilot")); MeshNoteDocument observed = null!; using var subscription = handle.Watch(value => observed = value); await handle.ReplaceAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "node-replaced", Revision = 3 }); var snapshot = await handle.LatestAsync(); snapshot.Text.Should().Be("node-replaced"); observed.Text.Should().Be("node-replaced"); observed.Revision.Should().Be(3); } [Test] public void SurfaceCatalog_DescribesTypedRuntimeSurfaces() { var route = new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "co-located frame slab"); var sources = new[] { CultMesh.ProjectionSource( "daemon:aetheria.frame.latest.v1", schemaId: "gamecult.aetheria.daemon_frame.v1") }; var query = new CultMeshQuerySurface( "aetheria.zone.objects.visible", (_request, _context) => Task.FromResult(Array.Empty()), sources: sources, routeHint: route); var feed = CultMesh.LiveFeed( "aetheria.rts.viewport.feed", (_request, _context) => Task.FromResult("frame"), sources: sources, routeHint: route); var operation = new CultMeshOperationHandle( "aetheria.entity.pilot.move", (_request, _context) => Task.FromResult(new CultMeshOperationReceipt("aetheria.entity.pilot.move", true))); var pointer = CultMesh.StatePointer( "aetheria.selection.current", async () => "entity:ship:1", () => new Subject(), route, sources); var document = CultMesh.Document( "daemon:aetheria.frame.latest.v1", CultMesh.Verse("aetheria.local", "unity-raven").Context, _context => Task.FromResult(new MeshNoteDocument { Schema = "frame", Text = "current", Revision = 1 }), _context => new Subject(), sources, route); var collection = new CultMeshCollectionHandle( "daemon:aetheria.contacts.v1", () => Task.FromResult>(Array.Empty()), () => new Subject>(), sources, route); var nativeView = new CultMeshNativeSliceViewDescriptor( "aetheria.zone.render", "gamecult.aetheria.render_body.v1", rowCount: 128, new[] { CultMeshNativeSliceColumn.For("position") }, route); var catalog = CultMesh.DescribeSurfaceCatalog( "gamecult.aetheria.rts.surfaces.v1", new[] { CultMesh.DescribeSurface(query), CultMesh.DescribeSurface(feed), CultMesh.DescribeSurface(operation), CultMesh.DescribeSurface(document), CultMesh.DescribeSurface(collection), CultMesh.DescribeSurface(pointer), CultMesh.DescribeSurface(nativeView) }); catalog.CatalogId.Should().Be("gamecult.aetheria.rts.surfaces.v1"); catalog.Surfaces.Should().HaveCount(7); catalog.Surfaces[0].Kind.Should().Be(CultMeshSurfaceKind.Query); catalog.Surfaces[0].SurfaceId.Should().Be("aetheria.zone.objects.visible"); catalog.Surfaces[0].RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); catalog.Surfaces[0].Sources.Should().HaveCount(1); catalog.Find("aetheria.rts.viewport.feed")!.Kind.Should().Be(CultMeshSurfaceKind.LiveFeed); catalog.Find("aetheria.entity.pilot.move")!.Kind.Should().Be(CultMeshSurfaceKind.Operation); catalog.Find("daemon:aetheria.frame.latest.v1")!.Kind.Should().Be(CultMeshSurfaceKind.Document); catalog.Find("daemon:aetheria.contacts.v1")!.Kind.Should().Be(CultMeshSurfaceKind.Collection); catalog.Find("aetheria.selection.current")!.Kind.Should().Be(CultMeshSurfaceKind.StatePointer); catalog.Find("aetheria.selection.current")!.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); catalog.Find("aetheria.selection.current")!.Sources.Should().ContainSingle(); catalog.Find("aetheria.zone.render")!.Kind.Should().Be(CultMeshSurfaceKind.NativeSliceView); catalog.Find("aetheria.zone.render")!.RouteHint.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); catalog.Find("missing").Should().BeNull(); catalog.FindByKind(CultMeshSurfaceKind.Operation) .Should() .ContainSingle() .Which .SurfaceId .Should() .Be("aetheria.entity.pilot.move"); catalog.FindByKind(CultMeshSurfaceKind.Query).Should().ContainSingle(); var index = CultMesh.DescribeSurfaceCatalogIndex(catalog); index.CatalogId.Should().Be(catalog.CatalogId); index.Queries.Should().ContainSingle().Which.SurfaceId.Should().Be("aetheria.zone.objects.visible"); index.LiveFeeds.Should().ContainSingle().Which.SurfaceId.Should().Be("aetheria.rts.viewport.feed"); index.Operations.Should().ContainSingle().Which.SurfaceId.Should().Be("aetheria.entity.pilot.move"); index.Documents.Should().ContainSingle().Which.SurfaceId.Should().Be("daemon:aetheria.frame.latest.v1"); index.Collections.Should().ContainSingle().Which.SurfaceId.Should().Be("daemon:aetheria.contacts.v1"); index.StatePointers.Should().ContainSingle().Which.SurfaceId.Should().Be("aetheria.selection.current"); index.NativeSliceViews.Should().ContainSingle().Which.SurfaceId.Should().Be("aetheria.zone.render"); index.ProjectionRecipes.Should().BeEmpty(); } [Test] public async Task PollingQueryWatcher_TurnsSnapshotsIntoReactiveFeed() { var frameId = 0; var observed = new List(); var feed = CultMesh.LiveFeed( "aetheria.rts.viewport.feed", (_request, _context) => Task.FromResult(frameId), CultMesh.PollingQueryWatcher( (_request, _context) => Task.FromResult(frameId), new CultMeshPollingWatchOptions(TimeSpan.FromMilliseconds(5)))); using (feed .Watch(new MeshViewportRequest(-1, 1), CultMeshQueryContext.ForRuntime("browser-starfire")) .Subscribe(value => observed.Add(value))) { await Task.Delay(35); frameId = 1; await Task.Delay(35); frameId = 1; await Task.Delay(25); frameId = 2; await Task.Delay(35); } frameId = 3; await Task.Delay(25); observed.Should().Equal(0, 1, 2); } [Test] public void NativeSliceDescriptor_DescribesSharedColumnsWithoutOpeningTransport() { var descriptor = new CultMeshNativeSliceViewDescriptor( "aetheria.zone.render", "gamecult.aetheria.render_body.v1", rowCount: 128, new[] { CultMeshNativeSliceColumn.For("position"), CultMeshNativeSliceColumn.For("velocity") }, new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "CultCache slab"), nativeHandle: "cultcache-slab:aetheria-zone-render"); descriptor.Route.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); descriptor.Columns.Should().HaveCount(2); descriptor.Columns[0].Name.Should().Be("position"); descriptor.DenseRowStrideBytes.Should().Be(16); descriptor.FindColumn("velocity")!.ElementSizeBytes.Should().Be(8); var diagnostic = CultMesh.DescribeNativeSliceView(descriptor); diagnostic.ViewId.Should().Be("aetheria.zone.render"); diagnostic.SchemaId.Should().Be("gamecult.aetheria.render_body.v1"); diagnostic.RowCount.Should().Be(128); diagnostic.Route.Kind.Should().Be(CultMeshLocalityKind.SharedMemory); diagnostic.NativeHandle.Should().Be("cultcache-slab:aetheria-zone-render"); diagnostic.DenseRowStrideBytes.Should().Be(16); diagnostic.Columns.Should().HaveCount(2); diagnostic.Columns.Should().NotBeSameAs(descriptor.Columns); } [Test] public void NegotiatesGpuTextureStreamsWithoutForcingCopies() { var catalog = CultMesh.CreateStreamCatalog(); var stream = new CultMeshStreamDescriptor( "mimir:kiyo-pro:rgba", "mimir-live", "starfire", CultMeshStreamKind.Video, new CultMeshStreamClock("mimir:clock", "kiyo-pro", sampleRate: 90_000, confidence: 0.92d), new[] { CultMeshStreamBodyTransport.SharedD3D12Texture, CultMeshStreamBodyTransport.SharedMemory, CultMeshStreamBodyTransport.CultCachePage }, video: new CultMeshVideoStreamFormat(1920, 1080, "rgba8", framesPerSecond: 60), maxInFlightFrames: 4); catalog.Declare(stream); var negotiation = catalog.Negotiate( stream.StreamId, new CultMeshStreamConsumerProfile( "fensalir", "mimir-live", new[] { CultMeshStreamBodyTransport.SharedD3D12Texture, CultMeshStreamBodyTransport.CultCachePage }, acceptedKinds: new[] { CultMeshStreamKind.Video }, canImportGpuHandles: true, maxInFlightFrames: 2)); negotiation.Transport.Should().Be(CultMeshStreamBodyTransport.SharedD3D12Texture); negotiation.CopyBudget.Should().Be(CultMeshStreamCopyBudget.ZeroCopyTarget); negotiation.MaxInFlightFrames.Should().Be(2); var handle = new CultMeshStreamFrameHandle( stream.StreamId, sequence: 42, timestampNs: 123_456_789, CultMeshStreamBodyTransport.SharedD3D12Texture, nativeHandle: "shared-handle:0xfeedbeef", fenceHandle: "fence:0x1234", fenceValue: 7); catalog.PublishFrame(handle); catalog.LatestFrame(stream.StreamId).Should().BeSameAs(handle); } [Test] public void NegotiationRejectsStreamsWithoutACommonTransport() { var catalog = CultMesh.CreateStreamCatalog(); catalog.Declare(new CultMeshStreamDescriptor( "mimir:kiyo-pro:gpu-only", "mimir-live", "starfire", CultMeshStreamKind.Video, new CultMeshStreamClock("mimir:clock", "kiyo-pro"), new[] { CultMeshStreamBodyTransport.SharedD3D12Texture })); var consumer = new CultMeshStreamConsumerProfile( "cpu-recorder", "mimir-live", new[] { CultMeshStreamBodyTransport.SharedMemory }, acceptedKinds: new[] { CultMeshStreamKind.Video }); Action act = () => catalog.Negotiate("mimir:kiyo-pro:gpu-only", consumer); act.Should().Throw() .WithMessage("Stream and consumer have no compatible body transport."); } [Test] public void SharedMemoryRingPublishesWritableSlotsWithoutInternalCopies() { var catalog = CatalogWithByteStream(); using var ring = catalog.CreateSharedMemoryRing("mimir:leap:depth", slotCount: 3, slotByteLength: 16); ring.TryAcquireWriteSlot(out var write).Should().BeTrue(); ReadOnlySpan seed = stackalloc byte[] { 1, 2, 3, 4 }; seed.CopyTo(write.Span); var handle = ring.CommitWriteSlot(write, timestampNs: 99, byteLength: 4); catalog.PublishFrame(handle); ring.TryAcquireLatestRead(out var read).Should().BeTrue(); using (read) { read.Handle.Sequence.Should().Be(0); read.Handle.UnavoidableCopyCount.Should().Be(0); read.Span.ToArray().Should().Equal(1, 2, 3, 4); } var stats = ring.Stats(); stats.PublishedFrames.Should().Be(1); stats.UnavoidableCopyCount.Should().Be(0); catalog.LatestFrame("mimir:leap:depth")!.ResourceKey.Should().Be("mimir:leap:depth:slot:0"); } [Test] public void SharedMemoryRingDoesNotOverwriteSlotsHeldByReaders() { var catalog = CatalogWithByteStream(); using var ring = catalog.CreateSharedMemoryRing("mimir:leap:depth", slotCount: 3, slotByteLength: 8); ring.TryAcquireWriteSlot(out var firstWrite).Should().BeTrue(); firstWrite.Span[0] = 11; ring.CommitWriteSlot(firstWrite, timestampNs: 1, byteLength: 1); ring.TryAcquireLatestRead(out var read).Should().BeTrue(); ring.TryAcquireWriteSlot(out var secondWrite).Should().BeTrue(); ring.CommitWriteSlot(secondWrite, timestampNs: 2, byteLength: 1); ring.TryAcquireWriteSlot(out var thirdWrite).Should().BeTrue(); ring.CommitWriteSlot(thirdWrite, timestampNs: 3, byteLength: 1); ring.TryAcquireLatestRead(out var thirdRead).Should().BeTrue(); ring.TryAcquireWriteSlot(out var overwriteSecond).Should().BeTrue(); ring.CommitWriteSlot(overwriteSecond, timestampNs: 4, byteLength: 1); ring.TryAcquireLatestRead(out var secondRead).Should().BeTrue(); ring.TryAcquireWriteSlot(out _).Should().BeFalse(); ring.Stats().BlockedWrites.Should().Be(1); read.Dispose(); thirdRead.Dispose(); secondRead.Dispose(); ring.TryAcquireWriteSlot(out var fifthWrite).Should().BeTrue(); fifthWrite.Span[0] = 12; ring.CommitWriteSlot(fifthWrite, timestampNs: 5, byteLength: 1); var stats = ring.Stats(); stats.PublishedFrames.Should().Be(5); stats.DroppedFrames.Should().Be(2); stats.LatestSequence.Should().Be(4); } [Test] public void CopyPublishMarksFallbackCopiesExplicitly() { var catalog = CatalogWithByteStream(); using var ring = catalog.CreateSharedMemoryRing("mimir:leap:depth", slotCount: 3, slotByteLength: 8); ring.TryPublishCopy(stackalloc byte[] { 5, 6, 7 }, timestampNs: 10, durationNs: 2, out var handle) .Should() .BeTrue(); handle.UnavoidableCopyCount.Should().Be(1); ring.Stats().UnavoidableCopyCount.Should().Be(1); } private static CultMeshStreamCatalog CatalogWithByteStream() { var catalog = CultMesh.CreateStreamCatalog(); catalog.Declare(new CultMeshStreamDescriptor( "mimir:leap:depth", "mimir-live", "starfire", CultMeshStreamKind.Tensor, new CultMeshStreamClock("mimir:clock", "leap", confidence: 0.8d), new[] { CultMeshStreamBodyTransport.SharedMemory, CultMeshStreamBodyTransport.CultCachePage })); return catalog; } private static async Task WaitForAsync(Func predicate) { var deadline = DateTimeOffset.UtcNow.AddSeconds(2); while (DateTimeOffset.UtcNow < deadline) { if (predicate()) { return; } await Task.Delay(20); } predicate().Should().BeTrue(); } private sealed class TemporaryDirectory : IDisposable { public TemporaryDirectory() { Path = System.IO.Path.Combine( System.IO.Path.GetTempPath(), "cultmesh-tests-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(Path); } public string Path { get; } public void Dispose() { if (Directory.Exists(Path)) { Directory.Delete(Path, recursive: true); } } } [CultDocument("tests.mesh_indexed_player", "tests.mesh_indexed_player.v1")] [MessagePackObject(AllowPrivate = true)] internal sealed class MeshIndexedPlayer { [Key(0)] public string Name { get; set; } = string.Empty; [Key(1)] [CultIndex] public string TeamId { get; set; } = string.Empty; [Key(2)] public int Score { get; set; } } [CultDocument("tests.mesh_note", "tests.mesh_note.v1")] [MessagePackObject(AllowPrivate = true)] internal sealed class MeshNoteDocument { [Key(0)] public string Schema { get; set; } = string.Empty; [Key(1)] public string Text { get; set; } = string.Empty; [Key(2)] public int Revision { get; set; } } [CultDocument("tests.mesh_publication_note", "tests.mesh_publication_note.v1")] [MessagePackObject(AllowPrivate = true)] internal sealed class MeshPublicationNoteDocument { [Key(0)] public string Schema { get; set; } = string.Empty; [Key(1)] public string Text { get; set; } = string.Empty; [Key(2)] public int Revision { get; set; } } [CultDocument("tests.mesh_other", "tests.mesh_other.v1")] [MessagePackObject(AllowPrivate = true)] internal sealed class MeshOtherDocument { [Key(0)] public string Schema { get; set; } = string.Empty; [Key(1)] public string Text { get; set; } = string.Empty; } } public readonly struct MeshMoveRequest { public MeshMoveRequest(int entityId, float x, float y) { EntityId = entityId; X = x; Y = y; } public int EntityId { get; } public float X { get; } public float Y { get; } } public readonly struct MeshViewportRequest { public MeshViewportRequest(float minX, float maxX) { MinX = minX; MaxX = maxX; } public float MinX { get; } public float MaxX { get; } } public readonly struct MeshVec2 { public MeshVec2(float x, float y) { X = x; Y = y; } public float X { get; } public float Y { get; } } public sealed class MeshAetheriaDomain { private readonly CultMeshVerseContext _context; public MeshAetheriaDomain(CultMeshVerseContext context) { _context = context; } public MeshEntityFacade Entity(int entityId) { return new MeshEntityFacade(_context, entityId); } public MeshZoneFacade Zone(string zoneId) { return new MeshZoneFacade(_context, zoneId); } } public sealed class MeshEntityFacade { public MeshEntityFacade(CultMeshVerseContext context, int entityId) { Pilot = new MeshPilotFacade(context, entityId); } public MeshPilotFacade Pilot { get; } } public sealed class MeshPilotFacade { private readonly int _entityId; private readonly CultMeshBoundOperationHandle _move; public MeshPilotFacade(CultMeshVerseContext context, int entityId) { _entityId = entityId; _move = CultMesh.BindOperation( context, new CultMeshOperationHandle( "aetheria.entity.pilot.move", (request, context) => Task.FromResult(new CultMeshOperationReceipt( "aetheria.entity.pilot.move", accepted: request.EntityId == _entityId && Math.Abs(request.X) > 0 && context.Claims.Any(claim => claim.Role == "pilot-control"), context.RouteHint)))); } public Task MoveAsync(MeshVec2 direction, string idempotencyKey) { return _move.InvokeAsync( new MeshMoveRequest(_entityId, direction.X, direction.Y), idempotencyKey); } } public sealed class MeshZoneFacade { public MeshZoneFacade(CultMeshVerseContext context, string zoneId) { Objects = new MeshObjectsFacade(context, zoneId); } public MeshObjectsFacade Objects { get; } } public sealed class MeshObjectsFacade { private readonly string _zoneId; private readonly CultMeshBoundQuerySurface _visibleObjects; public MeshObjectsFacade(CultMeshVerseContext context, string zoneId) { _zoneId = zoneId; _visibleObjects = CultMesh.BindQuery( context, new CultMeshQuerySurface( "aetheria.zone.objects.visible", (parameters, context) => Task.FromResult(new[] { $"{context.RuntimeId}:{_zoneId}:{parameters.MinX}:{parameters.MaxX}:{context.RouteHint.Kind}" }))); } public Task VisibleWithinAsync(MeshViewportRequest request) { return _visibleObjects.ExecuteAsync(request); } } internal sealed class MeshSnapshotSchemaClient : ICultNetSchemaClient { private readonly Func _respond; private readonly List> _snapshotHandlers = new(); private readonly List> _errorHandlers = new(); public MeshSnapshotSchemaClient( Func respond) { _respond = respond; } public bool Connected { get; private set; } public int ConnectCount { get; private set; } public void Connect(string host, int port) { ConnectCount++; Connected = true; } public void SendCultNet(T message) where T : ICultNetSchemaMessage { if (message is not CultNetSnapshotRequestMessage request) { foreach (var handler in _errorHandlers) { handler(new CultNetErrorMessage { Error = $"Unexpected message {typeof(T).Name}." }); } return; } var response = _respond(request); foreach (var handler in _snapshotHandlers) { handler(response); } } public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { if (typeof(T) == typeof(CultNetSnapshotResponseRawMessage)) { _snapshotHandlers.Add(message => callback((T)(object)message)); } else if (typeof(T) == typeof(CultNetErrorMessage)) { _errorHandlers.Add(message => callback((T)(object)message)); } } public void Dispose() { } } internal sealed class FailedBackgroundSchemaClient : ICultNetSchemaClient, ICultNetSchemaClientHealth { private readonly bool _connected; public FailedBackgroundSchemaClient(Exception error, bool connected = true) { _connected = connected; BackgroundFailure = Task.FromResult(error); } public bool Connected { get; private set; } public Task BackgroundFailure { get; } public void Connect(string host, int port) => Connected = _connected; public void SendCultNet(T message) where T : ICultNetSchemaMessage { } public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { } public void Dispose() { } } internal sealed class BlockingCountClock : ICultMeshClock { private int _delayCount; public DateTimeOffset UtcNow => DateTimeOffset.UnixEpoch; public int DelayCount => Volatile.Read(ref _delayCount); public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken = default) { Interlocked.Increment(ref _delayCount); return Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); } }