Declare runtime world surface lowering targets

This commit is contained in:
Meta Vens 2026-07-09 02:32:23 +01:00
commit c232f1453e
12 changed files with 262 additions and 3 deletions

View file

@ -280,6 +280,16 @@ Exit criteria:
Recently cut:
- Runtime capability manifests now declare world-surface lowering targets.
`web/eve-runtime-capability.json` claims `web-reference`, and
`packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json`
claims `unity-uitoolkit`, each with supported world surface kinds,
projection kinds, support level, evidence paths, and the rule that runtimes
lower provider-owned world state without owning it. Parity compares those
claims against the ledger, exports them through runtime records and
`packs/runtime.json`, the runtime-owner smoke asserts them, and the generic
conformance smoke now reports the advertised but unclaimed `unity-scene`
lowering target as an explicit capability gap.
- Aetheria interactive world surfaces now carry a structured provider-owned
`worldInteraction` contract. The provider advertisement names projection
kind, provider state schemas, daemon command boundary, receipt schema,

View file

@ -114,13 +114,15 @@ are not plugin runtime state.
The runtime conformance pack is evidence-shaped, not fixture-shaped. Its
`packs/runtime.json` document includes `runtimeTargets` so runtime repos can
consume status, supported features, plugin support, command schema, lifecycle,
capture status, and missing evidence without reading Eve's local parity
capture status, world-surface lowering claims, and missing evidence without reading Eve's local parity
manifest. The runtime-owner consumer smoke asserts EveUnity lifecycle stage
status and pending release/test/capture proofs from the exported runtime record,
plus release contract fields such as package name, artifact kind, tag pattern,
and package root, plus Unity EditMode test runner contract fields such as
runner script, package name, test assembly, and platform. Split blockers stay
machine-readable instead of hiding in handoff prose.
runner script, package name, test assembly, and platform. It also asserts that
web and Unity UI Toolkit claim their provider-advertised world-surface lowering
targets. Split blockers stay machine-readable instead of hiding in handoff
prose.
After generating the parity report, `run-parity-harness.ps1` copies
`artifacts/conformance/latest` into

View file

@ -93,6 +93,13 @@ Conformance scenarios can require `worldSurfaces[]` entries and the parity
harness checks them against the advertisement. This makes an interactive world
surface portable without giving Eve or a runtime private Aetheria knowledge.
Runtime capability manifests answer the other half with
`worldSurfaceLowering[]`. Each claim names a `targetId`, supported surface
kinds, projection kinds, support level, evidence paths, and the ownership rule
that the runtime lowers provider state without becoming provider truth. If a
provider advertises a lowering target and no runtime claims it, conformance
emits a capability gap instead of quietly pretending the engine exists.
## Quality Tiers
A low-end or debugging renderer may lower a world surface into labels, simple

View file

@ -55,6 +55,12 @@ under `artifacts/aetheria-unity-editmode/{stamp}`, and temporarily adds
Unity package consumption proof. CultLib still owns the .NET/NuGet dependency
story for its assemblies.
The manifest also declares `worldSurfaceLowering` target `unity-uitoolkit`.
That means this package can lower provider-advertised `interactive-world` and
`interactive-world-editor` surfaces as semantic UI Toolkit command surfaces.
It does not claim Unity scene/world simulation ownership; Aetheria still owns
world state, assets, command acceptance, and receipts.
Those lifecycle claims are validated by the parity harness. A missing evidence
path is a runtime capability error, not a README footnote.

View file

@ -46,6 +46,20 @@
"emitter": "EveUiToolkitSurfaceLowerer.EmitCommand",
"clientId": "unity-uitoolkit"
},
"worldSurfaceLowering": [
{
"targetId": "unity-uitoolkit",
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
"supportLevel": "ui-toolkit-semantic-command-surface",
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
"evidencePaths": [
"packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs",
"packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs",
"web/fixtures/aetheria.provider-advertisement.json"
]
}
],
"lifecycle": {
"release": {
"ownerRepo": "EveUnity",

View file

@ -124,6 +124,55 @@
},
"additionalProperties": true
},
"worldSurfaceLowering": {
"type": "array",
"items": {
"type": "object",
"required": [
"targetId",
"surfaceKinds",
"projectionKinds",
"supportLevel",
"ownership"
],
"properties": {
"targetId": {
"type": "string",
"minLength": 1
},
"surfaceKinds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"projectionKinds": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"supportLevel": {
"type": "string",
"minLength": 1
},
"ownership": {
"type": "string",
"minLength": 1
},
"evidencePaths": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": true
}
},
"lifecycle": {
"type": "object",
"required": [

View file

@ -33,6 +33,7 @@ try {
node .\tools\conformance\consume-export.mjs $consumerExport `
--expect-capability-matrix `
--expect-capability-gap runtime:Fensalir:direct2d:capture:missing `
--expect-capability-gap "runtime:Eve:world-surface-lowering:provider:aetheria:surface:aetheria.daemon.game:world-lowering-target:unity-scene:missing-runtime" `
--expect-capability-gap "split-target:EveUnity:EveUnity:proof:Tagged UPM release" `
--expect-conformance-handoff
if ($LASTEXITCODE -ne 0) {

View file

@ -48,6 +48,12 @@ node $consumerScript $consumerExport `
--expect-runtime-feature windows-flutter:embeddedDocuments `
--expect-runtime-feature unity-uitoolkit:embeddedDocuments `
--expect-runtime-feature direct2d:embeddedDocuments `
--expect-runtime-world-target web:web-reference `
--expect-runtime-world-field web:web-reference:supportLevel:reference-dom-canvas-command-boundary `
--expect-runtime-world-field web:web-reference:ownership:runtime-lowers-provider-world-surface-without-owning-world-state `
--expect-runtime-world-target unity-uitoolkit:unity-uitoolkit `
--expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:supportLevel:ui-toolkit-semantic-command-surface `
--expect-runtime-world-field unity-uitoolkit:unity-uitoolkit:ownership:runtime-lowers-provider-world-surface-without-owning-world-state `
--expect-runtime-handoff unity-uitoolkit `
--expect-runtime-command-schema web:gamecult.eve.command.v1 `
--expect-runtime-command-schema windows-flutter:gamecult.eve.command.v1 `

View file

@ -34,6 +34,8 @@ if (!exportDirectory) {
" --expect-provider-handoff <providerId>",
" --expect-runtime-status <runtimeId:status>",
" --expect-runtime-feature <runtimeId:feature>",
" --expect-runtime-world-target <runtimeId:targetId>",
" --expect-runtime-world-field <runtimeId:targetId:field.path:value>",
" --expect-runtime-handoff <runtimeId>",
" --expect-runtime-command-schema <runtimeId:schema>",
" --expect-runtime-capture-status <runtimeId:status>",
@ -311,6 +313,34 @@ function validateIndex(index, directory, expectations, errors) {
errors.push(`runtimes:${expectation.runtimeId}:feature:${expectation.feature}:missing`);
}
}
for (const expectation of expectations.runtimeWorldTargets) {
const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId);
if (!runtime) {
errors.push(`runtimes:${expectation.runtimeId}:missing`);
continue;
}
const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : [];
if (!worldTargets.some(candidate => candidate.targetId === expectation.targetId)) {
errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`);
}
}
for (const expectation of expectations.runtimeWorldFields) {
const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId);
if (!runtime) {
errors.push(`runtimes:${expectation.runtimeId}:missing`);
continue;
}
const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : [];
const target = worldTargets.find(candidate => candidate.targetId === expectation.targetId);
if (!target) {
errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`);
continue;
}
const actual = readNestedField(target, expectation.fieldPath);
if (actual !== expectation.value) {
errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`);
}
}
for (const expectation of expectations.runtimeCommandSchemas) {
const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId);
if (!runtime) {
@ -539,6 +569,8 @@ function parseArguments(args) {
runtimes: [],
runtimeStatuses: [],
runtimeFeatures: [],
runtimeWorldTargets: [],
runtimeWorldFields: [],
runtimeHandoffs: [],
runtimeCommandSchemas: [],
runtimeCaptureStatuses: [],
@ -577,6 +609,8 @@ function parseArguments(args) {
["--expect-provider-handoff", expectations.providerHandoffs],
["--expect-runtime-status", expectations.runtimeStatuses],
["--expect-runtime-feature", expectations.runtimeFeatures],
["--expect-runtime-world-target", expectations.runtimeWorldTargets],
["--expect-runtime-world-field", expectations.runtimeWorldFields],
["--expect-runtime-handoff", expectations.runtimeHandoffs],
["--expect-runtime-command-schema", expectations.runtimeCommandSchemas],
["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses],
@ -639,6 +673,10 @@ function parseArguments(args) {
target.push(parseRuntimeExpectation(value, "status"));
} else if (option === "--expect-runtime-feature") {
target.push(parseRuntimeExpectation(value, "feature"));
} else if (option === "--expect-runtime-world-target") {
target.push(parseRuntimeWorldTargetExpectation(value));
} else if (option === "--expect-runtime-world-field") {
target.push(parseRuntimeWorldFieldExpectation(value));
} else if (option === "--expect-runtime-handoff") {
target.push(value);
} else if (option === "--expect-runtime-command-schema") {
@ -742,6 +780,32 @@ function parseRuntimeExpectation(value, field) {
};
}
function parseRuntimeWorldTargetExpectation(value) {
const separator = value.indexOf(":");
if (separator <= 0 || separator === value.length - 1) {
console.error(`Expected runtime world target in <runtimeId:targetId> form, got: ${value}`);
process.exit(2);
}
return {
runtimeId: value.slice(0, separator),
targetId: value.slice(separator + 1),
};
}
function parseRuntimeWorldFieldExpectation(value) {
const parts = value.split(":");
if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) {
console.error(`Expected runtime world field in <runtimeId:targetId:field.path:value> form, got: ${value}`);
process.exit(2);
}
return {
runtimeId: parts[0],
targetId: parts[1],
fieldPath: parts[2],
value: parts.slice(3).join(":"),
};
}
function parseRuntimeLifecycleFieldExpectation(value) {
const parts = value.split(":");
if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) {

View file

@ -622,6 +622,20 @@
"capabilities": ["embed.tex"]
}
],
"worldSurfaceLowering": [
{
"targetId": "web-reference",
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
"supportLevel": "reference-dom-canvas-command-boundary",
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
"evidencePaths": [
"web/surface.js",
"packages/eve-browser-lowering/src/index.ts",
"web/fixtures/aetheria.provider-advertisement.json"
]
}
],
"expectedSourceSymbols": [
{
"path": "web/surface.js",
@ -947,6 +961,20 @@
"reason": "Unity UI Toolkit currently preserves embed.tex as generic Eve structure; no TeX projection adapter exists in this runtime."
}
],
"worldSurfaceLowering": [
{
"targetId": "unity-uitoolkit",
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
"supportLevel": "ui-toolkit-semantic-command-surface",
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
"evidencePaths": [
"packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs",
"packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs",
"web/fixtures/aetheria.provider-advertisement.json"
]
}
],
"expectedPaths": [
"packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs",
"packages/org.gamecult.eve.unity-uitoolkit/Runtime/IEveUiToolkitPluginProjectionAdapter.cs",

View file

@ -345,6 +345,7 @@ async function evaluateRuntime(runtime, fixtureResults) {
supportedPlugins,
unsupportedPlugins: runtime.unsupportedPlugins || [],
unsupportedPluginNotes,
worldSurfaceLowering: runtime.worldSurfaceLowering || [],
missingRequiredFeatures,
commandTransportSmoke: runtime.commandTransportSmoke || null,
commandTransportSmokeErrors,
@ -1134,6 +1135,7 @@ async function validateRuntimeCapabilityManifest(runtime) {
const supportedFeatures = document.supportedFeatures || [];
const supportedPlugins = document.supportedPlugins || [];
const unsupportedPlugins = document.unsupportedPlugins || [];
const worldSurfaceLowering = document.worldSurfaceLowering || [];
const commandTransport = document.commandTransport || {};
const lifecycle = document.lifecycle || {};
@ -1148,6 +1150,11 @@ async function validateRuntimeCapabilityManifest(runtime) {
unsupportedPlugins,
`${capabilityManifest.manifestPath}:unsupportedPlugins`,
));
errors.push(...compareWorldSurfaceLoweringClaims(
runtime.worldSurfaceLowering || [],
worldSurfaceLowering,
`${capabilityManifest.manifestPath}:worldSurfaceLowering`,
));
const expectedCommandSchema = runtime.commandTransportSmoke?.schema || "";
if (expectedCommandSchema && commandTransport.schema !== expectedCommandSchema) {
@ -1175,6 +1182,32 @@ async function validateRuntimeCapabilityManifest(runtime) {
return errors;
}
function compareWorldSurfaceLoweringClaims(expectedClaims, actualClaims, label) {
const errors = [];
const actualByTarget = new Map((actualClaims || []).map(claim => [claim.targetId, claim]));
for (const expected of expectedClaims || []) {
const actual = actualByTarget.get(expected.targetId);
if (!actual) {
errors.push(`${label}:${expected.targetId}:missing`);
continue;
}
for (const key of ["supportLevel", "ownership"]) {
if ((expected[key] || "") !== (actual[key] || "")) {
errors.push(`${label}:${expected.targetId}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`);
}
}
errors.push(...missingMembers(expected.surfaceKinds || [], actual.surfaceKinds || [], `${label}:${expected.targetId}.surfaceKinds`));
errors.push(...missingMembers(expected.projectionKinds || [], actual.projectionKinds || [], `${label}:${expected.targetId}.projectionKinds`));
errors.push(...missingMembers(expected.evidencePaths || [], actual.evidencePaths || [], `${label}:${expected.targetId}.evidencePaths`));
for (const evidencePath of actual.evidencePaths || []) {
if (!existsSync(path.join(repoRoot, evidencePath))) {
errors.push(`${label}:${expected.targetId}.evidencePath:${evidencePath}:missing`);
}
}
}
return errors;
}
function compareRuntimeLifecycleClaims(expectedLifecycle, actualLifecycle, manifestPath) {
const errors = [];
for (const stage of ["release", "test", "capture"]) {
@ -1516,6 +1549,7 @@ function buildConformanceExport(report) {
supportedFeatures: runtime.supportedFeatures,
supportedPlugins: runtime.supportedPlugins,
unsupportedPlugins: runtime.unsupportedPlugins,
worldSurfaceLowering: runtime.worldSurfaceLowering,
requiredFixtures: runtime.requiredFixtures,
pluginFixtures: runtime.pluginFixtures,
capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "",
@ -1600,6 +1634,7 @@ function buildConformanceExport(report) {
commandTransportSchema: runtime.commandTransportSmoke?.schema || "",
captureStatus: runtime.capture?.status || "",
lifecycle: runtime.lifecycle,
worldSurfaceLowering: runtime.worldSurfaceLowering || [],
})),
splitTargets: report.splitTargets || [],
};
@ -1647,6 +1682,7 @@ function buildCapabilityMatrix(report) {
supportedFeatures: runtime.supportedFeatures || [],
supportedPlugins: runtime.supportedPlugins || [],
unsupportedPlugins: runtime.unsupportedPlugins || [],
worldSurfaceLowering: runtime.worldSurfaceLowering || [],
commandTransportSchema: runtime.commandTransportSmoke?.schema || "",
captureStatus: runtime.capture?.status || "",
splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""),
@ -1777,6 +1813,28 @@ function collectCapabilityGaps(report) {
}
}
const worldLoweringTargets = new Set();
for (const runtime of report.runtimes || []) {
for (const claim of runtime.worldSurfaceLowering || []) {
if (claim.targetId) worldLoweringTargets.add(claim.targetId);
}
}
for (const provider of report.providers || []) {
for (const surface of provider.surfaceContracts || []) {
for (const targetId of surface.worldInteraction?.loweringTargets || []) {
if (!worldLoweringTargets.has(targetId)) {
addGap({
kind: "runtime",
ownerRepo: "Eve",
subjectId: "world-surface-lowering",
gap: `provider:${provider.providerId}:surface:${surface.surfaceId}:world-lowering-target:${targetId}:missing-runtime`,
severity: "blocker",
});
}
}
}
}
for (const target of report.splitTargets || []) {
for (const blocker of target.blockers || []) {
addGap({

View file

@ -48,6 +48,20 @@
"emitter": "createEveCommandIntent",
"clientId": "browser.reference"
},
"worldSurfaceLowering": [
{
"targetId": "web-reference",
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
"supportLevel": "reference-dom-canvas-command-boundary",
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
"evidencePaths": [
"web/surface.js",
"packages/eve-browser-lowering/src/index.ts",
"web/fixtures/aetheria.provider-advertisement.json"
]
}
],
"lifecycle": {
"release": {
"ownerRepo": "Eve",