Add independent semantic fields plugin
This commit is contained in:
parent
ae7e2fa2a4
commit
e562055bfe
12 changed files with 378 additions and 11 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -31,6 +31,10 @@
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@gamecult/eve-plugin-fields": {
|
||||||
|
"resolved": "plugins/eve-plugin-fields",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@gamecult/eve-plugin-tex": {
|
"node_modules/@gamecult/eve-plugin-tex": {
|
||||||
"resolved": "plugins/eve-plugin-tex",
|
"resolved": "plugins/eve-plugin-tex",
|
||||||
"link": true
|
"link": true
|
||||||
|
|
@ -73,6 +77,14 @@
|
||||||
"katex": "cli.js"
|
"katex": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"plugins/eve-plugin-fields": {
|
||||||
|
"name": "@gamecult/eve-plugin-fields",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"bin": {
|
||||||
|
"eve-plugin-fields": "src/sidecar.mjs",
|
||||||
|
"eve-plugin-fields-cultnet": "src/cultnet-sidecar.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"plugins/eve-plugin-tex": {
|
"plugins/eve-plugin-tex": {
|
||||||
"name": "@gamecult/eve-plugin-tex",
|
"name": "@gamecult/eve-plugin-tex",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"workspaces": ["plugins/*"],
|
"workspaces": ["plugins/*"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "C:\\Progra~1\\nodejs\\node.exe --test plugins/*/test/*.test.mjs",
|
"test": "C:\\Progra~1\\nodejs\\node.exe --test plugins/*/test/*.test.mjs",
|
||||||
"witness:tex": "C:\\Progra~1\\nodejs\\node.exe scripts/run-tex-witness.mjs"
|
"witness:tex": "C:\\Progra~1\\nodejs\\node.exe scripts/run-tex-witness.mjs tex",
|
||||||
|
"witness:fields": "C:\\Progra~1\\nodejs\\node.exe scripts/run-tex-witness.mjs fields"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@msgpack/msgpack": "^3.1.3",
|
"@msgpack/msgpack": "^3.1.3",
|
||||||
|
|
|
||||||
28
plugins/eve-plugin-fields/advertisement.json
Normal file
28
plugins/eve-plugin-fields/advertisement.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"schema": "gamecult.eve.plugin_advertisement.v1",
|
||||||
|
"pluginId": "fields.surface",
|
||||||
|
"ownerService": "eveplugins",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"manifestAddress": "file://plugins/eve-plugin-fields/plugin.json",
|
||||||
|
"runtime": {
|
||||||
|
"invocationModel": "executable-sidecar",
|
||||||
|
"contract": "gamecult.eve.plugin_abi.v1",
|
||||||
|
"transports": ["cultnet-rudp", "stdio"],
|
||||||
|
"authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"],
|
||||||
|
"sidecar": {
|
||||||
|
"processKind": "long-running-daemon",
|
||||||
|
"protocol": "cultnet-operation-v0",
|
||||||
|
"debugProtocol": "stdio-ndjson",
|
||||||
|
"requestSchema": "gamecult.eve.plugin_abi.request.v1",
|
||||||
|
"responseSchema": "gamecult.eve.plugin_abi.response.v1",
|
||||||
|
"operations": ["describe", "validate", "project", "measure"],
|
||||||
|
"commandEnvelope": "gamecult.eve.command_invocation.v1",
|
||||||
|
"receiptSchema": "gamecult.eve.plugin_receipt.v1",
|
||||||
|
"stateAuthority": "projects-provider-owned-field-documents-without-mutating-them"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": ["gamecult.fields.surface2d.v1", "gamecult.fields.scalar.v1", "gamecult.fields.vector.v1", "gamecult.fields.objects.v1"],
|
||||||
|
"componentKinds": ["field.surface2d", "gravity.surface"],
|
||||||
|
"commands": [],
|
||||||
|
"fixtures": ["fields-surface-basic"]
|
||||||
|
}
|
||||||
10
plugins/eve-plugin-fields/fixtures/basic-field.json
Normal file
10
plugins/eve-plugin-fields/fixtures/basic-field.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"schema": "gamecult.fields.surface2d.v1",
|
||||||
|
"fieldId": "fixture.field",
|
||||||
|
"bounds": { "minX": -10, "minY": -10, "maxX": 10, "maxY": 10 },
|
||||||
|
"channels": [
|
||||||
|
{ "channelId": "height", "kind": "scalar", "documentId": "cultmesh://fixture/fields/height", "schemaId": "gamecult.fields.scalar.v1" },
|
||||||
|
{ "channelId": "flow", "kind": "vector", "documentId": "cultmesh://fixture/fields/flow", "schemaId": "gamecult.fields.vector.v1" }
|
||||||
|
],
|
||||||
|
"objects": { "documentId": "cultmesh://fixture/fields/objects", "schemaId": "gamecult.fields.objects.v1" }
|
||||||
|
}
|
||||||
10
plugins/eve-plugin-fields/package.json
Normal file
10
plugins/eve-plugin-fields/package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "@gamecult/eve-plugin-fields",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"bin": {
|
||||||
|
"eve-plugin-fields": "src/sidecar.mjs",
|
||||||
|
"eve-plugin-fields-cultnet": "src/cultnet-sidecar.mjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
plugins/eve-plugin-fields/plugin-abi-fixture.json
Normal file
16
plugins/eve-plugin-fields/plugin-abi-fixture.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"schema": "gamecult.eve.plugin_abi_fixture.v1",
|
||||||
|
"fixtureId": "fields-surface-live-sidecar",
|
||||||
|
"pluginId": "fields.surface",
|
||||||
|
"ownerRepo": "EvePlugins",
|
||||||
|
"contract": "gamecult.eve.plugin_abi.v1",
|
||||||
|
"requestSchema": "gamecult.eve.plugin_abi.request.v1",
|
||||||
|
"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": "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 } }
|
||||||
|
]
|
||||||
|
}
|
||||||
40
plugins/eve-plugin-fields/plugin.json
Normal file
40
plugins/eve-plugin-fields/plugin.json
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"schema": "gamecult.eve.plugin.v1",
|
||||||
|
"pluginId": "fields.surface",
|
||||||
|
"title": "Semantic Fields",
|
||||||
|
"owner": "EvePlugins",
|
||||||
|
"incubation": {
|
||||||
|
"ownerRepo": "EvePlugins",
|
||||||
|
"currentHostRepo": "EvePlugins",
|
||||||
|
"splitTarget": "EvePlugins/eve-plugin-fields",
|
||||||
|
"graduationTrigger": "Split when field schemas, sidecar witness, and two runtime adapters are stable."
|
||||||
|
},
|
||||||
|
"componentKinds": ["field.surface2d", "gravity.surface"],
|
||||||
|
"commands": [],
|
||||||
|
"runtime": {
|
||||||
|
"invocationModel": "executable-sidecar",
|
||||||
|
"contract": "gamecult.eve.plugin_abi.v1",
|
||||||
|
"transports": ["cultnet-rudp", "stdio"],
|
||||||
|
"authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"],
|
||||||
|
"sidecar": {
|
||||||
|
"processKind": "long-running-daemon",
|
||||||
|
"protocol": "cultnet-operation-v0",
|
||||||
|
"debugProtocol": "stdio-ndjson",
|
||||||
|
"requestSchema": "gamecult.eve.plugin_abi.request.v1",
|
||||||
|
"responseSchema": "gamecult.eve.plugin_abi.response.v1",
|
||||||
|
"operations": ["describe", "validate", "project", "measure"],
|
||||||
|
"commandEnvelope": "gamecult.eve.command_invocation.v1",
|
||||||
|
"receiptSchema": "gamecult.eve.plugin_receipt.v1",
|
||||||
|
"stateAuthority": "projects-provider-owned-field-documents-without-mutating-them"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stateSchemas": ["gamecult.fields.surface2d.v1", "gamecult.fields.scalar.v1", "gamecult.fields.vector.v1", "gamecult.fields.objects.v1"],
|
||||||
|
"fixtures": [
|
||||||
|
{ "fixtureId": "fields-surface-basic", "schema": "gamecult.fields.surface2d.v1", "path": "plugins/eve-plugin-fields/fixtures/basic-field.json" }
|
||||||
|
],
|
||||||
|
"abiFixtures": ["plugins/eve-plugin-fields/plugin-abi-fixture.json"],
|
||||||
|
"capabilities": {
|
||||||
|
"requires": ["field.surface2d"],
|
||||||
|
"optionalCapabilities": ["gravity.surface", "field.scalar", "field.vector", "field.objects"]
|
||||||
|
}
|
||||||
|
}
|
||||||
62
plugins/eve-plugin-fields/src/cultnet-sidecar.mjs
Normal file
62
plugins/eve-plugin-fields/src/cultnet-sidecar.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Buffer } from "node:buffer";
|
||||||
|
import { decode, encode } from "@msgpack/msgpack";
|
||||||
|
import { startCultNetOperationServer } from "cultnet-ts";
|
||||||
|
import { dispatch, pluginId } from "./plugin.mjs";
|
||||||
|
|
||||||
|
const host = process.env.EVE_PLUGIN_HOST || "127.0.0.1";
|
||||||
|
const port = Number(process.env.EVE_PLUGIN_PORT || 0);
|
||||||
|
const runtimeId = "eve-plugin-fields";
|
||||||
|
const server = await startCultNetOperationServer({
|
||||||
|
runtimeId,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
handler: request => {
|
||||||
|
if (request.serviceId !== pluginId) throw new Error(`Expected service ${pluginId}.`);
|
||||||
|
if (request.payloadSchema !== "gamecult.eve.plugin_abi.request.v1") throw new Error("Unsupported plugin payload schema.");
|
||||||
|
const abiRequest = decode(Buffer.from(request.payload, "base64"));
|
||||||
|
let abiResponse;
|
||||||
|
try {
|
||||||
|
abiResponse = {
|
||||||
|
schema: "gamecult.eve.plugin_abi.response.v1",
|
||||||
|
pluginId,
|
||||||
|
operation: abiRequest.operation,
|
||||||
|
requestId: abiRequest.requestId,
|
||||||
|
status: "accepted",
|
||||||
|
output: dispatch(abiRequest),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
abiResponse = {
|
||||||
|
schema: "gamecult.eve.plugin_abi.response.v1",
|
||||||
|
pluginId,
|
||||||
|
operation: abiRequest?.operation || request.operation,
|
||||||
|
requestId: abiRequest?.requestId || request.messageId,
|
||||||
|
status: "rejected",
|
||||||
|
output: {},
|
||||||
|
diagnostics: [{ message: error instanceof Error ? error.message : String(error) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
schemaVersion: "cultnet.operation_response.v0",
|
||||||
|
messageId: request.messageId,
|
||||||
|
serviceId: pluginId,
|
||||||
|
operation: request.operation,
|
||||||
|
status: abiResponse.status,
|
||||||
|
payloadSchema: abiResponse.schema,
|
||||||
|
payloadEncoding: "messagepack-base64",
|
||||||
|
payload: Buffer.from(encode(abiResponse)).toString("base64"),
|
||||||
|
diagnostics: (abiResponse.diagnostics || []).map(item => item.message),
|
||||||
|
sourceRuntimeId: runtimeId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdout.write(`${JSON.stringify({ schema: "gamecult.eve.plugin_endpoint.v1", pluginId, endpoint: server.endpoint })}\n`);
|
||||||
|
const stop = async () => {
|
||||||
|
await server.close();
|
||||||
|
process.exit(0);
|
||||||
|
};
|
||||||
|
process.once("SIGINT", stop);
|
||||||
|
process.once("SIGTERM", stop);
|
||||||
|
await new Promise(() => {});
|
||||||
102
plugins/eve-plugin-fields/src/plugin.mjs
Normal file
102
plugins/eve-plugin-fields/src/plugin.mjs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
export const pluginId = "fields.surface";
|
||||||
|
export const operations = ["describe", "validate", "project", "measure"];
|
||||||
|
export const componentKinds = ["field.surface2d", "gravity.surface"];
|
||||||
|
|
||||||
|
export function dispatch(request) {
|
||||||
|
if (request?.schema !== "gamecult.eve.plugin_abi.request.v1") throw new Error("Unsupported request schema.");
|
||||||
|
if (request.pluginId !== pluginId) throw new Error(`Expected plugin ${pluginId}.`);
|
||||||
|
if (!operations.includes(request.operation)) throw new Error(`Unsupported operation ${request.operation}.`);
|
||||||
|
switch (request.operation) {
|
||||||
|
case "describe": return describe();
|
||||||
|
case "validate": return validate(request.input || {});
|
||||||
|
case "project": return project(request.input || {});
|
||||||
|
case "measure": return measure(request.input || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describe() {
|
||||||
|
return {
|
||||||
|
pluginId,
|
||||||
|
componentKinds,
|
||||||
|
commands: [],
|
||||||
|
capabilities: ["field.surface2d", "gravity.surface", "field.scalar", "field.vector", "field.objects"],
|
||||||
|
operations,
|
||||||
|
stateAuthority: "provider-owns-field-documents",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validate(input) {
|
||||||
|
const requested = Array.isArray(input.componentKinds) ? input.componentKinds : [];
|
||||||
|
const document = input.document || {};
|
||||||
|
const diagnostics = [];
|
||||||
|
if (document.schema && document.schema !== "gamecult.fields.surface2d.v1") diagnostics.push(`Unsupported field schema ${document.schema}.`);
|
||||||
|
if (document.bounds && !validBounds(document.bounds)) diagnostics.push("Field bounds must have increasing finite axes.");
|
||||||
|
return {
|
||||||
|
acceptedComponentKinds: requested.filter(kind => componentKinds.includes(kind)),
|
||||||
|
rejectedComponentKinds: requested.filter(kind => !componentKinds.includes(kind)),
|
||||||
|
validDocument: diagnostics.length === 0,
|
||||||
|
diagnostics,
|
||||||
|
requiredCapabilities: ["field.surface2d"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function project(input) {
|
||||||
|
const document = requiredObject(input.document, "document");
|
||||||
|
if (document.schema !== "gamecult.fields.surface2d.v1") throw new Error("document must use gamecult.fields.surface2d.v1.");
|
||||||
|
if (!validBounds(document.bounds)) throw new Error("document bounds are invalid.");
|
||||||
|
const channels = Array.isArray(document.channels) ? document.channels.map(normalizeChannel) : [];
|
||||||
|
return {
|
||||||
|
schema: "gamecult.fields.projection.v1",
|
||||||
|
projectionKind: "semantic-field-surface",
|
||||||
|
ownedComponentKinds: componentKinds,
|
||||||
|
fieldId: requiredString(document.fieldId, "fieldId"),
|
||||||
|
bounds: { ...document.bounds },
|
||||||
|
channels,
|
||||||
|
objects: document.objects ? normalizeDocumentRef(document.objects, "objects") : null,
|
||||||
|
nativeProjectionAuthority: "runtime-adapter",
|
||||||
|
providerStateMutated: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function measure(input) {
|
||||||
|
const projection = project(input);
|
||||||
|
return {
|
||||||
|
measurementKind: "field-domain-metrics",
|
||||||
|
width: projection.bounds.maxX - projection.bounds.minX,
|
||||||
|
height: projection.bounds.maxY - projection.bounds.minY,
|
||||||
|
channelCount: projection.channels.length,
|
||||||
|
preservesProviderAuthority: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeChannel(channel, index) {
|
||||||
|
const value = requiredObject(channel, `channels[${index}]`);
|
||||||
|
const kind = requiredString(value.kind, `channels[${index}].kind`);
|
||||||
|
if (!["scalar", "vector", "color", "mask"].includes(kind)) throw new Error(`Unsupported field channel kind ${kind}.`);
|
||||||
|
return {
|
||||||
|
channelId: requiredString(value.channelId, `channels[${index}].channelId`),
|
||||||
|
kind,
|
||||||
|
...normalizeDocumentRef(value, `channels[${index}]`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDocumentRef(value, field) {
|
||||||
|
return {
|
||||||
|
documentId: requiredString(value.documentId, `${field}.documentId`),
|
||||||
|
schemaId: requiredString(value.schemaId, `${field}.schemaId`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function validBounds(value) {
|
||||||
|
return value && [value.minX, value.minY, value.maxX, value.maxY].every(Number.isFinite) && value.minX < value.maxX && value.minY < value.maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredObject(value, field) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} is required.`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredString(value, field) {
|
||||||
|
if (typeof value !== "string" || !value.trim()) throw new Error(`${field} is required.`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
27
plugins/eve-plugin-fields/src/sidecar.mjs
Normal file
27
plugins/eve-plugin-fields/src/sidecar.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import readline from "node:readline";
|
||||||
|
import { dispatch, pluginId } from "./plugin.mjs";
|
||||||
|
|
||||||
|
readline.createInterface({ input: process.stdin }).on("line", line => {
|
||||||
|
if (!line.trim()) return;
|
||||||
|
let request;
|
||||||
|
try {
|
||||||
|
request = JSON.parse(line.replace(/^\uFEFF/, ""));
|
||||||
|
respond(request, dispatch(request));
|
||||||
|
} catch (error) {
|
||||||
|
respond(request, {}, "rejected", error instanceof Error ? error.message : String(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function respond(request, output, status = "accepted", message) {
|
||||||
|
process.stdout.write(`${JSON.stringify({
|
||||||
|
schema: "gamecult.eve.plugin_abi.response.v1",
|
||||||
|
pluginId,
|
||||||
|
operation: request?.operation || "unknown",
|
||||||
|
requestId: request?.requestId || "unknown",
|
||||||
|
status,
|
||||||
|
output,
|
||||||
|
...(message ? { diagnostics: [{ message }] } : {}),
|
||||||
|
})}\n`);
|
||||||
|
}
|
||||||
41
plugins/eve-plugin-fields/test/plugin.test.mjs
Normal file
41
plugins/eve-plugin-fields/test/plugin.test.mjs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { dispatch } from "../src/plugin.mjs";
|
||||||
|
|
||||||
|
function request(operation, input = {}) {
|
||||||
|
return dispatch({ schema: "gamecult.eve.plugin_abi.request.v1", pluginId: "fields.surface", operation, requestId: operation, input });
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
schema: "gamecult.fields.surface2d.v1",
|
||||||
|
fieldId: "fixture.field",
|
||||||
|
bounds: { minX: -10, minY: -5, maxX: 10, maxY: 5 },
|
||||||
|
channels: [
|
||||||
|
{ channelId: "height", kind: "scalar", documentId: "cultmesh://fixture/height", schemaId: "gamecult.fields.scalar.v1" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
test("projects provider-owned field documents without taking native projection authority", () => {
|
||||||
|
const result = request("project", { document });
|
||||||
|
assert.equal(result.schema, "gamecult.fields.projection.v1");
|
||||||
|
assert.equal(result.projectionKind, "semantic-field-surface");
|
||||||
|
assert.equal(result.channels[0].documentId, "cultmesh://fixture/height");
|
||||||
|
assert.equal(result.nativeProjectionAuthority, "runtime-adapter");
|
||||||
|
assert.equal(result.providerStateMutated, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects components and schemas outside field ownership", () => {
|
||||||
|
const result = request("validate", { componentKinds: ["field.surface2d", "world.scene3d"], document });
|
||||||
|
assert.deepEqual(result.acceptedComponentKinds, ["field.surface2d"]);
|
||||||
|
assert.deepEqual(result.rejectedComponentKinds, ["world.scene3d"]);
|
||||||
|
assert.equal(result.validDocument, true);
|
||||||
|
assert.equal(request("validate", { componentKinds: ["field.surface2d"], document: { ...document, schema: "provider.private.v1" } }).validDocument, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("measures field domains without mutating provider state", () => {
|
||||||
|
const result = request("measure", { document });
|
||||||
|
assert.equal(result.width, 20);
|
||||||
|
assert.equal(result.height, 10);
|
||||||
|
assert.equal(result.channelCount, 1);
|
||||||
|
assert.equal(result.preservesProviderAuthority, true);
|
||||||
|
});
|
||||||
|
|
@ -8,8 +8,26 @@ import { decode, encode } from "@msgpack/msgpack";
|
||||||
import { invokeCultNetOperation } from "cultnet-ts";
|
import { invokeCultNetOperation } from "cultnet-ts";
|
||||||
|
|
||||||
const root = path.resolve(import.meta.dirname, "..");
|
const root = path.resolve(import.meta.dirname, "..");
|
||||||
const fixture = JSON.parse(fs.readFileSync(path.join(root, "plugins/eve-plugin-tex/plugin-abi-fixture.json"), "utf8"));
|
const target = process.argv[2] || "tex";
|
||||||
const child = spawn(process.execPath, [path.join(root, "plugins/eve-plugin-tex/src/cultnet-sidecar.mjs")], { cwd: root, stdio: ["ignore", "pipe", "inherit"] });
|
const targets = {
|
||||||
|
tex: {
|
||||||
|
directory: "eve-plugin-tex",
|
||||||
|
artifactDirectory: "tex-math",
|
||||||
|
witnessId: "tex.math.owner-sidecar",
|
||||||
|
authority: "plugin-sidecar-owns-tex-semantics-provider-retains-source-and-command-acceptance",
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
directory: "eve-plugin-fields",
|
||||||
|
artifactDirectory: "fields-surface",
|
||||||
|
witnessId: "fields.surface.owner-sidecar",
|
||||||
|
authority: "plugin-sidecar-owns-field-semantics-provider-retains-field-state-and-runtime-retains-native-projection",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const config = targets[target];
|
||||||
|
if (!config) throw new Error(`Unknown plugin witness target '${target}'.`);
|
||||||
|
const pluginRoot = path.join(root, "plugins", config.directory);
|
||||||
|
const fixture = JSON.parse(fs.readFileSync(path.join(pluginRoot, "plugin-abi-fixture.json"), "utf8"));
|
||||||
|
const child = spawn(process.execPath, [path.join(pluginRoot, "src/cultnet-sidecar.mjs")], { cwd: root, stdio: ["ignore", "pipe", "inherit"] });
|
||||||
const responses = readline.createInterface({ input: child.stdout })[Symbol.asyncIterator]();
|
const responses = readline.createInterface({ input: child.stdout })[Symbol.asyncIterator]();
|
||||||
const endpointLine = await responses.next();
|
const endpointLine = await responses.next();
|
||||||
assert.equal(endpointLine.done, false);
|
assert.equal(endpointLine.done, false);
|
||||||
|
|
@ -22,7 +40,7 @@ const operationWitnesses = [];
|
||||||
try {
|
try {
|
||||||
for (let index = 0; index < fixture.operations.length; index += 1) {
|
for (let index = 0; index < fixture.operations.length; index += 1) {
|
||||||
const operation = fixture.operations[index];
|
const operation = fixture.operations[index];
|
||||||
const requestId = `tex-witness-${index}`;
|
const requestId = `${target}-witness-${index}`;
|
||||||
const operationStartedAt = performance.now();
|
const operationStartedAt = performance.now();
|
||||||
const abiRequest = { schema: fixture.requestSchema, pluginId: fixture.pluginId, operation: operation.operation, requestId, input: operation.input };
|
const abiRequest = { schema: fixture.requestSchema, pluginId: fixture.pluginId, operation: operation.operation, requestId, input: operation.input };
|
||||||
const envelope = await invokeCultNetOperation(endpoint, {
|
const envelope = await invokeCultNetOperation(endpoint, {
|
||||||
|
|
@ -54,23 +72,23 @@ try {
|
||||||
child.kill();
|
child.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputDir = path.join(root, "artifacts", "tex-math");
|
const outputDir = path.join(root, "artifacts", config.artifactDirectory);
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
const witness = {
|
const witness = {
|
||||||
schema: "gamecult.eve.plugin_witness.v1",
|
schema: "gamecult.eve.plugin_witness.v1",
|
||||||
witnessId: "tex.math.owner-sidecar",
|
witnessId: config.witnessId,
|
||||||
pluginId: "tex.math",
|
pluginId: fixture.pluginId,
|
||||||
ownerRepo: "EvePlugins",
|
ownerRepo: "EvePlugins",
|
||||||
status: "pass",
|
status: "pass",
|
||||||
transport: "cultnet-operation-v0+rudp",
|
transport: "cultnet-operation-v0+rudp",
|
||||||
generatedAtUtc: new Date().toISOString(),
|
generatedAtUtc: new Date().toISOString(),
|
||||||
durationMs: Number((performance.now() - startedAt).toFixed(3)),
|
durationMs: Number((performance.now() - startedAt).toFixed(3)),
|
||||||
operations: operationWitnesses,
|
operations: operationWitnesses,
|
||||||
advertisementPath: "plugins/eve-plugin-tex/advertisement.json",
|
advertisementPath: `plugins/${config.directory}/advertisement.json`,
|
||||||
fixturePath: "plugins/eve-plugin-tex/plugin-abi-fixture.json",
|
fixturePath: `plugins/${config.directory}/plugin-abi-fixture.json`,
|
||||||
executable: { artifact: "plugins/eve-plugin-tex/src/cultnet-sidecar.mjs", command: "node" },
|
executable: { artifact: `plugins/${config.directory}/src/cultnet-sidecar.mjs`, command: "node" },
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
authority: "plugin-sidecar-owns-tex-semantics-provider-retains-source-and-command-acceptance",
|
authority: config.authority,
|
||||||
};
|
};
|
||||||
fs.writeFileSync(path.join(outputDir, "runtime-witness.json"), `${JSON.stringify(witness, null, 2)}\n`);
|
fs.writeFileSync(path.join(outputDir, "runtime-witness.json"), `${JSON.stringify(witness, null, 2)}\n`);
|
||||||
console.log(path.join(outputDir, "runtime-witness.json"));
|
console.log(path.join(outputDir, "runtime-witness.json"));
|
||||||
|
|
|
||||||
Reference in a new issue