From bcfc261efd47c6a31c6b00aa9af851408e1eef45 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 8 Jul 2026 23:46:42 +0100 Subject: [PATCH] Validate Eve provider advertisements --- docs/eve-migration-roadmap.md | 20 ++- ....eve.provider_advertisement.v1.schema.json | 166 ++++++++++++++++++ tools/parity/parity-manifest.json | 49 +++++- tools/parity/run-parity.mjs | 102 ++++++++++- web/fixtures/aetheria-world-surface.json | 137 +++++++++++++++ .../aetheria.provider-advertisement.json | 157 +++++++++++++++++ web/surface.js | 8 + 7 files changed, 630 insertions(+), 9 deletions(-) create mode 100644 schemas/gamecult.eve.provider_advertisement.v1.schema.json create mode 100644 web/fixtures/aetheria-world-surface.json create mode 100644 web/fixtures/aetheria.provider-advertisement.json diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 1a44bad..2c652ff 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -136,6 +136,7 @@ Work: - Add explicit authority/staleness witness UI in the browser reference for stale, missing, predicted, denied, accepted, and reconciled values. - Add provider advertisement fixture checks for local and live discovery paths. + First cut is in place for Repixelizer and Aetheria provider advertisements. - Add plugin advertisement fixture checks before adding more plugin semantics. Exit criteria: @@ -273,14 +274,21 @@ Exit criteria: ## Active Work Queue -1. Add provider advertisement checks to the parity harness. -2. Add command descriptor checks to fixtures and runtime source-symbol +1. Add command descriptor checks to fixtures and runtime source-symbol expectations. -3. Add authority/staleness witness fixture to browser reference. -4. Add conformance-pack metadata files beside fixtures. -5. Add a Direct2D adapter spike or explicitly demote Direct2D until Fensalir owns +2. Add authority/staleness witness fixture to browser reference. +3. Add conformance-pack metadata files beside fixtures. +4. Add a Direct2D adapter spike or explicitly demote Direct2D until Fensalir owns the body. -6. Add split-readiness reports for EveFlutter and EveUnity. +5. Add split-readiness reports for EveFlutter and EveUnity. + +Recently cut: + +- Provider advertisement validation now runs in the parity harness. +- Repixelizer and Aetheria provider advertisements are checked against + `gamecult.eve.provider_advertisement.v1`. +- Aetheria has a minimal provider-owned interactive world fixture in Eve so the + browser oracle can prove the surface path without owning Aetheria truth. ## Stop Conditions diff --git a/schemas/gamecult.eve.provider_advertisement.v1.schema.json b/schemas/gamecult.eve.provider_advertisement.v1.schema.json new file mode 100644 index 0000000..fc9ca94 --- /dev/null +++ b/schemas/gamecult.eve.provider_advertisement.v1.schema.json @@ -0,0 +1,166 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "gamecult.eve.provider_advertisement.v1", + "title": "Eve Provider Advertisement", + "type": "object", + "required": [ + "schema", + "providerId", + "serviceId", + "verseId", + "title", + "kind", + "freshness", + "schemas", + "witnesses", + "surfaces", + "commands" + ], + "properties": { + "schema": { + "const": "gamecult.eve.provider_advertisement.v1" + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "serviceId": { + "type": "string", + "minLength": 1 + }, + "verseId": { + "type": "string", + "minLength": 1 + }, + "rootVerse": { + "type": "string", + "minLength": 1 + }, + "canonicalService": { + "type": "string" + }, + "locatedService": { + "type": "string" + }, + "cultMeshAddress": { + "type": "string" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "updatedAt": { + "type": "string" + }, + "updatedAtUtc": { + "type": "string" + }, + "freshness": { + "type": "object", + "required": ["state"], + "properties": { + "state": { + "type": "string", + "minLength": 1 + }, + "lastSeenAt": { + "type": "string" + }, + "lastSeenAtUtc": { + "type": "string" + }, + "maxAgeMs": { + "type": "number" + } + }, + "additionalProperties": true + }, + "schemas": { + "type": "array", + "items": { + "type": ["string", "object"] + } + }, + "witnesses": { + "type": "array", + "items": { + "type": "object", + "required": ["kind"], + "properties": { + "kind": { + "type": "string", + "minLength": 1 + }, + "ref": { + "type": "string" + }, + "path": { + "type": "string" + }, + "summary": { + "type": "string" + } + }, + "additionalProperties": true + } + }, + "surfaces": { + "type": "array", + "items": { + "type": "object", + "required": ["surfaceId", "schema", "transport"], + "properties": { + "surfaceId": { + "type": "string", + "minLength": 1 + }, + "schema": { + "type": "string", + "minLength": 1 + }, + "key": { + "type": "string" + }, + "url": { + "type": "string" + }, + "transport": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + } + }, + "commands": { + "type": "array", + "items": { + "type": "object", + "required": ["command", "transport"], + "properties": { + "command": { + "type": "string", + "minLength": 1 + }, + "transport": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string" + } + }, + "additionalProperties": true + } + } + }, + "additionalProperties": true +} diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index b053af5..c2ef9ec 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -7,7 +7,8 @@ }, "schemas": { "gamecult.eve.plugin.v1": "schemas/gamecult.eve.plugin.v1.schema.json", - "gamecult.eve.plugin_advertisement.v1": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json" + "gamecult.eve.plugin_advertisement.v1": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", + "gamecult.eve.provider_advertisement.v1": "schemas/gamecult.eve.provider_advertisement.v1.schema.json" }, "conformancePacks": [ { @@ -168,6 +169,22 @@ ] } }, + { + "id": "aetheria-world", + "title": "Aetheria Interactive World", + "pack": "provider", + "ownerRepo": "Aetheria", + "surface": { + "transport": "local-json", + "path": "web/fixtures/aetheria-world-surface.json" + }, + "expect": { + "providerId": "aetheria", + "componentKinds": ["surface", "card", "text", "metric", "partition", "field.surface2d", "control.button"], + "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent"], + "bindings": ["aetheria.daemon.frameId", "aetheria.daemon.soaView.gravityField", "aetheria.daemon.commands"] + } + }, { "id": "reactive-dsl", "title": "Reactive DSL", @@ -215,6 +232,36 @@ } } ], + "providerAdvertisements": [ + { + "providerId": "repixelizer", + "title": "Repixelizer", + "kind": "service.product", + "ownerRepo": "Repixelizer", + "repoRole": "provider-external", + "graduationTrigger": "Provider-owned product state, assets, sessions, and receipts stay in Repixelizer; Eve keeps only the advertisement-shaped fixture.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "web/fixtures/repixelizer.provider-advertisement.json", + "requiredFixtures": ["repixelizer"], + "expectedSchemas": ["gamecult.eve.surface.v1"], + "expectedSurfaces": ["repixelizer.operator.surface"], + "expectedCommands": ["repixelizer.job.submit", "repixelizer.job.cancel_own"] + }, + { + "providerId": "aetheria", + "title": "Aetheria", + "kind": "game.runtime", + "ownerRepo": "Aetheria", + "repoRole": "provider-external", + "graduationTrigger": "Aetheria owns RTS/ARPG world surfaces, generated level state, assets, command receipts, and scenario conformance; Eve keeps contract fixtures only.", + "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", + "advertisementPath": "web/fixtures/aetheria.provider-advertisement.json", + "requiredFixtures": ["aetheria-world"], + "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "aetheria.runtime.daemon.frame.v1"], + "expectedSurfaces": ["aetheria.daemon.game", "aetheria.daemon.editor"], + "expectedCommands": ["aetheria.daemon.commands"] + } + ], "pluginManifests": [ { "pluginId": "sai.vn", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index b253dbb..a659ff7 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -22,6 +22,7 @@ for (const fixture of manifest.fixtures) { } const pluginResults = await Promise.all((manifest.pluginManifests || []).map(plugin => evaluatePlugin(plugin, fixtureResults))); +const providerResults = await Promise.all((manifest.providerAdvertisements || []).map(provider => evaluateProvider(provider, fixtureResults))); const runtimeResults = await Promise.all(manifest.runtimes.map(runtime => evaluateRuntime(runtime, fixtureResults))); const report = { schema: "gamecult.eve.parity_report.v1", @@ -30,9 +31,10 @@ const report = { repoStrategy: manifest.repoStrategy || {}, conformancePacks: manifest.conformancePacks || [], responsiveCases: manifest.responsiveCases || [], - summary: summarize(fixtureResults, runtimeResults, pluginResults), + summary: summarize(fixtureResults, runtimeResults, pluginResults, providerResults), fixtures: fixtureResults, plugins: pluginResults, + providers: providerResults, runtimes: runtimeResults, }; @@ -43,6 +45,7 @@ await writeFile(path.join(outputRoot, "latest.md"), renderMarkdown(report)); console.log(`Parity report: ${path.relative(repoRoot, path.join(runDirectory, "parity-report.md"))}`); if (report.summary.failedFixtures > 0) process.exitCode = 1; +if (report.summary.failedProviders > 0) process.exitCode = 1; async function evaluateFixture(fixture) { const startedAt = Date.now(); @@ -277,6 +280,75 @@ async function evaluatePlugin(plugin, fixtureResults) { }; } +async function evaluateProvider(provider, fixtureResults) { + const expectedPaths = provider.expectedPaths || []; + const missingPaths = expectedPaths.filter(candidate => !existsSync(path.join(repoRoot, candidate))); + const requiredFixtures = provider.requiredFixtures || []; + const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); + const missingIncubationFields = requiredIncubationFields(provider).filter(field => !provider[field]); + const advertisementErrors = await validateJsonDocument(provider.schemaPath, provider.advertisementPath, { + schema: "gamecult.eve.provider_advertisement.v1", + providerId: provider.providerId, + }); + const advertisement = advertisementErrors.length ? null : await readJsonDocument(provider.advertisementPath); + const schemaIds = advertisement ? extractProviderSchemaIds(advertisement.schemas || []) : []; + const advertisedSchemaIds = advertisement ? [...new Set([ + ...schemaIds, + ...(advertisement.surfaces || []).map(surface => surface.schema).filter(Boolean), + ...(advertisement.commands || []).map(command => command.schema).filter(Boolean), + ])].sort() : []; + const surfaceIds = advertisement ? (advertisement.surfaces || []).map(surface => surface.surfaceId).filter(Boolean).sort() : []; + const commandIds = advertisement ? (advertisement.commands || []).map(command => command.command).filter(Boolean).sort() : []; + const witnessKinds = advertisement ? (advertisement.witnesses || []).map(witness => witness.kind).filter(Boolean).sort() : []; + const missingSchemas = (provider.expectedSchemas || []).filter(schema => !advertisedSchemaIds.includes(schema)); + const missingSurfaces = (provider.expectedSurfaces || []).filter(surface => !surfaceIds.includes(surface)); + const missingCommands = (provider.expectedCommands || []).filter(command => !commandIds.includes(command)); + const status = missingPaths.length + ? "missing-body" + : missingRequiredFixtures.length + ? "missing-required-fixture" + : missingIncubationFields.length + ? "missing-ownership-metadata" + : advertisementErrors.length + ? "invalid-provider-advertisement" + : missingSchemas.length || missingSurfaces.length || missingCommands.length + ? "capability-gap" + : "advertised"; + + return { + providerId: provider.providerId, + title: provider.title, + kind: provider.kind, + ownerRepo: provider.ownerRepo || "", + repoRole: provider.repoRole || "", + graduationTrigger: provider.graduationTrigger || "", + requiredFixtures, + missingRequiredFixtures, + schemaPath: provider.schemaPath || "", + advertisementPath: provider.advertisementPath || "", + advertisementErrors, + expectedPaths, + missingPaths, + missingIncubationFields, + expectedSchemas: provider.expectedSchemas || [], + expectedSurfaces: provider.expectedSurfaces || [], + expectedCommands: provider.expectedCommands || [], + schemaIds, + advertisedSchemaIds, + surfaceIds, + commandIds, + witnessKinds, + missingSchemas, + missingSurfaces, + missingCommands, + status, + }; +} + +async function readJsonDocument(documentPath) { + return JSON.parse(await readFile(path.join(repoRoot, documentPath), "utf8")); +} + async function validateJsonDocument(schemaPath, documentPath, expected = {}) { const errors = []; if (!schemaPath) errors.push("schemaPath:missing"); @@ -303,6 +375,14 @@ async function validateJsonDocument(schemaPath, documentPath, expected = {}) { return errors; } +function extractProviderSchemaIds(schemas) { + return schemas.map(schema => { + if (typeof schema === "string") return schema; + if (schema && typeof schema === "object") return schema.schema || schema.id || ""; + return ""; + }).filter(Boolean).sort(); +} + function validateSchemaSubset(schema, value, pointer = "$") { const errors = []; if (!schema || typeof schema !== "object") return errors; @@ -419,13 +499,16 @@ function addCheck(checks, id, pass, detail) { checks.push({ id, pass, ...detail }); } -function summarize(fixtures, runtimes, plugins) { +function summarize(fixtures, runtimes, plugins, providers) { return { totalFixtures: fixtures.length, passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, totalPlugins: plugins.length, healthyPlugins: plugins.filter(plugin => plugin.status === "incubating" || plugin.status === "external-owner-planned").length, + totalProviders: providers.length, + advertisedProviders: providers.filter(provider => provider.status === "advertised").length, + failedProviders: providers.filter(provider => provider.status !== "advertised").length, totalRuntimes: runtimes.length, activeRuntimes: runtimes.filter(runtime => runtime.status === "active").length, pendingRuntimes: runtimes.filter(runtime => runtime.status !== "active").length, @@ -442,6 +525,7 @@ function renderMarkdown(report) { "", `- Fixtures: ${report.summary.passedFixtures}/${report.summary.totalFixtures} passed`, `- Plugins: ${report.summary.healthyPlugins}/${report.summary.totalPlugins} declared`, + `- Providers: ${report.summary.advertisedProviders}/${report.summary.totalProviders} advertised`, `- Runtimes: ${report.summary.activeRuntimes}/${report.summary.totalRuntimes} active`, "", "## Repo Strategy", @@ -496,6 +580,20 @@ function renderMarkdown(report) { lines.push(`| ${plugin.title || plugin.pluginId} | ${plugin.status} | ${plugin.ownerRepo} | ${plugin.capabilities.join(", ")} | ${missing} |`); } + lines.push("", "## Providers", "", "| Provider | Status | Owner | Surfaces | Commands | Witnesses | Missing |", "| --- | --- | --- | --- | --- | --- | --- |"); + for (const provider of report.providers || []) { + const missing = [ + ...provider.missingPaths, + ...provider.missingRequiredFixtures.map(id => `fixture:${id}`), + ...provider.missingIncubationFields.map(id => `metadata:${id}`), + ...provider.advertisementErrors.map(id => `advertisement:${id}`), + ...provider.missingSchemas.map(id => `schema:${id}`), + ...provider.missingSurfaces.map(id => `surface:${id}`), + ...provider.missingCommands.map(id => `command:${id}`), + ].join(", "); + lines.push(`| ${provider.title || provider.providerId} | ${provider.status} | ${provider.ownerRepo} | ${provider.surfaceIds.join(", ")} | ${provider.commandIds.join(", ")} | ${provider.witnessKinds.join(", ")} | ${missing} |`); + } + lines.push("", "## Runtimes", "", "| Runtime | Status | Owner | Capture | Required Fixtures | Plugin Fixtures | Features | Plugin Gaps | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |"); for (const runtime of report.runtimes) { const missing = [ diff --git a/web/fixtures/aetheria-world-surface.json b/web/fixtures/aetheria-world-surface.json new file mode 100644 index 0000000..0607c16 --- /dev/null +++ b/web/fixtures/aetheria-world-surface.json @@ -0,0 +1,137 @@ +{ + "type": "surface-state", + "schema": "gamecult.eve.surface.v1", + "providerId": "aetheria", + "providerKind": "game.runtime", + "title": "Aetheria Interactive World Surface", + "version": 1, + "updatedAtUtc": "2026-07-08T00:00:00Z", + "surface": { + "id": "aetheria.daemon.game", + "root": { + "id": "aetheria.daemon.game.root", + "kind": "surface", + "props": {}, + "children": [ + { + "id": "aetheria.daemon.game.summary", + "kind": "card", + "props": { + "title": "Daemon World" + }, + "children": [ + { + "id": "aetheria.daemon.game.summary.text", + "kind": "text", + "props": { + "text": "Provider-owned world state projected through Eve. Unity and web clients lower this document; Aetheria keeps the live truth." + }, + "children": [] + }, + { + "id": "aetheria.daemon.game.frame", + "kind": "metric", + "props": { + "label": "Frame", + "bind": "aetheria.daemon.frameId", + "value": "7" + }, + "children": [] + } + ] + }, + { + "id": "aetheria.daemon.game.partition", + "kind": "partition", + "props": { + "direction": "horizontal" + }, + "children": [ + { + "id": "aetheria.daemon.game.gravity", + "kind": "field.surface2d", + "props": { + "label": "Current Zone Gravity", + "bind": "aetheria.daemon.soaView.gravityField", + "fieldId": "aetheria.zone.gravity", + "fieldKind": "scalar2d", + "bounds": "-2048,-2048,2048,2048", + "bodies": "Terminus:-620:240:1400;GateRelay:420:-110:760;SurveyDrone:80:560:160", + "objects": "station:-410:120;relay:420:-110;drone:80:560", + "sampleCount": "64", + "visualizer": "isolines" + }, + "children": [] + }, + { + "id": "aetheria.daemon.game.commands", + "kind": "card", + "props": { + "title": "Command Boundary" + }, + "children": [ + { + "id": "aetheria.daemon.game.command.focus", + "kind": "control.button", + "props": { + "label": "Focus Relay", + "command": "aetheria.daemon.commands", + "commandId": "aetheria.daemon.focus", + "action": { + "target": "aetheria.daemon.commands", + "operation": "focus_entity", + "entityId": "GateRelay" + } + }, + "children": [] + }, + { + "id": "aetheria.daemon.game.command.move", + "kind": "control.button", + "props": { + "label": "Issue Move Intent", + "command": "aetheria.daemon.commands", + "commandId": "aetheria.daemon.move_intent", + "action": { + "target": "aetheria.daemon.commands", + "operation": "set_move_vector", + "vector": "0.28,0.0,0.64" + } + }, + "children": [] + }, + { + "id": "aetheria.daemon.game.authority", + "kind": "text", + "props": { + "text": "Commands are intents against Aetheria's typed daemon boundary, not renderer-local game state." + }, + "children": [] + } + ] + } + ] + } + ] + }, + "styles": { + "tokens": { + "colorBackground": "#101318", + "colorPanel": "#1C2526", + "colorText": "#F1F4E8", + "colorMuted": "#A9B3A7", + "colorAccent": "#D6B24A", + "colorDanger": "#C95D63" + } + } + }, + "commands": [ + { + "command": "aetheria.daemon.commands", + "schema": "gamecult.eve.command.v1", + "transport": "cultmesh", + "authority": "AetheriaRuntimeDaemonCommandBoundaryDocument", + "result": "accepted-denied-or-reconciled" + } + ] +} diff --git a/web/fixtures/aetheria.provider-advertisement.json b/web/fixtures/aetheria.provider-advertisement.json new file mode 100644 index 0000000..f993070 --- /dev/null +++ b/web/fixtures/aetheria.provider-advertisement.json @@ -0,0 +1,157 @@ +{ + "schema": "gamecult.eve.provider_advertisement.v1", + "providerId": "aetheria", + "serviceId": "aetheria.service", + "verseId": "aetheria.local", + "rootVerse": "asgard", + "canonicalService": "aetheria", + "locatedService": "aetheria.local", + "cultMeshAddress": "cultmesh://aetheria.local/eve/providers/aetheria", + "title": "Aetheria", + "kind": "game.runtime", + "updatedAtUtc": "2026-07-08T00:00:00Z", + "freshness": { + "state": "fixture", + "lastSeenAtUtc": "2026-07-08T00:00:00Z", + "maxAgeMs": 15000 + }, + "schemas": [ + "aetheria.world_state.v1", + "aetheria.item_definition.v1", + "aetheria.corporation.v2", + "aetheria.name_file.v2", + "aetheria.trade_value_policy.v1", + "aetheria.player_settings.v1", + "aetheria.loadout_template.v1", + "aetheria.run_state.v1", + "aetheria.zone_state.v1", + "aetheria.entity_snapshot.v1", + "aetheria.verse_host_settings.v1", + "aetheria.runtime_session.v1", + "aetheria.eve_command_acceptance_status.v1", + "aetheria.runtime.daemon.provider_advertisement.v1", + "aetheria.runtime.daemon.frame.v1", + "aetheria.runtime.daemon.soa_view.v1", + "aetheria.runtime.daemon.health.v1", + "aetheria.runtime.daemon.command_boundary.v1", + "aetheria.runtime.daemon.game_surface.v1", + "aetheria.runtime.daemon.editor_surface.v1", + "aetheria.runtime.daemon.command.v1", + "gamecult.eve.surface.v1", + "gamecult.eve.command.v1" + ], + "witnesses": [ + { + "kind": "repo", + "ref": "E:\\Projects\\Aetheria", + "summary": "Aetheria owns daemon state, Unity/web clients, assets, receipts, and provider scenarios." + }, + { + "kind": "source", + "ref": "E:\\Projects\\Aetheria\\Aetheria.State\\AetheriaEveSurfaceDocuments.cs", + "summary": "BuildProviderAdvertisement publishes the live provider advertisement from typed Aetheria state." + }, + { + "kind": "cultcache", + "ref": "aetheria-world.cc", + "summary": "Typed Aetheria state file; local fixture stands in for smoke-created path." + }, + { + "kind": "cultmesh-record", + "ref": "AetheriaRuntimeVerseRecordKeys.DaemonGameSurface", + "summary": "Aetheria daemon game Eve GUI surface record." + }, + { + "kind": "cultmesh-record", + "ref": "AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary", + "summary": "Aetheria daemon typed command boundary record." + } + ], + "surfaces": [ + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "aetheria.catalog.operator", + "key": "eve:surface:aetheria.catalog.operator", + "transport": "cultmesh-managed", + "status": "available", + "audience": "operator", + "mode": "interactive" + }, + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "aetheria.operations", + "key": "eve:surface:aetheria.operations", + "transport": "cultmesh-managed", + "status": "available", + "audience": "operator", + "mode": "interactive" + }, + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "aetheria.daemon.game", + "key": "AetheriaRuntimeVerseRecordKeys.DaemonGameSurface", + "transport": "cultmesh-record", + "status": "available", + "audience": "player", + "mode": "interactive", + "summary": "Authoritative daemon game surface consumed by Unity and web lowerers." + }, + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "aetheria.daemon.game.tui", + "key": "AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface", + "transport": "cultmesh-record", + "status": "available", + "audience": "agent", + "mode": "interactive" + }, + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "aetheria.daemon.editor", + "key": "AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface", + "transport": "cultmesh-record", + "status": "available", + "audience": "operator", + "mode": "interactive" + } + ], + "commands": [ + { + "command": "aetheria.daemon.commands", + "transport": "cultmesh", + "summary": "Aetheria daemon typed command boundary." + }, + { + "command": "aetheria.catalog.refresh", + "transport": "cultmesh", + "summary": "Refresh catalog state." + }, + { + "command": "aetheria.operations.refresh", + "transport": "cultmesh", + "summary": "Refresh operations state." + }, + { + "command": "aetheria.player_settings.refresh", + "transport": "cultmesh", + "summary": "Refresh player settings state." + } + ], + "styleCapabilities": [ + { + "styleProfile": "aetheria.daemon.world", + "preferredLowerings": ["unity-uitoolkit", "web-reference", "tui"], + "tokenGroups": ["aetheria.world", "aetheria.daemon", "aetheria.commandBoundary"], + "lossiness": { + "web-reference": "proves generic Eve lowering and command/binding shape; does not own Aetheria assets or simulation truth", + "tui": "dense command/state inspection only" + } + } + ], + "contacts": [ + { + "kind": "repo", + "path": "E:\\Projects\\Aetheria" + } + ] +} diff --git a/web/surface.js b/web/surface.js index ba8baa3..2b54cba 100644 --- a/web/surface.js +++ b/web/surface.js @@ -18,6 +18,13 @@ let liveHermodr = false; let openProviderGeneration = 0; const localProviders = [ + { + providerId: "aetheria", + title: "Aetheria", + kind: "game.runtime", + advertisement: "./fixtures/aetheria.provider-advertisement.json", + surfaces: [{ transport: "local-json", surfaceId: "aetheria.daemon.game", url: "./fixtures/aetheria-world-surface.json" }], + }, { providerId: "repixelizer", title: "Repixelizer", @@ -92,6 +99,7 @@ async function bootProviders() { })); const requestedProviderId = new URLSearchParams(location.search).get("provider"); const firstProduct = providers.find(provider => provider.providerId === requestedProviderId || provider.aliases?.includes(requestedProviderId)) + || providers.find(provider => provider.providerId === "aetheria") || providers.find(provider => provider.providerId === "aetheria.main_menu.root") || providers.find(provider => provider.providerId === "aetheria.inventory.panel") || providers.find(provider => provider.providerId === "repixelizer")