Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a4a75c1b | ||
|
|
c4a61e818e | ||
|
|
59924e324c | ||
|
|
382a23d8f8 | ||
|
|
dabea374b1 | ||
|
|
97bbbcdc61 | ||
|
|
7d486b0089 | ||
|
|
030383470f | ||
|
|
0b099274a4 |
18 changed files with 209 additions and 7 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
node_modules/
|
||||
artifacts/
|
||||
|
||||
**/bin/
|
||||
**/obj/
|
||||
|
|
|
|||
16
plugins/eve-plugin-fields/GameCult.Eve.PluginFields.csproj
Normal file
16
plugins/eve-plugin-fields/GameCult.Eve.PluginFields.csproj
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Description>Runtime-neutral C# contracts for gamecult.fields documents.</Description>
|
||||
<CultLibRoot Condition="'$(CultLibRoot)' == ''">$(MSBuildThisFileDirectory)..\..\..\CultLib</CultLibRoot>
|
||||
<PackageId>GameCult.Eve.PluginFields</PackageId>
|
||||
<Version>0.2.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Caching\GameCult.Caching.csproj" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.7" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7122b42864d44e029d5d718a53d03186
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d6eb92523e7048c4844a7fe9d3bdcf4a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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<double> PhaseY { get; }
|
||||
IReadOnlyList<double> AnimationSpeed { get; }
|
||||
IReadOnlyList<double> SourceFlags { get; }
|
||||
IReadOnlyList<double> FalloffScale { get; }
|
||||
IReadOnlyList<double> FalloffExponent { get; }
|
||||
}
|
||||
|
||||
public interface IEveFieldsSplatsDocument
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 115753bcd85347649537e429e9dd9e8f
|
||||
|
|
@ -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<EveFieldsSplatLayer> Layers { get; set; } = Array.Empty<EveFieldsSplatLayer>();
|
||||
[Key(9)] public EveFieldsSplatSoa Splats { get; set; } = new EveFieldsSplatSoa();
|
||||
|
||||
IEveFieldsViewport IEveFieldsSplatsDocument.Viewport => Viewport;
|
||||
IReadOnlyList<IEveFieldsSplatLayer> 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<double> CenterX { get; set; } = Array.Empty<double>();
|
||||
[Key(2)] public IReadOnlyList<double> CenterY { get; set; } = Array.Empty<double>();
|
||||
[Key(3)] public IReadOnlyList<double> HalfExtentX { get; set; } = Array.Empty<double>();
|
||||
[Key(4)] public IReadOnlyList<double> HalfExtentY { get; set; } = Array.Empty<double>();
|
||||
[Key(5)] public IReadOnlyList<double> RotationCos { get; set; } = Array.Empty<double>();
|
||||
[Key(6)] public IReadOnlyList<double> RotationSin { get; set; } = Array.Empty<double>();
|
||||
[Key(7)] public IReadOnlyList<int> Channel { get; set; } = Array.Empty<int>();
|
||||
[Key(8)] public IReadOnlyList<int> Falloff { get; set; } = Array.Empty<int>();
|
||||
[Key(9)] public IReadOnlyList<double> ValueR { get; set; } = Array.Empty<double>();
|
||||
[Key(10)] public IReadOnlyList<double> ValueG { get; set; } = Array.Empty<double>();
|
||||
[Key(11)] public IReadOnlyList<double> ValueB { get; set; } = Array.Empty<double>();
|
||||
[Key(12)] public IReadOnlyList<double> ValueA { get; set; } = Array.Empty<double>();
|
||||
[Key(13)] public IReadOnlyList<string> SourceKey { get; set; } = Array.Empty<string>();
|
||||
[Key(14)] public IReadOnlyList<int> LayerIndex { get; set; } = Array.Empty<int>();
|
||||
[Key(15)] public IReadOnlyList<int> SourceKind { get; set; } = Array.Empty<int>();
|
||||
[Key(16)] public IReadOnlyList<double> FrequencyX { get; set; } = Array.Empty<double>();
|
||||
[Key(17)] public IReadOnlyList<double> FrequencyY { get; set; } = Array.Empty<double>();
|
||||
[Key(18)] public IReadOnlyList<double> PhaseX { get; set; } = Array.Empty<double>();
|
||||
[Key(19)] public IReadOnlyList<double> PhaseY { get; set; } = Array.Empty<double>();
|
||||
[Key(20)] public IReadOnlyList<double> AnimationSpeed { get; set; } = Array.Empty<double>();
|
||||
[Key(21)] public IReadOnlyList<double> SourceFlags { get; set; } = Array.Empty<double>();
|
||||
[Key(22)] public IReadOnlyList<double> FalloffScale { get; set; } = Array.Empty<double>();
|
||||
[Key(23)] public IReadOnlyList<double> FalloffExponent { get; set; } = Array.Empty<double>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7e1b64023d224e8cb0bfaec57adad5c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b0812861c9b453cbd5b02c6dc84098c
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 59ad75f1e14ba2245b8766107f53a8b7
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in a new issue