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: