From c232f1453ebfb90d31a5efc8ee450e02ede76cb6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 02:32:23 +0100 Subject: [PATCH] Declare runtime world surface lowering targets --- docs/eve-migration-roadmap.md | 10 +++ docs/parity-testing-harness.md | 8 ++- docs/world-state-lowering.md | 7 ++ .../README.md | 6 ++ .../eve-runtime-capability.json | 14 ++++ ...cult.eve.runtime_capability.v1.schema.json | 49 ++++++++++++++ scripts/run-conformance-consumer-smoke.ps1 | 1 + ...ntime-owner-conformance-consumer-smoke.ps1 | 6 ++ tools/conformance/consume-export.mjs | 64 +++++++++++++++++++ tools/parity/parity-manifest.json | 28 ++++++++ tools/parity/run-parity.mjs | 58 +++++++++++++++++ web/eve-runtime-capability.json | 14 ++++ 12 files changed, 262 insertions(+), 3 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 889e4f7..d695603 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -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, diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index 6395fd2..67ce540 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -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 diff --git a/docs/world-state-lowering.md b/docs/world-state-lowering.md index 8efd0fb..6e5e7c7 100644 --- a/docs/world-state-lowering.md +++ b/docs/world-state-lowering.md @@ -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 diff --git a/packages/org.gamecult.eve.unity-uitoolkit/README.md b/packages/org.gamecult.eve.unity-uitoolkit/README.md index 1c5ea8c..83b8bc2 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/README.md +++ b/packages/org.gamecult.eve.unity-uitoolkit/README.md @@ -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. diff --git a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json index a01759a..5d48209 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json +++ b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json @@ -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", diff --git a/schemas/gamecult.eve.runtime_capability.v1.schema.json b/schemas/gamecult.eve.runtime_capability.v1.schema.json index 06b04d7..601fd4d 100644 --- a/schemas/gamecult.eve.runtime_capability.v1.schema.json +++ b/schemas/gamecult.eve.runtime_capability.v1.schema.json @@ -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": [ diff --git a/scripts/run-conformance-consumer-smoke.ps1 b/scripts/run-conformance-consumer-smoke.ps1 index 667788f..b5f70f6 100644 --- a/scripts/run-conformance-consumer-smoke.ps1 +++ b/scripts/run-conformance-consumer-smoke.ps1 @@ -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) { diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 0ab61fc..78d1324 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -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 ` diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs index be367d3..a92d9b9 100644 --- a/tools/conformance/consume-export.mjs +++ b/tools/conformance/consume-export.mjs @@ -34,6 +34,8 @@ if (!exportDirectory) { " --expect-provider-handoff ", " --expect-runtime-status ", " --expect-runtime-feature ", + " --expect-runtime-world-target ", + " --expect-runtime-world-field ", " --expect-runtime-handoff ", " --expect-runtime-command-schema ", " --expect-runtime-capture-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 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 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(":")) { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 98e7788..148b315 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -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", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 0c45811..a24d3cc 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -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({ diff --git a/web/eve-runtime-capability.json b/web/eve-runtime-capability.json index 20285ad..a650f0e 100644 --- a/web/eve-runtime-capability.json +++ b/web/eve-runtime-capability.json @@ -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",