From e1c635d033c8d23f53456d130f752e2090a62637 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 13:25:20 +0100 Subject: [PATCH] Add EveTui plugin fallback projections --- docs/eve-migration-roadmap.md | 34 +++++---- .../eve-tui/eve-runtime-capability.json | 31 ++++++-- .../eve-tui/evetui-split-handoff.json | 20 ++++- .../incubating/eve-tui/src/eve-tui-shell.mjs | 76 ++++++++++++++++++- .../eve-tui/test/eve-tui-shell.test.mjs | 66 +++++++++++++++- schemas/gamecult.eve.tui_grid.v1.schema.json | 51 +++++++++++++ scripts/run-evetui-lifecycle-smoke.ps1 | 29 +++++-- scripts/run-evetui-provider-shell-smoke.ps1 | 13 +++- scripts/run-evetui-split-handoff-smoke.ps1 | 18 ++++- ...ntime-owner-conformance-consumer-smoke.ps1 | 10 ++- ...plit-target-conformance-consumer-smoke.ps1 | 1 + tools/parity/parity-manifest.json | 19 ++--- 12 files changed, 319 insertions(+), 49 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 2bb38fe..a25d2f8 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -393,8 +393,8 @@ Recently cut: `runtimePluginProjectionCoverage[]` records. Runtime-owner smokes assert Unity UI Toolkit support for `sai.vn`, `norn.graph`, and `tex.math`, Unity scene support for the same three plugin capabilities, and Electron shell - support for the same sidecar plugin projection shells without importing - plugin semantics. + and TUI support for the same sidecar plugin projection shells without + importing plugin semantics. - Provider/runtime/plugin projection coverage is now exported as `providerRuntimePluginProjectionCoverage[]`. The runtime-owner smoke proves the Sai VN provider surface lowers fully in web, lowers in Unity UI Toolkit @@ -404,7 +404,7 @@ Recently cut: projection. - Runtime/plugin projection gaps are now exported as `runtimePluginProjectionGaps[]`. The runtime-owner smoke asserts remaining - device-edge and TUI plugin gaps as typed records, so missing Sai, Norn, or TeX + device-edge plugin gaps as typed records, so missing Sai, Norn, or TeX projection support is owned by the runtime split target and does not imply those plugin semantics should move into Eve core or Aetheria product code. - Split target blockers are now exported as `splitTargetBlockers[]` and as @@ -501,23 +501,25 @@ Recently cut: - EveTui now has a pending split target and handoff at `runtimes/incubating/eve-tui/evetui-split-handoff.json`. It claims the advertised `tui` world-surface lowering target as a lossy terminal-grid - command surface. The handoff draws the line between the provider-agnostic - terminal/grid lowerer and provider-owned TUI surfaces, and the split-target - consumer smoke keeps runtime graduation and transcript/cell-grid capture - visible as blockers. + command surface and carries compact Sai, Norn, and TeX sidecar plugin + fallback shells. The handoff draws the line between the provider-agnostic + terminal/grid lowerer, plugin-owned semantics, and provider-owned TUI + surfaces, while the split-target consumer smoke keeps runtime graduation and + transcript/cell-grid capture ownership visible as blockers. - EveTui now has a direct split handoff smoke at `scripts/run-evetui-split-handoff-smoke.ps1`, a pending runtime capability manifest at `runtimes/incubating/eve-tui/eve-runtime-capability.json`, and a lifecycle smoke at `scripts/run-evetui-lifecycle-smoke.ps1`. The manifest declares provider advertisement consumption, command transport, terminal-grid - summary, terminal-grid lowering, and the `tui` world/editor lowering claim; - it now carries an executable terminal capture request contract through + summary, terminal-grid lowering, sidecar plugin fallback projection, and the + `tui` world/editor lowering claim; it now carries an executable terminal + capture request contract through `tools/evetui/evetui-capture-contract.mjs` and `scripts/run-evetui-capture-contract-smoke.ps1`, plus a deterministic `gamecult.eve.tui_grid.v1` JSON capture artifact through `tools/evetui/evetui-capture-artifact.mjs` and - `scripts/run-evetui-capture-smoke.ps1`. Plugin projection, terminal package - release, and owner-repo production of the capture path remain pending. + `scripts/run-evetui-capture-smoke.ps1`. Terminal package release and + owner-repo production of the capture path remain pending. - EveTui release lifecycle now carries a structured pending release request contract. The runtime capability manifest reads version truth from `runtimes/incubating/eve-tui/package.json`, names the @@ -866,11 +868,11 @@ Recently cut: - The parity harness now emits an `EveConformance`-shaped export under `artifacts/conformance/latest`, with a top-level index and per-pack JSON files for core, plugin, provider, and runtime consumers. -- Unity and Electron plugin projection capability gaps are now explicitly - demoted. UI Toolkit, Unity scene, and Electron runtimes own generic projection - and command requests, while Sai, Norn, and TeX semantics remain sidecar-plugin - responsibilities. Runtimes only add projection shells for capabilities a - plugin advertises through Eve. +- Unity, Electron, and TUI plugin projection capability gaps are now explicitly + demoted. UI Toolkit, Unity scene, Electron, and compact TUI runtimes own + generic projection and command requests, while Sai, Norn, and TeX semantics + remain sidecar-plugin responsibilities. Runtimes only add projection shells or + lossy terminal fallbacks for capabilities a plugin advertises through Eve. - Flutter and Unity now have runtime command-transport smoke evidence in the parity report. Flutter emits `gamecult.eve.command.v1` intents from lowered controls, and Unity command requests now carry the same command schema. diff --git a/runtimes/incubating/eve-tui/eve-runtime-capability.json b/runtimes/incubating/eve-tui/eve-runtime-capability.json index b270bfe..8e98bd3 100644 --- a/runtimes/incubating/eve-tui/eve-runtime-capability.json +++ b/runtimes/incubating/eve-tui/eve-runtime-capability.json @@ -18,23 +18,41 @@ "commandTransport", "terminalGridSummary", "terminalGridLowering", - "embeddedDocuments" + "embeddedDocuments", + "pluginProjection" ], - "supportedPlugins": [], - "unsupportedPlugins": [ + "supportedPlugins": [ { "pluginId": "sai.vn", - "reason": "TUI provider-shell skeleton exists, but no terminal adapter exists for Sai sidecar-advertised VN projection in this runtime." + "capabilities": [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump" + ], + "projectionAdapter": "EveTuiShell.buildPluginProjection", + "lossiness": "terminal-summary" }, { "pluginId": "norn.graph", - "reason": "TUI provider-shell skeleton exists, but no terminal fallback exists for Norn sidecar-advertised graph projection in this runtime." + "capabilities": [ + "embed.norn" + ], + "projectionAdapter": "EveTuiShell.buildPluginProjection", + "lossiness": "terminal-outline" }, { "pluginId": "tex.math", - "reason": "TUI provider-shell skeleton exists, but no terminal fallback exists for TeX sidecar-advertised math projection in this runtime." + "capabilities": [ + "embed.tex", + "tex.inline", + "tex.block" + ], + "projectionAdapter": "EveTuiShell.buildPluginProjection", + "lossiness": "source-text" } ], + "unsupportedPlugins": [], "worldSurfaceLowering": [ { "targetId": "tui", @@ -46,6 +64,7 @@ "evidencePaths": [ "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "web/fixtures/sai-vn-surface.json", "web/fixtures/aetheria.provider-advertisement.json" ] } diff --git a/runtimes/incubating/eve-tui/evetui-split-handoff.json b/runtimes/incubating/eve-tui/evetui-split-handoff.json index 981190b..f80a2ee 100644 --- a/runtimes/incubating/eve-tui/evetui-split-handoff.json +++ b/runtimes/incubating/eve-tui/evetui-split-handoff.json @@ -15,7 +15,8 @@ "currentPaths": [ "runtimes/incubating/eve-tui/package.json", "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs" + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "web/fixtures/sai-vn-surface.json" ], "destinationOwner": "EveTui", "replacementProof": "EveTui hosts a provider-agnostic TUI lowerer that consumes provider advertisements and Eve surface documents without importing provider internals." @@ -41,6 +42,18 @@ "destinationOwner": "EveTui", "replacementProof": "TUI actions emit gamecult.eve.command.v1 intents through provider-advertised command boundaries and render provider receipts instead of mutating local state." }, + { + "id": "tui-plugin-projection", + "stage": "plugin-projection", + "currentPaths": [ + "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "runtimes/incubating/eve-tui/eve-runtime-capability.json", + "web/fixtures/sai-vn-surface.json" + ], + "destinationOwner": "EveTui", + "replacementProof": "EveTui owns lossy terminal fallback shells for Sai, Norn, and TeX sidecar-advertised components; Sai keeps story state, Norn keeps graph layout/state, and EvePlugins keeps TeX rendering semantics." + }, { "id": "tui-capture-lifecycle", "stage": "capture", @@ -68,12 +81,15 @@ "provider internals as TUI runtime authority", "renderer-local product state as provider truth", "terminal-only command routes that bypass gamecult.eve.command.v1", - "plugin semantic internals", + "Sai story state, VN/Ink semantics, or story command interpretation as TUI runtime authority", + "Norn graph layout, graph state, or command semantics as TUI runtime authority", + "TeX parsing, typesetting, baseline metrics, or render-cache semantics as TUI runtime authority", "lossy text summaries pretending to be provider state" ], "requiredExternalProofs": [ "EveTui publishes a runtime capability manifest with terminal/grid capabilities.", "EveTui owns a provider-shell contract skeleton that selects advertised provider surfaces, lowers Eve surface trees, emits gamecult.eve.command.v1 intents, and produces an explicitly lossy terminal grid.", + "EveTui projects Sai, Norn, and TeX sidecar plugin fallback shells without importing plugin semantic internals.", "EveTui owns the provider-agnostic terminal grid lowerer outside Eve incubation.", "EveTui consumes at least one non-Aetheria provider advertisement.", "EveTui consumes Aetheria's agent-facing TUI surface through provider advertisements without importing Aetheria internals.", diff --git a/runtimes/incubating/eve-tui/src/eve-tui-shell.mjs b/runtimes/incubating/eve-tui/src/eve-tui-shell.mjs index a6410c4..39485d3 100644 --- a/runtimes/incubating/eve-tui/src/eve-tui-shell.mjs +++ b/runtimes/incubating/eve-tui/src/eve-tui-shell.mjs @@ -82,6 +82,7 @@ export class EveTuiShell { width: this.width, lines, embeddedDocumentSlots: collectEmbeddedDocumentSlots(document.root), + pluginProjections: collectPluginProjections(document.root), lossiness: "terminal-grid-command-surface; summarizes provider-authored world surface without owning provider state", }; } @@ -154,7 +155,11 @@ function buildComponentLines(component, width, depth = 0) { const props = objectValue(source.props); const label = firstString(props.label, props.title, props.text, props.bind, props.command, props.fieldId); const indent = " ".repeat(Math.min(depth, 6)); - const ownLine = fitLine(`${indent}${terminalElementKind(kind)} ${id}${label ? ` ${label}` : ""}`, width); + const pluginProjection = buildPluginProjection(kind, source); + const pluginSuffix = pluginProjection + ? ` [${pluginProjection.pluginId}:${pluginProjection.semanticOwner}]` + : ""; + const ownLine = fitLine(`${indent}${terminalElementKind(kind)} ${id}${label ? ` ${label}` : ""}${pluginSuffix}`, width); const children = Array.isArray(source.children) ? source.children : []; const embeddedDocuments = normalizeEmbeddedDocuments(source.embeddedDocuments); return [ @@ -176,6 +181,21 @@ function collectEmbeddedDocumentSlots(component, slots = []) { return slots; } +function collectPluginProjections(component, projections = []) { + const source = objectValue(component); + const projection = buildPluginProjection(firstString(source.kind), source); + if (projection) { + projections.push({ + ownerId: firstString(source.id), + ...projection, + }); + } + for (const child of Array.isArray(source.children) ? source.children : []) { + collectPluginProjections(child, projections); + } + return projections; +} + function normalizeEmbeddedDocuments(value) { return Array.isArray(value) ? value @@ -192,6 +212,9 @@ function normalizeEmbeddedDocuments(value) { function terminalElementKind(componentKind) { if (!componentKind) return "empty"; + if (componentKind === "vn.stage") return "sai-vn"; + if (componentKind === "embed.norn") return "norn"; + if (componentKind === "embed.tex") return "tex"; if (componentKind.startsWith("control.")) return "command"; if (componentKind.startsWith("embed.")) return "plugin"; if (componentKind === "surface.slot") return "slot"; @@ -201,6 +224,57 @@ function terminalElementKind(componentKind) { return componentKind; } +function buildPluginProjection(componentKind, component) { + const props = objectValue(component.props); + if (componentKind === "vn.stage") { + return { + pluginId: "sai.vn", + projectionKind: "sai-vn-terminal-stage-summary", + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["vn.stage", "story.choose", "story.continue", "story.jump"], + documentId: firstString(props.storyId, component.id), + semanticOwner: "Sai", + fallbackKind: "terminal-summary", + }; + } + + if (componentKind === "embed.norn") { + return { + pluginId: "norn.graph", + projectionKind: "norn-graph-terminal-outline", + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["embed.norn"], + documentId: firstString(props.sourceUri, props.documentId, component.id), + semanticOwner: "Norn", + fallbackKind: "terminal-outline", + }; + } + + if (componentKind === "embed.tex") { + return { + pluginId: "tex.math", + projectionKind: texProjectionKind(props), + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["embed.tex", "tex.inline", "tex.block"], + documentId: firstString(props.sourceUri, props.source, component.id), + semanticOwner: "EvePlugins", + fallbackKind: "source-text", + }; + } + + return null; +} + +function texProjectionKind(props) { + const display = firstString(props.display, "inline"); + if (display === "block") return "tex-math-terminal-block-source"; + if (display === "page") return "tex-math-terminal-page-source"; + return "tex-math-terminal-inline-source"; +} + function fitLine(value, width) { const text = String(value || ""); if (text.length <= width) return text; diff --git a/runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs b/runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs index 9ae2210..4181ac8 100644 --- a/runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs +++ b/runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs @@ -74,8 +74,21 @@ test("lowers provider surface trees into a terminal grid", () => { assert.ok(grid.lines.every(line => line.length <= 44)); assert.ok(grid.lines.some(line => line.includes("world aetheria.daemon.game.gravity"))); assert.ok(grid.lines.some(line => line.includes("command aetheria.daemon.game.focus"))); - assert.ok(grid.lines.some(line => line.includes("plugin aetheria.daemon.game.norn"))); + assert.ok(grid.lines.some(line => line.includes("norn aetheria.daemon.game.norn"))); assert.ok(grid.lines.some(line => line.includes("slot norn.map"))); + assert.deepEqual(grid.pluginProjections, [ + { + ownerId: "aetheria.daemon.game.norn", + pluginId: "norn.graph", + projectionKind: "norn-graph-terminal-outline", + abiSchema: "gamecult.eve.plugin_abi.v1", + commandBoundary: "sidecar-advertised-plugin-abi", + capabilities: ["embed.norn"], + documentId: "aetheria.daemon.game.norn", + semanticOwner: "Norn", + fallbackKind: "terminal-outline", + }, + ]); assert.deepEqual(grid.embeddedDocumentSlots, [ { ownerId: "aetheria.daemon.game.norn", @@ -95,6 +108,36 @@ test("rejects surface documents that do not match the advertised TUI target", () ); }); +test("lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", () => { + const shell = new EveTuiShell({ width: 72 }); + const saiSurface = JSON.parse(readFileSync(path.join(repoRoot, "web/fixtures/sai-vn-surface.json"), "utf8")); + const grid = shell.lowerSurface(saiSurface, saiAdvertisement(), "sai.visual_novel.surface"); + + assert.deepEqual(validateSchemaSubset(tuiGridSchema, grid), []); + assert.equal(grid.providerId, "gamecult.home.vn"); + assert.equal(grid.surfaceId, "sai.visual_novel.surface"); + assert.ok(grid.lines.every(line => line.length <= 72)); + assert.ok(grid.lines.some(line => line.includes("sai-vn sai.root"))); + assert.ok(grid.lines.some(line => line.includes("norn sai.graph"))); + assert.ok(grid.lines.some(line => line.includes("tex sai.tex.log-power"))); + + const byPlugin = new Map(grid.pluginProjections.map(projection => [projection.pluginId, projection])); + assert.equal(byPlugin.get("sai.vn").semanticOwner, "Sai"); + assert.equal(byPlugin.get("sai.vn").projectionKind, "sai-vn-terminal-stage-summary"); + assert.deepEqual(byPlugin.get("sai.vn").capabilities, [ + "vn.stage", + "story.choose", + "story.continue", + "story.jump", + ]); + assert.equal(byPlugin.get("norn.graph").semanticOwner, "Norn"); + assert.equal(byPlugin.get("norn.graph").fallbackKind, "terminal-outline"); + assert.equal(byPlugin.get("tex.math").semanticOwner, "EvePlugins"); + assert.equal(byPlugin.get("tex.math").projectionKind, "tex-math-terminal-block-source"); + assert.equal(byPlugin.get("tex.math").fallbackKind, "source-text"); + assert.equal(byPlugin.get("tex.math").documentId, "\\\\mathrm{votes}(p)=1+\\\\lfloor\\\\log_b(1+p)\\\\rfloor"); +}); + function advertisement() { return { schema: "gamecult.eve.provider_advertisement.v1", @@ -127,6 +170,27 @@ function advertisement() { }; } +function saiAdvertisement() { + return { + schema: "gamecult.eve.provider_advertisement.v1", + providerId: "gamecult.home.vn", + title: "GameCult Compound VN", + surfaces: [ + { + surfaceId: "sai.visual_novel.surface", + transport: "local-json", + url: "web/fixtures/sai-vn-surface.json", + worldInteraction: { + projectionKind: "provider-authored-world-surface", + commandBoundary: "sai.vn.plugin.commands", + receiptSchema: "gamecult.eve.command_receipt.v1", + ownership: "provider-owns-story-state-plugin-sidecars-own-nested-semantics", + }, + }, + ], + }; +} + function surfaceDocument(surfaceId = "aetheria.daemon.game") { return { type: "surface-state", diff --git a/schemas/gamecult.eve.tui_grid.v1.schema.json b/schemas/gamecult.eve.tui_grid.v1.schema.json index 4ab70c7..cc0bdb4 100644 --- a/schemas/gamecult.eve.tui_grid.v1.schema.json +++ b/schemas/gamecult.eve.tui_grid.v1.schema.json @@ -81,6 +81,57 @@ "additionalProperties": true } }, + "pluginProjections": { + "type": "array", + "items": { + "type": "object", + "required": [ + "ownerId", + "pluginId", + "projectionKind", + "semanticOwner", + "fallbackKind" + ], + "properties": { + "ownerId": { + "type": "string", + "minLength": 1 + }, + "pluginId": { + "type": "string", + "minLength": 1 + }, + "projectionKind": { + "type": "string", + "minLength": 1 + }, + "abiSchema": { + "type": "string" + }, + "commandBoundary": { + "type": "string" + }, + "capabilities": { + "type": "array", + "items": { + "type": "string" + } + }, + "documentId": { + "type": "string" + }, + "semanticOwner": { + "type": "string", + "minLength": 1 + }, + "fallbackKind": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + } + }, "lossiness": { "type": "string", "minLength": 1 diff --git a/scripts/run-evetui-lifecycle-smoke.ps1 b/scripts/run-evetui-lifecycle-smoke.ps1 index 4cbaea5..ddf52d5 100644 --- a/scripts/run-evetui-lifecycle-smoke.ps1 +++ b/scripts/run-evetui-lifecycle-smoke.ps1 @@ -36,13 +36,30 @@ if ($manifest.incubation.splitTarget -ne "EveTui") { throw "Unexpected EveTui split target: $($manifest.incubation.splitTarget)" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments", "pluginProjection")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveTui manifest missing provider-shell feature: $feature" } } -if (@($manifest.supportedPlugins).Count -ne 0) { - throw "EveTui must not claim plugin projection before terminal projection adapters for sidecar-advertised capabilities exist" + +$expectedPlugins = @{ + "sai.vn" = @("vn.stage", "story.choose", "story.continue", "story.jump") + "norn.graph" = @("embed.norn") + "tex.math" = @("embed.tex", "tex.inline", "tex.block") +} +foreach ($pluginId in $expectedPlugins.Keys) { + $supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 + if (-not $supported) { + throw "EveTui manifest missing supported plugin declaration: $pluginId" + } + foreach ($capability in $expectedPlugins[$pluginId]) { + if (-not (@($supported.capabilities) -contains $capability)) { + throw "EveTui supported plugin $pluginId missing capability: $capability" + } + } + if ($supported.projectionAdapter -ne "EveTuiShell.buildPluginProjection") { + throw "EveTui supported plugin $pluginId has unexpected projection adapter: $($supported.projectionAdapter)" + } } $worldSurfaceLoweringClaims = @() @@ -69,10 +86,10 @@ foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) { } } -foreach ($pluginId in @("sai.vn", "norn.graph", "tex.math")) { +foreach ($pluginId in $expectedPlugins.Keys) { $unsupported = @($manifest.unsupportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 - if (-not $unsupported) { - throw "EveTui manifest missing unsupported plugin declaration: $pluginId" + if ($unsupported) { + throw "EveTui manifest must not report $pluginId unsupported while terminal fallback projection is declared" } } diff --git a/scripts/run-evetui-provider-shell-smoke.ps1 b/scripts/run-evetui-provider-shell-smoke.ps1 index c9a3385..2cf6734 100644 --- a/scripts/run-evetui-provider-shell-smoke.ps1 +++ b/scripts/run-evetui-provider-shell-smoke.ps1 @@ -24,12 +24,19 @@ if ($manifest.lifecycle.test.status -ne "provider-shell-contract-skeleton") { throw "Unexpected EveTui provider shell status: $($manifest.lifecycle.test.status)" } -foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering")) { +foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "pluginProjection")) { if (-not (@($manifest.supportedFeatures) -contains $feature)) { throw "EveTui provider shell missing supported feature: $feature" } } +foreach ($pluginId in @("sai.vn", "norn.graph", "tex.math")) { + $supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1 + if (-not $supported) { + throw "EveTui provider shell missing supported plugin projection: $pluginId" + } +} + $worldSurfaceLoweringClaims = @() if ($null -ne $manifest.worldSurfaceLowering) { $worldSurfaceLoweringClaims = @($manifest.worldSurfaceLowering) @@ -78,14 +85,14 @@ foreach ($relativePath in $expectedFiles) { } $shellSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-tui\src\eve-tui-shell.mjs") -Raw -foreach ($symbol in @("EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "terminalElementKind", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema")) { +foreach ($symbol in @("EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "buildPluginProjection", "collectPluginProjections", "terminalElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema")) { if (-not $shellSource.Contains($symbol)) { throw "EveTui provider shell source missing symbol: $symbol" } } $testSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-tui\test\eve-tui-shell.test.mjs") -Raw -foreach ($symbol in @("selects advertised provider surfaces", "command intents carry provider-advertised boundaries", "summary grid is explicitly lossy", "lowers provider surface trees into a terminal grid", "rejects surface documents that do not match the advertised TUI target", "aetheria.daemon.commands")) { +foreach ($symbol in @("selects advertised provider surfaces", "command intents carry provider-advertised boundaries", "summary grid is explicitly lossy", "lowers provider surface trees into a terminal grid", "lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", "pluginProjections", "sai-vn-terminal-stage-summary", "norn-graph-terminal-outline", "tex-math-terminal-block-source", "rejects surface documents that do not match the advertised TUI target", "aetheria.daemon.commands")) { if (-not $testSource.Contains($symbol)) { throw "EveTui provider shell test missing symbol: $symbol" } diff --git a/scripts/run-evetui-split-handoff-smoke.ps1 b/scripts/run-evetui-split-handoff-smoke.ps1 index 3391977..54a8e2f 100644 --- a/scripts/run-evetui-split-handoff-smoke.ps1 +++ b/scripts/run-evetui-split-handoff-smoke.ps1 @@ -42,6 +42,7 @@ $expectedMoveSets = @{ "tui-runtime-body" = "runtime-body" "tui-world-surface-lowering" = "world-surface-lowering" "tui-command-transport" = "command" + "tui-plugin-projection" = "plugin-projection" "tui-capture-lifecycle" = "capture" } $moveSets = @($handoff.moveSets) @@ -61,7 +62,7 @@ foreach ($id in $expectedMoveSets.Keys) { } $currentPaths = if ($null -eq $moveSet.currentPaths) { @() } else { @($moveSet.currentPaths) } $observedProviderPaths = if ($null -eq $moveSet.observedProviderPaths) { @() } else { @($moveSet.observedProviderPaths) } - if ($id -in @("tui-runtime-body", "tui-world-surface-lowering", "tui-command-transport", "tui-capture-lifecycle")) { + if ($id -in @("tui-runtime-body", "tui-world-surface-lowering", "tui-command-transport", "tui-plugin-projection", "tui-capture-lifecycle")) { if ($currentPaths.Count -eq 0) { throw "EveTui split handoff move set $id must name provider-shell skeleton paths" } @@ -94,7 +95,9 @@ foreach ($forbiddenImport in @( "provider internals as TUI runtime authority", "renderer-local product state as provider truth", "terminal-only command routes that bypass gamecult.eve.command.v1", - "plugin semantic internals", + "Sai story state, VN/Ink semantics, or story command interpretation as TUI runtime authority", + "Norn graph layout, graph state, or command semantics as TUI runtime authority", + "TeX parsing, typesetting, baseline metrics, or render-cache semantics as TUI runtime authority", "lossy text summaries pretending to be provider state" )) { if (-not (@($handoff.forbiddenImports) -contains $forbiddenImport)) { @@ -102,6 +105,17 @@ foreach ($forbiddenImport in @( } } +$pluginMoveSet = $moveSets | Where-Object { $_.id -eq "tui-plugin-projection" } | Select-Object -First 1 +foreach ($relativePath in @( + "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", + "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "web/fixtures/sai-vn-surface.json" +)) { + if (-not (@($pluginMoveSet.currentPaths) -contains $relativePath)) { + throw "EveTui plugin projection move set missing current path: $relativePath" + } +} + foreach ($proof in @($handoff.requiredExternalProofs)) { if (-not $proof) { throw "EveTui split handoff contains an empty required external proof" diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 2e88df9..29d42bb 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -74,6 +74,7 @@ node $consumerScript $consumerExport ` --expect-runtime-feature tui:terminalGridSummary ` --expect-runtime-feature tui:terminalGridLowering ` --expect-runtime-feature tui:embeddedDocuments ` + --expect-runtime-feature tui:pluginProjection ` --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 ` @@ -99,6 +100,9 @@ node $consumerScript $consumerExport ` --expect-runtime-plugin-projection electron-shell:sai.vn:supported:EveElectron ` --expect-runtime-plugin-projection electron-shell:norn.graph:supported:EveElectron ` --expect-runtime-plugin-projection electron-shell:tex.math:supported:EveElectron ` + --expect-runtime-plugin-projection tui:sai.vn:supported:EveTui ` + --expect-runtime-plugin-projection tui:norn.graph:supported:EveTui ` + --expect-runtime-plugin-projection tui:tex.math:supported:EveTui ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:web:sai.vn:supported:Eve ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:web:tex.math:optional-supported:Eve ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:unity-uitoolkit:sai.vn:supported:EveUnity ` @@ -110,11 +114,11 @@ node $consumerScript $consumerExport ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:sai.vn:supported:EveElectron ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:norn.graph:optional-supported:EveElectron ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:electron-shell:tex.math:optional-supported:EveElectron ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:tui:sai.vn:supported:EveTui ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:tui:norn.graph:optional-supported:EveTui ` + --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:tui:tex.math:optional-supported:EveTui ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:ios-uikit:sai.vn:unsupported-runtime-projection:EveIOS ` --expect-provider-runtime-plugin-projection gamecult.home.vn:sai.visual_novel.surface:ios-uikit:norn.graph:optional-unsupported-runtime-projection:EveIOS ` - --expect-runtime-plugin-gap tui:sai.vn:EveTui ` - --expect-runtime-plugin-gap tui:norn.graph:EveTui ` - --expect-runtime-plugin-gap tui:tex.math:EveTui ` --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/scripts/run-split-target-conformance-consumer-smoke.ps1 b/scripts/run-split-target-conformance-consumer-smoke.ps1 index 6ae36e8..4df4be0 100644 --- a/scripts/run-split-target-conformance-consumer-smoke.ps1 +++ b/scripts/run-split-target-conformance-consumer-smoke.ps1 @@ -80,6 +80,7 @@ try { --expect-split-handoff-move EveTui:tui:tui-runtime-body:current:exists:eve-tui-shell.mjs ` --expect-split-handoff-move EveTui:tui:tui-world-surface-lowering:current:exists:eve-tui-shell.mjs ` --expect-split-handoff-move EveTui:tui:tui-command-transport:current:exists:eve-tui-shell.mjs ` + --expect-split-handoff-move EveTui:tui:tui-plugin-projection:current:exists:sai-vn-surface.json ` --expect-split-handoff-move EveTui:tui:tui-capture-lifecycle:current:exists:evetui-capture-contract.mjs ` --expect-split-target-blocker "EveTui:TUI runtime body graduates to EveTui outside provider product code" if ($LASTEXITCODE -ne 0) { diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 4bcb5f3..4604f3d 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -830,23 +830,23 @@ }, "splitHandoffPath": "runtimes/incubating/eve-tui/evetui-split-handoff.json", "requiredFixtures": ["embedded-surface", "aetheria-world"], - "pluginFixtures": [], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments"], - "supportedPlugins": [], - "unsupportedPlugins": [ + "pluginFixtures": ["sai-vn"], + "supportedFeatures": ["providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments", "pluginProjection"], + "supportedPlugins": [ { "pluginId": "sai.vn", - "reason": "TUI provider-shell skeleton exists, but no terminal adapter exists for Sai sidecar-advertised VN projection in this runtime." + "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] }, { "pluginId": "norn.graph", - "reason": "TUI provider-shell skeleton exists, but no terminal fallback exists for Norn sidecar-advertised graph projection in this runtime." + "capabilities": ["embed.norn"] }, { "pluginId": "tex.math", - "reason": "TUI provider-shell skeleton exists, but no terminal fallback exists for TeX sidecar-advertised math projection in this runtime." + "capabilities": ["embed.tex", "tex.inline", "tex.block"] } ], + "unsupportedPlugins": [], "worldSurfaceLowering": [ { "targetId": "tui", @@ -858,6 +858,7 @@ "evidencePaths": [ "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", + "web/fixtures/sai-vn-surface.json", "web/fixtures/aetheria.provider-advertisement.json" ] } @@ -884,11 +885,11 @@ "expectedSourceSymbols": [ { "path": "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "contains": ["EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "collectEmbeddedDocumentSlots", "normalizeEmbeddedDocuments", "embeddedDocumentSlots", "terminalElementKind", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema"] + "contains": ["EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "buildPluginProjection", "collectPluginProjections", "collectEmbeddedDocumentSlots", "normalizeEmbeddedDocuments", "embeddedDocumentSlots", "pluginProjections", "terminalElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema"] }, { "path": "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", - "contains": ["selects advertised provider surfaces", "command intents carry provider-advertised boundaries", "summary grid is explicitly lossy", "lowers provider surface trees into a terminal grid", "embeddedDocumentSlots", "norn.map", "rejects surface documents that do not match the advertised TUI target", "aetheria.daemon.commands"] + "contains": ["selects advertised provider surfaces", "command intents carry provider-advertised boundaries", "summary grid is explicitly lossy", "lowers provider surface trees into a terminal grid", "lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", "embeddedDocumentSlots", "pluginProjections", "norn.map", "sai-vn-terminal-stage-summary", "norn-graph-terminal-outline", "tex-math-terminal-block-source", "rejects surface documents that do not match the advertised TUI target", "aetheria.daemon.commands"] }, { "path": "tools/evetui/evetui-capture-contract.mjs",