From f9a2b6a6d65b2ecc86b388e9688a8b9bc19c8b0e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 10 Jul 2026 16:10:43 +0100 Subject: [PATCH] Graduate Norn graph semantics from Eve --- docs/eve-migration-roadmap.md | 14 +++--- docs/surface-contract-v1.md | 33 +++++++------- web/fixtures/sai-vn-surface.conformance.json | 3 +- web/fixtures/sai-vn-surface.json | 44 +++++-------------- .../sai-vn.provider-advertisement.json | 5 +++ web/provider-advertisements.test.mjs | 14 ++++++ 6 files changed, 55 insertions(+), 58 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index a0014e8..f9b4e52 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -52,16 +52,16 @@ advertisements and typed command/receipt documents are the boundary. - EveUnity's clean `TestProject` owns generic scene and UI Toolkit package verification; ordinary runtime tests no longer open or mutate Aetheria. - EveUnity publishes immutable `0.1.0` Git package tags for surface and UI - Toolkit plus scene `0.1.1`, whose generic CultMesh composition root discovers + Toolkit plus scene `0.1.4`, whose generic CultMesh composition root discovers playable worlds without product identifiers. Its release-only consumer - resolves those tags plus CultLib's assembled Unity package and passes 26 + resolves those tags plus CultLib's assembled Unity package and passes 30 Unity EditMode tests without sibling package paths. Aetheria pins the same released sources. - The same released-package consumer cold-starts from Aetheria's CultMesh rendezvous endpoint with no configured provider or surface ID, discovers the daemon's `interactive-world`, loads provider-owned assets, and reconciles movement, targeting, and action receipts without importing Aetheria code. -- Aetheria pins the same scene `0.1.1` release; no Aetheria Unity component +- Aetheria pins the same scene `0.1.4` release; no Aetheria Unity component constructs the generic CultMesh client transport used by that witness. - EveUnity can start from one CultMesh rendezvous endpoint, discover a Verse, select an advertised `interactive-world` surface, and connect without @@ -106,6 +106,9 @@ advertisements and typed command/receipt documents are the boundary. sessions and Norn serves graph projection and measurement through CultNet RUDP operation envelopes. A Sai surface may request nested Norn or TeX capabilities when those plugins are available. +- Norn owns the nested graph document, layout semantics, runtime advertisement, + ABI fixture, and conformance evidence. Eve's Sai contract fixture carries only + an `embed.norn` capability request and Norn-owned CultMesh document reference. - EvePlugins owns the TeX sidecar and KaTeX-backed semantic witness. - EvePlugins owns the independent `fields.surface` sidecar, generic `gamecult.fields.*` document model, fixtures, tests, and CultNet witness. @@ -114,7 +117,8 @@ advertisements and typed command/receipt documents are the boundary. - Aetheria publishes objects, gravity, and render splats as `gamecult.fields.*` documents and advertises `fields.surface` on graphical surfaces that use them. Hermodr carries decoded MessagePack values without - interpreting field tuples; the browser fields adapter owns that conversion. + interpreting field schemas or tuples; each runtime's Fields adapter owns + conversion and projection. - The browser fields adapter owns field component matching, canvas lifecycle, provider-document polling, tuple conversion, Canvas/WebGL projection, GPU resources, asset overlays, and capability-gap reporting. The general CultUI @@ -172,8 +176,6 @@ advertisements and typed command/receipt documents are the boundary. - Publish the shared TypeScript and Rust CultNet operation hosts from tagged CultLib releases. Sai, Norn, and TeX prove the same operation envelope across both language implementations; stdio remains a local diagnostic adapter. -- Graduate remaining graph fixture ownership fully to Norn once no Eve fixture - is its semantic owner. Sai already owns VN/Ink execution and its live witness. - Keep nested plugin availability explicit and non-transitive: Sai does not own Norn or TeX. - Move any remaining provider-specific field source composition only when it diff --git a/docs/surface-contract-v1.md b/docs/surface-contract-v1.md index 0275fa4..16ec3f9 100644 --- a/docs/surface-contract-v1.md +++ b/docs/surface-contract-v1.md @@ -169,27 +169,24 @@ typesetting semantics. `embed.norn` means "run Norn here." It is not a request to draw a static graph with whatever local widget happens to be nearby. -Minimum props: +Minimum boundary: -- `engine.id`: `norn` -- `engine.contract`: Norn surface contract id, currently - `gamecult.norn.surface.v1`. -- `engine.web.wasm`: optional WASM asset for browser clients. -- `engine.native.rustCrate`: native solver crate, usually `norn-rs`. -- `layout`: layout mode and solver knobs. -- `graph.nodes` and `graph.edges`: provider graph state. -- `interaction.nodeAction`: command emitted when a node is activated. -- `placement`: scene placement instructions. +- `pluginId`: `norn.graph`. +- `capability`: `embed.norn`. +- an embedded-document slot with `schemaId: norn.graph.document.v1` and a + provider/Norn-owned CultMesh `documentId`. +- `interaction.nodeAction`: command emitted to the composing provider when a + node is activated. +- `placement`: scene placement instructions owned by the composing surface. -Each target renderer embeds Norn by using the strongest local path available: +Graph nodes, edges, solver settings, runtime packages, WASM assets, and native +crate hints belong to Norn's document and plugin advertisement. They are not +inline Eve or Sai surface semantics. -- Web: Norn WASM / `@gamecult/norn-viewer`. -- iOS: native graph view using the Norn layout output, eventually Rust FFI. -- Android: native graph view using the same Norn contract, eventually Rust FFI. -- Windows Direct2D: Norn layout lowered to Direct2D/DirectWrite primitives. - -If a client cannot run Norn, it must report a capability gap. It may show a -read-only fallback, but it must not silently pretend the fallback is full Norn. +The Norn plugin advertisement declares its invocation and projection paths. A +runtime resolves the advertised `embed.norn` capability without importing Norn +internals. If it cannot do so, it must report a capability gap. It may show a +read-only fallback, but it must not silently claim full Norn support. ### `embed.tex` diff --git a/web/fixtures/sai-vn-surface.conformance.json b/web/fixtures/sai-vn-surface.conformance.json index 684a2a1..602b034 100644 --- a/web/fixtures/sai-vn-surface.conformance.json +++ b/web/fixtures/sai-vn-surface.conformance.json @@ -9,10 +9,11 @@ "path": "web/fixtures/sai-vn-surface.json" }, "purpose": "Proves a Sai-owned visual-novel plugin surface can compose independently-owned Norn and TeX nested surfaces.", - "exitCriteria": "Provider scenarios remain in Eve only as contract fixtures; Sai owns VN/Ink story state, commands, projection, and plugin ABI cases.", + "exitCriteria": "Provider scenarios remain in Eve only as contract fixtures; Sai owns VN/Ink state and commands, while Norn and TeX own their nested documents, semantics, runtime advertisements, and plugin ABI cases.", "asserts": [ "vn.stage component", "embed.norn component", + "Norn-owned embedded graph document reference", "embed.tex component", "story command descriptors", "required Sai plugin capability", diff --git a/web/fixtures/sai-vn-surface.json b/web/fixtures/sai-vn-surface.json index fc04a0f..4acecf9 100644 --- a/web/fixtures/sai-vn-surface.json +++ b/web/fixtures/sai-vn-surface.json @@ -57,43 +57,21 @@ { "id": "sai.graph", "kind": "embed.norn", + "embeddedDocuments": [ + { + "slotId": "graph", + "documentId": "cultmesh://norn/graphs/sai-nested-norn", + "schemaId": "norn.graph.document.v1", + "presentationKind": "embed.norn" + } + ], "props": { "label": "GameCult Ink Knot Graph", - "engine": { - "id": "norn", - "contract": "gamecult.norn.surface.v1", - "web": { - "wasm": "/static/interactive/norn/norn-rs.wasm", - "package": "@gamecult/norn-viewer" - }, - "native": { - "rustCrate": "norn-rs", - "direct2dAdapter": "fensalir.norn.direct2d" - } - }, - "layout": { - "mode": "norn.2d", - "iterations": 220, - "rankGap": 150, - "nodeGap": 88, - "edgeLength": 132 - }, - "graph": { - "nodes": [ - { "id": "hub", "label": "Hub", "target": "hub", "x": 0.2, "y": 0.2, "current": true }, - { "id": "eve", "label": "Eve", "target": "eve", "x": 0.72, "y": 0.24 }, - { "id": "sai", "label": "Sai", "target": "sai", "x": 0.48, "y": 0.55 } - ], - "edges": [ - { "source": "hub", "target": "eve" }, - { "source": "hub", "target": "sai" }, - { "source": "sai", "target": "eve" } - ] - }, + "pluginId": "norn.graph", + "capability": "embed.norn", "interaction": { "nodeAction": "story.jump", - "focusFollowsStory": true, - "rendererMustUseNorn": true + "focusFollowsStory": true }, "placement": { "space": "scene", diff --git a/web/fixtures/sai-vn.provider-advertisement.json b/web/fixtures/sai-vn.provider-advertisement.json index 1075429..e8bc5a3 100644 --- a/web/fixtures/sai-vn.provider-advertisement.json +++ b/web/fixtures/sai-vn.provider-advertisement.json @@ -34,6 +34,11 @@ "kind": "owner-repo", "ref": "Sai", "summary": "Sai owns VN/Ink semantics once the plugin ABI graduates; Eve only hosts this fixture while the contract is forming." + }, + { + "kind": "plugin-advertisement", + "ref": "Norn/plugins/norn-graph.plugin-advertisement.json", + "summary": "Norn owns graph documents, layout semantics, runtime hints, and graph conformance fixtures." } ], "surfaces": [ diff --git a/web/provider-advertisements.test.mjs b/web/provider-advertisements.test.mjs index bfeac43..2a15a6d 100644 --- a/web/provider-advertisements.test.mjs +++ b/web/provider-advertisements.test.mjs @@ -6,6 +6,7 @@ import { findProviderCatalogEntry, mergeProviderAdvertisement } from "./provider const catalog = JSON.parse(readFileSync(new URL("./local-provider-catalog.json", import.meta.url), "utf8")); const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8")); const saiAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/sai-vn.provider-advertisement.json", import.meta.url), "utf8")); +const saiSurface = JSON.parse(readFileSync(new URL("./fixtures/sai-vn-surface.json", import.meta.url), "utf8")); test("merges generic world fixture transport with advertised world interaction", () => { const catalogProvider = findProviderCatalogEntry(catalog, "eve.world-smoke"); @@ -43,3 +44,16 @@ test("preserves Sai required and optional nested plugin requirements from advert assert.deepEqual(byPlugin.get("tex.math").requiredCapabilities, []); assert.deepEqual(byPlugin.get("tex.math").optionalCapabilities, ["embed.tex"]); }); + +test("composes Norn by advertised capability and owner document", () => { + const norn = saiSurface.surface.root.children.find(component => component.kind === "embed.norn"); + const [graphDocument] = norn.embeddedDocuments; + + assert.equal(norn.props.pluginId, "norn.graph"); + assert.equal(norn.props.capability, "embed.norn"); + assert.equal(graphDocument.schemaId, "norn.graph.document.v1"); + assert.equal(graphDocument.documentId, "cultmesh://norn/graphs/sai-nested-norn"); + assert.equal("engine" in norn.props, false); + assert.equal("layout" in norn.props, false); + assert.equal("graph" in norn.props, false); +});