diff --git a/packages/eve-contracts/dist/generated/command-receipt.d.ts b/packages/eve-contracts/dist/generated/command-receipt.d.ts index 1ee105b..735cc2f 100644 --- a/packages/eve-contracts/dist/generated/command-receipt.d.ts +++ b/packages/eve-contracts/dist/generated/command-receipt.d.ts @@ -13,7 +13,14 @@ export interface EveCommandReceipt { [k: string]: any; }[]; issuedAtUtc?: string; + /** + * Provider-state generation that causally owns this result. It is not implicitly an Eve surface version. + */ sourceVersion: number; + /** + * Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality. + */ + presentationSurfaceVersion?: number; /** * Canonical digest of the immutable invocation envelope finalized by this receipt. */ diff --git a/packages/eve-contracts/dist/generated/schemas.d.ts b/packages/eve-contracts/dist/generated/schemas.d.ts index 8ea315a..a75705f 100644 --- a/packages/eve-contracts/dist/generated/schemas.d.ts +++ b/packages/eve-contracts/dist/generated/schemas.d.ts @@ -640,6 +640,12 @@ export declare const eveContractSchemas: { readonly sourceVersion: { readonly type: "integer"; readonly minimum: 0; + readonly description: "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version."; + }; + readonly presentationSurfaceVersion: { + readonly type: "integer"; + readonly minimum: 1; + readonly description: "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality."; }; readonly invocationHash: { readonly type: "string"; diff --git a/packages/eve-contracts/dist/generated/schemas.js b/packages/eve-contracts/dist/generated/schemas.js index c036cf9..4695090 100644 --- a/packages/eve-contracts/dist/generated/schemas.js +++ b/packages/eve-contracts/dist/generated/schemas.js @@ -767,7 +767,13 @@ export const eveContractSchemas = { }, "sourceVersion": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version." + }, + "presentationSurfaceVersion": { + "type": "integer", + "minimum": 1, + "description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality." }, "invocationHash": { "type": "string", diff --git a/packages/eve-contracts/src/generated/command-receipt.ts b/packages/eve-contracts/src/generated/command-receipt.ts index 58fba85..7b95536 100644 --- a/packages/eve-contracts/src/generated/command-receipt.ts +++ b/packages/eve-contracts/src/generated/command-receipt.ts @@ -15,7 +15,14 @@ diagnostics?: { [k: string]: any }[] issuedAtUtc?: string +/** + * Provider-state generation that causally owns this result. It is not implicitly an Eve surface version. + */ sourceVersion: number +/** + * Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality. + */ +presentationSurfaceVersion?: number /** * Canonical digest of the immutable invocation envelope finalized by this receipt. */ diff --git a/packages/eve-contracts/src/generated/schemas.ts b/packages/eve-contracts/src/generated/schemas.ts index 18c877e..4716939 100644 --- a/packages/eve-contracts/src/generated/schemas.ts +++ b/packages/eve-contracts/src/generated/schemas.ts @@ -767,7 +767,13 @@ export const eveContractSchemas = { }, "sourceVersion": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version." + }, + "presentationSurfaceVersion": { + "type": "integer", + "minimum": 1, + "description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality." }, "invocationHash": { "type": "string", diff --git a/packages/org.gamecult.eve.surface/GameCult.Eve.Surface.csproj b/packages/org.gamecult.eve.surface/GameCult.Eve.Surface.csproj index b74ed1f..c821cfb 100644 --- a/packages/org.gamecult.eve.surface/GameCult.Eve.Surface.csproj +++ b/packages/org.gamecult.eve.surface/GameCult.Eve.Surface.csproj @@ -6,7 +6,7 @@ enable Portable Eve surface and provider contracts. GameCult.Eve.Surface - 0.3.5 + 0.3.6 GameCult MPL-2.0 https://github.com/GameCult/Eve diff --git a/packages/org.gamecult.eve.surface/Runtime/EveProviderDocuments.cs b/packages/org.gamecult.eve.surface/Runtime/EveProviderDocuments.cs index e924a8d..31705be 100644 --- a/packages/org.gamecult.eve.surface/Runtime/EveProviderDocuments.cs +++ b/packages/org.gamecult.eve.surface/Runtime/EveProviderDocuments.cs @@ -367,8 +367,9 @@ namespace GameCult.Eve.Surface string issuedAtUtc, long sourceVersion, EveSurfaceNavigationTarget? navigation = null, - string invocationHash = "") - : this(SchemaId, receiptId, commandId, command, state, ownerRepo, authority, providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash) + string invocationHash = "", + long presentationSurfaceVersion = 0) + : this(SchemaId, receiptId, commandId, command, state, ownerRepo, authority, providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash, presentationSurfaceVersion) { } @@ -387,7 +388,8 @@ namespace GameCult.Eve.Surface string issuedAtUtc, long sourceVersion, EveSurfaceNavigationTarget? navigation = null, - string invocationHash = "") + string invocationHash = "", + long presentationSurfaceVersion = 0) { Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema; ReceiptId = receiptId ?? ""; @@ -403,6 +405,7 @@ namespace GameCult.Eve.Surface SourceVersion = sourceVersion; Navigation = navigation; InvocationHash = invocationHash ?? ""; + PresentationSurfaceVersion = presentationSurfaceVersion; } [Key(0)] public string Schema { get; } @@ -419,6 +422,7 @@ namespace GameCult.Eve.Surface [Key(11)] public long SourceVersion { get; } [Key(12)] public EveSurfaceNavigationTarget? Navigation { get; } [Key(13)] public string InvocationHash { get; } + [Key(14)] public long PresentationSurfaceVersion { get; } } /// @@ -428,7 +432,7 @@ namespace GameCult.Eve.Surface public sealed class EveCommandReceiptCompatibilityFormatter : IMessagePackFormatter { - private const int LegacyFieldCount = 14; + private const int LegacyFieldCount = 15; private const int RequiredAndScalarFieldCount = 12; public void Serialize( @@ -439,7 +443,8 @@ namespace GameCult.Eve.Surface if (value == null) { writer.WriteNil(); return; } var optionalFields = (value.Navigation == null ? 0 : 1) + - (string.IsNullOrWhiteSpace(value.InvocationHash) ? 0 : 1); + (string.IsNullOrWhiteSpace(value.InvocationHash) ? 0 : 1) + + (value.PresentationSurfaceVersion <= 0 ? 0 : 1); writer.WriteMapHeader(RequiredAndScalarFieldCount + optionalFields); Write(ref writer, "schema", value.Schema); Write(ref writer, "receiptId", value.ReceiptId); @@ -460,6 +465,11 @@ namespace GameCult.Eve.Surface } if (!string.IsNullOrWhiteSpace(value.InvocationHash)) Write(ref writer, "invocationHash", value.InvocationHash); + if (value.PresentationSurfaceVersion > 0) + { + writer.Write("presentationSurfaceVersion"); + writer.Write(value.PresentationSurfaceVersion); + } } public EveCommandReceiptDocument? Deserialize( @@ -490,10 +500,12 @@ namespace GameCult.Eve.Surface var sourceVersion = fields > 11 ? reader.ReadInt64() : 0; var navigation = fields > 12 ? ReadNavigation(ref reader, options) : null; var invocationHash = ReadString(ref reader, fields, 13); + var presentationSurfaceVersion = fields > 14 ? reader.ReadInt64() : 0; for (var index = LegacyFieldCount; index < fields; index++) reader.Skip(); return new EveCommandReceiptDocument( schema, receiptId, commandId, command, state, ownerRepo, authority, - providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash); + providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash, + presentationSurfaceVersion); } finally { reader.Depth--; } } @@ -519,6 +531,7 @@ namespace GameCult.Eve.Surface long sourceVersion = 0; EveSurfaceNavigationTarget? navigation = null; var invocationHash = ""; + long presentationSurfaceVersion = 0; var fields = reader.ReadMapHeader(); for (var index = 0; index < fields; index++) { @@ -538,13 +551,15 @@ namespace GameCult.Eve.Surface case "sourceVersion": sourceVersion = reader.ReadInt64(); break; case "navigation": navigation = ReadNavigation(ref reader, options); break; case "invocationHash": invocationHash = ReadString(ref reader); break; + case "presentationSurfaceVersion": presentationSurfaceVersion = reader.ReadInt64(); break; default: reader.Skip(); break; } } return new EveCommandReceiptDocument( schema, receiptId, commandId, command, state, ownerRepo, authority, - providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash); + providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash, + presentationSurfaceVersion); } finally { reader.Depth--; } } diff --git a/packages/org.gamecult.eve.surface/package.json b/packages/org.gamecult.eve.surface/package.json index 378f218..fdbc322 100644 --- a/packages/org.gamecult.eve.surface/package.json +++ b/packages/org.gamecult.eve.surface/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.eve.surface", "displayName": "Eve Surface Contract", - "version": "0.3.5", + "version": "0.3.6", "unity": "2021.3", "description": "Renderer-neutral C# contracts and builders for gamecult.eve.surface.v1 documents.", "dependencies": { diff --git a/schemas/gamecult.eve.command_receipt.v1.schema.json b/schemas/gamecult.eve.command_receipt.v1.schema.json index b668e95..d862a90 100644 --- a/schemas/gamecult.eve.command_receipt.v1.schema.json +++ b/schemas/gamecult.eve.command_receipt.v1.schema.json @@ -67,7 +67,13 @@ }, "sourceVersion": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version." + }, + "presentationSurfaceVersion": { + "type": "integer", + "minimum": 1, + "description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality." }, "invocationHash": { "type": "string", diff --git a/tests/GameCult.Eve.Surface.Serialization.Tests/EveSurfaceSerializationCompatibilityTests.cs b/tests/GameCult.Eve.Surface.Serialization.Tests/EveSurfaceSerializationCompatibilityTests.cs index be11714..c756359 100644 --- a/tests/GameCult.Eve.Surface.Serialization.Tests/EveSurfaceSerializationCompatibilityTests.cs +++ b/tests/GameCult.Eve.Surface.Serialization.Tests/EveSurfaceSerializationCompatibilityTests.cs @@ -266,7 +266,8 @@ public sealed class EveSurfaceSerializationCompatibilityTests "interactive-world", new[] { "cultnet+tcp://odin.gamecult.example:3076" }, "commander-daemon"), - "sha256:launch-envelope"); + "sha256:launch-envelope", + presentationSurfaceVersion: 77); var bytes = MessagePackSerializer.Serialize(document, Options); var reader = new MessagePackReader(bytes); @@ -274,7 +275,7 @@ public sealed class EveSurfaceSerializationCompatibilityTests var restored = MessagePackSerializer.Deserialize(bytes, Options); Assert.That(reader.NextMessagePackType, Is.EqualTo(MessagePackType.Map)); - Assert.That(reader.ReadMapHeader(), Is.EqualTo(14)); + Assert.That(reader.ReadMapHeader(), Is.EqualTo(15)); Assert.That(wireJson.RootElement.GetProperty("navigation").ValueKind, Is.EqualTo(JsonValueKind.Object)); Assert.That(restored.Navigation, Is.Not.Null); Assert.That(restored.Navigation!.VerseId, Is.EqualTo("gamecult.aetheria")); @@ -283,6 +284,7 @@ public sealed class EveSurfaceSerializationCompatibilityTests Assert.That(restored.Navigation.SurfaceKind, Is.EqualTo("interactive-world")); Assert.That(restored.Navigation.RendezvousEndpoints, Is.EqualTo(new[] { "cultnet+tcp://odin.gamecult.example:3076" })); Assert.That(restored.InvocationHash, Is.EqualTo("sha256:launch-envelope")); + Assert.That(restored.PresentationSurfaceVersion, Is.EqualTo(77)); } [Test] @@ -322,6 +324,7 @@ public sealed class EveSurfaceSerializationCompatibilityTests Assert.That(restored.Navigation!.SurfaceId, Is.EqualTo("aetheria.pilot")); Assert.That(restored.Navigation.AuthorityRuntimeId, Is.Empty); Assert.That(restored.InvocationHash, Is.Empty); + Assert.That(restored.PresentationSurfaceVersion, Is.Zero); }); } @@ -349,8 +352,10 @@ public sealed class EveSurfaceSerializationCompatibilityTests { Assert.That(wireJson.RootElement.TryGetProperty("navigation", out _), Is.False); Assert.That(wireJson.RootElement.TryGetProperty("invocationHash", out _), Is.False); + Assert.That(wireJson.RootElement.TryGetProperty("presentationSurfaceVersion", out _), Is.False); Assert.That(restored.Navigation, Is.Null); Assert.That(restored.InvocationHash, Is.Empty); + Assert.That(restored.PresentationSurfaceVersion, Is.Zero); }); }