diff --git a/.gitignore b/.gitignore index 36a9264..69f9c63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ artifacts/ - +**/bin/ +**/obj/ diff --git a/plugins/eve-plugin-fields/GameCult.Eve.PluginFields.csproj b/plugins/eve-plugin-fields/GameCult.Eve.PluginFields.csproj new file mode 100644 index 0000000..cbfbfb3 --- /dev/null +++ b/plugins/eve-plugin-fields/GameCult.Eve.PluginFields.csproj @@ -0,0 +1,16 @@ + + + netstandard2.1 + enable + latest + Runtime-neutral C# contracts for gamecult.fields documents. + $(MSBuildThisFileDirectory)..\..\..\CultLib + GameCult.Eve.PluginFields + 0.2.3 + + + + + + + diff --git a/plugins/eve-plugin-fields/advertisement.json b/plugins/eve-plugin-fields/advertisement.json index 7cf626e..08a999b 100644 --- a/plugins/eve-plugin-fields/advertisement.json +++ b/plugins/eve-plugin-fields/advertisement.json @@ -2,7 +2,7 @@ "schema": "gamecult.eve.plugin_advertisement.v1", "pluginId": "fields.surface", "ownerService": "eveplugins", - "version": "0.1.0", + "version": "0.1.1", "manifestAddress": "file://plugins/eve-plugin-fields/plugin.json", "runtime": { "invocationModel": "executable-sidecar", diff --git a/plugins/eve-plugin-fields/plugin-abi-fixture.json b/plugins/eve-plugin-fields/plugin-abi-fixture.json index d75aba9..c98a2a8 100644 --- a/plugins/eve-plugin-fields/plugin-abi-fixture.json +++ b/plugins/eve-plugin-fields/plugin-abi-fixture.json @@ -8,9 +8,9 @@ "responseSchema": "gamecult.eve.plugin_abi.response.v1", "authority": "EvePlugins owns field semantics; providers own sampled field state and runtimes own native projection.", "operations": [ - { "operation": "describe", "input": {}, "expect": { "pluginId": "fields.surface", "componentKinds": ["field.surface2d", "gravity.surface"], "commands": [] } }, - { "operation": "validate", "input": { "componentKinds": ["field.surface2d", "world.scene3d"], "document": { "schema": "gamecult.fields.surface2d.v1", "bounds": { "minX": -1, "minY": -1, "maxX": 1, "maxY": 1 } } }, "expect": { "acceptedComponentKinds": ["field.surface2d"], "rejectedComponentKinds": ["world.scene3d"], "validDocument": true } }, + { "operation": "describe", "input": {}, "expect": { "pluginId": "fields.surface", "componentKinds": ["field.surface2d", "gravity.surface"], "commands": [], "capabilities": ["field.surface2d", "gravity.surface", "field.scalar", "field.vector", "field.objects"] } }, + { "operation": "validate", "input": { "componentKinds": ["field.surface2d", "gravity.surface", "world.scene3d"], "document": { "schema": "gamecult.fields.surface2d.v1", "bounds": { "minX": -1, "minY": -1, "maxX": 1, "maxY": 1 } } }, "expect": { "acceptedComponentKinds": ["field.surface2d", "gravity.surface"], "rejectedComponentKinds": ["world.scene3d"], "validDocument": true } }, { "operation": "project", "input": { "document": { "schema": "gamecult.fields.surface2d.v1", "fieldId": "fixture.field", "bounds": { "minX": -1, "minY": -1, "maxX": 1, "maxY": 1 }, "channels": [{ "channelId": "height", "kind": "scalar", "documentId": "cultmesh://fixture/height", "schemaId": "gamecult.fields.scalar.v1" }] } }, "expect": { "projectionKind": "semantic-field-surface", "ownedComponentKinds": ["field.surface2d", "gravity.surface"], "providerStateMutated": false } }, - { "operation": "measure", "input": { "document": { "schema": "gamecult.fields.surface2d.v1", "fieldId": "fixture.field", "bounds": { "minX": -1, "minY": -2, "maxX": 3, "maxY": 2 }, "channels": [] } }, "expect": { "measurementKind": "field-domain-metrics", "width": 4, "height": 4, "preservesProviderAuthority": true } } + { "operation": "measure", "input": { "document": { "schema": "gamecult.fields.surface2d.v1", "fieldId": "fixture.field", "bounds": { "minX": -1, "minY": -2, "maxX": 3, "maxY": 2 }, "channels": [] } }, "expect": { "measurementKind": "field-domain-metrics", "measurementOutputs": ["width", "height", "channelCount"], "width": 4, "height": 4, "preservesProviderAuthority": true } } ] } diff --git a/plugins/eve-plugin-fields/plugin.json b/plugins/eve-plugin-fields/plugin.json index 6dfa5f8..d178460 100644 --- a/plugins/eve-plugin-fields/plugin.json +++ b/plugins/eve-plugin-fields/plugin.json @@ -4,11 +4,20 @@ "title": "Semantic Fields", "owner": "EvePlugins", "incubation": { + "status": "graduated", "ownerRepo": "EvePlugins", "currentHostRepo": "EvePlugins", "splitTarget": "EvePlugins/eve-plugin-fields", "graduationTrigger": "Split when field schemas, sidecar witness, and two runtime adapters are stable." }, + "graduation": { + "status": "graduated", + "runtimeAdapters": ["web-reference", "unity-scene"], + "evidence": [ + "artifacts/fields-surface/runtime-witness.json", + "EveUnity/artifacts/fields-surface/latest/unity-fields.png" + ] + }, "componentKinds": ["field.surface2d", "gravity.surface"], "commands": [], "runtime": { diff --git a/plugins/eve-plugin-fields/src/plugin.mjs b/plugins/eve-plugin-fields/src/plugin.mjs index 1659e7e..1fb84b7 100644 --- a/plugins/eve-plugin-fields/src/plugin.mjs +++ b/plugins/eve-plugin-fields/src/plugin.mjs @@ -62,6 +62,7 @@ export function measure(input) { const projection = project(input); return { measurementKind: "field-domain-metrics", + measurementOutputs: ["width", "height", "channelCount"], width: projection.bounds.maxX - projection.bounds.minX, height: projection.bounds.maxY - projection.bounds.minY, channelCount: projection.channels.length, diff --git a/plugins/eve-plugin-fields/test/plugin.test.mjs b/plugins/eve-plugin-fields/test/plugin.test.mjs index 9d75db4..85df1b7 100644 --- a/plugins/eve-plugin-fields/test/plugin.test.mjs +++ b/plugins/eve-plugin-fields/test/plugin.test.mjs @@ -38,6 +38,7 @@ test("measures field domains without mutating provider state", () => { assert.equal(result.width, 20); assert.equal(result.height, 10); assert.equal(result.channelCount, 1); + assert.deepEqual(result.measurementOutputs, ["width", "height", "channelCount"]); assert.equal(result.preservesProviderAuthority, true); }); @@ -48,5 +49,15 @@ test("publishes a runtime-neutral Unity contract package", () => { assert.equal(manifest.name, "org.gamecult.eve.plugin-fields"); assert.match(contracts, /interface IEveFieldsSplatsDocument/); assert.match(contracts, /gamecult\.fields\.splats\.v1/); + assert.match(contracts, /AnimatedCellNoiseB = 3/); + assert.match(contracts, /AnimatedRadialCosine = 4/); + assert.match(contracts, /AbsoluteValue = 1/); assert.doesNotMatch(contracts, /UnityEngine|Aetheria|MessagePack|GameCult\.Caching/); + assert.match(readFileSync(new URL("../GameCult.Eve.PluginFields.csproj", import.meta.url), "utf8"), /netstandard2\.1/); +}); + +test("records fields plugin graduation after two native runtime proofs", () => { + const manifest = JSON.parse(readFileSync(new URL("../plugin.json", import.meta.url), "utf8")); + assert.equal(manifest.graduation.status, "graduated"); + assert.deepEqual(manifest.graduation.runtimeAdapters, ["web-reference", "unity-scene"]); }); diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md index 8584a0c..fabc7f1 100644 --- a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md @@ -3,3 +3,26 @@ This package owns the portable C# view of `gamecult.fields.*` documents. It has no Unity rendering, provider state, transport, or command authority. Providers implement these read-only interfaces; Unity runtime adapters consume them. + +`PowerPulse` is the portable compact-support radial falloff +`pow(saturate(1 - pow(distance01 * scale, 2)), exponent)`. Producers publish +`FalloffScale` and `FalloffExponent` alongside each SoA splat. Missing parameter +entries retain the neutral defaults `1` and `1`, so older v1 documents remain +readable. + +Procedural sources are evaluated in field-world coordinates, not splat-local +UVs. `SimplexNoise` samples signed Ashima 3D simplex at +`(worldXY * frequency + phase, 0)`. `AnimatedSimplexNoise` uses +`simulationTimeSeconds * animationSpeed` for the third coordinate. +`AnimatedCellNoiseB` is the portable moving, wrappable cellular source whose +distance metric is `(length(frac(p) - .5) * 1.5 + .25) * +max(abs(x) * .866 + y * .5, -y)`. `AbsoluteValue` folds a signed source after +sampling. Constant offsets remain ordinary constant splats, so source sampling +and additive composition retain separate ownership. + +`AnimatedRadialCosine` is splat-local rather than field-world anchored. It +evaluates +`cos(pow(distance01, FrequencyY) * FrequencyX + PhaseX + simulationTimeSeconds * AnimationSpeed)`. +`FrequencyX` is radial frequency and `FrequencyY` is the positive radial +exponent. The ordinary splat falloff owns the envelope, which keeps oscillation +and compact support independently composable. diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md.meta new file mode 100644 index 0000000..52811c6 --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7122b42864d44e029d5d718a53d03186 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime.meta new file mode 100644 index 0000000..b5575fd --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d6eb92523e7048c4844a7fe9d3bdcf4a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs index 075a8e4..8c712e2 100644 --- a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs @@ -43,6 +43,13 @@ namespace GameCult.Eve.PluginFields public const int Constant = 0; public const int SimplexNoise = 1; public const int AnimatedSimplexNoise = 2; + public const int AnimatedCellNoiseB = 3; + public const int AnimatedRadialCosine = 4; + } + + public static class EveFieldsSplatSourceFlags + { + public const int AbsoluteValue = 1; } public static class EveFieldsSplatFalloffs @@ -51,6 +58,7 @@ namespace GameCult.Eve.PluginFields public const int Linear = 1; public const int Smooth = 2; public const int InverseSmooth = 3; + public const int PowerPulse = 4; } public interface IEveFieldsViewport @@ -99,6 +107,8 @@ namespace GameCult.Eve.PluginFields IReadOnlyList PhaseY { get; } IReadOnlyList AnimationSpeed { get; } IReadOnlyList SourceFlags { get; } + IReadOnlyList FalloffScale { get; } + IReadOnlyList FalloffExponent { get; } } public interface IEveFieldsSplatsDocument diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs.meta new file mode 100644 index 0000000..73cae51 --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsContracts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 115753bcd85347649537e429e9dd9e8f diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs new file mode 100644 index 0000000..c33679d --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using GameCult.Caching; +using MessagePack; + +namespace GameCult.Eve.PluginFields +{ + [CultDocument("gamecult.fields.splats", EveFieldsSchemas.Splats)] + [MessagePackObject] + public sealed class EveFieldsSplatsDocument : IEveFieldsSplatsDocument + { + [Key(0)] public string Schema { get; set; } = EveFieldsSchemas.Splats; + [Key(1)] public long FrameId { get; set; } + [Key(2)] public string PublishedAtUtc { get; set; } = ""; + [Key(3)] public double SimulationTimeSeconds { get; set; } + [Key(4)] public string RunId { get; set; } = ""; + [Key(5)] public int ZoneIndex { get; set; } + [Key(6)] public string ZoneName { get; set; } = ""; + [Key(7)] public EveFieldsViewport Viewport { get; set; } = new EveFieldsViewport(); + [Key(8)] public IReadOnlyList Layers { get; set; } = Array.Empty(); + [Key(9)] public EveFieldsSplatSoa Splats { get; set; } = new EveFieldsSplatSoa(); + + IEveFieldsViewport IEveFieldsSplatsDocument.Viewport => Viewport; + IReadOnlyList IEveFieldsSplatsDocument.Layers => Layers; + IEveFieldsSplatSoa IEveFieldsSplatsDocument.Splats => Splats; + } + + [MessagePackObject] + public sealed class EveFieldsViewport : IEveFieldsViewport + { + [Key(0)] public double MinX { get; set; } + [Key(1)] public double MinY { get; set; } + [Key(2)] public double MaxX { get; set; } + [Key(3)] public double MaxY { get; set; } + } + + [MessagePackObject] + public sealed class EveFieldsSplatLayer : IEveFieldsSplatLayer + { + [Key(0)] public string LayerKey { get; set; } = ""; + [Key(1)] public string DisplayName { get; set; } = ""; + [Key(2)] public int Channel { get; set; } + [Key(3)] public string BlendMode { get; set; } = EveFieldsSplatBlendModes.Add; + [Key(4)] public string GraphicsFormat { get; set; } = "R16_SFloat"; + [Key(5)] public bool ClearBeforeDraw { get; set; } = true; + [Key(6)] public double ClearR { get; set; } + [Key(7)] public double ClearG { get; set; } + [Key(8)] public double ClearB { get; set; } + [Key(9)] public double ClearA { get; set; } + } + + [MessagePackObject] + public sealed class EveFieldsSplatSoa : IEveFieldsSplatSoa + { + [Key(0)] public int Count { get; set; } + [Key(1)] public IReadOnlyList CenterX { get; set; } = Array.Empty(); + [Key(2)] public IReadOnlyList CenterY { get; set; } = Array.Empty(); + [Key(3)] public IReadOnlyList HalfExtentX { get; set; } = Array.Empty(); + [Key(4)] public IReadOnlyList HalfExtentY { get; set; } = Array.Empty(); + [Key(5)] public IReadOnlyList RotationCos { get; set; } = Array.Empty(); + [Key(6)] public IReadOnlyList RotationSin { get; set; } = Array.Empty(); + [Key(7)] public IReadOnlyList Channel { get; set; } = Array.Empty(); + [Key(8)] public IReadOnlyList Falloff { get; set; } = Array.Empty(); + [Key(9)] public IReadOnlyList ValueR { get; set; } = Array.Empty(); + [Key(10)] public IReadOnlyList ValueG { get; set; } = Array.Empty(); + [Key(11)] public IReadOnlyList ValueB { get; set; } = Array.Empty(); + [Key(12)] public IReadOnlyList ValueA { get; set; } = Array.Empty(); + [Key(13)] public IReadOnlyList SourceKey { get; set; } = Array.Empty(); + [Key(14)] public IReadOnlyList LayerIndex { get; set; } = Array.Empty(); + [Key(15)] public IReadOnlyList SourceKind { get; set; } = Array.Empty(); + [Key(16)] public IReadOnlyList FrequencyX { get; set; } = Array.Empty(); + [Key(17)] public IReadOnlyList FrequencyY { get; set; } = Array.Empty(); + [Key(18)] public IReadOnlyList PhaseX { get; set; } = Array.Empty(); + [Key(19)] public IReadOnlyList PhaseY { get; set; } = Array.Empty(); + [Key(20)] public IReadOnlyList AnimationSpeed { get; set; } = Array.Empty(); + [Key(21)] public IReadOnlyList SourceFlags { get; set; } = Array.Empty(); + [Key(22)] public IReadOnlyList FalloffScale { get; set; } = Array.Empty(); + [Key(23)] public IReadOnlyList FalloffExponent { get; set; } = Array.Empty(); + } +} diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs.meta new file mode 100644 index 0000000..edc01a3 --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/EveFieldsDocuments.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e1b64023d224e8cb0bfaec57adad5c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef index b1ae4e7..8afd12c 100644 --- a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef @@ -2,6 +2,12 @@ "name": "GameCult.Eve.PluginFields", "rootNamespace": "GameCult.Eve.PluginFields", "references": [], + "overrideReferences": true, + "precompiledReferences": [ + "GameCult.Caching.dll", + "MessagePack.Annotations.dll", + "MessagePack.dll" + ], "autoReferenced": true, "noEngineReferences": true } diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef.meta new file mode 100644 index 0000000..e4325fa --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/Runtime/GameCult.Eve.PluginFields.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4b0812861c9b453cbd5b02c6dc84098c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json index a925186..a985686 100644 --- a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json @@ -1,8 +1,11 @@ { "name": "org.gamecult.eve.plugin-fields", - "version": "0.1.0", + "version": "0.2.3", "displayName": "Eve Fields Plugin Contracts", "description": "Runtime-neutral C# contracts for gamecult.fields documents.", - "unity": "2022.3", + "unity": "2021.3", + "dependencies": { + "org.gamecult.cultlib": "1.0.13" + }, "license": "MPL-2.0" } diff --git a/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json.meta b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json.meta new file mode 100644 index 0000000..0bc6c7d --- /dev/null +++ b/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 59ad75f1e14ba2245b8766107f53a8b7 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: