Post Step 4 Completion

This commit is contained in:
2026-09-14 07:57:18 -07:00
parent db973a86f3
commit 446bf3533e
52 changed files with 32034 additions and 3 deletions
@@ -0,0 +1,855 @@
# XZBT-NGN Exhibit Engine Implementation Plan
## Version 5.2
**Status:** Architectural implementation plan
**Product:** XZBT-NGN
**Product identity:** XZBT-NGN Exhibit Engine
**Dependency:** XZBT Exhibit Contract 5.2
**Current implementation state:** Not yet implemented
---
## 1. Purpose
XZBT-NGN is the host, orchestration, integration, recording, authoring, administration, and packaging environment for XZBT-compatible exhibits.
It does not replace the exhibit.
It extends it.
Version 5.2 assumes no legacy NGN implementation and treats the XZBT Exhibit Contract as the architectural dependency NGN must follow.
---
## 2. Product Boundary
The intended ecosystem is:
### Free standalone exhibit
A complete independently usable experience.
### Free packaged authored experience
A self-running artifact exported by NGN that may contain scenario data and a compact runtime.
### Premium XZBT-NGN Exhibit Engine
Provides:
- hosting;
- orchestration;
- recording;
- scenario authoring;
- webhooks;
- telemetry connectors;
- external automation;
- administration;
- packaging;
- remote control;
- integrations.
The commercial value comes from added capability, not intentional degradation of free exhibits.
---
## 3. Primary Responsibilities
NGN owns:
- contract session management;
- exhibit discovery;
- capability cataloging;
- state synchronization;
- event monitoring;
- scenario runtime;
- scenario recording;
- timeline authoring;
- webhook endpoints;
- connector framework;
- telemetry mapping;
- automation;
- administration;
- diagnostics;
- packaging;
- distribution support;
- future multi-exhibit orchestration.
---
## 4. Contract-First Rule
NGN MUST interact with exhibits through the XZBT Exhibit Contract target surface.
Direct exhibit-specific escape hatches are development diagnostics, not architecture.
NGN MUST dynamically discover targets.
Core NGN MUST NOT hard-code SciFi-XZBT target vocabulary.
---
## 5. Initial MVP
The first useful NGN proves only the contract boundary.
It should:
1. host or attach to one SciFi-XZBT instance;
2. complete contract handshake;
3. request `describe`;
4. render discovered capabilities and targets;
5. request state;
6. invoke and set selected targets;
7. receive and display events;
8. display sequence and state revision;
9. detect and recover synchronization loss;
10. log errors.
This milestone exists before scenario authoring, recording, webhooks, or connectors.
---
## 6. Exhibit Hosting
NGN SHOULD be able to host compatible HTML exhibits from a stable local origin.
Hosting enables:
- same-origin communication;
- controlled dependency delivery;
- predictable asset paths;
- future local Web3D hosting;
- packaging support;
- browser receiver support.
Hosting is not required for the ordinary standalone artifact.
---
## 7. Session Management
NGN maintains one logical contract session per connected exhibit instance.
Track:
- exhibit identity;
- contract version;
- session ID;
- registry revision;
- state revision;
- expected event sequence;
- capabilities;
- target descriptors;
- current state;
- connection status.
When a new session is established, reset expected event sequence and session-scoped correlation state.
---
## 8. Discovery Catalog
NGN builds its control catalog from `describe`.
Generic UI mapping:
```text
range -> slider / numeric control
state -> state editor / toggle where appropriate
selection -> dropdown / list
impulse -> trigger control
```
`kind: impulse` is sufficient to infer invokability.
Exhibit-provided labels may drive UI display.
Core behavior does not depend on presentation hints.
---
## 9. Registry Revision Handling
NGN records `registryRevision` from `describe`.
On `registry.changed`, NGN MUST schedule a fresh `describe`.
Use a short debounce so a burst of registry changes results in one rediscovery.
Recommended initial debounce:
```text
100 ms
```
If the exhibit uses a fixed registry, ordinary context changes should not trigger rediscovery.
---
## 10. State Synchronization
NGN keeps a local mirror of contract-visible persistent state.
Synchronization strategy:
1. request initial `state.get`;
2. record `stateRevision`;
3. apply normalized state/selection events;
4. track event sequence;
5. resynchronize on uncertainty.
Resync triggers include:
- event sequence gap;
- state revision gap that cannot be explained by one received mutation transaction;
- reconnect/new session;
- explicit `registry.changed` requiring rediscovery;
- configurable synchronization timeout;
- parse/validation failure on a state event.
On resync, request fresh `state.get`.
---
## 11. Feedback-Loop Prevention
NGN respects authoritative event `source`.
Default recorder sources:
```text
ui
midi
hotkey
```
Optional:
```text
host
```
Normally excluded:
```text
scenario
internal
system
```
Scenario playback does not record itself by default.
Host-triggered integrations do not recursively retrigger identical integrations unless explicitly configured.
---
## 12. Scenario Model
A scenario is an NGN-owned orchestration document.
The ordinary exhibit does not need to understand the document.
A scenario may include:
- timed state changes;
- impulses;
- speech;
- waits;
- marks;
- tracks;
- ramps;
- holds;
- conditions;
- repetition;
- random variation;
- external signals;
- transitions;
- metadata;
- required capabilities;
- cleanup policy.
---
## 13. Scenario Format
Scenario schema is distinct from the XZBT Exhibit Contract.
It references canonical contract target IDs.
Example:
```json
{
"format": "xzbt-scenario",
"version": 1,
"requires": ["speech"],
"timeline": [
{
"at": 0,
"op": "set",
"target": "view.observation",
"value": true
},
{
"at": 2000,
"op": "invoke",
"target": "speech.say",
"args": {
"text": "Approaching docking perimeter."
}
}
]
}
```
Scenario data is inert data.
It MUST NOT contain arbitrary executable JavaScript, CSS, selectors, or host-language expressions.
The scenario format should eventually receive its own specification.
---
## 14. Scenario Validation
Before playback, validate:
- scenario schema;
- target existence;
- target kind;
- writable/invokable semantics;
- required capability state;
- value ranges;
- selection options;
- optional exhibit/product compatibility constraints.
Invalid operations should be surfaced before playback where possible.
---
## 15. Scenario Clock
NGN owns the live scenario clock.
Initial support:
- millisecond timing;
- pause/resume;
- marks;
- loops;
- holds.
Later support may include:
- variable speed;
- repeat regions;
- musical timing;
- advanced conditions.
Browser-hosted exhibit timing is application-level timing, not sample-accurate show control.
---
## 16. State Capture and Cleanup
Scenario playback may capture relevant initial state.
Cleanup policy may:
- leave final state;
- restore captured state;
- restore selected targets only.
Impulse targets are never restorable.
Persistent state targets are restorable by default unless the exhibit explicitly sets `restorable: false`.
---
## 17. Scenario Recorder
NGN records normalized exhibit events using canonical target IDs.
Default sources:
```text
ui
midi
hotkey
```
Optional:
```text
host
```
Normally excluded:
```text
scenario
internal
system
```
The recorder never stores internal SciFi-XZBT IDs.
---
## 18. Recorder Compression
Continuous controls may generate many events.
Later compression may include:
- threshold reduction;
- sample reduction;
- ramp fitting;
- minimum time interval.
This is deferred optimization, not MVP behavior.
---
## 19. Timeline Authoring
Future authoring may support:
- tracks;
- marks;
- ranges;
- impulses;
- speech;
- ramps;
- holds;
- loops;
- conditions;
- external triggers;
- annotations;
- metadata.
The first editor can be much simpler.
---
## 20. Webhook Engine
Webhooks terminate at NGN.
NGN handles:
- authentication;
- authorization;
- validation;
- rate limiting;
- routing;
- logging;
- translation.
Webhook actions may target:
- exhibit contract operations;
- NGN scenario controls;
- telemetry mappings;
- connector commands.
The standalone exhibit never needs an HTTP listener.
---
## 21. Connector Framework
NGN may later provide connectors for:
- Streamer.bot;
- OBS-related systems;
- REST;
- MQTT;
- serial devices;
- MIDI;
- simulators;
- game servers;
- home automation;
- future MCP integrations.
Connectors normalize external inputs into:
- exhibit contract operations;
- NGN scenario controls;
- NGN telemetry updates.
---
## 22. Telemetry Mapping
Structured telemetry is optional per exhibit.
NGN MUST discover whether telemetry targets actually exist.
It must not assume that SciFi-XZBT has fictional telemetry fields merely because the contract supports them.
When writable telemetry exists, NGN may provide:
- scaling;
- unit conversion;
- clamping;
- smoothing;
- update-rate control;
- stale-data policy;
- ownership lease renewal;
- fallback to simulation.
---
## 23. Telemetry Staleness
For external telemetry mappings, NGN SHOULD define stale-data behavior.
Options may include:
- keep last value;
- mark stale;
- release ownership;
- return to exhibit simulation;
- invoke fallback value.
The mapping configuration should make this explicit.
---
## 24. Speech Integration
NGN may send explicit text to an exhibit's `speech.say` target.
Potential sources include:
- scenario text;
- webhook payload;
- generated dialogue;
- simulator data;
- operator input.
NGN does not reproduce the exhibit's speech engine.
---
## 25. Scenario Packaging
NGN SHOULD eventually generate self-running packaged HTML experiences.
Concept:
```text
Base Exhibit
+
Scenario Data
+
Compact Scenario Runtime
+
Required Embedded Configuration
|
v
Packaged HTML
```
The packaged artifact may run without NGN.
---
## 26. Shared Scenario Runtime Core
Live and packaged playback MUST share one scenario-runtime core.
The runtime MUST drive the exhibit through the same canonical target surface used in live NGN operation.
It MUST NOT bypass contract semantics by calling arbitrary exhibit internals.
The runtime transport is pluggable:
```text
live NGN -> host transport / postMessage
packaged HTML -> direct in-process contract adapter
```
This keeps scenario semantics aligned while avoiding unnecessary host-bridge overhead inside a packaged artifact.
---
## 27. Compact Packaged Runtime
The exported runtime includes only execution functionality needed by the package.
It may include:
- scenario clock;
- set/invoke dispatch;
- waits;
- marks;
- loops;
- required conditions;
- cleanup logic.
It does not include:
- authoring UI;
- recorder;
- webhook server;
- connector framework;
- NGN administration;
- multi-exhibit management.
---
## 28. Packaged Exhibit Contract Access
A packaged exhibit retains the same canonical exhibit target surface.
The embedded runtime is a local consumer of that surface.
External host attachment may remain available if the base exhibit supports it, but is not required for packaged scenario execution.
---
## 29. Package Size Discipline
The compact scenario runtime should remain small relative to the exhibit.
Packaging SHOULD report:
- base exhibit size;
- runtime size;
- scenario-data size;
- final package size.
No hard numeric limit is imposed in v5.2, but the runtime SHOULD NOT approach the size of the base exhibit unless a future capability explicitly justifies it.
---
## 30. Administration
Future NGN administration may include:
- installed exhibits;
- running exhibits;
- session health;
- discovered capabilities;
- target catalog;
- current state;
- action testing;
- scenarios;
- webhook configuration;
- connectors;
- telemetry mappings;
- logs;
- packaging tools.
---
## 31. Security
Because NGN may expose local or network services, it owns the heavier trust boundary.
Eventually address:
- authentication;
- authorization;
- webhook tokens;
- origin restrictions;
- input validation;
- rate limiting;
- connector permissions;
- bind-address defaults;
- TLS guidance;
- secret storage;
- audit logging;
- least privilege.
The exhibit still validates every contract message.
---
## 32. Network Defaults
NGN SHOULD default to safe local binding.
Network exposure must be explicit.
A premium feature must not accidentally become an unauthenticated LAN or Internet control surface.
---
## 33. Logging and Diagnostics
NGN should maintain structured logs for:
- session connection;
- version negotiation;
- describe results;
- registry changes;
- state resync;
- scenario actions;
- webhook requests;
- connector errors;
- capability failures;
- packaging operations.
Logging should support diagnosis without requiring browser developer tools.
---
## 34. Version Compatibility
Track separately:
- NGN application version;
- XZBT contract version;
- exhibit product version;
- scenario format version;
- registry revision;
- state revision.
These are distinct concepts.
---
## 35. Implementation Phases
### Phase 0 - Contract harness
Build a small synthetic exhibit and host test harness for XZBT Contract 5.2.
### Phase 1 - Single exhibit host
Host SciFi-XZBT and establish a session.
### Phase 2 - Dynamic discovery UI
Render capabilities, targets, state, and events.
### Phase 3 - State synchronization
Implement revision and sequence tracking plus resync behavior.
### Phase 4 - Minimal scenario runtime
Support timed `set` / `invoke` operations and cleanup.
### Phase 5 - Recorder
Capture canonical operator events.
### Phase 6 - Scenario persistence and basic editing
Save/load scenario documents and provide basic editing.
### Phase 7 - Webhooks
Add authenticated external triggers.
### Phase 8 - Telemetry connector framework
Only after at least one exhibit exposes structured writable telemetry.
### Phase 9 - Integration adapters
Add Streamer.bot and other useful adapters.
### Phase 10 - Packaging
Export self-running HTML using the shared runtime core.
### Phase 11 - Advanced authoring
Add richer timeline features, ramps, conditions, musical timing, and repeat regions.
### Phase 12 - Multi-exhibit engine
Add orchestration across multiple sessions when justified.
---
## 36. MVP Acceptance Criteria
NGN MVP succeeds when:
- one SciFi-XZBT exhibit can be hosted or attached;
- contract handshake succeeds;
- `describe` renders dynamically;
- current state is displayed;
- representative state can be changed;
- representative impulses can be triggered;
- events are displayed with source and sequence;
- state revision changes are tracked;
- reconnect resets sequence expectations;
- lost synchronization triggers fresh `state.get`;
- registry changes trigger debounced rediscovery;
- no SciFi-specific target vocabulary is hard-coded into core NGN logic.
---
## 37. Scenario Milestone Acceptance Criteria
The first scenario milestone succeeds when:
- a simple scenario document loads;
- target references validate against `describe`;
- timed set/invoke operations execute;
- scenario-origin events do not recursively record themselves;
- initial-state restore works for restorable targets;
- abort cleanup is predictable;
- the same runtime core can be used by the packager.
---
## 38. Packaging Milestone Acceptance Criteria
Packaging succeeds when:
- a base exhibit and scenario combine into one distributable HTML;
- the package runs without NGN;
- the scenario runtime drives the canonical target surface;
- no authoring/admin/webhook stack is included;
- live and packaged runtime behavior matches for supported operations;
- package size is reported;
- the ordinary exhibit remains independently usable outside the package process.
---
## 39. Deferred Features
Intentionally deferred:
- musical timing;
- complex condition language;
- multi-exhibit synchronization;
- distributed receivers;
- cloud orchestration;
- advanced telemetry transforms;
- generic plugin marketplace;
- arbitrary scripting;
- sample-accurate show control.
---
## 40. Architectural Rule
Use the same test for every proposed feature.
**Does the exhibit need it to remain a complete standalone exhibit?**
Then it may belong in the exhibit.
**Does it coordinate, automate, connect, record, author, distribute, or externally control exhibits?**
Then it belongs primarily in XZBT-NGN.
---
## 41. Version 5.2 Summary
Version 5.2 preserves the Version 5 architecture and hardens the implementation boundary.
The first responsibility of NGN remains proving reliable discovery, observation, synchronization, and control.
Scenario authoring, webhooks, telemetry, integrations, and packaging all build on that verified contract surface.
@@ -0,0 +1,577 @@
# XZBT Exhibit Authoring Guide
## Version 5.2
**Status:** Authoring guidance (non-normative except where explicitly marked)
**Companion to:** XZBT Exhibit Contract Specification 5.2
**Audience:** developers and coding agents building a new XZBT-compatible exhibit
---
## A. Purpose and Audience
This guide explains how to structure a new, standalone exhibit so that it correctly implements the XZBT Exhibit Contract from the start, without absorbing responsibilities that belong to the XZBT-NGN Exhibit Engine.
It is written for two kinds of reader: a human developer building an exhibit by hand, and a coding agent implementing one from a specification. Both should be able to follow it without prior exposure to any specific existing exhibit.
Two documents work together and answer different questions:
- The **XZBT Exhibit Contract Specification** defines the external behavior a conforming exhibit MUST, SHOULD, or MAY exhibit at the message and semantics level: the handshake, the message envelope, target kinds, revisions, events, capabilities, and error codes.
- This **Authoring Guide** explains a recommended internal implementation structure that makes satisfying that contract straightforward, consistent, and maintainable, and calls out the mistakes that make it hard.
Where this guide uses MUST, MUST NOT, SHOULD, or SHOULD NOT for something that is not already a contract requirement, that is an authoring recommendation, not a new contract rule. Every such case is called out explicitly. Nothing in this guide expands, narrows, or reinterprets Contract 5.2.
An example exhibit, SciFi-XZBT, is referenced throughout for concreteness. Nothing here is specific to that exhibit's science-fiction subject matter, and none of its vocabulary should be treated as universal. A train exhibit, an aviation exhibit, a castle exhibit, and any future XZBT exhibit are equally valid, and this guide is written so that none of them inherit assumptions that only make sense for a starship bridge.
---
## B. Core Design Principle: Standalone First
**CONTRACT REQUIREMENT.** Contract 5.2 §2 states that an XZBT-compatible exhibit MUST remain operable without XZBT-NGN, and that connection to XZBT-NGN is additive.
This is the single governing constraint on everything else in this guide. In practice it means an exhibit:
- MUST run meaningfully with no host attached at all — full native UI, full native behavior, full native content.
- MUST NOT require a handshake, a session, or any host message to initialize, animate, play audio, or respond to local input.
- MUST NOT require network access, a local server, or any orchestration layer for ordinary operation.
- MUST preserve its native UI's own behavior and feel regardless of whether a host is attached.
- MUST remain a complete, self-contained artifact a person can open and use with nothing else running.
**AUTHORING RECOMMENDATION.** The contract layer should be dormant, not merely tolerant, when no host is present. Concretely: the exhibit boots, and only when (and if) a host later sends `hello` does any session-related code path activate. A useful test during development is to delete every host-detection code path and confirm the double-clicked file behaves identically. If a future host connects, its presence should feel like an enhancement layered on top of a complete experience, never like flipping the exhibit into a different mode of existence.
---
## C. Recommended Internal Architecture
**AUTHORING RECOMMENDATION.** No specific framework, build system, or language is required. Plain HTML, CSS, and JavaScript with no framework at all remains completely valid, and a conforming exhibit may equally be built with a modern framework, a game engine's UI layer, or a native application shell rendering through the same message-passing conventions where applicable. What matters is the separation of concerns below, not the tooling used to express it.
A recommended internal separation, roughly in dependency order (each layer may call the ones above it, and generally should not be called by them):
1. **Exhibit state/model.** The actual data that constitutes the exhibit's condition: is the transport running, what is the master volume, which scene or theme is selected, what alert state is active. This is the source of truth `state.get` eventually reads from.
2. **Exhibit services/behavior.** The logic that changes the model in response to something happening: starting playback, applying a scene, running the simulation loop, synthesizing a sound. This layer owns the actual mutation of state 1 and should not care who or what asked for the mutation.
3. **Native UI.** Buttons, sliders, dropdowns, canvases, whatever the exhibit's own screen presents. This layer calls into layer 2 and renders the current state from layer 1. It has no privileged access to contract internals and no special powers the contract adapter lacks.
4. **Canonical control layer.** One set of functions — the mutation/invoke chokepoint described in section H — that every input source (UI, hotkeys, host) calls to change state or trigger an action. This is the layer that makes source attribution, transaction semantics, and idempotency actually enforceable, because everything funnels through it.
5. **Contract adapter.** The layer that knows about XZBT vocabulary: canonical target IDs, descriptors, `describe`, `state.get`, event formatting, capability reporting. It translates between the canonical control layer's internal calls and the wire-level contract semantics. It does not itself contain exhibit behavior.
6. **Optional host transport.** The thin layer that moves contract messages across whatever channel is in use (same-origin `postMessage`, a wrapper binding, a local IPC channel). It has no knowledge of exhibit semantics; it only frames, validates origin, and forwards.
The dependency direction that matters most: layers 13 must work with layers 46 entirely absent. Layer 6 must be swappable without touching layers 14. A helpful gut check while designing any new piece of functionality is to ask which of these six things it is, and to resist the temptation to let a contract concern (layer 56) leak down into exhibit behavior (layer 13), or an exhibit behavior concern leak up into the adapter.
---
## D. Canonical Target Registry
**CONTRACT REQUIREMENT.** Target IDs use a canonical dotted namespace (Contract §8): lowercase ASCII letters, digits, hyphens and dots, beginning with a lowercase letter, at least one dot-separated segment boundary, no whitespace, no empty segments, and never interpreted as a JavaScript property path.
**AUTHORING RECOMMENDATION.** Design the registry as one stable, deliberately curated public catalog: canonical dotted IDs, each with a declared kind (state, range, selection, or impulse), current value where applicable, and a clear category. This catalog is what a generic host discovers through `describe`; it should describe the exhibit's meaning, not its markup.
Target IDs should name what a thing *means* to an operator, not where it lives in the DOM or how it happens to be wired internally.
Good examples:
```text
transport.playing
environment.intensity
mode.selected
event.pulse
```
Bad examples:
```text
button-14
slider-left
panelB.knob2
```
The difference is not cosmetic. A meaningful ID survives a UI redesign, a relayout, or a rewrite of the exhibit's internals, because it describes a concept the exhibit owns rather than a widget the exhibit happens to render today. `button-14` tells a host nothing about what will happen if it is invoked and becomes meaningless the day the button moves.
**AUTHORING RECOMMENDATION.** Adopt a fixed catalog rather than a catalog that changes shape as the exhibit's internal context changes. A target that only makes sense in one mode, theme, or scene should still generally remain in the catalog and simply report `CAPABILITY_UNAVAILABLE` (or an equivalent contextual error) when invoked outside that context, rather than disappearing and reappearing. Registry churn driven by ordinary context changes forces every attached host to rediscover the whole catalog constantly, which is expensive and provides no real benefit — the fixed-catalog approach lets a host build its control surface once and treat temporary unavailability as a normal, expected state rather than a structural surprise. See Section N for the discoverable-versus-invokable distinction this depends on.
---
## E. Target Descriptor Design
**CONTRACT REQUIREMENT.** Each target returned by `describe` must include enough metadata for a generic host to inspect and operate it (Contract §9): `id`, `kind`, `readable`, `writable`, `restorable`, `category`, `requires`, and kind-specific fields — `min`/`max`/`step` for range, `valueType` for state, `options` for selection. Where a target accepts structured arguments, those arguments must be validated against a declared schema (Contract §11).
**CONTRACT REQUIREMENT.** Descriptors must reflect real behavior. The contract must not advertise fake actions, unsupported values, or nonexistent capabilities. A published target that can never successfully execute in any context, for reasons no declared capability explains, is a describe-accuracy defect, not a permissible "discoverable but unavailable" case — that allowance exists specifically for context-gated unavailability tied to a real, declared capability (Contract §9, §17).
**AUTHORING RECOMMENDATION — the lesson from Step 3.** During SciFi-XZBT's Step 3 verification, several range descriptors advertised a wider span than the exhibit's own native slider could select, and in one case a native slider could select a value the descriptor explicitly forbade. The forbidden-but-selectable case is the dangerous direction: it lets a normal, local, unremarkable action (dragging a slider) produce a value the exhibit has told every host is impossible, which the canonical mutation path then has to silently reject — the thumb moves, nothing else does, and the operator gets no explanation.
The general rule this produced: **the native UI and the public contract range for the same underlying value should represent one coherent operator-facing range.** If the exhibit's own control can only usefully reach a narrower or coarser range than the descriptor claims, that is usually fine and often desirable — the descriptor can legitimately describe more of the backing capability than any one local widget exposes. If the native control can reach *further* or *finer* than the descriptor claims, that is a real defect regardless of how it happened, because a local, ordinary interaction is now able to produce a contract-invalid state. Author descriptors and native controls together, and treat "control broader than contract" as a bug class to check for explicitly during development, not just at final review.
---
## F. State Design
**CONTRACT REQUIREMENT.** Persistent state belongs in `state.get`; impulses do not appear in a restorable snapshot (Contract §10, §13). A state snapshot must not imply that every internal exhibit variable is externally exposed — only readable persistent targets belong in `values`.
**AUTHORING RECOMMENDATION.** Snapshots should contain stable, externally meaningful state: the things an operator or an authored scenario would reasonably want to read back or restore. Do not build a parallel shadow model purely to satisfy the contract's shape. If the exhibit's real internal state cannot yet answer a question the contract needs answered (for example, "is Observation mode currently on"), add a real readable getter to the exhibit's own model rather than fabricating a separate value the adapter tracks independently. A contract adapter that keeps its own belief about exhibit state, disconnected from the exhibit's actual state, will eventually disagree with it.
**CONTRACT REQUIREMENT.** No-op writes must not produce a new `stateRevision` (Contract §12, §14.3).
**CONTRACT REQUIREMENT — transaction semantics.** One top-level contract-visible mutation is one mutation transaction. All contract-visible state changes committed by that transaction share one resulting `stateRevision`; the exhibit computes and commits the transaction, increments `stateRevision` exactly once if anything externally visible changed, and only then emits the resulting `state.changed`/`selection.changed` events carrying that revision (Contract §14).
**AUTHORING RECOMMENDATION.** When a single operation changes several values coherently — selecting a scene that also selects that scene's default preset is the canonical example — commit all of them together, increment the revision once, and emit every changed target's event carrying that same shared revision. Treat "how many logically-connected values changed as one gesture" as the boundary of a transaction, not "how many individual setter functions happened to run."
---
## G. Absolute Setters and Idempotency
**AUTHORING RECOMMENDATION**, reflecting how Contract 5.2's state model is meant to be exercised (Contract §12 defines `set` as changing writable persistent state to a given value, not toggling it): public setters for persistent state should be absolute and idempotent. Setting a value to its current value twice should leave the exhibit in exactly the same state, with no second state change and no second revision.
Good:
```text
set mute = true
set mode = "night"
set intensity = 0.5
```
Bad:
```text
toggle mute
click mute button
move slider +10%
```
An exhibit's native UI is free to use toggle buttons or relative gestures internally — that is a legitimate presentation choice, and native UI is exhibit-owned. But the canonical contract path for the same underlying state must convert any such relative or toggle-shaped local action into an absolute value before it reaches the canonical mutation service. A host, a scenario, or another integration needs to be able to say "make this true" without knowing or caring what the current state was, and a relative-only setter makes that impossible to do reliably.
---
## H. Canonical Mutation / Invoke Path
**AUTHORING RECOMMENDATION**, and the single most load-bearing pattern in this guide: route every externally visible state change and every externally visible action through one canonical internal path.
Host control, native UI, hotkeys, and any other local control surface should converge on the same underlying services wherever practically possible. Concretely, something shaped like:
```text
applyMutation(targetId, value, source, context)
invokeAction(targetId, args, source, context)
```
should be the only way contract-visible state changes or actions actually happen, regardless of what triggered them.
**AUTHORING RECOMMENDATION — avoid DOM-click proxying.** Do not use synthetic DOM events (dispatching a fake `click`, `input`, or `change` event to make something happen) as the canonical mechanism for state mutation. This pattern shows up naturally when a hotkey handler is implemented as "find the button that does this and click it programmatically" — it is easy to write and easy to get wrong. It breaks source attribution (the event now looks identical to a real user click, because the code path *is* the click handler), it can double-apply if both the synthetic dispatch and a canonical call happen, and it makes the actual mutation logic impossible to test or reason about independently of the DOM. This was, concretely, the root cause of a real hotkey source-attribution defect in SciFi-XZBT: two keyboard shortcuts synthesized a button click to trigger their action, which meant the resulting event reported `source=ui` no matter how the action was actually triggered. The fix was to extract the shared logic into one function that both the button's own listener and the hotkey handler call directly, each passing its own true source.
Direct business/state methods — not DOM proxies — should be authoritative. The DOM is a rendering of state, not a control bus.
---
## I. Native UI Integration
**AUTHORING RECOMMENDATION.**
- The UI must remain fully functional with no host attached — this is a restatement of Section B at the UI layer specifically.
- UI code should call the canonical services described in Section H, not reimplement mutation logic inline in an event handler.
- Changes originating from the UI should emit the same normalized events a host-originated change would, with `source=ui`.
- UI state and contract state must stay synchronized: if a host sets a value, the UI must faithfully reflect it, and if the UI changes a value, the contract's readable state must reflect that immediately.
- Host-set values must render in the native UI exactly as they would if a local operator had set them — the UI must not have a way to silently distinguish or discard a host-originated value it doesn't like the look of. If a native control cannot exactly represent a value (see the range lesson in Section E), it must not simply drop or revert that value on the next unrelated interaction.
**CONTRACT REQUIREMENT.** Not every public target needs a visible UI control (Contract §19: sound and visual actions "should be exposed as impulse targets where useful," which does not imply UI parity). A target may legitimately be external-only when it maps to real, intentional exhibit behavior that simply has no dedicated on-screen control — for example, an event that exists as a synthesis function the exhibit already performs in another context, exposed to hosts without inventing a redundant button for it. What is not acceptable is a target whose only path to actually happening is a UI element that does not exist; the target must resolve to a real, verified, callable action regardless of whether a button triggers it too.
---
## J. Source Attribution
**CONTRACT REQUIREMENT.** Normalized events must identify the authoritative source of the action when known (Contract §15). The base source values are `ui`, `midi`, `hotkey`, `host`, `scenario`, `internal`, and `system`. A `source` value supplied inside a host command must be ignored; the receiving bridge assigns the authoritative source, never the sender.
**AUTHORING RECOMMENDATION.** Assign source at the trusted boundary that actually knows the truth, never by trusting a caller's claim about itself. The button's own click listener knows it is `ui`. The keydown handler knows it is `hotkey`. The host transport, after validating a message came through the negotiated session, knows it is `host`. None of these should accept an override from the thing they're receiving input from.
Do not teach host-provided source passthrough as authoritative: a host cannot spoof local provenance, by design, because source is assigned by the exhibit's own receiving path, not read out of the incoming message.
For future external control routed through XZBT-NGN — MIDI is the concrete example, following its removal from the standalone exhibit in favor of NGN-owned integration — the exhibit sees these operations arrive as ordinary contract requests over its host transport, and correctly reports `source=host`, because from the exhibit's point of view that is exactly what they are. NGN is free to separately retain finer-grained provenance (which connector, which physical controller) on its own side of the boundary; that detail is an NGN concern and never needs to cross into the exhibit's own event stream as a new base source value.
---
## K. Events
**CONTRACT REQUIREMENT.** A conforming exhibit must publish normalized events for contract-visible operations. The base event types are:
```text
state.changed
action.executed
selection.changed
capability.changed
registry.changed
error
```
**CONTRACT REQUIREMENT — sequencing.** Event `sequence` must increase monotonically within one session and is session-scoped: it resets when a new `sessionId` is issued, and a host must reset its own expected sequence after a reconnect (Contract §16.1). `stateRevision` tracks committed persistent-state history and does not reset with a new session; `sequence` tracks delivery order of every emitted event, including impulses and errors. The two counters serve different purposes and must not be treated as interchangeable (Contract §14.4).
**CONTRACT REQUIREMENT.** Impulses may emit `action.executed` without changing `stateRevision`, because an impulse by definition is not persistent state (Contract §10.4, §14).
**CONTRACT REQUIREMENT — correlation.** When an event is caused directly by a host request, it should carry that request's `requestId` as `correlationId` (Contract §16.2).
**AUTHORING RECOMMENDATION.** Do not introduce new canonical event types beyond the base six without very strong justification, and never as a routine part of building a new exhibit — the base set is deliberately exhibit-generic and sufficient for state, selection, action, capability, registry, and error reporting across very different subject matter.
---
## L. Session and Transport Separation
**CONTRACT REQUIREMENT.** The contract defines semantics and normalized message envelopes, not one mandatory transport. Permitted transports include same-origin `postMessage`, trusted wrapper bindings, a local WebSocket transport, local application IPC, and future host-specific bindings (Contract §4). The exhibit must not need to know whether a request originated from a webhook, an automation tool, a simulator, a telemetry source, an administrative UI, a scenario engine, or any other integration — NGN translates all of these into contract operations before they ever reach the exhibit.
**AUTHORING RECOMMENDATION.** Keep session semantics — handshake, `sessionId` issuance, sequence tracking — cleanly separated from exhibit logic, so that swapping the transport later (say, from `postMessage` to a packaged in-process adapter, per the NGN packaging model) requires changing only the transport layer described in Section C, not the contract adapter or the exhibit itself. Do not let `postMessage`-specific assumptions (message event shape, origin checks) leak into the contract adapter's own logic; keep them isolated in the transport layer that happens to implement that one binding.
**CONTRACT REQUIREMENT — same-origin security.** Where `postMessage` is used, both `event.origin` and `event.source` must be validated against the expected host relationship on every message (Contract §25).
---
## M. Capabilities
**CONTRACT REQUIREMENT.** Capabilities are discoverable and stateful. The base lifecycle states are `unsupported`, `available`, `loading`, `ready`, `busy`, and `error` (Contract §17). A capability may change state during a session, and any such change must emit `capability.changed`. A host must treat the current discovered state as authoritative.
**AUTHORING RECOMMENDATION.** Do not advertise a capability that does not exist, and do not invent lifecycle transitions the exhibit cannot honestly report. Speech is a useful worked example: `available` should mean the exhibit implements speech at all; `loading` should reflect real initialization in progress (voice model loading, engine startup); `ready` should mean speech can actually be used right now; `error` should reflect a genuine initialization or operation failure. `busy` is optional — it describes ongoing occupancy (mid-playback, for instance) and is not required by the contract; wiring it costs real engineering effort (start/end hooks, interruption handling) for a state most hosts do not strictly need. It is entirely acceptable to wire `available → loading → ready/error` for a capability and leave `busy` unimplemented; what is not acceptable is a capability that is hard-coded to a static state that is never actually verified against real subsystem behavior. Not every capability needs to exercise every lifecycle state — use only the states that reflect something real for that capability.
---
## N. Contextual Availability
**CONTRACT REQUIREMENT.** When a required capability is not usable, a target remains discoverable but must not be operated successfully; a generic host should present it as unavailable rather than removing it (Contract §9). `registryRevision` should not change merely because a fixed target becomes contextually unavailable (Contract §23).
**AUTHORING RECOMMENDATION.** Keep discoverable and currently invokable as two distinct questions. A sound effect can remain permanently in the fixed catalog while returning `CAPABILITY_UNAVAILABLE` in the wrong mode or theme — that is normal, expected, and cheap for a host to handle. What must not happen is the registry itself churning (targets appearing and disappearing, `registryRevision` incrementing) merely because the exhibit switched modes; that is exactly the unnecessary rediscovery cost Section D warns against. Reserve `registryRevision` increments for cases where the target set or its descriptor metadata genuinely changed — not for ordinary state transitions the exhibit goes through as part of normal operation.
---
## O. Safe Text Handling
**CONTRACT REQUIREMENT.** Any text surface the exhibit exposes must treat text strictly as data (Contract §20). The contract must not permit arbitrary HTML, JavaScript, CSS, selectors, or executable expressions through a text target. Each text target should declare a maximum accepted length.
**AUTHORING RECOMMENDATION.** Use explicit, narrowly-typed text arguments in a target's schema rather than an open-ended payload object. Reject unknown argument keys (Contract §11: "unexpected argument keys should be rejected"). Render accepted text using safe mechanisms — `textContent` assignment or an equivalent that cannot be interpreted as markup — never `innerHTML` or an equivalent that would let a string become executable content. This applies equally to text arriving through a live contract message and text arriving embedded in scenario data (Section R): scenario data must remain inert, and no contract or scenario argument should ever be capable of causing arbitrary script execution inside the exhibit.
---
## P. Optional Telemetry
**CONTRACT REQUIREMENT.** Telemetry support is entirely optional; the contract defines how telemetry is represented if an exhibit exposes it, without requiring any exhibit to implement it (Contract §21).
**AUTHORING RECOMMENDATION.** Do not invent telemetry fields merely to look complete against the contract. A telemetry target should exist only when the exhibit actually has a meaningful structured value behind it — a real number, from a real subsystem, that changes in a way worth exposing. Manufacturing a `telemetry.*` target with no real backing state produces exactly the kind of describe-accuracy problem Section E warns against for ordinary targets. If the exhibit's current telemetry-like presentation is really just audio activity levels or scrolling text rather than a genuine structured numeric model, building real structured telemetry is a new feature to design deliberately, not a checkbox to tick during initial contract compliance.
Acquisition, mapping, unit conversion, smoothing, and staleness policy for telemetry sourced from something external to the exhibit are XZBT-NGN's responsibility, not the exhibit's (Contract §22, NGN Plan §2223). The exhibit's job is to declare what it can meaningfully receive or report and to behave sensibly according to its own declared contract when values arrive; it does not need to know or care where a telemetry value ultimately originated.
---
## Q. External Integrations
**CONTRACT REQUIREMENT.** The exhibit exposes what it can do; the contract defines how that is described, observed, and invoked; XZBT-NGN decides how to orchestrate and integrate it (Contract §30). NGN, not the exhibit, owns discovery, coordination, automation, external connectors, recording, and administration (Contract §3.2, NGN Plan §3, §40).
**AUTHORING RECOMMENDATION.** A standalone exhibit generally should not own MIDI, webhooks, external automation buses, game integrations, or other connector-specific logic. The architectural test to apply to any proposed piece of functionality, borrowed directly from the settled NGN/exhibit boundary: if it coordinates, automates, connects, authors, records, distributes, or externally controls the exhibit, it belongs primarily in XZBT-NGN. If it is required for the exhibit to remain a complete standalone experience, it belongs in the exhibit.
This is not an absolute prohibition — an integration that is truly intrinsic to what makes a specific exhibit a complete standalone artifact could still belong in the exhibit itself. But that should be a deliberate, justified exception, argued explicitly against the test above, not a default. When in doubt, expose a clean primitive on the exhibit side (a target that does the underlying thing) and let external integration logic live entirely in NGN, translating whatever external signal into a normal contract operation against that primitive. This is precisely the shape of the resolution used when MIDI-specific code was separated from the generic control-bus abstraction it had been bundled with: the generic bus mechanism is a legitimate exhibit-side primitive; the MIDI-specific binding and mapping logic is not.
---
## R. Scenarios
**CONTRACT REQUIREMENT.** The XZBT Exhibit Contract does not require a general-purpose scenario engine in the standalone exhibit; a scenario is an orchestration concept, and XZBT-NGN executes scenarios by issuing ordinary contract operations over time (Contract §26). Scenario data must remain inert and must never be interpreted as arbitrary executable code.
**AUTHORING RECOMMENDATION.** General scenario authoring, recording, and playback belong to XZBT-NGN, not the exhibit. What the exhibit should provide is a set of clean, controllable primitives — well-designed targets — that a scenario can drive; it does not need to know a scenario is happening at all, and from the exhibit's point of view a scenario-driven operation is just a `set` or `invoke` arriving through its host transport like any other.
A separately packaged exhibit build, produced by NGN for self-running distribution, may later include a minimal runtime that drives the exhibit's own canonical target surface from embedded scenario data (NGN Plan §2627). That compact runtime is still a consumer of the same contract surface the exhibit already exposes — it is not a reason to build a general scenario editor or player into every ordinary exhibit. Do not embed scenario authoring or playback tooling in the exhibit as a default; if a packaged build needs it, that tooling is added at packaging time by NGN's shared runtime core, not maintained as a permanent part of the standalone artifact.
---
## S. Packaging and Offline Operation
**AUTHORING RECOMMENDATION**, following directly from Section B: a standalone exhibit intended to run as a single distributable artifact should remain fully self-contained wherever that is the intended distribution shape — no server required for normal standalone use, no external script or stylesheet dependency that would break when opened offline or from a local file. Any optional network-dependent enhancement (checking for updates, an optional cloud feature) should fail gracefully and silently fall back to fully local behavior rather than degrading the core experience or throwing a visible error.
Packaging should preserve contract behavior: whatever process turns exhibit source into a distributable artifact should not need special-case handling to keep the contract adapter, canonical mutation path, or event surface intact — if packaging strips or reorders code in a way that breaks contract compliance, that is a packaging defect, not an acceptable tradeoff.
**Do not treat SciFi-XZBT's own specific offline/packaging policy (its particular single-file HTML packaging approach, or any Web3D-related constraint it happens to carry) as a universal requirement.** A different exhibit might reasonably ship as a small set of files, or run inside a different host shell entirely. The universal principle is graceful, complete standalone operation; the specific packaging mechanics are exhibit-owned implementation choices.
---
## T. Error Handling
**CONTRACT REQUIREMENT.** Implementations should use stable, machine-readable error codes rather than relying on message text (Contract §24). The recommended base codes are:
```text
UNSUPPORTED_VERSION
INVALID_MESSAGE
INVALID_SESSION
UNKNOWN_TARGET
INVALID_VALUE
INVALID_ARGUMENTS
CAPABILITY_UNAVAILABLE
TARGET_READ_ONLY
TARGET_NOT_INVOKABLE
TARGET_NOT_SETTABLE
INTERNAL_ERROR
```
Human-readable error text is advisory only; hosts should branch on codes, never on message strings.
**AUTHORING RECOMMENDATION.**
- Reject invalid input cleanly and immediately, with a specific code, rather than accepting it and behaving unpredictably.
- Never apply a partial mutation — if any part of a validated transaction cannot be committed, commit none of it.
- Do not silently clamp an out-of-range value to the nearest valid one unless the target's own descriptor explicitly documents clamping as its defined behavior; silent clamping without a declared contract for it produces exactly the kind of state/host disagreement Section E warns about.
- Never report success when the requested state did not actually change as requested. A response of `ok: true` is a claim that the operation was accepted for execution (Contract §11) — it must correspond to something real actually happening, not to the request merely being well-formed.
---
## U. Testing Strategy
**AUTHORING RECOMMENDATION.** A new exhibit's XZBT compliance should be exercised at several distinct levels, each catching a different class of defect:
1. **Synthetic/unit tests.** Exercise the canonical mutation/invoke path and the contract adapter directly, without a browser or a real host — target validation, no-op detection, revision increment logic, event shape.
2. **Contract adapter tests.** Verify `describe`, `state.get`, capability reporting, and error codes conform to the wire-level contract shape, independent of any specific transport.
3. **Real browser host tests.** Attach an actual host (or a minimal test harness acting as one) over the real transport and verify handshake, discovery, state read/write, invoke, and event delivery end to end.
4. **Native UI convergence tests.** Confirm that UI-originated changes and host-originated changes both flow through the same canonical path and produce equivalent, correctly-sourced events — this is where a lingering DOM-click proxy (Section H) or a UI/contract range mismatch (Section E) tends to surface.
5. **Standalone no-host tests.** Load the exhibit with no host attached at all and confirm every feature works exactly as it would with one — this is the direct verification of Section B's governing principle.
6. **Packaging tests.** Confirm the packaged/distributable artifact behaves identically to the development build with respect to contract compliance.
7. **Local-file/offline tests, where applicable.** For an exhibit intended to run as a double-clicked local file or fully offline, confirm it actually does — including any browser security constraints on `file://` access that a development environment's own tooling may not replicate.
A minimal conformance checklist for quick reference during development is provided in Appendix A.
---
## V. Reference Exhibit Expectations
**AUTHORING RECOMMENDATION**, describing what a later reference-exhibit pass (not undertaken here) should aim to demonstrate. Reference exhibits such as a train exposition or an aviation exposition should be:
- small — deliberately minimal in scope, not a showcase of every contract feature at once;
- intentionally simple — easy to read end to end as a worked example;
- fully standalone, per Section B;
- visibly different in subject domain from any existing exhibit, to prove the contract generalizes rather than merely repeating one domain's shape;
- contract-correct against every applicable MUST in the specification;
- free of any dependency on science-fiction-specific vocabulary or assumptions;
- useful primarily as an example implementation for future exhibit authors to read.
The point of a reference exhibit is to prove genericity, not to demonstrate feature richness. A train exhibit exposing `engine.throttle`, `brake.pressure`, `event.whistle`, and `telemetry.speed` (the exact example given in Contract §27) makes the architectural point far better than a large, feature-complete build would.
---
## W. Anti-Patterns
The following are recurring mistakes to avoid when building a new exhibit, several of them drawn directly from real defects found during SciFi-XZBT's contract verification:
- Using DOM element IDs, CSS selectors, or internal widget names as public contract target IDs.
- Implementing a persistent, toggle-shaped piece of state as a toggle-only contract action instead of an absolute setter.
- Maintaining a separate "shadow" adapter state that can drift from the exhibit's real internal state.
- Using synthetic DOM clicks or dispatched events as the canonical control-mutation mechanism.
- Trusting a host-supplied `source` value instead of assigning source at the trusted receiving boundary.
- Publishing a target in `describe` that has no real, verified, callable backing action in any context.
- Changing the registry, or incrementing `registryRevision`, on every ordinary mode or context switch.
- Embedding external integrations (MIDI, webhooks, third-party connectors) directly into the standalone exhibit by default.
- Making any part of ordinary standalone operation depend on XZBT-NGN being present.
- Putting arbitrary executable code, HTML, or expressions into scenario data or contract text arguments.
- Adding telemetry fields that have no real backing state, purely to look complete against the contract.
- Letting a native UI control accept or produce values outside the range its own public descriptor advertises.
- Silently reverting a committed host- or scenario-set value the next time a local control is touched.
---
## X. Recommended New-Exhibit Build Order
**AUTHORING RECOMMENDATION.** A practical sequence for building a new XZBT-compatible exhibit from scratch:
1. Build the exhibit's own standalone behavior first — visuals, audio, simulation, native UI — with no contract awareness at all.
2. Define stable internal state and services (layers 12 of Section C) that the exhibit's own UI already uses.
3. Define the canonical public target catalog (Section D) against that real internal model.
4. Implement absolute setters and idempotent actions (Section G) backing each target.
5. Route the native UI through the canonical services (Section H) so UI-originated and future host-originated changes share one path.
6. Add the contract adapter: descriptors, target metadata, capability model (Sections E, M).
7. Add discovery, state, and event surfaces: `describe`, `state.get`, `state.changed`/`action.executed`/etc. (Sections F, K).
8. Add the transport layer (Section L), keeping it isolated from exhibit and adapter logic.
9. Add capability reporting wired to real subsystem lifecycle, not static claims (Section M).
10. Run the conformance tests in Section U, including the checklist in Appendix A.
11. Test standalone and offline operation explicitly, with no host present at all.
12. Only after all of the above is solid, integrate with a real XZBT-NGN instance.
Building in this order keeps the exhibit correct and complete on its own at every step, and treats contract compliance as a layer added on top of a working exhibit rather than a scaffold the exhibit is built inside of.
---
## Y. Minimal Example Architecture
The following are short, conceptual, JavaScript-like pseudocode fragments illustrating the patterns above. They are not a working application and deliberately omit error handling detail already covered in Section T.
**Target registry (Section D, E):**
```js
const TARGET_REGISTRY = {
'transport.playing': { kind: 'state', valueType: 'boolean', readable: true, writable: true, restorable: true, category: 'transport', requires: [] },
'mix.master': { kind: 'range', min: 0, max: 1, step: 0.01, readable: true, writable: true, restorable: true, category: 'mix', requires: ['audio'] },
'mode.selected': { kind: 'selection', options: [{ value: 'day', label: 'Day' }, { value: 'night', label: 'Night' }], readable: true, writable: true, restorable: true, category: 'mode', requires: [] },
'event.pulse': { kind: 'impulse', readable: false, writable: false, restorable: false, category: 'event', requires: [] },
};
```
**Absolute setter (Section G):**
```js
function setMuted(on) {
if (exhibitState.muted === on) return { changed: false };
exhibitState.muted = on;
audioEngine.setMuted(on);
return { changed: true };
}
```
**Canonical mutation transaction (Section F, H):**
```js
function applyMutation(targetId, value, source) {
const before = snapshotRelevantState(targetId);
const result = dispatchToSetter(targetId, value); // calls setMuted(), etc. directly
if (!result.changed) return { ok: true, revisionChanged: false };
const changedTargets = diffState(before, snapshotRelevantState(targetId));
stateRevision += 1;
for (const t of changedTargets) {
emitEvent('state.changed', { target: t.id, value: t.value, stateRevision, source });
}
return { ok: true, revisionChanged: true };
}
```
**Impulse action (Section G, K):**
```js
function invokeAction(targetId, args, source) {
const descriptor = TARGET_REGISTRY[targetId];
if (!descriptor || descriptor.kind !== 'impulse') return { ok: false, code: 'TARGET_NOT_INVOKABLE' };
if (!capabilityReady(descriptor.requires)) return { ok: false, code: 'CAPABILITY_UNAVAILABLE' };
performRealAction(targetId, args); // no synthetic DOM dispatch
emitEvent('action.executed', { target: targetId, args, source });
return { ok: true };
}
```
**State snapshot (Section F):**
```js
function getStateSnapshot() {
const values = {};
for (const [id, d] of Object.entries(TARGET_REGISTRY)) {
if (d.readable && d.kind !== 'impulse') values[id] = readCurrentValue(id);
}
return { stateRevision, values };
}
```
**Event emission (Section K):**
```js
let sequence = 0;
function emitEvent(type, payload) {
sequence += 1;
hostTransport.send({ xzbt: '5.2', type, sessionId: currentSessionId, sequence, timestamp: Date.now(), ...payload });
}
```
**Host transport adapter (Section C, L):**
```js
window.addEventListener('message', (event) => {
if (event.source !== window.parent || !isTrustedOrigin(event.origin)) return;
const msg = event.data;
if (!isValidEnvelope(msg)) return respondError(msg, 'INVALID_MESSAGE');
if (msg.type !== 'hello' && msg.sessionId !== currentSessionId) return respondError(msg, 'INVALID_SESSION');
switch (msg.type) {
case 'hello': return handleHello(msg);
case 'describe': return respondDescribe(msg);
case 'state.get': return respondState(msg);
case 'set': return respond(msg, applyMutation(msg.target, msg.value, 'host'));
case 'invoke': return respond(msg, invokeAction(msg.target, msg.args, 'host'));
}
});
```
---
## Z. Final Author Checklist
Before calling a new exhibit XZBT-compatible, confirm:
- It runs completely, with full native behavior, with no host attached.
- Every public target ID is meaningful, lowercase-dotted, and free of DOM/widget-specific naming.
- Every descriptor reflects a real, verified backing action or state — nothing fake, nothing aspirational.
- Persistent setters are absolute and idempotent; setting the same value twice produces one state, zero extra revisions.
- All contract-visible mutation and invocation, from every input source, passes through one canonical path.
- No synthetic DOM dispatch is used as the mutation mechanism.
- Source is assigned only at the trusted receiving boundary, never trusted from the caller.
- One mutation transaction produces at most one `stateRevision` increment, and no-ops produce none.
- Native UI and public contract ranges represent one coherent operator-facing range.
- Capability states reflect real subsystem lifecycle, using only the states that are genuinely meaningful for that capability.
- The registry is fixed; context changes produce contextual errors, not registry churn.
- All text targets are handled as safe data, with declared length limits and no markup/script injection path.
- Telemetry, if present at all, is backed by real structured values — never fabricated for completeness.
- MIDI, webhooks, and other external integrations are not embedded in the exhibit by default.
- No general scenario authoring/recording/playback system has been built into the exhibit.
- Errors use stable machine-readable codes; no partial mutations; no silent clamping without a declared contract for it.
- The exhibit has been tested standalone, offline (where applicable), and through a real host, per Section U.
---
## Appendix A: Minimal Conformance Checklist
- [ ] Handshake: exhibit responds to `hello` with a session, or `UNSUPPORTED_VERSION` when incompatible.
- [ ] `describe` returns identity, contract version, `registryRevision`, `stateRevision`, capabilities, and targets.
- [ ] At least one discoverable target of each kind actually in use (state/range/selection/impulse).
- [ ] `state.get` returns only readable persistent targets, matching `describe`.
- [ ] `set` on a persistent target is idempotent; a no-op set does not change `stateRevision`.
- [ ] `invoke` on an impulse target executes a real action and emits `action.executed`.
- [ ] One transaction, one revision: a multi-value change increments `stateRevision` exactly once.
- [ ] Events carry monotonic `sequence`, reset on new session; `stateRevision` does not reset with session.
- [ ] `source` is accurate for `ui`, `hotkey`, and `host` origins in real, physically-triggered tests.
- [ ] Capability states reflect real initialization/readiness, not static placeholders.
- [ ] Unknown target, invalid value, invalid session, and invalid arguments each return the correct distinct error code.
- [ ] `postMessage` transport (if used) validates both `event.origin` and `event.source`.
- [ ] The exhibit runs fully standalone with no host, network, or server present.
- [ ] The packaged/distributable build behaves identically to the development build for all of the above.
## Appendix B: Recommended Project Structure
This is an illustrative layout, not a required one — see Section C for the underlying separation of concerns it expresses.
```text
/exhibit-root
index.html # entry point; loads with no host required
/js
model.js # exhibit state (layer 1)
services.js # exhibit behavior / mutation logic (layer 2)
ui.js # native UI, calls canonical services (layer 3)
canonical-control.js # applyMutation / invokeAction chokepoint (layer 4)
contract-adapter.js # describe/state.get/events/capabilities (layer 5)
host-transport.js # postMessage or other binding (layer 6)
/css
style.css
/assets
...
```
A single-file packaged build may inline all of the above; the separation should still exist logically within the source even when the distributable artifact is one file.
## Appendix C: Glossary
**XZBT Exhibit Contract** — the versioned specification defining the message envelope, target model, revisions, events, capabilities, and error codes an exhibit and a host share.
**XZBT-compatible exhibit** — any exhibit implementing the XZBT Exhibit Contract.
**Standalone exhibit** — an exhibit's normal, complete, independently usable form, with no host attached.
**XZBT-NGN Exhibit Engine** — the separate orchestration, integration, recording, authoring, and administration layer that connects to one or more exhibits through the contract; never required for standalone exhibit operation.
**Canonical target** — a publicly registered, dotted-ID addressable unit of exhibit state or action.
**State** — a target kind representing persistent value (boolean, string, or bounded scalar).
**Range** — a target kind representing persistent bounded numeric state, with `min`/`max`/`step`.
**Selection** — a target kind representing a persistent value chosen from a declared set of options.
**Impulse** — a target kind representing a non-persistent action or event; never part of a restorable snapshot.
**Capability** — a discoverable, stateful description of subsystem availability (`unsupported`/`available`/`loading`/`ready`/`busy`/`error`).
**stateRevision** — a monotonically increasing counter over committed persistent-state history, incremented at most once per mutation transaction.
**registryRevision** — a counter over the current set and metadata of discoverable targets; should not change for ordinary contextual availability shifts.
**Sequence** — a per-session, monotonically increasing counter over all emitted events, reset on new session.
**Session** — the negotiated context, established by `hello`, under which all other contract requests after the handshake must carry a valid `sessionId`.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,865 @@
# SciFi-XZBT XZBT Contract Implementation Plan
## Version 5.2
**Status:** Proposed implementation plan
**Product:** SciFi-XZBT
**Contract:** XZBT Exhibit Contract 5.2
**Primary goal:** Make the existing standalone exhibit contract-addressable without turning it into XZBT-NGN.
---
## 1. Purpose
SciFi-XZBT will implement the XZBT Exhibit Contract while remaining a self-contained standalone HTML exhibit.
Version 5.2 is grounded in the v5.1 codebase review and deliberately corrects the remaining ambiguity around:
- current control-bus inventory;
- absolute state versus toggles;
- mutation/event emission;
- state revision semantics;
- observation versus viewport-frame control;
- universe/preset behavior;
- display ticker behavior;
- host validation;
- packaging-runtime compatibility.
The implementation should extend the real application foundation rather than create a parallel control system.
---
## 2. Standalone Guarantee
SciFi-XZBT MUST continue to operate normally without:
- XZBT-NGN;
- a host handshake;
- a web server;
- webhooks;
- external telemetry;
- scenario files;
- external automation.
The contract layer is dormant when no host is attached.
---
## 3. Existing Verified Foundation and Verification Boundary
The current codebase is known to include:
- `XZBTControlBus`;
- MIDI routing through that bus;
- a set of range controls;
- a set of action controls;
- speech/TTS;
- universe/preset setters;
- universe events;
- soundboard actions;
- Observation mode;
- an Observation viewport-frame toggle;
- existing bus listeners;
- no general host bridge;
- no general scenario/recording system;
- no structured fictional telemetry model.
### 3.1 Control-bus inventory safeguard
Review materials conflict on the exact total count.
One review states **22 targets: 14 range + 8 action**, but the same review explicitly names only seven action targets:
```text
master-play
fnc-enable
watch-experience
observation-viewport
red-alert
generate-announcement
mute
```
Therefore v5.2 MUST NOT hard-code 21 or 22 as a normative implementation fact.
Phase 0 MUST enumerate the actual `register()` / `rangeTarget()` calls in the target commit and produce the authoritative inventory.
The implementation plan below accounts for the known named targets and requires any additional verified target to be added before coding proceeds.
---
## 4. Scope
Version 5.2 implementation includes:
- canonical external target registry;
- direct adapters to existing setters where possible;
- new absolute setters where required;
- `describe`;
- target descriptors;
- state snapshot;
- state revision;
- event stream;
- source normalization;
- capability reporting;
- host attachment;
- explicit speech target;
- sound/event exposure;
- universe/preset exposure;
- safe ticker text exposure;
- compatibility with current MIDI and native UI behavior.
Structured telemetry remains optional and does not block first conformance.
---
## 5. Explicitly Out of Scope
The normal SciFi-XZBT HTML will not gain:
- scenario authoring;
- general scenario file loading;
- scenario recording;
- scenario library management;
- timeline editing;
- webhook endpoints;
- third-party connectors;
- Streamer.bot integration;
- Internet-facing authentication;
- remote administration;
- external telemetry acquisition;
- scenario scheduling;
- scenario chaining;
- host-side queue management;
- NGN administration;
- casting server functionality.
These remain XZBT-NGN responsibilities.
---
## 6. Canonical External Target Registry
The external registry is the single source of truth for:
- `describe`;
- validation;
- state readers;
- state writers;
- event target IDs;
- NGN discovery.
Existing internal IDs are not renamed.
### 6.1 Known range mappings
The following known range targets are pure external-to-internal mappings in concept, but their current synthetic-DOM application path must be replaced by direct setter calls during convergence.
```text
mix.master -> master-vol
mix.hull.level -> hull-vol
mix.hull.frequency -> hull-freq
mix.hull.cutoff -> hull-cutoff
mix.drive.level -> warp-vol
mix.drive.pulse-rate -> warp-bpm
mix.drive.carrier -> warp-carrier
mix.environment.level -> air-vol
mix.environment.cutoff -> air-cutoff
mix.telemetry.level -> telemetry-vol
mix.telemetry.density -> telemetry-density
speech.robot-amount -> ai-robot-amount
fnc.level -> fnc-level
view.activity -> observation-activity
```
Phase 0 MUST confirm all 14 names against the repository before implementation.
### 6.2 Known action/state adapters
These are not simple renames:
```text
transport.playing
transport.muted
fnc.enabled
view.observation
view.viewport-frame
event.alert-red
alert.active
speech.say
```
Known internal candidates include:
```text
master-play
mute
fnc-enable
watch-experience
observation-viewport
red-alert
generate-announcement
```
The external semantics are defined below and MUST NOT be implemented as blind DOM clicks.
### 6.3 Universe and preset selection
Add new canonical targets:
```text
universe.selected
preset.selected
```
These wrap the existing verified universe/preset setter functions.
They are new contract registrations, not mappings of pre-existing bus targets.
---
## 7. Absolute State Semantics
Persistent contract state MUST be idempotent.
### 7.1 Transport playing
Add:
```text
setPlaying(on, source)
```
It MUST:
- read current playing state;
- do nothing on a no-op;
- await startup/shutdown transition completion;
- not silently drop a request because a transition is already in progress;
- surface failure to the contract adapter.
### 7.2 Transport muted
Add:
```text
setMuted(on, source)
```
It MUST be absolute and idempotent.
### 7.3 FNC enabled
Use the existing absolute FNC setter directly where verified.
Do not synthesize a DOM click.
### 7.4 Observation mode
`view.observation` means entering or exiting the overall Observation experience.
Use existing absolute enter/exit functions where available.
Add a readable getter for current Observation state.
### 7.5 Viewport frame
`view.viewport-frame` means the viewport/bezel-frame state inside Observation.
It is separate from `view.observation`.
### 7.6 Alert
`event.alert-red` is an impulse.
`alert.active` is persistent state, preferably a selection:
```text
none
red
yellow
```
The two semantics MUST remain separate.
---
## 8. Fixed Target Catalog Decision
SciFi-XZBT v5.2 adopts a **fixed canonical target catalog**.
Universe-specific actions and sound effects remain discoverable across universe changes.
If a target is not valid in the current context, an attempt to invoke it returns `CAPABILITY_UNAVAILABLE` or another appropriate context error.
A universe change SHOULD NOT remove and re-add large sections of the target registry.
Therefore `registryRevision` remains stable unless target definitions themselves actually change.
This prevents unnecessary NGN re-description churn.
---
## 9. Describe Implementation
Add a contract description provider containing:
- exhibit identity;
- build/version metadata;
- contract version;
- registry revision;
- state revision;
- capability descriptors;
- target descriptors.
The canonical external registry is the source of truth.
Descriptors MUST NOT be duplicated in separate hand-maintained tables.
---
## 10. State Snapshot
Create one authoritative function that returns contract-visible persistent state.
Initial expected fields include:
```text
transport.playing
transport.muted
mix.master
mix.hull.level
mix.hull.frequency
mix.hull.cutoff
mix.drive.level
mix.drive.pulse-rate
mix.drive.carrier
mix.environment.level
mix.environment.cutoff
mix.telemetry.level
mix.telemetry.density
speech.robot-amount
fnc.enabled
fnc.level
view.observation
view.viewport-frame
view.activity
alert.active
universe.selected
preset.selected
```
Phase 0 must verify each read path.
Known required additions include:
- readable Observation state;
- canonical universe getter;
- canonical preset getter;
- canonical alert-state reader if not already directly exposed.
---
## 11. Universe and Preset Behavior
When `universe.selected` changes:
1. validate the universe ID against the real registry;
2. apply the universe;
3. select that universe's defined default preset;
4. commit both values in one mutation transaction;
5. emit the resulting selection/state events with one resulting `stateRevision`.
The system MUST NOT leave `preset.selected` pointing at a preset invalid for the current universe.
Unknown universe or preset IDs return `INVALID_VALUE`.
---
## 12. Mutation Chokepoint
All contract-visible persistent mutations MUST pass through one canonical mutation service in `js/app.js`.
Recommended conceptual API:
```text
applyMutation(targetId, value, source, context)
```
The implementation may use a class or object rather than this exact function name, but there MUST be one authoritative mutation path.
Responsibilities:
- validate target and value;
- call the direct underlying setter;
- detect no-op writes;
- commit one mutation transaction;
- increment `stateRevision` once when state changes;
- emit normalized state/selection events;
- preserve authoritative source;
- return success/failure.
The existing `XZBTControlBus.listeners` mechanism should be reused or extended as an event-subscription foundation where practical.
---
## 13. Remove Synthetic-DOM Mutation as the Canonical Path
Existing range bus application currently reaches real setters by assigning DOM values and dispatching synthetic `input` / `change` events.
Version 5.2 MUST separate programmatic state mutation from DOM event simulation.
The canonical mutation path MUST call the real setter directly.
UI listeners should call the canonical mutation path, not the other way around.
This prevents double application and makes source attribution reliable.
---
## 14. UI, MIDI, Hotkey, and Host Convergence
Required architecture:
```text
UI
MIDI
Hotkey
Host Contract
Scenario-originated local runtime
|
v
Canonical Mutation / Invoke Service
|
v
Existing SciFi-XZBT subsystem
```
Every path that changes contract-visible state MUST use the canonical service.
Every path that performs a contract-visible impulse MUST use the canonical invoke service.
The phrase "where practical" is removed for contract-visible behavior.
---
## 15. State Revision
SciFi-XZBT follows the contract's mutation-transaction rule.
One top-level mutation transaction produces at most one new `stateRevision`.
A no-op produces no revision increment.
The revision increments after commit and before resulting events are emitted.
Universe changes plus default-preset selection are one transaction.
Preset application may update multiple exposed values in one transaction.
---
## 16. Event Surface
SciFi-XZBT MUST emit normalized events.
At minimum:
```text
state.changed
action.executed
selection.changed
capability.changed
registry.changed
error
```
Each event includes:
- sequence;
- timestamp;
- target where applicable;
- value or args;
- authoritative source;
- state revision where applicable;
- correlation ID when directly caused by a host request.
Session event sequence resets on new contract session.
---
## 17. Source Normalization
Use:
```text
ui
midi
hotkey
host
scenario
internal
system
```
A host-supplied `source` is ignored.
The bridge assigns `host`.
Packaged scenario runtime assigns `scenario`.
Existing MIDI routing retains `midi`.
---
## 18. Speech
Expose:
```text
speech.say
```
This calls the existing explicit `speak(text)` implementation.
Do not confuse it with "generate announcement."
If generated-announcement behavior is exposed separately, it receives a distinct impulse target such as:
```text
speech.generate-announcement
```
SciFi-XZBT remains responsible for:
- TTS loading;
- preferred engine;
- fallback synthesis;
- selected voice;
- robotic voice processing;
- spoken-unit normalization;
- interruption behavior;
- playback.
### 18.1 Speech capability state
Add a readable capability state field to the generative/speech subsystem.
Use:
```text
available
loading
ready
busy
error
```
Use `unsupported` only when speech is not implemented at all.
### 18.2 Queue behavior
Current behavior interrupts prior speech.
Preserve that behavior in the first implementation.
A successful `speech.say` means accepted and started, not completed.
### 18.3 Length limit
Set a conservative maximum accepted text length.
Recommended first limit:
```text
2000 characters
```
This limit is an implementation constant and may be tuned later.
Over-limit requests return `INVALID_VALUE`.
---
## 19. Sound Effects
Build a verified stable public inventory from the current soundboard and synthesis dispatch.
Do not automatically expose every internal synthesis helper.
The fixed external catalog may include targets that are contextually unavailable in the current universe.
Unavailable invocation returns a defined error instead of removing the target.
---
## 20. Universe Events
Build a verified stable public event catalog from current universe `events[]` definitions and actual dispatch behavior.
Do not treat examples as authoritative inventory.
Keep persistent alert state separate from alert impulses.
---
## 21. Display Ticker
Expose:
```text
display.ticker
```
as a **transient write**.
Semantics:
- text is immediately displayed;
- it remains subject to normal exhibit ticker replacement;
- the contract does not claim persistence;
- the exhibit SHOULD emit a state/action event indicating the accepted write;
- a future persistent-host-caption feature would use a different target.
Use safe `textContent`-style assignment.
Recommended maximum:
```text
512 characters
```
Over-limit input returns `INVALID_VALUE`.
Do not invent `display.status` or `display.transient` until real surfaces exist.
---
## 22. Telemetry
The current codebase does not yet have structured fictional display telemetry fields.
Version 5.2 keeps two stages.
### Stage A - Contract readiness
Ensure future `telemetry.*` targets can be added cleanly.
### Stage B - New exhibit feature
Later create real structured telemetry fields with meaningful visible representation.
Only then advertise writable telemetry capabilities.
Structured telemetry is not required for first conformance.
---
## 23. Host Attachment
Initial host attachment uses:
1. same-origin parent/frame `postMessage`;
2. optional explicit trusted wrapper binding.
The bridge MUST validate:
- `event.origin`;
- `event.source`;
- contract version;
- session;
- message type;
- target;
- value type;
- numeric bounds;
- args schema;
- unexpected args keys;
- selection membership;
- capability state;
- text length limits.
The base exhibit does not automatically connect to remote services.
No webhook listener belongs in the HTML.
---
## 24. Capability Model
Initial capabilities may include:
```text
audio
speech
midi
observation
display-text
```
Do not advertise telemetry-write until it exists.
A target's `requires` list references these capability IDs.
Context-specific target unavailability does not require capability removal.
---
## 25. Registry Revision
Because v5.2 uses a fixed canonical target catalog, ordinary universe changes SHOULD NOT change `registryRevision`.
Increment it only when:
- a target is added or removed;
- target descriptor metadata changes;
- option metadata genuinely changes in a way that changes the registry contract.
Use contextual availability errors rather than registry churn where practical.
---
## 26. Build and Packaging
The existing PowerShell packaging process remains authoritative for the ordinary single-file standalone artifact.
Contract integration MUST preserve the packaging script's expected include patterns.
Ordinary free packaging remains independent of XZBT-NGN.
Future NGN scenario packaging is separate.
---
## 27. Implementation Phases
### Phase 0 - Repository verification
Produce an authoritative inventory from the target commit.
Required output:
- every control-bus registration;
- all internal IDs;
- range/action counts;
- direct backing setter;
- current source paths;
- universe/preset accessors;
- event/sound catalogs;
- package-script assumptions.
Do not proceed while target count remains disputed.
### Phase 1 - Canonical registry
Create the single source of truth for canonical external target descriptors and internal adapters.
### Phase 2 - Direct setters and absolute state
Implement direct range mutation and absolute setters for toggle-like state.
### Phase 3 - Mutation service
Implement the canonical mutation/invoke chokepoint and transaction semantics.
### Phase 4 - State and discovery
Implement:
- `describe`;
- `state.get`;
- `stateRevision`;
- `registryRevision`;
- capabilities.
### Phase 5 - Input convergence
Rewrite all contract-visible UI, MIDI, and hotkey mutation paths to call the canonical service.
This is a substantial refactor, not a small wiring task.
No contract-visible inline listener may bypass the canonical mutation path at phase completion.
### Phase 6 - Event normalization
Publish normalized events with source, sequence, revision, and correlation.
### Phase 7 - Speech, SFX, universe events, ticker
Expose verified exhibit functions through canonical targets.
### Phase 8 - Host bridge
Implement same-origin messaging and validation.
### Phase 9 - Standalone verification
Confirm complete application behavior without any host.
### Phase 10 - Optional structured telemetry
Only after intentionally designing real telemetry fields.
---
## 28. First Contract-Compatible Acceptance Criteria
The first compatible release succeeds when:
- SciFi-XZBT still runs normally by itself;
- a test host completes handshake;
- a test host calls `describe`;
- the authoritative target inventory matches the repository;
- persistent state can be read;
- representative ranges can be set directly;
- toggle-like targets are absolute and idempotent;
- setting a boolean state to its current value twice leaves the same state;
- universe changes select a valid default preset;
- Observation and viewport-frame controls are distinct;
- representative sound and visual impulses can be invoked;
- `speech.say` accepts explicit text;
- `display.ticker` behaves according to its transient contract;
- UI, MIDI, hotkey, and host changes emit normalized events;
- event source is accurate;
- session sequence and state revision work;
- no-op sets do not increment revision;
- malformed commands fail safely;
- unexpected origins are ignored;
- no scenario engine or webhook system has been added.
Structured telemetry is not required.
---
## 29. Future Telemetry Acceptance Criteria
When structured telemetry is later added:
- each field has a real visible or meaningful exhibit representation;
- metadata is discoverable;
- read-only and writable fields are explicit;
- external ownership prevents immediate simulation overwrite;
- stale or released ownership returns cleanly to simulation;
- NGN remains responsible for acquiring real telemetry.
---
## 30. Architectural Test
When considering a future feature:
**Does SciFi-XZBT need this to remain a complete standalone exhibit?**
If yes, it may belong in SciFi-XZBT.
**Does the feature coordinate, automate, connect, record, author, distribute, or externally control the exhibit?**
If yes, it belongs primarily in XZBT-NGN.
---
## 31. Version 5.2 Summary
Version 5.2 does not change the Version 5 architecture.
It makes the SciFi-XZBT implementation plan concrete enough to begin coding after Phase 0 verification.
The implementation must normalize the real application rather than pretending that existing DOM-click proxies and synthetic events already satisfy the contract.