Thing/docs/plugin-architecture.md
2026-07-07 01:10:35 +01:00

14 KiB

Eve Plugin Architecture

Eve should be a presentation kernel, not a presentation monorepo.

The repo currently contains several renderer bodies because they were useful proof vehicles: browser reference, iOS/UIKit, Android/Kotlin, Flutter parity, Unity packages, and fixture lowerers. That does not mean every renderer, domain representation, and product client should live in Eve forever. The durable architecture is smaller and sharper:

  • Eve core owns the surface/control contract.
  • Plugins own domain representation semantics.
  • Renderer clients own runtime substrate.
  • Providers own accepted state and side effects.
  • Odin/CultMesh owns discovery and routing.

The kernel should be boring enough that new renderers can prove conformance without joining a single giant repo, and domain plugins can add expressive surface kinds without forcing Eve core to absorb every product vocabulary.

Authority

Eve core owns:

  • gamecult.eve.surface.v1 and related surface schemas;
  • gamecult.eve.command.v1 and operation binding/invocation descriptors;
  • CultUI authoring and lowering contracts;
  • plugin manifest schema and discovery rules;
  • renderer capability reporting;
  • conformance and parity harness expectations;
  • core component kinds that are genuinely cross-domain.

Plugins own:

  • domain component kinds;
  • domain command semantics;
  • domain state projection rules;
  • plugin-local fixtures and conformance cases;
  • compatibility adapters for existing libraries or products.

Renderer clients own:

  • native projection into their runtime;
  • input capture and command emission;
  • local sensor/media capture when relevant;
  • runtime-specific capability gaps;
  • screenshots, frame capture, and performance telemetry.

Providers own:

  • live state;
  • command acceptance or refusal;
  • receipts;
  • reconciliation;
  • side effects.

Odin/CultMesh owns:

  • provider advertisement discovery;
  • plugin advertisement discovery;
  • document routing;
  • freshness and availability view;
  • replay/subscription substrate.

Why Not A Monorepo

The monorepo path is tempting because Eve already has several client bodies. It would make local edits easy at first: add Sai, absorb Aetheria's Electron client, add Unity full-scene lowering, add Godot, add Direct2D, keep everything under one Makefile. That is how a kernel becomes a warehouse with a logo.

The long-term costs are worse:

  • Unity, Electron, Godot, Swift/UIKit, Kotlin/Android, Flutter, browser, and native Direct2D have different build systems and failure modes.
  • Product semantics such as visual novels, RTS overlays, ARPG world-space HUDs, graph maps, proof fields, and sensor fusion views change at different speeds.
  • Renderer clients need to release, test, and deploy independently.
  • A domain plugin should be able to evolve without asking every renderer body to accept its source tree.
  • Eve core should stay small enough that a renderer author can understand the contract without learning every GameCult product.

Eve can keep reference implementations and first-party packages while the contract forms. The target is not "everything leaves Eve tomorrow." The target is "nothing joins Eve core unless it protects a core invariant."

Plugin Model

An Eve plugin extends the representation vocabulary. It does not become a provider and does not accept commands on behalf of an app.

Plugin responsibilities:

  • declare component kinds it introduces;
  • declare command names or operation descriptors it introduces;
  • declare state document schemas it can project or consume;
  • provide renderer-agnostic semantics for those kinds;
  • provide fallback behavior when a renderer lacks support;
  • provide fixtures for the parity harness;
  • optionally provide reference lowerers for web or another runtime.

Plugins may be distributed as separate repos, package artifacts, or in-repo packages during incubation. The ownership rule is semantic, not packaging ceremony: the plugin owns the representation shape, while providers own live state and renderer clients own projection.

Example Plugin Manifest

{
  "schema": "gamecult.eve.plugin.v1",
  "pluginId": "sai.vn",
  "title": "Sai Visual Novel",
  "owner": "sai",
  "version": "0.1.0",
  "componentKinds": [
    "vn.stage",
    "panel.dialogue",
    "text.dialogue",
    "rail.actions"
  ],
  "commands": [
    {
      "command": "story.continue",
      "schema": "sai.vn.story_command.v1",
      "effect": "advance-story"
    },
    {
      "command": "story.choose",
      "schema": "sai.vn.story_command.v1",
      "effect": "choose-branch"
    },
    {
      "command": "story.jump",
      "schema": "sai.vn.story_command.v1",
      "effect": "jump-to-knot"
    }
  ],
  "stateSchemas": [
    "sai.vn.story_session.v1",
    "sai.vn.visual_manifest.v1"
  ],
  "fixtures": [
    {
      "fixtureId": "sai.vn.basic-scene",
      "schema": "gamecult.eve.surface.v1",
      "path": "fixtures/basic-scene.surface.json"
    }
  ],
  "capabilities": {
    "requires": [
      "image.background",
      "control.button"
    ],
    "optional": [
      "embed.norn",
      "embed.tex",
      "scene.placement.keystone"
    ]
  }
}

The exact fields can change, but the invariant should not: plugin metadata must be enough for Eve, Odin, and renderer clients to know what semantics are being introduced, what fixtures prove them, and what capability gaps must be reported instead of hidden.

Discovery

There are two different discoveries. Do not collapse them.

Provider discovery answers:

What live thing owns state and accepts commands?

Plugin discovery answers:

What representation vocabulary and command semantics are needed to render and interact with this surface correctly?

Provider advertisements remain gamecult.eve.provider_advertisement.v1. Plugin advertisements should be a separate document family, gamecult.eve.plugin_advertisement.v1, or a typed plugin catalog referenced from provider advertisements.

Recommended discovery path:

  1. A plugin publishes a plugin advertisement through CultMesh/Odin or ships a local package manifest.
  2. A provider advertisement names the plugins its surfaces require.
  3. Odin indexes providers and plugins separately.
  4. A renderer asks Odin for the provider surface and the required plugin manifests.
  5. The renderer checks local plugin support.
  6. Unsupported required plugin capability becomes a visible capability gap.
  7. Optional unsupported capability becomes a visible degraded lowering.

Provider advertisement sketch:

{
  "schema": "gamecult.eve.provider_advertisement.v1",
  "providerId": "aetheria.rts.commander",
  "surfaces": [
    {
      "surfaceId": "aetheria.rts.commander.vn-briefing",
      "schema": "gamecult.eve.surface.v1",
      "requiresPlugins": [
        {
          "pluginId": "sai.vn",
          "versionRange": "^0.1.0",
          "requiredCapabilities": [
            "vn.stage",
            "story.choose"
          ]
        }
      ]
    }
  ]
}

Plugin advertisement sketch:

{
  "schema": "gamecult.eve.plugin_advertisement.v1",
  "pluginId": "sai.vn",
  "ownerService": "sai",
  "version": "0.1.0",
  "manifestAddress": "cultmesh://asgard.sai/plugins/vn/manifest",
  "schemas": [
    "sai.vn.story_session.v1",
    "sai.vn.visual_manifest.v1"
  ],
  "componentKinds": [
    "vn.stage"
  ],
  "commands": [
    "story.continue",
    "story.choose",
    "story.jump"
  ],
  "fixtures": [
    "cultmesh://asgard.sai/plugins/vn/fixtures/basic-scene"
  ]
}

Local development may use file manifests, but live discovery should flow through Odin/CultMesh. A renderer must not scrape random repo paths to guess plugin support.

Sai As The VN Plugin Owner

Sai should own Eve's VN/Ink representation plugin.

Sai's current body is already crisp:

  • static-site Ink loading through inkjs;
  • visual manifest interpretation;
  • DOM rendering for static sites;
  • static-site auto-init;
  • Eve surface projection through eve.js.

Sai should not become a daemon merely because Eve needs VN semantics. Sai can own the plugin because it understands Ink, visual manifests, choices, story state, scene assets, and static-site constraints. The active app or provider still owns live session state and receipts.

Sai plugin responsibilities:

  • define vn.stage semantics;
  • define story state projection shape;
  • define story.continue, story.choose, and story.jump semantics;
  • define visual manifest schema as Eve-facing representation input;
  • define optional embedded scene affordances such as Norn/TeX placement when they are used inside VN scenes;
  • provide fixtures for Eve parity;
  • provide a web/static-site reference implementation.

Sai does not own:

  • Aetheria story session state;
  • RTS or ARPG command acceptance;
  • multiplayer synchronization;
  • save/load authority;
  • Unity/Electron/Godot renderer bodies;
  • CultMesh provider routing.

Building An App With Eve And Sai

An app that wants VN functionality composes four layers.

  1. App provider owns live state. For Aetheria, this might be aetheria.rts.commander and aetheria.arpg.world. Each provider owns its own current story/session state, save/load integration, eligibility, command acceptance, and receipts.

  2. Sai plugin owns VN representation. The provider uses Sai's plugin schema to express story state as vn.stage, choices, speaker, line, scene, sprites, manifest-backed assets, and story commands.

  3. Eve owns surface/control contract. Eve validates that the surface and command descriptors conform to gamecult.eve.surface.v1, the plugin manifest, and operation binding rules.

  4. Renderer client owns projection. Electron, Unity UI Toolkit, Unity scene, Godot, browser, or Direct2D lower the same surface locally and emit commands back to the provider's advertised route.

The app-provider receipt is the proof, not the button click. A Unity ARPG scene and an Electron RTS shell can both emit story.choose; the Aetheria provider decides whether that command is accepted in that runtime, records the receipt, and republishes state.

Aetheria RTS And ARPG Example

Aetheria may need VN functionality in two runtimes:

  • RTS layer: commander briefings, dialogue overlays, strategic decisions, lore panels, faction negotiations, mission choices.
  • ARPG layer: in-world conversations, diegetic prompts, scene dialogue, cinematic overlays, localized interaction choices.

Sai should not care which runtime is displaying the VN. Sai should care about the VN representation:

  • current story path/knot;
  • speaker;
  • dialogue line;
  • choices;
  • scene/background/sprites;
  • visual manifest references;
  • story commands;
  • optional embedded surfaces.

Aetheria owns the runtime-specific meaning:

  • whether a choice is allowed during combat;
  • whether a dialogue pauses simulation;
  • whether RTS and ARPG sessions share story state;
  • whether a choice writes to save state;
  • whether multiplayer/authority rules allow the command;
  • whether a receipt is visible to the player, operator, or both.

Renderer clients own projection:

  • Electron RTS may lower vn.stage as a 2D overlay panel or mission briefing.
  • Unity UI Toolkit may lower the same surface as a native 2D UI document.
  • Unity full-scene renderer may place dialogue in world space or cinematic scene space.
  • Godot may lower the same plugin semantics into Godot controls or scene nodes.

The shared command path:

renderer click
  -> Eve command envelope
  -> Aetheria provider command route
  -> Aetheria accepts/denies story command
  -> provider writes receipt
  -> provider republishes surface state
  -> renderer updates from provider state

No runtime gets to decide the story consequence just because it rendered the button.

Renderer Client Repos

Long-term renderer/client bodies should split out when they become real apps:

  • EveElectron2D or Aetheria-owned Electron client for RTS/editor/operator 2D.
  • EveUnityUIToolkit for Unity UI Toolkit 2D lowering.
  • EveUnityScene or Aetheria-owned Unity scene frontend for full game 2D/3D.
  • EveGodot for Godot lowering.
  • Fensalir for Direct2D/native runtime lowering if Fensalir remains the body.
  • EveWebReference can stay in Eve core as the canonical reference renderer unless it grows product-specific runtime ownership.

While these live in the Eve repo as proofs, they should behave like clients of the plugin ABI. They should not be allowed to reach into plugin source or provider internals as a convenience.

Conformance

Every plugin needs conformance fixtures. Every renderer that claims plugin support needs to run them.

For Sai VN:

  • basic vn.stage scene;
  • choice command fixture;
  • continue command fixture;
  • visual manifest asset fixture;
  • embedded Norn/TeX optional fixture;
  • degraded renderer fixture for clients without scene placement;
  • receipt round-trip fixture driven by a fake provider.

The parity manifest should be extended so runtimes declare plugin support:

{
  "runtimeId": "aetheria.unity.uitoolkit",
  "supportedPlugins": [
    {
      "pluginId": "sai.vn",
      "capabilities": [
        "vn.stage",
        "story.choose",
        "story.continue"
      ]
    }
  ]
}

A renderer can be honest without being complete. Unsupported features should show up as capability gaps, not disappear into a local approximation.

Repo Strategy

Recommended path:

  1. Keep Eve core in E:/Projects/Eve.
  2. Define gamecult.eve.plugin.v1 and gamecult.eve.plugin_advertisement.v1.
  3. Let Sai publish the first domain plugin manifest for VN/Ink.
  4. Add Sai VN fixtures to Eve parity as plugin fixtures, not Eve core fixtures.
  5. Make the browser reference load plugin manifests and report missing capabilities.
  6. Spin renderer clients out as they become real bodies.
  7. Keep in-repo clients only as references or incubation bodies with explicit exit criteria.

Absorbing Sai into Eve is the fallback if the plugin boundary fails. It should not be the first move. The cleaner machine is Eve as kernel, Sai as VN plugin, Aetheria as provider, and renderer clients as separate bodies.