Thing/docs/plugin-architecture.md

442 lines
14 KiB
Markdown
Raw Normal View History

2026-07-07 01:10:35 +01:00
# 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
```json
{
"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:
```json
{
"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:
```json
{
"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:
```text
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:
```json
{
"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.